1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2025-12-10 02:38:06 +00:00

Part two of turning the rabbit hole inside out

This commit is contained in:
Jeremy Ruston
2012-05-05 22:57:21 +01:00
parent ee6d7b5cc5
commit 9465da4335
128 changed files with 0 additions and 0 deletions

646
core/boot.js Normal file
View File

@@ -0,0 +1,646 @@
/*\
title: $:/core/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, browser: true */
/*global modules: false, $tw: false */
"use strict";
/////////////////////////// Setting up $tw
// Set up $tw global for the server
if(typeof(window) === "undefined" && !global.$tw) {
global.$tw = {isBrowser: false};
}
// Boot information
$tw.boot = {};
// Modules store registers all the modules the system has seen
$tw.modules = $tw.modules || {};
$tw.modules.titles = $tw.modules.titles || {}; // hashmap by module title of {fn:, exports:, moduleType:}
// Plugins store organises module exports by module type
$tw.plugins = $tw.plugins || {};
$tw.plugins.moduleTypes = $tw.plugins.moduleTypes || {}; // 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.bootModuleSubDir = $tw.config.bootModuleSubDir || "./modules";
$tw.config.wikiPluginsSubDir = $tw.config.wikiPluginsSubDir || "./plugins";
// File extensions
$tw.config.fileExtensions = {
".tid": {type: "application/x-tiddler", encoding: "utf8"},
".txt": {type: "text/plain", encoding: "utf8"},
".css": {type: "text/css", encoding: "utf8"},
".html": {type: "text/html", 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.pad(value.getUTCMonth() + 1) +
$tw.utils.pad(value.getUTCDate()) +
$tw.utils.pad(value.getUTCHours()) +
$tw.utils.pad(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.moduleTypes hashmap
*/
$tw.plugins.registerPlugin = function(name,moduleType,moduleExports) {
if(!(moduleType in $tw.plugins.moduleTypes)) {
$tw.plugins.moduleTypes[moduleType] = [];
}
$tw.plugins.moduleTypes[moduleType].push(moduleExports);
};
/*
Register all plugin module tiddlers
*/
$tw.plugins.registerPluginModules = 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.plugins.registerPlugin(title,tiddler.fields["module-type"],$tw.modules.execute(title));
}
}
};
/*
Get all the plugins of a particular type in a hashmap by their `name` field
*/
$tw.plugins.getPluginsByTypeAsHashmap = function(moduleType,nameField) {
nameField = nameField || "name";
var plugins = $tw.plugins.moduleTypes[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.plugins.applyMethods = function(moduleType,object) {
var modules = $tw.plugins.moduleTypes[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 = {};
/*
Register and install the built in tiddler field plugins
*/
$tw.plugins.registerPlugin($tw.config.root + "/kernel/tiddlerfields/modified","tiddlerfield",{
name: "modified",
parse: $tw.utils.parseDate,
stringify: $tw.utils.stringifyDate
});
$tw.plugins.registerPlugin($tw.config.root + "/kernel/tiddlerfields/created","tiddlerfield",{
name: "created",
parse: $tw.utils.parseDate,
stringify: $tw.utils.stringifyDate
});
$tw.plugins.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.fieldPlugins = $tw.plugins.getPluginsByTypeAsHashmap("tiddlerfield");
/////////////////////////// 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 = {};
/*
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 && $tw.config.fileExtensions[type]) {
// 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];
}
if(!deserializer) {
// If we still don't have a deserializer, treat it as plain text
deserializer = $tw.Wiki.tiddlerDeserializerPlugins["text/plain"];
}
for(var f in srcFields) {
fields[f] = srcFields[f];
}
if(!fields.type) {
fields.type = type;
}
if(deserializer) {
return deserializer.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.plugins.registerPlugin($tw.config.root + "/kernel/tiddlerdeserializer/js","tiddlerdeserializer",{
"application/javascript": 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].split("\n\n")[0],fields);
}
return [fields];
}
});
$tw.plugins.registerPlugin($tw.config.root + "/kernel/tiddlerdeserializer/tid","tiddlerdeserializer",{
"application/x-tiddler": 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.plugins.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerPlugins);
/////////////////////////// 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.modules.execute = function(moduleName,moduleRoot) {
var name = moduleRoot ? $tw.utils.resolvePath(moduleName,moduleRoot) : moduleName,
require = function(modRequire) {
return $tw.modules.execute(modRequire,name);
},
exports = {},
module = $tw.modules.titles[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.plugins.registerPlugin($tw.config.root + "/kernel/tiddlerdeserializer/dom","tiddlerdeserializer",{
"(DOM)": 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(node.hasAttribute("data-module") && s !== -1 && e !== -1) {
text = text.substring(s+1,e-1);
}
var fields = {text: text},
attributes = node.attributes;
for(var a=0; a<attributes.length; a++) {
if(attributes[a].nodeName.substr(0,13) === "data-tiddler-") {
fields[attributes[a].nodeName.substr(13)] = attributes[a].nodeValue;
}
}
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.plugins.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerPlugins);
// 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");
$tw.boot.bootFile = path.basename(module.filename);
$tw.boot.bootPath = path.dirname(module.filename);
$tw.boot.wikiPath = process.cwd();
/*
Load the tiddlers contained in a particular file (and optionally the accompanying .meta file)
*/
$tw.plugins.loadTiddlersFromFile = function(file,fields,wiki) {
wiki = wiki || $tw.wiki;
var ext = path.extname(file),
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])];
}
}
wiki.addTiddlers(tiddlers);
};
/*
Load all the plugins from the plugins directory
*/
$tw.plugins.loadPluginsFromFolder = function(filepath,basetitle,excludeRegExp,wiki) {
basetitle = basetitle || "$:/plugins";
excludeRegExp = excludeRegExp || /^\.DS_Store$|.meta$/;
wiki = wiki || $tw.wiki.shadows;
if(path.existsSync(filepath)) {
var stat = fs.statSync(filepath);
if(stat.isDirectory()) {
var files = fs.readdirSync(filepath);
// Look for a tiddlywiki.plugin file
if(files.indexOf("tiddlywiki.plugin") !== -1) {
// If so, process the files it describes
var pluginInfo = JSON.parse(fs.readFileSync(filepath + "/tiddlywiki.plugin").toString("utf8"));
for(var p=0; p<pluginInfo.tiddlers.length; p++) {
$tw.plugins.loadTiddlersFromFile(path.resolve(filepath,pluginInfo.tiddlers[p].file),pluginInfo.tiddlers[p].fields,wiki);
}
} else {
// If not, read all the files in the directory
for(var f=0; f<files.length; f++) {
var file = files[f];
if(!excludeRegExp.test(file)) {
$tw.plugins.loadPluginsFromFolder(filepath + "/" + file,basetitle + "/" + file,excludeRegExp,wiki);
}
}
}
} else if(stat.isFile()) {
$tw.plugins.loadTiddlersFromFile(filepath,{title: basetitle},wiki);
}
}
};
/*
Execute the module named 'moduleName'. The name can optionally be relative to the module named 'moduleRoot'
*/
$tw.modules.execute = function(moduleName,moduleRoot) {
var name = moduleRoot ? $tw.utils.resolvePath(moduleName,moduleRoot) : moduleName,
module = $tw.modules.titles[name],
tiddler = $tw.wiki.getTiddler(name),
sandbox = {
module: module,
exports: {},
console: console,
process: process,
$tw: $tw,
require: function(title) {
return $tw.modules.execute(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.titles[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.plugins.loadPluginsFromFolder(path.resolve($tw.boot.bootPath,$tw.config.bootModuleSubDir));
// Load any plugins in the wiki plugins directory
$tw.plugins.loadPluginsFromFolder(path.resolve($tw.boot.wikiPath,$tw.config.wikiPluginsSubDir));
// HACK: to be replaced when we re-establish sync plugins
// Load shadow tiddlers from wiki shadows directory
$tw.plugins.loadPluginsFromFolder(path.resolve($tw.boot.wikiPath,"./shadows"));
// Load tiddlers from wiki tiddlers directory
$tw.plugins.loadPluginsFromFolder(path.resolve($tw.boot.wikiPath,"./tiddlers"),null,null,$tw.wiki);
// End of if(!$tw.isBrowser)
}
/////////////////////////// Final initialisation
// Register plugins from the tiddlers we've just loaded
$tw.plugins.registerPluginModules();
// Run any startup plugin modules
var mainModules = $tw.plugins.moduleTypes["startup"];
for(var m=0; m<mainModules.length; m++) {
mainModules[m].startup.call($tw);
}
})();

33
core/bootprefix.js Normal file
View File

@@ -0,0 +1,33 @@
/*\
title: $:/core/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 = {titles: {}}; // 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.modules.define = function(moduleName,moduleType,fn) {
$tw.modules.titles[moduleName] = {
moduleType: moduleType,
fn: fn
};
};

29
core/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.

117
core/modules/commander.js Normal file
View File

@@ -0,0 +1,117 @@
/*\
title: $:/core/modules/commander.js
type: application/javascript
module-type: global
The $tw.Commander class is a command interpreter
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Parse a sequence of commands
commandTokens: an array of command string tokens
wiki: reference to the wiki store object
streams: {output:, error:}, each of which has a write(string) method
callback: a callback invoked as callback(err) where err is null if there was no error
*/
var Commander = function(commandTokens,callback,wiki,streams) {
this.commandTokens = commandTokens;
this.nextToken = 0;
this.callback = callback;
this.wiki = wiki;
this.streams = streams;
};
/*
Execute the sequence of commands and invoke a callback on completion
*/
Commander.prototype.execute = function() {
this.executeNextCommand();
};
/*
Execute the next command in the sequence
*/
Commander.prototype.executeNextCommand = function() {
var self = this;
// Invoke the callback if there are no more commands
if(this.nextToken >= this.commandTokens.length) {
this.callback(null);
} else {
// Get and check the command token
var commandName = this.commandTokens[this.nextToken++];
if(commandName.substr(0,2) !== "--") {
this.callback("Missing command");
} else {
commandName = commandName.substr(2); // Trim off the --
// Accumulate the parameters to the command
var params = [];
while(this.nextToken < this.commandTokens.length &&
this.commandTokens[this.nextToken].substr(0,2) !== "--") {
params.push(this.commandTokens[this.nextToken++]);
}
// Get the command info
var command = $tw.commands[commandName],
c,err;
if(!command) {
this.callback("Unknown command: " + commandName);
} else {
if(this.verbose) {
this.streams.output.write("Executing command: " + commandName + " " + params.join(" ") + "\n");
}
if(command.info.synchronous) {
// Synchronous command
c = new command.Command(params,this);
err = c.execute();
if(err) {
this.callback(err);
} else {
this.executeNextCommand();
}
} else {
// Asynchronous command
c = new command.Command(params,this,function(err) {
if(err) {
self.callback(err);
} else {
self.executeNextCommand();
}
});
err = c.execute();
if(err) {
this.callback(err);
}
}
}
}
}
};
Commander.initCommands = function(moduleType) {
// Install the command modules
moduleType = moduleType || "command";
$tw.commands = {};
var modules = $tw.plugins.moduleTypes[moduleType],
n,m,f,c;
if(modules) {
for(n=0; n<modules.length; n++) {
m = modules[n];
$tw.commands[m.info.name] = {};
c = $tw.commands[m.info.name];
// Add the methods defined by the module
for(f in m) {
c[f] = m[f];
}
}
}
};
exports.Commander = Commander;
})();

View File

@@ -0,0 +1,112 @@
/*\
title: $:/core/modules/commands/dump.js
type: application/javascript
module-type: command
Dump command
\*/
(function(){
/*jshint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "dump",
synchronous: true
};
var Command = function(params,commander) {
this.params = params;
this.commander = commander;
this.output = commander.streams.output;
};
Command.prototype.execute = function() {
if(this.params.length < 1) {
return "Too few parameters for dump command";
}
var subcommand = this.subcommands[this.params[0]];
if(subcommand) {
return subcommand.call(this);
} else {
return "Unknown subcommand (" + this.params[0] + ") for dump command";
}
};
Command.prototype.subcommands = {};
Command.prototype.subcommands.tiddler = function() {
if(this.params.length < 2) {
return "Too few parameters for dump tiddler command";
}
var tiddler = this.commander.wiki.getTiddler(this.params[1]);
this.output.write("Tiddler '" + this.params[1] + "' contains these fields:\n");
for(var t in tiddler.fields) {
this.output.write(" " + t + ": " + tiddler.getFieldString(t) + "\n");
}
return null; // No error
};
Command.prototype.subcommands.tiddlers = function() {
var tiddlers = this.commander.wiki.sortTiddlers();
this.output.write("Wiki contains these tiddlers:\n");
for(var t=0; t<tiddlers.length; t++) {
this.output.write(tiddlers[t] + "\n");
}
return null; // No error
};
Command.prototype.subcommands.shadows = function() {
var tiddlers = this.commander.wiki.shadows.sortTiddlers();
this.output.write("Wiki contains these shadow tiddlers:\n");
for(var t=0; t<tiddlers.length; t++) {
this.output.write(tiddlers[t] + "\n");
}
return null; // No error
};
Command.prototype.subcommands.config = function() {
var self = this;
var quotePropertyName = function(p) {
var unquotedPattern = /^[A-Za-z0-9_]*$/mg;
if(unquotedPattern.test(p)) {
return p;
} else {
return "[\"" + $tw.utils.stringify(p) + "\"]";
}
},
dumpConfig = function(object,prefix) {
for(var n in object) {
var v = object[n];
if(typeof v === "object") {
dumpConfig(v,prefix + "." + quotePropertyName(n));
} else if(typeof v === "string") {
self.output.write(prefix + "." + quotePropertyName(n) + ": \"" + $tw.utils.stringify(v) + "\"\n");
} else {
self.output.write(prefix + "." + quotePropertyName(n) + ": " + v.toString() + "\n");
}
}
},
dumpObject = function(heading,object) {
self.output.write(heading +"\n");
for(var n in object) {
self.output.write(" " + n + "\n");
}
};
this.output.write("Configuration:\n");
dumpConfig($tw.config," $tw.config");
dumpObject("Tiddler field plugins:",$tw.Tiddler.fieldPlugins);
dumpObject("Loaded modules:",$tw.modules.titles);
dumpObject("Loaded plugins:",$tw.plugins.moduleTypes);
dumpObject("Command plugins:",$tw.commands);
dumpObject("Parser plugins:",$tw.wiki.parsers);
dumpObject("Macro plugins:",$tw.wiki.macros);
dumpObject("Deserializer plugins:",$tw.Wiki.tiddlerDeserializerPlugins);
return null; // No error
};
exports.Command = Command;
})();

View File

@@ -0,0 +1,56 @@
/*\
title: $:/core/modules/commands/load.js
type: application/javascript
module-type: command
Load tiddlers command
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "load",
synchronous: false
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this,
fs = require("fs"),
path = require("path");
if(this.params.length < 1) {
return "Missing filename";
}
fs.readFile(this.params[0],"utf8",function(err,data) {
if(err) {
self.callback(err);
} else {
var fields = {title: self.params[0]},
extname = path.extname(self.params[0]),
type = extname === ".html" ? "application/x-tiddlywiki" : extname;
var tiddlers = self.commander.wiki.deserializeTiddlers(type,data,fields);
if(!tiddlers) {
self.callback("No tiddlers found in file \"" + self.params[0] + "\"");
} else {
for(var t=0; t<tiddlers.length; t++) {
self.commander.wiki.addTiddler(new $tw.Tiddler(tiddlers[t]));
}
self.callback(null);
}
}
});
return null;
};
exports.Command = Command;
})();

View File

@@ -0,0 +1,44 @@
/*\
title: $:/core/modules/commands/savetiddler.js
type: application/javascript
module-type: command
Save tiddlers command
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "savetiddler",
synchronous: false
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
if(this.params.length < 2) {
return "Missing filename";
}
var self = this,
fs = require("fs"),
path = require("path"),
title = this.params[0],
filename = this.params[1],
type = this.params[2] || "text/html";
fs.writeFile(filename,this.commander.wiki.renderTiddler(type,title),"utf8",function(err) {
self.callback(err);
});
return null;
};
exports.Command = Command;
})();

View File

@@ -0,0 +1,32 @@
/*\
title: $:/core/modules/commands/verbose.js
type: application/javascript
module-type: command
Verbose command
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "verbose",
synchronous: true
};
var Command = function(params,commander) {
this.params = params;
this.commander = commander;
};
Command.prototype.execute = function() {
this.commander.verbose = true;
return null; // No error
};
exports.Command = Command;
})();

View File

@@ -0,0 +1,32 @@
/*\
title: $:/core/modules/commands/version.js
type: application/javascript
module-type: command
Version command
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "version",
synchronous: true
};
var Command = function(params,commander) {
this.params = params;
this.commander = commander;
};
Command.prototype.execute = function() {
this.commander.streams.output.write($tw.utils.getVersionString() + "\n");
return null; // No error
};
exports.Command = Command;
})();

View File

@@ -0,0 +1,74 @@
/*\
title: $:/core/modules/commands/wikitest.js
type: application/javascript
module-type: command
wikitest command
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "wikitest",
synchronous: true
};
var Command = function(params,commander) {
this.params = params;
this.commander = commander;
};
Command.prototype.execute = function() {
if(this.params.length <1) {
return "Missing parameters";
}
var fs = require("fs"),
path = require("path"),
testdirectory = this.params[0],
saveResults = this.params[1] === "save",
files = fs.readdirSync(testdirectory),
titles = [],
f,t,extname,basename;
for(f=0; f<files.length; f++) {
extname = path.extname(files[f]);
if(extname === ".tid") {
var tiddlers = this.commander.wiki.deserializeTiddlers(extname,fs.readFileSync(path.resolve(testdirectory,files[f]),"utf8"));
if(tiddlers.length > 1) {
throw "Cannot use .JSON files";
}
this.commander.wiki.addTiddler(new $tw.Tiddler(tiddlers[0]));
titles.push(tiddlers[0].title);
}
}
for(t=0; t<titles.length; t++) {
var htmlFilename = path.resolve(testdirectory,titles[t] + ".html"),
plainFilename = path.resolve(testdirectory,titles[t] + ".txt"),
htmlTarget = fs.readFileSync(htmlFilename,"utf8"),
plainTarget = fs.readFileSync(plainFilename,"utf8"),
tiddler = this.commander.wiki.getTiddler(titles[t]),
htmlRender = this.commander.wiki.renderTiddler("text/html",titles[t]),
plainRender = this.commander.wiki.renderTiddler("text/plain",titles[t]);
if(saveResults) {
// Save results
fs.writeFileSync(htmlFilename,htmlRender,"utf8");
fs.writeFileSync(plainFilename,plainRender,"utf8");
} else {
// Report results
if(htmlTarget !== htmlRender) {
this.commander.streams.output.write("Tiddler " + titles[t] + " html error\nTarget: " + htmlTarget + "\nFound: " + htmlRender +"\n");
}
if(plainTarget !== plainRender) {
this.commander.streams.output.write("Tiddler " + titles[t] + " plain text error\nTarget: " + plainTarget + "\nFound: " + plainRender + "\n");
}
}
}
return null; // No error
};
exports.Command = Command;
})();

31
core/modules/config.js Normal file
View File

@@ -0,0 +1,31 @@
/*\
title: $:/core/modules/config.js
type: application/javascript
module-type: config
Core configuration constants
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.dateFormats = {
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"],
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
// suffixes for dates, eg "1st","2nd","3rd"..."30th","31st"
daySuffixes: ["st","nd","rd","th","th","th","th","th","th","th",
"th","th","th","th","th","th","th","th","th","th",
"st","nd","rd","th","th","th","th","th","th","th",
"st"],
am: "am",
pm: "pm"
};
exports.htmlEntities = {quot:34, amp:38, apos:39, lt:60, gt:62, nbsp:160, iexcl:161, cent:162, pound:163, curren:164, yen:165, brvbar:166, sect:167, uml:168, copy:169, ordf:170, laquo:171, not:172, shy:173, reg:174, macr:175, deg:176, plusmn:177, sup2:178, sup3:179, acute:180, micro:181, para:182, middot:183, cedil:184, sup1:185, ordm:186, raquo:187, frac14:188, frac12:189, frac34:190, iquest:191, Agrave:192, Aacute:193, Acirc:194, Atilde:195, Auml:196, Aring:197, AElig:198, Ccedil:199, Egrave:200, Eacute:201, Ecirc:202, Euml:203, Igrave:204, Iacute:205, Icirc:206, Iuml:207, ETH:208, Ntilde:209, Ograve:210, Oacute:211, Ocirc:212, Otilde:213, Ouml:214, times:215, Oslash:216, Ugrave:217, Uacute:218, Ucirc:219, Uuml:220, Yacute:221, THORN:222, szlig:223, agrave:224, aacute:225, acirc:226, atilde:227, auml:228, aring:229, aelig:230, ccedil:231, egrave:232, eacute:233, ecirc:234, euml:235, igrave:236, iacute:237, icirc:238, iuml:239, eth:240, ntilde:241, ograve:242, oacute:243, ocirc:244, otilde:245, ouml:246, divide:247, oslash:248, ugrave:249, uacute:250, ucirc:251, uuml:252, yacute:253, thorn:254, yuml:255, OElig:338, oelig:339, Scaron:352, scaron:353, Yuml:376, fnof:402, circ:710, tilde:732, Alpha:913, Beta:914, Gamma:915, Delta:916, Epsilon:917, Zeta:918, Eta:919, Theta:920, Iota:921, Kappa:922, Lambda:923, Mu:924, Nu:925, Xi:926, Omicron:927, Pi:928, Rho:929, Sigma:931, Tau:932, Upsilon:933, Phi:934, Chi:935, Psi:936, Omega:937, alpha:945, beta:946, gamma:947, delta:948, epsilon:949, zeta:950, eta:951, theta:952, iota:953, kappa:954, lambda:955, mu:956, nu:957, xi:958, omicron:959, pi:960, rho:961, sigmaf:962, sigma:963, tau:964, upsilon:965, phi:966, chi:967, psi:968, omega:969, thetasym:977, upsih:978, piv:982, ensp:8194, emsp:8195, thinsp:8201, zwnj:8204, zwj:8205, lrm:8206, rlm:8207, ndash:8211, mdash:8212, lsquo:8216, rsquo:8217, sbquo:8218, ldquo:8220, rdquo:8221, bdquo:8222, dagger:8224, Dagger:8225, bull:8226, hellip:8230, permil:8240, prime:8242, Prime:8243, lsaquo:8249, rsaquo:8250, oline:8254, frasl:8260, euro:8364, image:8465, weierp:8472, real:8476, trade:8482, alefsym:8501, larr:8592, uarr:8593, rarr:8594, darr:8595, harr:8596, crarr:8629, lArr:8656, uArr:8657, rArr:8658, dArr:8659, hArr:8660, forall:8704, part:8706, exist:8707, empty:8709, nabla:8711, isin:8712, notin:8713, ni:8715, prod:8719, sum:8721, minus:8722, lowast:8727, radic:8730, prop:8733, infin:8734, ang:8736, and:8743, or:8744, cap:8745, cup:8746, int:8747, there4:8756, sim:8764, cong:8773, asymp:8776, ne:8800, equiv:8801, le:8804, ge:8805, sub:8834, sup:8835, nsub:8836, sube:8838, supe:8839, oplus:8853, otimes:8855, perp:8869, sdot:8901, lceil:8968, rceil:8969, lfloor:8970, rfloor:8971, lang:9001, rang:9002, loz:9674, spades:9824, clubs:9827, hearts:9829, diams:9830 };
})();

View File

@@ -0,0 +1,80 @@
/*\
title: $:/core/modules/dependencies.js
type: application/javascript
module-type: global
Represents the dependencies of a tiddler or a parser node as these fields:
tiddlers: A hashmap of explicitly tiddler titles, with the value `false` if the dependency is skinny, and `true` if it is fat
dependentAll: True if there is an implicit skinny dependency on all available tiddlers
dependentOnContextTiddler: True if the node has a fat dependency on the current context tiddler
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Dependencies = function(skinnyTiddlers,fatTiddlers,dependentAll) {
var t,title;
this.tiddlers = {};
this.dependentAll = dependentAll;
if(skinnyTiddlers) {
for(t=0; t<skinnyTiddlers.length; t++) {
title = skinnyTiddlers[t];
if(typeof title === "string" && title !== "") {
this.tiddlers[title] = false;
}
}
}
if(fatTiddlers) {
for(t=0; t<fatTiddlers.length; t++) {
title = fatTiddlers[t];
if(typeof title === "string" && title !== "") {
this.tiddlers[title] = true;
}
}
}
};
/*
Adds a dependency to a given tiddler. Note how setting a dependency of fat=false on a tiddler that already has
a dependency of fat=true will leave the fat setting as true
*/
Dependencies.prototype.addDependency = function(tiddlerTitle,fat) {
if(!this.tiddlers[tiddlerTitle]) {
this.tiddlers[tiddlerTitle] = fat;
}
};
Dependencies.prototype.mergeDependencies = function(dep) {
this.dependentAll = dep.dependentAll || this.dependentAll;
for(var t in dep.tiddlers) {
this.addDependency(t,dep.tiddlers[t]);
}
};
/*
Determine if these dependencies are impacted by the specified array of changes
changes: Hashmap of {modified: bool, deleted: bool}
contextTiddlerTitle: The title of the current context tiddler
*/
Dependencies.prototype.hasChanged = function(changes,contextTiddlerTitle) {
if(this.dependentAll) {
return true;
}
if(!!this.dependentOnContextTiddler && contextTiddlerTitle && changes.hasOwnProperty(contextTiddlerTitle)) {
return true;
}
for(var c in changes) {
if(this.tiddlers.hasOwnProperty(c)) {
return true;
}
}
return false;
};
exports.Dependencies = Dependencies;
})();

View File

@@ -0,0 +1,27 @@
/*\
title: $:/core/modules/deserializers.js
type: application/javascript
module-type: tiddlerdeserializer
Plugins to deserialise tiddlers from a block of text
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports["text/plain"] = function(text,fields) {
fields.text = text;
fields.type = "text/plain";
return [fields];
};
exports["text/html"] = function(text,fields) {
fields.text = text;
fields.type = "text/html";
return [fields];
};
})();

View File

@@ -0,0 +1,45 @@
/*\
title: $:/core/modules/macros/button.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "button",
params: {
name: {byName: "default", type: "text"},
label: {byName: true, type: "text"},
"class": {byName: true, type: "text"}
},
events: ["click"]
};
exports.handleEvent = function(event) {
switch(event.type) {
case "click":
var buttonEvent = document.createEvent("Event");
buttonEvent.initEvent("tw-" + this.params.name,true,true);
buttonEvent.tiddlerTitle = this.tiddlerTitle;
buttonEvent.commandOrigin = this;
event.target.dispatchEvent(buttonEvent);
event.preventDefault();
return false;
}
return true;
};
exports.executeMacro = function() {
var attributes = {};
if(this.hasParameter("class")) {
attributes["class"] = this.params["class"].split(" ");
}
return [$tw.Tree.Element("button",attributes,[$tw.Tree.Text(this.params.label)])];
};
})();

View File

@@ -0,0 +1,156 @@
/*\
title: $:/core/modules/macros/chooser.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "chooser",
wrapperTag: "div",
params: {
},
events: ["touchstart","touchmove","touchend","mouseover","mousemove","mouseup","mouseout"]
};
exports.showChooser = function() {
if(!this.chooserDisplayed) {
this.chooserDisplayed = true;
var nodes = [];
this.wiki.forEachTiddler("title",function(title,tiddler) {
nodes.push($tw.Tree.Element("li",{
"data-link": title
},[
$tw.Tree.Text(title)
]));
});
var wrapper = $tw.Tree.Element("ul",{},nodes);
wrapper.execute(this.parents,this.tiddlerTitle);
this.children = [wrapper];
this.children[0].renderInDom(this.domNode);
}
};
/*
Select the appropriate chooser item given a touch/mouse position in screen coordinates
*/
exports.select = function(y) {
if(this.children.length > 0) {
var targetIndex = Math.floor(this.children[0].domNode.childNodes.length * (y/window.innerHeight)),
target = this.children[0].domNode.childNodes[targetIndex];
if(target) {
this.deselect();
this.selectedNode = target;
$tw.utils.addClass(target,"selected");
}
}
};
exports.deselect = function() {
if(this.selectedNode) {
$tw.utils.removeClass(this.selectedNode,"selected");
this.selectedNode = null;
}
};
exports.action = function() {
if(this.selectedNode) {
var navEvent = document.createEvent("Event");
navEvent.initEvent("tw-navigate",true,true);
navEvent.navigateTo = this.selectedNode.getAttribute("data-link");
this.domNode.dispatchEvent(navEvent);
}
};
/*
Set the position of the chooser panel within its wrapper given a touch/mouse position in screen coordinates
*/
exports.hoverChooser = function(x,y) {
if(this.chooserDisplayed) {
// Get the target element that the touch/mouse is over
this.select(y);
// Things we need for sizing and positioning the chooser
var domPanel = this.children[0].domNode,
heightPanel = domPanel.offsetHeight,
widthPanel = domPanel.offsetWidth;
// Position the chooser div to account for scrolling
this.children[0].domNode.style.top = window.pageYOffset + "px";
// Scale the panel to fit
var scaleFactor = window.innerHeight/heightPanel;
// Scale up as we move right
var expandFactor = x > 50 ? ((x+150)/200) : 1;
// Set up the transform
var scale = scaleFactor * expandFactor,
translateX = x > 16 ? 0 : -(((16-x)/16) * widthPanel) / scale,
translateY = (y / scale) - ((y/window.innerHeight) * heightPanel);
domPanel.style.webkitTransformOrigin =
domPanel.style.MozTransformOrigin = "0 0";
domPanel.style.webkitTransform =
domPanel.style.MozTransform = "scale(" + scale + ") translateX(" + translateX + "px) translateY(" + translateY + "px)";
}
};
exports.hideChooser = function() {
if(this.chooserDisplayed) {
this.deselect();
this.chooserDisplayed = false;
this.domNode.removeChild(this.children[0].domNode);
this.children = [];
}
};
exports.handleEvent = function(event) {
switch(event.type) {
case "touchstart":
this.showChooser();
this.hoverChooser(event.touches[0].clientX,event.touches[0].clientY);
event.preventDefault();
return false;
case "touchmove":
this.hoverChooser(event.touches[0].clientX,event.touches[0].clientY);
event.preventDefault();
return false;
case "touchend":
this.action();
this.hideChooser();
event.preventDefault();
return false;
case "mouseover":
if(event.target === this.domNode) {
this.showChooser();
this.hoverChooser(event.clientX,event.clientY);
event.preventDefault();
return false;
}
break;
case "mousemove":
this.hoverChooser(event.clientX,event.clientY);
event.preventDefault();
return false;
case "mouseup":
this.action();
this.hideChooser();
event.preventDefault();
return false;
case "mouseout":
if(!$tw.utils.domContains(this.domNode,event.relatedTarget)) {
this.hideChooser();
event.preventDefault();
return false;
}
break;
}
return true;
};
exports.executeMacro = function() {
this.chooserDisplayed = false;
return [];
};
})();

View File

@@ -0,0 +1,25 @@
/*\
title: $:/core/modules/macros/comment.js
type: application/javascript
module-type: macro
Comment macro, a no-op
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "!",
params: {}
};
exports.executeMacro = function() {
return [];
};
})();

View File

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

View File

@@ -0,0 +1,80 @@
/*\
title: $:/core/modules/macros/edit/edit.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "edit",
dependentOnContextTiddler: true,
params: {
field: {byPos: 0, type: "text"}
}
};
exports.executeMacro = function() {
// Get the tiddler being editted
var field = this.hasParameter("field") ? this.params.field : "text",
tiddler = this.wiki.getTiddler(this.tiddlerTitle),
Editor;
// Figure out which editor to use
// TODO: Tiddler field plugins should be able to specify a field type from which the editor is derived
if(field === "text" && tiddler.fields.type) {
Editor = this.wiki.macros.edit.editors[tiddler.fields.type];
}
if(!Editor) {
Editor = this.wiki.macros.edit.editors["text/x-tiddlywiki"];
}
this.editor = new Editor(this);
// Call the editor to generate the child nodes
var children = this.editor.getChildren();
for(var t=0; t<children.length; t++) {
children[t].execute(this.parents,this.tiddlerTitle);
}
return children;
};
exports.addEventHandlers = function() {
if(this.editor.addEventHandlers) {
this.editor.addEventHandlers();
}
};
exports.postRenderInDom = function() {
if(this.editor.postRenderInDom) {
this.editor.postRenderInDom();
}
};
exports.refreshInDom = function(changes) {
var t;
// Only refresh if a dependency is triggered
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
// Only refresh if the editor lets us
if(this.editor.isRefreshable()) {
// Remove the previous children
while(this.domNode.hasChildNodes()) {
this.domNode.removeChild(this.domNode.firstChild);
}
// Execute the new children
this.execute(this.parents,this.tiddlerTitle);
// Render to the DOM
for(t=0; t<this.children.length; t++) {
this.children[t].renderInDom(this.domNode);
}
}
} else {
// Refresh any children
for(t=0; t<this.children.length; t++) {
this.children[t].refreshInDom(changes);
}
}
};
})();

View File

@@ -0,0 +1,167 @@
/*\
title: $:/core/modules/macros/edit/editors/bitmapeditor.js
type: application/javascript
module-type: editor
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
function BitmapEditor(macroNode) {
this.macroNode = macroNode;
}
BitmapEditor.prototype.getChildren = function() {
return [$tw.Tree.Element("canvas",{
"class": ["tw-edit-field"]
},[])];
};
BitmapEditor.prototype.postRenderInDom = function() {
var tiddler = this.macroNode.wiki.getTiddler(this.macroNode.tiddlerTitle),
canvas = this.macroNode.children[0].domNode,
currImage = new Image();
// Set the macro node itself to be position: relative
this.macroNode.domNode.style.position = "relative";
// Get the current bitmap into an image object
currImage.src = "data:" + tiddler.fields.type + ";base64," + tiddler.fields.text;
// Copy it to the on-screen canvas
canvas.width = currImage.width;
canvas.height = currImage.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(currImage,0,0);
// And also copy the current bitmap to the off-screen canvas
this.currCanvas = document.createElement("canvas");
this.currCanvas.width = currImage.width;
this.currCanvas.height = currImage.height;
ctx = this.currCanvas.getContext("2d");
ctx.drawImage(currImage,0,0);
};
BitmapEditor.prototype.addEventHandlers = function() {
var self = this;
this.macroNode.domNode.addEventListener("touchstart",function(event) {
self.brushDown = true;
self.strokeStart(event.touches[0].clientX,event.touches[0].clientY);
event.preventDefault();
event.stopPropagation();
return false;
},false);
this.macroNode.domNode.addEventListener("touchmove",function(event) {
if(self.brushDown) {
self.strokeMove(event.touches[0].clientX,event.touches[0].clientY);
}
event.preventDefault();
event.stopPropagation();
return false;
},false);
this.macroNode.domNode.addEventListener("touchend",function(event) {
if(self.brushDown) {
self.brushDown = false;
self.strokeEnd();
}
event.preventDefault();
event.stopPropagation();
return false;
},false);
this.macroNode.domNode.addEventListener("mousedown",function(event) {
self.strokeStart(event.clientX,event.clientY);
self.brushDown = true;
event.preventDefault();
event.stopPropagation();
return false;
},false);
this.macroNode.domNode.addEventListener("mousemove",function(event) {
if(self.brushDown) {
self.strokeMove(event.clientX,event.clientY);
event.preventDefault();
event.stopPropagation();
return false;
}
},false);
this.macroNode.domNode.addEventListener("mouseup",function(event) {
if(self.brushDown) {
self.brushDown = false;
self.strokeEnd();
event.preventDefault();
event.stopPropagation();
return false;
}
return true;
},false);
};
BitmapEditor.prototype.adjustCoordinates = function(x,y) {
var canvas = this.macroNode.children[0].domNode,
canvasRect = canvas.getBoundingClientRect(),
scale = canvas.width/canvasRect.width;
return {x: (x - canvasRect.left) * scale, y: (y - canvasRect.top) * scale};
};
BitmapEditor.prototype.strokeStart = function(x,y) {
// Start off a new stroke
this.stroke = [this.adjustCoordinates(x,y)];
};
BitmapEditor.prototype.strokeMove = function(x,y) {
var canvas = this.macroNode.children[0].domNode,
ctx = canvas.getContext("2d"),
t;
// Add the new position to the end of the stroke
this.stroke.push(this.adjustCoordinates(x,y));
// Redraw the previous image
ctx.drawImage(this.currCanvas,0,0);
// Render the stroke
ctx.lineWidth = 3;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.beginPath();
ctx.moveTo(this.stroke[0].x,this.stroke[0].y);
for(t=1; t<this.stroke.length-1; t++) {
var s1 = this.stroke[t],
s2 = this.stroke[t-1],
tx = (s1.x + s2.x)/2,
ty = (s1.y + s2.y)/2;
ctx.quadraticCurveTo(s2.x,s2.y,tx,ty);
}
ctx.stroke();
};
BitmapEditor.prototype.strokeEnd = function() {
// Copy the bitmap to the off-screen canvas
var canvas = this.macroNode.children[0].domNode,
ctx = this.currCanvas.getContext("2d");
ctx.drawImage(canvas,0,0);
// Save the image into the tiddler
this.saveChanges();
};
BitmapEditor.prototype.saveChanges = function() {
var tiddler = this.macroNode.wiki.getTiddler(this.macroNode.tiddlerTitle);
if(tiddler) {
// data URIs look like "data:<type>;base64,<text>"
var dataURL = this.macroNode.children[0].domNode.toDataURL(tiddler.fields.type,1.0),
posColon = dataURL.indexOf(":"),
posSemiColon = dataURL.indexOf(";"),
posComma = dataURL.indexOf(","),
type = dataURL.substring(posColon+1,posSemiColon),
text = dataURL.substring(posComma+1);
var update = {type: type, text: text};
this.macroNode.wiki.addTiddler(new $tw.Tiddler(tiddler,update));
}
};
BitmapEditor.prototype.isRefreshable = function() {
// Don't ever refresh the bitmap editor
return false;
};
exports["image/jpg"] = BitmapEditor;
exports["image/jpeg"] = BitmapEditor;
exports["image/png"] = BitmapEditor;
exports["image/gif"] = BitmapEditor;
})();

View File

@@ -0,0 +1,106 @@
/*\
title: $:/core/modules/macros/edit/editors/texteditor.js
type: application/javascript
module-type: editor
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
function TextEditor(macroNode) {
this.macroNode = macroNode;
}
TextEditor.prototype.getChildren = function() {
var tiddler = this.macroNode.wiki.getTiddler(this.macroNode.tiddlerTitle),
field = this.macroNode.hasParameter("field") ? this.macroNode.params.field : "title",
value;
if(tiddler) {
value = tiddler.getFieldString(field);
} else {
switch(field) {
case "text":
value = "Type the text for the tiddler '" + this.macroNode.tiddlerTitle + "'";
break;
case "title":
value = this.macroNode.tiddlerTitle;
break;
default:
value = "";
break;
}
}
var attributes = {
"class": ["tw-edit-field"]
},
tagName,
content = [];
if(field === "text") {
tagName = "textarea";
content.push($tw.Tree.Text(value));
} else {
tagName = "input";
attributes.type = "text";
attributes.value = value;
}
return [$tw.Tree.Element(tagName,attributes,content)];
};
TextEditor.prototype.addEventHandlers = function() {
this.macroNode.domNode.addEventListener("focus",this,false);
this.macroNode.domNode.addEventListener("keyup",this,false);
};
TextEditor.prototype.handleEvent = function(event) {
// Get the value of the field if it might have changed
if("keyup".split(" ").indexOf(event.type) !== -1) {
this.saveChanges();
}
// Whatever the event, fix the height of the textarea if required
var self = this;
window.setTimeout(function() {
self.fixHeight();
},5);
return true;
};
TextEditor.prototype.saveChanges = function() {
var text = this.macroNode.children[0].domNode.value,
tiddler = this.macroNode.wiki.getTiddler(this.macroNode.tiddlerTitle);
if(tiddler && text !== tiddler.fields[this.macroNode.params.field]) {
var update = {};
update[this.macroNode.params.field] = text;
this.macroNode.wiki.addTiddler(new $tw.Tiddler(tiddler,update));
}
};
TextEditor.prototype.fixHeight = function() {
if(this.macroNode.children[0] && this.macroNode.children[0].domNode) {
var wrapper = this.macroNode.domNode,
textarea = this.macroNode.children[0].domNode;
// Set the text area height to 1px temporarily, which allows us to read the true scrollHeight
var prevWrapperHeight = wrapper.style.height;
wrapper.style.height = textarea.style.height + "px";
textarea.style.overflow = "hidden";
textarea.style.height = "1px";
textarea.style.height = textarea.scrollHeight + "px";
wrapper.style.height = prevWrapperHeight;
}
};
TextEditor.prototype.postRenderInDom = function() {
this.fixHeight();
};
TextEditor.prototype.isRefreshable = function() {
// Don't refresh the editor if it contains the caret or selection
return document.activeElement !== this.macroNode.children[0].domNode;
};
exports["text/x-tiddlywiki"] = TextEditor;
exports["text/plain"] = TextEditor;
})();

View File

@@ -0,0 +1,46 @@
/*\
title: $:/core/modules/macros/image.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "image",
params: {
src: {byName: "default", type: "tiddler"},
text: {byName: true, type: "text"},
alignment: {byName: true, type: "text"}
}
};
exports.executeMacro = function() {
if(this.wiki.tiddlerExists(this.params.src)) {
var imageTree = this.wiki.parseTiddler(this.params.src).tree,
cloneImage = [];
for(var t=0; t<imageTree.length; t++) {
cloneImage.push(imageTree[t].clone());
}
if(this.params.text) {
return [$tw.Tree.Element("div",{
alt: this.params.text,
title: this.params.text
},cloneImage)];
} else {
return cloneImage;
}
} else {
return [$tw.Tree.Element("img",{
src: this.params.src,
alt: this.params.text,
title: this.params.text
})];
}
};
})();

View File

@@ -0,0 +1,37 @@
/*\
title: $:/core/modules/macros/include.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "^",
params: {
filter: {byPos: 0, type: "filter"},
as: {byPos: 1, as: "text"}
}
};
exports.executeMacro = function() {
var as = this.params.as || "text/plain";
if(this.hasParameter("filter")) {
var titles = this.wiki.filterTiddlers(this.params.filter),
result = [];
for(var t=0; t<titles.length; t++) {
result.push(this.wiki.serializeTiddler(titles[t],as));
}
return [$tw.Tree.Element("pre",{},[
$tw.Tree.Text(result.join("\n"))
])];
}
return [];
};
})();

View File

@@ -0,0 +1,88 @@
/*\
title: $:/core/modules/macros/link.js
type: application/javascript
module-type: macro
Implements the link macro.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var isLinkExternal = function(to) {
var externalRegExp = /(?:file|http|https|mailto|ftp|irc|news|data):[^\s'"]+(?:\/|\b)/i;
return externalRegExp.test(to);
};
exports.info = {
name: "link",
params: {
to: {byName: "default", type: "tiddler", skinny: true},
space: {byName: true, type: "text"}
},
events: ["click"]
};
exports.handleEvent = function (event) {
switch(event.type) {
case "click":
if(isLinkExternal(this.params.to)) {
event.target.setAttribute("target","_blank");
return true;
} else {
var navEvent = document.createEvent("Event");
navEvent.initEvent("tw-navigate",true,true);
navEvent.navigateTo = this.params.to;
event.target.dispatchEvent(navEvent);
event.preventDefault();
return false;
}
}
};
exports.executeMacro = function() {
// Assemble the information about the link
var linkInfo = {
to: this.params.to,
space: this.params.space
};
// Generate the default link characteristics
linkInfo.isExternal = isLinkExternal(linkInfo.to);
if(!linkInfo.isExternal) {
linkInfo.isMissing = !this.wiki.tiddlerExists(linkInfo.to);
}
linkInfo.attributes = {
href: linkInfo.to
};
if(!linkInfo.isExternal) {
linkInfo.attributes.href = encodeURIComponent(linkInfo.to);
}
// Generate the default classes for the link
linkInfo.attributes["class"] = ["tw-tiddlylink"];
if(linkInfo.isExternal) {
linkInfo.attributes["class"].push("tw-tiddlylink-external");
} else {
linkInfo.attributes["class"].push("tw-tiddlylink-internal");
if(linkInfo.isMissing) {
linkInfo.attributes["class"].push("tw-tiddlylink-missing");
} else {
linkInfo.attributes["class"].push("tw-tiddlylink-resolves");
}
}
// Create the link
var children;
if(linkInfo.suppressLink) {
children = this.cloneContent();
} else {
children = [$tw.Tree.Element("a",linkInfo.attributes,this.cloneContent())];
}
for(var t=0; t<children.length; t++) {
children[t].execute(this.parents,this.tiddlerTitle);
}
return children;
};
})();

View File

@@ -0,0 +1,80 @@
/*\
title: $:/core/modules/macros/list.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var handlers = {
all: function(wiki) {
return wiki.sortTiddlers("title","excludeLists");
},
missing: function(wiki) {
return wiki.getMissingTitles();
},
orphans: function(wiki) {
return wiki.getOrphanTitles();
},
shadowed: function(wiki) {
return wiki.getShadowTitles();
},
touched: function(wiki) {
// Server syncing isn't implemented yet
return [];
},
filter: function(wiki) {
// Filters aren't implemented yet
return [];
}
};
exports.info = {
name: "list",
dependentAll: true, // Tiddlers containing <<list>> macro are dependent on every tiddler
params: {
type: {byName: "default", type: "text"},
template: {byName: true, type: "tiddler"},
emptyMessage: {byName: true, type: "text"}
}
};
exports.executeMacro = function() {
var templateType = "text/x-tiddlywiki",
templateText = "<<view title link>>",
template = this.params.template ? this.wiki.getTiddler(this.params.template) : null,
children = [],
t,
parents = this.parents;
if(template) {
parents = parents.slice(0);
parents.push(template.title);
templateType = template.type;
templateText = template.text;
}
var handler = handlers[this.params.type];
handler = handler || handlers.all;
var tiddlers = handler(this.wiki);
if(tiddlers.length === 0) {
return [$tw.Tree.Text(this.params.emptyMessage || "")];
} else {
var templateTree = this.wiki.parseText(templateType,templateText).tree;
for(t=0; t<tiddlers.length; t++) {
var cloneTemplate = [];
for(var c=0; c<templateTree.length; c++) {
cloneTemplate.push(templateTree[c].clone());
}
var listNode = $tw.Tree.Element("li",null,cloneTemplate);
listNode.execute(parents,tiddlers[t]);
children.push(listNode);
}
return [$tw.Tree.Element("ul",null,children)];
}
};
})();

View File

@@ -0,0 +1,177 @@
/*\
title: $:/core/modules/macros/slider.js
type: application/javascript
module-type: macro
!Introduction
The slider macro is used to selectively reveal a chunk of text. By default, it renders as a button that may be clicked or touched to reveal the enclosed text.
The enclosed text can be a string of WikiText or be taken from a target tiddler.
The current state of the slider can be stored as the string "open" or "closed" in a specified tiddler. If the value of that tiddler changes then the slider is automatically updated. If no state tiddler is specified then the state of the slider isn't retained, but the slider still works as expected.
!!Parameters
|`state` //(defaults to 1st parameter)// |The title of the tiddler to contain the current state of the slider |
|`default` |The initial state of the slider, either `open` or `closed` |
|`class` |A CSS class to be applied to the slider root element |
|`content` |The WikiText to be enclosed in the slider. Overrides the `target` parameter, if present |
|`target` //(defaults to 2nd parameter)// |The title of the tiddler that contains the enclosed text. Ignored if the `content` parameter is specified |
|`label` //(defaults to 3rd parameter)// |The plain text to be displayed as the label for the slider button |
|`tooltip` //(defaults to 4th parameter)// |The plain text tooltip to be displayed when the mouse hovers over the slider button |
!!Markup
The markup generated by the slider macro is:
{{{
<span class="tw-slider {user defined class}">
<a class="btn-info">{slider label}</a>
<div class="tw-slider-body" style="display:{state}">{slider content}</div>
</span>
}}}
!!Examples
A minimal slider:
{{{
<<slider target:MyTiddler>>
}}}
!!Notes
The slider is a good study example of a simple interactive macro.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "slider",
params: {
state: {byPos: 0, type: "tiddler"},
target: {byPos: 1, type: "tiddler"},
label: {byPos: 2, type: "text"},
tooltip: {byPos: 3, type: "text"},
"default": {byName: true, type: "text"},
"class": {byName: true, type: "text"},
content: {byName: true, type: "text"}
},
events: ["click"]
};
exports.getOpenState = function() {
if(this.hasParameter("state")) {
var stateTiddler = this.wiki.getTiddler(this.params.state);
if(stateTiddler) {
return stateTiddler.fields.text.trim() === "open";
}
}
if(this.hasParameter("default")) {
return this.params["default"] === "open";
}
return false;
};
exports.saveOpenState = function() {
if(this.hasParameter("state")) {
var stateTiddler = this.wiki.getTiddler(this.params.state) || {title: this.params.state, text: ""};
this.wiki.addTiddler(new $tw.Tiddler(stateTiddler,{text: this.isOpen ? "open" : "closed"}));
return true;
}
return false;
};
exports.getSliderChildren = function() {
if(this.hasParameter("content")) {
return this.wiki.parseText("text/x-tiddlywiki",this.params.content).tree;
} else if(this.hasParameter("target")) {
return [$tw.Tree.Macro(
"tiddler",
{target: this.params.target},
null,
this.wiki)];
} else {
return [$tw.Tree.errorNode("No content specified for slider")];
}
};
exports.handleEvent = function(event) {
switch(event.type) {
case "click":
if(event.target === this.domNode.firstChild.firstChild) {
this.isOpen = !this.isOpen;
if(!this.saveOpenState()) {
this.refreshInDom({});
}
event.preventDefault();
return false;
} else {
return true;
}
}
return true;
};
exports.executeMacro = function() {
this.isOpen = this.getOpenState();
var sliderChildren = [];
if(this.isOpen) {
sliderChildren = this.getSliderChildren();
}
var attributes = {
"class": ["tw-slider"]
};
if(this.hasParameter("class")) {
attributes["class"].push(this.params["class"]);
}
if(this.hasParameter("state")) {
attributes["data-tw-slider-type"] = this.params.state;
}
if(this.hasParameter("tooltip")) {
attributes.alt = this.params.tooltip;
attributes.title = this.params.tooltip;
}
var children = $tw.Tree.Element("span",
attributes,
[
$tw.Tree.Element("a",
{
"class": ["btn","btn-info"]
},[
$tw.Tree.Text(this.params.label ? this.params.label : this.params.target)
]
),
$tw.Tree.Element("div",
{
"class": ["tw-slider-body"],
"style": {"display": this.isOpen ? "block" : "none"}
},
sliderChildren
)
]
);
children.execute(this.parents,this.tiddlerTitle);
return [children];
};
exports.refreshInDom = function(changes) {
var needChildrenRefresh = true; // Avoid refreshing the children nodes if we don't need to
// If the state tiddler has changed then reset the open state
if(this.hasParameter("state") && changes.hasOwnProperty(this.params.state)) {
this.isOpen = this.getOpenState();
}
// Render the children if the slider is open and we don't have any children yet
if(this.isOpen && this.children[0].children[1].children.length === 0) {
// Remove the existing dom node for the body
this.children[0].domNode.removeChild(this.children[0].children[1].domNode);
// Get the slider children and execute it
this.children[0].children[1].children = this.getSliderChildren();
this.children[0].children[1].execute(this.parents,this.tiddlerTitle);
this.children[0].children[1].renderInDom(this.children[0].domNode,null);
needChildrenRefresh = false; // Don't refresh the children if we've just created them
}
// Set the visibility of the slider children
this.children[0].children[1].domNode.style.display = this.isOpen ? "block" : "none";
// Refresh any children
if(needChildrenRefresh) {
for(var t=0; t<this.children.length; t++) {
this.children[t].refreshInDom(changes);
}
}
};
})();

View File

@@ -0,0 +1,195 @@
/*\
title: $:/core/modules/macros/story.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
function scrollToTop(duration) {
if (duration < 0) {
return;
}
var delta = (-document.body.scrollTop/duration) * 10;
window.setTimeout(function() {
document.body.scrollTop = document.body.scrollTop + delta;
scrollToTop(duration-10);
}, 10);
}
exports.info = {
name: "story",
params: {
story: {byName: "default", type: "tiddler"},
defaultViewTemplate: {byName: true, type: "tiddler"},
defaultEditTemplate: {byName: true, type: "tiddler"}
},
events: ["tw-navigate","tw-EditTiddler","tw-SaveTiddler"]
};
exports.handleEvent = function(event) {
var template, storyTiddler, story, storyRecord, tiddler, storyTiddlerModified, t;
switch(event.type) {
case "tw-navigate":
// Navigate to a specified tiddler
template = this.hasParameter("defaultViewTemplate") ? this.params.defaultViewTemplate : "ViewTemplate";
storyTiddler = this.wiki.getTiddler(this.params.story);
story = {tiddlers: []};
if(storyTiddler && storyTiddler.fields.hasOwnProperty("text")) {
story = JSON.parse(storyTiddler.fields.text);
}
story.tiddlers.unshift({title: event.navigateTo, template: template});
this.wiki.addTiddler(new $tw.Tiddler(storyTiddler,{text: JSON.stringify(story)}));
scrollToTop(400);
event.stopPropagation();
return false;
case "tw-EditTiddler":
// Put the specified tiddler into edit mode
template = this.hasParameter("defaultEditTemplate") ? this.params.defaultEditTemplate : "EditTemplate";
storyTiddler = this.wiki.getTiddler(this.params.story);
story = {tiddlers: []};
if(storyTiddler && storyTiddler.fields.hasOwnProperty("text")) {
story = JSON.parse(storyTiddler.fields.text);
}
for(t=0; t<story.tiddlers.length; t++) {
storyRecord = story.tiddlers[t];
if(storyRecord.title === event.tiddlerTitle && storyRecord.template !== template) {
storyRecord.title = "Draft " + (new Date()) + " of " + event.tiddlerTitle;
storyRecord.template = template;
tiddler = this.wiki.getTiddler(event.tiddlerTitle);
this.wiki.addTiddler(new $tw.Tiddler(
{
text: "Type the text for the tiddler '" + event.tiddlerTitle + "'"
},
tiddler,
{
title: storyRecord.title,
"draft.title": event.tiddlerTitle,
"draft.of": event.tiddlerTitle
}));
}
}
this.wiki.addTiddler(new $tw.Tiddler(storyTiddler,{text: JSON.stringify(story)}));
event.stopPropagation();
return false;
case "tw-SaveTiddler":
template = this.hasParameter("defaultViewTemplate") ? this.params.defaultEditTemplate : "ViewTemplate";
storyTiddler = this.wiki.getTiddler(this.params.story);
story = {tiddlers: []};
storyTiddlerModified = false;
if(storyTiddler && storyTiddler.fields.hasOwnProperty("text")) {
story = JSON.parse(storyTiddler.fields.text);
}
for(t=0; t<story.tiddlers.length; t++) {
storyRecord = story.tiddlers[t];
if(storyRecord.title === event.tiddlerTitle && storyRecord.template !== template) {
tiddler = this.wiki.getTiddler(storyRecord.title);
if(tiddler && tiddler.fields.hasOwnProperty("draft.title")) {
// Save the draft tiddler as the real tiddler
this.wiki.addTiddler(new $tw.Tiddler(tiddler,{title: tiddler.fields["draft.title"],"draft.title": undefined, "draft.of": undefined}));
// Remove the draft tiddler
this.wiki.deleteTiddler(storyRecord.title);
// Remove the original tiddler if we're renaming it
if(tiddler.fields["draft.of"] !== tiddler.fields["draft.title"]) {
this.wiki.deleteTiddler(tiddler.fields["draft.of"]);
}
// Make the story record point to the newly saved tiddler
storyRecord.title = tiddler.fields["draft.title"];
storyRecord.template = template;
// Check if we're modifying the story tiddler itself
if(tiddler.fields["draft.title"] === this.params.story) {
storyTiddlerModified = true;
}
}
}
}
if(!storyTiddlerModified) {
this.wiki.addTiddler(new $tw.Tiddler(storyTiddler,{text: JSON.stringify(story)}));
}
event.stopPropagation();
return false;
}
};
exports.executeMacro = function() {
var story = JSON.parse(this.wiki.getTiddlerText(this.params.story)),
children = [];
for(var t=0; t<story.tiddlers.length; t++) {
var m = $tw.Tree.Macro("tiddler",
{target: story.tiddlers[t].title,template: story.tiddlers[t].template},
null,
this.wiki);
m.execute(this.parents,this.tiddlerTitle);
children.push(m);
}
return children;
};
exports.refreshInDom = function(changes) {
var t;
/*jslint browser: true */
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
// Get the tiddlers we're supposed to be displaying
var self = this,
story = JSON.parse(this.wiki.getTiddlerText(this.params.story)),
template = this.params.template,
n,domNode,
findTiddler = function (childIndex,tiddlerTitle,templateTitle) {
while(childIndex < self.children.length) {
var params = self.children[childIndex].params;
if(params.target === tiddlerTitle) {
if(!templateTitle || params.template === templateTitle) {
return childIndex;
}
}
childIndex++;
}
return null;
};
for(t=0; t<story.tiddlers.length; t++) {
// See if the node we want is already there
var tiddlerNode = findTiddler(t,story.tiddlers[t].title,story.tiddlers[t].template);
if(tiddlerNode === null) {
// If not, render the tiddler
var m = $tw.Tree.Macro("tiddler",
{target: story.tiddlers[t].title,template: story.tiddlers[t].template},
null,
this.wiki);
m.execute(this.parents,this.tiddlerTitle);
m.renderInDom(this.domNode,this.domNode.childNodes[t]);
this.children.splice(t,0,m);
} else {
// Delete any nodes preceding the one we want
if(tiddlerNode > t) {
// First delete the DOM nodes
for(n=t; n<tiddlerNode; n++) {
domNode = this.children[n].domNode;
domNode.parentNode.removeChild(domNode);
}
// Then delete the actual renderer nodes
this.children.splice(t,tiddlerNode-t);
}
// Refresh the DOM node we're reusing
this.children[t].refreshInDom(changes);
}
}
// Remove any left over nodes
if(this.children.length > story.tiddlers.length) {
for(t=story.tiddlers.length; t<this.children.length; t++) {
domNode = this.children[t].domNode;
domNode.parentNode.removeChild(domNode);
}
this.children.splice(story.tiddlers.length,this.children.length-story.tiddlers.length);
}
} else {
for(t=0; t<this.children.length; t++) {
this.children[t].refreshInDom(changes);
}
}
};
})();

View File

@@ -0,0 +1,158 @@
/*\
title: $:/core/modules/macros/tiddler.js
type: application/javascript
module-type: macro
The tiddler macros transcludes another macro into the tiddler being rendered.
Parameters:
target: the title of the tiddler to transclude
template: the title of the tiddler to use as a template for the transcluded tiddler
with: optional parameters to be substituted into the rendered tiddler
The simplest case is to just supply a target tiddler:
<<tiddler Foo>> or <<transclude target:Foo>>
This will render the tiddler Foo within the current tiddler. If the tiddler Foo includes
the view macro (or other macros that reference the fields of the current tiddler), then the
fields of the tiddler Foo will be accessed.
If you want to transclude the tiddler as a template, so that the fields referenced by the view
macro are those of the tiddler doing the transcluding, then you can instead specify the tiddler
as a template:
<<tiddler template:Foo>>
The effect is the same as the previous example: the text of the tiddler Foo is rendered. The
difference is that the view macro will access the fields of the tiddler doing the transcluding.
The `target` and `template` parameters may be combined:
<<tiddler target:Foo template:Bar>>
Here, the text of the tiddler `Bar` will be transcluded, with the macros within it accessing the fields
of the tiddler `Foo`.
Finally, the `with` parameter is used to substitute values for the special markers $1, $2, $3 etc. The
substitutions are performed on the tiddler whose text is being rendered: either the tiddler named in
the `template` parameter or, if that parameter is missing, the tiddler named in the `target` parameter.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "tiddler",
cascadeParams: true, // Cascade names of named parameters to following anonymous parameters
params: {
target: {byName: "default", type: "tiddler"},
template: {byName: true, type: "tiddler"},
"with": {byName: true, type: "text", dependentAll: true}
}
};
exports.evaluateDependencies = function() {
var dependencies = new $tw.Dependencies(),
template = this.srcParams.template;
if(template === undefined) {
template = this.srcParams.target;
}
if(typeof template === "function") {
dependencies.dependentAll = true;
} else {
dependencies.addDependency(template,true);
}
return dependencies;
};
exports.executeMacro = function() {
var renderTitle = this.params.target,
renderTemplate = this.params.template,
children,
childrenClone = [],
t,
parents = this.parents.slice(0);
// If there's no render title specified then use the current tiddler title
if(typeof renderTitle !== "string") {
renderTitle = this.tiddlerTitle;
}
// If there's no template specified then use the target tiddler title
if(typeof renderTemplate !== "string") {
renderTemplate = renderTitle;
}
// Check for recursion
if(parents.indexOf(renderTemplate) !== -1) {
children = [$tw.Tree.errorNode("Tiddler recursion error in <<tiddler>> macro")];
} else {
if(this.hasParameter("with")) {
// Parameterised transclusion
var targetTiddler = this.wiki.getTiddler(renderTemplate),
text = targetTiddler.fields.text;
var withTokens = [this.params["with"]]; // TODO: Allow for more than one with: parameter
for(t=0; t<withTokens.length; t++) {
var placeholderRegExp = new RegExp("\\$"+(t+1),"mg");
text = text.replace(placeholderRegExp,withTokens[t]);
}
children = this.wiki.parseText(targetTiddler.fields.type,text).tree;
} else {
// There's no parameterisation, so we can just render the target tiddler directly
var parseTree = this.wiki.parseTiddler(renderTemplate);
children = parseTree ? parseTree.tree : [];
}
}
// Update the stack of tiddler titles for recursion detection
parents.push(renderTemplate);
// Clone the children
for(t=0; t<children.length; t++) {
childrenClone.push(children[t].clone());
}
// Execute macros within the children
for(t=0; t<childrenClone.length; t++) {
childrenClone[t].execute(parents,renderTitle);
}
// Set up the attributes for the wrapper element
var attributes = {
"data-tiddler-target": renderTitle,
"data-tiddler-template": renderTemplate,
"class": ["tw-tiddler-frame"]
};
if(!this.wiki.tiddlerExists(renderTitle)) {
attributes["class"].push("tw-tiddler-missing");
}
// Return the children
return [$tw.Tree.Element("div",attributes,childrenClone)];
};
exports.refreshInDom = function(changes) {
var t;
// Set the class for missing tiddlers
var renderTitle = this.params.target;
if(typeof renderTitle !== "string") {
renderTitle = this.params.template;
}
if(renderTitle) {
$tw.utils.toggleClass(this.children[0].domNode,"tw-tiddler-missing",!this.wiki.tiddlerExists(renderTitle));
}
// Rerender the tiddler if it is impacted by the changes
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
// Manually reexecute and rerender this macro
while(this.domNode.hasChildNodes()) {
this.domNode.removeChild(this.domNode.firstChild);
}
this.execute(this.parents,this.tiddlerTitle);
for(t=0; t<this.children.length; t++) {
this.children[t].renderInDom(this.domNode);
}
} else {
// Refresh any children
for(t=0; t<this.children.length; t++) {
this.children[t].refreshInDom(changes);
}
}
};
})();

View File

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

View File

@@ -0,0 +1,56 @@
/*\
title: $:/core/modules/macros/video.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "video",
params: {
src: {byName: "default", type: "text"},
type: {byName: true, type: "text"},
width: {byName: true, type: "text"},
height: {byName: true, type: "text"}
}
};
exports.executeMacro = function() {
var src = this.params.src,
videoType = this.params.type || "vimeo",
videoWidth = this.params.width || 640,
videoHeight = this.params.height || 360;
switch(videoType) {
case "vimeo":
return [$tw.Tree.Element("iframe",{
src: "http://player.vimeo.com/video/" + src + "?autoplay=0",
width: videoWidth,
height: videoHeight,
frameborder: 0
})];
case "youtube":
return [$tw.Tree.Element("iframe",{
type: "text/html",
src: "http://www.youtube.com/embed/" + src,
width: videoWidth,
height: videoHeight,
frameborder: 0
})];
case "archiveorg":
return [$tw.Tree.Element("iframe",{
src: "http://www.archive.org/embed/" + src,
width: videoWidth,
height: videoHeight,
frameborder: 0
})];
default:
return [];
}
};
})();

105
core/modules/macros/view.js Normal file
View File

@@ -0,0 +1,105 @@
/*\
title: $:/core/modules/macros/view.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "view",
dependentOnContextTiddler: true,
params: {
field: {byPos: 0, type: "text"},
format: {byPos: 1, type: "text"},
template: {byPos: 2, type: "text"}
}
};
exports.executeMacro = function() {
var tiddler = this.wiki.getTiddler(this.tiddlerTitle),
field = this.hasParameter("field") ? this.params.field : "title",
value,
children,
t,
childrenClone = [],
parents = this.parents;
if(tiddler) {
value = tiddler.fields[field];
} else {
switch(field) {
case "text":
value = "The tiddler '" + this.tiddlerTitle + "' does not exist";
break;
case "title":
value = this.tiddlerTitle;
break;
case "modified":
case "created":
value = new Date();
break;
default:
value = "Missing tiddler '" + this.tiddlerTitle + "'";
break;
}
}
switch(this.params.format) {
case "link":
if(value === undefined) {
return [];
} else {
var link = $tw.Tree.Macro("link",
{to: value},
[$tw.Tree.Text(value)],
this.wiki);
link.execute(parents,this.tiddlerTitle);
return [link];
}
break;
case "wikified":
if(tiddler && this.params.field === "text") {
if(parents.indexOf(tiddler.fields.title) !== -1) {
children = [$tw.Tree.errorNode("Tiddler recursion error in <<view>> macro")];
} else {
children = this.wiki.parseTiddler(tiddler.fields.title).tree;
}
parents = parents.slice(0);
parents.push(tiddler.fields.title);
} else {
children = this.wiki.parseText("text/x-tiddlywiki",value).tree;
}
for(t=0; t<children.length; t++) {
childrenClone.push(children[t].clone());
}
for(t=0; t<childrenClone.length; t++) {
childrenClone[t].execute(parents,this.tiddlerTitle);
}
return childrenClone;
case "date":
var template = this.params.template || "DD MMM YYYY";
if(value === undefined) {
return [];
} else {
return [$tw.Tree.Text($tw.utils.formatDateString(value,template))];
}
break;
default: // "text"
// Get the stringified version of the field value
if(field !== "text" && tiddler) {
value = tiddler.getFieldString(field);
}
if(value === undefined || value === null) {
return [];
} else {
return [$tw.Tree.Text(value)];
}
}
return [];
};
})();

View File

@@ -0,0 +1,105 @@
/*\
title: $:/core/modules/macros/zoomer.js
type: application/javascript
module-type: macro
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "zoomer",
wrapperTag: "div",
params: {
},
events: ["touchstart","touchmove","touchend"]
};
exports.startZoomer = function(x,y) {
this.inZoomer = true;
this.startX = x;
this.startY = y;
$tw.utils.addClass(document.body,"in-zoomer");
};
exports.stopZoomer = function() {
var newScrollY = this.yFactor * (this.bodyHeight - this.windowHeight);
this.inZoomer = false;
window.scrollTo(0,newScrollY);
document.body.style.webkitTransform = "translateY(" + newScrollY * this.xFactor + "px) " +
"scale(" + this.scale + ") " +
"translateY(" + ((this.windowHeight / this.scale) - this.bodyHeight) * this.yFactor * this.xFactor + "px)";
$tw.utils.removeClass(document.body,"in-zoomer");
document.body.style.webkitTransform = "translateY(0) scale(1) translateY(0)";
};
/*
Zoom the body element given a touch/mouse position in screen coordinates
*/
exports.hoverZoomer = function(x,y) {
// Put the transform origin at the top in the middle
document.body.style.webkitTransformOrigin = "50% 0";
// Some shortcuts
this.bodyWidth = document.body.offsetWidth;
this.bodyHeight = document.body.offsetHeight;
this.windowWidth = window.innerWidth;
this.windowHeight = window.innerHeight;
// Compute the scale factor for fitting the entire page into the window. This is
// the scale factor we'll use when the touch is far to the left
this.minScale = this.windowHeight / this.bodyHeight;
if(this.minScale < 0.1) {
// Don't scale to less than 10% of original size
this.minScale = 0.1;
} else if(this.minScale > 1) {
// Nor should we scale up if the body is shorter than the window
this.minScale = 1;
}
// We divide the screen into two horizontal zones divided by the right edge of the body at maximum zoom (ie minimum scale)
this.splitPos = this.windowWidth/2 + (this.bodyWidth * this.minScale)/2;
// Compute the 0->1 ratio (from right to left) of the position of the touch within the right zone
this.xFactor = (this.windowWidth - x) / (this.windowWidth - this.splitPos);
if(this.xFactor > 1) {
this.xFactor = 1;
}
// And the 0->1 ratio (from top to bottom) of the position of the touch down the screen
this.yFactor = y/this.windowHeight;
// Now interpolate the scale
this.scale = (this.minScale - 1) * this.xFactor + 1;
// Apply the transform. The malarkey with .toFixed() is because otherwise we might get numbers in
// exponential notation (such as 5.1e-15) that are illegal in CSS
var preTranslateY = window.scrollY * this.xFactor,
scale = this.scale,
postTranslateY = ((this.windowHeight / this.scale) - this.bodyHeight) * this.yFactor * this.xFactor;
var transform = "translateY(" + preTranslateY.toFixed(8) + "px) " +
"scale(" + scale.toFixed(8) + ") " +
"translateY(" + postTranslateY.toFixed(8) + "px)";
document.body.style.webkitTransform = transform;
};
exports.handleEvent = function(event) {
switch(event.type) {
case "touchstart":
this.startZoomer(event.touches[0].clientX,event.touches[0].clientY);
this.hoverZoomer(event.touches[0].clientX,event.touches[0].clientY);
event.preventDefault();
return false;
case "touchmove":
this.hoverZoomer(event.touches[0].clientX,event.touches[0].clientY);
event.preventDefault();
return false;
case "touchend":
this.stopZoomer();
event.preventDefault();
return false;
}
};
exports.executeMacro = function() {
this.inZoomer = false;
return [];
};
})();

View File

@@ -0,0 +1,35 @@
/*\
title: $:/core/modules/parsers/imageparser.js
type: application/javascript
module-type: parser
Parses an image into a parse tree containing an HTML img element
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var ImageParser = function(options) {
this.wiki = options.wiki;
};
ImageParser.prototype.parse = function(type,text) {
var src;
if(type === "image/svg+xml" || type === ".svg") {
src = "data:image/svg+xml," + encodeURIComponent(text);
} else {
src = "data:" + type + ";base64," + text;
}
return new $tw.Renderer([$tw.Tree.Element("img",{src: src})],new $tw.Dependencies());
};
exports["image/svg+xml"] = ImageParser;
exports["image/jpg"] = ImageParser;
exports["image/jpeg"] = ImageParser;
exports["image/png"] = ImageParser;
exports["image/gif"] = ImageParser;
})();

View File

@@ -0,0 +1,11 @@
{
"tiddlers": [
{
"file": "../../../../../node_modules/esprima/esprima.js",
"fields": {
"title": "$:/core/modules/parsers/javascriptparser/esprima.js",
"type": "application/javascript"
}
}
]
}

View File

@@ -0,0 +1,50 @@
/*\
title: $:/core/modules/parsers/jsonparser.js
type: application/javascript
module-type: parser
Parses a JSON object into a parse tree
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var renderObject = function(obj) {
var children = [],t;
if($tw.utils.isArray(obj)) {
for(t=0; t<obj.length; t++) {
children.push($tw.Tree.Element("li",{
"class": ["jsonArrayMember"]
},[renderObject(obj[t])]));
}
return $tw.Tree.Element("ul",{
"class": ["jsonArray"]
},children);
} else if(typeof obj === "object") {
for(t in obj) {
children.push($tw.Tree.Element("li",{
"class": ["jsonObjectMember"]
},[$tw.Tree.splitLabelNode("JSON",[$tw.Tree.Text(t)],[renderObject(obj[t])])]));
}
return $tw.Tree.Element("ul",{
"class": ["jsonObject"]
},children);
} else {
return $tw.Tree.labelNode("JSON" + (typeof obj),[$tw.Tree.Text(JSON.stringify(obj))],["jsonValue"]);
}
};
var JSONParser = function(options) {
this.wiki = options.wiki;
};
JSONParser.prototype.parse = function(type,text) {
return new $tw.Renderer([renderObject(JSON.parse(text))],new $tw.Dependencies());
};
exports["application/json"] = JSONParser;
})();

View File

@@ -0,0 +1,25 @@
/*\
title: $:/core/modules/parsers/plaintextparser.js
type: application/javascript
module-type: parser
Renders plain text tiddlers
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var PlainTextParser = function(options) {
this.wiki = options.wiki;
};
PlainTextParser.prototype.parse = function(type,text) {
return new $tw.Renderer([$tw.Tree.Element("pre",{},[$tw.Tree.Text(text)])],new $tw.Dependencies());
};
exports["text/plain"] = PlainTextParser;
})();

View File

@@ -0,0 +1,59 @@
/*\
title: $:/core/modules/parsers/tiddlytextparser.js
type: application/javascript
module-type: parser
Parses a plain text block that can also contain macros and transclusions.
The syntax for transclusions is:
[[tiddlerTitle]]
The syntax for macros is:
<<macroName params>>
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var TiddlyTextParser = function(options) {
this.wiki = options.wiki;
};
TiddlyTextParser.prototype.parse = function(type,text) {
var output = [],
dependencies = new $tw.Dependencies(),
macroRegExp = /(?:\[\[([^\]]+)\]\])|(?:<<(?:([!@£\$%\^\&\*\(\)`\~'"\|\\\/;\:\.\,\+\=\-\_\{\}])|([^>\s]+))(?:\s*)((?:[^>]|(?:>(?!>)))*)>>)/mg,
lastMatchPos = 0,
match,
macroNode;
do {
match = macroRegExp.exec(text);
if(match) {
output.push($tw.Tree.Text(text.substring(lastMatchPos,match.index)));
var macroName = match[2] || match[3];
if(match[1]) { // Transclusion
macroNode = $tw.Tree.Macro("tiddler",{
target: match[1]
},[],this.wiki);
} else if(macroName) { // Macro call
macroNode = $tw.Tree.Macro(macroName,match[4],[],this.wiki);
}
output.push(macroNode);
dependencies.mergeDependencies(macroNode.dependencies);
lastMatchPos = match.index + match[0].length;
} else {
output.push($tw.Tree.Text(text.substr(lastMatchPos)));
}
} while(match);
return new $tw.Renderer(output,dependencies);
};
exports["text/x-tiddlywiki-css"] = TiddlyTextParser;
exports["text/x-tiddlywiki-html"] = TiddlyTextParser;
})();

View File

@@ -0,0 +1,709 @@
/*\
title: $:/core/modules/parsers/wikitextparser/rules/wikitextrules.js
type: application/javascript
module-type: wikitextrule
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var textPrimitives = {
upperLetter: "[A-Z\u00c0-\u00de\u0150\u0170]",
lowerLetter: "[a-z0-9_\\-\u00df-\u00ff\u0151\u0171]",
anyLetter: "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]",
anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]",
sliceSeparator: "::",
sectionSeparator: "##",
urlPattern: "(?:file|http|https|mailto|ftp|irc|news|data):[^\\s'\"]+(?:/|\\b)",
unWikiLink: "~",
brackettedLink: "\\[\\[([^\\]]+)\\]\\]",
titledBrackettedLink: "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]"
};
textPrimitives.wikiLink = "(?:(?:" + textPrimitives.upperLetter + "+" +
textPrimitives.lowerLetter + "+" +
textPrimitives.upperLetter +
textPrimitives.anyLetter + "*)|(?:" +
textPrimitives.upperLetter + "{2,}" +
textPrimitives.lowerLetter + "+))";
textPrimitives.cssLookahead = "(?:(" + textPrimitives.anyLetter +
"+)\\(([^\\)\\|\\n]+)(?:\\):))|(?:(" + textPrimitives.anyLetter + "+):([^;\\|\\n]+);)";
textPrimitives.cssLookaheadRegExp = new RegExp(textPrimitives.cssLookahead,"mg");
textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" + textPrimitives.titledBrackettedLink + ")|(?:" +
textPrimitives.brackettedLink + ")|(?:" +
textPrimitives.urlPattern + ")","mg");
textPrimitives.tiddlerAnyLinkRegExp = new RegExp("("+ textPrimitives.wikiLink + ")|(?:" +
textPrimitives.titledBrackettedLink + ")|(?:" +
textPrimitives.brackettedLink + ")|(?:" +
textPrimitives.urlPattern + ")","mg");
// Helper to add an attribute to an HTML node
var setAttr = function(node,attr,value) {
if(!node.attributes) {
node.attributes = {};
}
node.attributes[attr] = value;
};
var inlineCssHelper = function(w) {
var styles = [];
textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
var lookaheadMatch = textPrimitives.cssLookaheadRegExp.exec(w.source);
while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
var s,v;
if(lookaheadMatch[1]) {
s = lookaheadMatch[1];
v = lookaheadMatch[2];
} else {
s = lookaheadMatch[3];
v = lookaheadMatch[4];
}
if(s=="bgcolor")
s = "backgroundColor";
if(s=="float")
s = "cssFloat";
styles.push({style: s, value: v});
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
lookaheadMatch = textPrimitives.cssLookaheadRegExp.exec(w.source);
}
return styles;
};
var applyCssHelper = function(e,styles) {
if(styles.length > 0) {
if(!e.attributes) {
e.attributes = {};
}
if(!e.attributes.style) {
e.attributes.style = {};
}
for(var t=0; t< styles.length; t++) {
e.attributes.style[styles[t].style] = styles[t].value;
}
}
};
var enclosedTextHelper = function(w) {
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
var text = lookaheadMatch[1];
w.output.push($tw.Tree.Element(this.element,null,[$tw.Tree.Text(text)]));
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
};
var insertMacroCall = function(w,output,name,params,children) {
if(name in w.wiki.macros) {
var macroNode = $tw.Tree.Macro(name,params,children,w.wiki);
w.dependencies.mergeDependencies(macroNode.dependencies);
output.push(macroNode);
}
};
var rules = [
{
name: "table",
match: "^\\|(?:[^\\n]*)\\|(?:[fhck]?)$",
lookaheadRegExp: /^\|([^\n]*)\|([fhck]?)$/mg,
rowTermRegExp: /(\|(?:[fhck]?)$\n?)/mg,
cellRegExp: /(?:\|([^\n\|]*)\|)|(\|[fhck]?$\n?)/mg,
cellTermRegExp: /((?:\x20*)\|)/mg,
rowTypes: {"c":"caption", "h":"thead", "":"tbody", "f":"tfoot"},
handler: function(w)
{
var table = $tw.Tree.Element("table",{"class": "table"},[]);
w.output.push(table);
var prevColumns = [];
var currRowType = null;
var rowContainer;
var rowCount = 0;
w.nextMatch = w.matchStart;
this.lookaheadRegExp.lastIndex = w.nextMatch;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
var nextRowType = lookaheadMatch[2];
if(nextRowType == "k") {
table.attributes["class"] = lookaheadMatch[1];
w.nextMatch += lookaheadMatch[0].length+1;
} else {
if(nextRowType != currRowType) {
rowContainer = $tw.Tree.Element(this.rowTypes[nextRowType],{},[]);
table.children.push(rowContainer);
currRowType = nextRowType;
}
if(currRowType == "c") {
// Caption
w.nextMatch++;
// Move the caption to the first row if it isn't already
if(table.children.length !== 1) {
table.children.pop(); // Take rowContainer out of the children array
table.children.splice(0,0,rowContainer); // Insert it at the bottom
}
rowContainer.attributes.align = rowCount === 0 ? "top" : "bottom";
w.subWikifyTerm(rowContainer.children,this.rowTermRegExp);
} else {
var theRow = $tw.Tree.Element("tr",{},[]);
theRow.attributes["class"] = rowCount%2 ? "oddRow" : "evenRow";
rowContainer.children.push(theRow);
this.rowHandler(w,theRow.children,prevColumns);
rowCount++;
}
}
this.lookaheadRegExp.lastIndex = w.nextMatch;
lookaheadMatch = this.lookaheadRegExp.exec(w.source);
}
},
rowHandler: function(w,e,prevColumns)
{
var col = 0;
var colSpanCount = 1;
var prevCell = null;
this.cellRegExp.lastIndex = w.nextMatch;
var cellMatch = this.cellRegExp.exec(w.source);
while(cellMatch && cellMatch.index == w.nextMatch) {
if(cellMatch[1] == "~") {
// Rowspan
var last = prevColumns[col];
if(last) {
last.rowSpanCount++;
last.element.attributes.rowspan = last.rowSpanCount;
last.element.attributes.valign = "center";
if(colSpanCount > 1) {
last.element.attributes.colspan = colSpanCount;
colSpanCount = 1;
}
}
w.nextMatch = this.cellRegExp.lastIndex-1;
} else if(cellMatch[1] == ">") {
// Colspan
colSpanCount++;
w.nextMatch = this.cellRegExp.lastIndex-1;
} else if(cellMatch[2]) {
// End of row
if(prevCell && colSpanCount > 1) {
prevCell.attributes.colspan = colSpanCount;
}
w.nextMatch = this.cellRegExp.lastIndex;
break;
} else {
// Cell
w.nextMatch++;
var styles = inlineCssHelper(w);
var spaceLeft = false;
var chr = w.source.substr(w.nextMatch,1);
while(chr == " ") {
spaceLeft = true;
w.nextMatch++;
chr = w.source.substr(w.nextMatch,1);
}
var cell;
if(chr == "!") {
cell = $tw.Tree.Element("th",{},[]);
e.push(cell);
w.nextMatch++;
} else {
cell = $tw.Tree.Element("td",{},[]);
e.push(cell);
}
prevCell = cell;
prevColumns[col] = {rowSpanCount:1,element:cell};
if(colSpanCount > 1) {
cell.attributes.colspan = colSpanCount;
colSpanCount = 1;
}
applyCssHelper(cell,styles);
w.subWikifyTerm(cell.children,this.cellTermRegExp);
if(w.matchText.substr(w.matchText.length-2,1) == " ") // spaceRight
cell.attributes.align = spaceLeft ? "center" : "left";
else if(spaceLeft)
cell.attributes.align = "right";
w.nextMatch--;
}
col++;
this.cellRegExp.lastIndex = w.nextMatch;
cellMatch = this.cellRegExp.exec(w.source);
}
}
},
{
name: "heading",
match: "^!{1,6}",
termRegExp: /(\n)/mg,
handler: function(w)
{
var e = $tw.Tree.Element("h" + w.matchLength,{},[]);
w.output.push(e);
w.subWikifyTerm(e.children,this.termRegExp);
}
},
{
name: "list",
match: "^(?:[\\*#;:]+)",
lookaheadRegExp: /^(?:(?:(\*)|(#)|(;)|(:))+)/mg,
termRegExp: /(\n)/mg,
handler: function(w)
{
var stack = [w.output];
var currLevel = 0, currType = null;
var listLevel, listType, itemType, baseType;
w.nextMatch = w.matchStart;
this.lookaheadRegExp.lastIndex = w.nextMatch;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
if(lookaheadMatch[1]) {
listType = "ul";
itemType = "li";
} else if(lookaheadMatch[2]) {
listType = "ol";
itemType = "li";
} else if(lookaheadMatch[3]) {
listType = "dl";
itemType = "dt";
} else if(lookaheadMatch[4]) {
listType = "dl";
itemType = "dd";
}
if(!baseType)
baseType = listType;
listLevel = lookaheadMatch[0].length;
w.nextMatch += lookaheadMatch[0].length;
var t,e;
if(listLevel > currLevel) {
for(t=currLevel; t<listLevel; t++) {
var target = stack[stack.length-1];
if(currLevel !== 0 && target.children) {
target = target.children[target.children.length-1];
}
e = $tw.Tree.Element(listType,{},[]);
target.push(e);
stack.push(e.children);
}
} else if(listType!=baseType && listLevel==1) {
w.nextMatch -= lookaheadMatch[0].length;
return;
} else if(listLevel < currLevel) {
for(t=currLevel; t>listLevel; t--)
stack.pop();
} else if(listLevel == currLevel && listType != currType) {
stack.pop();
e = $tw.Tree.Element(listType,{},[]);
stack[stack.length-1].push(e);
stack.push(e.children);
}
currLevel = listLevel;
currType = listType;
e = $tw.Tree.Element(itemType,{},[]);
stack[stack.length-1].push(e);
w.subWikifyTerm(e.children,this.termRegExp);
this.lookaheadRegExp.lastIndex = w.nextMatch;
lookaheadMatch = this.lookaheadRegExp.exec(w.source);
}
}
},
{
name: "quoteByBlock",
match: "^<<<\\n",
termRegExp: /(^<<<(\n|$))/mg,
element: "blockquote",
handler: function(w) {
var e = $tw.Tree.Element(this.element,{},[]);
w.output.push(e);
w.subWikifyTerm(e.children,this.termRegExp);
}
},
{
name: "quoteByLine",
match: "^>+",
lookaheadRegExp: /^>+/mg,
termRegExp: /(\n)/mg,
element: "blockquote",
handler: function(w)
{
var stack = [w.output];
var currLevel = 0;
var newLevel = w.matchLength;
var t,matched,e;
do {
if(newLevel > currLevel) {
for(t=currLevel; t<newLevel; t++) {
e = $tw.Tree.Element(this.element,{},[]);
stack[stack.length-1].push(e);
}
} else if(newLevel < currLevel) {
for(t=currLevel; t>newLevel; t--)
stack.pop();
}
currLevel = newLevel;
w.subWikifyTerm(stack[stack.length-1],this.termRegExp);
stack[stack.length-1].push($tw.Tree.Element("br"));
this.lookaheadRegExp.lastIndex = w.nextMatch;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
if(matched) {
newLevel = lookaheadMatch[0].length;
w.nextMatch += lookaheadMatch[0].length;
}
} while(matched);
}
},
{
name: "rule",
match: "^----+$\\n?|<hr ?/?>\\n?",
handler: function(w)
{
w.output.push($tw.Tree.Element("hr"));
}
},
{
name: "monospacedByLine",
match: "^(?:/\\*\\{\\{\\{\\*/|\\{\\{\\{|//\\{\\{\\{|<!--\\{\\{\\{-->)\\n",
element: "pre",
handler: function(w)
{
switch(w.matchText) {
case "/*{{{*/\n": // CSS
this.lookaheadRegExp = /\/\*\{\{\{\*\/\n*((?:^[^\n]*\n)+?)(\n*^\f*\/\*\}\}\}\*\/$\n?)/mg;
break;
case "{{{\n": // monospaced block
this.lookaheadRegExp = /^\{\{\{\n((?:^[^\n]*\n)+?)(^\f*\}\}\}$\n?)/mg;
break;
case "//{{{\n": // plugin
this.lookaheadRegExp = /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\f*\/\/\}\}\}$\n?)/mg;
break;
case "<!--{{{-->\n": //template
this.lookaheadRegExp = /<!--\{\{\{-->\n*((?:^[^\n]*\n)+?)(\n*^\f*<!--\}\}\}-->$\n?)/mg;
break;
default:
break;
}
enclosedTextHelper.call(this,w);
}
},
{
name: "typedBlock",
match: "^\\$\\$\\$(?:.*)\\n",
lookaheadRegExp: /^\$\$\$(.*)\n((?:^[^\n]*\n)+?)(^\f*\$\$\$$\n?)/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
// The wikitext parsing infrastructure is horribly unre-entrant
var mimeType = lookaheadMatch[1],
content = lookaheadMatch[2],
oldOutput = w.output,
oldSource = w.source,
oldNextMatch = w.nextMatch,
oldChildren = w.children,
oldDependencies = w.dependencies,
parseTree = w.wiki.parseText(mimeType,content,{defaultType: "text/plain"});
w.output = oldOutput;
w.source = oldSource;
w.nextMatch = oldNextMatch;
w.children = oldChildren;
w.dependencies = oldDependencies;
w.output.push.apply(w.output,parseTree.nodes);
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "wikifyComment",
match: "^(?:/\\*\\*\\*|<!---)\\n",
handler: function(w)
{
var termRegExp = (w.matchText == "/***\n") ? (/(^\*\*\*\/\n)/mg) : (/(^--->\n)/mg);
w.subWikifyTerm(w.output,termRegExp);
}
},
{
name: "macro",
match: "<<",
lookaheadRegExp: /<<(?:([!@£\$%\^\&\*\(\)`\~'"\|\\\/;\:\.\,\+\=\-\_\{\}])|([^>\s]+))(?:\s*)((?:[^>]|(?:>(?!>)))*)>>/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source),
name = lookaheadMatch[1] || lookaheadMatch[2];
if(lookaheadMatch && lookaheadMatch.index == w.matchStart && name) {
w.nextMatch = this.lookaheadRegExp.lastIndex;
insertMacroCall(w,w.output,name,lookaheadMatch[3]);
}
}
},
{
name: "prettyLink",
match: "\\[\\[",
lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\]/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
var text = lookaheadMatch[1],
link = text;
if(lookaheadMatch[3]) {
// Pretty bracketted link
link = lookaheadMatch[3];
}
insertMacroCall(w,w.output,"link",{to: link},[$tw.Tree.Text(text)]);
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "wikiLink",
match: textPrimitives.unWikiLink+"?"+textPrimitives.wikiLink,
handler: function(w)
{
if(w.matchText.substr(0,1) == textPrimitives.unWikiLink) {
w.outputText(w.output,w.matchStart+1,w.nextMatch);
return;
}
if(w.matchStart > 0) {
var preRegExp = new RegExp(textPrimitives.anyLetterStrict,"mg");
preRegExp.lastIndex = w.matchStart-1;
var preMatch = preRegExp.exec(w.source);
if(preMatch.index == w.matchStart-1) {
w.outputText(w.output,w.matchStart,w.nextMatch);
return;
}
}
if(w.autoLinkWikiWords) {
insertMacroCall(w,w.output,"link",{to: w.matchText},[$tw.Tree.Text(w.source.substring(w.matchStart,w.nextMatch))]);
} else {
w.outputText(w.output,w.matchStart,w.nextMatch);
}
}
},
{
name: "urlLink",
match: textPrimitives.urlPattern,
handler: function(w)
{
insertMacroCall(w,w.output,"link",{to: w.matchText},[$tw.Tree.Text(w.source.substring(w.matchStart,w.nextMatch))]);
}
},
{
name: "image",
match: "\\[[<>]?[Ii][Mm][Gg]\\[",
// [<] sequence below is to avoid lessThan-questionMark sequence so TiddlyWikis can be included in PHP files
lookaheadRegExp: /\[([<]?)(>?)[Ii][Mm][Gg]\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source),
imageParams = {},
linkParams = {};
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
if(lookaheadMatch[1]) {
imageParams.alignment = "left";
} else if(lookaheadMatch[2]) {
imageParams.alignment = "right";
}
if(lookaheadMatch[3]) {
imageParams.text = lookaheadMatch[3];
}
imageParams.src = lookaheadMatch[4];
if(lookaheadMatch[5]) {
linkParams.target = lookaheadMatch[5];
var linkChildren = [];
insertMacroCall(w,w.output,"link",linkParams,linkChildren);
insertMacroCall(w,linkChildren,"image",imageParams);
} else {
insertMacroCall(w,w.output,"image",imageParams);
}
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "html",
match: "<[Hh][Tt][Mm][Ll]>",
lookaheadRegExp: /<[Hh][Tt][Mm][Ll]>((?:.|\n)*?)<\/[Hh][Tt][Mm][Ll]>/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
w.output.push($tw.Tree.Element("html",{},[$tw.Tree.Raw(lookaheadMatch[1])]));
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "commentByBlock",
match: "/%",
lookaheadRegExp: /\/%((?:.|\n)*?)%\//mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
},
{
name: "characterFormat",
match: "''|//|__|\\^\\^|~~|--(?!\\s|$)|\\{\\{\\{|`",
handler: function(w)
{
var e,lookaheadRegExp,lookaheadMatch;
switch(w.matchText) {
case "''":
e = $tw.Tree.Element("strong",null,[]);
w.output.push(e);
w.subWikifyTerm(e.children,/('')/mg);
break;
case "//":
e = $tw.Tree.Element("em",null,[]);
w.output.push(e);
w.subWikifyTerm(e.children,/(\/\/)/mg);
break;
case "__":
e = $tw.Tree.Element("u",null,[]);
w.output.push(e);
w.subWikifyTerm(e.children,/(__)/mg);
break;
case "^^":
e = $tw.Tree.Element("sup",null,[]);
w.output.push(e);
w.subWikifyTerm(e.children,/(\^\^)/mg);
break;
case "~~":
e = $tw.Tree.Element("sub",null,[]);
w.output.push(e);
w.subWikifyTerm(e.children,/(~~)/mg);
break;
case "--":
e = $tw.Tree.Element("strike",null,[]);
w.output.push(e);
w.subWikifyTerm(e.children,/(--)/mg);
break;
case "`":
lookaheadRegExp = /`((?:.|\n)*?)`/mg;
lookaheadRegExp.lastIndex = w.matchStart;
lookaheadMatch = lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
w.output.push($tw.Tree.Element("code",null,[$tw.Tree.Text(lookaheadMatch[1])]));
w.nextMatch = lookaheadRegExp.lastIndex;
}
break;
case "{{{":
lookaheadRegExp = /\{\{\{((?:.|\n)*?)\}\}\}/mg;
lookaheadRegExp.lastIndex = w.matchStart;
lookaheadMatch = lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
w.output.push($tw.Tree.Element("code",null,[$tw.Tree.Text(lookaheadMatch[1])]));
w.nextMatch = lookaheadRegExp.lastIndex;
}
break;
}
}
},
{
name: "customFormat",
match: "@@|\\{\\{",
handler: function(w)
{
switch(w.matchText) {
case "@@":
var e = $tw.Tree.Element("span",null,[]);
w.output.push(e);
var styles = inlineCssHelper(w);
if(styles.length === 0)
setAttr(e,"class","marked");
else
applyCssHelper(e,styles);
w.subWikifyTerm(e.children,/(@@)/mg);
break;
case "{{":
var lookaheadRegExp = /\{\{[\s]*([\-\w]+[\-\s\w]*)[\s]*\{(\n?)/mg;
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source);
if(lookaheadMatch) {
w.nextMatch = lookaheadRegExp.lastIndex;
e = $tw.Tree.Element(lookaheadMatch[2] == "\n" ? "div" : "span",{
"class": lookaheadMatch[1]
},[]);
w.output.push(e);
w.subWikifyTerm(e.children,/(\}\}\})/mg);
}
break;
}
}
},
{
name: "mdash",
match: "--",
handler: function(w)
{
w.output.push($tw.Tree.Entity("&mdash;"));
}
},
{
name: "lineBreak",
match: "\\n|<br ?/?>",
handler: function(w)
{
w.output.push($tw.Tree.Element("br"));
}
},
{
name: "rawText",
match: "\"{3}|<nowiki>",
lookaheadRegExp: /(?:\"{3}|<nowiki>)((?:.|\n)*?)(?:\"{3}|<\/nowiki>)/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
w.output.push($tw.Tree.Text(lookaheadMatch[1]));
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "htmlEntitiesEncoding",
match: "&#?[a-zA-Z0-9]{2,8};",
handler: function(w)
{
w.output.push($tw.Tree.Entity(w.matchText));
}
}
];
exports.rules = rules;
})();

View File

@@ -0,0 +1,186 @@
/*\
title: $:/core/modules/parsers/wikitextparser/wikitextparser.js
type: application/javascript
module-type: parser
Parses a block of tiddlywiki-format wiki text into a parse tree object. This is a transliterated version of the old TiddlyWiki code. The plan is to replace it with a new, mostly backwards compatible parser built in PEGJS.
A wikitext parse tree is an array of objects with a `type` field that can be `text`,`macro` or the name of an HTML element.
Text nodes are represented as `{type: "text", value: "A string of text"}`.
Macro nodes look like this:
`
{type: "macro", name: "view", params: {
one: {type: "eval", value: "2+2"},
two: {type: "string", value: "twenty two"}
}}
`
HTML nodes look like this:
`
{type: "div", attributes: {
src: "one"
styles: {
"background-color": "#fff",
"color": "#000"
}
}}
`
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Creates a new instance of the wiki text parser with the specified options. The
options are a hashmap of mandatory members as follows:
wiki: The wiki object to use to parse any cascaded content (eg transclusion)
Planned:
enableRules: An array of names of wiki text rules to enable. If not specified, all rules are available
extraRules: An array of additional rule handlers to add
enableMacros: An array of names of macros to enable. If not specified, all macros are available
extraMacros: An array of additional macro handlers to add
*/
var WikiTextParser = function(options) {
this.wiki = options.wiki;
this.autoLinkWikiWords = true;
};
WikiTextParser.prototype.installRules = function(rules) {
var pattern = [];
for(var n=0; n<rules.length; n++) {
pattern.push("(" + rules[n].match + ")");
}
this.rules = rules;
this.rulesRegExp = new RegExp(pattern.join("|"),"mg");
};
WikiTextParser.prototype.parse = function(type,text) {
this.source = text;
this.nextMatch = 0;
this.children = [];
this.dependencies = new $tw.Dependencies();
this.output = null;
this.subWikify(this.children);
var tree = new $tw.Renderer(this.children,this.dependencies);
this.source = null;
this.children = null;
return tree;
};
WikiTextParser.prototype.outputText = function(place,startPos,endPos) {
if(startPos < endPos) {
place.push($tw.Tree.Text(this.source.substring(startPos,endPos)));
}
};
WikiTextParser.prototype.subWikify = function(output,terminator) {
// Handle the terminated and unterminated cases separately, this speeds up wikifikation by about 30%
if(terminator)
this.subWikifyTerm(output,new RegExp("(" + terminator + ")","mg"));
else
this.subWikifyUnterm(output);
};
WikiTextParser.prototype.subWikifyUnterm = function(output) {
// subWikify can be indirectly recursive, so we need to save the old output pointer
var oldOutput = this.output;
this.output = output;
// Get the first match
this.rulesRegExp.lastIndex = this.nextMatch;
var ruleMatch = this.rulesRegExp.exec(this.source);
while(ruleMatch) {
// Output any text before the match
if(ruleMatch.index > this.nextMatch)
this.outputText(this.output,this.nextMatch,ruleMatch.index);
// Set the match parameters for the handler
this.matchStart = ruleMatch.index;
this.matchLength = ruleMatch[0].length;
this.matchText = ruleMatch[0];
this.nextMatch = this.rulesRegExp.lastIndex;
// Figure out which rule matched and call its handler
var t;
for(t=1; t<ruleMatch.length; t++) {
if(ruleMatch[t]) {
this.rules[t-1].handler(this);
this.rulesRegExp.lastIndex = this.nextMatch;
break;
}
}
// Get the next match
ruleMatch = this.rulesRegExp.exec(this.source);
}
// Output any text after the last match
if(this.nextMatch < this.source.length) {
this.outputText(this.output,this.nextMatch,this.source.length);
this.nextMatch = this.source.length;
}
// Restore the output pointer
this.output = oldOutput;
};
WikiTextParser.prototype.subWikifyTerm = function(output,terminatorRegExp) {
// subWikify can be indirectly recursive, so we need to save the old output pointer
var oldOutput = this.output;
this.output = output;
// Get the first matches for the rule and terminator RegExps
terminatorRegExp.lastIndex = this.nextMatch;
var terminatorMatch = terminatorRegExp.exec(this.source);
this.rulesRegExp.lastIndex = this.nextMatch;
var ruleMatch = this.rulesRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
while(terminatorMatch || ruleMatch) {
// Check for a terminator match before the next rule match
if(terminatorMatch && (!ruleMatch || terminatorMatch.index <= ruleMatch.index)) {
// Output any text before the match
if(terminatorMatch.index > this.nextMatch)
this.outputText(this.output,this.nextMatch,terminatorMatch.index);
// Set the match parameters
this.matchText = terminatorMatch[1];
this.matchLength = terminatorMatch[1].length;
this.matchStart = terminatorMatch.index;
this.nextMatch = this.matchStart + this.matchLength;
// Restore the output pointer
this.output = oldOutput;
return;
}
// It must be a rule match; output any text before the match
if(ruleMatch.index > this.nextMatch)
this.outputText(this.output,this.nextMatch,ruleMatch.index);
// Set the match parameters
this.matchStart = ruleMatch.index;
this.matchLength = ruleMatch[0].length;
this.matchText = ruleMatch[0];
this.nextMatch = this.rulesRegExp.lastIndex;
// Figure out which rule matched and call its handler
var t;
for(t=1; t<ruleMatch.length; t++) {
if(ruleMatch[t]) {
this.rules[t-1].handler(this);
this.rulesRegExp.lastIndex = this.nextMatch;
break;
}
}
// Get the next match
terminatorRegExp.lastIndex = this.nextMatch;
terminatorMatch = terminatorRegExp.exec(this.source);
ruleMatch = this.rulesRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
}
// Output any text after the last match
if(this.nextMatch < this.source.length) {
this.outputText(this.output,this.nextMatch,this.source.length);
this.nextMatch = this.source.length;
}
// Restore the output pointer
this.output = oldOutput;
};
exports["text/x-tiddlywiki"] = WikiTextParser;
})();

48
core/modules/renderer.js Normal file
View File

@@ -0,0 +1,48 @@
/*\
title: $:/core/modules/renderer.js
type: application/javascript
module-type: global
Represents a parse tree and associated data
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Renderer = function(tree,dependencies) {
this.tree = tree;
this.dependencies = dependencies;
};
Renderer.prototype.execute = function(parents,tiddlerTitle) {
for(var t=0; t<this.tree.length; t++) {
this.tree[t].execute(parents,tiddlerTitle);
}
};
Renderer.prototype.render = function(type) {
var output = [];
for(var t=0; t<this.tree.length; t++) {
output.push(this.tree[t].render(type));
}
return output.join("");
};
Renderer.prototype.renderInDom = function(parentDomNode,insertBefore) {
for(var t=0; t<this.tree.length; t++) {
this.tree[t].renderInDom(parentDomNode,insertBefore);
}
};
Renderer.prototype.refreshInDom = function(changes) {
for(var t=0; t<this.tree.length; t++) {
this.tree[t].refreshInDom(changes);
}
};
exports.Renderer = Renderer;
})();

View File

@@ -0,0 +1,71 @@
/*\
title: $:/core/modules/serializers.js
type: application/javascript
module-type: tiddlerserializer
Plugins to serialise tiddlers to a block of text
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports["text/plain"] = function(tiddler) {
return tiddler ? tiddler.fields.text : "";
};
exports["text/html"] = function(tiddler) {
var text = this.renderTiddler("text/html",tiddler.fields.title);
return text ? text : "";
};
exports["application/javascript"] = function(tiddler) {
var attributes = {type: "text/javascript"}; // The script type is set to text/javascript for compatibility with old browsers
for(var f in tiddler.fields) {
if(f !== "text") {
attributes["data-tiddler-" + f] = tiddler.getFieldString(f);
}
}
return $tw.Tree.Element(
"script",
attributes,
[$tw.Tree.Raw(tiddler.fields.text)]
).render("text/html");
};
exports["application/x-tiddler-module"] = function(tiddler) {
var attributes = {
type: "text/javascript",
"data-module": "yes"
}, // The script type is set to text/javascript for compatibility with old browsers
text = tiddler.fields.text;
text = "$tw.modules.define(\"" + tiddler.fields.title + "\",\"" + tiddler.fields["module-type"] + "\",function(module,exports,require) {" + text + "});\n";
for(var f in tiddler.fields) {
if(f !== "text") {
attributes["data-tiddler-" + f] = tiddler.getFieldString(f);
}
}
return $tw.Tree.Element(
"script",
attributes,
[$tw.Tree.Raw(text)]
).render("text/html");
};
exports["application/x-tiddler-html-div"] = function(tiddler) {
var result = [];
result.push("<div");
for(var f in tiddler.fields) {
if(f !== "text") {
result.push(" " + f + "=\"" + tiddler.getFieldString(f) + "\"");
}
}
result.push(">\n<pre>");
result.push($tw.utils.htmlEncode(tiddler.fields.text));
result.push("</pre>\n</div>");
return result.join("");
};
})();

70
core/modules/startup.js Normal file
View File

@@ -0,0 +1,70 @@
/*\
title: $:/core/modules/startup.js
type: application/javascript
module-type: startup
This is the main application logic for both the client and server
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.startup = function() {
var modules,n,m,f,commander;
// Set up additional global objects
$tw.plugins.applyMethods("global",$tw);
// Wire up plugin modules
$tw.plugins.applyMethods("config",$tw.config);
$tw.plugins.applyMethods("utils",$tw.utils);
$tw.Tiddler.fieldPlugins = $tw.plugins.getPluginsByTypeAsHashmap("tiddlerfield");
$tw.plugins.applyMethods("tiddlermethod",$tw.Tiddler.prototype);
$tw.plugins.applyMethods("wikimethod",$tw.Wiki.prototype);
$tw.plugins.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerPlugins);
$tw.Wiki.tiddlerSerializerPlugins = {};
$tw.plugins.applyMethods("tiddlerserializer",$tw.Wiki.tiddlerSerializerPlugins);
$tw.plugins.applyMethods("treeutils",$tw.Tree);
$tw.plugins.applyMethods("treenode",$tw.Tree);
// Get version information
$tw.version = $tw.utils.extractVersionInfo();
// Load up the tiddlers in the root of the core directory (we couldn't do before because we didn't have the serializers installed)
if(!$tw.isBrowser) {
$tw.plugins.loadPluginsFromFolder($tw.boot.bootPath,"$:/core",/^\.DS_Store$|.meta$|^modules$/);
}
// Set up the wiki store
$tw.wiki.initMacros();
$tw.wiki.initEditors();
$tw.wiki.initParsers();
// Set up the command plugins
$tw.Commander.initCommands();
// Host-specific startup
if($tw.isBrowser) {
// Display the PageTemplate
var renderer = $tw.wiki.parseTiddler("PageTemplate");
renderer.execute([],"PageTemplate");
renderer.renderInDom(document.body);
$tw.wiki.addEventListener("",function(changes) {
renderer.refreshInDom(changes);
});
console.log("$tw",$tw);
} else {
// Start a commander with the command line arguments
commander = new $tw.Commander(
Array.prototype.slice.call(process.argv,2),
function(err) {
if(err) {
console.log("Error: " + err);
}
},
$tw.wiki,
{output: process.stdout, error: process.stderr}
);
commander.execute();
}
};
})();

34
core/modules/tiddler.js Normal file
View File

@@ -0,0 +1,34 @@
/*\
title: $:/core/modules/tiddler.js
type: application/javascript
module-type: tiddlermethod
Extension methods for the $tw.Tiddler object
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.hasTag = function(tag) {
return this.fields.tags && this.fields.tags.indexOf(tag) !== -1;
};
exports.getFieldString = function(field) {
var value = this.fields[field];
// Check for a missing field
if(value === undefined) {
return undefined;
}
// Parse the field with the associated plugin (if any)
var fieldPlugin = $tw.Tiddler.fieldPlugins[field];
if(fieldPlugin) {
return fieldPlugin.stringify.call(this,value);
} else {
return value.toString();
}
};
})();

28
core/modules/tree.js Normal file
View File

@@ -0,0 +1,28 @@
/*\
title: $:/core/modules/tree.js
type: application/javascript
module-type: global
Renderer objects encapsulate a tree of nodes that are capable of rendering and selectively updating an
HTML representation of themselves. The following node types are defined:
* ''Macro'' - represents an invocation of a macro
* ''Element'' - represents a single HTML element
* ''Text'' - represents an HTML text node
* ''Entity'' - represents an HTML entity node
* ''Raw'' - represents a chunk of unparsed HTML text
These node types are implemented with prototypal inheritance from a base Node class. One
unusual convenience in the implementation is that the node constructors can be called without an explicit
`new` keyword: the constructors check for `this` not being an instance of themselves, and recursively invoke
themselves with `new` when required.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.Tree = {};
})();

View File

@@ -0,0 +1,57 @@
/*\
title: $:/core/modules/tree.utils.js
type: application/javascript
module-type: treeutils
Static utility methods for the $tw.Tree class
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Construct an error message
*/
exports.errorNode = function(text) {
return $tw.Tree.Element("span",{
"class": ["label","label-important"]
},[
new $tw.Tree.Text(text)
]);
};
/*
Construct a label
*/
exports.labelNode = function(type,value,classes) {
classes = (classes || []).slice(0);
classes.push("label");
return $tw.Tree.Element("span",{
"class": classes,
"data-tw-label-type": type
},value);
};
/*
Construct a split label
*/
exports.splitLabelNode = function(type,left,right,classes) {
classes = (classes || []).slice(0);
classes.push("splitLabel");
return $tw.Tree.Element("span",{
"class": classes
},[
$tw.Tree.Element("span",{
"class": ["splitLabelLeft"],
"data-tw-label-type": type
},left),
$tw.Tree.Element("span",{
"class": ["splitLabelRight"]
},right)
]);
};
})();

View File

@@ -0,0 +1,144 @@
/*\
title: $:/core/modules/treenodes/element.js
type: application/javascript
module-type: treenode
Element nodes
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Node = require("./node.js").Node;
var Element = function(type,attributes,children) {
if(this instanceof Element) {
this.type = type;
this.attributes = attributes || {};
this.children = children;
} else {
return new Element(type,attributes,children);
}
};
Element.prototype = new Node();
Element.prototype.constructor = Element;
Element.prototype.clone = function() {
var childClones;
if(this.children) {
childClones = [];
for(var t=0; t<this.children.length; t++) {
childClones.push(this.children[t].clone());
}
}
return new Element(this.type,this.attributes,childClones);
};
Element.prototype.execute = function(parents,tiddlerTitle) {
if(this.children) {
for(var t=0; t<this.children.length; t++) {
this.children[t].execute(parents,tiddlerTitle);
}
}
};
Element.prototype.render = function(type) {
var isHtml = type === "text/html",
output = [];
if(isHtml) {
output.push("<",this.type);
if(this.attributes) {
for(var a in this.attributes) {
var v = this.attributes[a];
if(v !== undefined) {
if($tw.utils.isArray(v)) {
v = v.join(" ");
} else if(typeof v === "object") {
var s = [];
for(var p in v) {
s.push(p + ":" + v[p] + ";");
}
v = s.join("");
}
output.push(" ",a,"='",$tw.utils.htmlEncode(v),"'");
}
}
}
output.push(">");
}
if(this.children) {
for(var t=0; t<this.children.length; t++) {
output.push(this.children[t].render(type));
}
if(isHtml) {
output.push("</",this.type,">");
}
}
return output.join("");
};
Element.prototype.renderInDom = function(parentDomNode,insertBefore) {
var element = document.createElement(this.type);
if(this.attributes) {
for(var a in this.attributes) {
var v = this.attributes[a];
if(v !== undefined) {
if($tw.utils.isArray(v)) { // Ahem, could there be arrays other than className?
element.className = v.join(" ");
} else if (typeof v === "object") { // ...or objects other than style?
for(var p in v) {
element.style[p] = v[p];
}
} else {
element.setAttribute(a,v);
}
}
}
}
if(insertBefore) {
parentDomNode.insertBefore(element,insertBefore);
} else {
parentDomNode.appendChild(element);
}
this.domNode = element;
if(this.children) {
for(var t=0; t<this.children.length; t++) {
this.children[t].renderInDom(element);
}
}
};
Element.prototype.refresh = function(changes) {
if(this.children) {
for(var t=0; t<this.children.length; t++) {
this.children[t].refresh(changes);
}
}
};
Element.prototype.refreshInDom = function(changes) {
if(this.children) {
for(var t=0; t<this.children.length; t++) {
this.children[t].refreshInDom(changes);
}
}
};
Element.prototype.broadcastEvent = function(event) {
if(this.children) {
for(var t=0; t<this.children.length; t++) {
if(!this.children[t].broadcastEvent(event)) {
return false;
}
}
}
return true;
};
exports.Element = Element;
})();

View File

@@ -0,0 +1,39 @@
/*\
title: $:/core/modules/treenodes/entity.js
type: application/javascript
module-type: treenode
Entity nodes
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Node = require("./node.js").Node;
var Entity = function(entity) {
if(this instanceof Entity) {
this.entity = entity;
} else {
return new Entity(entity);
}
};
Entity.prototype = new Node();
Entity.prototype.constructor = Entity;
Entity.prototype.render = function(type) {
return type === "text/html" ? this.entity : $tw.utils.entityDecode(this.entity);
};
Entity.prototype.renderInDom = function(domNode) {
this.domNode = document.createTextNode($tw.utils.entityDecode(this.entity));
domNode.appendChild(this.domNode);
};
exports.Entity = Entity;
})();

View File

@@ -0,0 +1,248 @@
/*\
title: $:/core/modules/treenodes/macro.js
type: application/javascript
module-type: treenode
Macro node, used as the base class for macros
\*/
(function(){
/*jshint node: true, browser: true */
/*global $tw: false */
"use strict";
var Node = require("./node.js").Node;
/*
Construct a renderer node representing a macro invocation
macroName: name of the macro
srcParams: a string or a hashmap of parameters (each can be a string, or a fn(tiddler,wiki) for evaluated parameters)
children: optional array of child nodes
store: reference to the WikiStore associated with this macro
dependencies: optional Dependencies object representing the dependencies of this macro
Note that the dependencies will be evaluated if not provided.
*/
var Macro = function(macroName,srcParams,content,wiki,dependencies) {
var MacroClass = wiki ? wiki.macros[macroName] : null; // Get the macro class
if(this instanceof Macro) {
// Save the details
this.macroName = macroName;
this.srcParams = srcParams || {};
this.content = content || [];
this.wiki = wiki;
this.dependencies = dependencies;
// Parse the macro parameters if required
if(typeof this.srcParams === "string") {
this.srcParams = this.parseMacroParamString(srcParams);
}
// Evaluate the dependencies if required
if(macroName && !this.dependencies) {
this.dependencies = this.evaluateDependencies();
}
// Get a reference to the static information about this macro
if(wiki && wiki.macros[macroName]) {
this.MacroClass = MacroClass;
this.info = MacroClass.prototype.info;
}
} else {
// If Macro() has been called without 'new' then instantiate the right macro class
if(!MacroClass) {
throw "Unknown macro '" + macroName + "'";
}
return new MacroClass(macroName,srcParams,content,wiki,dependencies);
}
};
Macro.prototype = new Node();
Macro.prototype.constructor = Macro;
/*
Evaluate the dependencies of this macro invocation. If the macro provides an `evaluateDependencies` method
then it is invoked to evaluate the dependencies. Otherwise it generates the dependencies based on the
macro parameters provided
*/
Macro.prototype.evaluateDependencies = function() {
// Figure out the dependencies from the metadata and parameters
var dependencies = new $tw.Dependencies();
if(this.info.dependentAll) {
dependencies.dependentAll = true;
}
if(this.info.dependentOnContextTiddler) {
dependencies.dependentOnContextTiddler = true;
}
for(var m in this.info.params) {
var paramInfo = this.info.params[m];
if(m in this.srcParams && paramInfo.type === "tiddler") {
if(typeof this.srcParams[m] === "function") {
dependencies.dependentAll = true;
} else {
dependencies.addDependency(this.srcParams[m],!paramInfo.skinny);
}
}
}
return dependencies;
};
Macro.prototype.parseMacroParamString = function(paramString) {
/*jslint evil: true */
var params = {},
args = new $tw.utils.ArgParser(paramString,{defaultName: "anon", cascadeDefaults: this.info.cascadeDefaults}),
self = this,
insertParam = function(name,arg) {
if(arg.evaluated) {
params[name] = eval("(function(tiddler,wiki) {return " + arg.string + ";})");
} else {
params[name] = arg.string;
}
};
for(var m in this.info.params) {
var param = this.info.params[m],
arg;
if("byPos" in param && args.byPos[param.byPos] && (args.byPos[param.byPos].n === "anon" || args.byPos[param.byPos].n === m)) {
arg = args.byPos[param.byPos].v;
insertParam(m,arg);
} else {
arg = args.getValueByName(m);
if(!arg && param.byName === "default") {
arg = args.getValueByName("anon");
}
if(arg) {
insertParam(m,arg);
}
}
}
return params;
};
Macro.prototype.hasParameter = function(name) {
return this.params.hasOwnProperty(name);
};
Macro.prototype.cloneContent = function() {
var contentClones = [];
for(var t=0; t<this.content.length; t++) {
contentClones.push(this.content[t].clone());
}
return contentClones;
};
Macro.prototype.clone = function() {
return new this.MacroClass(this.macroName,this.srcParams,this.cloneContent(),this.wiki,this.dependencies);
};
Macro.prototype.execute = function(parents,tiddlerTitle) {
parents = parents || [];
// Evaluate macro parameters to get their values
this.params = {};
var tiddler = this.wiki.getTiddler(tiddlerTitle);
if(!tiddler) {
tiddler = {title: tiddlerTitle};
}
for(var p in this.srcParams) {
if(typeof this.srcParams[p] === "function") {
this.params[p] = this.srcParams[p](tiddler.fields,this.wiki);
} else {
this.params[p] = this.srcParams[p];
}
}
// Save the context info for use when we're refreshing it
this.tiddlerTitle = tiddlerTitle;
this.parents = parents;
// Execute the macro to generate its children, and recursively execute them
this.children = this.executeMacro.call(this);
};
Macro.prototype.render = function(type) {
var output = [];
for(var t=0; t<this.children.length; t++) {
output.push(this.children[t].render(type));
}
return output.join("");
};
Macro.prototype.renderInDom = function(parentDomNode,insertBefore) {
// Create the wrapper node for the macro
var domNode = document.createElement(this.info.wrapperTag || "span");
this.domNode = domNode;
if(insertBefore) {
parentDomNode.insertBefore(domNode,insertBefore);
} else {
parentDomNode.appendChild(domNode);
}
// Add some debugging information to it
domNode.setAttribute("data-tw-macro",this.macroName);
// Ask the macro to add event handlers to the node
this.addEventHandlers();
// Render the content of the macro
for(var t=0; t<this.children.length; t++) {
this.children[t].renderInDom(domNode);
}
this.postRenderInDom();
};
Macro.prototype.addEventHandlers = function() {
if(this.info.events) {
for(var t=0; t<this.info.events.length; t++) {
// Register this macro node to handle the event via the handleEvent() method
this.domNode.addEventListener(this.info.events[t],this,false);
}
}
};
Macro.prototype.broadcastEvent = function(event) {
if(!this.handleEvent(event)) {
return false;
}
for(var t=0; t<this.children.length; t++) {
if(!this.children[t].broadcastEvent(event)) {
return false;
}
}
return true;
};
Macro.prototype.postRenderInDom = function() {
// Do nothing, individual macros can override
};
Macro.prototype.refresh = function(changes) {
var t,
self = this;
// Check if any of the dependencies of this macro node have changed
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
// Re-execute the macro if so
this.execute(this.parents,this.tiddlerTitle);
} else {
// Refresh any children
for(t=0; t<this.children.length; t++) {
this.children[t].refresh(changes);
}
}
};
Macro.prototype.refreshInDom = function(changes) {
var t,
self = this;
// Check if any of the dependencies of this macro node have changed
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
// Manually reexecute and rerender this macro
while(this.domNode.hasChildNodes()) {
this.domNode.removeChild(this.domNode.firstChild);
}
this.execute(this.parents,this.tiddlerTitle);
for(t=0; t<this.children.length; t++) {
this.children[t].renderInDom(this.domNode);
}
} else {
// Refresh any children
for(t=0; t<this.children.length; t++) {
this.children[t].refreshInDom(changes);
}
}
};
exports.Macro = Macro;
})();

View File

@@ -0,0 +1,80 @@
/*\
title: $:/core/modules/treenodes/node.js
type: application/javascript
module-type: treenode
Base class for all other tree nodes
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
The virtual base class for all types of renderer nodes. It keeps track of child nodes but otherwise contains no functionality
*/
var Node = function() {
if(this instanceof Node) {
} else {
return new Node();
}
};
/*
Makes a copy of a node, or returns the node itself if it doesn't need cloning because it doesn't have any state
*/
Node.prototype.clone = function() {
// By default we don't actually clone nodes, we just re-use them (we do clone macros and elements)
return this;
};
/*
Fire an event at a node which is also broadcast to any child nodes
*/
Node.prototype.broadcastEvent = function(event) {
return true; // true means that the event wasn't handled by this node
};
/*
Execute a node and derive its child nodes
parents: array of titles of each transcluded parent tiddler (used for detecting recursion)
tiddlerTitle: title of the context tiddler within which this node is being executed
*/
Node.prototype.execute = function(parents,tiddlerTitle) {
};
/*
Render a node and its children to a particular MIME type
type: Content type to which the node should be rendered
*/
Node.prototype.render = function(type) {
};
/*
Render a node and its children into the DOM
parentDomNode: node that should become the parent of the rendered node
insertBefore: optional node that the node should be inserted before
*/
Node.prototype.renderInDom = function(parentDomNode,insertBefore) {
};
/*
Re-execute a node if it is impacted by a set of tiddler changes
changes: hashmap of tiddler changes in the format used by $tw.Wiki
*/
Node.prototype.refresh = function(changes) {
};
/*
Re-execute a node if it is impacted by a set of tiddler changes, and update the associated DOM elements as necessary
changes: hashmap of tiddler changes in the format used by $tw.Wiki
*/
Node.prototype.refreshInDom = function(changes) {
};
exports.Node = Node;
})();

View File

@@ -0,0 +1,40 @@
/*\
title: $:/core/modules/treenodes/raw.js
type: application/javascript
module-type: treenode
Raw, unparsed HTML nodes
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Node = require("./node.js").Node;
var Raw = function(html) {
if(this instanceof Raw) {
this.html = html;
} else {
return new Raw(html);
}
};
Raw.prototype = new Node();
Raw.prototype.constructor = Raw;
Raw.prototype.render = function(type) {
return this.html;
};
Raw.prototype.renderInDom = function(domNode) {
this.domNode = document.createElement("div");
this.domNode.innerHTML = this.html;
domNode.appendChild(this.domNode);
};
exports.Raw = Raw;
})();

View File

@@ -0,0 +1,39 @@
/*\
title: $:/core/modules/treenodes/text.js
type: application/javascript
module-type: treenode
Text nodes
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Node = require("./node.js").Node;
var Text = function(text) {
if(this instanceof Text) {
this.text = text;
} else {
return new Text(text);
}
};
Text.prototype = new Node();
Text.prototype.constructor = Text;
Text.prototype.render = function(type) {
return type === "text/html" ? $tw.utils.htmlEncode(this.text) : this.text;
};
Text.prototype.renderInDom = function(domNode) {
this.domNode = document.createTextNode(this.text);
domNode.appendChild(this.domNode);
};
exports.Text = Text;
})();

View File

@@ -0,0 +1,117 @@
/*\
title: $:/core/modules/utils.argparser.js
type: application/javascript
module-type: utils
Parse a space-separated string of name:value parameters. Values can be quoted with single quotes, double quotes, double square brackets, or double curly braces.
The parameters are returned in a structure that can be referenced like this:
(return).byName["name"][0] - First occurance of parameter with a given name
(return).byPos[0].n - Name of parameter in first position
(return).byPos[0].v.string - Value of parameter in first position
(return).byPos[0].v.evaluated - True if the parameter is to be evaluated
Options and their defaults are:
defaultName: null,
defaultValue: null,
noNames: false,
cascadeDefaults: false,
allowEval: true
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var ArgParser = function(argString,options) {
options = options || {};
var defaultName = options.defaultName,
defaultValue = options.defaultValue;
var parseToken = function(match,p) {
var n;
if(match[p]) { // Double quoted
n = {string: match[p]};
} else if(match[p+1]) { // Single quoted
n = {string: match[p+1]};
} else if(match[p+2]) { // Double-square-bracket quoted
n = {string: match[p+2]};
} else if(match[p+3]) { // Double-brace quoted
n = {string: match[p+3], evaluated: true};
} else if(match[p+4]) { // Unquoted
n = {string: match[p+4]};
} else if(match[p+5]) { // empty quote
n = {string: ""};
}
return n;
};
this.byPos = [];
var dblQuote = "(?:\"((?:(?:\\\\\")|[^\"])+)\")",
sngQuote = "(?:'((?:(?:\\\\\')|[^'])+)')",
dblSquare = "(?:\\[\\[((?:\\s|\\S)*?)\\]\\])",
dblBrace = "(?:\\{\\{((?:\\s|\\S)*?)\\}\\})",
unQuoted = options.noNames ? "([^\"'\\s]\\S*)" : "([^\"':\\s][^\\s:]*)",
emptyQuote = "((?:\"\")|(?:''))",
skipSpace = "(?:\\s*)",
token = "(?:" + dblQuote + "|" + sngQuote + "|" + dblSquare + "|" + dblBrace + "|" + unQuoted + "|" + emptyQuote + ")",
re = options.noNames ? new RegExp(token,"mg") : new RegExp(skipSpace + token + skipSpace + "(?:(\\:)" + skipSpace + token + ")?","mg"),
match,n,v;
do {
match = re.exec(argString);
if(match) {
n = parseToken(match,1);
if(options.noNames) {
this.byPos.push({n:"", v:n});
} else {
v = parseToken(match,8);
if(v === undefined && defaultName) {
v = n;
n = defaultName;
} else if(v === undefined && defaultValue) {
v = defaultValue;
}
if(n.evaluated === true) {
n = "{{" + n.string + "}}";
} else if (typeof n === "object" && n.hasOwnProperty("string")) {
n = n.string;
}
this.byPos.push({n:n, v:v});
if(options.cascadeDefaults) {
defaultName = n;
defaultValue = v;
}
}
}
} while(match);
this.byName = {};
for(var t=0; t<this.byPos.length; t++) {
n = this.byPos[t].n;
v = this.byPos[t].v;
if(this.byName.hasOwnProperty(n))
this.byName[n].push(v);
else
this.byName[n] = [v];
}
};
// Retrieve the first occurance of a named parameter, or the default if missing
ArgParser.prototype.getValueByName = function(n) {
var v = this.byName[n];
return v && v.length > 0 ? v[0] : null;
};
// Retrieve all the string values as an array
ArgParser.prototype.getStringValues = function() {
var result = [];
for(var t=0; t<this.byPos.length; t++) {
result.push(this.byPos[t].v.string);
}
return result;
};
exports.ArgParser = ArgParser;
})();

309
core/modules/utils.js Normal file
View File

@@ -0,0 +1,309 @@
/*\
title: $:/core/modules/utils.js
type: application/javascript
module-type: utils
Various static utility functions.
This file is a bit of a dumping ground; the expectation is that most of these functions will be refactored.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.deepCopy = function(object) {
var result,t;
if($tw.utils.isArray(object)) {
// Copy arrays
result = object.slice(0);
} else if(typeof object === "object") {
result = {};
for(t in object) {
if(object[t] !== undefined) {
result[t] = $tw.utils.deepCopy(object[t]);
}
}
} else {
result = object;
}
return result;
};
exports.extendDeepCopy = function(object,extendedProperties) {
var result = $tw.utils.deepCopy(object),t;
for(t in extendedProperties) {
if(extendedProperties[t] !== undefined) {
result[t] = $tw.utils.deepCopy(extendedProperties[t]);
}
}
return result;
};
exports.formatDateString = function (date,template) {
var t = template.replace(/0hh12/g,$tw.utils.pad($tw.utils.getHours12(date)));
t = t.replace(/hh12/g,$tw.utils.getHours12(date));
t = t.replace(/0hh/g,$tw.utils.pad(date.getHours()));
t = t.replace(/hh/g,date.getHours());
t = t.replace(/mmm/g,$tw.config.dateFormats.shortMonths[date.getMonth()]);
t = t.replace(/0mm/g,$tw.utils.pad(date.getMinutes()));
t = t.replace(/mm/g,date.getMinutes());
t = t.replace(/0ss/g,$tw.utils.pad(date.getSeconds()));
t = t.replace(/ss/g,date.getSeconds());
t = t.replace(/[ap]m/g,$tw.utils.getAmPm(date).toLowerCase());
t = t.replace(/[AP]M/g,$tw.utils.getAmPm(date).toUpperCase());
t = t.replace(/wYYYY/g,$tw.utils.getYearForWeekNo(date));
t = t.replace(/wYY/g,$tw.utils.pad($tw.utils.getYearForWeekNo(date)-2000));
t = t.replace(/YYYY/g,date.getFullYear());
t = t.replace(/YY/g,$tw.utils.pad(date.getFullYear()-2000));
t = t.replace(/MMM/g,$tw.config.dateFormats.months[date.getMonth()]);
t = t.replace(/0MM/g,$tw.utils.pad(date.getMonth()+1));
t = t.replace(/MM/g,date.getMonth()+1);
t = t.replace(/0WW/g,$tw.utils.pad($tw.utils.getWeek(date)));
t = t.replace(/WW/g,$tw.utils.getWeek(date));
t = t.replace(/DDD/g,$tw.config.dateFormats.days[date.getDay()]);
t = t.replace(/ddd/g,$tw.config.dateFormats.shortDays[date.getDay()]);
t = t.replace(/0DD/g,$tw.utils.pad(date.getDate()));
t = t.replace(/DDth/g,date.getDate()+$tw.utils.getDaySuffix(date));
t = t.replace(/DD/g,date.getDate());
var tz = date.getTimezoneOffset();
var atz = Math.abs(tz);
t = t.replace(/TZD/g,(tz < 0 ? '+' : '-') + $tw.utils.pad(Math.floor(atz / 60)) + ':' + $tw.utils.pad(atz % 60));
t = t.replace(/\\/g,"");
return t;
};
exports.getAmPm = function(date) {
return date.getHours() >= 12 ? $tw.config.dateFormats.pm : $tw.config.dateFormats.am;
};
exports.getDaySuffix = function(date) {
return $tw.config.dateFormats.daySuffixes[date.getDate()-1];
};
exports.getWeek = function(date) {
var dt = new Date(date.getTime());
var d = dt.getDay();
if(d === 0) d=7;// JavaScript Sun=0, ISO Sun=7
dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week to calculate weekNo
var n = Math.floor((dt.getTime()-new Date(dt.getFullYear(),0,1)+3600000)/86400000);
return Math.floor(n/7)+1;
};
exports.getYearForWeekNo = function(date) {
var dt = new Date(date.getTime());
var d = dt.getDay();
if(d === 0) d=7;// JavaScript Sun=0, ISO Sun=7
dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week
return dt.getFullYear();
};
exports.getHours12 = function(date) {
var h = date.getHours();
return h > 12 ? h-12 : ( h > 0 ? h : 12 );
};
// Convert & to "&amp;", < to "&lt;", > to "&gt;" and " to "&quot;"
exports.htmlEncode = function(s)
{
if(s) {
return s.toString().replace(/&/mg,"&amp;").replace(/</mg,"&lt;").replace(/>/mg,"&gt;").replace(/\"/mg,"&quot;");
} else {
return "";
}
};
// Converts all HTML entities to their character equivalents
exports.entityDecode = function(s) {
var e = s.substr(1,s.length-2); // Strip the & and the ;
if(e.charAt(0) === "#") {
if(e.charAt(1) === "x" || e.charAt(1) === "X") {
return String.fromCharCode(parseInt(e.substr(2),16));
} else {
return String.fromCharCode(parseInt(e.substr(1),10));
}
} else {
var c = $tw.config.htmlEntities[e];
if(c) {
return String.fromCharCode(c);
} else {
return s; // Couldn't convert it as an entity, just return it raw
}
}
};
exports.unescapeLineBreaks = function(s) {
return s.replace(/\\n/mg,"\n").replace(/\\b/mg," ").replace(/\\s/mg,"\\").replace(/\r/mg,"");
};
/*
* Returns an escape sequence for given character. Uses \x for characters <=
* 0xFF to save space, \u for the rest.
*
* The code needs to be in sync with th code template in the compilation
* function for "action" nodes.
*/
// Copied from peg.js, thanks to David Majda
exports.escape = function(ch) {
var charCode = ch.charCodeAt(0);
if (charCode <= 0xFF) {
return '\\x' + $tw.utils.pad(charCode.toString(16).toUpperCase());
} else {
return '\\u' + $tw.utils.pad(charCode.toString(16).toUpperCase(),4);
}
};
// Turns a string into a legal JavaScript string
// Copied from peg.js, thanks to David Majda
exports.stringify = function(s) {
/*
* ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a string
* literal except for the closing quote character, backslash, carriage return,
* line separator, paragraph separator, and line feed. Any character may
* appear in the form of an escape sequence.
*
* For portability, we also escape escape all non-ASCII characters.
*/
return s
.replace(/\\/g, '\\\\') // backslash
.replace(/"/g, '\\"') // double quote character
.replace(/'/g, "\\'") // single quote character
.replace(/\r/g, '\\r') // carriage return
.replace(/\n/g, '\\n') // line feed
.replace(/[\x80-\uFFFF]/g, exports.escape); // non-ASCII characters
};
exports.nextTick = function(fn) {
/*global window: false */
if(typeof window !== "undefined") {
// Apparently it would be faster to use postMessage - http://dbaron.org/log/20100309-faster-timeouts
window.setTimeout(fn,4);
} else {
process.nextTick(fn);
}
};
/*
Determines whether element 'a' contains element 'b'
Code thanks to John Resig, http://ejohn.org/blog/comparing-document-position/
*/
exports.domContains = function(a,b) {
return a.contains ?
a != b && a.contains(b) :
!!(a.compareDocumentPosition(b) & 16);
};
exports.hasClass = function(el,className) {
return el.className.split(" ").indexOf(className) !== -1;
};
exports.addClass = function(el,className) {
var c = el.className.split(" ");
if(c.indexOf(className) === -1) {
c.push(className);
}
el.className = c.join(" ");
};
exports.removeClass = function(el,className) {
var c = el.className.split(" "),
p = c.indexOf(className);
if(p !== -1) {
c.splice(p,1);
el.className = c.join(" ");
}
};
exports.toggleClass = function(el,className,status) {
if(status === undefined) {
status = !exports.hasClass(el,className);
}
if(status) {
exports.addClass(el,className);
} else {
exports.removeClass(el,className);
}
};
exports.applyStyleSheet = function(id,css) {
var el = document.getElementById(id);
if(document.createStyleSheet) { // Older versions of IE
if(el) {
el.parentNode.removeChild(el);
}
document.getElementsByTagName("head")[0].insertAdjacentHTML("beforeEnd",
'&nbsp;<style id="' + id + '" type="text/css">' + css + '</style>'); // fails without &nbsp;
} else { // Modern browsers
if(el) {
el.replaceChild(document.createTextNode(css), el.firstChild);
} else {
el = document.createElement("style");
el.type = "text/css";
el.id = id;
el.appendChild(document.createTextNode(css));
document.getElementsByTagName("head")[0].appendChild(el);
}
}
};
/*
Parse a version number string of the form "1.2.3", "1.2.3.a4", or "1.2.3.b4" into:
{major: <number>, minor: <number>, revision: <number>, alpha: <number>, beta: <number>}
*/
exports.parseVersion = function(versionString) {
versionString = versionString || "0.0.0";
var components = versionString.split("."),
version = {
major: components[0] ? parseInt(components[0],10) : null,
minor: components[1] ? parseInt(components[1],10) : null,
revision: components[2] ? parseInt(components[2],10) : null
};
if(components[3]) {
if(components[3].charAt(0) === "a") {
version.alpha = parseInt(components[3].substr(1),10);
} else if(components[3].charAt(0) === "b") {
version.beta = parseInt(components[3].substr(1),10);
}
}
return version;
};
exports.getVersionString = function() {
return ($tw.version.major || "0") + "." +
($tw.version.minor || "0") + "." +
($tw.version.revision || "0") +
($tw.version.alpha ? ".a" + $tw.version.alpha : "") +
($tw.version.beta ? ".b" + $tw.version.beta : "");
};
/*
Extract the version number from the meta tag or from the boot file
*/
if($tw.isBrowser) {
// Browser version
exports.extractVersionInfo = function() {
var metatags = document.getElementsByTagName("meta");
for(var t=0; t<metatags.length; t++) {
var m = metatags[t];
if(m.name === "tiddlywiki-version") {
return $tw.utils.parseVersion(m.content);
}
}
return $tw.utils.parseVersion();
};
} else {
// Server version
exports.extractVersionInfo = function() {
var fs = require("fs");
return $tw.utils.parseVersion(fs.readFileSync($tw.boot.bootPath + "/version.txt").toString("utf8"));
};
}
})();

View File

@@ -0,0 +1,91 @@
/*\
title: $:/core/modules/wiki.filters.js
type: application/javascript
module-type: wikimethod
Filter method for the $tw.Wiki object
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
//# Extensible filter functions
exports.filters = {
tiddler: function(results,match) {
var title = match[1] || match[4];
if(results.indexOf(title) === -1) {
results.push(title);
}
},
tag: function(results,match) {
},
sort: function(results,match) {
},
limit: function(results,match) {
return results.slice(0,parseInt(match[3],10));
},
field: function(results,match) {
},
is: function(results,match) {
switch(match[3]) {
case "shadowStyle":
this.shadows.forEachTiddler(function(title,tiddler) {
if(tiddler.fields.type === "text/css") {
if(results.indexOf(title) === -1) {
results.push(title);
}
}
});
break;
case "shadowModule":
this.shadows.forEachTiddler(function(title,tiddler) {
if(tiddler.fields.type === "application/javascript" && tiddler.fields["module-type"]) {
if(results.indexOf(title) === -1) {
results.push(title);
}
}
});
break;
case "shadowPlain":
this.shadows.forEachTiddler(function(title,tiddler) {
if((tiddler.fields.type !== "application/javascript" || !tiddler.fields["module-type"]) &&
tiddler.fields.type !== "text/css") {
if(results.indexOf(title) === -1) {
results.push(title);
}
}
});
break;
case "tiddler":
this.forEachTiddler(function(title,tiddler) {
if(results.indexOf(title) === -1) {
results.push(title);
}
});
break;
}
}
};
// Return the tiddler titles from the store that match a filter expression
// filter - filter expression (eg "tiddlertitle [[multi word tiddler title]] [tag[systemConfig]]")
// Returns an array of tiddler titles that match the filter expression
exports.filterTiddlers = function(filter) {
// text or [foo[bar]] or [[tiddler title]]
var re = /([^\s\[\]]+)|(?:\[([ \w\.\-]+)\[([^\]]+)\]\])|(?:\[\[([^\]]+)\]\])/mg;
var results = [];
if(filter) {
var match = re.exec(filter);
while(match) {
var handler = (match[1]||match[4]) ? 'tiddler' : (this.filters[match[2]] ? match[2] : 'field');
this.filters[handler].call(this,results,match);
match = re.exec(filter);
}
}
return results;
};
})();

339
core/modules/wiki.js Normal file
View File

@@ -0,0 +1,339 @@
/*\
title: $:/core/modules/wiki.js
type: application/javascript
module-type: wikimethod
Extension methods for the $tw.Wiki object
Adds the following properties to the wiki object:
* `eventListeners` is an array of {filter: <string>, listener: fn}
* `changedTiddlers` is a hashmap describing changes to named tiddlers since wiki change events were
last dispatched. Each entry is a hashmap containing two fields:
modified: true/false
deleted: true/false
* `changeCount` is a hashmap by tiddler title containing a numerical index that starts at zero and is
incremented each time a tiddler is created changed or deleted
* `caches` is a hashmap by tiddler title containing a further hashmap of named cache objects. Caches
are automatically cleared when a tiddler is modified or deleted
* `macros` is a hashmap by macro name containing an object class inheriting from the Macro tree node
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.getTiddlerText = function(title,defaultText) {
defaultText = typeof defaultText === "string" ? defaultText : null;
var t = this.getTiddler(title);
return t ? t.fields.text : defaultText;
};
exports.addEventListener = function(filter,listener) {
this.eventListeners = this.eventListeners || [];
this.eventListeners.push({
filter: filter,
listener: listener
});
};
exports.removeEventListener = function(filter,listener) {
for(var c=this.eventListeners.length-1; c>=0; c--) {
var l = this.eventListeners[c];
if(l.filter === filter && l.listener === listener) {
this.eventListeners.splice(c,1);
}
}
};
/*
Causes a tiddler to be marked as changed, incrementing the change count, and triggers event handlers.
This method should be called after the changes it describes have been made to the wiki.tiddlers[] array.
title: Title of tiddler
isDeleted: defaults to false (meaning the tiddler has been created or modified),
true if the tiddler has been created
*/
exports.touchTiddler = function(title,isDeleted) {
// Record the touch in the list of changed tiddlers
this.changedTiddlers = this.changedTiddlers || {};
this.changedTiddlers[title] = this.changedTiddlers[title] || [];
this.changedTiddlers[title][isDeleted ? "deleted" : "modified"] = true;
// Increment the change count
this.changeCount = this.changeCount || {};
if(this.changeCount.hasOwnProperty(title)) {
this.changeCount[title]++;
} else {
this.changeCount[title] = 1;
}
// Trigger events
this.eventListeners = this.eventListeners || [];
if(!this.eventsTriggered) {
var me = this;
$tw.utils.nextTick(function() {
var changes = me.changedTiddlers;
me.changedTiddlers = {};
me.eventsTriggered = false;
for(var e=0; e<me.eventListeners.length; e++) {
var listener = me.eventListeners[e];
listener.listener(changes);
}
});
this.eventsTriggered = true;
}
};
exports.getChangeCount = function(title) {
this.changeCount = this.changeCount || {};
if(this.changeCount.hasOwnProperty(title)) {
return this.changeCount[title];
} else {
return 0;
}
};
exports.deleteTiddler = function(title) {
delete this.tiddlers[title];
this.clearCache(title);
this.touchTiddler(title,true);
};
exports.tiddlerExists = function(title) {
if(this.tiddlers[title]) {
return true;
} else if (this.shadows) {
return this.shadows.tiddlerExists(title);
}
};
exports.addTiddler = function(tiddler) {
// Check if we're passed a fields hashmap instead of a tiddler
if(!(tiddler instanceof $tw.Tiddler)) {
tiddler = new $tw.Tiddler(tiddler);
}
var title = tiddler.fields.title;
this.tiddlers[title] = tiddler;
this.clearCache(title);
this.touchTiddler(title);
};
exports.serializeTiddler = function(title,type) {
var serializer = $tw.Wiki.tiddlerSerializerPlugins[type],
tiddler = this.getTiddler(title);
if(serializer && tiddler) {
return serializer.call(this,tiddler);
} else {
return null;
}
};
/*
Return a sorted array of tiddler titles, optionally filtered by a tag
*/
exports.sortTiddlers = function(sortField,excludeTag) {
sortField = sortField || "title";
var tiddlers = [], t, titles = [];
for(t in this.tiddlers) {
tiddlers.push(this.tiddlers[t]);
}
tiddlers.sort(function(a,b) {
var aa = a.fields[sortField] || 0,
bb = b.fields[sortField] || 0;
if(aa < bb) {
return -1;
} else {
if(aa > bb) {
return 1;
} else {
return 0;
}
}
});
for(t=0; t<tiddlers.length; t++) {
if(!excludeTag || !tiddlers[t].hasTag(excludeTag)) {
titles.push(tiddlers[t].fields.title);
}
}
return titles;
};
exports.forEachTiddler = function(/* [sortField,[excludeTag,]]callback */) {
var arg = 0,
sortField = arguments.length > 1 ? arguments[arg++] : null,
excludeTag = arguments.length > 2 ? arguments[arg++] : null,
callback = arguments[arg++],
titles = this.sortTiddlers(sortField,excludeTag),
t, tiddler;
for(t=0; t<titles.length; t++) {
tiddler = this.tiddlers[titles[t]];
if(tiddler) {
callback.call(this,tiddler.fields.title,tiddler);
}
}
};
exports.getMissingTitles = function() {
return []; // Todo
};
exports.getOrphanTitles = function() {
return []; // Todo
};
exports.getShadowTitles = function() {
return this.shadows ? this.shadows.sortTiddlers() : [];
};
// Return the named cache object for a tiddler. If the cache doesn't exist then the initializer function is invoked to create it
exports.getCacheForTiddler = function(title,cacheName,initializer) {
this.caches = this.caches || {};
var caches = this.caches[title];
if(caches && caches[cacheName]) {
return caches[cacheName];
} else {
if(!caches) {
caches = {};
this.caches[title] = caches;
}
caches[cacheName] = initializer();
return caches[cacheName];
}
};
// Clear all caches associated with a particular tiddler
exports.clearCache = function(title) {
this.caches = this.caches || {};
if(this.caches.hasOwnProperty(title)) {
delete this.caches[title];
}
};
exports.initParsers = function(moduleType) {
// Install the parser modules
moduleType = moduleType || "parser";
$tw.wiki.parsers = {};
var modules = $tw.plugins.moduleTypes[moduleType],
n,m,f;
if(modules) {
for(n=0; n<modules.length; n++) {
m = modules[n];
// Add the parsers defined by the module
for(f in m) {
$tw.wiki.parsers[f] = new m[f]({wiki: this}); // Store an instance of the parser
}
}
}
// Install the wikitext rules
modules = $tw.plugins.moduleTypes["wikitextrule"];
var wikitextparser = this.parsers["text/x-tiddlywiki"];
if(modules && wikitextparser) {
for(n=0; n<modules.length; n++) {
m = modules[n];
// Add the rules defined by the module - currently a hack as we overwrite them
wikitextparser.installRules(m.rules);
}
}
};
/*
Parse a block of text of a specified MIME type
Options are:
defaultType: Default MIME type to use if the specified one is unknown
*/
exports.parseText = function(type,text,options) {
options = options || {};
var parser = this.parsers[type];
if(!parser) {
parser = this.parsers[options.defaultType || "text/x-tiddlywiki"];
}
if(parser) {
return parser.parse(type,text);
} else {
return null;
}
};
exports.parseTiddler = function(title) {
var me = this,
tiddler = this.getTiddler(title);
return tiddler ? this.getCacheForTiddler(title,"parseTree",function() {
return me.parseText(tiddler.fields.type,tiddler.fields.text);
}) : null;
};
/*
Parse text in a specified format and render it into another format
outputType: content type for the output
textType: content type of the input text
text: input text
options: see below
Options are:
defaultType: Default MIME type to use if the specified one is unknown
*/
exports.renderText = function(outputType,textType,text,options) {
var renderer = this.parseText(textType,text,options);
renderer.execute([]);
return renderer.render(outputType);
};
exports.renderTiddler = function(outputType,title) {
var renderer = this.parseTiddler(title);
renderer.execute([],title);
return renderer.render(outputType);
};
/*
Install macro plugins into this wiki
moduleType: Plugin type to install (defaults to "macro")
It's useful to remember what the `new` keyword does. It:
# Creates a new object. It's type is a plain `object`
# Sets the new objects internal, inaccessible, `[[prototype]]` property to the
constructor function's external, accessible, `prototype` object
# Executes the constructor function, passing the new object as `this`
*/
exports.initMacros = function(moduleType) {
moduleType = moduleType || "macro";
$tw.wiki.macros = {};
var MacroClass = require("./treenodes/macro.js").Macro,
modules = $tw.plugins.moduleTypes[moduleType],
n,m,f,
subclassMacro = function(module) {
// Make a copy of the Macro() constructor function
var MacroMaker = function Macro() {
MacroClass.apply(this,arguments);
};
// Set the prototype to a new instance of the prototype of the Macro class
MacroMaker.prototype = new MacroClass();
// Add the prototype methods for this instance of the macro
for(var f in module) {
MacroMaker.prototype[f] = module[f];
}
// Make a more convenient reference to the macro info
return MacroMaker;
};
if(modules) {
for(n=0; n<modules.length; n++) {
m = modules[n];
$tw.wiki.macros[m.info.name] = subclassMacro(m);
}
}
};
/*
Install editor plugins for the edit macro
*/
exports.initEditors = function(moduleType) {
moduleType = moduleType || "editor";
var editMacro = this.macros.edit;
if(editMacro) {
editMacro.editors = {};
$tw.plugins.applyMethods(moduleType,editMacro.editors);
}
};
})();

View File

@@ -0,0 +1,26 @@
title: $:/core/static.template.html
type: text/x-tiddlywiki-html
<!doctype html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="application-name" content="TiddlyWiki" />
<meta name="generator" content="TiddlyWiki" />
<meta name="tiddlywiki-version" content="<<^"$:/core/version.txt" text/plain>>" />
<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="
<<^"$:/core/copyright.txt" text/plain>>
" />
<title><<tiddler target:$:/shadows/title>></title>
<!----------- This is a Tiddlywiki file. The points of interest in the file are marked with this pattern ----------->
<style id="styleArea" type="text/css">
<<^"[is[shadowStyle]]" text/plain>>
</style>
</head>
<body>
<<^"PageTemplate" text/html>>
</body>
</html>

View File

@@ -0,0 +1,47 @@
title: $:/core/tiddlywiki5.template.html
type: text/x-tiddlywiki-html
<!doctype html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="application-name" content="TiddlyWiki" />
<meta name="generator" content="TiddlyWiki" />
<meta name="tiddlywiki-version" content="<<^"$:/core/version.txt" text/plain>>" />
<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="
<<^"$:/core/copyright.txt" text/plain>>
" />
<title><<tiddler target:$:/shadows/title>></title>
<!----------- This is a Tiddlywiki file. The points of interest in the file are marked with this pattern ----------->
<style id="styleArea" type="text/css">
<<^"[is[shadowStyle]]" text/plain>>
</style>
</head>
<body>
<!----------- Static content for Google and browsers without JavaScript ----------->
<noscript>
<div id="splashArea" style="display:none;">
<<^"$:/wiki/splash" text/html>>
</div>
</noscript>
<!----------- Shadow tiddlers ----------->
<div id="shadowArea" style="display:none;">
<<^"[is[shadowPlain]]" application/x-tiddler-html-div>>
</div>
<!----------- Ordinary tiddlers ----------->
<div id="storeArea" style="display:none;">
<<^"[is[tiddler]]" application/x-tiddler-html-div>>
</div>
<!----------- Boot kernel prologue ----------->
<<^"$:/core/bootprefix.js" application/javascript>>
<!----------- Plugin modules ----------->
<div id="pluginModules" style="display:none;">
<<^"[is[shadowModule]]" application/x-tiddler-module>>
</div>
<!----------- Boot kernel ----------->
<<^"$:/core/boot.js" application/javascript>>
</body>
</html>

1
core/version.txt Normal file
View File

@@ -0,0 +1 @@
5.0.0.a2