Part two of turning the rabbit hole inside out

This commit is contained in:
Jeremy Ruston
2012-05-05 22:57:21 +01:00
parent ee6d7b5cc5
commit 9465da4335
128 changed files with 0 additions and 0 deletions
+117
View File
@@ -0,0 +1,117 @@
/*\
title: $:/core/modules/commander.js
type: application/javascript
module-type: global
The $tw.Commander class is a command interpreter
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Parse a sequence of commands
commandTokens: an array of command string tokens
wiki: reference to the wiki store object
streams: {output:, error:}, each of which has a write(string) method
callback: a callback invoked as callback(err) where err is null if there was no error
*/
var Commander = function(commandTokens,callback,wiki,streams) {
this.commandTokens = commandTokens;
this.nextToken = 0;
this.callback = callback;
this.wiki = wiki;
this.streams = streams;
};
/*
Execute the sequence of commands and invoke a callback on completion
*/
Commander.prototype.execute = function() {
this.executeNextCommand();
};
/*
Execute the next command in the sequence
*/
Commander.prototype.executeNextCommand = function() {
var self = this;
// Invoke the callback if there are no more commands
if(this.nextToken >= this.commandTokens.length) {
this.callback(null);
} else {
// Get and check the command token
var commandName = this.commandTokens[this.nextToken++];
if(commandName.substr(0,2) !== "--") {
this.callback("Missing command");
} else {
commandName = commandName.substr(2); // Trim off the --
// Accumulate the parameters to the command
var params = [];
while(this.nextToken < this.commandTokens.length &&
this.commandTokens[this.nextToken].substr(0,2) !== "--") {
params.push(this.commandTokens[this.nextToken++]);
}
// Get the command info
var command = $tw.commands[commandName],
c,err;
if(!command) {
this.callback("Unknown command: " + commandName);
} else {
if(this.verbose) {
this.streams.output.write("Executing command: " + commandName + " " + params.join(" ") + "\n");
}
if(command.info.synchronous) {
// Synchronous command
c = new command.Command(params,this);
err = c.execute();
if(err) {
this.callback(err);
} else {
this.executeNextCommand();
}
} else {
// Asynchronous command
c = new command.Command(params,this,function(err) {
if(err) {
self.callback(err);
} else {
self.executeNextCommand();
}
});
err = c.execute();
if(err) {
this.callback(err);
}
}
}
}
}
};
Commander.initCommands = function(moduleType) {
// Install the command modules
moduleType = moduleType || "command";
$tw.commands = {};
var modules = $tw.plugins.moduleTypes[moduleType],
n,m,f,c;
if(modules) {
for(n=0; n<modules.length; n++) {
m = modules[n];
$tw.commands[m.info.name] = {};
c = $tw.commands[m.info.name];
// Add the methods defined by the module
for(f in m) {
c[f] = m[f];
}
}
}
};
exports.Commander = Commander;
})();
+112
View File
@@ -0,0 +1,112 @@
/*\
title: $:/core/modules/commands/dump.js
type: application/javascript
module-type: command
Dump command
\*/
(function(){
/*jshint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "dump",
synchronous: true
};
var Command = function(params,commander) {
this.params = params;
this.commander = commander;
this.output = commander.streams.output;
};
Command.prototype.execute = function() {
if(this.params.length < 1) {
return "Too few parameters for dump command";
}
var subcommand = this.subcommands[this.params[0]];
if(subcommand) {
return subcommand.call(this);
} else {
return "Unknown subcommand (" + this.params[0] + ") for dump command";
}
};
Command.prototype.subcommands = {};
Command.prototype.subcommands.tiddler = function() {
if(this.params.length < 2) {
return "Too few parameters for dump tiddler command";
}
var tiddler = this.commander.wiki.getTiddler(this.params[1]);
this.output.write("Tiddler '" + this.params[1] + "' contains these fields:\n");
for(var t in tiddler.fields) {
this.output.write(" " + t + ": " + tiddler.getFieldString(t) + "\n");
}
return null; // No error
};
Command.prototype.subcommands.tiddlers = function() {
var tiddlers = this.commander.wiki.sortTiddlers();
this.output.write("Wiki contains these tiddlers:\n");
for(var t=0; t<tiddlers.length; t++) {
this.output.write(tiddlers[t] + "\n");
}
return null; // No error
};
Command.prototype.subcommands.shadows = function() {
var tiddlers = this.commander.wiki.shadows.sortTiddlers();
this.output.write("Wiki contains these shadow tiddlers:\n");
for(var t=0; t<tiddlers.length; t++) {
this.output.write(tiddlers[t] + "\n");
}
return null; // No error
};
Command.prototype.subcommands.config = function() {
var self = this;
var quotePropertyName = function(p) {
var unquotedPattern = /^[A-Za-z0-9_]*$/mg;
if(unquotedPattern.test(p)) {
return p;
} else {
return "[\"" + $tw.utils.stringify(p) + "\"]";
}
},
dumpConfig = function(object,prefix) {
for(var n in object) {
var v = object[n];
if(typeof v === "object") {
dumpConfig(v,prefix + "." + quotePropertyName(n));
} else if(typeof v === "string") {
self.output.write(prefix + "." + quotePropertyName(n) + ": \"" + $tw.utils.stringify(v) + "\"\n");
} else {
self.output.write(prefix + "." + quotePropertyName(n) + ": " + v.toString() + "\n");
}
}
},
dumpObject = function(heading,object) {
self.output.write(heading +"\n");
for(var n in object) {
self.output.write(" " + n + "\n");
}
};
this.output.write("Configuration:\n");
dumpConfig($tw.config," $tw.config");
dumpObject("Tiddler field plugins:",$tw.Tiddler.fieldPlugins);
dumpObject("Loaded modules:",$tw.modules.titles);
dumpObject("Loaded plugins:",$tw.plugins.moduleTypes);
dumpObject("Command plugins:",$tw.commands);
dumpObject("Parser plugins:",$tw.wiki.parsers);
dumpObject("Macro plugins:",$tw.wiki.macros);
dumpObject("Deserializer plugins:",$tw.Wiki.tiddlerDeserializerPlugins);
return null; // No error
};
exports.Command = Command;
})();
+56
View File
@@ -0,0 +1,56 @@
/*\
title: $:/core/modules/commands/load.js
type: application/javascript
module-type: command
Load tiddlers command
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "load",
synchronous: false
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this,
fs = require("fs"),
path = require("path");
if(this.params.length < 1) {
return "Missing filename";
}
fs.readFile(this.params[0],"utf8",function(err,data) {
if(err) {
self.callback(err);
} else {
var fields = {title: self.params[0]},
extname = path.extname(self.params[0]),
type = extname === ".html" ? "application/x-tiddlywiki" : extname;
var tiddlers = self.commander.wiki.deserializeTiddlers(type,data,fields);
if(!tiddlers) {
self.callback("No tiddlers found in file \"" + self.params[0] + "\"");
} else {
for(var t=0; t<tiddlers.length; t++) {
self.commander.wiki.addTiddler(new $tw.Tiddler(tiddlers[t]));
}
self.callback(null);
}
}
});
return null;
};
exports.Command = Command;
})();
+44
View File
@@ -0,0 +1,44 @@
/*\
title: $:/core/modules/commands/savetiddler.js
type: application/javascript
module-type: command
Save tiddlers command
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "savetiddler",
synchronous: false
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
if(this.params.length < 2) {
return "Missing filename";
}
var self = this,
fs = require("fs"),
path = require("path"),
title = this.params[0],
filename = this.params[1],
type = this.params[2] || "text/html";
fs.writeFile(filename,this.commander.wiki.renderTiddler(type,title),"utf8",function(err) {
self.callback(err);
});
return null;
};
exports.Command = Command;
})();
+32
View File
@@ -0,0 +1,32 @@
/*\
title: $:/core/modules/commands/verbose.js
type: application/javascript
module-type: command
Verbose command
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "verbose",
synchronous: true
};
var Command = function(params,commander) {
this.params = params;
this.commander = commander;
};
Command.prototype.execute = function() {
this.commander.verbose = true;
return null; // No error
};
exports.Command = Command;
})();
+32
View File
@@ -0,0 +1,32 @@
/*\
title: $:/core/modules/commands/version.js
type: application/javascript
module-type: command
Version command
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "version",
synchronous: true
};
var Command = function(params,commander) {
this.params = params;
this.commander = commander;
};
Command.prototype.execute = function() {
this.commander.streams.output.write($tw.utils.getVersionString() + "\n");
return null; // No error
};
exports.Command = Command;
})();
+74
View File
@@ -0,0 +1,74 @@
/*\
title: $:/core/modules/commands/wikitest.js
type: application/javascript
module-type: command
wikitest command
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "wikitest",
synchronous: true
};
var Command = function(params,commander) {
this.params = params;
this.commander = commander;
};
Command.prototype.execute = function() {
if(this.params.length <1) {
return "Missing parameters";
}
var fs = require("fs"),
path = require("path"),
testdirectory = this.params[0],
saveResults = this.params[1] === "save",
files = fs.readdirSync(testdirectory),
titles = [],
f,t,extname,basename;
for(f=0; f<files.length; f++) {
extname = path.extname(files[f]);
if(extname === ".tid") {
var tiddlers = this.commander.wiki.deserializeTiddlers(extname,fs.readFileSync(path.resolve(testdirectory,files[f]),"utf8"));
if(tiddlers.length > 1) {
throw "Cannot use .JSON files";
}
this.commander.wiki.addTiddler(new $tw.Tiddler(tiddlers[0]));
titles.push(tiddlers[0].title);
}
}
for(t=0; t<titles.length; t++) {
var htmlFilename = path.resolve(testdirectory,titles[t] + ".html"),
plainFilename = path.resolve(testdirectory,titles[t] + ".txt"),
htmlTarget = fs.readFileSync(htmlFilename,"utf8"),
plainTarget = fs.readFileSync(plainFilename,"utf8"),
tiddler = this.commander.wiki.getTiddler(titles[t]),
htmlRender = this.commander.wiki.renderTiddler("text/html",titles[t]),
plainRender = this.commander.wiki.renderTiddler("text/plain",titles[t]);
if(saveResults) {
// Save results
fs.writeFileSync(htmlFilename,htmlRender,"utf8");
fs.writeFileSync(plainFilename,plainRender,"utf8");
} else {
// Report results
if(htmlTarget !== htmlRender) {
this.commander.streams.output.write("Tiddler " + titles[t] + " html error\nTarget: " + htmlTarget + "\nFound: " + htmlRender +"\n");
}
if(plainTarget !== plainRender) {
this.commander.streams.output.write("Tiddler " + titles[t] + " plain text error\nTarget: " + plainTarget + "\nFound: " + plainRender + "\n");
}
}
}
return null; // No error
};
exports.Command = Command;
})();
+31
View File
@@ -0,0 +1,31 @@
/*\
title: $:/core/modules/config.js
type: application/javascript
module-type: config
Core configuration constants
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.dateFormats = {
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"],
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
// suffixes for dates, eg "1st","2nd","3rd"..."30th","31st"
daySuffixes: ["st","nd","rd","th","th","th","th","th","th","th",
"th","th","th","th","th","th","th","th","th","th",
"st","nd","rd","th","th","th","th","th","th","th",
"st"],
am: "am",
pm: "pm"
};
exports.htmlEntities = {quot:34, amp:38, apos:39, lt:60, gt:62, nbsp:160, iexcl:161, cent:162, pound:163, curren:164, yen:165, brvbar:166, sect:167, uml:168, copy:169, ordf:170, laquo:171, not:172, shy:173, reg:174, macr:175, deg:176, plusmn:177, sup2:178, sup3:179, acute:180, micro:181, para:182, middot:183, cedil:184, sup1:185, ordm:186, raquo:187, frac14:188, frac12:189, frac34:190, iquest:191, Agrave:192, Aacute:193, Acirc:194, Atilde:195, Auml:196, Aring:197, AElig:198, Ccedil:199, Egrave:200, Eacute:201, Ecirc:202, Euml:203, Igrave:204, Iacute:205, Icirc:206, Iuml:207, ETH:208, Ntilde:209, Ograve:210, Oacute:211, Ocirc:212, Otilde:213, Ouml:214, times:215, Oslash:216, Ugrave:217, Uacute:218, Ucirc:219, Uuml:220, Yacute:221, THORN:222, szlig:223, agrave:224, aacute:225, acirc:226, atilde:227, auml:228, aring:229, aelig:230, ccedil:231, egrave:232, eacute:233, ecirc:234, euml:235, igrave:236, iacute:237, icirc:238, iuml:239, eth:240, ntilde:241, ograve:242, oacute:243, ocirc:244, otilde:245, ouml:246, divide:247, oslash:248, ugrave:249, uacute:250, ucirc:251, uuml:252, yacute:253, thorn:254, yuml:255, OElig:338, oelig:339, Scaron:352, scaron:353, Yuml:376, fnof:402, circ:710, tilde:732, Alpha:913, Beta:914, Gamma:915, Delta:916, Epsilon:917, Zeta:918, Eta:919, Theta:920, Iota:921, Kappa:922, Lambda:923, Mu:924, Nu:925, Xi:926, Omicron:927, Pi:928, Rho:929, Sigma:931, Tau:932, Upsilon:933, Phi:934, Chi:935, Psi:936, Omega:937, alpha:945, beta:946, gamma:947, delta:948, epsilon:949, zeta:950, eta:951, theta:952, iota:953, kappa:954, lambda:955, mu:956, nu:957, xi:958, omicron:959, pi:960, rho:961, sigmaf:962, sigma:963, tau:964, upsilon:965, phi:966, chi:967, psi:968, omega:969, thetasym:977, upsih:978, piv:982, ensp:8194, emsp:8195, thinsp:8201, zwnj:8204, zwj:8205, lrm:8206, rlm:8207, ndash:8211, mdash:8212, lsquo:8216, rsquo:8217, sbquo:8218, ldquo:8220, rdquo:8221, bdquo:8222, dagger:8224, Dagger:8225, bull:8226, hellip:8230, permil:8240, prime:8242, Prime:8243, lsaquo:8249, rsaquo:8250, oline:8254, frasl:8260, euro:8364, image:8465, weierp:8472, real:8476, trade:8482, alefsym:8501, larr:8592, uarr:8593, rarr:8594, darr:8595, harr:8596, crarr:8629, lArr:8656, uArr:8657, rArr:8658, dArr:8659, hArr:8660, forall:8704, part:8706, exist:8707, empty:8709, nabla:8711, isin:8712, notin:8713, ni:8715, prod:8719, sum:8721, minus:8722, lowast:8727, radic:8730, prop:8733, infin:8734, ang:8736, and:8743, or:8744, cap:8745, cup:8746, int:8747, there4:8756, sim:8764, cong:8773, asymp:8776, ne:8800, equiv:8801, le:8804, ge:8805, sub:8834, sup:8835, nsub:8836, sube:8838, supe:8839, oplus:8853, otimes:8855, perp:8869, sdot:8901, lceil:8968, rceil:8969, lfloor:8970, rfloor:8971, lang:9001, rang:9002, loz:9674, spades:9824, clubs:9827, hearts:9829, diams:9830 };
})();
+80
View File
@@ -0,0 +1,80 @@
/*\
title: $:/core/modules/dependencies.js
type: application/javascript
module-type: global
Represents the dependencies of a tiddler or a parser node as these fields:
tiddlers: A hashmap of explicitly tiddler titles, with the value `false` if the dependency is skinny, and `true` if it is fat
dependentAll: True if there is an implicit skinny dependency on all available tiddlers
dependentOnContextTiddler: True if the node has a fat dependency on the current context tiddler
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Dependencies = function(skinnyTiddlers,fatTiddlers,dependentAll) {
var t,title;
this.tiddlers = {};
this.dependentAll = dependentAll;
if(skinnyTiddlers) {
for(t=0; t<skinnyTiddlers.length; t++) {
title = skinnyTiddlers[t];
if(typeof title === "string" && title !== "") {
this.tiddlers[title] = false;
}
}
}
if(fatTiddlers) {
for(t=0; t<fatTiddlers.length; t++) {
title = fatTiddlers[t];
if(typeof title === "string" && title !== "") {
this.tiddlers[title] = true;
}
}
}
};
/*
Adds a dependency to a given tiddler. Note how setting a dependency of fat=false on a tiddler that already has
a dependency of fat=true will leave the fat setting as true
*/
Dependencies.prototype.addDependency = function(tiddlerTitle,fat) {
if(!this.tiddlers[tiddlerTitle]) {
this.tiddlers[tiddlerTitle] = fat;
}
};
Dependencies.prototype.mergeDependencies = function(dep) {
this.dependentAll = dep.dependentAll || this.dependentAll;
for(var t in dep.tiddlers) {
this.addDependency(t,dep.tiddlers[t]);
}
};
/*
Determine if these dependencies are impacted by the specified array of changes
changes: Hashmap of {modified: bool, deleted: bool}
contextTiddlerTitle: The title of the current context tiddler
*/
Dependencies.prototype.hasChanged = function(changes,contextTiddlerTitle) {
if(this.dependentAll) {
return true;
}
if(!!this.dependentOnContextTiddler && contextTiddlerTitle && changes.hasOwnProperty(contextTiddlerTitle)) {
return true;
}
for(var c in changes) {
if(this.tiddlers.hasOwnProperty(c)) {
return true;
}
}
return false;
};
exports.Dependencies = Dependencies;
})();
+27
View File
@@ -0,0 +1,27 @@
/*\
title: $:/core/modules/deserializers.js
type: application/javascript
module-type: tiddlerdeserializer
Plugins to deserialise tiddlers from a block of text
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports["text/plain"] = function(text,fields) {
fields.text = text;
fields.type = "text/plain";
return [fields];
};
exports["text/html"] = function(text,fields) {
fields.text = text;
fields.type = "text/html";
return [fields];
};
})();
+45
View File
@@ -0,0 +1,45 @@
/*\
title: $:/core/modules/macros/button.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "button",
params: {
name: {byName: "default", type: "text"},
label: {byName: true, type: "text"},
"class": {byName: true, type: "text"}
},
events: ["click"]
};
exports.handleEvent = function(event) {
switch(event.type) {
case "click":
var buttonEvent = document.createEvent("Event");
buttonEvent.initEvent("tw-" + this.params.name,true,true);
buttonEvent.tiddlerTitle = this.tiddlerTitle;
buttonEvent.commandOrigin = this;
event.target.dispatchEvent(buttonEvent);
event.preventDefault();
return false;
}
return true;
};
exports.executeMacro = function() {
var attributes = {};
if(this.hasParameter("class")) {
attributes["class"] = this.params["class"].split(" ");
}
return [$tw.Tree.Element("button",attributes,[$tw.Tree.Text(this.params.label)])];
};
})();
+156
View File
@@ -0,0 +1,156 @@
/*\
title: $:/core/modules/macros/chooser.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "chooser",
wrapperTag: "div",
params: {
},
events: ["touchstart","touchmove","touchend","mouseover","mousemove","mouseup","mouseout"]
};
exports.showChooser = function() {
if(!this.chooserDisplayed) {
this.chooserDisplayed = true;
var nodes = [];
this.wiki.forEachTiddler("title",function(title,tiddler) {
nodes.push($tw.Tree.Element("li",{
"data-link": title
},[
$tw.Tree.Text(title)
]));
});
var wrapper = $tw.Tree.Element("ul",{},nodes);
wrapper.execute(this.parents,this.tiddlerTitle);
this.children = [wrapper];
this.children[0].renderInDom(this.domNode);
}
};
/*
Select the appropriate chooser item given a touch/mouse position in screen coordinates
*/
exports.select = function(y) {
if(this.children.length > 0) {
var targetIndex = Math.floor(this.children[0].domNode.childNodes.length * (y/window.innerHeight)),
target = this.children[0].domNode.childNodes[targetIndex];
if(target) {
this.deselect();
this.selectedNode = target;
$tw.utils.addClass(target,"selected");
}
}
};
exports.deselect = function() {
if(this.selectedNode) {
$tw.utils.removeClass(this.selectedNode,"selected");
this.selectedNode = null;
}
};
exports.action = function() {
if(this.selectedNode) {
var navEvent = document.createEvent("Event");
navEvent.initEvent("tw-navigate",true,true);
navEvent.navigateTo = this.selectedNode.getAttribute("data-link");
this.domNode.dispatchEvent(navEvent);
}
};
/*
Set the position of the chooser panel within its wrapper given a touch/mouse position in screen coordinates
*/
exports.hoverChooser = function(x,y) {
if(this.chooserDisplayed) {
// Get the target element that the touch/mouse is over
this.select(y);
// Things we need for sizing and positioning the chooser
var domPanel = this.children[0].domNode,
heightPanel = domPanel.offsetHeight,
widthPanel = domPanel.offsetWidth;
// Position the chooser div to account for scrolling
this.children[0].domNode.style.top = window.pageYOffset + "px";
// Scale the panel to fit
var scaleFactor = window.innerHeight/heightPanel;
// Scale up as we move right
var expandFactor = x > 50 ? ((x+150)/200) : 1;
// Set up the transform
var scale = scaleFactor * expandFactor,
translateX = x > 16 ? 0 : -(((16-x)/16) * widthPanel) / scale,
translateY = (y / scale) - ((y/window.innerHeight) * heightPanel);
domPanel.style.webkitTransformOrigin =
domPanel.style.MozTransformOrigin = "0 0";
domPanel.style.webkitTransform =
domPanel.style.MozTransform = "scale(" + scale + ") translateX(" + translateX + "px) translateY(" + translateY + "px)";
}
};
exports.hideChooser = function() {
if(this.chooserDisplayed) {
this.deselect();
this.chooserDisplayed = false;
this.domNode.removeChild(this.children[0].domNode);
this.children = [];
}
};
exports.handleEvent = function(event) {
switch(event.type) {
case "touchstart":
this.showChooser();
this.hoverChooser(event.touches[0].clientX,event.touches[0].clientY);
event.preventDefault();
return false;
case "touchmove":
this.hoverChooser(event.touches[0].clientX,event.touches[0].clientY);
event.preventDefault();
return false;
case "touchend":
this.action();
this.hideChooser();
event.preventDefault();
return false;
case "mouseover":
if(event.target === this.domNode) {
this.showChooser();
this.hoverChooser(event.clientX,event.clientY);
event.preventDefault();
return false;
}
break;
case "mousemove":
this.hoverChooser(event.clientX,event.clientY);
event.preventDefault();
return false;
case "mouseup":
this.action();
this.hideChooser();
event.preventDefault();
return false;
case "mouseout":
if(!$tw.utils.domContains(this.domNode,event.relatedTarget)) {
this.hideChooser();
event.preventDefault();
return false;
}
break;
}
return true;
};
exports.executeMacro = function() {
this.chooserDisplayed = false;
return [];
};
})();
+25
View File
@@ -0,0 +1,25 @@
/*\
title: $:/core/modules/macros/comment.js
type: application/javascript
module-type: macro
Comment macro, a no-op
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "!",
params: {}
};
exports.executeMacro = function() {
return [];
};
})();
+25
View File
@@ -0,0 +1,25 @@
/*\
title: $:/core/modules/macros/echo.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "echo",
params: {
text: {byPos: 0, type: "text"}
}
};
exports.executeMacro = function() {
return [$tw.Tree.Text(this.params.text)];
};
})();
+80
View File
@@ -0,0 +1,80 @@
/*\
title: $:/core/modules/macros/edit/edit.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "edit",
dependentOnContextTiddler: true,
params: {
field: {byPos: 0, type: "text"}
}
};
exports.executeMacro = function() {
// Get the tiddler being editted
var field = this.hasParameter("field") ? this.params.field : "text",
tiddler = this.wiki.getTiddler(this.tiddlerTitle),
Editor;
// Figure out which editor to use
// TODO: Tiddler field plugins should be able to specify a field type from which the editor is derived
if(field === "text" && tiddler.fields.type) {
Editor = this.wiki.macros.edit.editors[tiddler.fields.type];
}
if(!Editor) {
Editor = this.wiki.macros.edit.editors["text/x-tiddlywiki"];
}
this.editor = new Editor(this);
// Call the editor to generate the child nodes
var children = this.editor.getChildren();
for(var t=0; t<children.length; t++) {
children[t].execute(this.parents,this.tiddlerTitle);
}
return children;
};
exports.addEventHandlers = function() {
if(this.editor.addEventHandlers) {
this.editor.addEventHandlers();
}
};
exports.postRenderInDom = function() {
if(this.editor.postRenderInDom) {
this.editor.postRenderInDom();
}
};
exports.refreshInDom = function(changes) {
var t;
// Only refresh if a dependency is triggered
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
// Only refresh if the editor lets us
if(this.editor.isRefreshable()) {
// Remove the previous children
while(this.domNode.hasChildNodes()) {
this.domNode.removeChild(this.domNode.firstChild);
}
// Execute the new children
this.execute(this.parents,this.tiddlerTitle);
// Render to the DOM
for(t=0; t<this.children.length; t++) {
this.children[t].renderInDom(this.domNode);
}
}
} else {
// Refresh any children
for(t=0; t<this.children.length; t++) {
this.children[t].refreshInDom(changes);
}
}
};
})();
@@ -0,0 +1,167 @@
/*\
title: $:/core/modules/macros/edit/editors/bitmapeditor.js
type: application/javascript
module-type: editor
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
function BitmapEditor(macroNode) {
this.macroNode = macroNode;
}
BitmapEditor.prototype.getChildren = function() {
return [$tw.Tree.Element("canvas",{
"class": ["tw-edit-field"]
},[])];
};
BitmapEditor.prototype.postRenderInDom = function() {
var tiddler = this.macroNode.wiki.getTiddler(this.macroNode.tiddlerTitle),
canvas = this.macroNode.children[0].domNode,
currImage = new Image();
// Set the macro node itself to be position: relative
this.macroNode.domNode.style.position = "relative";
// Get the current bitmap into an image object
currImage.src = "data:" + tiddler.fields.type + ";base64," + tiddler.fields.text;
// Copy it to the on-screen canvas
canvas.width = currImage.width;
canvas.height = currImage.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(currImage,0,0);
// And also copy the current bitmap to the off-screen canvas
this.currCanvas = document.createElement("canvas");
this.currCanvas.width = currImage.width;
this.currCanvas.height = currImage.height;
ctx = this.currCanvas.getContext("2d");
ctx.drawImage(currImage,0,0);
};
BitmapEditor.prototype.addEventHandlers = function() {
var self = this;
this.macroNode.domNode.addEventListener("touchstart",function(event) {
self.brushDown = true;
self.strokeStart(event.touches[0].clientX,event.touches[0].clientY);
event.preventDefault();
event.stopPropagation();
return false;
},false);
this.macroNode.domNode.addEventListener("touchmove",function(event) {
if(self.brushDown) {
self.strokeMove(event.touches[0].clientX,event.touches[0].clientY);
}
event.preventDefault();
event.stopPropagation();
return false;
},false);
this.macroNode.domNode.addEventListener("touchend",function(event) {
if(self.brushDown) {
self.brushDown = false;
self.strokeEnd();
}
event.preventDefault();
event.stopPropagation();
return false;
},false);
this.macroNode.domNode.addEventListener("mousedown",function(event) {
self.strokeStart(event.clientX,event.clientY);
self.brushDown = true;
event.preventDefault();
event.stopPropagation();
return false;
},false);
this.macroNode.domNode.addEventListener("mousemove",function(event) {
if(self.brushDown) {
self.strokeMove(event.clientX,event.clientY);
event.preventDefault();
event.stopPropagation();
return false;
}
},false);
this.macroNode.domNode.addEventListener("mouseup",function(event) {
if(self.brushDown) {
self.brushDown = false;
self.strokeEnd();
event.preventDefault();
event.stopPropagation();
return false;
}
return true;
},false);
};
BitmapEditor.prototype.adjustCoordinates = function(x,y) {
var canvas = this.macroNode.children[0].domNode,
canvasRect = canvas.getBoundingClientRect(),
scale = canvas.width/canvasRect.width;
return {x: (x - canvasRect.left) * scale, y: (y - canvasRect.top) * scale};
};
BitmapEditor.prototype.strokeStart = function(x,y) {
// Start off a new stroke
this.stroke = [this.adjustCoordinates(x,y)];
};
BitmapEditor.prototype.strokeMove = function(x,y) {
var canvas = this.macroNode.children[0].domNode,
ctx = canvas.getContext("2d"),
t;
// Add the new position to the end of the stroke
this.stroke.push(this.adjustCoordinates(x,y));
// Redraw the previous image
ctx.drawImage(this.currCanvas,0,0);
// Render the stroke
ctx.lineWidth = 3;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
ctx.moveTo(this.stroke[0].x,this.stroke[0].y);
for(t=1; t<this.stroke.length-1; t++) {
var s1 = this.stroke[t],
s2 = this.stroke[t-1],
tx = (s1.x + s2.x)/2,
ty = (s1.y + s2.y)/2;
ctx.quadraticCurveTo(s2.x,s2.y,tx,ty);
}
ctx.stroke();
};
BitmapEditor.prototype.strokeEnd = function() {
// Copy the bitmap to the off-screen canvas
var canvas = this.macroNode.children[0].domNode,
ctx = this.currCanvas.getContext("2d");
ctx.drawImage(canvas,0,0);
// Save the image into the tiddler
this.saveChanges();
};
BitmapEditor.prototype.saveChanges = function() {
var tiddler = this.macroNode.wiki.getTiddler(this.macroNode.tiddlerTitle);
if(tiddler) {
// data URIs look like "data:<type>;base64,<text>"
var dataURL = this.macroNode.children[0].domNode.toDataURL(tiddler.fields.type,1.0),
posColon = dataURL.indexOf(":"),
posSemiColon = dataURL.indexOf(";"),
posComma = dataURL.indexOf(","),
type = dataURL.substring(posColon+1,posSemiColon),
text = dataURL.substring(posComma+1);
var update = {type: type, text: text};
this.macroNode.wiki.addTiddler(new $tw.Tiddler(tiddler,update));
}
};
BitmapEditor.prototype.isRefreshable = function() {
// Don't ever refresh the bitmap editor
return false;
};
exports["image/jpg"] = BitmapEditor;
exports["image/jpeg"] = BitmapEditor;
exports["image/png"] = BitmapEditor;
exports["image/gif"] = BitmapEditor;
})();
@@ -0,0 +1,106 @@
/*\
title: $:/core/modules/macros/edit/editors/texteditor.js
type: application/javascript
module-type: editor
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
function TextEditor(macroNode) {
this.macroNode = macroNode;
}
TextEditor.prototype.getChildren = function() {
var tiddler = this.macroNode.wiki.getTiddler(this.macroNode.tiddlerTitle),
field = this.macroNode.hasParameter("field") ? this.macroNode.params.field : "title",
value;
if(tiddler) {
value = tiddler.getFieldString(field);
} else {
switch(field) {
case "text":
value = "Type the text for the tiddler '" + this.macroNode.tiddlerTitle + "'";
break;
case "title":
value = this.macroNode.tiddlerTitle;
break;
default:
value = "";
break;
}
}
var attributes = {
"class": ["tw-edit-field"]
},
tagName,
content = [];
if(field === "text") {
tagName = "textarea";
content.push($tw.Tree.Text(value));
} else {
tagName = "input";
attributes.type = "text";
attributes.value = value;
}
return [$tw.Tree.Element(tagName,attributes,content)];
};
TextEditor.prototype.addEventHandlers = function() {
this.macroNode.domNode.addEventListener("focus",this,false);
this.macroNode.domNode.addEventListener("keyup",this,false);
};
TextEditor.prototype.handleEvent = function(event) {
// Get the value of the field if it might have changed
if("keyup".split(" ").indexOf(event.type) !== -1) {
this.saveChanges();
}
// Whatever the event, fix the height of the textarea if required
var self = this;
window.setTimeout(function() {
self.fixHeight();
},5);
return true;
};
TextEditor.prototype.saveChanges = function() {
var text = this.macroNode.children[0].domNode.value,
tiddler = this.macroNode.wiki.getTiddler(this.macroNode.tiddlerTitle);
if(tiddler && text !== tiddler.fields[this.macroNode.params.field]) {
var update = {};
update[this.macroNode.params.field] = text;
this.macroNode.wiki.addTiddler(new $tw.Tiddler(tiddler,update));
}
};
TextEditor.prototype.fixHeight = function() {
if(this.macroNode.children[0] && this.macroNode.children[0].domNode) {
var wrapper = this.macroNode.domNode,
textarea = this.macroNode.children[0].domNode;
// Set the text area height to 1px temporarily, which allows us to read the true scrollHeight
var prevWrapperHeight = wrapper.style.height;
wrapper.style.height = textarea.style.height + "px";
textarea.style.overflow = "hidden";
textarea.style.height = "1px";
textarea.style.height = textarea.scrollHeight + "px";
wrapper.style.height = prevWrapperHeight;
}
};
TextEditor.prototype.postRenderInDom = function() {
this.fixHeight();
};
TextEditor.prototype.isRefreshable = function() {
// Don't refresh the editor if it contains the caret or selection
return document.activeElement !== this.macroNode.children[0].domNode;
};
exports["text/x-tiddlywiki"] = TextEditor;
exports["text/plain"] = TextEditor;
})();
+46
View File
@@ -0,0 +1,46 @@
/*\
title: $:/core/modules/macros/image.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "image",
params: {
src: {byName: "default", type: "tiddler"},
text: {byName: true, type: "text"},
alignment: {byName: true, type: "text"}
}
};
exports.executeMacro = function() {
if(this.wiki.tiddlerExists(this.params.src)) {
var imageTree = this.wiki.parseTiddler(this.params.src).tree,
cloneImage = [];
for(var t=0; t<imageTree.length; t++) {
cloneImage.push(imageTree[t].clone());
}
if(this.params.text) {
return [$tw.Tree.Element("div",{
alt: this.params.text,
title: this.params.text
},cloneImage)];
} else {
return cloneImage;
}
} else {
return [$tw.Tree.Element("img",{
src: this.params.src,
alt: this.params.text,
title: this.params.text
})];
}
};
})();
+37
View File
@@ -0,0 +1,37 @@
/*\
title: $:/core/modules/macros/include.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "^",
params: {
filter: {byPos: 0, type: "filter"},
as: {byPos: 1, as: "text"}
}
};
exports.executeMacro = function() {
var as = this.params.as || "text/plain";
if(this.hasParameter("filter")) {
var titles = this.wiki.filterTiddlers(this.params.filter),
result = [];
for(var t=0; t<titles.length; t++) {
result.push(this.wiki.serializeTiddler(titles[t],as));
}
return [$tw.Tree.Element("pre",{},[
$tw.Tree.Text(result.join("\n"))
])];
}
return [];
};
})();
+88
View File
@@ -0,0 +1,88 @@
/*\
title: $:/core/modules/macros/link.js
type: application/javascript
module-type: macro
Implements the link macro.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var isLinkExternal = function(to) {
var externalRegExp = /(?:file|http|https|mailto|ftp|irc|news|data):[^\s'"]+(?:\/|\b)/i;
return externalRegExp.test(to);
};
exports.info = {
name: "link",
params: {
to: {byName: "default", type: "tiddler", skinny: true},
space: {byName: true, type: "text"}
},
events: ["click"]
};
exports.handleEvent = function (event) {
switch(event.type) {
case "click":
if(isLinkExternal(this.params.to)) {
event.target.setAttribute("target","_blank");
return true;
} else {
var navEvent = document.createEvent("Event");
navEvent.initEvent("tw-navigate",true,true);
navEvent.navigateTo = this.params.to;
event.target.dispatchEvent(navEvent);
event.preventDefault();
return false;
}
}
};
exports.executeMacro = function() {
// Assemble the information about the link
var linkInfo = {
to: this.params.to,
space: this.params.space
};
// Generate the default link characteristics
linkInfo.isExternal = isLinkExternal(linkInfo.to);
if(!linkInfo.isExternal) {
linkInfo.isMissing = !this.wiki.tiddlerExists(linkInfo.to);
}
linkInfo.attributes = {
href: linkInfo.to
};
if(!linkInfo.isExternal) {
linkInfo.attributes.href = encodeURIComponent(linkInfo.to);
}
// Generate the default classes for the link
linkInfo.attributes["class"] = ["tw-tiddlylink"];
if(linkInfo.isExternal) {
linkInfo.attributes["class"].push("tw-tiddlylink-external");
} else {
linkInfo.attributes["class"].push("tw-tiddlylink-internal");
if(linkInfo.isMissing) {
linkInfo.attributes["class"].push("tw-tiddlylink-missing");
} else {
linkInfo.attributes["class"].push("tw-tiddlylink-resolves");
}
}
// Create the link
var children;
if(linkInfo.suppressLink) {
children = this.cloneContent();
} else {
children = [$tw.Tree.Element("a",linkInfo.attributes,this.cloneContent())];
}
for(var t=0; t<children.length; t++) {
children[t].execute(this.parents,this.tiddlerTitle);
}
return children;
};
})();
+80
View File
@@ -0,0 +1,80 @@
/*\
title: $:/core/modules/macros/list.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var handlers = {
all: function(wiki) {
return wiki.sortTiddlers("title","excludeLists");
},
missing: function(wiki) {
return wiki.getMissingTitles();
},
orphans: function(wiki) {
return wiki.getOrphanTitles();
},
shadowed: function(wiki) {
return wiki.getShadowTitles();
},
touched: function(wiki) {
// Server syncing isn't implemented yet
return [];
},
filter: function(wiki) {
// Filters aren't implemented yet
return [];
}
};
exports.info = {
name: "list",
dependentAll: true, // Tiddlers containing <<list>> macro are dependent on every tiddler
params: {
type: {byName: "default", type: "text"},
template: {byName: true, type: "tiddler"},
emptyMessage: {byName: true, type: "text"}
}
};
exports.executeMacro = function() {
var templateType = "text/x-tiddlywiki",
templateText = "<<view title link>>",
template = this.params.template ? this.wiki.getTiddler(this.params.template) : null,
children = [],
t,
parents = this.parents;
if(template) {
parents = parents.slice(0);
parents.push(template.title);
templateType = template.type;
templateText = template.text;
}
var handler = handlers[this.params.type];
handler = handler || handlers.all;
var tiddlers = handler(this.wiki);
if(tiddlers.length === 0) {
return [$tw.Tree.Text(this.params.emptyMessage || "")];
} else {
var templateTree = this.wiki.parseText(templateType,templateText).tree;
for(t=0; t<tiddlers.length; t++) {
var cloneTemplate = [];
for(var c=0; c<templateTree.length; c++) {
cloneTemplate.push(templateTree[c].clone());
}
var listNode = $tw.Tree.Element("li",null,cloneTemplate);
listNode.execute(parents,tiddlers[t]);
children.push(listNode);
}
return [$tw.Tree.Element("ul",null,children)];
}
};
})();
+177
View File
@@ -0,0 +1,177 @@
/*\
title: $:/core/modules/macros/slider.js
type: application/javascript
module-type: macro
!Introduction
The slider macro is used to selectively reveal a chunk of text. By default, it renders as a button that may be clicked or touched to reveal the enclosed text.
The enclosed text can be a string of WikiText or be taken from a target tiddler.
The current state of the slider can be stored as the string "open" or "closed" in a specified tiddler. If the value of that tiddler changes then the slider is automatically updated. If no state tiddler is specified then the state of the slider isn't retained, but the slider still works as expected.
!!Parameters
|`state` //(defaults to 1st parameter)// |The title of the tiddler to contain the current state of the slider |
|`default` |The initial state of the slider, either `open` or `closed` |
|`class` |A CSS class to be applied to the slider root element |
|`content` |The WikiText to be enclosed in the slider. Overrides the `target` parameter, if present |
|`target` //(defaults to 2nd parameter)// |The title of the tiddler that contains the enclosed text. Ignored if the `content` parameter is specified |
|`label` //(defaults to 3rd parameter)// |The plain text to be displayed as the label for the slider button |
|`tooltip` //(defaults to 4th parameter)// |The plain text tooltip to be displayed when the mouse hovers over the slider button |
!!Markup
The markup generated by the slider macro is:
{{{
<span class="tw-slider {user defined class}">
<a class="btn-info">{slider label}</a>
<div class="tw-slider-body" style="display:{state}">{slider content}</div>
</span>
}}}
!!Examples
A minimal slider:
{{{
<<slider target:MyTiddler>>
}}}
!!Notes
The slider is a good study example of a simple interactive macro.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "slider",
params: {
state: {byPos: 0, type: "tiddler"},
target: {byPos: 1, type: "tiddler"},
label: {byPos: 2, type: "text"},
tooltip: {byPos: 3, type: "text"},
"default": {byName: true, type: "text"},
"class": {byName: true, type: "text"},
content: {byName: true, type: "text"}
},
events: ["click"]
};
exports.getOpenState = function() {
if(this.hasParameter("state")) {
var stateTiddler = this.wiki.getTiddler(this.params.state);
if(stateTiddler) {
return stateTiddler.fields.text.trim() === "open";
}
}
if(this.hasParameter("default")) {
return this.params["default"] === "open";
}
return false;
};
exports.saveOpenState = function() {
if(this.hasParameter("state")) {
var stateTiddler = this.wiki.getTiddler(this.params.state) || {title: this.params.state, text: ""};
this.wiki.addTiddler(new $tw.Tiddler(stateTiddler,{text: this.isOpen ? "open" : "closed"}));
return true;
}
return false;
};
exports.getSliderChildren = function() {
if(this.hasParameter("content")) {
return this.wiki.parseText("text/x-tiddlywiki",this.params.content).tree;
} else if(this.hasParameter("target")) {
return [$tw.Tree.Macro(
"tiddler",
{target: this.params.target},
null,
this.wiki)];
} else {
return [$tw.Tree.errorNode("No content specified for slider")];
}
};
exports.handleEvent = function(event) {
switch(event.type) {
case "click":
if(event.target === this.domNode.firstChild.firstChild) {
this.isOpen = !this.isOpen;
if(!this.saveOpenState()) {
this.refreshInDom({});
}
event.preventDefault();
return false;
} else {
return true;
}
}
return true;
};
exports.executeMacro = function() {
this.isOpen = this.getOpenState();
var sliderChildren = [];
if(this.isOpen) {
sliderChildren = this.getSliderChildren();
}
var attributes = {
"class": ["tw-slider"]
};
if(this.hasParameter("class")) {
attributes["class"].push(this.params["class"]);
}
if(this.hasParameter("state")) {
attributes["data-tw-slider-type"] = this.params.state;
}
if(this.hasParameter("tooltip")) {
attributes.alt = this.params.tooltip;
attributes.title = this.params.tooltip;
}
var children = $tw.Tree.Element("span",
attributes,
[
$tw.Tree.Element("a",
{
"class": ["btn","btn-info"]
},[
$tw.Tree.Text(this.params.label ? this.params.label : this.params.target)
]
),
$tw.Tree.Element("div",
{
"class": ["tw-slider-body"],
"style": {"display": this.isOpen ? "block" : "none"}
},
sliderChildren
)
]
);
children.execute(this.parents,this.tiddlerTitle);
return [children];
};
exports.refreshInDom = function(changes) {
var needChildrenRefresh = true; // Avoid refreshing the children nodes if we don't need to
// If the state tiddler has changed then reset the open state
if(this.hasParameter("state") && changes.hasOwnProperty(this.params.state)) {
this.isOpen = this.getOpenState();
}
// Render the children if the slider is open and we don't have any children yet
if(this.isOpen && this.children[0].children[1].children.length === 0) {
// Remove the existing dom node for the body
this.children[0].domNode.removeChild(this.children[0].children[1].domNode);
// Get the slider children and execute it
this.children[0].children[1].children = this.getSliderChildren();
this.children[0].children[1].execute(this.parents,this.tiddlerTitle);
this.children[0].children[1].renderInDom(this.children[0].domNode,null);
needChildrenRefresh = false; // Don't refresh the children if we've just created them
}
// Set the visibility of the slider children
this.children[0].children[1].domNode.style.display = this.isOpen ? "block" : "none";
// Refresh any children
if(needChildrenRefresh) {
for(var t=0; t<this.children.length; t++) {
this.children[t].refreshInDom(changes);
}
}
};
})();
+195
View File
@@ -0,0 +1,195 @@
/*\
title: $:/core/modules/macros/story.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
function scrollToTop(duration) {
if (duration < 0) {
return;
}
var delta = (-document.body.scrollTop/duration) * 10;
window.setTimeout(function() {
document.body.scrollTop = document.body.scrollTop + delta;
scrollToTop(duration-10);
}, 10);
}
exports.info = {
name: "story",
params: {
story: {byName: "default", type: "tiddler"},
defaultViewTemplate: {byName: true, type: "tiddler"},
defaultEditTemplate: {byName: true, type: "tiddler"}
},
events: ["tw-navigate","tw-EditTiddler","tw-SaveTiddler"]
};
exports.handleEvent = function(event) {
var template, storyTiddler, story, storyRecord, tiddler, storyTiddlerModified, t;
switch(event.type) {
case "tw-navigate":
// Navigate to a specified tiddler
template = this.hasParameter("defaultViewTemplate") ? this.params.defaultViewTemplate : "ViewTemplate";
storyTiddler = this.wiki.getTiddler(this.params.story);
story = {tiddlers: []};
if(storyTiddler && storyTiddler.fields.hasOwnProperty("text")) {
story = JSON.parse(storyTiddler.fields.text);
}
story.tiddlers.unshift({title: event.navigateTo, template: template});
this.wiki.addTiddler(new $tw.Tiddler(storyTiddler,{text: JSON.stringify(story)}));
scrollToTop(400);
event.stopPropagation();
return false;
case "tw-EditTiddler":
// Put the specified tiddler into edit mode
template = this.hasParameter("defaultEditTemplate") ? this.params.defaultEditTemplate : "EditTemplate";
storyTiddler = this.wiki.getTiddler(this.params.story);
story = {tiddlers: []};
if(storyTiddler && storyTiddler.fields.hasOwnProperty("text")) {
story = JSON.parse(storyTiddler.fields.text);
}
for(t=0; t<story.tiddlers.length; t++) {
storyRecord = story.tiddlers[t];
if(storyRecord.title === event.tiddlerTitle && storyRecord.template !== template) {
storyRecord.title = "Draft " + (new Date()) + " of " + event.tiddlerTitle;
storyRecord.template = template;
tiddler = this.wiki.getTiddler(event.tiddlerTitle);
this.wiki.addTiddler(new $tw.Tiddler(
{
text: "Type the text for the tiddler '" + event.tiddlerTitle + "'"
},
tiddler,
{
title: storyRecord.title,
"draft.title": event.tiddlerTitle,
"draft.of": event.tiddlerTitle
}));
}
}
this.wiki.addTiddler(new $tw.Tiddler(storyTiddler,{text: JSON.stringify(story)}));
event.stopPropagation();
return false;
case "tw-SaveTiddler":
template = this.hasParameter("defaultViewTemplate") ? this.params.defaultEditTemplate : "ViewTemplate";
storyTiddler = this.wiki.getTiddler(this.params.story);
story = {tiddlers: []};
storyTiddlerModified = false;
if(storyTiddler && storyTiddler.fields.hasOwnProperty("text")) {
story = JSON.parse(storyTiddler.fields.text);
}
for(t=0; t<story.tiddlers.length; t++) {
storyRecord = story.tiddlers[t];
if(storyRecord.title === event.tiddlerTitle && storyRecord.template !== template) {
tiddler = this.wiki.getTiddler(storyRecord.title);
if(tiddler && tiddler.fields.hasOwnProperty("draft.title")) {
// Save the draft tiddler as the real tiddler
this.wiki.addTiddler(new $tw.Tiddler(tiddler,{title: tiddler.fields["draft.title"],"draft.title": undefined, "draft.of": undefined}));
// Remove the draft tiddler
this.wiki.deleteTiddler(storyRecord.title);
// Remove the original tiddler if we're renaming it
if(tiddler.fields["draft.of"] !== tiddler.fields["draft.title"]) {
this.wiki.deleteTiddler(tiddler.fields["draft.of"]);
}
// Make the story record point to the newly saved tiddler
storyRecord.title = tiddler.fields["draft.title"];
storyRecord.template = template;
// Check if we're modifying the story tiddler itself
if(tiddler.fields["draft.title"] === this.params.story) {
storyTiddlerModified = true;
}
}
}
}
if(!storyTiddlerModified) {
this.wiki.addTiddler(new $tw.Tiddler(storyTiddler,{text: JSON.stringify(story)}));
}
event.stopPropagation();
return false;
}
};
exports.executeMacro = function() {
var story = JSON.parse(this.wiki.getTiddlerText(this.params.story)),
children = [];
for(var t=0; t<story.tiddlers.length; t++) {
var m = $tw.Tree.Macro("tiddler",
{target: story.tiddlers[t].title,template: story.tiddlers[t].template},
null,
this.wiki);
m.execute(this.parents,this.tiddlerTitle);
children.push(m);
}
return children;
};
exports.refreshInDom = function(changes) {
var t;
/*jslint browser: true */
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
// Get the tiddlers we're supposed to be displaying
var self = this,
story = JSON.parse(this.wiki.getTiddlerText(this.params.story)),
template = this.params.template,
n,domNode,
findTiddler = function (childIndex,tiddlerTitle,templateTitle) {
while(childIndex < self.children.length) {
var params = self.children[childIndex].params;
if(params.target === tiddlerTitle) {
if(!templateTitle || params.template === templateTitle) {
return childIndex;
}
}
childIndex++;
}
return null;
};
for(t=0; t<story.tiddlers.length; t++) {
// See if the node we want is already there
var tiddlerNode = findTiddler(t,story.tiddlers[t].title,story.tiddlers[t].template);
if(tiddlerNode === null) {
// If not, render the tiddler
var m = $tw.Tree.Macro("tiddler",
{target: story.tiddlers[t].title,template: story.tiddlers[t].template},
null,
this.wiki);
m.execute(this.parents,this.tiddlerTitle);
m.renderInDom(this.domNode,this.domNode.childNodes[t]);
this.children.splice(t,0,m);
} else {
// Delete any nodes preceding the one we want
if(tiddlerNode > t) {
// First delete the DOM nodes
for(n=t; n<tiddlerNode; n++) {
domNode = this.children[n].domNode;
domNode.parentNode.removeChild(domNode);
}
// Then delete the actual renderer nodes
this.children.splice(t,tiddlerNode-t);
}
// Refresh the DOM node we're reusing
this.children[t].refreshInDom(changes);
}
}
// Remove any left over nodes
if(this.children.length > story.tiddlers.length) {
for(t=story.tiddlers.length; t<this.children.length; t++) {
domNode = this.children[t].domNode;
domNode.parentNode.removeChild(domNode);
}
this.children.splice(story.tiddlers.length,this.children.length-story.tiddlers.length);
}
} else {
for(t=0; t<this.children.length; t++) {
this.children[t].refreshInDom(changes);
}
}
};
})();
+158
View File
@@ -0,0 +1,158 @@
/*\
title: $:/core/modules/macros/tiddler.js
type: application/javascript
module-type: macro
The tiddler macros transcludes another macro into the tiddler being rendered.
Parameters:
target: the title of the tiddler to transclude
template: the title of the tiddler to use as a template for the transcluded tiddler
with: optional parameters to be substituted into the rendered tiddler
The simplest case is to just supply a target tiddler:
<<tiddler Foo>> or <<transclude target:Foo>>
This will render the tiddler Foo within the current tiddler. If the tiddler Foo includes
the view macro (or other macros that reference the fields of the current tiddler), then the
fields of the tiddler Foo will be accessed.
If you want to transclude the tiddler as a template, so that the fields referenced by the view
macro are those of the tiddler doing the transcluding, then you can instead specify the tiddler
as a template:
<<tiddler template:Foo>>
The effect is the same as the previous example: the text of the tiddler Foo is rendered. The
difference is that the view macro will access the fields of the tiddler doing the transcluding.
The `target` and `template` parameters may be combined:
<<tiddler target:Foo template:Bar>>
Here, the text of the tiddler `Bar` will be transcluded, with the macros within it accessing the fields
of the tiddler `Foo`.
Finally, the `with` parameter is used to substitute values for the special markers $1, $2, $3 etc. The
substitutions are performed on the tiddler whose text is being rendered: either the tiddler named in
the `template` parameter or, if that parameter is missing, the tiddler named in the `target` parameter.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "tiddler",
cascadeParams: true, // Cascade names of named parameters to following anonymous parameters
params: {
target: {byName: "default", type: "tiddler"},
template: {byName: true, type: "tiddler"},
"with": {byName: true, type: "text", dependentAll: true}
}
};
exports.evaluateDependencies = function() {
var dependencies = new $tw.Dependencies(),
template = this.srcParams.template;
if(template === undefined) {
template = this.srcParams.target;
}
if(typeof template === "function") {
dependencies.dependentAll = true;
} else {
dependencies.addDependency(template,true);
}
return dependencies;
};
exports.executeMacro = function() {
var renderTitle = this.params.target,
renderTemplate = this.params.template,
children,
childrenClone = [],
t,
parents = this.parents.slice(0);
// If there's no render title specified then use the current tiddler title
if(typeof renderTitle !== "string") {
renderTitle = this.tiddlerTitle;
}
// If there's no template specified then use the target tiddler title
if(typeof renderTemplate !== "string") {
renderTemplate = renderTitle;
}
// Check for recursion
if(parents.indexOf(renderTemplate) !== -1) {
children = [$tw.Tree.errorNode("Tiddler recursion error in <<tiddler>> macro")];
} else {
if(this.hasParameter("with")) {
// Parameterised transclusion
var targetTiddler = this.wiki.getTiddler(renderTemplate),
text = targetTiddler.fields.text;
var withTokens = [this.params["with"]]; // TODO: Allow for more than one with: parameter
for(t=0; t<withTokens.length; t++) {
var placeholderRegExp = new RegExp("\\$"+(t+1),"mg");
text = text.replace(placeholderRegExp,withTokens[t]);
}
children = this.wiki.parseText(targetTiddler.fields.type,text).tree;
} else {
// There's no parameterisation, so we can just render the target tiddler directly
var parseTree = this.wiki.parseTiddler(renderTemplate);
children = parseTree ? parseTree.tree : [];
}
}
// Update the stack of tiddler titles for recursion detection
parents.push(renderTemplate);
// Clone the children
for(t=0; t<children.length; t++) {
childrenClone.push(children[t].clone());
}
// Execute macros within the children
for(t=0; t<childrenClone.length; t++) {
childrenClone[t].execute(parents,renderTitle);
}
// Set up the attributes for the wrapper element
var attributes = {
"data-tiddler-target": renderTitle,
"data-tiddler-template": renderTemplate,
"class": ["tw-tiddler-frame"]
};
if(!this.wiki.tiddlerExists(renderTitle)) {
attributes["class"].push("tw-tiddler-missing");
}
// Return the children
return [$tw.Tree.Element("div",attributes,childrenClone)];
};
exports.refreshInDom = function(changes) {
var t;
// Set the class for missing tiddlers
var renderTitle = this.params.target;
if(typeof renderTitle !== "string") {
renderTitle = this.params.template;
}
if(renderTitle) {
$tw.utils.toggleClass(this.children[0].domNode,"tw-tiddler-missing",!this.wiki.tiddlerExists(renderTitle));
}
// Rerender the tiddler if it is impacted by the changes
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
// Manually reexecute and rerender this macro
while(this.domNode.hasChildNodes()) {
this.domNode.removeChild(this.domNode.firstChild);
}
this.execute(this.parents,this.tiddlerTitle);
for(t=0; t<this.children.length; t++) {
this.children[t].renderInDom(this.domNode);
}
} else {
// Refresh any children
for(t=0; t<this.children.length; t++) {
this.children[t].refreshInDom(changes);
}
}
};
})();
+23
View File
@@ -0,0 +1,23 @@
/*\
title: $:/core/modules/macros/version.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "version",
params: {
}
};
exports.executeMacro = function() {
return [$tw.Tree.Text($tw.utils.getVersionString())];
};
})();
+56
View File
@@ -0,0 +1,56 @@
/*\
title: $:/core/modules/macros/video.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "video",
params: {
src: {byName: "default", type: "text"},
type: {byName: true, type: "text"},
width: {byName: true, type: "text"},
height: {byName: true, type: "text"}
}
};
exports.executeMacro = function() {
var src = this.params.src,
videoType = this.params.type || "vimeo",
videoWidth = this.params.width || 640,
videoHeight = this.params.height || 360;
switch(videoType) {
case "vimeo":
return [$tw.Tree.Element("iframe",{
src: "http://player.vimeo.com/video/" + src + "?autoplay=0",
width: videoWidth,
height: videoHeight,
frameborder: 0
})];
case "youtube":
return [$tw.Tree.Element("iframe",{
type: "text/html",
src: "http://www.youtube.com/embed/" + src,
width: videoWidth,
height: videoHeight,
frameborder: 0
})];
case "archiveorg":
return [$tw.Tree.Element("iframe",{
src: "http://www.archive.org/embed/" + src,
width: videoWidth,
height: videoHeight,
frameborder: 0
})];
default:
return [];
}
};
})();
+105
View File
@@ -0,0 +1,105 @@
/*\
title: $:/core/modules/macros/view.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "view",
dependentOnContextTiddler: true,
params: {
field: {byPos: 0, type: "text"},
format: {byPos: 1, type: "text"},
template: {byPos: 2, type: "text"}
}
};
exports.executeMacro = function() {
var tiddler = this.wiki.getTiddler(this.tiddlerTitle),
field = this.hasParameter("field") ? this.params.field : "title",
value,
children,
t,
childrenClone = [],
parents = this.parents;
if(tiddler) {
value = tiddler.fields[field];
} else {
switch(field) {
case "text":
value = "The tiddler '" + this.tiddlerTitle + "' does not exist";
break;
case "title":
value = this.tiddlerTitle;
break;
case "modified":
case "created":
value = new Date();
break;
default:
value = "Missing tiddler '" + this.tiddlerTitle + "'";
break;
}
}
switch(this.params.format) {
case "link":
if(value === undefined) {
return [];
} else {
var link = $tw.Tree.Macro("link",
{to: value},
[$tw.Tree.Text(value)],
this.wiki);
link.execute(parents,this.tiddlerTitle);
return [link];
}
break;
case "wikified":
if(tiddler && this.params.field === "text") {
if(parents.indexOf(tiddler.fields.title) !== -1) {
children = [$tw.Tree.errorNode("Tiddler recursion error in <<view>> macro")];
} else {
children = this.wiki.parseTiddler(tiddler.fields.title).tree;
}
parents = parents.slice(0);
parents.push(tiddler.fields.title);
} else {
children = this.wiki.parseText("text/x-tiddlywiki",value).tree;
}
for(t=0; t<children.length; t++) {
childrenClone.push(children[t].clone());
}
for(t=0; t<childrenClone.length; t++) {
childrenClone[t].execute(parents,this.tiddlerTitle);
}
return childrenClone;
case "date":
var template = this.params.template || "DD MMM YYYY";
if(value === undefined) {
return [];
} else {
return [$tw.Tree.Text($tw.utils.formatDateString(value,template))];
}
break;
default: // "text"
// Get the stringified version of the field value
if(field !== "text" && tiddler) {
value = tiddler.getFieldString(field);
}
if(value === undefined || value === null) {
return [];
} else {
return [$tw.Tree.Text(value)];
}
}
return [];
};
})();
+105
View File
@@ -0,0 +1,105 @@
/*\
title: $:/core/modules/macros/zoomer.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "zoomer",
wrapperTag: "div",
params: {
},
events: ["touchstart","touchmove","touchend"]
};
exports.startZoomer = function(x,y) {
this.inZoomer = true;
this.startX = x;
this.startY = y;
$tw.utils.addClass(document.body,"in-zoomer");
};
exports.stopZoomer = function() {
var newScrollY = this.yFactor * (this.bodyHeight - this.windowHeight);
this.inZoomer = false;
window.scrollTo(0,newScrollY);
document.body.style.webkitTransform = "translateY(" + newScrollY * this.xFactor + "px) " +
"scale(" + this.scale + ") " +
"translateY(" + ((this.windowHeight / this.scale) - this.bodyHeight) * this.yFactor * this.xFactor + "px)";
$tw.utils.removeClass(document.body,"in-zoomer");
document.body.style.webkitTransform = "translateY(0) scale(1) translateY(0)";
};
/*
Zoom the body element given a touch/mouse position in screen coordinates
*/
exports.hoverZoomer = function(x,y) {
// Put the transform origin at the top in the middle
document.body.style.webkitTransformOrigin = "50% 0";
// Some shortcuts
this.bodyWidth = document.body.offsetWidth;
this.bodyHeight = document.body.offsetHeight;
this.windowWidth = window.innerWidth;
this.windowHeight = window.innerHeight;
// Compute the scale factor for fitting the entire page into the window. This is
// the scale factor we'll use when the touch is far to the left
this.minScale = this.windowHeight / this.bodyHeight;
if(this.minScale < 0.1) {
// Don't scale to less than 10% of original size
this.minScale = 0.1;
} else if(this.minScale > 1) {
// Nor should we scale up if the body is shorter than the window
this.minScale = 1;
}
// We divide the screen into two horizontal zones divided by the right edge of the body at maximum zoom (ie minimum scale)
this.splitPos = this.windowWidth/2 + (this.bodyWidth * this.minScale)/2;
// Compute the 0->1 ratio (from right to left) of the position of the touch within the right zone
this.xFactor = (this.windowWidth - x) / (this.windowWidth - this.splitPos);
if(this.xFactor > 1) {
this.xFactor = 1;
}
// And the 0->1 ratio (from top to bottom) of the position of the touch down the screen
this.yFactor = y/this.windowHeight;
// Now interpolate the scale
this.scale = (this.minScale - 1) * this.xFactor + 1;
// Apply the transform. The malarkey with .toFixed() is because otherwise we might get numbers in
// exponential notation (such as 5.1e-15) that are illegal in CSS
var preTranslateY = window.scrollY * this.xFactor,
scale = this.scale,
postTranslateY = ((this.windowHeight / this.scale) - this.bodyHeight) * this.yFactor * this.xFactor;
var transform = "translateY(" + preTranslateY.toFixed(8) + "px) " +
"scale(" + scale.toFixed(8) + ") " +
"translateY(" + postTranslateY.toFixed(8) + "px)";
document.body.style.webkitTransform = transform;
};
exports.handleEvent = function(event) {
switch(event.type) {
case "touchstart":
this.startZoomer(event.touches[0].clientX,event.touches[0].clientY);
this.hoverZoomer(event.touches[0].clientX,event.touches[0].clientY);
event.preventDefault();
return false;
case "touchmove":
this.hoverZoomer(event.touches[0].clientX,event.touches[0].clientY);
event.preventDefault();
return false;
case "touchend":
this.stopZoomer();
event.preventDefault();
return false;
}
};
exports.executeMacro = function() {
this.inZoomer = false;
return [];
};
})();
+35
View File
@@ -0,0 +1,35 @@
/*\
title: $:/core/modules/parsers/imageparser.js
type: application/javascript
module-type: parser
Parses an image into a parse tree containing an HTML img element
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var ImageParser = function(options) {
this.wiki = options.wiki;
};
ImageParser.prototype.parse = function(type,text) {
var src;
if(type === "image/svg+xml" || type === ".svg") {
src = "data:image/svg+xml," + encodeURIComponent(text);
} else {
src = "data:" + type + ";base64," + text;
}
return new $tw.Renderer([$tw.Tree.Element("img",{src: src})],new $tw.Dependencies());
};
exports["image/svg+xml"] = ImageParser;
exports["image/jpg"] = ImageParser;
exports["image/jpeg"] = ImageParser;
exports["image/png"] = ImageParser;
exports["image/gif"] = ImageParser;
})();
@@ -0,0 +1,11 @@
{
"tiddlers": [
{
"file": "../../../../../node_modules/esprima/esprima.js",
"fields": {
"title": "$:/core/modules/parsers/javascriptparser/esprima.js",
"type": "application/javascript"
}
}
]
}
+50
View File
@@ -0,0 +1,50 @@
/*\
title: $:/core/modules/parsers/jsonparser.js
type: application/javascript
module-type: parser
Parses a JSON object into a parse tree
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var renderObject = function(obj) {
var children = [],t;
if($tw.utils.isArray(obj)) {
for(t=0; t<obj.length; t++) {
children.push($tw.Tree.Element("li",{
"class": ["jsonArrayMember"]
},[renderObject(obj[t])]));
}
return $tw.Tree.Element("ul",{
"class": ["jsonArray"]
},children);
} else if(typeof obj === "object") {
for(t in obj) {
children.push($tw.Tree.Element("li",{
"class": ["jsonObjectMember"]
},[$tw.Tree.splitLabelNode("JSON",[$tw.Tree.Text(t)],[renderObject(obj[t])])]));
}
return $tw.Tree.Element("ul",{
"class": ["jsonObject"]
},children);
} else {
return $tw.Tree.labelNode("JSON" + (typeof obj),[$tw.Tree.Text(JSON.stringify(obj))],["jsonValue"]);
}
};
var JSONParser = function(options) {
this.wiki = options.wiki;
};
JSONParser.prototype.parse = function(type,text) {
return new $tw.Renderer([renderObject(JSON.parse(text))],new $tw.Dependencies());
};
exports["application/json"] = JSONParser;
})();
+25
View File
@@ -0,0 +1,25 @@
/*\
title: $:/core/modules/parsers/plaintextparser.js
type: application/javascript
module-type: parser
Renders plain text tiddlers
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var PlainTextParser = function(options) {
this.wiki = options.wiki;
};
PlainTextParser.prototype.parse = function(type,text) {
return new $tw.Renderer([$tw.Tree.Element("pre",{},[$tw.Tree.Text(text)])],new $tw.Dependencies());
};
exports["text/plain"] = PlainTextParser;
})();
+59
View File
@@ -0,0 +1,59 @@
/*\
title: $:/core/modules/parsers/tiddlytextparser.js
type: application/javascript
module-type: parser
Parses a plain text block that can also contain macros and transclusions.
The syntax for transclusions is:
[[tiddlerTitle]]
The syntax for macros is:
<<macroName params>>
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var TiddlyTextParser = function(options) {
this.wiki = options.wiki;
};
TiddlyTextParser.prototype.parse = function(type,text) {
var output = [],
dependencies = new $tw.Dependencies(),
macroRegExp = /(?:\[\[([^\]]+)\]\])|(?:<<(?:([!@£\$%\^\&\*\(\)`\~'"\|\\\/;\:\.\,\+\=\-\_\{\}])|([^>\s]+))(?:\s*)((?:[^>]|(?:>(?!>)))*)>>)/mg,
lastMatchPos = 0,
match,
macroNode;
do {
match = macroRegExp.exec(text);
if(match) {
output.push($tw.Tree.Text(text.substring(lastMatchPos,match.index)));
var macroName = match[2] || match[3];
if(match[1]) { // Transclusion
macroNode = $tw.Tree.Macro("tiddler",{
target: match[1]
},[],this.wiki);
} else if(macroName) { // Macro call
macroNode = $tw.Tree.Macro(macroName,match[4],[],this.wiki);
}
output.push(macroNode);
dependencies.mergeDependencies(macroNode.dependencies);
lastMatchPos = match.index + match[0].length;
} else {
output.push($tw.Tree.Text(text.substr(lastMatchPos)));
}
} while(match);
return new $tw.Renderer(output,dependencies);
};
exports["text/x-tiddlywiki-css"] = TiddlyTextParser;
exports["text/x-tiddlywiki-html"] = TiddlyTextParser;
})();
+709
View File
@@ -0,0 +1,709 @@
/*\
title: $:/core/modules/parsers/wikitextparser/rules/wikitextrules.js
type: application/javascript
module-type: wikitextrule
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var textPrimitives = {
upperLetter: "[A-Z\u00c0-\u00de\u0150\u0170]",
lowerLetter: "[a-z0-9_\\-\u00df-\u00ff\u0151\u0171]",
anyLetter: "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]",
anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]",
sliceSeparator: "::",
sectionSeparator: "##",
urlPattern: "(?:file|http|https|mailto|ftp|irc|news|data):[^\\s'\"]+(?:/|\\b)",
unWikiLink: "~",
brackettedLink: "\\[\\[([^\\]]+)\\]\\]",
titledBrackettedLink: "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]"
};
textPrimitives.wikiLink = "(?:(?:" + textPrimitives.upperLetter + "+" +
textPrimitives.lowerLetter + "+" +
textPrimitives.upperLetter +
textPrimitives.anyLetter + "*)|(?:" +
textPrimitives.upperLetter + "{2,}" +
textPrimitives.lowerLetter + "+))";
textPrimitives.cssLookahead = "(?:(" + textPrimitives.anyLetter +
"+)\\(([^\\)\\|\\n]+)(?:\\):))|(?:(" + textPrimitives.anyLetter + "+):([^;\\|\\n]+);)";
textPrimitives.cssLookaheadRegExp = new RegExp(textPrimitives.cssLookahead,"mg");
textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" + textPrimitives.titledBrackettedLink + ")|(?:" +
textPrimitives.brackettedLink + ")|(?:" +
textPrimitives.urlPattern + ")","mg");
textPrimitives.tiddlerAnyLinkRegExp = new RegExp("("+ textPrimitives.wikiLink + ")|(?:" +
textPrimitives.titledBrackettedLink + ")|(?:" +
textPrimitives.brackettedLink + ")|(?:" +
textPrimitives.urlPattern + ")","mg");
// Helper to add an attribute to an HTML node
var setAttr = function(node,attr,value) {
if(!node.attributes) {
node.attributes = {};
}
node.attributes[attr] = value;
};
var inlineCssHelper = function(w) {
var styles = [];
textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
var lookaheadMatch = textPrimitives.cssLookaheadRegExp.exec(w.source);
while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
var s,v;
if(lookaheadMatch[1]) {
s = lookaheadMatch[1];
v = lookaheadMatch[2];
} else {
s = lookaheadMatch[3];
v = lookaheadMatch[4];
}
if(s=="bgcolor")
s = "backgroundColor";
if(s=="float")
s = "cssFloat";
styles.push({style: s, value: v});
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
lookaheadMatch = textPrimitives.cssLookaheadRegExp.exec(w.source);
}
return styles;
};
var applyCssHelper = function(e,styles) {
if(styles.length > 0) {
if(!e.attributes) {
e.attributes = {};
}
if(!e.attributes.style) {
e.attributes.style = {};
}
for(var t=0; t< styles.length; t++) {
e.attributes.style[styles[t].style] = styles[t].value;
}
}
};
var enclosedTextHelper = function(w) {
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
var text = lookaheadMatch[1];
w.output.push($tw.Tree.Element(this.element,null,[$tw.Tree.Text(text)]));
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
};
var insertMacroCall = function(w,output,name,params,children) {
if(name in w.wiki.macros) {
var macroNode = $tw.Tree.Macro(name,params,children,w.wiki);
w.dependencies.mergeDependencies(macroNode.dependencies);
output.push(macroNode);
}
};
var rules = [
{
name: "table",
match: "^\\|(?:[^\\n]*)\\|(?:[fhck]?)$",
lookaheadRegExp: /^\|([^\n]*)\|([fhck]?)$/mg,
rowTermRegExp: /(\|(?:[fhck]?)$\n?)/mg,
cellRegExp: /(?:\|([^\n\|]*)\|)|(\|[fhck]?$\n?)/mg,
cellTermRegExp: /((?:\x20*)\|)/mg,
rowTypes: {"c":"caption", "h":"thead", "":"tbody", "f":"tfoot"},
handler: function(w)
{
var table = $tw.Tree.Element("table",{"class": "table"},[]);
w.output.push(table);
var prevColumns = [];
var currRowType = null;
var rowContainer;
var rowCount = 0;
w.nextMatch = w.matchStart;
this.lookaheadRegExp.lastIndex = w.nextMatch;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
var nextRowType = lookaheadMatch[2];
if(nextRowType == "k") {
table.attributes["class"] = lookaheadMatch[1];
w.nextMatch += lookaheadMatch[0].length+1;
} else {
if(nextRowType != currRowType) {
rowContainer = $tw.Tree.Element(this.rowTypes[nextRowType],{},[]);
table.children.push(rowContainer);
currRowType = nextRowType;
}
if(currRowType == "c") {
// Caption
w.nextMatch++;
// Move the caption to the first row if it isn't already
if(table.children.length !== 1) {
table.children.pop(); // Take rowContainer out of the children array
table.children.splice(0,0,rowContainer); // Insert it at the bottom
}
rowContainer.attributes.align = rowCount === 0 ? "top" : "bottom";
w.subWikifyTerm(rowContainer.children,this.rowTermRegExp);
} else {
var theRow = $tw.Tree.Element("tr",{},[]);
theRow.attributes["class"] = rowCount%2 ? "oddRow" : "evenRow";
rowContainer.children.push(theRow);
this.rowHandler(w,theRow.children,prevColumns);
rowCount++;
}
}
this.lookaheadRegExp.lastIndex = w.nextMatch;
lookaheadMatch = this.lookaheadRegExp.exec(w.source);
}
},
rowHandler: function(w,e,prevColumns)
{
var col = 0;
var colSpanCount = 1;
var prevCell = null;
this.cellRegExp.lastIndex = w.nextMatch;
var cellMatch = this.cellRegExp.exec(w.source);
while(cellMatch && cellMatch.index == w.nextMatch) {
if(cellMatch[1] == "~") {
// Rowspan
var last = prevColumns[col];
if(last) {
last.rowSpanCount++;
last.element.attributes.rowspan = last.rowSpanCount;
last.element.attributes.valign = "center";
if(colSpanCount > 1) {
last.element.attributes.colspan = colSpanCount;
colSpanCount = 1;
}
}
w.nextMatch = this.cellRegExp.lastIndex-1;
} else if(cellMatch[1] == ">") {
// Colspan
colSpanCount++;
w.nextMatch = this.cellRegExp.lastIndex-1;
} else if(cellMatch[2]) {
// End of row
if(prevCell && colSpanCount > 1) {
prevCell.attributes.colspan = colSpanCount;
}
w.nextMatch = this.cellRegExp.lastIndex;
break;
} else {
// Cell
w.nextMatch++;
var styles = inlineCssHelper(w);
var spaceLeft = false;
var chr = w.source.substr(w.nextMatch,1);
while(chr == " ") {
spaceLeft = true;
w.nextMatch++;
chr = w.source.substr(w.nextMatch,1);
}
var cell;
if(chr == "!") {
cell = $tw.Tree.Element("th",{},[]);
e.push(cell);
w.nextMatch++;
} else {
cell = $tw.Tree.Element("td",{},[]);
e.push(cell);
}
prevCell = cell;
prevColumns[col] = {rowSpanCount:1,element:cell};
if(colSpanCount > 1) {
cell.attributes.colspan = colSpanCount;
colSpanCount = 1;
}
applyCssHelper(cell,styles);
w.subWikifyTerm(cell.children,this.cellTermRegExp);
if(w.matchText.substr(w.matchText.length-2,1) == " ") // spaceRight
cell.attributes.align = spaceLeft ? "center" : "left";
else if(spaceLeft)
cell.attributes.align = "right";
w.nextMatch--;
}
col++;
this.cellRegExp.lastIndex = w.nextMatch;
cellMatch = this.cellRegExp.exec(w.source);
}
}
},
{
name: "heading",
match: "^!{1,6}",
termRegExp: /(\n)/mg,
handler: function(w)
{
var e = $tw.Tree.Element("h" + w.matchLength,{},[]);
w.output.push(e);
w.subWikifyTerm(e.children,this.termRegExp);
}
},
{
name: "list",
match: "^(?:[\\*#;:]+)",
lookaheadRegExp: /^(?:(?:(\*)|(#)|(;)|(:))+)/mg,
termRegExp: /(\n)/mg,
handler: function(w)
{
var stack = [w.output];
var currLevel = 0, currType = null;
var listLevel, listType, itemType, baseType;
w.nextMatch = w.matchStart;
this.lookaheadRegExp.lastIndex = w.nextMatch;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
if(lookaheadMatch[1]) {
listType = "ul";
itemType = "li";
} else if(lookaheadMatch[2]) {
listType = "ol";
itemType = "li";
} else if(lookaheadMatch[3]) {
listType = "dl";
itemType = "dt";
} else if(lookaheadMatch[4]) {
listType = "dl";
itemType = "dd";
}
if(!baseType)
baseType = listType;
listLevel = lookaheadMatch[0].length;
w.nextMatch += lookaheadMatch[0].length;
var t,e;
if(listLevel > currLevel) {
for(t=currLevel; t<listLevel; t++) {
var target = stack[stack.length-1];
if(currLevel !== 0 && target.children) {
target = target.children[target.children.length-1];
}
e = $tw.Tree.Element(listType,{},[]);
target.push(e);
stack.push(e.children);
}
} else if(listType!=baseType && listLevel==1) {
w.nextMatch -= lookaheadMatch[0].length;
return;
} else if(listLevel < currLevel) {
for(t=currLevel; t>listLevel; t--)
stack.pop();
} else if(listLevel == currLevel && listType != currType) {
stack.pop();
e = $tw.Tree.Element(listType,{},[]);
stack[stack.length-1].push(e);
stack.push(e.children);
}
currLevel = listLevel;
currType = listType;
e = $tw.Tree.Element(itemType,{},[]);
stack[stack.length-1].push(e);
w.subWikifyTerm(e.children,this.termRegExp);
this.lookaheadRegExp.lastIndex = w.nextMatch;
lookaheadMatch = this.lookaheadRegExp.exec(w.source);
}
}
},
{
name: "quoteByBlock",
match: "^<<<\\n",
termRegExp: /(^<<<(\n|$))/mg,
element: "blockquote",
handler: function(w) {
var e = $tw.Tree.Element(this.element,{},[]);
w.output.push(e);
w.subWikifyTerm(e.children,this.termRegExp);
}
},
{
name: "quoteByLine",
match: "^>+",
lookaheadRegExp: /^>+/mg,
termRegExp: /(\n)/mg,
element: "blockquote",
handler: function(w)
{
var stack = [w.output];
var currLevel = 0;
var newLevel = w.matchLength;
var t,matched,e;
do {
if(newLevel > currLevel) {
for(t=currLevel; t<newLevel; t++) {
e = $tw.Tree.Element(this.element,{},[]);
stack[stack.length-1].push(e);
}
} else if(newLevel < currLevel) {
for(t=currLevel; t>newLevel; t--)
stack.pop();
}
currLevel = newLevel;
w.subWikifyTerm(stack[stack.length-1],this.termRegExp);
stack[stack.length-1].push($tw.Tree.Element("br"));
this.lookaheadRegExp.lastIndex = w.nextMatch;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
if(matched) {
newLevel = lookaheadMatch[0].length;
w.nextMatch += lookaheadMatch[0].length;
}
} while(matched);
}
},
{
name: "rule",
match: "^----+$\\n?|<hr ?/?>\\n?",
handler: function(w)
{
w.output.push($tw.Tree.Element("hr"));
}
},
{
name: "monospacedByLine",
match: "^(?:/\\*\\{\\{\\{\\*/|\\{\\{\\{|//\\{\\{\\{|<!--\\{\\{\\{-->)\\n",
element: "pre",
handler: function(w)
{
switch(w.matchText) {
case "/*{{{*/\n": // CSS
this.lookaheadRegExp = /\/\*\{\{\{\*\/\n*((?:^[^\n]*\n)+?)(\n*^\f*\/\*\}\}\}\*\/$\n?)/mg;
break;
case "{{{\n": // monospaced block
this.lookaheadRegExp = /^\{\{\{\n((?:^[^\n]*\n)+?)(^\f*\}\}\}$\n?)/mg;
break;
case "//{{{\n": // plugin
this.lookaheadRegExp = /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\f*\/\/\}\}\}$\n?)/mg;
break;
case "<!--{{{-->\n": //template
this.lookaheadRegExp = /<!--\{\{\{-->\n*((?:^[^\n]*\n)+?)(\n*^\f*<!--\}\}\}-->$\n?)/mg;
break;
default:
break;
}
enclosedTextHelper.call(this,w);
}
},
{
name: "typedBlock",
match: "^\\$\\$\\$(?:.*)\\n",
lookaheadRegExp: /^\$\$\$(.*)\n((?:^[^\n]*\n)+?)(^\f*\$\$\$$\n?)/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
// The wikitext parsing infrastructure is horribly unre-entrant
var mimeType = lookaheadMatch[1],
content = lookaheadMatch[2],
oldOutput = w.output,
oldSource = w.source,
oldNextMatch = w.nextMatch,
oldChildren = w.children,
oldDependencies = w.dependencies,
parseTree = w.wiki.parseText(mimeType,content,{defaultType: "text/plain"});
w.output = oldOutput;
w.source = oldSource;
w.nextMatch = oldNextMatch;
w.children = oldChildren;
w.dependencies = oldDependencies;
w.output.push.apply(w.output,parseTree.nodes);
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "wikifyComment",
match: "^(?:/\\*\\*\\*|<!---)\\n",
handler: function(w)
{
var termRegExp = (w.matchText == "/***\n") ? (/(^\*\*\*\/\n)/mg) : (/(^--->\n)/mg);
w.subWikifyTerm(w.output,termRegExp);
}
},
{
name: "macro",
match: "<<",
lookaheadRegExp: /<<(?:([!@£\$%\^\&\*\(\)`\~'"\|\\\/;\:\.\,\+\=\-\_\{\}])|([^>\s]+))(?:\s*)((?:[^>]|(?:>(?!>)))*)>>/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source),
name = lookaheadMatch[1] || lookaheadMatch[2];
if(lookaheadMatch && lookaheadMatch.index == w.matchStart && name) {
w.nextMatch = this.lookaheadRegExp.lastIndex;
insertMacroCall(w,w.output,name,lookaheadMatch[3]);
}
}
},
{
name: "prettyLink",
match: "\\[\\[",
lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\]/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
var text = lookaheadMatch[1],
link = text;
if(lookaheadMatch[3]) {
// Pretty bracketted link
link = lookaheadMatch[3];
}
insertMacroCall(w,w.output,"link",{to: link},[$tw.Tree.Text(text)]);
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "wikiLink",
match: textPrimitives.unWikiLink+"?"+textPrimitives.wikiLink,
handler: function(w)
{
if(w.matchText.substr(0,1) == textPrimitives.unWikiLink) {
w.outputText(w.output,w.matchStart+1,w.nextMatch);
return;
}
if(w.matchStart > 0) {
var preRegExp = new RegExp(textPrimitives.anyLetterStrict,"mg");
preRegExp.lastIndex = w.matchStart-1;
var preMatch = preRegExp.exec(w.source);
if(preMatch.index == w.matchStart-1) {
w.outputText(w.output,w.matchStart,w.nextMatch);
return;
}
}
if(w.autoLinkWikiWords) {
insertMacroCall(w,w.output,"link",{to: w.matchText},[$tw.Tree.Text(w.source.substring(w.matchStart,w.nextMatch))]);
} else {
w.outputText(w.output,w.matchStart,w.nextMatch);
}
}
},
{
name: "urlLink",
match: textPrimitives.urlPattern,
handler: function(w)
{
insertMacroCall(w,w.output,"link",{to: w.matchText},[$tw.Tree.Text(w.source.substring(w.matchStart,w.nextMatch))]);
}
},
{
name: "image",
match: "\\[[<>]?[Ii][Mm][Gg]\\[",
// [<] sequence below is to avoid lessThan-questionMark sequence so TiddlyWikis can be included in PHP files
lookaheadRegExp: /\[([<]?)(>?)[Ii][Mm][Gg]\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source),
imageParams = {},
linkParams = {};
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
if(lookaheadMatch[1]) {
imageParams.alignment = "left";
} else if(lookaheadMatch[2]) {
imageParams.alignment = "right";
}
if(lookaheadMatch[3]) {
imageParams.text = lookaheadMatch[3];
}
imageParams.src = lookaheadMatch[4];
if(lookaheadMatch[5]) {
linkParams.target = lookaheadMatch[5];
var linkChildren = [];
insertMacroCall(w,w.output,"link",linkParams,linkChildren);
insertMacroCall(w,linkChildren,"image",imageParams);
} else {
insertMacroCall(w,w.output,"image",imageParams);
}
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "html",
match: "<[Hh][Tt][Mm][Ll]>",
lookaheadRegExp: /<[Hh][Tt][Mm][Ll]>((?:.|\n)*?)<\/[Hh][Tt][Mm][Ll]>/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
w.output.push($tw.Tree.Element("html",{},[$tw.Tree.Raw(lookaheadMatch[1])]));
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "commentByBlock",
match: "/%",
lookaheadRegExp: /\/%((?:.|\n)*?)%\//mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
},
{
name: "characterFormat",
match: "''|//|__|\\^\\^|~~|--(?!\\s|$)|\\{\\{\\{|`",
handler: function(w)
{
var e,lookaheadRegExp,lookaheadMatch;
switch(w.matchText) {
case "''":
e = $tw.Tree.Element("strong",null,[]);
w.output.push(e);
w.subWikifyTerm(e.children,/('')/mg);
break;
case "//":
e = $tw.Tree.Element("em",null,[]);
w.output.push(e);
w.subWikifyTerm(e.children,/(\/\/)/mg);
break;
case "__":
e = $tw.Tree.Element("u",null,[]);
w.output.push(e);
w.subWikifyTerm(e.children,/(__)/mg);
break;
case "^^":
e = $tw.Tree.Element("sup",null,[]);
w.output.push(e);
w.subWikifyTerm(e.children,/(\^\^)/mg);
break;
case "~~":
e = $tw.Tree.Element("sub",null,[]);
w.output.push(e);
w.subWikifyTerm(e.children,/(~~)/mg);
break;
case "--":
e = $tw.Tree.Element("strike",null,[]);
w.output.push(e);
w.subWikifyTerm(e.children,/(--)/mg);
break;
case "`":
lookaheadRegExp = /`((?:.|\n)*?)`/mg;
lookaheadRegExp.lastIndex = w.matchStart;
lookaheadMatch = lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
w.output.push($tw.Tree.Element("code",null,[$tw.Tree.Text(lookaheadMatch[1])]));
w.nextMatch = lookaheadRegExp.lastIndex;
}
break;
case "{{{":
lookaheadRegExp = /\{\{\{((?:.|\n)*?)\}\}\}/mg;
lookaheadRegExp.lastIndex = w.matchStart;
lookaheadMatch = lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
w.output.push($tw.Tree.Element("code",null,[$tw.Tree.Text(lookaheadMatch[1])]));
w.nextMatch = lookaheadRegExp.lastIndex;
}
break;
}
}
},
{
name: "customFormat",
match: "@@|\\{\\{",
handler: function(w)
{
switch(w.matchText) {
case "@@":
var e = $tw.Tree.Element("span",null,[]);
w.output.push(e);
var styles = inlineCssHelper(w);
if(styles.length === 0)
setAttr(e,"class","marked");
else
applyCssHelper(e,styles);
w.subWikifyTerm(e.children,/(@@)/mg);
break;
case "{{":
var lookaheadRegExp = /\{\{[\s]*([\-\w]+[\-\s\w]*)[\s]*\{(\n?)/mg;
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source);
if(lookaheadMatch) {
w.nextMatch = lookaheadRegExp.lastIndex;
e = $tw.Tree.Element(lookaheadMatch[2] == "\n" ? "div" : "span",{
"class": lookaheadMatch[1]
},[]);
w.output.push(e);
w.subWikifyTerm(e.children,/(\}\}\})/mg);
}
break;
}
}
},
{
name: "mdash",
match: "--",
handler: function(w)
{
w.output.push($tw.Tree.Entity("&mdash;"));
}
},
{
name: "lineBreak",
match: "\\n|<br ?/?>",
handler: function(w)
{
w.output.push($tw.Tree.Element("br"));
}
},
{
name: "rawText",
match: "\"{3}|<nowiki>",
lookaheadRegExp: /(?:\"{3}|<nowiki>)((?:.|\n)*?)(?:\"{3}|<\/nowiki>)/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
w.output.push($tw.Tree.Text(lookaheadMatch[1]));
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "htmlEntitiesEncoding",
match: "&#?[a-zA-Z0-9]{2,8};",
handler: function(w)
{
w.output.push($tw.Tree.Entity(w.matchText));
}
}
];
exports.rules = rules;
})();
@@ -0,0 +1,186 @@
/*\
title: $:/core/modules/parsers/wikitextparser/wikitextparser.js
type: application/javascript
module-type: parser
Parses a block of tiddlywiki-format wiki text into a parse tree object. This is a transliterated version of the old TiddlyWiki code. The plan is to replace it with a new, mostly backwards compatible parser built in PEGJS.
A wikitext parse tree is an array of objects with a `type` field that can be `text`,`macro` or the name of an HTML element.
Text nodes are represented as `{type: "text", value: "A string of text"}`.
Macro nodes look like this:
`
{type: "macro", name: "view", params: {
one: {type: "eval", value: "2+2"},
two: {type: "string", value: "twenty two"}
}}
`
HTML nodes look like this:
`
{type: "div", attributes: {
src: "one"
styles: {
"background-color": "#fff",
"color": "#000"
}
}}
`
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Creates a new instance of the wiki text parser with the specified options. The
options are a hashmap of mandatory members as follows:
wiki: The wiki object to use to parse any cascaded content (eg transclusion)
Planned:
enableRules: An array of names of wiki text rules to enable. If not specified, all rules are available
extraRules: An array of additional rule handlers to add
enableMacros: An array of names of macros to enable. If not specified, all macros are available
extraMacros: An array of additional macro handlers to add
*/
var WikiTextParser = function(options) {
this.wiki = options.wiki;
this.autoLinkWikiWords = true;
};
WikiTextParser.prototype.installRules = function(rules) {
var pattern = [];
for(var n=0; n<rules.length; n++) {
pattern.push("(" + rules[n].match + ")");
}
this.rules = rules;
this.rulesRegExp = new RegExp(pattern.join("|"),"mg");
};
WikiTextParser.prototype.parse = function(type,text) {
this.source = text;
this.nextMatch = 0;
this.children = [];
this.dependencies = new $tw.Dependencies();
this.output = null;
this.subWikify(this.children);
var tree = new $tw.Renderer(this.children,this.dependencies);
this.source = null;
this.children = null;
return tree;
};
WikiTextParser.prototype.outputText = function(place,startPos,endPos) {
if(startPos < endPos) {
place.push($tw.Tree.Text(this.source.substring(startPos,endPos)));
}
};
WikiTextParser.prototype.subWikify = function(output,terminator) {
// Handle the terminated and unterminated cases separately, this speeds up wikifikation by about 30%
if(terminator)
this.subWikifyTerm(output,new RegExp("(" + terminator + ")","mg"));
else
this.subWikifyUnterm(output);
};
WikiTextParser.prototype.subWikifyUnterm = function(output) {
// subWikify can be indirectly recursive, so we need to save the old output pointer
var oldOutput = this.output;
this.output = output;
// Get the first match
this.rulesRegExp.lastIndex = this.nextMatch;
var ruleMatch = this.rulesRegExp.exec(this.source);
while(ruleMatch) {
// Output any text before the match
if(ruleMatch.index > this.nextMatch)
this.outputText(this.output,this.nextMatch,ruleMatch.index);
// Set the match parameters for the handler
this.matchStart = ruleMatch.index;
this.matchLength = ruleMatch[0].length;
this.matchText = ruleMatch[0];
this.nextMatch = this.rulesRegExp.lastIndex;
// Figure out which rule matched and call its handler
var t;
for(t=1; t<ruleMatch.length; t++) {
if(ruleMatch[t]) {
this.rules[t-1].handler(this);
this.rulesRegExp.lastIndex = this.nextMatch;
break;
}
}
// Get the next match
ruleMatch = this.rulesRegExp.exec(this.source);
}
// 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;
};
WikiTextParser.prototype.subWikifyTerm = function(output,terminatorRegExp) {
// subWikify can be indirectly recursive, so we need to save the old output pointer
var oldOutput = this.output;
this.output = output;
// Get the first matches for the rule and terminator RegExps
terminatorRegExp.lastIndex = this.nextMatch;
var terminatorMatch = terminatorRegExp.exec(this.source);
this.rulesRegExp.lastIndex = this.nextMatch;
var ruleMatch = this.rulesRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
while(terminatorMatch || ruleMatch) {
// Check for a terminator match before the next rule match
if(terminatorMatch && (!ruleMatch || terminatorMatch.index <= ruleMatch.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.matchText = terminatorMatch[1];
this.matchLength = terminatorMatch[1].length;
this.matchStart = terminatorMatch.index;
this.nextMatch = this.matchStart + this.matchLength;
// Restore the output pointer
this.output = oldOutput;
return;
}
// It must be a rule match; output any text before the match
if(ruleMatch.index > this.nextMatch)
this.outputText(this.output,this.nextMatch,ruleMatch.index);
// Set the match parameters
this.matchStart = ruleMatch.index;
this.matchLength = ruleMatch[0].length;
this.matchText = ruleMatch[0];
this.nextMatch = this.rulesRegExp.lastIndex;
// Figure out which rule matched and call its handler
var t;
for(t=1; t<ruleMatch.length; t++) {
if(ruleMatch[t]) {
this.rules[t-1].handler(this);
this.rulesRegExp.lastIndex = this.nextMatch;
break;
}
}
// Get the next match
terminatorRegExp.lastIndex = this.nextMatch;
terminatorMatch = terminatorRegExp.exec(this.source);
ruleMatch = this.rulesRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
}
// 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;
};
exports["text/x-tiddlywiki"] = WikiTextParser;
})();
+48
View File
@@ -0,0 +1,48 @@
/*\
title: $:/core/modules/renderer.js
type: application/javascript
module-type: global
Represents a parse tree and associated data
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Renderer = function(tree,dependencies) {
this.tree = tree;
this.dependencies = dependencies;
};
Renderer.prototype.execute = function(parents,tiddlerTitle) {
for(var t=0; t<this.tree.length; t++) {
this.tree[t].execute(parents,tiddlerTitle);
}
};
Renderer.prototype.render = function(type) {
var output = [];
for(var t=0; t<this.tree.length; t++) {
output.push(this.tree[t].render(type));
}
return output.join("");
};
Renderer.prototype.renderInDom = function(parentDomNode,insertBefore) {
for(var t=0; t<this.tree.length; t++) {
this.tree[t].renderInDom(parentDomNode,insertBefore);
}
};
Renderer.prototype.refreshInDom = function(changes) {
for(var t=0; t<this.tree.length; t++) {
this.tree[t].refreshInDom(changes);
}
};
exports.Renderer = Renderer;
})();
+71
View File
@@ -0,0 +1,71 @@
/*\
title: $:/core/modules/serializers.js
type: application/javascript
module-type: tiddlerserializer
Plugins to serialise tiddlers to a block of text
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports["text/plain"] = function(tiddler) {
return tiddler ? tiddler.fields.text : "";
};
exports["text/html"] = function(tiddler) {
var text = this.renderTiddler("text/html",tiddler.fields.title);
return text ? text : "";
};
exports["application/javascript"] = function(tiddler) {
var attributes = {type: "text/javascript"}; // The script type is set to text/javascript for compatibility with old browsers
for(var f in tiddler.fields) {
if(f !== "text") {
attributes["data-tiddler-" + f] = tiddler.getFieldString(f);
}
}
return $tw.Tree.Element(
"script",
attributes,
[$tw.Tree.Raw(tiddler.fields.text)]
).render("text/html");
};
exports["application/x-tiddler-module"] = function(tiddler) {
var attributes = {
type: "text/javascript",
"data-module": "yes"
}, // The script type is set to text/javascript for compatibility with old browsers
text = tiddler.fields.text;
text = "$tw.modules.define(\"" + tiddler.fields.title + "\",\"" + tiddler.fields["module-type"] + "\",function(module,exports,require) {" + text + "});\n";
for(var f in tiddler.fields) {
if(f !== "text") {
attributes["data-tiddler-" + f] = tiddler.getFieldString(f);
}
}
return $tw.Tree.Element(
"script",
attributes,
[$tw.Tree.Raw(text)]
).render("text/html");
};
exports["application/x-tiddler-html-div"] = function(tiddler) {
var result = [];
result.push("<div");
for(var f in tiddler.fields) {
if(f !== "text") {
result.push(" " + f + "=\"" + tiddler.getFieldString(f) + "\"");
}
}
result.push(">\n<pre>");
result.push($tw.utils.htmlEncode(tiddler.fields.text));
result.push("</pre>\n</div>");
return result.join("");
};
})();
+70
View File
@@ -0,0 +1,70 @@
/*\
title: $:/core/modules/startup.js
type: application/javascript
module-type: startup
This is the main application logic for both the client and server
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.startup = function() {
var modules,n,m,f,commander;
// Set up additional global objects
$tw.plugins.applyMethods("global",$tw);
// Wire up plugin modules
$tw.plugins.applyMethods("config",$tw.config);
$tw.plugins.applyMethods("utils",$tw.utils);
$tw.Tiddler.fieldPlugins = $tw.plugins.getPluginsByTypeAsHashmap("tiddlerfield");
$tw.plugins.applyMethods("tiddlermethod",$tw.Tiddler.prototype);
$tw.plugins.applyMethods("wikimethod",$tw.Wiki.prototype);
$tw.plugins.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerPlugins);
$tw.Wiki.tiddlerSerializerPlugins = {};
$tw.plugins.applyMethods("tiddlerserializer",$tw.Wiki.tiddlerSerializerPlugins);
$tw.plugins.applyMethods("treeutils",$tw.Tree);
$tw.plugins.applyMethods("treenode",$tw.Tree);
// Get version information
$tw.version = $tw.utils.extractVersionInfo();
// Load up the tiddlers in the root of the core directory (we couldn't do before because we didn't have the serializers installed)
if(!$tw.isBrowser) {
$tw.plugins.loadPluginsFromFolder($tw.boot.bootPath,"$:/core",/^\.DS_Store$|.meta$|^modules$/);
}
// Set up the wiki store
$tw.wiki.initMacros();
$tw.wiki.initEditors();
$tw.wiki.initParsers();
// Set up the command plugins
$tw.Commander.initCommands();
// Host-specific startup
if($tw.isBrowser) {
// Display the PageTemplate
var renderer = $tw.wiki.parseTiddler("PageTemplate");
renderer.execute([],"PageTemplate");
renderer.renderInDom(document.body);
$tw.wiki.addEventListener("",function(changes) {
renderer.refreshInDom(changes);
});
console.log("$tw",$tw);
} else {
// Start a commander with the command line arguments
commander = new $tw.Commander(
Array.prototype.slice.call(process.argv,2),
function(err) {
if(err) {
console.log("Error: " + err);
}
},
$tw.wiki,
{output: process.stdout, error: process.stderr}
);
commander.execute();
}
};
})();
+34
View File
@@ -0,0 +1,34 @@
/*\
title: $:/core/modules/tiddler.js
type: application/javascript
module-type: tiddlermethod
Extension methods for the $tw.Tiddler object
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.hasTag = function(tag) {
return this.fields.tags && this.fields.tags.indexOf(tag) !== -1;
};
exports.getFieldString = function(field) {
var value = this.fields[field];
// Check for a missing field
if(value === undefined) {
return undefined;
}
// Parse the field with the associated plugin (if any)
var fieldPlugin = $tw.Tiddler.fieldPlugins[field];
if(fieldPlugin) {
return fieldPlugin.stringify.call(this,value);
} else {
return value.toString();
}
};
})();
+28
View File
@@ -0,0 +1,28 @@
/*\
title: $:/core/modules/tree.js
type: application/javascript
module-type: global
Renderer objects encapsulate a tree of nodes that are capable of rendering and selectively updating an
HTML representation of themselves. The following node types are defined:
* ''Macro'' - represents an invocation of a macro
* ''Element'' - represents a single HTML element
* ''Text'' - represents an HTML text node
* ''Entity'' - represents an HTML entity node
* ''Raw'' - represents a chunk of unparsed HTML text
These node types are implemented with prototypal inheritance from a base Node class. One
unusual convenience in the implementation is that the node constructors can be called without an explicit
`new` keyword: the constructors check for `this` not being an instance of themselves, and recursively invoke
themselves with `new` when required.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.Tree = {};
})();
+57
View File
@@ -0,0 +1,57 @@
/*\
title: $:/core/modules/tree.utils.js
type: application/javascript
module-type: treeutils
Static utility methods for the $tw.Tree class
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Construct an error message
*/
exports.errorNode = function(text) {
return $tw.Tree.Element("span",{
"class": ["label","label-important"]
},[
new $tw.Tree.Text(text)
]);
};
/*
Construct a label
*/
exports.labelNode = function(type,value,classes) {
classes = (classes || []).slice(0);
classes.push("label");
return $tw.Tree.Element("span",{
"class": classes,
"data-tw-label-type": type
},value);
};
/*
Construct a split label
*/
exports.splitLabelNode = function(type,left,right,classes) {
classes = (classes || []).slice(0);
classes.push("splitLabel");
return $tw.Tree.Element("span",{
"class": classes
},[
$tw.Tree.Element("span",{
"class": ["splitLabelLeft"],
"data-tw-label-type": type
},left),
$tw.Tree.Element("span",{
"class": ["splitLabelRight"]
},right)
]);
};
})();
+144
View File
@@ -0,0 +1,144 @@
/*\
title: $:/core/modules/treenodes/element.js
type: application/javascript
module-type: treenode
Element nodes
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Node = require("./node.js").Node;
var Element = function(type,attributes,children) {
if(this instanceof Element) {
this.type = type;
this.attributes = attributes || {};
this.children = children;
} else {
return new Element(type,attributes,children);
}
};
Element.prototype = new Node();
Element.prototype.constructor = Element;
Element.prototype.clone = function() {
var childClones;
if(this.children) {
childClones = [];
for(var t=0; t<this.children.length; t++) {
childClones.push(this.children[t].clone());
}
}
return new Element(this.type,this.attributes,childClones);
};
Element.prototype.execute = function(parents,tiddlerTitle) {
if(this.children) {
for(var t=0; t<this.children.length; t++) {
this.children[t].execute(parents,tiddlerTitle);
}
}
};
Element.prototype.render = function(type) {
var isHtml = type === "text/html",
output = [];
if(isHtml) {
output.push("<",this.type);
if(this.attributes) {
for(var a in this.attributes) {
var v = this.attributes[a];
if(v !== undefined) {
if($tw.utils.isArray(v)) {
v = v.join(" ");
} else if(typeof v === "object") {
var s = [];
for(var p in v) {
s.push(p + ":" + v[p] + ";");
}
v = s.join("");
}
output.push(" ",a,"='",$tw.utils.htmlEncode(v),"'");
}
}
}
output.push(">");
}
if(this.children) {
for(var t=0; t<this.children.length; t++) {
output.push(this.children[t].render(type));
}
if(isHtml) {
output.push("</",this.type,">");
}
}
return output.join("");
};
Element.prototype.renderInDom = function(parentDomNode,insertBefore) {
var element = document.createElement(this.type);
if(this.attributes) {
for(var a in this.attributes) {
var v = this.attributes[a];
if(v !== undefined) {
if($tw.utils.isArray(v)) { // Ahem, could there be arrays other than className?
element.className = v.join(" ");
} else if (typeof v === "object") { // ...or objects other than style?
for(var p in v) {
element.style[p] = v[p];
}
} else {
element.setAttribute(a,v);
}
}
}
}
if(insertBefore) {
parentDomNode.insertBefore(element,insertBefore);
} else {
parentDomNode.appendChild(element);
}
this.domNode = element;
if(this.children) {
for(var t=0; t<this.children.length; t++) {
this.children[t].renderInDom(element);
}
}
};
Element.prototype.refresh = function(changes) {
if(this.children) {
for(var t=0; t<this.children.length; t++) {
this.children[t].refresh(changes);
}
}
};
Element.prototype.refreshInDom = function(changes) {
if(this.children) {
for(var t=0; t<this.children.length; t++) {
this.children[t].refreshInDom(changes);
}
}
};
Element.prototype.broadcastEvent = function(event) {
if(this.children) {
for(var t=0; t<this.children.length; t++) {
if(!this.children[t].broadcastEvent(event)) {
return false;
}
}
}
return true;
};
exports.Element = Element;
})();
+39
View File
@@ -0,0 +1,39 @@
/*\
title: $:/core/modules/treenodes/entity.js
type: application/javascript
module-type: treenode
Entity nodes
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Node = require("./node.js").Node;
var Entity = function(entity) {
if(this instanceof Entity) {
this.entity = entity;
} else {
return new Entity(entity);
}
};
Entity.prototype = new Node();
Entity.prototype.constructor = Entity;
Entity.prototype.render = function(type) {
return type === "text/html" ? this.entity : $tw.utils.entityDecode(this.entity);
};
Entity.prototype.renderInDom = function(domNode) {
this.domNode = document.createTextNode($tw.utils.entityDecode(this.entity));
domNode.appendChild(this.domNode);
};
exports.Entity = Entity;
})();
+248
View File
@@ -0,0 +1,248 @@
/*\
title: $:/core/modules/treenodes/macro.js
type: application/javascript
module-type: treenode
Macro node, used as the base class for macros
\*/
(function(){
/*jshint node: true, browser: true */
/*global $tw: false */
"use strict";
var Node = require("./node.js").Node;
/*
Construct a renderer node representing a macro invocation
macroName: name of the macro
srcParams: a string or a hashmap of parameters (each can be a string, or a fn(tiddler,wiki) for evaluated parameters)
children: optional array of child nodes
store: reference to the WikiStore associated with this macro
dependencies: optional Dependencies object representing the dependencies of this macro
Note that the dependencies will be evaluated if not provided.
*/
var Macro = function(macroName,srcParams,content,wiki,dependencies) {
var MacroClass = wiki ? wiki.macros[macroName] : null; // Get the macro class
if(this instanceof Macro) {
// Save the details
this.macroName = macroName;
this.srcParams = srcParams || {};
this.content = content || [];
this.wiki = wiki;
this.dependencies = dependencies;
// Parse the macro parameters if required
if(typeof this.srcParams === "string") {
this.srcParams = this.parseMacroParamString(srcParams);
}
// Evaluate the dependencies if required
if(macroName && !this.dependencies) {
this.dependencies = this.evaluateDependencies();
}
// Get a reference to the static information about this macro
if(wiki && wiki.macros[macroName]) {
this.MacroClass = MacroClass;
this.info = MacroClass.prototype.info;
}
} else {
// If Macro() has been called without 'new' then instantiate the right macro class
if(!MacroClass) {
throw "Unknown macro '" + macroName + "'";
}
return new MacroClass(macroName,srcParams,content,wiki,dependencies);
}
};
Macro.prototype = new Node();
Macro.prototype.constructor = Macro;
/*
Evaluate the dependencies of this macro invocation. If the macro provides an `evaluateDependencies` method
then it is invoked to evaluate the dependencies. Otherwise it generates the dependencies based on the
macro parameters provided
*/
Macro.prototype.evaluateDependencies = function() {
// Figure out the dependencies from the metadata and parameters
var dependencies = new $tw.Dependencies();
if(this.info.dependentAll) {
dependencies.dependentAll = true;
}
if(this.info.dependentOnContextTiddler) {
dependencies.dependentOnContextTiddler = true;
}
for(var m in this.info.params) {
var paramInfo = this.info.params[m];
if(m in this.srcParams && paramInfo.type === "tiddler") {
if(typeof this.srcParams[m] === "function") {
dependencies.dependentAll = true;
} else {
dependencies.addDependency(this.srcParams[m],!paramInfo.skinny);
}
}
}
return dependencies;
};
Macro.prototype.parseMacroParamString = function(paramString) {
/*jslint evil: true */
var params = {},
args = new $tw.utils.ArgParser(paramString,{defaultName: "anon", cascadeDefaults: this.info.cascadeDefaults}),
self = this,
insertParam = function(name,arg) {
if(arg.evaluated) {
params[name] = eval("(function(tiddler,wiki) {return " + arg.string + ";})");
} else {
params[name] = arg.string;
}
};
for(var m in this.info.params) {
var param = this.info.params[m],
arg;
if("byPos" in param && args.byPos[param.byPos] && (args.byPos[param.byPos].n === "anon" || args.byPos[param.byPos].n === m)) {
arg = args.byPos[param.byPos].v;
insertParam(m,arg);
} else {
arg = args.getValueByName(m);
if(!arg && param.byName === "default") {
arg = args.getValueByName("anon");
}
if(arg) {
insertParam(m,arg);
}
}
}
return params;
};
Macro.prototype.hasParameter = function(name) {
return this.params.hasOwnProperty(name);
};
Macro.prototype.cloneContent = function() {
var contentClones = [];
for(var t=0; t<this.content.length; t++) {
contentClones.push(this.content[t].clone());
}
return contentClones;
};
Macro.prototype.clone = function() {
return new this.MacroClass(this.macroName,this.srcParams,this.cloneContent(),this.wiki,this.dependencies);
};
Macro.prototype.execute = function(parents,tiddlerTitle) {
parents = parents || [];
// Evaluate macro parameters to get their values
this.params = {};
var tiddler = this.wiki.getTiddler(tiddlerTitle);
if(!tiddler) {
tiddler = {title: tiddlerTitle};
}
for(var p in this.srcParams) {
if(typeof this.srcParams[p] === "function") {
this.params[p] = this.srcParams[p](tiddler.fields,this.wiki);
} else {
this.params[p] = this.srcParams[p];
}
}
// Save the context info for use when we're refreshing it
this.tiddlerTitle = tiddlerTitle;
this.parents = parents;
// Execute the macro to generate its children, and recursively execute them
this.children = this.executeMacro.call(this);
};
Macro.prototype.render = function(type) {
var output = [];
for(var t=0; t<this.children.length; t++) {
output.push(this.children[t].render(type));
}
return output.join("");
};
Macro.prototype.renderInDom = function(parentDomNode,insertBefore) {
// Create the wrapper node for the macro
var domNode = document.createElement(this.info.wrapperTag || "span");
this.domNode = domNode;
if(insertBefore) {
parentDomNode.insertBefore(domNode,insertBefore);
} else {
parentDomNode.appendChild(domNode);
}
// Add some debugging information to it
domNode.setAttribute("data-tw-macro",this.macroName);
// Ask the macro to add event handlers to the node
this.addEventHandlers();
// Render the content of the macro
for(var t=0; t<this.children.length; t++) {
this.children[t].renderInDom(domNode);
}
this.postRenderInDom();
};
Macro.prototype.addEventHandlers = function() {
if(this.info.events) {
for(var t=0; t<this.info.events.length; t++) {
// Register this macro node to handle the event via the handleEvent() method
this.domNode.addEventListener(this.info.events[t],this,false);
}
}
};
Macro.prototype.broadcastEvent = function(event) {
if(!this.handleEvent(event)) {
return false;
}
for(var t=0; t<this.children.length; t++) {
if(!this.children[t].broadcastEvent(event)) {
return false;
}
}
return true;
};
Macro.prototype.postRenderInDom = function() {
// Do nothing, individual macros can override
};
Macro.prototype.refresh = function(changes) {
var t,
self = this;
// Check if any of the dependencies of this macro node have changed
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
// Re-execute the macro if so
this.execute(this.parents,this.tiddlerTitle);
} else {
// Refresh any children
for(t=0; t<this.children.length; t++) {
this.children[t].refresh(changes);
}
}
};
Macro.prototype.refreshInDom = function(changes) {
var t,
self = this;
// Check if any of the dependencies of this macro node have changed
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
// Manually reexecute and rerender this macro
while(this.domNode.hasChildNodes()) {
this.domNode.removeChild(this.domNode.firstChild);
}
this.execute(this.parents,this.tiddlerTitle);
for(t=0; t<this.children.length; t++) {
this.children[t].renderInDom(this.domNode);
}
} else {
// Refresh any children
for(t=0; t<this.children.length; t++) {
this.children[t].refreshInDom(changes);
}
}
};
exports.Macro = Macro;
})();
+80
View File
@@ -0,0 +1,80 @@
/*\
title: $:/core/modules/treenodes/node.js
type: application/javascript
module-type: treenode
Base class for all other tree nodes
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
The virtual base class for all types of renderer nodes. It keeps track of child nodes but otherwise contains no functionality
*/
var Node = function() {
if(this instanceof Node) {
} else {
return new Node();
}
};
/*
Makes a copy of a node, or returns the node itself if it doesn't need cloning because it doesn't have any state
*/
Node.prototype.clone = function() {
// By default we don't actually clone nodes, we just re-use them (we do clone macros and elements)
return this;
};
/*
Fire an event at a node which is also broadcast to any child nodes
*/
Node.prototype.broadcastEvent = function(event) {
return true; // true means that the event wasn't handled by this node
};
/*
Execute a node and derive its child nodes
parents: array of titles of each transcluded parent tiddler (used for detecting recursion)
tiddlerTitle: title of the context tiddler within which this node is being executed
*/
Node.prototype.execute = function(parents,tiddlerTitle) {
};
/*
Render a node and its children to a particular MIME type
type: Content type to which the node should be rendered
*/
Node.prototype.render = function(type) {
};
/*
Render a node and its children into the DOM
parentDomNode: node that should become the parent of the rendered node
insertBefore: optional node that the node should be inserted before
*/
Node.prototype.renderInDom = function(parentDomNode,insertBefore) {
};
/*
Re-execute a node if it is impacted by a set of tiddler changes
changes: hashmap of tiddler changes in the format used by $tw.Wiki
*/
Node.prototype.refresh = function(changes) {
};
/*
Re-execute a node if it is impacted by a set of tiddler changes, and update the associated DOM elements as necessary
changes: hashmap of tiddler changes in the format used by $tw.Wiki
*/
Node.prototype.refreshInDom = function(changes) {
};
exports.Node = Node;
})();
+40
View File
@@ -0,0 +1,40 @@
/*\
title: $:/core/modules/treenodes/raw.js
type: application/javascript
module-type: treenode
Raw, unparsed HTML nodes
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Node = require("./node.js").Node;
var Raw = function(html) {
if(this instanceof Raw) {
this.html = html;
} else {
return new Raw(html);
}
};
Raw.prototype = new Node();
Raw.prototype.constructor = Raw;
Raw.prototype.render = function(type) {
return this.html;
};
Raw.prototype.renderInDom = function(domNode) {
this.domNode = document.createElement("div");
this.domNode.innerHTML = this.html;
domNode.appendChild(this.domNode);
};
exports.Raw = Raw;
})();
+39
View File
@@ -0,0 +1,39 @@
/*\
title: $:/core/modules/treenodes/text.js
type: application/javascript
module-type: treenode
Text nodes
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Node = require("./node.js").Node;
var Text = function(text) {
if(this instanceof Text) {
this.text = text;
} else {
return new Text(text);
}
};
Text.prototype = new Node();
Text.prototype.constructor = Text;
Text.prototype.render = function(type) {
return type === "text/html" ? $tw.utils.htmlEncode(this.text) : this.text;
};
Text.prototype.renderInDom = function(domNode) {
this.domNode = document.createTextNode(this.text);
domNode.appendChild(this.domNode);
};
exports.Text = Text;
})();
+117
View File
@@ -0,0 +1,117 @@
/*\
title: $:/core/modules/utils.argparser.js
type: application/javascript
module-type: utils
Parse a space-separated string of name:value parameters. Values can be quoted with single quotes, double quotes, double square brackets, or double curly braces.
The parameters are returned in a structure that can be referenced like this:
(return).byName["name"][0] - First occurance of parameter with a given name
(return).byPos[0].n - Name of parameter in first position
(return).byPos[0].v.string - Value of parameter in first position
(return).byPos[0].v.evaluated - True if the parameter is to be evaluated
Options and their defaults are:
defaultName: null,
defaultValue: null,
noNames: false,
cascadeDefaults: false,
allowEval: true
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var ArgParser = function(argString,options) {
options = options || {};
var defaultName = options.defaultName,
defaultValue = options.defaultValue;
var parseToken = function(match,p) {
var n;
if(match[p]) { // Double quoted
n = {string: match[p]};
} else if(match[p+1]) { // Single quoted
n = {string: match[p+1]};
} else if(match[p+2]) { // Double-square-bracket quoted
n = {string: match[p+2]};
} else if(match[p+3]) { // Double-brace quoted
n = {string: match[p+3], evaluated: true};
} else if(match[p+4]) { // Unquoted
n = {string: match[p+4]};
} else if(match[p+5]) { // empty quote
n = {string: ""};
}
return n;
};
this.byPos = [];
var dblQuote = "(?:\"((?:(?:\\\\\")|[^\"])+)\")",
sngQuote = "(?:'((?:(?:\\\\\')|[^'])+)')",
dblSquare = "(?:\\[\\[((?:\\s|\\S)*?)\\]\\])",
dblBrace = "(?:\\{\\{((?:\\s|\\S)*?)\\}\\})",
unQuoted = options.noNames ? "([^\"'\\s]\\S*)" : "([^\"':\\s][^\\s:]*)",
emptyQuote = "((?:\"\")|(?:''))",
skipSpace = "(?:\\s*)",
token = "(?:" + dblQuote + "|" + sngQuote + "|" + dblSquare + "|" + dblBrace + "|" + unQuoted + "|" + emptyQuote + ")",
re = options.noNames ? new RegExp(token,"mg") : new RegExp(skipSpace + token + skipSpace + "(?:(\\:)" + skipSpace + token + ")?","mg"),
match,n,v;
do {
match = re.exec(argString);
if(match) {
n = parseToken(match,1);
if(options.noNames) {
this.byPos.push({n:"", v:n});
} else {
v = parseToken(match,8);
if(v === undefined && defaultName) {
v = n;
n = defaultName;
} else if(v === undefined && defaultValue) {
v = defaultValue;
}
if(n.evaluated === true) {
n = "{{" + n.string + "}}";
} else if (typeof n === "object" && n.hasOwnProperty("string")) {
n = n.string;
}
this.byPos.push({n:n, v:v});
if(options.cascadeDefaults) {
defaultName = n;
defaultValue = v;
}
}
}
} while(match);
this.byName = {};
for(var t=0; t<this.byPos.length; t++) {
n = this.byPos[t].n;
v = this.byPos[t].v;
if(this.byName.hasOwnProperty(n))
this.byName[n].push(v);
else
this.byName[n] = [v];
}
};
// Retrieve the first occurance of a named parameter, or the default if missing
ArgParser.prototype.getValueByName = function(n) {
var v = this.byName[n];
return v && v.length > 0 ? v[0] : null;
};
// Retrieve all the string values as an array
ArgParser.prototype.getStringValues = function() {
var result = [];
for(var t=0; t<this.byPos.length; t++) {
result.push(this.byPos[t].v.string);
}
return result;
};
exports.ArgParser = ArgParser;
})();
+309
View File
@@ -0,0 +1,309 @@
/*\
title: $:/core/modules/utils.js
type: application/javascript
module-type: utils
Various static utility functions.
This file is a bit of a dumping ground; the expectation is that most of these functions will be refactored.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.deepCopy = function(object) {
var result,t;
if($tw.utils.isArray(object)) {
// Copy arrays
result = object.slice(0);
} else if(typeof object === "object") {
result = {};
for(t in object) {
if(object[t] !== undefined) {
result[t] = $tw.utils.deepCopy(object[t]);
}
}
} else {
result = object;
}
return result;
};
exports.extendDeepCopy = function(object,extendedProperties) {
var result = $tw.utils.deepCopy(object),t;
for(t in extendedProperties) {
if(extendedProperties[t] !== undefined) {
result[t] = $tw.utils.deepCopy(extendedProperties[t]);
}
}
return result;
};
exports.formatDateString = function (date,template) {
var t = template.replace(/0hh12/g,$tw.utils.pad($tw.utils.getHours12(date)));
t = t.replace(/hh12/g,$tw.utils.getHours12(date));
t = t.replace(/0hh/g,$tw.utils.pad(date.getHours()));
t = t.replace(/hh/g,date.getHours());
t = t.replace(/mmm/g,$tw.config.dateFormats.shortMonths[date.getMonth()]);
t = t.replace(/0mm/g,$tw.utils.pad(date.getMinutes()));
t = t.replace(/mm/g,date.getMinutes());
t = t.replace(/0ss/g,$tw.utils.pad(date.getSeconds()));
t = t.replace(/ss/g,date.getSeconds());
t = t.replace(/[ap]m/g,$tw.utils.getAmPm(date).toLowerCase());
t = t.replace(/[AP]M/g,$tw.utils.getAmPm(date).toUpperCase());
t = t.replace(/wYYYY/g,$tw.utils.getYearForWeekNo(date));
t = t.replace(/wYY/g,$tw.utils.pad($tw.utils.getYearForWeekNo(date)-2000));
t = t.replace(/YYYY/g,date.getFullYear());
t = t.replace(/YY/g,$tw.utils.pad(date.getFullYear()-2000));
t = t.replace(/MMM/g,$tw.config.dateFormats.months[date.getMonth()]);
t = t.replace(/0MM/g,$tw.utils.pad(date.getMonth()+1));
t = t.replace(/MM/g,date.getMonth()+1);
t = t.replace(/0WW/g,$tw.utils.pad($tw.utils.getWeek(date)));
t = t.replace(/WW/g,$tw.utils.getWeek(date));
t = t.replace(/DDD/g,$tw.config.dateFormats.days[date.getDay()]);
t = t.replace(/ddd/g,$tw.config.dateFormats.shortDays[date.getDay()]);
t = t.replace(/0DD/g,$tw.utils.pad(date.getDate()));
t = t.replace(/DDth/g,date.getDate()+$tw.utils.getDaySuffix(date));
t = t.replace(/DD/g,date.getDate());
var tz = date.getTimezoneOffset();
var atz = Math.abs(tz);
t = t.replace(/TZD/g,(tz < 0 ? '+' : '-') + $tw.utils.pad(Math.floor(atz / 60)) + ':' + $tw.utils.pad(atz % 60));
t = t.replace(/\\/g,"");
return t;
};
exports.getAmPm = function(date) {
return date.getHours() >= 12 ? $tw.config.dateFormats.pm : $tw.config.dateFormats.am;
};
exports.getDaySuffix = function(date) {
return $tw.config.dateFormats.daySuffixes[date.getDate()-1];
};
exports.getWeek = function(date) {
var dt = new Date(date.getTime());
var d = dt.getDay();
if(d === 0) d=7;// JavaScript Sun=0, ISO Sun=7
dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week to calculate weekNo
var n = Math.floor((dt.getTime()-new Date(dt.getFullYear(),0,1)+3600000)/86400000);
return Math.floor(n/7)+1;
};
exports.getYearForWeekNo = function(date) {
var dt = new Date(date.getTime());
var d = dt.getDay();
if(d === 0) d=7;// JavaScript Sun=0, ISO Sun=7
dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week
return dt.getFullYear();
};
exports.getHours12 = function(date) {
var h = date.getHours();
return h > 12 ? h-12 : ( h > 0 ? h : 12 );
};
// Convert & to "&amp;", < to "&lt;", > to "&gt;" and " to "&quot;"
exports.htmlEncode = function(s)
{
if(s) {
return s.toString().replace(/&/mg,"&amp;").replace(/</mg,"&lt;").replace(/>/mg,"&gt;").replace(/\"/mg,"&quot;");
} else {
return "";
}
};
// Converts all HTML entities to their character equivalents
exports.entityDecode = function(s) {
var e = s.substr(1,s.length-2); // Strip the & and the ;
if(e.charAt(0) === "#") {
if(e.charAt(1) === "x" || e.charAt(1) === "X") {
return String.fromCharCode(parseInt(e.substr(2),16));
} else {
return String.fromCharCode(parseInt(e.substr(1),10));
}
} else {
var c = $tw.config.htmlEntities[e];
if(c) {
return String.fromCharCode(c);
} else {
return s; // Couldn't convert it as an entity, just return it raw
}
}
};
exports.unescapeLineBreaks = function(s) {
return s.replace(/\\n/mg,"\n").replace(/\\b/mg," ").replace(/\\s/mg,"\\").replace(/\r/mg,"");
};
/*
* Returns an escape sequence for given character. Uses \x for characters <=
* 0xFF to save space, \u for the rest.
*
* The code needs to be in sync with th code template in the compilation
* function for "action" nodes.
*/
// Copied from peg.js, thanks to David Majda
exports.escape = function(ch) {
var charCode = ch.charCodeAt(0);
if (charCode <= 0xFF) {
return '\\x' + $tw.utils.pad(charCode.toString(16).toUpperCase());
} else {
return '\\u' + $tw.utils.pad(charCode.toString(16).toUpperCase(),4);
}
};
// Turns a string into a legal JavaScript string
// Copied from peg.js, thanks to David Majda
exports.stringify = function(s) {
/*
* ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string
* literal except for the closing quote character, backslash, carriage return,
* line separator, paragraph separator, and line feed. Any character may
* appear in the form of an escape sequence.
*
* For portability, we also escape escape all non-ASCII characters.
*/
return s
.replace(/\\/g, '\\\\') // backslash
.replace(/"/g, '\\"') // double quote character
.replace(/'/g, "\\'") // single quote character
.replace(/\r/g, '\\r') // carriage return
.replace(/\n/g, '\\n') // line feed
.replace(/[\x80-\uFFFF]/g, exports.escape); // non-ASCII characters
};
exports.nextTick = function(fn) {
/*global window: false */
if(typeof window !== "undefined") {
// Apparently it would be faster to use postMessage - http://dbaron.org/log/20100309-faster-timeouts
window.setTimeout(fn,4);
} else {
process.nextTick(fn);
}
};
/*
Determines whether element 'a' contains element 'b'
Code thanks to John Resig, http://ejohn.org/blog/comparing-document-position/
*/
exports.domContains = function(a,b) {
return a.contains ?
a != b && a.contains(b) :
!!(a.compareDocumentPosition(b) & 16);
};
exports.hasClass = function(el,className) {
return el.className.split(" ").indexOf(className) !== -1;
};
exports.addClass = function(el,className) {
var c = el.className.split(" ");
if(c.indexOf(className) === -1) {
c.push(className);
}
el.className = c.join(" ");
};
exports.removeClass = function(el,className) {
var c = el.className.split(" "),
p = c.indexOf(className);
if(p !== -1) {
c.splice(p,1);
el.className = c.join(" ");
}
};
exports.toggleClass = function(el,className,status) {
if(status === undefined) {
status = !exports.hasClass(el,className);
}
if(status) {
exports.addClass(el,className);
} else {
exports.removeClass(el,className);
}
};
exports.applyStyleSheet = function(id,css) {
var el = document.getElementById(id);
if(document.createStyleSheet) { // Older versions of IE
if(el) {
el.parentNode.removeChild(el);
}
document.getElementsByTagName("head")[0].insertAdjacentHTML("beforeEnd",
'&nbsp;<style id="' + id + '" type="text/css">' + css + '</style>'); // fails without &nbsp;
} else { // Modern browsers
if(el) {
el.replaceChild(document.createTextNode(css), el.firstChild);
} else {
el = document.createElement("style");
el.type = "text/css";
el.id = id;
el.appendChild(document.createTextNode(css));
document.getElementsByTagName("head")[0].appendChild(el);
}
}
};
/*
Parse a version number string of the form "1.2.3", "1.2.3.a4", or "1.2.3.b4" into:
{major: <number>, minor: <number>, revision: <number>, alpha: <number>, beta: <number>}
*/
exports.parseVersion = function(versionString) {
versionString = versionString || "0.0.0";
var components = versionString.split("."),
version = {
major: components[0] ? parseInt(components[0],10) : null,
minor: components[1] ? parseInt(components[1],10) : null,
revision: components[2] ? parseInt(components[2],10) : null
};
if(components[3]) {
if(components[3].charAt(0) === "a") {
version.alpha = parseInt(components[3].substr(1),10);
} else if(components[3].charAt(0) === "b") {
version.beta = parseInt(components[3].substr(1),10);
}
}
return version;
};
exports.getVersionString = function() {
return ($tw.version.major || "0") + "." +
($tw.version.minor || "0") + "." +
($tw.version.revision || "0") +
($tw.version.alpha ? ".a" + $tw.version.alpha : "") +
($tw.version.beta ? ".b" + $tw.version.beta : "");
};
/*
Extract the version number from the meta tag or from the boot file
*/
if($tw.isBrowser) {
// Browser version
exports.extractVersionInfo = function() {
var metatags = document.getElementsByTagName("meta");
for(var t=0; t<metatags.length; t++) {
var m = metatags[t];
if(m.name === "tiddlywiki-version") {
return $tw.utils.parseVersion(m.content);
}
}
return $tw.utils.parseVersion();
};
} else {
// Server version
exports.extractVersionInfo = function() {
var fs = require("fs");
return $tw.utils.parseVersion(fs.readFileSync($tw.boot.bootPath + "/version.txt").toString("utf8"));
};
}
})();
+91
View File
@@ -0,0 +1,91 @@
/*\
title: $:/core/modules/wiki.filters.js
type: application/javascript
module-type: wikimethod
Filter method for the $tw.Wiki object
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
//# Extensible filter functions
exports.filters = {
tiddler: function(results,match) {
var title = match[1] || match[4];
if(results.indexOf(title) === -1) {
results.push(title);
}
},
tag: function(results,match) {
},
sort: function(results,match) {
},
limit: function(results,match) {
return results.slice(0,parseInt(match[3],10));
},
field: function(results,match) {
},
is: function(results,match) {
switch(match[3]) {
case "shadowStyle":
this.shadows.forEachTiddler(function(title,tiddler) {
if(tiddler.fields.type === "text/css") {
if(results.indexOf(title) === -1) {
results.push(title);
}
}
});
break;
case "shadowModule":
this.shadows.forEachTiddler(function(title,tiddler) {
if(tiddler.fields.type === "application/javascript" && tiddler.fields["module-type"]) {
if(results.indexOf(title) === -1) {
results.push(title);
}
}
});
break;
case "shadowPlain":
this.shadows.forEachTiddler(function(title,tiddler) {
if((tiddler.fields.type !== "application/javascript" || !tiddler.fields["module-type"]) &&
tiddler.fields.type !== "text/css") {
if(results.indexOf(title) === -1) {
results.push(title);
}
}
});
break;
case "tiddler":
this.forEachTiddler(function(title,tiddler) {
if(results.indexOf(title) === -1) {
results.push(title);
}
});
break;
}
}
};
// Return the tiddler titles from the store that match a filter expression
// filter - filter expression (eg "tiddlertitle [[multi word tiddler title]] [tag[systemConfig]]")
// Returns an array of tiddler titles that match the filter expression
exports.filterTiddlers = function(filter) {
// text or [foo[bar]] or [[tiddler title]]
var re = /([^\s\[\]]+)|(?:\[([ \w\.\-]+)\[([^\]]+)\]\])|(?:\[\[([^\]]+)\]\])/mg;
var results = [];
if(filter) {
var match = re.exec(filter);
while(match) {
var handler = (match[1]||match[4]) ? 'tiddler' : (this.filters[match[2]] ? match[2] : 'field');
this.filters[handler].call(this,results,match);
match = re.exec(filter);
}
}
return results;
};
})();
+339
View File
@@ -0,0 +1,339 @@
/*\
title: $:/core/modules/wiki.js
type: application/javascript
module-type: wikimethod
Extension methods for the $tw.Wiki object
Adds the following properties to the wiki object:
* `eventListeners` is an array of {filter: <string>, listener: fn}
* `changedTiddlers` is a hashmap describing changes to named tiddlers since wiki change events were
last dispatched. Each entry is a hashmap containing two fields:
modified: true/false
deleted: true/false
* `changeCount` is a hashmap by tiddler title containing a numerical index that starts at zero and is
incremented each time a tiddler is created changed or deleted
* `caches` is a hashmap by tiddler title containing a further hashmap of named cache objects. Caches
are automatically cleared when a tiddler is modified or deleted
* `macros` is a hashmap by macro name containing an object class inheriting from the Macro tree node
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.getTiddlerText = function(title,defaultText) {
defaultText = typeof defaultText === "string" ? defaultText : null;
var t = this.getTiddler(title);
return t ? t.fields.text : defaultText;
};
exports.addEventListener = function(filter,listener) {
this.eventListeners = this.eventListeners || [];
this.eventListeners.push({
filter: filter,
listener: listener
});
};
exports.removeEventListener = function(filter,listener) {
for(var c=this.eventListeners.length-1; c>=0; c--) {
var l = this.eventListeners[c];
if(l.filter === filter && l.listener === listener) {
this.eventListeners.splice(c,1);
}
}
};
/*
Causes a tiddler to be marked as changed, incrementing the change count, and triggers event handlers.
This method should be called after the changes it describes have been made to the wiki.tiddlers[] array.
title: Title of tiddler
isDeleted: defaults to false (meaning the tiddler has been created or modified),
true if the tiddler has been created
*/
exports.touchTiddler = function(title,isDeleted) {
// Record the touch in the list of changed tiddlers
this.changedTiddlers = this.changedTiddlers || {};
this.changedTiddlers[title] = this.changedTiddlers[title] || [];
this.changedTiddlers[title][isDeleted ? "deleted" : "modified"] = true;
// Increment the change count
this.changeCount = this.changeCount || {};
if(this.changeCount.hasOwnProperty(title)) {
this.changeCount[title]++;
} else {
this.changeCount[title] = 1;
}
// Trigger events
this.eventListeners = this.eventListeners || [];
if(!this.eventsTriggered) {
var me = this;
$tw.utils.nextTick(function() {
var changes = me.changedTiddlers;
me.changedTiddlers = {};
me.eventsTriggered = false;
for(var e=0; e<me.eventListeners.length; e++) {
var listener = me.eventListeners[e];
listener.listener(changes);
}
});
this.eventsTriggered = true;
}
};
exports.getChangeCount = function(title) {
this.changeCount = this.changeCount || {};
if(this.changeCount.hasOwnProperty(title)) {
return this.changeCount[title];
} else {
return 0;
}
};
exports.deleteTiddler = function(title) {
delete this.tiddlers[title];
this.clearCache(title);
this.touchTiddler(title,true);
};
exports.tiddlerExists = function(title) {
if(this.tiddlers[title]) {
return true;
} else if (this.shadows) {
return this.shadows.tiddlerExists(title);
}
};
exports.addTiddler = function(tiddler) {
// Check if we're passed a fields hashmap instead of a tiddler
if(!(tiddler instanceof $tw.Tiddler)) {
tiddler = new $tw.Tiddler(tiddler);
}
var title = tiddler.fields.title;
this.tiddlers[title] = tiddler;
this.clearCache(title);
this.touchTiddler(title);
};
exports.serializeTiddler = function(title,type) {
var serializer = $tw.Wiki.tiddlerSerializerPlugins[type],
tiddler = this.getTiddler(title);
if(serializer && tiddler) {
return serializer.call(this,tiddler);
} else {
return null;
}
};
/*
Return a sorted array of tiddler titles, optionally filtered by a tag
*/
exports.sortTiddlers = function(sortField,excludeTag) {
sortField = sortField || "title";
var tiddlers = [], t, titles = [];
for(t in this.tiddlers) {
tiddlers.push(this.tiddlers[t]);
}
tiddlers.sort(function(a,b) {
var aa = a.fields[sortField] || 0,
bb = b.fields[sortField] || 0;
if(aa < bb) {
return -1;
} else {
if(aa > bb) {
return 1;
} else {
return 0;
}
}
});
for(t=0; t<tiddlers.length; t++) {
if(!excludeTag || !tiddlers[t].hasTag(excludeTag)) {
titles.push(tiddlers[t].fields.title);
}
}
return titles;
};
exports.forEachTiddler = function(/* [sortField,[excludeTag,]]callback */) {
var arg = 0,
sortField = arguments.length > 1 ? arguments[arg++] : null,
excludeTag = arguments.length > 2 ? arguments[arg++] : null,
callback = arguments[arg++],
titles = this.sortTiddlers(sortField,excludeTag),
t, tiddler;
for(t=0; t<titles.length; t++) {
tiddler = this.tiddlers[titles[t]];
if(tiddler) {
callback.call(this,tiddler.fields.title,tiddler);
}
}
};
exports.getMissingTitles = function() {
return []; // Todo
};
exports.getOrphanTitles = function() {
return []; // Todo
};
exports.getShadowTitles = function() {
return this.shadows ? this.shadows.sortTiddlers() : [];
};
// Return the named cache object for a tiddler. If the cache doesn't exist then the initializer function is invoked to create it
exports.getCacheForTiddler = function(title,cacheName,initializer) {
this.caches = this.caches || {};
var caches = this.caches[title];
if(caches && caches[cacheName]) {
return caches[cacheName];
} else {
if(!caches) {
caches = {};
this.caches[title] = caches;
}
caches[cacheName] = initializer();
return caches[cacheName];
}
};
// Clear all caches associated with a particular tiddler
exports.clearCache = function(title) {
this.caches = this.caches || {};
if(this.caches.hasOwnProperty(title)) {
delete this.caches[title];
}
};
exports.initParsers = function(moduleType) {
// Install the parser modules
moduleType = moduleType || "parser";
$tw.wiki.parsers = {};
var modules = $tw.plugins.moduleTypes[moduleType],
n,m,f;
if(modules) {
for(n=0; n<modules.length; n++) {
m = modules[n];
// Add the parsers defined by the module
for(f in m) {
$tw.wiki.parsers[f] = new m[f]({wiki: this}); // Store an instance of the parser
}
}
}
// Install the wikitext rules
modules = $tw.plugins.moduleTypes["wikitextrule"];
var wikitextparser = this.parsers["text/x-tiddlywiki"];
if(modules && wikitextparser) {
for(n=0; n<modules.length; n++) {
m = modules[n];
// Add the rules defined by the module - currently a hack as we overwrite them
wikitextparser.installRules(m.rules);
}
}
};
/*
Parse a block of text of a specified MIME type
Options are:
defaultType: Default MIME type to use if the specified one is unknown
*/
exports.parseText = function(type,text,options) {
options = options || {};
var parser = this.parsers[type];
if(!parser) {
parser = this.parsers[options.defaultType || "text/x-tiddlywiki"];
}
if(parser) {
return parser.parse(type,text);
} else {
return null;
}
};
exports.parseTiddler = function(title) {
var me = this,
tiddler = this.getTiddler(title);
return tiddler ? this.getCacheForTiddler(title,"parseTree",function() {
return me.parseText(tiddler.fields.type,tiddler.fields.text);
}) : null;
};
/*
Parse text in a specified format and render it into another format
outputType: content type for the output
textType: content type of the input text
text: input text
options: see below
Options are:
defaultType: Default MIME type to use if the specified one is unknown
*/
exports.renderText = function(outputType,textType,text,options) {
var renderer = this.parseText(textType,text,options);
renderer.execute([]);
return renderer.render(outputType);
};
exports.renderTiddler = function(outputType,title) {
var renderer = this.parseTiddler(title);
renderer.execute([],title);
return renderer.render(outputType);
};
/*
Install macro plugins into this wiki
moduleType: Plugin type to install (defaults to "macro")
It's useful to remember what the `new` keyword does. It:
# Creates a new object. It's type is a plain `object`
# Sets the new objects internal, inaccessible, `[[prototype]]` property to the
constructor function's external, accessible, `prototype` object
# Executes the constructor function, passing the new object as `this`
*/
exports.initMacros = function(moduleType) {
moduleType = moduleType || "macro";
$tw.wiki.macros = {};
var MacroClass = require("./treenodes/macro.js").Macro,
modules = $tw.plugins.moduleTypes[moduleType],
n,m,f,
subclassMacro = function(module) {
// Make a copy of the Macro() constructor function
var MacroMaker = function Macro() {
MacroClass.apply(this,arguments);
};
// Set the prototype to a new instance of the prototype of the Macro class
MacroMaker.prototype = new MacroClass();
// Add the prototype methods for this instance of the macro
for(var f in module) {
MacroMaker.prototype[f] = module[f];
}
// Make a more convenient reference to the macro info
return MacroMaker;
};
if(modules) {
for(n=0; n<modules.length; n++) {
m = modules[n];
$tw.wiki.macros[m.info.name] = subclassMacro(m);
}
}
};
/*
Install editor plugins for the edit macro
*/
exports.initEditors = function(moduleType) {
moduleType = moduleType || "editor";
var editMacro = this.macros.edit;
if(editMacro) {
editMacro.editors = {};
$tw.plugins.applyMethods(moduleType,editMacro.editors);
}
};
})();