>", parser.fullMatch());
}
}
};
macros.endsilently = {
handler: function () {}
};
version.extensions.choiceMacro = {
major: 2,
minor: 0,
revision: 0
};
macros.choice = {
callback: function() {
var i, other, passage = findPassageParent(this);
if (passage) {
other = passage.querySelectorAll(".choice");
for (i = 0; i < other.length; i++) {
other[i].outerHTML = "" + other[i].innerHTML + "";
}
state.history[0].variables["choice clicked"][passage.id.replace(/\|[^\]]*$/,'')] = true;
}
},
handler: function (A, C, D, parser) {
var link, id, match,
text = D[1] || D[0].split("|")[0],
passage = findPassageParent(A);
// Get ID of the "choice clicked" entry
if (!passage) {
throwError(A, "<<"+C+">> can't be used here.",parser.fullMatch());
return;
}
id = (passage && passage.id.replace(/\|[^\]]*$/,''));
if (id && (state.history[0].variables["choice clicked"] ||
(state.history[0].variables["choice clicked"] = {}))[id]) {
insertElement(A, "span", null, "disabled", text);
}
else {
match = new RegExp(Wikifier.linkFormatter.lookahead).exec(parser.fullMatch());
if (match) {
link = Wikifier.linkFormatter.makeLink(A,match,this.callback);
}
else {
link = Wikifier.linkFormatter.makeLink(A,[0,text,D[0]],this.callback);
}
link.className += " " + C;
}
}
};
version.extensions.backMacro = {
major: 2,
minor: 0,
revision: 0
};
macros.back = {
labeltext: '« back',
handler: function (a, b, e, parser) {
var labelParam, c, el,
labeltouse = this.labeltext,
steps = 1,
stepsParam = e.indexOf("steps"),
stepsParam2 = "";
// Steps parameter
if(stepsParam > 0) {
stepsParam2 = e[stepsParam - 1];
if(stepsParam2[0] == '$') {
try {
stepsParam2 = internalEval(Wikifier.parse(stepsParam2));
}
catch(r) {
throwError(a, parser.fullMatch() + " bad expression: " + r.message, parser.fullMatch())
return;
}
}
// Previously, trying to go back more steps than were present in the
// history would silently revert to just 1 step.
// Instead, let's just go back to the start.
steps = +stepsParam2;
if(steps >= state.history.length - 1) {
steps = state.history.length - 2;
}
e.splice(stepsParam - 1, 2);
}
// Label parameter
labelParam = e.indexOf("label");
if(labelParam > -1) {
if(!e[labelParam + 1]) {
throwError(a, parser.fullMatch() + ": " + e[labelParam] + ' keyword needs an additional label parameter', parser.fullMatch());
return;
}
labeltouse = e[labelParam + 1];
e.splice(labelParam, 2);
}
// What's left is the passage name parameter
if(stepsParam <= 0) {
if(e[0]) {
if(e[0].charAt(0) == '$') {
try {
e = internalEval(Wikifier.parse(e[0]));
}
catch(r) {
throwError(a, parser.fullMatch() + " bad expression: " + r.message, parser.fullMatch())
return;
}
}
else {
e = e[0];
}
if(!tale.has(e)) {
throwError(a, "The \"" + e + "\" passage does not exist",parser.fullMatch());
return;
}
for(c = 0; c < state.history.length; c++) {
if(state.history[c].passage.title == e) {
steps = c;
break;
}
}
}
}
el = document.createElement("a");
el.className = b;
addClickHandler(el, (function(b) { return function () {
return macros.back.onclick(b == "back", steps, el)
}}(b)));
el.innerHTML = labeltouse;
a.appendChild(el);
}
};
version.extensions.returnMacro = {
major: 2,
minor: 0,
revision: 0
};
macros["return"] = {
labeltext: '« return',
handler: function(a,b,e) {
macros.back.handler.call(this,a,b,e);
}
};
version.extensions.textInputMacro = {
major: 2,
minor: 0,
revision: 0
};
macros.checkbox = macros.radio = macros.textinput = {
handler: function (A, C, D, parser) {
var match,
class_ = C.replace('input','Input'),
q = A.querySelectorAll('input'),
id = class_ + "|" + ((q && q.length) || 0);
input = insertElement(null, 'input', id, class_);
input.name=D[0];
input.type=C.replace('input','');
// IE 8 support - delay insertion until now
A.appendChild(input);
if (C == "textinput" && D[1]) {
match = new RegExp(Wikifier.linkFormatter.lookahead).exec(parser.fullMatch());
if (match) {
Wikifier.linkFormatter.makeLink(A,match, macros.button.callback, 'button');
}
else {
Wikifier.linkFormatter.makeLink(A,[0,(D[2] || D[1]),D[1]], macros.button.callback, 'button');
}
}
else if ((C == "radio" || C == "checkbox") && D[1]) {
input.value = D[1];
insertElement(A, 'label','', '', D[1]).setAttribute('for',id);
if (D[2]) {
insertElement(A,'br');
D.splice(1,1);
macros[C].handler(A,C,D)
}
}
}
};
version.extensions.buttonMacro = {
major: 1,
minor: 0,
revision: 0
};
macros.button = {
callback: function() {
var el = findPassageParent(this);
if (el) {
var inputs = el.querySelectorAll("input");
for (i = 0; i < inputs.length; i++) {
if (inputs[i].type!="checkbox" && (inputs[i].type!="radio" || inputs[i].checked)) {
macros.set.run(null, Wikifier.parse(inputs[i].name+' = "'+inputs[i].value.replace(/"/g,'\\"')+'"'));
}
else if (inputs[i].type=="checkbox" && inputs[i].checked) {
macros.set.run(null, Wikifier.parse(
inputs[i].name+' = [].concat('+inputs[i].name+' || []);'));
macros.set.run(null, Wikifier.parse(
inputs[i].name+'.push("'+inputs[i].value.replace(/"/g,'\\"')+'")'));
}
}
}
},
handler: function (A, C, D, parser) {
var link,
match = new RegExp(Wikifier.linkFormatter.lookahead).exec(parser.fullMatch());
if (match) {
Wikifier.linkFormatter.makeLink(A, match, this.callback, 'button');
}
else {
Wikifier.linkFormatter.makeLink(A,[0,D[1] || D[0], D[0]], this.callback, 'button');
}
}
};
/*
**
** Passage object
**
*/
function Passage(c, b, a, ofunc) {
var t;
if (!this || this.constructor != Passage) {
throw new ReferenceError("passage() must be in lowercase");
}
this.title = c;
ofunc = typeof ofunc == 'function' && ofunc;
if (b) {
this.id = a;
// Load tags
this.tags = b.getAttribute("tags");
if (typeof this.tags == "string") {
if (ofunc) {
this.tags = ofunc(this.tags);
}
this.tags = this.tags.readBracketedList();
} else this.tags = [];
// Load text
t = b.firstChild ? b.firstChild.nodeValue : "";
if (ofunc && !this.isImage()) {
this.text = ofunc(Passage.unescapeLineBreaks(t));
} else {
this.text = Passage.unescapeLineBreaks(t);
}
// Preload linked images
if (!this.isImage()) {
this.preloadImages();
}
// Check for the .char selector, or the [data-char] selector
// False positives aren't a big issue.
if (/\.char\b|\[data\-char\b/.exec(this.text) && Wikifier.charSpanFormatter) {
Wikifier.formatters.push(Wikifier.charSpanFormatter);
delete Wikifier.charSpanFormatter;
}
} else {
this.text = '@@This passage does not exist: ' + c + '@@';
this.tags = [];
}
}
Passage.prototype.isImage = function() {
return !!~(this.tags.indexOf("Twine.image"));
};
Passage.prototype.preloadImages = function() {
// Don't preload URLs containing '$' - suspect that they are variables.
var u = "\\s*['\"]?([^\"'$]+\\.(jpe?g|a?png|gif|bmp|webp|svg))['\"]?\\s*",
k = function(c, e) {
var i,d;
do {
d = c.exec(this.text);
if(d) {
i = new Image();
i.src = d[e];
}
} while (d);
return k;
};
k.call(this, new RegExp(Wikifier.imageFormatter.lookahead.replace("[^\\[\\]\\|]+",u), "mg"), 4)
.call(this, new RegExp("url\\s*\\(" + u + "\\)", "mig"), 1)
.call(this, new RegExp("src\\s*=" + u, "mig"), 1);
};
Passage.unescapeLineBreaks = function (a) {
if (a && typeof a == "string") {
return a.replace(/\\n/mg, "\n").replace(/\\t/mg, "\t").replace(/\\s/mg, "\\").replace(/\\/mg, "\\").replace(/\r/mg, "")
} else {
return ""
}
};
Passage.prototype.setTags = function(b) {
var t = this.tags != null && this.tags.length ? this.tags.join(' ') : "";
if (t) {
b.setAttribute('data-tags', this.tags.join(' '));
}
document.body.setAttribute("data-tags", t);
};
Passage.prototype.processText = function() {
var ret = this.text;
if (~this.tags.indexOf("nobr")) {
ret = ret.replace(/\n/g,"\u200c");
}
if (this.isImage()) {
ret = "[img[" + ret + "]]"
}
return ret;
};
/*
**
** Tale object
**
*/
function Tale() {
var a,b,c,lines,i,kv,nsc,isImage,
settings = this.storysettings = {
lookup: function(a, dfault) {
// The two runtime settings (undo and bookmark) default to true.
if (!(a in this)) return dfault;
return (this[a]+"") != "off";
}
},
HTMLtitle = document.querySelector('title');
tiddlerTitle = '';
this.defaultTitle = (HTMLtitle && (HTMLtitle.textContent || HTMLtitle.innerText)) || "Untitled Story";
this.passages = {};
//Look for and load the StorySettings
if (document.normalize) document.normalize();
a = document.getElementById("storeArea").children;
for (b = 0; b < a.length; b++) {
c = a[b];
if (c.getAttribute && c.getAttribute("tiddler") == 'StorySettings') {
lines = new Passage('StorySettings', c, 0, null, null).text.split('\n');
for (i in lines) {
if (typeof lines[i] == "string" && lines[i].indexOf(':') > -1) {
kv = lines[i].toLowerCase().split(':');
kv[0] = kv[0].replace(/^\s+|\s+$/g, '');
kv[1] = kv[1].replace(/^\s+|\s+$/g, '');
if (kv[0] != "lookup") {
settings[kv[0]] = kv[1];
}
}
}
}
}
//Load in the passages
if (settings.obfuscate == 'rot13') {
for (b = 0; b < a.length; b++) {
c = a[b];
if (c.getAttribute && (tiddlerTitle = c.getAttribute("tiddler"))) {
isImage = (c.getAttribute("tags")+"").indexOf("Twine.image")>-1;
if (tiddlerTitle != 'StorySettings' && !isImage)
tiddlerTitle = rot13(tiddlerTitle);
this.passages[tiddlerTitle] = new Passage(tiddlerTitle, c, b+1, !isImage && rot13);
}
}
} else {
for (b = 0; b < a.length; b++) {
c = a[b];
if (c.getAttribute && (tiddlerTitle = c.getAttribute("tiddler"))) {
this.passages[tiddlerTitle] = new Passage(tiddlerTitle, c, b, null, null)
}
}
}
}
Tale.prototype.has = function (a) {
if (typeof a == "string") {
return (this.passages[a] != null)
} else {
for (var i in this.passages) {
if (this.passages[i].id == a) {
return true
}
}
return false
}
};
Tale.prototype.get = function (a) {
if (typeof a == "string") {
return this.passages[a] || new Passage(a)
} else {
for (var i in this.passages) {
if (this.passages[i].id == a) {
return this.passages[i]
}
}
}
};
Tale.prototype.lookup = function (h, g, a) {
var d = [];
for (var c in this.passages) {
var f = this.passages[c];
for (var b = 0; b < f[h].length; b++) {
if (f[h][b] == g) {
d.push(f)
}
}
}
if (!a) {
a = "title"
}
d.sort(function (k, j) {
if (k[a] == j[a]) {
return (0)
} else {
return (k[a] < j[a]) ? -1 : +1
}
});
return d
};
Tale.prototype.canUndo = function() {
return this.storysettings.lookup('undo',true);
};
Tale.prototype.identity = function () {
var meta = document.querySelector("meta[name='identity']"),
identity = meta ? meta.getAttribute("content") : "story";
return (Tale.prototype.identity = function() {
return identity;
})();
};
Tale.prototype.forEachStylesheet = function(tags, callback) {
var passage, i;
tags = tags || [];
if (typeof callback != "function")
return;
for (passage in this.passages) {
passage = tale.passages[passage];
if (passage && ~passage.tags.indexOf("stylesheet")) {
for (i = 0; i < tags.length; i++) {
if (~passage.tags.indexOf(tags[i])) {
callback(passage);
break;
}
}
}
}
};
Tale.prototype.setPageElements = function() {
var storyTitle;
setPageElement("storyTitle", "StoryTitle", this.defaultTitle);
storyTitle = document.getElementById("storyTitle");
document.title = this.title = (storyTitle && (storyTitle.textContent || storyTitle.innerText)) || this.defaultTitle;
setPageElement("storySubtitle", "StorySubtitle", "");
if (tale.has("StoryAuthor")) {
setPageElement("titleSeparator", null, "\n");
setPageElement("storyAuthor", "StoryAuthor", "");
}
if (tale.has("StoryMenu")) {
document.getElementById("storyMenu").setAttribute("style","");
setPageElement("storyMenu", "StoryMenu", "");
}
};
/*
**
** Wikifier object
**
*/
function Wikifier(place, source) {
this.source = source;
this.output = place;
this.nextMatch = 0;
this.assembleFormatterMatches(Wikifier.formatters);
this.subWikify(this.output);
}
Wikifier.textPrimitives = {
upperLetter: "[A-Z\u00c0-\u00de\u0150\u0170]",
lowerLetter: "[a-z\u00df-\u00ff_0-9\\-\u0151\u0171]",
anyLetter: "[A-Za-z\u00c0-\u00de\u00df-\u00ff_0-9\\-\u0150\u0170\u0151\u0171]"
}
Wikifier.textPrimitives.variable = "\\$((?:"+Wikifier.textPrimitives.anyLetter.replace("\\-", "")+"*"+
Wikifier.textPrimitives.anyLetter.replace("0-9\\-", "")+"+"+
Wikifier.textPrimitives.anyLetter.replace("\\-", "")+"*)+)";
Wikifier.textPrimitives.unquoted = "(?=(?:[^\"'\\\\]*(?:\\\\.|'(?:[^'\\\\]*\\\\.)*[^'\\\\]*'|\"(?:[^\"\\\\]*\\\\.)*[^\"\\\\]*\"))*[^'\"]*$)";
Wikifier.prototype.assembleFormatterMatches = function (formatters) {
this.formatters = [];
var pattern = [];
for (var n = 0; n < formatters.length; n++) {
pattern.push("(" + formatters[n].match + ")");
this.formatters.push(formatters[n]);
};
this.formatterRegExp = new RegExp(pattern.join("|"), "mg");
};
Wikifier.prototype.subWikify = function (output, terminator) {
// Temporarily replace the output pointer
var terminatorMatch, formatterMatch, oldOutput = this.output;
this.output = output;
// Prepare the terminator RegExp
var terminatorRegExp = terminator ? new RegExp("(" + terminator + ")", "mg") : null;
do {
// Prepare the RegExp match positions
this.formatterRegExp.lastIndex = this.nextMatch;
if (terminatorRegExp) terminatorRegExp.lastIndex = this.nextMatch;
// Get the first matches
formatterMatch = this.formatterRegExp.exec(this.source);
terminatorMatch = terminatorRegExp ? terminatorRegExp.exec(this.source) : null;
// Check for a terminator match
if (terminatorMatch && (!formatterMatch || terminatorMatch.index <= formatterMatch.index)) {
// Output any text before the match
if (terminatorMatch.index > this.nextMatch) this.outputText(this.output, this.nextMatch, terminatorMatch.index);
// Set the match parameters
this.matchStart = terminatorMatch.index;
this.matchLength = terminatorMatch[1].length;
this.matchText = terminatorMatch[1];
this.nextMatch = terminatorMatch.index + terminatorMatch[1].length;
// Restore the output pointer and exit
this.output = oldOutput;
return;
}
// Check for a formatter match
else if (formatterMatch) {
// Output any text before the match
if (formatterMatch.index > this.nextMatch) this.outputText(this.output, this.nextMatch, formatterMatch.index);
// Set the match parameters
this.matchStart = formatterMatch.index;
this.matchLength = formatterMatch[0].length;
this.matchText = formatterMatch[0];
this.nextMatch = this.formatterRegExp.lastIndex;
// Figure out which formatter matched
var matchingFormatter = -1;
for (var t = 1; t < formatterMatch.length; t++) {
if (formatterMatch[t]) {
matchingFormatter = t - 1;
break;
}
}
// Call the formatter
if (matchingFormatter != -1) { this.formatters[matchingFormatter].handler(this); }
}
}
while (terminatorMatch || formatterMatch);
// Output any text after the last match
if (this.nextMatch < this.source.length) {
this.outputText(this.output, this.nextMatch, this.source.length);
this.nextMatch = this.source.length;
}
// Restore the output pointer
this.output = oldOutput;
};
Wikifier.prototype.outputText = function (place, startPos, endPos) {
if (place) {
insertText(place, this.source.substring(startPos, endPos));
}
};
Wikifier.prototype.fullMatch = function() {
return this.source.slice(this.matchStart, this.source.indexOf('>>', this.matchStart)+2);
};
Wikifier.prototype.fullArgs = function (includeName) {
var source = this.source.replace(/\u200c/g," "),
endPos = this.nextMatch-2,
startPos = source.indexOf(includeName ? '<<' : ' ', this.matchStart);
if (!~startPos || !~endPos || endPos <= startPos) {
return "";
}
return Wikifier.parse(source.slice(startPos + (includeName ? 2 : 1), endPos).trim());
};
Wikifier.parse = function (input) {
var m, re, b = input, found = [],
g = Wikifier.textPrimitives.unquoted;
function alter(from,to) {
b = b.replace(new RegExp(from+g,"gim"),to);
return alter;
}
// Extract all the variables, and set them to 0 if undefined.
re = new RegExp(Wikifier.textPrimitives.variable+g,"gi");
while (m = re.exec(input)) {
if (!~found.indexOf(m[0])) {
// This deliberately contains a 'null or undefined' check
b = m[0]+" == null && ("+m[0]+" = 0);"+b;
found.push(m[0]);
}
}
alter(Wikifier.textPrimitives.variable, "state.history[0].variables.$1")
// Old operators
("\\beq\\b", " == ")
("\\bneq\\b", " != ")
("\\bgt\\b", " > ")
("\\bgte\\b", " >= ")
("\\blt\\b", " < ")
("\\blte\\b", " <= ")
("\\band\\b", " && ")
("\\bor\\b", " || ")
("\\bnot\\b", " ! ")
// New operators
("\\bis\\b", " == ")
("\\bto\\b", " = ");
return b
};
Wikifier.formatHelpers = {
charFormatHelper: function (a) {
var b = insertElement(a.output, this.element);
a.subWikify(b, this.terminator)
},
inlineCssHelper: function (w) {
var s, v, lookaheadMatch, gotMatch,
styles = [],
lookahead = Wikifier.styleByCharFormatter.lookahead,
lookaheadRegExp = new RegExp(lookahead, "mg"),
hadStyle = false,
unDash = function (str) {
var s = str.split("-");
if (s.length > 1) for (var t = 1; t < s.length; t++)
s[t] = s[t].substr(0, 1).toUpperCase() + s[t].substr(1);
return s.join("");
};
styles.className = "";
do {
lookaheadRegExp.lastIndex = w.nextMatch;
lookaheadMatch = lookaheadRegExp.exec(w.source);
gotMatch = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
if (gotMatch) {
hadStyle = true;
if (lookaheadMatch[5]) {
styles.className += lookaheadMatch[5].replace(/\./g," ") + " ";
} else if (lookaheadMatch[1]) {
s = unDash(lookaheadMatch[1]);
v = lookaheadMatch[2];
} else {
s = unDash(lookaheadMatch[3]);
v = lookaheadMatch[4];
}
switch (s) {
case "bgcolor":
s = "backgroundColor";
break;
case "float":
s = "cssFloat";
break
}
styles.push({
style: s,
value: v
});
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
} while (gotMatch);
return styles;
},
monospacedByLineHelper: function (w) {
var lookaheadRegExp = new RegExp(this.lookahead, "mg");
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source);
if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
insertElement(w.output, "pre", null, null, lookaheadMatch[1]);
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
}
};
Wikifier.formatters = [
{
name: "table",
match: "^\\|(?:[^\\n]*)\\|(?:[fhc]?)$",
lookahead: "^\\|([^\\n]*)\\|([fhc]?)$",
rowTerminator: "\\|(?:[fhc]?)$\\n?",
cellPattern: "(?:\\|([^\\n\\|]*)\\|)|(\\|[fhc]?$\\n?)",
cellTerminator: "(?:\\x20*)\\|",
rowTypes: {
"c": "caption",
"h": "thead",
"": "tbody",
"f": "tfoot"
},
handler: function (w) {
var rowContainer, rowElement,lookaheadMatch, matched,
table = insertElement(w.output, "table"),
lookaheadRegExp = new RegExp(this.lookahead, "mg"),
currRowType = null,
nextRowType,
prevColumns = [],
rowCount = 0;
w.nextMatch = w.matchStart;
do {
lookaheadRegExp.lastIndex = w.nextMatch;
lookaheadMatch = lookaheadRegExp.exec(w.source),
matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
if (matched) {
nextRowType = lookaheadMatch[2];
if (nextRowType != currRowType) rowContainer = insertElement(table, this.rowTypes[nextRowType]);
currRowType = nextRowType;
if (currRowType == "c") {
if (rowCount == 0) rowContainer.setAttribute("align", "top");
else rowContainer.setAttribute("align", "bottom");
w.nextMatch = w.nextMatch + 1;
w.subWikify(rowContainer, this.rowTerminator);
} else {
rowElement = insertElement(rowContainer, "tr");
this.rowHandler(w, rowElement, prevColumns);
}
rowCount++;
}
} while (matched);
},
rowHandler: function (w, e, prevColumns) {
var cellMatch, matched, col = 0,
currColCount = 1,
cellRegExp = new RegExp(this.cellPattern, "mg");
do {
cellRegExp.lastIndex = w.nextMatch;
cellMatch = cellRegExp.exec(w.source);
matched = cellMatch && cellMatch.index == w.nextMatch;
if (matched) {
if (cellMatch[1] == "~") {
var last = prevColumns[col];
if (last) {
last.rowCount++;
last.element.setAttribute("rowSpan", last.rowCount);
last.element.setAttribute("rowspan", last.rowCount);
last.element.valign = "center";
}
w.nextMatch = cellMatch.index + cellMatch[0].length - 1;
} else if (cellMatch[1] == ">") {
currColCount++;
w.nextMatch = cellMatch.index + cellMatch[0].length - 1;
} else if (cellMatch[2]) {
w.nextMatch = cellMatch.index + cellMatch[0].length;
break;
} else {
var spaceLeft = false,
spaceRight = false,
lastColCount, lastColElement, styles, cell, t;
w.nextMatch++;
styles = Wikifier.formatHelpers.inlineCssHelper(w);
while (w.source.substr(w.nextMatch, 1) == " ") {
spaceLeft = true;
w.nextMatch++;
}
if (w.source.substr(w.nextMatch, 1) == "!") {
cell = insertElement(e, "th");
w.nextMatch++;
} else cell = insertElement(e, "td");
prevColumns[col] = {
rowCount: 1,
element: cell
};
lastColCount = 1;
lastColElement = cell;
if (currColCount > 1) {
cell.setAttribute("colSpan", currColCount);
cell.setAttribute("colspan", currColCount);
currColCount = 1;
}
for (t = 0; t < styles.length; t++)
cell.style[styles[t].style] = styles[t].value;
w.subWikify(cell, this.cellTerminator);
if (w.matchText.substr(w.matchText.length - 2, 1) == " ") spaceRight = true;
if (spaceLeft && spaceRight) cell.align = "center";
else if (spaceLeft) cell.align = "right";
else if (spaceRight) cell.align = "left";
w.nextMatch = w.nextMatch - 1;
}
col++;
}
} while (matched);
}
},
{
name: "rule",
match: "^----$\\n?",
handler: function (w) {
insertElement(w.output, "hr");
}
},
{
name: "emdash",
match: "--",
becomes: String.fromCharCode(8212),
handler: function (a) {
insertElement(a.output, "span", null, "char", this.becomes).setAttribute("data-char","emdash");
}
},
{
name: "heading",
match: "^!{1,5}",
terminator: "\\n",
handler: function (w) {
var e = insertElement(w.output, "h" + w.matchLength);
w.subWikify(e, this.terminator);
}
},
{
name: "monospacedByLine",
match: "^\\{\\{\\{\\n",
lookahead: "^\\{\\{\\{\\n((?:^[^\\n]*\\n)+?)(^\\}\\}\\}$\\n?)",
handler: Wikifier.formatHelpers.monospacedByLineHelper
},
{
name: "quoteByBlock",
match: "^<<<\\n",
terminator: "^<<<\\n",
handler: function (w) {
var e = insertElement(w.output, "blockquote");
w.subWikify(e, this.terminator);
}
},
{
name: "list",
match: "^(?:(?:\\*+)|(?:#+))",
lookahead: "^(?:(\\*+)|(#+))",
terminator: "\\n",
handler: function (w) {
var newType, newLevel, t, len, bulletType, lookaheadMatch, matched,
lookaheadRegExp = new RegExp(this.lookahead, "mg"),
placeStack = [w.output],
currType = null,
currLevel = 0;
w.nextMatch = w.matchStart;
do {
lookaheadRegExp.lastIndex = w.nextMatch;
lookaheadMatch = lookaheadRegExp.exec(w.source);
matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
if (matched) {
newLevel = lookaheadMatch[0].length;
if (lookaheadMatch[1]) {
bulletType = lookaheadMatch[1].slice(-1);
newType = "ul";
}
else if (lookaheadMatch[2]) {
newType = "ol";
}
w.nextMatch += newLevel;
if (newLevel > currLevel) {
for (t = currLevel; t < newLevel; t++) {
placeStack.push(insertElement(placeStack[placeStack.length - 1], newType));
}
} else if (newLevel < currLevel) {
for (t = currLevel; t > newLevel; t--)
placeStack.pop();
} else if (newLevel == currLevel && newType != currType) {
placeStack.pop();
placeStack.push(insertElement(placeStack[placeStack.length - 1], newType));
}
currLevel = newLevel;
currType = newType;
t = insertElement(placeStack[placeStack.length - 1], "li");
// Currently unused
if (bulletType && bulletType != "*") {
t.setAttribute("data-bullet", bulletType);
}
w.subWikify(t, this.terminator);
}
} while (matched);
}
},
(Wikifier.urlFormatter = {
name: "urlLink",
match: "(?:https?|mailto|javascript|ftp|data):[^\\s'\"]+(?:/|\\b)",
handler: function (w) {
var e = Wikifier.createExternalLink(w.output, w.matchText);
w.outputText(e, w.matchStart, w.nextMatch);
}
}),
(Wikifier.linkFormatter = {
name: "prettyLink",
match: "\\[\\[",
lookahead: "\\[\\[([^\\|]*?)(?:\\|(.*?))?\\](?:\\[(.*?)\])?\\]",
makeInternalOrExternal: function(out,title,callback,type) {
if (title && !tale.has(title) && (title.match(Wikifier.urlFormatter.match,"g") || ~title.search(/[\.\\\/#]/)))
return Wikifier.createExternalLink(out, title, callback, type);
else
return Wikifier.createInternalLink(out, title, callback, type);
},
// This base callback executes the code in a setter
makeCallback: function(code,callback) {
return function() {
macros.set.run(null, Wikifier.parse(code), null, code);
typeof callback == "function" && callback.call(this);
}
},
makeLink: function(out, match, callback2, type) {
var link, title, callback;
if (match[3]) { // Code
callback = this.makeCallback(match[3],callback2);
}
else {
typeof callback2 == "function" && (callback = callback2);
}
title = Wikifier.parsePassageTitle(match[2] || match[1]);
link = this.makeInternalOrExternal(out,title,callback, type);
setPageElement(link, null, match[2] ? match[1] : title);
return link;
},
handler: function (w) {
var lookaheadRegExp = new RegExp(this.lookahead, "mg");
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source)
if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
this.makeLink(w.output, lookaheadMatch)
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
}
}),
(Wikifier.imageFormatter = {
name: "image",
match: "\\[(?:[<]{0,1})(?:[>]{0,1})[Ii][Mm][Gg]\\[",
lookahead: "\\[([<]?)(>?)img\\[(?:([^\\|\\]]+)\\|)?([^\\[\\]\\|]+)\\](?:\\[([^\\]]*)\\]?)?(\\])",
importedImage: function(img, passageName) {
var imgPassages, imgname;
// Try to parse it as a variable
try {
imgname = internalEval(Wikifier.parse(passageName));
}
catch(e) {
}
if (!imgname) {
imgname = passageName;
}
// Base64 passage transclusion
imgPassages = tale.lookup("tags", "Twine.image");
for (j = 0; j < imgPassages.length; j++) {
if (imgPassages[j].title == imgname) {
img.src = imgPassages[j].text;
break;
}
}
},
handler: function (w) {
var e, img, j, lookaheadMatch,
lookaheadRegExp = new RegExp(this.lookahead, "mig");
lookaheadRegExp.lastIndex = w.matchStart;
lookaheadMatch = lookaheadRegExp.exec(w.source);
if (lookaheadMatch && lookaheadMatch.index == w.matchStart)
{
e = w.output, title = Wikifier.parsePassageTitle(lookaheadMatch[5])
if (title) {
e = Wikifier.linkFormatter.makeInternalOrExternal(w.output, title);
}
img = insertElement(e, "img");
if (lookaheadMatch[1]) img.align = "left";
else if (lookaheadMatch[2]) img.align = "right";
if (lookaheadMatch[3]) img.title = lookaheadMatch[3];
// Setup the image if it's referencing an image passage.
this.importedImage(img,lookaheadMatch[4]);
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
}
}),
{
name: "macro",
match: "<<",
lookahead: /<<([^>\s]+)(?:\s*)((?:\\.|'(?:[^'\\]*\\.)*[^'\\]*'|"(?:[^"\\]*\\.)*[^"\\]*"|[^'"\\>]|>(?!>))*)>>/mg,
handler: function (w) {
var lookaheadRegExp = new RegExp(this.lookahead);
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source.replace(/\u200c/g,'\n'));
if (lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1]) {
var params = lookaheadMatch[2].readMacroParams();
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
var name = lookaheadMatch[1];
try {
var macro = macros[name];
if (macro && typeof macro == "object" && macro.handler) {
macro.handler(w.output, name, params, w);
}
// Variable?
else if (name[0] == '$') {
macros.print.handler(w.output, name, [name].concat(params), w);
}
// Passage
else if (tale.has(name)) {
macros.display.handler(w.output, name, [name].concat(params), w);
}
else throwError(w.output, 'No macro or passage called "' + name + '"', w.fullMatch());
} catch (e) {
throwError(w.output, 'Error executing macro ' + name + ': ' + e.toString(), w.fullMatch());
}
}
}
},
{
name: "html",
match: "",
lookahead: "((?:.|\\n)*?)",
handler: function (w) {
var lookaheadRegExp = new RegExp(this.lookahead, "mg");
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source)
if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
var e = insertElement(w.output, "span");
e.innerHTML = lookaheadMatch[1];
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
}
},
{
name: "commentByBlock",
match: "/%",
lookahead: "/%((?:.|\\n)*?)%/",
handler: function (w) {
var lookaheadRegExp = new RegExp(this.lookahead, "mg");
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source)
if (lookaheadMatch && lookaheadMatch.index == w.matchStart) w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
},
{
name: "boldByChar",
match: "''",
terminator: "''",
element: "strong",
handler: Wikifier.formatHelpers.charFormatHelper
},
{
name: "strikeByChar",
match: "==",
terminator: "==",
element: "strike",
handler: Wikifier.formatHelpers.charFormatHelper
},
{
name: "underlineByChar",
match: "__",
terminator: "__",
element: "u",
handler: Wikifier.formatHelpers.charFormatHelper
},
{
name: "italicByChar",
match: "//",
terminator: "//",
element: "em",
handler: Wikifier.formatHelpers.charFormatHelper
},
{
name: "subscriptByChar",
match: "~~",
terminator: "~~",
element: "sub",
handler: Wikifier.formatHelpers.charFormatHelper
},
{
name: "superscriptByChar",
match: "\\^\\^",
terminator: "\\^\\^",
element: "sup",
handler: Wikifier.formatHelpers.charFormatHelper
},
{
name: "monospacedByChar",
match: "\\{\\{\\{",
lookahead: "\\{\\{\\{((?:.|\\n)*?)\\}\\}\\}",
handler: function (w) {
var lookaheadRegExp = new RegExp(this.lookahead, "mg");
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source)
if (lookaheadMatch && lookaheadMatch.index == w.matchStart) {
var e = insertElement(w.output, "code", null, null, lookaheadMatch[1]);
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
}
},
(Wikifier.styleByCharFormatter = {
name: "styleByChar",
match: "@@",
terminator: "@@",
lookahead: "(?:([^\\(@]+)\\(([^\\)\\|\\n]+)(?:\\):))|(?:([^\\.:@]+):([^;\\|\\n]+);)|(?:\\.([^;\\|\\n]+);)",
handler: function (w) {
var e = insertElement(w.output, "span", null, null, null);
var styles = Wikifier.formatHelpers.inlineCssHelper(w);
if (styles.length == 0) {
e.className = "marked";
}
else {
for (var t = 0; t < styles.length; t++) {
e.style[styles[t].style] = styles[t].value;
}
if (typeof styles.className == "string") {
e.className = styles.className;
}
}
w.subWikify(e, this.terminator);
}
}),
{
name: "lineBreak",
match: "\\n",
handler: function (w) {
insertElement(w.output, "br");
}
},
{
name: "continuedLine",
match: "\\\\\\s*?\\n",
handler: function(a) {
a.nextMatch = a.matchStart+2;
}
},
{
name: "htmlCharacterReference",
match: "(?:(?:?[a-zA-Z0-9]{2,8};|.)(?:?(?:x0*(?:3[0-6][0-9a-fA-F]|1D[c-fC-F][0-9a-fA-F]|20[d-fD-F][0-9a-fA-F]|FE2[0-9a-fA-F])|0*(?:76[89]|7[7-9][0-9]|8[0-7][0-9]|761[6-9]|76[2-7][0-9]|84[0-3][0-9]|844[0-7]|6505[6-9]|6506[0-9]|6507[0-1]));)+|?[a-zA-Z0-9]{2,8};)",
handler: function(w)
{
var el = document.createElement("div");
el.innerHTML = w.matchText;
insertText(w.output, el.textContent);
}
},
{
name: "htmltag",
match: "<(?:\\/?[\\w\\-]+|[\\w\\-]+(?:(?:\\s+[\\w\\-]+(?:\\s*=\\s*(?:\\\".*?\\\"|'.*?'|[^'\\\">\\s]+))?)+\\s*|\\s*)\\/?)>",
tagname: "<(\\w+)",
voids: ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"],
tableElems: ["table","thead","tbody","tfoot","th","tr","td","colgroup","col","caption","figcaption"],
cleanupTables: function (e) {
var i, name, elems = [].slice.call(e.children);
// Remove non-table child elements that aren't children of | s
for (i = 0; i < elems.length; i++) {
if (elems[i].tagName) {
name = elems[i].tagName.toLowerCase();
if (this.tableElems.indexOf(name)==-1) {
elems[i].outerHTML = '';
}
else if (['col','caption','figcaption','td','th'].indexOf(name)==-1) {
this.cleanupTables.call(this,elems[i]);
}
}
}
},
handler: function (a) {
var tmp, passage, setter, e, isvoid, isstyle, lookaheadRegExp, lookaheadMatch, lookahead,
re = new RegExp(this.tagname).exec(a.matchText),
tn = re && re[1] && re[1].toLowerCase();
if(tn && tn != "html") {
lookahead = "<\\/\\s*" + tn + "\\s*>";
isvoid = (this.voids.indexOf(tn) != -1);
isstyle = tn == "style" || tn == "script";
lookaheadRegExp = new RegExp(lookahead, "mg");
lookaheadRegExp.lastIndex = a.matchStart;
lookaheadMatch = lookaheadRegExp.exec(a.source);
if (lookaheadMatch || isvoid) {
if (isstyle) {
e = document.createElement(tn);
e.type = "text/css"; // IE8 compatibility
tmp = a.source.slice(a.nextMatch, lookaheadMatch.index);
e.styleSheet ? (e.styleSheet.cssText = tmp) : (e.innerHTML = tmp);
a.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
else {
// Creating a loose | element creates it wrapped inside a
element.
e = document.createElement(a.output.tagName);
e.innerHTML = a.matchText;
while(e.firstChild) {
e = e.firstChild;
}
if (!isvoid) {
a.subWikify(e, lookahead);
}
}
if (e.tagName.toLowerCase() == 'table') {
this.cleanupTables.call(this,e);
}
// Special data-setter attribute
if (setter = e.getAttribute("data-setter")) {
setter = Wikifier.linkFormatter.makeCallback(setter);
}
// Special data-passage attribute
if (passage = e.getAttribute("data-passage")) {
if (tn != "img") {
addClickHandler(e, Wikifier.linkFunction(Wikifier.parsePassageTitle(passage), e, setter));
if (tn == "area" || tn == "a") {
e.setAttribute("href", "javascript:;");
}
}
else {
Wikifier.imageFormatter.importedImage(e, passage);
}
}
a.output.appendChild(e);
} else {
throwError(a.output,"HTML tag '"+tn+"' wasn't closed.", a.matchText);
}
}
}
}];
// Optional - included if the ".char" selector appears anywhere in the story.
Wikifier.charSpanFormatter = {
name: "char",
match: "[^\n]",
handler: function (a) {
// Line breaks do NOT get their own charspans
insertElement(a.output, "span", null, "char", a.matchText).setAttribute("data-char", a.matchText == " " ? "space" :
a.matchText == "\t" ? "tab" : a.matchText);
}
};
Wikifier.parsePassageTitle = function(title) {
if (title && !tale.has(title)) {
try {
title = (internalEval(this.parse(title)) || title)+"";
}
catch(e) {}
}
return title;
};
Wikifier.linkFunction = function(title, el, callback) {
return function() {
if (state.rewindTo) {
var passage = findPassageParent(el);
if (passage && passage.parentNode.lastChild != passage) {
state.rewindTo(passage, true);
}
}
if (title) {
state.display(title, el, null, callback);
}
else if (typeof callback == "function") {
callback();
}
};
};
Wikifier.createInternalLink = function (place, title, callback, type) {
var tag = (type == "button" ? 'button' : 'a'),
suffix = (type == "button" ? "Button" : "Link"),
el = insertElement(place, tag);
if (tale.has(title)) {
el.className = 'internal'+suffix;
if (visited(title)) el.className += ' visited'+suffix;
}
else el.className = 'broken'+suffix;
addClickHandler(el, Wikifier.linkFunction(title, el, callback));
if (place) place.appendChild(el);
return el;
};
Wikifier.createExternalLink = function (place, url, callback, type) {
var tag = (type == "button" ? 'button' : 'a'),
el = insertElement(place, tag);
el.href = url;
el.className = "external"+(type == "button" ? "Button" : "Link");
el.target = "_blank";
if (typeof callback == "function") {
addClickHandler(el,callback);
}
if (place) place.appendChild(el);
return el;
};
// Functions usable solely by custom scripts
// This includes restart(), above.
function visited(e) {
var ret = 0, i = 0;
if (!state) {
return 0;
}
e = e || state.history[0].passage.title;
if (arguments.length > 1) {
for (ret = state.history.length; i 1) {
for (i = arguments.length-1; i >= 1; i--) {
ret = ret.concat(tags(arguments[i]));
}
}
ret = ret.concat(tale.get(e).tags);
return ret;
}
function previous() {
if (state && state.history[1]) {
for (var d = 1; d < state.history.length && state.history[d].passage; d++) {
if (state.history[d].passage.title != state.history[0].passage.title) {
return state.history[d].passage.title
}
}
}
return ""
}
// A random integer function
// 1 argument: random int from 0 to a inclusive
// 2 arguments: random int from a to b inclusive (order irrelevant)
function random(a, b) {
var from, to;
if (!b) {
from = 0;
to = a;
} else {
from = Math.min(a, b);
to = Math.max(a, b);
}
to += 1;
return ~~((Math.random() * (to - from))) + from;
}
function either() {
if (Array.isArray(arguments[0]) && arguments.length == 1) {
return either.apply(this,arguments[0]);
}
return arguments[~~(Math.random()*arguments.length)];
}
function parameter(n) {
n = n || 0;
if (macros.display.parameters[n]) {
return macros.display.parameters[n];
}
return 0
}
function bookmark() {
return state.hash || "#";
}
/* Used to eval within Twine, but outside the context of a particular function */
function internalEval(s) {
// eval("{x:1,y:1}") fails miserably unless the obj. literal
// is not the first expression in the line (hence, "0,").
return eval("0,"+s);
}
/* Used to execute script passages */
function scriptEval(s) {
try {
eval(s.text);
} catch (e) {
alert("There is a technical problem with this " + tale.identity() + " (" + s.title + ": " + e.message + ")."+softErrorMessage);
}
}
/* Unload prompt */
window.onbeforeunload = function() {
if (tale && tale.storysettings.lookup("exitprompt",false) && state && state.history.length > 1) {
return "You are about to end this " + tale.identity() + ".";
}
};
/* Error reporting */
var oldOnError = window.onerror || null,
softErrorMessage = "You may be able to continue playing, but some parts may not work properly.";
window.onerror = function (msg, a, b, c, error) {
var s = (error && (".\n\n" + error.stack.replace(/\([^\)]+\)/g,'') + "\n\n")) || (" (" + msg + ").\n");
alert("Sorry to interrupt, but this " + ((tale && tale.identity && tale.identity()) || "page") + "'s code has got itself in a mess" + s + softErrorMessage.slice(1));
// Restore previous onerror
window.onerror = oldOnError;
if (typeof window.onerror == "function") {
window.onerror(msg, a, b, c, error);
}
};
/* Init function */
var $;
function main() {
// Used by old custom scripts.
// Cedes to jQuery if it exists.
// Since jQuery is inserted after this script element,
// wait until main() is called before doing this.
$ = window.$ || function(a) {
return (typeof a == "string" ? document.getElementById(a) : a);
}
var imgs, scripts, macro, style, i,
styleText = "",
passages = document.getElementById("passages");
// Run checks after custom macros are installed
function sanityCheck(thing) {
var i, j, s = "NOTE: The " + thing,
checks = { prerender: prerender, postrender: postrender, macros: macros };
// Warn if prerender/postrender/macros aren't objects, and
// prerender/postrender's own properties aren't functions.
for (i in checks) {
if (Object.prototype.hasOwnProperty.call(checks, i) && !sanityCheck[i]) {
if (!checks[i] || typeof checks[i] != "object") {
alert(s + " seems to have corrupted the " + i + " object."+softErrorMessage);
sanityCheck[i] = true;
continue;
}
if (i != "macros") {
for (j in checks[i]) {
if (Object.prototype.hasOwnProperty.call(checks[i], j)
&& typeof checks[i][j] != "function") {
alert(s + " added a property '" + j + "' to " + i + ", "
+"which is a "+typeof checks[i][j]+", not a function."+softErrorMessage);
sanityCheck[i] = true;
break;
}
}
}
}
}
}
// Check for basic compatibility
if (!window.JSON || !document.querySelector) {
return (passages.innerHTML = "This " + tale.identity() + " requires a newer web browser. Sorry.");
} else {
passages.innerHTML = "";
}
// Initialise Tale
tale = window.tale = new Tale();
// Check for IE8 image compatibility
if (~document.documentElement.className.indexOf("lt-ie9")) {
imgs = tale.lookup("tags", "Twine.image");
for (i = 0; i < imgs.length; i++) {
if (imgs[i].text.length >= 32768) {
alert("NOTE: This " + tale.identity() + "'s HTML file contains embedded images that may be too large for this browser to display."+softErrorMessage);
break;
}
}
}
// Run all script passages
scripts = tale.lookup("tags", "script");
for (i = 0; i < scripts.length; i++) {
scriptEval(scripts[i]);
sanityCheck('script passage "'+scripts[i].title+'"');
}
// Need to create state now, since it's used by remember.init()
state = window.state = new History();
for (i in macros) {
macro = macros[i];
if (typeof macro.init == "function") {
macro.init();
sanityCheck('init() of the custom macro "'+i+'"');
}
}
// Run all stylesheet passages
style = document.getElementById("storyCSS");
for (i in tale.passages) {
i = tale.passages[i];
if (i.tags.indexOf("stylesheet")==-1) {
continue;
}
// No other tags?
if (i.tags + "" == "stylesheet") {
styleText += i.text;
}
else if (i.tags.length == 2 && i.tags.indexOf("transition") >-1) {
setTransitionCSS(i.text);
}
}
styleText = alterCSS(styleText);
style.styleSheet ? (style.styleSheet.cssText = styleText) : (style.innerHTML = styleText);
state.init();
}
setTimeout(function f() {
var size, bar = document.getElementById("loadingbar"), store = document.getElementById("storeArea");
if (!bar) {
return;
}
if (store) {
size = store.getAttribute("data-size");
if (store.children.length <= size && !tale) {
// +1 so that the bar can reach the end
bar.style.width = ~~((store.children.length+1)/size*100)+"%";
}
else {
bar.outerHTML = "";
return;
}
}
setTimeout(f,5);
},5);