Introduce plugin module mechanism

See the readme for details
This commit is contained in:
Jeremy Ruston 2012-04-30 12:23:03 +01:00
parent e9e211e51d
commit d93bbbbe7b
101 changed files with 14449 additions and 0 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.DS_Store
tmp/
rabbithole/tmp/

14
rabbithole/bld.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/sh
# build TiddlyWiki5
# create a temporary directory if it doesn't already exist
mkdir -p tmp
mkdir -p tmp/tw5
# cook TiddlyWiki5
node ../tiddlywiki.js \
--recipe $PWD/tw5.com/tw5.com.recipe \
--store tw5.com/store \
--savewiki tmp/tw5 \
|| exit 1

29
rabbithole/copyright.txt Normal file
View File

@ -0,0 +1,29 @@
TiddlyWiki created by Jeremy Ruston, (jeremy [at] jermolene [dot] com)
Copyright (c) Jeremy Ruston 2004-2007
Copyright (c) UnaMesa Association 2007-2012
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
Neither the name of the UnaMesa Association nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.

619
rabbithole/core/boot.js Normal file
View File

@ -0,0 +1,619 @@
/*\
title: $:/boot.js
type: application/javascript
The main boot kernel for TiddlyWiki. This single file creates a barebones TW environment that is just
sufficient to bootstrap the plugins containing the main logic of the applicaiton.
On the server this file is executed directly to boot TiddlyWiki. In the browser, this file is packed
into a single HTML file along with other elements:
# bootprefix.js
# <module definitions>
# boot.js
The module definitions on the browser look like this:
$tw.defineModule("MyModule","moduletype",function(module,exports,require) {
// Module code inserted here
return exports;
});
In practice, each module is wrapped in a separate script block.
\*/
(function() {
/*jslint node: true */
/*global modules: false */
"use strict";
/////////////////////////// Setting up $tw
// Set up $tw global for the server
if(typeof(window) === "undefined" && !global.$tw) {
global.$tw = {isBrowser: false};
}
// Modules store registers all the modules the system has seen
$tw.modules = $tw.modules || {}; // hashmap by module name of {fn:, exports:, moduleType:}
// Plugins store organises module exports by module type
$tw.plugins = $tw.plugins || {}; // hashmap by module type of array of exports
// Config object
$tw.config = $tw.config || {};
// Constants
$tw.config.root = $tw.config.root || "$:"; // Root for module titles (eg, "$:/kernel/boot.js")
$tw.config.pluginSubDir = $tw.config.pluginSubDir || "./modules";
// File extensions
$tw.config.fileExtensions = {
".tid": {type: "application/x-tiddler", encoding: "utf8"},
".js": {type: "application/javascript", encoding: "utf8"},
".jpg": {type: "image/jpeg", encoding: "base64"},
".jpeg": {type: "image/jpeg", encoding: "base64"},
".png": {type: "image/png", encoding: "base64"},
".gif": {type: "image/gif", encoding: "base64"}
};
/////////////////////////// Utility functions
$tw.utils = $tw.utils || {};
/*
Determine if a value is an array
*/
$tw.utils.isArray = function(value) {
return Object.prototype.toString.call(value) == "[object Array]";
}
// Convert "&amp;" to &, "&lt;" to <, "&gt;" to > and "&quot;" to "
$tw.utils.htmlDecode = function(s) {
return s.toString().replace(/&lt;/mg,"<").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
};
/*
Pad a string to a given length with "0"s. Length defaults to 2
*/
$tw.utils.pad = function(value,length) {
length = length || 2;
var s = value.toString();
if(s.length < length)
s = "000000000000000000000000000".substr(0,length - s.length) + s;
return s;
};
// Convert a date into YYYYMMDDHHMM format
$tw.utils.stringifyDate = function(value) {
return value.getUTCFullYear() +
$tw.utils.pad2(value.getUTCMonth() + 1) +
$tw.utils.pad2(value.getUTCDate()) +
$tw.utils.pad2(value.getUTCHours()) +
$tw.utils.pad2(value.getUTCMinutes());
};
// Parse a date from a YYYYMMDDHHMMSSMMM format string
$tw.utils.parseDate = function(value) {
if(typeof value === "string") {
return new Date(Date.UTC(parseInt(value.substr(0,4),10),
parseInt(value.substr(4,2),10)-1,
parseInt(value.substr(6,2),10),
parseInt(value.substr(8,2)||"00",10),
parseInt(value.substr(10,2)||"00",10),
parseInt(value.substr(12,2)||"00",10),
parseInt(value.substr(14,3)||"000",10)));
} else if (value instanceof Date) {
return value;
} else {
return null;
}
};
// Parse a string array from a bracketted list
$tw.utils.parseStringArray = function(value) {
if(typeof value === "string") {
var memberRegExp = /(?:\[\[([^\]]+)\]\])|([^\s$]+)/mg,
results = [],
match;
do {
match = memberRegExp.exec(value);
if(match) {
results.push(match[1] || match[2]);
}
} while(match);
return results;
} else if ($tw.utils.isArray(value)) {
return value;
} else {
return null;
}
};
// Parse a block of name:value fields. The `fields` object is used as the basis for the return value
$tw.utils.parseFields = function(text,fields) {
text.split("\n").forEach(function(line) {
var p = line.indexOf(":");
if(p !== -1) {
var field = line.substr(0, p).trim(),
value = line.substr(p+1).trim();
fields[field] = value;
}
});
return fields;
};
/*
Resolves a source filepath delimited with `/` relative to a specified absolute root filepath.
In relative paths, the special folder name `..` refers to immediate parent directory, and the
name `.` refers to the current directory
*/
$tw.utils.resolvePath = function(sourcepath,rootpath) {
// If the source path starts with ./ or ../ then it is relative to the root
if(sourcepath.substr(0,2) === "./" || sourcepath.substr(0,3) === "../" ) {
var src = sourcepath.split("/"),
root = rootpath.split("/");
// Remove the filename part of the root
root.splice(root.length-1,1);
// Process the source path bit by bit onto the end of the root path
while(src.length > 0) {
var c = src.shift();
if(c === "..") { // Slice off the last root entry for a double dot
if(root.length > 0) {
root.splice(root.length-1,1);
}
} else if(c !== ".") { // Ignore dots
root.push(c); // Copy other elements across
}
}
return root.join("/");
} else {
// If it isn't relative, just return the path
return sourcepath;
}
};
/////////////////////////// Plugin mechanism
/*
Register a single plugin module in the $tw.plugins hashmap
*/
$tw.registerPlugin = function(name,moduleType,moduleExports) {
if(!(moduleType in $tw.plugins)) {
$tw.plugins[moduleType] = [];
}
$tw.plugins[moduleType].push(moduleExports);
};
/*
Register all plugin module tiddlers
*/
$tw.registerPlugins = function() {
for(var title in $tw.wiki.shadows.tiddlers) {
var tiddler = $tw.wiki.shadows.getTiddler(title);
if(tiddler.fields.type === "application/javascript" && tiddler.fields["module-type"] !== undefined) {
$tw.registerPlugin(title,tiddler.fields["module-type"],$tw.executeModule(title));
}
}
};
/*
Get all the plugins of a particular type in a hashmap by their `name` field
*/
$tw.getPluginsByTypeAsHashmap = function(moduleType,nameField) {
nameField = nameField || "name";
var plugins = $tw.plugins[moduleType],
results = {};
if(plugins) {
for(var t=0; t<plugins.length; t++) {
results[plugins[t][nameField]] = plugins[t];
}
}
return results;
};
/*
Apply the exports of the plugin modules of a particular type to a target object
*/
$tw.applyPluginMethods = function(moduleType,object) {
var modules = $tw.plugins[moduleType],
n,m,f;
if(modules) {
for(n=0; n<modules.length; n++) {
m = modules[n];
for(f in m) {
object[f] = m[f];
}
}
}
};
/////////////////////////// Barebones tiddler object
/*
Construct a tiddler object from a hashmap of tiddler fields. If multiple hasmaps are provided they are merged,
taking precedence to the right
*/
$tw.Tiddler = function(/* [fields,] fields */) {
this.fields = {};
for(var c=0; c<arguments.length; c++) {
var arg = arguments[c],
src = (arg instanceof $tw.Tiddler) ? arg.getFields() : arg;
for(var t in src) {
if(src[t] === undefined) {
if(t in this.fields) {
delete this.fields[t]; // If we get a field that's undefined, delete any previous field value
}
} else {
// Parse the field with the associated plugin (if any)
var fieldPlugin = $tw.Tiddler.fieldPlugins[t];
if(fieldPlugin) {
this.fields[t] = fieldPlugin.parse.call(this,src[t]);
} else {
this.fields[t] = src[t];
}
}
}
}
};
$tw.Tiddler.prototype.getFields = function() {
return this.fields;
};
/*
Hashmap of field plugins by plugin name
*/
$tw.Tiddler.fieldPlugins = {};
/*
Install any tiddler field plugin modules
*/
$tw.Tiddler.installPlugins = function() {
$tw.Tiddler.fieldPlugins = $tw.getPluginsByTypeAsHashmap("tiddlerfield");
};
/*
Register and install the built in tiddler field plugins
*/
$tw.registerPlugin($tw.config.root + "/kernel/tiddlerfields/modified","tiddlerfield",{
name: "modified",
parse: $tw.utils.parseDate,
stringify: $tw.utils.stringifyDate
});
$tw.registerPlugin($tw.config.root + "/kernel/tiddlerfields/created","tiddlerfield",{
name: "created",
parse: $tw.utils.parseDate,
stringify: $tw.utils.stringifyDate
});
$tw.registerPlugin($tw.config.root + "/kernel/tiddlerfields/tags","tiddlerfield",{
name: "tags",
parse: $tw.utils.parseStringArray,
stringify: function(value) {
var result = [];
for(var t=0; t<value.length; t++) {
if(value[t].indexOf(" ") !== -1) {
result.push("[[" + value[t] + "]]");
} else {
result.push(value[t]);
}
}
return result.join(" ");
}
});
// Install built in tiddler fields plugins so that they are available immediately
$tw.Tiddler.installPlugins();
/////////////////////////// Barebones wiki store
/*
Construct a wiki store object. Options are:
shadows: optional value to use as the wiki store for shadow tiddlers
*/
$tw.Wiki = function(options) {
options = options || {};
this.shadows = options.shadows !== undefined ? options.shadows : new $tw.Wiki({shadows: null});
this.tiddlers = {};
};
$tw.Wiki.prototype.addTiddler = function(tiddler) {
if(!(tiddler instanceof $tw.Tiddler)) {
tiddler = new $tw.Tiddler(tiddler);
}
this.tiddlers[tiddler.fields.title] = tiddler;
};
$tw.Wiki.prototype.addTiddlers = function(tiddlers) {
for(var t=0; t<tiddlers.length; t++) {
this.addTiddler(tiddlers[t]);
}
};
$tw.Wiki.prototype.getTiddler = function(title) {
var t = this.tiddlers[title];
if(t instanceof $tw.Tiddler) {
return t;
} else if(this.shadows) {
return this.shadows.getTiddler(title);
} else {
return null;
}
};
/*
Hashmap of field plugins by plugin name
*/
$tw.Wiki.tiddlerDeserializerPlugins = {};
/*
Install any tiddler deserializer plugin modules
*/
$tw.Wiki.installPlugins = function() {
$tw.Wiki.tiddlerDeserializerPlugins = $tw.getPluginsByTypeAsHashmap("tiddlerdeserializer");
};
/*
Extracts tiddlers from a typed block of text, specifying default field values
*/
$tw.Wiki.prototype.deserializeTiddlers = function(type,text,srcFields) {
srcFields = srcFields || {};
var deserializer = $tw.Wiki.tiddlerDeserializerPlugins[type],
fields = {};
if(!deserializer) {
// If we didn't find the serializer, try converting it from an extension to a content type
type = $tw.config.fileExtensions[type].type;
deserializer = $tw.Wiki.tiddlerDeserializerPlugins[type];
}
for(var f in srcFields) {
fields[f] = srcFields[f]
};
if(deserializer) {
return deserializer.deserialize.call(this,text,fields);
} else {
// Return a raw tiddler for unknown types
fields.text = text;
return [fields];
}
};
/*
Register the built in tiddler deserializer plugins
*/
$tw.registerPlugin($tw.config.root + "/kernel/tiddlerdeserializer/js","tiddlerdeserializer",{
name: "application/javascript",
deserialize: function(text,fields) {
var headerCommentRegExp = /^\/\*\\\n((?:^[^\n]*\n)+?)(^\\\*\/$\n?)/mg,
match = headerCommentRegExp.exec(text);
fields.text = text;
if(match) {
fields = $tw.utils.parseFields(match[1],fields);
}
return [fields];
}
});
$tw.registerPlugin($tw.config.root + "/kernel/tiddlerdeserializer/tid","tiddlerdeserializer",{
name: "application/x-tiddler",
deserialize: function(text,fields) {
var split = text.indexOf("\n\n");
if(split !== -1) {
fields = $tw.utils.parseFields(text.substr(0,split),fields);
fields.text = text.substr(split + 2);
} else {
fields.text = text;
}
return [fields];
}
});
// Install the tiddler deserializer plugins so they are immediately available
$tw.Wiki.installPlugins();
/////////////////////////// Intermediate initialisation
/*
Create the wiki store for the app
*/
$tw.wiki = new $tw.Wiki();
/////////////////////////// Browser definitions
if($tw.isBrowser) {
/*
Execute the module named 'moduleName'. The name can optionally be relative to the module named 'moduleRoot'
*/
$tw.executeModule = function(moduleName,moduleRoot) {
var name = moduleRoot ? $tw.utils.resolvePath(moduleName,moduleRoot) : moduleName,
require = function(modRequire) {
return $tw.executeModule(modRequire,name);
},
exports = {},
module = $tw.modules[name];
if(!module) {
throw new Error("Cannot find module named '" + moduleName + "' required by module '" + moduleRoot + "', resolved to " + name);
}
if(module.exports) {
return module.exports;
} else {
module.exports = {};
module.fn(module,module.exports,require);
return module.exports;
}
};
/*
Register a deserializer that can extract tiddlers from the DOM
*/
$tw.registerPlugin($tw.config.root + "/kernel/tiddlerdeserializer/dom","tiddlerdeserializer",{
name: "(DOM)",
deserialize: function(node) {
var extractTextTiddler = function(node) {
var e = node.firstChild;
while(e && e.nodeName.toLowerCase() !== "pre") {
e = e.nextSibling;
}
var title = node.getAttribute ? node.getAttribute("title") : null;
if(e && title) {
var attrs = node.attributes,
tiddler = {
text: $tw.utils.htmlDecode(e.innerHTML)
};
for(var i=attrs.length-1; i >= 0; i--) {
if(attrs[i].specified) {
tiddler[attrs[i].name] = attrs[i].value;
}
}
return tiddler;
} else {
return null;
}
},
extractModuleTiddler = function(node) {
if(node.hasAttribute && node.hasAttribute("data-tiddler-title")) {
var text = node.innerHTML,
s = text.indexOf("{"),
e = text.lastIndexOf("}");
if(s !== -1 && e !== -1) {
text = text.substring(s+1,e-1);
}
var fields = $tw.wiki.deserializeTiddlers("application/javascript",text)[0];
fields.title = node.getAttribute("data-tiddler-title");
return fields;
} else {
return null;
}
},
t,tiddlers = [];
for(t = 0; t < node.childNodes.length; t++) {
var tiddler = extractTextTiddler(node.childNodes[t]);
tiddler = tiddler || extractModuleTiddler(node.childNodes[t]);
if(tiddler) {
tiddlers.push(tiddler);
}
}
return tiddlers;
}
});
// Install the tiddler deserializer plugin
$tw.Wiki.installPlugins();
// Load the JavaScript system tiddlers from the DOM
$tw.wiki.shadows.addTiddlers(
$tw.wiki.deserializeTiddlers("(DOM)",document.getElementById("pluginModules"))
);
// Load the main store tiddlers from the DOM
$tw.wiki.addTiddlers(
$tw.wiki.deserializeTiddlers("(DOM)",document.getElementById("storeArea"))
);
// Load the shadow tiddlers from the DOM
$tw.wiki.shadows.addTiddlers(
$tw.wiki.deserializeTiddlers("(DOM)",document.getElementById("shadowArea"))
);
// End of if($tw.isBrowser)
}
/////////////////////////// Server definitions
if(!$tw.isBrowser) {
var fs = require("fs"),
path = require("path"),
vm = require("vm");
/*
Load the tiddlers contained in a particular file (and optionally the accompanying .meta file)
*/
$tw.loadTiddlersFromFile = function(file,basetitle) {
var ext = path.extname(file),
fields = {
title: basetitle
},
extensionInfo = $tw.config.fileExtensions[ext],
data = fs.readFileSync(file).toString(extensionInfo ? extensionInfo.encoding : "utf8"),
tiddlers = $tw.wiki.deserializeTiddlers(ext,data,fields),
metafile = file + ".meta";
if(ext !== ".json" && tiddlers.length === 1 && path.existsSync(metafile)) {
var metadata = fs.readFileSync(metafile).toString("utf8");
if(metadata) {
tiddlers = [$tw.utils.parseFields(metadata,tiddlers[0])];
}
}
$tw.wiki.shadows.addTiddlers(tiddlers);
};
/*
Load all the plugins from the plugins directory
*/
$tw.loadPlugins = function(filepath,basetitle) {
basetitle = basetitle || "$:/plugins";
var stat = fs.statSync(filepath);
if(stat.isDirectory()) {
var files = fs.readdirSync(filepath);
for(var f=0; f<files.length; f++) {
var file = files[f];
if(file !== ".DS_Store" && path.extname(file) !== ".meta") {
$tw.loadPlugins(filepath + "/" + file,basetitle + "/" + file);
}
}
} else if(stat.isFile()) {
$tw.loadTiddlersFromFile(filepath,basetitle);
}
}
/*
Execute the module named 'moduleName'. The name can optionally be relative to the module named 'moduleRoot'
*/
$tw.executeModule = function(moduleName,moduleRoot) {
var name = moduleRoot ? $tw.utils.resolvePath(moduleName,moduleRoot) : moduleName,
module = $tw.modules[name],
tiddler = $tw.wiki.getTiddler(name),
sandbox = {
module: module,
exports: {},
console: console,
$tw: $tw,
require: function(title) {
return $tw.executeModule(title,name);
}
};
if(!tiddler || tiddler.fields.type !== "application/javascript") {
// If there is no tiddler with that name, let node try to find it
return require(moduleName);
}
// Define the module if it is not defined
module = module || {
moduleType: tiddler.fields["module-type"]
};
$tw.modules[name] = module;
// Execute it to get its exports if we haven't already done so
if(!module.exports) {
vm.runInNewContext(tiddler.fields.text,sandbox,tiddler.fields.title);
module.exports = sandbox.exports;
}
// Return the exports of the module
return module.exports;
}
// Load plugins from the plugins directory
$tw.loadPlugins(path.resolve(path.dirname(module.filename),$tw.config.pluginSubDir));
// End of if(!$tw.isBrowser)
}
/////////////////////////// Final initialisation
// Register plugins from the tiddlers we've just loaded
$tw.registerPlugins();
// Now we can properly install all of our extension plugins
$tw.Tiddler.installPlugins();
$tw.Wiki.installPlugins();
// Run any startup plugin modules
var mainModules = $tw.plugins["startup"];
for(var m=0; m<mainModules.length; m++) {
mainModules[m].startup.call($tw);
}
})();

View File

@ -0,0 +1,33 @@
/*\
title: $:/bootprefix.js
type: application/javascript
This file sets up the globals that need to be available when JavaScript modules are executed in the browser. The overall sequence is:
# BootPrefix.js
# <module definitions>
# Boot.js
See Boot.js for further details of the boot process.
*/
// Set up $tw global for the browser
if(window && !window.$tw) {
window.$tw = {isBrowser: true};
}
$tw.modules = {}; // hashmap by module name of {fn:, exports:, moduleType:}
/*
Define a JavaScript tiddler module for later execution
moduleName: name of module being defined
moduleType: type of module
fn: function defining the module, called with the arguments (module,require,exports)
*/
$tw.defineModule = function(moduleName,moduleType,fn) {
$tw.modules[moduleName] = {
moduleType: moduleType,
fn: fn
};
};

View File

@ -0,0 +1,30 @@
/*\
title: $:/core/config.js
type: application/javascript
module-type: config
Core configuration constants
\*/
(function(){
/*jslint node: true */
"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 };
})();

View File

@ -0,0 +1,79 @@
/*\
title: $:/core/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 */
"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;
})();

View File

@ -0,0 +1,44 @@
/*\
title: $:/core/macros/button.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
"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)])];
};
})();

View File

@ -0,0 +1,155 @@
/*\
title: $:/core/macros/chooser.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
"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 [];
};
})();

View File

@ -0,0 +1,24 @@
/*\
title: $:/core/macros/echo.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true */
"use strict";
exports.info = {
name: "echo",
params: {
text: {byPos: 0, type: "text"}
}
};
exports.executeMacro = function() {
return [$tw.Tree.Text(this.params.text)];
};
})();

View File

@ -0,0 +1,79 @@
/*\
title: $:/core/macros/edit/edit.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
"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);
}
}
};
})();

View File

@ -0,0 +1,166 @@
/*\
title: $:/core/macros/edit/editors/bitmapeditor.js
type: application/javascript
module-type: editor
\*/
(function(){
/*jslint node: true, browser: true */
"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;
})();

View File

@ -0,0 +1,105 @@
/*\
title: $:/core/macros/edit/editors/texteditor.js
type: application/javascript
module-type: editor
\*/
(function(){
/*jslint node: true, browser: true */
"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 !window.getSelection().containsNode(this.macroNode.domNode, true);
};
exports["text/x-tiddlywiki"] = TextEditor;
exports["text/plain"] = TextEditor;
})();

View File

@ -0,0 +1,45 @@
/*\
title: $:/core/macros/image.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true */
"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
})];
}
};
})();

View File

@ -0,0 +1,84 @@
/*\
title: $:/core/macros/link.js
type: application/javascript
module-type: macro
Implements the link macro.
\*/
(function(){
/*jslint node: true, browser: true */
"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
};
// 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;
};
})();

View File

@ -0,0 +1,79 @@
/*\
title: $:/core/macros/list.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true */
"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)];
}
};
})();

View File

@ -0,0 +1,176 @@
/*\
title: $:/core/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 */
"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);
}
}
};
})();

View File

@ -0,0 +1,195 @@
/*\
title: $:/core/macros/story.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, jquery: true, browser: true */
"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;
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(var t=0; t<story.tiddlers.length; t++) {
var storyRecord = story.tiddlers[t];
if(storyRecord.title === event.tiddlerTitle && storyRecord.template !== template) {
storyRecord.title = "Draft " + (new Date()) + " of " + event.tiddlerTitle;
storyRecord.template = template;
var 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(var t=0; t<story.tiddlers.length; t++) {
var storyRecord = story.tiddlers[t];
if(storyRecord.title === event.tiddlerTitle && storyRecord.template !== template) {
var 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);
}
}
};
})();

View File

@ -0,0 +1,157 @@
/*\
title: $:/core/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 */
"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("with" in this.params) {
// Parameterised transclusion
var targetTiddler = this.wiki.getTiddler(renderTemplate),
text = targetTiddler.fields.text;
var withTokens = [this.params["with"]];
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);
}
}
};
})();

View File

@ -0,0 +1,22 @@
/*\
title: $:/core/macros/version.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true */
"use strict";
exports.info = {
name: "version",
params: {
}
}
exports.executeMacro = function() {
return [$tw.Tree.Text("5.0.0")];
};
})();

View File

@ -0,0 +1,55 @@
/*\
title: $:/core/macros/video.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true */
"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 [];
}
};
})();

View File

@ -0,0 +1,104 @@
/*\
title: $:/core/macros/view.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true */
"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 [];
};
})();

View File

@ -0,0 +1,104 @@
/*\
title: $:/core/macros/zoomer.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
"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 [];
};
})();

View File

@ -0,0 +1,34 @@
/*\
title: $:/core/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 */
"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;
})();

View File

@ -0,0 +1,49 @@
/*\
title: $:/core/parsers/jsonparser.js
type: application/javascript
module-type: parser
Parses a JSON object into a parse tree
\*/
(function(){
/*jslint node: true */
"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 Dependencies());
};
exports["application/json"] = JSONParser;
})();

View File

@ -0,0 +1,24 @@
/*\
title: $:/core/parsers/plaintextparser.js
type: application/javascript
module-type: parser
Renders plain text tiddlers
\*/
(function(){
/*jslint node: true */
"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;
})();

View File

@ -0,0 +1,56 @@
/*\
title: $:/core/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 */
"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)));
if(match[1]) { // Transclusion
macroNode = $tw.Tree.Macro("tiddler",{
target: match[1]
},[],this.wiki);
} else if(match[2]) { // Macro call
macroNode = $tw.Tree.Macro(match[2],match[3],[],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;
})();

View File

@ -0,0 +1,708 @@
/*\
title: $:/core/parsers/wikitextparser/rules/wikitextrules.js
type: application/javascript
module-type: wikitextrule
\*/
(function(){
/*jslint node: true */
"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);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1]) {
w.nextMatch = this.lookaheadRegExp.lastIndex;
var name = lookaheadMatch[1];
insertMacroCall(w,w.output,name,lookaheadMatch[2]);
}
}
},
{
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;
})();

View File

@ -0,0 +1,185 @@
/*\
title: $:/core/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 */
"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;
})();

View File

@ -0,0 +1,47 @@
/*\
title: $:/core/renderer.js
type: application/javascript
module-type: global
Represents a parse tree and associated data
\*/
(function(){
/*jslint node: true */
"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;
})();

View File

@ -0,0 +1,45 @@
/*\
title: $:/core/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 */
"use strict";
exports.startup = function() {
var modules,n,m,f;
// Set up additional global objects
$tw.applyPluginMethods("global",$tw);
// Wire up plugin modules
$tw.applyPluginMethods("config",$tw.config);
$tw.applyPluginMethods("utils",$tw.utils);
$tw.applyPluginMethods("tiddlermethod",$tw.Tiddler.prototype);
$tw.applyPluginMethods("wikimethod",$tw.Wiki.prototype);
$tw.applyPluginMethods("treeutils",$tw.Tree);
$tw.applyPluginMethods("treenode",$tw.Tree);
// Set up the wiki store
$tw.wiki.initMacros();
$tw.wiki.initEditors();
$tw.wiki.initParsers();
if($tw.isBrowser) {
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 {
console.log("$tw",require("util").inspect($tw,false,8));
}
}
})();

View File

@ -0,0 +1,33 @@
/*\
title: $:/core/tiddler.js
type: application/javascript
module-type: tiddlermethod
Extension methods for the $tw.Tiddler object
\*/
(function(){
/*jslint node: true */
"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();
}
};
})();

View File

@ -0,0 +1,27 @@
/*\
title: $:/core/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(){
/*jshint node: true, browser: true */
"use strict";
exports.Tree = {};
})();

View File

@ -0,0 +1,56 @@
/*\
title: $:/core/tree.utils.js
type: application/javascript
module-type: treeutils
Static utility methods for the $tw.Tree class
\*/
(function(){
/*jshint node: true, browser: true */
"use strict";
/*
Construct an error message
*/
exports.errorNode = function(text) {
return $tw.Tree.Element("span",{
"class": ["label","label-important"]
},[
new TextNode(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)
]);
};
})();

View File

@ -0,0 +1,138 @@
/*\
title: $:/core/treenodes/element.js
type: application/javascript
module-type: treenode
Element nodes
\*/
(function(){
/*jshint node: true, browser: true */
"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 = [];
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) {
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;
})();

View File

@ -0,0 +1,38 @@
/*\
title: $:/core/treenodes/entity.js
type: application/javascript
module-type: treenode
Entity nodes
\*/
(function(){
/*jshint node: true, browser: true */
"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;
})();

View File

@ -0,0 +1,245 @@
/*\
title: $:/core/treenodes/macro.js
type: application/javascript
module-type: treenode
Macro node, used as the base class for macros
\*/
(function(){
/*jshint node: true, browser: true */
"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 lookup the macro class to instantiate
var MacroClass = wiki.macros[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,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;
})();

View File

@ -0,0 +1,79 @@
/*\
title: $:/core/treenodes/node.js
type: application/javascript
module-type: treenode
Base class for all other tree nodes
\*/
(function(){
/*jshint node: true, browser: true */
"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;
})();

View File

@ -0,0 +1,39 @@
/*\
title: $:/core/treenodes/raw.js
type: application/javascript
module-type: treenode
Raw, unparsed HTML nodes
\*/
(function(){
/*jshint node: true, browser: true */
"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;
})();

View File

@ -0,0 +1,38 @@
/*\
title: $:/core/treenodes/text.js
type: application/javascript
module-type: treenode
Text nodes
\*/
(function(){
/*jshint node: true, browser: true */
"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;
})();

View File

@ -0,0 +1,116 @@
/*\
title: $:/core/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 */
"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;
})();

View File

@ -0,0 +1,222 @@
/*\
title: $:/core/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 */
"use strict";
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);
}
}
};
})();

View File

@ -0,0 +1,307 @@
/*\
title: $:/core/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 */
"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);
};
/*
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[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["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;
};
/*
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[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.applyPluginMethods(moduleType,editMacro.editors);
}
};
})();

64
rabbithole/readme.md Normal file
View File

@ -0,0 +1,64 @@
# Down the Rabbit Hole
This is a major refactoring of the TiddlyWiki5 code to introduce a plugin module architecture.
The work has been done by creating a new boot kernel architecture and then methodically transliterating the core TiddlyWiki5 code to fit into that architecture. The work hasn't completely caught up with the old code, and so the two code bases will live alongside one another for a little while the new code is built out.
## Try it out
The easiest way to try out the code is to visit http://tiddlywiki.com/tiddlywiki5/
For trying it out under `node.js`, two batch files are provided:
* `run.sh` boots the kernel and loads the core modules and then outputs the `$tw` global for inspection
* `bld.sh` builds the new TiddlyWiki 5 HTML file (placed in the `tmp/tw5/` directory by default)
## Boot Kernel
The file `core/boot.js` is a barebones TiddlyWiki kernel that is just sufficient to load the core plugin modules and trigger a startup plugin module to load up the rest of the application.
The kernel includes:
* Eight short shared utility functions
* Three methods implementing the plugin module mechanism
* The `$tw.Tiddler` class (and three field definition plugins)
* The `$tw.Wiki` class (and three tiddler deserialization methods)
* Code for the browser to load tiddlers from the HTML DOM
* Code for the server to load tiddlers from the file system
Each module is an ordinary `node.js`-style module, using the `require()` function to access other modules and the `exports` global to return JavaScript values. The boot kernel smooths over the differences between `node.js` and the browser, allowing the same plugin modules to execute in both environments.
In the browser, `core/boot.js` is packed into a template HTML file that contains the following elements in order:
* Ordinary and shadow tiddlers, packed as HTML `<DIV>` elements
* `core/bootprefix.js`, containing a few lines to set up the plugin environment
* Plugin JavaScript modules, packed as HTML `<SCRIPT>` blocks
* `core/boot.js`, containing the boot kernel
On the server, `core/boot.js` is executed directly. It uses the `node.js` local file API to load plugins directly from the file system in the `core/modules` directory. The code loading is performed synchronously for brevity (and because the system is in any case inherently blocked until plugins are loaded).
The boot kernel sets up the `$tw` global variable that is used to store all the state data of the system.
## Core
The 'core' is the boot kernel plus the set of plugin modules that it loads. It contains plugins of the following types:
* `tiddlerfield` - defines the characteristics of tiddler fields of a particular name
* `tiddlerdeserializer` - methods to extract tiddlers from text representations or the DOM
* `startup` - functions to be called by the kernel after booting
* `global` - members of the `$tw` global
* `config` - values to be merged over the `$tw.config` global
* `utils` - general purpose utility functions residing in `$tw.utils`
* `tiddlermethod` - additional methods for the `$tw.Tiddler` class
* `wikimethod` - additional methods for the `$tw.Wiki` class
* `treeutils` - static utility methods for parser tree nodes
* `treenode` - classes of parser tree nodes
* `macro` - macro definitions
* `editor` - interactive editors for different types of content
* `parser` - parsers for different types of content
* `wikitextrule` - individual rules for the wikitext parser
TiddlyWiki5 makes extensive use of JavaScript inheritance:
* Tree nodes defined in `$:/core/treenodes/` all inherit from `$:/core/treenodes/node.js`
* Macros defined in `$:/core/macros/` all inherit from `$:/core/treenodes/macro.js`

5
rabbithole/run.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
# run TiddlyWiki5
node core/boot.js || exit 1

View File

@ -0,0 +1,14 @@
template: tiddlywiki5.template.html
copyright: copyright.txt
jsbootstart: core/bootprefix.js
jsbootend: core/boot.js
pluginmodule: core/modules/*.js
pluginmodule: core/modules/macros/*.js
pluginmodule: core/modules/macros/edit/*.js
pluginmodule: core/modules/macros/edit/editors/*.js
pluginmodule: core/modules/treenodes/*.js
pluginmodule: core/modules/parsers/*.js
pluginmodule: core/modules/parsers/wikitextparser/*.js
pluginmodule: core/modules/parsers/wikitextparser/rules/*.js

View File

@ -0,0 +1,41 @@
<!doctype html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="format-detection" content="telephone=no">
<meta name="copyright" content="
&lt;!--@@copyright@@--&gt;
" />
<title>
<!--@@title@@-->
</title>
<style id="styleArea" type="text/css">
<!--@@style@@-->
</style>
</head>
<body>
<noscript>
<!--@@noscript@@-->
</noscript>
<div id="shadowArea" style="display:none;">
<!--@@shadow@@-->
</div>
<div id="storeArea" style="display:none;">
<!--@@tiddler@@-->
</div>
<script id="jsLibArea" type="text/javascript">
<!--@@jslib@@-->
</script>
<script id="jsBootStartArea" type="text/javascript">
<!--@@jsbootstart@@-->
</script>
<div id="pluginModules" style="display:none;">
<!--@@pluginmodule@@-->
</div>
<script id="jsBootEndArea" type="text/javascript">
<!--@@jsbootend@@-->
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

1737
rabbithole/tw5.com/lib/bootstrap/js/bootstrap.js vendored Executable file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

4
rabbithole/tw5.com/lib/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
title: StoryTiddlers
type: application/json
{
"tiddlers": [
{"title": "HelloThere", "template": "ViewTemplate"},
{"title": "Introduction", "template": "ViewTemplate"},
{"title": "NewWikiTextFeatures", "template": "ViewTemplate"}
]
}

View File

@ -0,0 +1,190 @@
body {
position: relative;
}
.title {
font-weight: bold;
font-size: 40px;
line-height: 48px;
}
.tw-tiddler-missing .title {
font-style: italic;
font-weight: normal;
}
.tw-edit-field {
border: 1px dotted #888;
}
textarea.tw-edit-field {
width: 100%;
}
canvas.tw-edit-field {
cursor: crosshair;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
img, canvas {
max-width: 100%;
}
a.tw-tiddlylink-external::before {
content: "\27a0\00a0";
}
a:hover {
text-decoration: none;
background: #88aaff;
color: #005580;
}
a.tw-tiddlylink {
font-style: normal;
font-weight: normal;
}
a.tw-tiddlylink-resolves {
font-style: normal;
font-weight: bold;
}
a.tw-tiddlylink-missing {
font-style: italic;
}
.splitLabel {
font-size: 9.5px;
margin-right: 5px;
}
.splitLabelLeft {
text-transform: uppercase;
font-weight: bold;
padding: 3px 2px 3px 5px;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
background-color: #ccc;
}
.splitLabelRight {
padding: 3px 5px 3px 2px;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
background-color: #eee;
}
.javascript-source {
border: 1px solid #888;
padding: 18px 18px 18px 18px;
-webkit-box-shadow: inset 1px 1px 1px 1px rgba(0, 0, 0, 0.2);
-moz-box-shadow: inset 1px 1px 1px 1px rgba(0, 0, 0, 0.2);
box-shadow: inset 1px 1px 1px 1px rgba(0, 0, 0, 0.2);
}
.javascript-source .javascript-boolean {
color: #066;
}
.javascript-source .javascript-identifier {
color: #606;
}
.javascript-source .javascript-keyword {
color: #008;
font-weight: bold;
}
.javascript-source .javascript-null {
color: #080;
}
.javascript-source .javascript-numeric {
color: #066;
}
.javascript-source .javascript-punctuator {
color: #660;
}
.javascript-source .javascript-string {
color: #080;
}
.javascript-source .javascript-comment {
color: #800;
padding: 4px 4px 4px 4px;
border: 1px solid #feed77;
background: #feed77;
background: -webkit-gradient(linear,left top,left bottom,color-stop(0%,#dede80),color-stop(7%,#feed77),color-stop(92%,#feed77),color-stop(100%,#dede80));
background: -webkit-linear-gradient(72deg,rgba(255, 255, 255, 0.5) 8%,rgba(255, 255, 255, 0.1) 80%),-webkit-linear-gradient(top,#dede80 0,#feed77 7%,#feed77 92%,#dede80 100%);
background: -moz-linear-gradient(72deg,rgba(255, 255, 255, 0.5) 8%,rgba(255, 255, 255, 0.1) 80%), -moz-linear-gradient(top,#dede80 0,#feed77 7%,#feed77 92%,#dede80 100%);
background: -o-linear-gradient(72deg,rgba(255, 255, 255, 0.5) 8%,rgba(255, 255, 255, 0.1) 80%), -o-linear-gradient(top,#dede80 0,#feed77 7%,#feed77 92%,#dede80 100%);
background: -ms-linear-gradient(72deg,rgba(255, 255, 255, 0.5) 8%,rgba(255, 255, 255, 0.1) 80%), -ms-linear-gradient(top,#dede80 0,#feed77 7%,#feed77 92%,#dede80 100%);
background: linear-gradient(72deg,rgba(255, 255, 255, 0.5) 8%,rgba(255, 255, 255, 0.1) 80%), linear-gradient(top,#dede80 0,#feed77 7%,#feed77 92%,#dede80 100%);
-webkit-box-shadow: 1px 1px 6px rgba(0,0,0,0.4);
-moz-box-shadow: 1px 1px 6px rgba(0,0,0,0.4);
box-shadow: 1px 1px 6px rgba(0,0,0,0.4);
}
.javascript-source .javascript-block-comment {
font-family: Helvetica, Arial, sans-serif;
}
.javascript-source .javascript-line-comment {
font-family: Helvetica, Arial, sans-serif;
}
.navigation-panel div {
position: absolute;
left: 0;
top: 0;
min-width: 16px;
height: 100%; /* Makes the element the same height as the body, since the body is position: relative */
}
.navigation-panel div ul {
position: relative;
top: 0;
margin: 0 0 0 0;
padding: 0px 4px 0px 4px;
list-style: none;
background: #ddd;
border-right: 1px solid #aaa;
}
.navigation-panel div ul li {
padding: 2px 4px 2px 4px;
}
.navigation-panel .selected {
color: #fff;
background: #a55;
}
body {
-webkit-transition: -webkit-transform 0.3s ease-in-out;
}
body.in-zoomer {
-webkit-transition: none;
}
.zoomer-panel div {
position: absolute;
right: 0;
top: 0;
min-width: 16px;
height: 100%; /* Makes the element the same height as the body, since the body is position: relative */
}
body.in-zoomer .zoomer-panel div {
background: rgba(235,235,235,0.5);
}
body.in-zoomer iframe {
visibility: hidden;
}

View File

@ -0,0 +1,9 @@
title: EditTemplate
modifier: JeremyRuston
<<view title>> <<button SaveTiddler label:"done" class:"btn btn-mini btn-success">>
{{title{
<<edit draft.title>>}}}
<<edit tags>>
{{body{
<<edit text>>}}}

View File

@ -0,0 +1,6 @@
title: PageTemplate
{{navigation-panel{<<chooser>>}}}{{zoomer-panel{<<zoomer>>}}}
{{container-fluid{
<<story story:StoryTiddlers>>
}}}

View File

@ -0,0 +1,9 @@
title: ViewTemplate
modifier: JeremyRuston
{{title{
<<view title>>}}}
{{small{
<<view modifier link>> <<view modified date>> <<view tags>> <<button EditTiddler label:"edit" class:"btn btn-mini btn-primary">>}}}
{{body{
<<view text wikified>>}}}

View File

@ -0,0 +1,4 @@
title: BaseColour
type: text/x-tiddlywiki-css
#ffe8d8

View File

@ -0,0 +1,6 @@
title: StyleSheet
type: text/x-tiddlywiki-css
body {
background-color: [[BaseColour]];
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -0,0 +1,3 @@
title: Command Line Sketch.jpg
modifier: JeremyRuston
type: image/jpeg

View File

@ -0,0 +1,60 @@
title: CommandLineInterface
modifier: JeremyRuston
TiddlyWiki5 can be used on the command line to perform an extensive set of operations based on RecipeFiles, TiddlerFiles and TiddlyWikiFiles.
The command line interface for TiddlyWiki5 has been designed to support two distinct usages:
* Cooking (or building) old 2.6.x versions of classic TiddlyWiki from the constituent JavaScript components
* Cooking and serving TiddlyWiki5 itself
!!Usage
`
node tiddlywiki.js <options>
`
The command line options are processed sequentially from left to right. Processing pauses during long operations, like loading a [[recipe file|RecipeFiles]] and all the subrecipes and [[tiddlers|TiddlerFiles]] that it references, and then resumes with the next command line option in sequence.
The state that is carried between options is as follows:
* The set of tiddlers that are currently loaded into memory
* The recipe file last used to load tiddlers (recipe files are used primarily to manage shadow tiddlers)
* The TiddlerFileStore used to store the main content tiddlers. This is independent of the current recipe
The following options are available:
|`--recipe <filepath>` |Loads a specfied `.recipe` file |
|`--load <filepath>` |Load additional tiddlers from 2.x.x TiddlyWiki files (`.html`), `.tiddler`, `.tid`, `.json` or other files |
|`--store <dirpath>` |Load a specified TiddlerFileStore |
|`--links none` |Determines how links will be generated by subsequent options. See below for details |
|`--savewiki <dirpath>` |Saves all the loaded tiddlers as a single file TiddlyWiki called `index.html` and an RSS feed called `index.xml` in a new directory of the specified name |
|`--savetiddler <title> <filename> [<type>]` |Save an individual tiddler as a specified MIME type, defaults to `text/html` |
|`--savetiddlers <outdir>` |Saves all the loaded tiddlers as `.tid` files in the specified directory |
|`--savehtml <outdir>` |Saves all the loaded tiddlers as static, unstyled `.html` files in the specified directory |
|`--servewiki <port>` |Serve the cooked TiddlyWiki over HTTP at `/` |
|`--servetiddlers <port>` |Serve individual tiddlers over HTTP at `/tiddlertitle` |
|`--wikitest <dir> [save]` |Run wikification tests against the tiddlers in the given directory. Include the `save` flag to save the test result files as the new targets |
|`--dumpstore` |Dump the TiddlyWiki store in JSON format |
|`--dumprecipe` |Dump the current recipe in JSON format |
|`--verbose` |verbose output, useful for debugging |
!! Examples
This example loads the tiddlers from a TiddlyWiki HTML file and makes them available over HTTP:
`
node tiddlywiki.js --load mywiki.html --servewiki 127.0.0.1:8000
`
This example cooks a TiddlyWiki from a recipe:
`
node tiddlywiki.js --recipe tiddlywiki.com/index.recipe --savewiki tmp/
`
This example ginsus a TiddlyWiki into its constituent tiddlers:
`
node tiddlywiki.js --load mywiki.html --savetiddlers tmp/tiddlers
`
!! Notes
The HTTP serving functionality built into TiddlyWiki5 is designed to support personal use scenarios. If you want to flexibly and robustly share tiddlers on the web for multiple users, you should use TiddlyWeb or TiddlySpace.
`--servewiki` and `--servetiddlers` are for different purposes and should not be used together. The former is for TiddlyWiki core developers who want to be able to edit the TiddlyWiki source files in a text editor and view the results in the browser by clicking refresh; it is slow because it reloads all the TiddlyWiki JavaScript files each time the page is loaded. The latter is for experimenting with the new wikification engine.
`--wikitest` looks for `*.tid` files in the specified folder. It then wikifies the tiddlers to both "text/plain" and "text/html" format and checks the results against the content of the `*.html` and `*.txt` files in the same directory.
`--links` controls the way that links are generated. Currently, the only option supported is:
`none`: Tiddler links are disabled

View File

@ -0,0 +1,7 @@
title: HelloThere
modifier: JeremyRuston
tags: Introduction
Welcome to TiddlyWiki5, a reboot of TiddlyWiki, the venerable, reusable non-linear personal web notebook first released in 2004. It is a complete interactive wiki that can run from a single HTML file in the browser or as a powerful [[node.js application|What is node.js?]].
TiddlyWiki5 is currently in early beta, which is to say that it is useful but incomplete. You can try out the prototype at http://tiddlywiki.com/tiddlywiki5, get involved in the [[development on GitHub|https://github.com/Jermolene/TiddlyWiki5]] or the discussions on [[the TiddlyWikiDev Google Group|http://groups.google.com/group/TiddlyWikiDev]].

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,3 @@
title: High Level Architecture.svg
modifier: JeremyRuston
type: image/svg+xml

View File

@ -0,0 +1,38 @@
title: ImageTests
This tiddler demonstrates different ways of displaying images
{{{
<<tiddler [[Motovun Jack.jpg]]>>
}}}
Appears as:
<<tiddler [[Motovun Jack.jpg]]>>
{{{
<<image [[Motovun Jack.jpg]] text:"A kitten">>
}}}
Appears as:
<<image [[Motovun Jack.jpg]] text:"A kitten">>
{{{
[img[Motovun Jack.jpg]]
}}}
Appears as:
[img[Motovun Jack.jpg]]
{{{
[img[tooltip|Motovun Jack.jpg]]
}}}
Appears as:
[img[tooltip|Motovun Jack.jpg]]
{{{
[img[tooltip|Motovun Jack.jpg][http://google.com/]]
}}}
Appears as:
[img[tooltip|Motovun Jack.jpg][http://google.com/]]
{{{
<<tiddler [[Tiddler Fishes.svg]]>>
}}}
Appears as:
<<tiddler [[Tiddler Fishes.svg]]>>
{{{
[img[Tiddler Fishes.svg]]
}}}
Appears as:
[img[Tiddler Fishes.svg]]

View File

@ -0,0 +1,33 @@
title: Introduction
TiddlyWiki5 gains new capabilities through a [[completely rebuilt architecture|TiddlyWikiArchitecture]] using the latest features of HTML5 and node.js. It runs natively under node.js, and can also use its own components to construct a version of itself that works entirely within the browser, just as TiddlyWiki has always done.
TiddlyWiki5 also functions as the build system for earlier versions of TiddlyWiki, replacing the elderly Cook and Ginsu tools (see https://github.com/TiddlyWiki/cooker for details). See the CommandLineInterface for details.
{{alert alert-error{
Try out the prototype tiddler chooser by swiping into the left edge of the screen (or hover the mouse over the extreme left edge of the browser window).
}}}
Learning more about TiddlyWiki5:
* WaysToUseTiddlyWiki discusses the various configurations in which TiddlyWiki5 can be used
* Some very rough UserInterfaceSketches showing where it is heading
Some useful tiddlers for feature testing:
* ClockTiddler showing automatic refreshing of tiddlers
* ImageTests showing different ways of embedding images
* SampleData showing how JSON tiddlers are handled
* SampleJavaScript and SampleJavaScriptWithError showing how JavaScript code is displayed
* VideoTests showing how different online video formats can be embedded
* SliderTests showing how sliders work
* TypedBlockTests showing how embedded typed text blocks work
* ShadowTiddlers, including ViewTemplate, EditTemplate and PageTemplate
Technical documentation includes:
* [[Testing]] regimen
* Details of the CommandLineInterface
* Overview of TiddlyWikiArchitecture
** MacroInternals
* Information about TiddlerFiles and RecipeFiles
* NewWikiTextFeatures
All tiddlers:
<<list all>>

View File

@ -0,0 +1,4 @@
title: JackSlider
modifier: JackBeNimble
closed

View File

@ -0,0 +1,40 @@
title: MacroInternals
! Macro Fields
Macros are implemented as conventional JavaScript modules that export a single variable called `macro` that contains these fields:
|!Field Name |!Description |
|`name` |The name of the macro |
|`params` |A hashmap of the parameters accepted by the macro (see below) |
|`events` |An optional hashmap of event handling functions (see below) |
|`execute` |The macro rendering function (see below) |
|`refreshInDom` |The optional macro rerendering function (see below) |
|`dependentAll` |True if the macro needs access to all available tiddlers |
! Macro Parameters
The `params` hashmap provides the following fields about each parameter:
|!Param Field Name |!Description |
|`byName` |Provided if the parameter should be referenced by name. The value can be `true` or `"default"` to indicate that anonymous parameters are acceptable |
|`byPos` |Provided if the parameter should be referenced by its numerical position (0-based) |
|`type` |The type of the parameter, either `text` or `tiddler` (used for dependency tracking) |
|`skinny` |True if the parameter is only dependent on the skinny fields of the target tiddler. The default is `false` which means that the target tiddler is treated as a fat dependency |
! Macro Rendering
The `execute` function should prepare the macro output. It is invoked with `this` pointing to the MacroNode object representing this macro invocation.
!Macro Rerendering
The `refreshInDom` function is called with the following parameters:
|!Param Name |!Description |
|`changes` |Hashmap of `{title: "created|modified|deleted"}` |
!Macro Event Handlers
Event handlers are called with the following parameters:
|!Param Name |!Description |
|`event` |The DOM node containing the existing rendering of the macro |
Event handlers should return `false` if they handle the event (and generally should also call `event.preventDefault()`), and `true` if they do not.

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -0,0 +1,3 @@
title: Main Screen Sketch.jpg
modifier: JeremyRuston
type: image/jpeg

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,3 @@
title: Motovun Jack.jpg
type: image/jpeg
source: http://www.flickr.com/photos/jermy/6292279493/in/photostream

View File

@ -0,0 +1,30 @@
title: NewWikiTextFeatures
modifier: JeremyRuston
It is proposed to extend the existing TiddlyWiki WikiText syntax with the following extensions
# Addition of {{{**bold**}}} character formatting
# Addition of {{{`backtick for code`}}} character formatting
# Addition of WikiCreole-style forced line break, e.g. {{{force\\linebreak}}}
# Addition of WikiCreole-style headings, e.g. {{{==Heading}}}
# Addition of WikiCreole-style headings in tables, e.g. {{{|=|=table|=header|}}}
# Addition of white-listed HTML and SVG tags intermixed with wikitext
# Addition of WikiCreole-style pretty links, e.g. {{{[[description -> link]]}}}
# Addition of multiline macros, e.g.
{{{
<<myMacro
param1: Parameter value
param2: value
"unnamed parameter"
param4: ((
A multiline parameter that can go on for as long as it likes
and contain linebreaks.
))
>>
}}}
# Addition of typed text blocks, e.g.
{{{
$$$.js
return "This will have syntax highlighting applied"
$$$
}}}

View File

@ -0,0 +1,14 @@
title: ReadMe
!Welcome to TiddlyWiki5
<<tiddler HelloThere>>
!Usage
<<tiddler CommandLineInterface>>
!Testing
<<tiddler Testing>>
!Architecture
<<tiddler TiddlyWikiArchitecture>>
!Planned WikiText Features
<<tiddler NewWikiTextFeatures>>
//This `readme` file was automatically generated by TiddlyWiki5//

View File

@ -0,0 +1,46 @@
title: RecipeFiles
modifier: JeremyRuston
{{{.recipe}}} files are text files that list the components to be assembled into a TiddlyWiki. They link to a simple template file that contains the basic structure of the TiddlyWiki document with additional markers to identify parts of the file where ingredients are inserted. Recipes determine which tiddlers should be included in the file, and put together the individual JavaScript files making up the TiddlyWiki core code.
Each line of the recipe file is either a comment starting with `#` or lists an ingredient, prefixed with a tag that describes what to do with the ingredient. Tags either identify a marker within the template file or are special tags that initiate an action.
Recipe files contain lines consisting of a marker, a colon and the pathname of an ingredient:
{{{
marker: filepath
}}}
The filepath is interpreted relative to the directory containing the recipe file. The filename can contain the `*` wildcard character, for example:
{{{
tiddler: ../content/*.tid
}}}
You can use filepaths or URLs to reference recipe files and tiddlers. For example, this recipe cooks the latest TiddlyWiki components directly from the online repositories:
{{{
# Get the recipe direct from GitHub
recipe: https://raw.github.com/TiddlyWiki/tiddlywiki/master/tiddlywikinonoscript.html.recipe
tiddler: http://tiddlywiki-com.tiddlyspace.com/bags/tiddlywiki-com-ref_public/tiddlers.json?fat=1
tiddler: http://tiddlywiki-com.tiddlyspace.com/bags/tiddlywiki-com_public/tiddlers.json?fat=1
}}}
The special marker {{{recipe}}} is used to load a sub-recipe file.
The special marker {{{template}}} is used to identify the HTML template. The HTML template contains markers in one of two alternative forms:
{{{
<!--@@marker@@-->
&lt;!--@@marker@@--&gt;
}}}
The special marker {{{title}}} is automatically filled in with a plain text rendering of the WindowTitle tiddler.
The special marker {{{tiddler}}} is used for the main content tiddlers that will be encoded in TiddlyWiki {{{<DIV>}}} format.
The special marker {{{copy}}} is used to indicate files that should be copied alongside the finished TiddlyWiki file.
Tiddler fields can be overridden by following an ingredient with a set of indented fields:
{{{
tiddler: ../js/myscript.js
title: MyScript
modifier: JoeBloggs
tiddler: ../js/myoldscript.js
title: MyOldScript
tags: [[one long one]] Short
}}}
Note that if tags are applied in this way they completely replace any previous tags on the tiddler.

View File

@ -0,0 +1,26 @@
title: SampleData
type: application/json
{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"age": 34,
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": ["GML", "XML", 23]
},
"GlossSee": "markup"
}
}
}
}
}

View File

@ -0,0 +1,9 @@
title: SampleJavaScript
type: application/javascript
/*
This is an example JavaScript file
*/
function myFunction(param) {
return param * Math.PI; // Perform a calculation
}

View File

@ -0,0 +1,13 @@
title: SampleJavaScriptWithError
type: application/javascript
/*
This is an example JavaScript file
*/
function myFunction(param) {
if(=) { // An error
param = param/17;
}
return param * Math.PI; // Perform a calculation
}

View File

@ -0,0 +1,3 @@
title: ShadowTiddlers
<<list shadowed>>

View File

@ -0,0 +1,4 @@
title: SiteSubtitle
modifier: JeremyRuston
a reusable non-linear personal web notebook

View File

@ -0,0 +1,4 @@
title: SiteTitle
modifier: JeremyRuston
TiddlyWiki5

View File

@ -0,0 +1,16 @@
title: SliderTests
The status of this slider is stored in the tiddler JackSlider:
<<slider JackSlider "Motovun Jack.jpg" "TiddlyWiki Kitten »" "See the kitten">>
And here's another slider that is also keyed to JackSlider:
<<slider JackSlider Introduction "HelloThere »">>
And here's another slider that contains a video, with the state stored in VideoSlider (currently <<tiddler VideoSlider>>):
<<slider VideoSlider VideoTests "VideoTests »">>
And here's a MiniSlider that works with text, rather than a separate tiddler.
<<slider state:MiniSlider label:"Click me! »" content:"This is the //content// of the slider. The state is stored in MiniSlider">>
And here's a slider that doesn't use a state tiddler:
<<slider label:"Click me! »" content:"This is the //content// of the slider. The state is not retained.">>

View File

@ -0,0 +1,9 @@
title: Testing
!Test Scripts
Three test scripts are provided, each as a Mac OS X `*.sh` bash script and a Windows `*.bat` batch file. In each case they should be run with the current directory set to the directory in which they reside.
* `test.sh`/`test.bat` cooks the main tiddlywiki.com recipe for TiddlyWiki 2.6.5 and compares it with the results of the old build process (ie, running cook.rb and then opening the file in a browser and performing a 'save changes' operation). It also runs a series of wikifications tests that work off the data in `test/wikitests/`.
* `tw5.sh`/`tw5.bat` builds TiddlyWiki5 as a static HTML file
* `tw5s.sh`/`tw5s.bat` serves TiddlyWiki5 over HTTP on port 8080

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -0,0 +1,2 @@
title: Tiddler Fishes.svg
type: image/svg+xml

View File

@ -0,0 +1,46 @@
title: TiddlerFiles
modifier: JeremyRuston
Tiddlers can be stored in text files in several different formats. Files containing single tiddlers can also have an auxiliary `.meta` file formatted as a sequence of name:value pairs:
{{{
title: TheTitle
modifier: someone
}}}
!! ~TiddlyWeb-style .tid files
These files consist of a sequence of lines containing name:value pairs, a blank line and then the text of the tiddler. For example:
{{{
title: MyTiddler
modifier: Jeremy
This is the text of my tiddler.
}}}
//The MIME type `application/x-tiddler` is used internally for these files//
!! TiddlyWiki `<DIV>` .tiddler files
Modern `*.tiddler` files look like this:
{{{
<div title="AnotherExampleStyleSheet" modifier="blaine" created="201102111106" modified="201102111310" tags="examples" creator="psd">
<pre>Note that there is an embedded <pre> tag, and line feeds are not escaped.
And, weirdly, there is no HTML encoding of the body.</pre>
</div>
}}}
These `*.tiddler` files are therefore not quite the same as the tiddlers found inside a TiddlyWiki HTML file, where the body is HTML encoded in the expected way.
Older `*.tiddler` files more closely matched the store format used by TiddlyWiki at the time:
{{{
<div tiddler="AnotherExampleStyleSheet" modifier="JeremyRuston" modified="200508181432" created="200508181432" tags="examples">This is an old-school .tiddler file, without an embedded &lt;pre&gt; tag.\nNote how the body is &quot;HTML encoded&quot; and new lines are escaped to \\n</div>
}}}
//The MIME type `application/x-tiddler-html-div` is used internally for these files//
!! ~TiddlyWeb-style JSON files
These files are a straightforward array of hashmaps of name:value fields. Currently only these known fields are processed: `title`, `text`, `created`, `creator`, `modified`, `modifier`, `type` and `tags`.
//The MIME type `application/json` is used internally for these files//
!! TiddlyWiki HTML files
TiddlyWiki HTML files contain a collection of tiddlers encoded in `<DIV>` format.
//The MIME type `application/x-tiddlywiki` is used internally for these files//

View File

@ -0,0 +1,7 @@
title: TiddlyWiki
modifier: JeremyRuston
modified: 20120211110622
TiddlyWiki is based on the idea of making information more useful by modelling it in the smallest meaningful semantic units, referred to as "tiddlers". Structure comes from links, tags, and stories (sequences of tiddlers). Tiddlers use a wikitext notation that concisely represents a wide range of text formatting and hypertext features.
TiddlyWiki has earned an enduring role as a tool that people [[love using|Raves]] for its rich, interactive interface to manipulate complex data with structure that doesn't easily fit into conventional tools like spreadsheets or wordprocessors. Because people can use it without needing any complicated server infrastructure, and because it is [[open source|What is open source?]], it has bought unprecedented freedom to people to keep their precious information under their own control. TiddlyWiki was originally created by JeremyRuston and is now a thriving [[open source|What is open source?]] project with a busy [[Community]] of independent developers.

View File

@ -0,0 +1,108 @@
title: TiddlyWikiArchitecture
modifier: JeremyRuston
!! Overview
The heart of TiddlyWiki can be seen as an extensible representation transformation engine. Given the text of a tiddler and its associated MIME type, the engine can produce a rendering of the tiddler in a new MIME type. Furthermore, it can efficiently selectively update the rendering to track any changes in the tiddler or its dependents.
The most important transformations are from `text/x-tiddlywiki` wikitext into `text/html` or `text/plain` but the engine is used throughout the system for other transformations, such as converting images for display in HTML, sanitising fragments of JavaScript, and processing CSS.
The key feature of wikitext is the ability to include one tiddler within another (usually referred to as //transclusion//). For example, one could have a tiddler called //Disclaimer// that contains the boilerplate of a legal disclaimer, and then include it within lots of different tiddlers with the macro call `<<tiddler Disclaimer>>`. This simple feature brings great power in terms of encapsulating and reusing content, and evolving a clean, usable implementation architecture to support it efficiently is a key objective of the TiddlyWiki5 design.
It turns out that the transclusion capability combined with the selective refreshing mechanism provides a good foundation for building TiddlyWiki's user interface itself. Consider, for example, the StoryMacro in its simplest form:
{{{
<<story story:MyStoryTiddler>>
}}}
The story macro looks for a list of tiddler titles in the tiddler `MyStoryTiddler`, and displays them in sequence. The subtle part is that subsequently, if `MyStoryTiddler` changes, the `<<story>>` macro is selectively re-rendered. So, to navigate to a new tiddler, code merely needs to add the name of the tiddler and a line break to the top of `MyStoryTiddler`:
{{{
var storyTiddler = store.getTiddler("MyStoryTiddler");
store.addTiddler(new Tiddler(storyTiddler,{text: navigateTo + "\n" + storyTiddler.text}));
}}}
The mechanisms that allow all of this to work are fairly intricate. The sections below progressively build the key architectural concepts of TiddlyWiki5 in a way that should provide a good basis for exploring the code directly.
!! Tiddlers
Tiddlers are an immutable dictionary of name:value pairs called fields.
The only field that is required is the {{{title}}} field, but useful tiddlers also have a {{{text}}} field, and some or all of the standard fields {{{modified}}}, {{{modifier}}}, {{{created}}}, {{{creator}}}, {{{tags}}} and {{{type}}}.
Hardcoded in the system is the knowledge that the `tags` field is a string array, and that the `modified` and `created` fields are JavaScript `Date` objects. All other fields are strings.
The {{{type}}} field identifies the representation of the tiddler text with a MIME type.
!! ~WikiStore
Groups of uniquely titled tiddlers are contained in WikiStore objects.
The WikiStore also manages the plugin modules used for macros, and operations like serializing, deserializing, parsing and rendering tiddlers.
Each WikiStore is connected to another shadow store that is used to provide default content. Under usual circumstances, when an attempt is made to retrieve a tiddler that doesn't exist in the store, the search continues into its shadow store (and so on, if the shadow store itself has a shadow store).
!! ~WikiStore Events
Clients can register event handlers with the WikiStore object. Event handlers can be registered to be triggered for modifications to any tiddler in the store, or with a filter to only be invoked when a particular tiddler or set of tiddlers changes.
Whenever a change is made to a tiddler, the wikistore registers a `nexttick` handler (if it hasn't already done so). The `nexttick` handler looks back at all the tiddler changes, and dispatches any matching event handlers.
!! Parsing and Rendering
TiddlyWiki parses the content of tiddlers to build an internal tree representation that is used for several purposes:
* Rendering a tiddler to other formats (e.g. converting wikitext to HTML)
* Detecting outgoing links from a tiddler, and from them...
* ...computing incoming links to a tiddler
* Detecting tiddlers that are orphans with no incoming links
* Detecting tiddlers that are referred to but missing
The parse tree is built when needed, and then cached by the WikiStore until the tiddler changes.
TiddlyWiki5 uses multiple parsers:
* Wikitext ({{{text/x-tiddlywiki}}}) in `js/WikiTextParser.js`
* JavaScript ({{{text/javascript}}}) in `js/JavaScriptParser.js`
* Images ({{{image/png}}} and {{{image/jpg}}}) in `js/ImageParser.js`
* JSON ({{{application/json}}}) in `js/JSONParser.js`
Additional parsers are planned:
* CSS ({{{text/css}}})
* Recipe ({{{text/x-tiddlywiki-recipe}}})
One global instance of each parser is instantiated in `js/App.js` and registered with the main WikiStore object.
The parsers are all used the same way:
$$$.js
var parseTree = parser.parse(type,text) // Parses the text and returns a parse tree object
$$$
The parse tree object exposes the following fields:
$$$.js
var renderer = parseTree.compile(type); // Compiles the parse tree into a renderer for the specified MIME type
console.log(parseTree.toString(type)); // Returns a readable string representation of the parse tree (either `text/html` or `text/plain`)
var dependencies = parseTree.dependencies; // Gets the dependencies of the parse tree (see below)
$$$
The dependencies are returned as an object like this:
{{{
{
tiddlers: {"tiddlertitle1": true, "tiddlertitle2": false},
dependentAll: false
}
}}}
The `tiddlers` field is a hashmap of the title of each tiddler that is linked or included in the current one. The value is `true` if the tiddler is a //'fat'// dependency (ie the text is included in some way) or `false` if the tiddler is a //`skinny`// dependency.
The `dependentAll` field is used to indicate that the tiddler contains a macro that scans the entire pool of tiddlers (for example the `<<list>>` macro), and is potentially dependent on any of them. The effect is that the tiddler should be rerendered whenever any other tiddler changes.
!! Rendering
The `parseTree.compile(type)` method returns a renderer object that contains a JavaScript function that generates the new representation of the original parsed text.
The renderer is invoked as follows:
$$$.js
var renderer = parseTree.compile("text/html");
var html = renderer.render(tiddler,store);
$$$
The `tiddler` parameter to the `render` method identifies the tiddler that is acting as the context for this rendering -- for example, it provides the fields displayed by the `<<view>>` macro. The `store` parameter is used to resolve any references to other tiddlers.
!! Rerendering
When rendering to the HTML/SVG DOM in the browser, TiddlyWiki5 also allows a previous rendering to be selectively updated in response to changes in dependent tiddlers. At the moment, only the WikiTextRenderer supports rerendering.
The rerender method on the renderer is called as follows:
{{{
var node = document.getElementById("myNode");
var renderer = parseTree.compile("text/html");
myNode.innerHTML = renderer.render(tiddler,store);
// And then, later:
renderer.rerender(node,changes,tiddler,store,renderStep);
}}}
The parameters to `rerender()` are:
|!Name |!Description |
|node |A reference to the DOM node containing the rendering to be rerendered |
|changes |A hashmap of `{title: "created|modified|deleted"}` indicating which tiddlers have changed since the original rendering |
|tiddler |The tiddler providing the rendering context |
|store |The store to use for resolving references to other tiddlers |
|renderStep |See below |
Currently, the only macro that supports rerendering is the `<<story>>` macro; all other macros are rerendered by calling the ordinary `render()` method again. The reason that the `<<story>>` macro goes to the trouble of having a `rerender()` method is so that it can be carefully selective about not disturbing tiddlers in the DOM that aren't affected by the change. If there were, for instance, a video playing in one of the open tiddlers it would be reset to the beginning if the tiddler were rerendered.

View File

@ -0,0 +1,8 @@
title: TiddlyWikiInternals
This diagram gives a birds eye view of the JavaScript files making up TiddlyWiki5:
<<tiddler [[High Level Architecture.svg]]>>
See TiddlyWikiArchitecture for an overview of the architecture of the system.

View File

@ -0,0 +1,55 @@
title: TypedBlockTests
WikiText can include blocks of text that are rendered with an explicit MIME type like this:
{{{
$$$application/javascript
//This is some JavaScript
return 2 + "one";
$$$
}}}
This renders as:
$$$application/javascript
//This is some JavaScript
return 2 + "one";
$$$
It is also possible to abbreviate the MIME type to a file extension. For example:
{{{
$$$.svg
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="100">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
</svg>
$$$
}}}
This renders as:
$$$.svg
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="100">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
</svg>
$$$
And, finally, a JSON example:
{{{
$$$.json
{"teapot": "brown","inside":["milk","sugar",23]}
$$$
}}}
Which renders as:
$$$.json
{"teapot": "brown","inside":["milk","sugar",23]}
$$$
Unknown types render as plain text:
{{{
$$$text/unknown
Some plain text, which will not be //formatted//.
$$$
}}}
Which renders as:
$$$text/unknown
Some plain text, which will not be //formatted//.
$$$

View File

@ -0,0 +1,35 @@
title: UserInterfaceSketches
modifier: JeremyRuston
!Introduction
At its core, TiddlyWiki is a representation transformation engine with a cunning line in selective live updating. The engine could be used to build many different user interface experiences. The role of the core UI is not to be all things to everyone, it is just to provide a beautiful and compelling interactive experience for the activities that experience has shown to be popular.
!Principles
* The best UI is transparent: just the content, no chrome unless you need it
* Design for touch and mobile first, keyboard and mouse second, and play well with screenreaders
!Sketches
The basic idea is to preserve the page itself for the content, styled and arranged as the user wants. The interactive features necessary to use the application are overlaid when needed, and then get out of the way:
[img[Main Screen Sketch.jpg]]
Swiping in from the left of the screen reveals the primary navigation chooser, an explorable fisheye menu that gives you access to all your tiddlers, categorised and linked to make them easy to find.
The sketch below shows the main screen layout along with the contents of the //view switcher//. This allows the user to select between different ways of looking at tiddlers:
* As a packed mosaic of images and text (like the jQuery Masonry plugin)
* As a row of stacks for Kanban type sorting activities
* As a lines-n-boxes diagram, with a plugin mechanism for laying them out
* As a screensaver, where random tiddlers float up the screen
* As a timeline/calendar
* As a stack of cards that are sorted one by one
* As a horizontal row of columns
* As a vertical stack of tiddlers (like classic TiddlyWiki)
* As a zoomable user interface (like Cecily)
* As a command line (see below)
[img[View Switcher Sketch.jpg]]
!Command Line
The idea of the command line is to give people an environment that makes it easy to interactively process tiddlers, performing one-off imports and exports of data, or batch operations on many tiddlers at once. It was somewhat inspired by [[this wonderful experiment on reimagining the command line terminal|http://acko.net/blog/on-termkit/]].
[img[Command Line Sketch.jpg]]

View File

@ -0,0 +1,7 @@
title: VideoTests
Here is a collection of embedded videos. Try opening and closing tiddlers while the videos are playing; they should not be affected.
<<video src:32001208 type:vimeo>>
<<video src:wvWHnK2FiCk type:youtube>>
<<video src:santa_claus_conquers_the_martians_ipod type:archiveorg>>

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -0,0 +1,3 @@
title: View Switcher Sketch.jpg
modifier: JeremyRuston
type: image/jpeg

View File

@ -0,0 +1,58 @@
title: WaysToUseTiddlyWiki
!Introduction
TiddlyWiki5 will be deployable in a wide variety of different arrangements to suit different needs. The archetypal configurations are listed below along with a measure of how much complexity is involved in setting up each one -- once set up properly, the TiddlyWiki user experience should be consistent regardless of configuration.
|!Name |!Description |!Barriers |
|SelfContained |TiddlyWiki's familiar model of a self-contained HTML file containing application logic and application data, with the ability to save changes in most browsers |Low: you just need a browser |
|ExplodedFolder |An extension of TiddlyWiki's classic model that explodes the single file into its constituent parts as separate files |Low: you just need a browser |
|LocalHelperApp |Running TiddlyWiki5 as a node.js helper application on your PC |Medium: you need to install node.js, and run a batch file |
|PrivateServerApp |Running TiddlyWiki5 as a node.js application on a cloud server |High: you need to understand how to install and maintain software on a server |
|Component |Using TiddlyWiki5's representation transformation engine and user interface layer as components in a larger system |Very High: you need to be a reasonably experienced software developer |
|CustomBrowser |Building a custom web browser around TiddlyWiki to let it work optimally on a particular platform |Very High: you need to be a pretty experienced software developer |
!SelfContained Configuration
This is the familiar form of TiddlyWiki: a single HTML file that runs in the browser containing both the application logic and your data. On many browsers you can edit your content and save your changes without any special add-ins. Even without the ability to save changes, this configuration offers compelling advantages as a content distribution medium by allowing you distribute a fully interactive web experience with just a dumb, reliable static web server.
The SelfContained configuration performs well on the web. By packing all the required JavaScript, CSS and HTML into a single file we avoid the latency that comes from traditional websites bringing in external resources as they load in the browser.
!ExplodedFolder Configuration
This form lays out your data and the components of TiddlyWiki as separate files:
{{{
folder/
index.html
tiddlywiki.js
content/
MyTiddler1.tid
MyTiddler2.jpg
MyTiddler3.svg
}}}
You can just open the `index.html` in the browser. The content tiddlers are lazily loaded as required, with the HTML file acting as a literal index, listing the titles and other metadata about each tiddler. In some browsers you will be able to make changes to the data through the browser too.
The key advantage of this configuration is that it plays well with existing desktop PC file tools, including source code control systems like Git and Subversion.
!LocalHelperApp Configuration
This configuration uses node.js to enable TiddlyWiki5 to run as a local web server. This means that you interact with it from your browser, typically going to a URL like `127.0.0.1:8080`.
On most operating systems, installing node.js is a single step operation - see http://nodejs.org/ for details. Once installed, running TiddlyWiki requires a simple command line that you can type in the terminal or assign to a shortcut:
{{{
node tiddlywiki myfolder/index.html
}}}
In this configuration, TiddlyWiki5 can store data in either a single SelfContained file, or in the ExplodedFolder layout.
!PrivateServerApp Configuration
TiddlyWiki5 can also be run under node.js on an Internet server hosted by a cloud provider such as Amazon or Joyent. In this configuration, your data is stored on a private computer in the cloud, and you access it securely using a browser. In this way you will be able to access your data from any device, including mobile devices.
!Component Configuration
Although TiddlyWiki5 can work as an HTTP server, it is not designed for large, high traffic installations. Instead, the representation transformation engine at the heart of TiddlyWiki5 can be used as a component within more elaborate HTTP servers to enable them to offer TiddlyWiki's flexible WikiText features.
Work is underway to use TiddlyWiki5 within TiddlyWeb and TiddlySpace:
https://github.com/cdent/tw5ikifier
!CustomBrowser Configuration
Many mobile platforms lack direct support in the browser to enable TiddlyWiki to save changes locally. One way around this is to use an online flavour of TiddlyWiki. A relatively straightforward option on many platforms is to write a custom application that embeds TiddlyWiki within a web browser control. For example, see these applications that work in this way with classic TiddlyWiki on iOS and Android:
* [[AndTidWiki|https://market.android.com/details?id=de.mgsimon.android.andtidwiki]] for Android Devices
* [[TWMobile|http://itunes.apple.com/gb/app/twmobile/id381945222?mt=8]] for iPad
* [[TWEdit|http://itunes.apple.com/gb/app/twedit/id409607956?mt=8]] for iPhone, iPad and iPod Touch
* [[tiddlyNotes|http://itunes.apple.com/us/app/tiddlynotes-lite/id465933435?mt=8]] for iPhone, iPad and iPod Touch

View File

@ -0,0 +1,10 @@
recipe: ../tiddlywiki5.recipe
style: lib/bootstrap/css/bootstrap.css
style: shadows/css/*.css
shadow: shadows/*.tid
shadow: shadows/templates/*.tid
shadow: shadows/tiddlycss/*.tid
#jslib: lib/jquery.js
#jslib: lib/bootstrap/js/bootstrap.js

Some files were not shown because too many files have changed in this diff Show More