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:
646
core/boot.js
Normal file
646
core/boot.js
Normal 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 "&" to &, "<" to <, ">" to > and """ to "
|
||||
$tw.utils.htmlDecode = function(s) {
|
||||
return s.toString().replace(/</mg,"<").replace(/>/mg,">").replace(/"/mg,"\"").replace(/&/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
33
core/bootprefix.js
Normal 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
29
core/copyright.txt
Normal 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
117
core/modules/commander.js
Normal file
@@ -0,0 +1,117 @@
|
||||
/*\
|
||||
title: $:/core/modules/commander.js
|
||||
type: application/javascript
|
||||
module-type: global
|
||||
|
||||
The $tw.Commander class is a command interpreter
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
Parse a sequence of commands
|
||||
commandTokens: an array of command string tokens
|
||||
wiki: reference to the wiki store object
|
||||
streams: {output:, error:}, each of which has a write(string) method
|
||||
callback: a callback invoked as callback(err) where err is null if there was no error
|
||||
*/
|
||||
var Commander = function(commandTokens,callback,wiki,streams) {
|
||||
this.commandTokens = commandTokens;
|
||||
this.nextToken = 0;
|
||||
this.callback = callback;
|
||||
this.wiki = wiki;
|
||||
this.streams = streams;
|
||||
};
|
||||
|
||||
/*
|
||||
Execute the sequence of commands and invoke a callback on completion
|
||||
*/
|
||||
Commander.prototype.execute = function() {
|
||||
this.executeNextCommand();
|
||||
};
|
||||
|
||||
/*
|
||||
Execute the next command in the sequence
|
||||
*/
|
||||
Commander.prototype.executeNextCommand = function() {
|
||||
var self = this;
|
||||
// Invoke the callback if there are no more commands
|
||||
if(this.nextToken >= this.commandTokens.length) {
|
||||
this.callback(null);
|
||||
} else {
|
||||
// Get and check the command token
|
||||
var commandName = this.commandTokens[this.nextToken++];
|
||||
if(commandName.substr(0,2) !== "--") {
|
||||
this.callback("Missing command");
|
||||
} else {
|
||||
commandName = commandName.substr(2); // Trim off the --
|
||||
// Accumulate the parameters to the command
|
||||
var params = [];
|
||||
while(this.nextToken < this.commandTokens.length &&
|
||||
this.commandTokens[this.nextToken].substr(0,2) !== "--") {
|
||||
params.push(this.commandTokens[this.nextToken++]);
|
||||
}
|
||||
// Get the command info
|
||||
var command = $tw.commands[commandName],
|
||||
c,err;
|
||||
if(!command) {
|
||||
this.callback("Unknown command: " + commandName);
|
||||
} else {
|
||||
if(this.verbose) {
|
||||
this.streams.output.write("Executing command: " + commandName + " " + params.join(" ") + "\n");
|
||||
}
|
||||
if(command.info.synchronous) {
|
||||
// Synchronous command
|
||||
c = new command.Command(params,this);
|
||||
err = c.execute();
|
||||
if(err) {
|
||||
this.callback(err);
|
||||
} else {
|
||||
this.executeNextCommand();
|
||||
}
|
||||
} else {
|
||||
// Asynchronous command
|
||||
c = new command.Command(params,this,function(err) {
|
||||
if(err) {
|
||||
self.callback(err);
|
||||
} else {
|
||||
self.executeNextCommand();
|
||||
}
|
||||
});
|
||||
err = c.execute();
|
||||
if(err) {
|
||||
this.callback(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Commander.initCommands = function(moduleType) {
|
||||
// Install the command modules
|
||||
moduleType = moduleType || "command";
|
||||
$tw.commands = {};
|
||||
var modules = $tw.plugins.moduleTypes[moduleType],
|
||||
n,m,f,c;
|
||||
if(modules) {
|
||||
for(n=0; n<modules.length; n++) {
|
||||
m = modules[n];
|
||||
$tw.commands[m.info.name] = {};
|
||||
c = $tw.commands[m.info.name];
|
||||
// Add the methods defined by the module
|
||||
for(f in m) {
|
||||
c[f] = m[f];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
exports.Commander = Commander;
|
||||
|
||||
})();
|
||||
112
core/modules/commands/dump.js
Normal file
112
core/modules/commands/dump.js
Normal file
@@ -0,0 +1,112 @@
|
||||
/*\
|
||||
title: $:/core/modules/commands/dump.js
|
||||
type: application/javascript
|
||||
module-type: command
|
||||
|
||||
Dump command
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jshint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "dump",
|
||||
synchronous: true
|
||||
};
|
||||
|
||||
var Command = function(params,commander) {
|
||||
this.params = params;
|
||||
this.commander = commander;
|
||||
this.output = commander.streams.output;
|
||||
};
|
||||
|
||||
Command.prototype.execute = function() {
|
||||
if(this.params.length < 1) {
|
||||
return "Too few parameters for dump command";
|
||||
}
|
||||
var subcommand = this.subcommands[this.params[0]];
|
||||
if(subcommand) {
|
||||
return subcommand.call(this);
|
||||
} else {
|
||||
return "Unknown subcommand (" + this.params[0] + ") for dump command";
|
||||
}
|
||||
};
|
||||
|
||||
Command.prototype.subcommands = {};
|
||||
|
||||
Command.prototype.subcommands.tiddler = function() {
|
||||
if(this.params.length < 2) {
|
||||
return "Too few parameters for dump tiddler command";
|
||||
}
|
||||
var tiddler = this.commander.wiki.getTiddler(this.params[1]);
|
||||
this.output.write("Tiddler '" + this.params[1] + "' contains these fields:\n");
|
||||
for(var t in tiddler.fields) {
|
||||
this.output.write(" " + t + ": " + tiddler.getFieldString(t) + "\n");
|
||||
}
|
||||
return null; // No error
|
||||
};
|
||||
|
||||
Command.prototype.subcommands.tiddlers = function() {
|
||||
var tiddlers = this.commander.wiki.sortTiddlers();
|
||||
this.output.write("Wiki contains these tiddlers:\n");
|
||||
for(var t=0; t<tiddlers.length; t++) {
|
||||
this.output.write(tiddlers[t] + "\n");
|
||||
}
|
||||
return null; // No error
|
||||
};
|
||||
|
||||
Command.prototype.subcommands.shadows = function() {
|
||||
var tiddlers = this.commander.wiki.shadows.sortTiddlers();
|
||||
this.output.write("Wiki contains these shadow tiddlers:\n");
|
||||
for(var t=0; t<tiddlers.length; t++) {
|
||||
this.output.write(tiddlers[t] + "\n");
|
||||
}
|
||||
return null; // No error
|
||||
};
|
||||
|
||||
Command.prototype.subcommands.config = function() {
|
||||
var self = this;
|
||||
var quotePropertyName = function(p) {
|
||||
var unquotedPattern = /^[A-Za-z0-9_]*$/mg;
|
||||
if(unquotedPattern.test(p)) {
|
||||
return p;
|
||||
} else {
|
||||
return "[\"" + $tw.utils.stringify(p) + "\"]";
|
||||
}
|
||||
},
|
||||
dumpConfig = function(object,prefix) {
|
||||
for(var n in object) {
|
||||
var v = object[n];
|
||||
if(typeof v === "object") {
|
||||
dumpConfig(v,prefix + "." + quotePropertyName(n));
|
||||
} else if(typeof v === "string") {
|
||||
self.output.write(prefix + "." + quotePropertyName(n) + ": \"" + $tw.utils.stringify(v) + "\"\n");
|
||||
} else {
|
||||
self.output.write(prefix + "." + quotePropertyName(n) + ": " + v.toString() + "\n");
|
||||
}
|
||||
}
|
||||
},
|
||||
dumpObject = function(heading,object) {
|
||||
self.output.write(heading +"\n");
|
||||
for(var n in object) {
|
||||
self.output.write(" " + n + "\n");
|
||||
}
|
||||
};
|
||||
this.output.write("Configuration:\n");
|
||||
dumpConfig($tw.config," $tw.config");
|
||||
dumpObject("Tiddler field plugins:",$tw.Tiddler.fieldPlugins);
|
||||
dumpObject("Loaded modules:",$tw.modules.titles);
|
||||
dumpObject("Loaded plugins:",$tw.plugins.moduleTypes);
|
||||
dumpObject("Command plugins:",$tw.commands);
|
||||
dumpObject("Parser plugins:",$tw.wiki.parsers);
|
||||
dumpObject("Macro plugins:",$tw.wiki.macros);
|
||||
dumpObject("Deserializer plugins:",$tw.Wiki.tiddlerDeserializerPlugins);
|
||||
return null; // No error
|
||||
};
|
||||
|
||||
exports.Command = Command;
|
||||
|
||||
})();
|
||||
56
core/modules/commands/load.js
Normal file
56
core/modules/commands/load.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/*\
|
||||
title: $:/core/modules/commands/load.js
|
||||
type: application/javascript
|
||||
module-type: command
|
||||
|
||||
Load tiddlers command
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "load",
|
||||
synchronous: false
|
||||
};
|
||||
|
||||
var Command = function(params,commander,callback) {
|
||||
this.params = params;
|
||||
this.commander = commander;
|
||||
this.callback = callback;
|
||||
};
|
||||
|
||||
Command.prototype.execute = function() {
|
||||
var self = this,
|
||||
fs = require("fs"),
|
||||
path = require("path");
|
||||
if(this.params.length < 1) {
|
||||
return "Missing filename";
|
||||
}
|
||||
fs.readFile(this.params[0],"utf8",function(err,data) {
|
||||
if(err) {
|
||||
self.callback(err);
|
||||
} else {
|
||||
var fields = {title: self.params[0]},
|
||||
extname = path.extname(self.params[0]),
|
||||
type = extname === ".html" ? "application/x-tiddlywiki" : extname;
|
||||
var tiddlers = self.commander.wiki.deserializeTiddlers(type,data,fields);
|
||||
if(!tiddlers) {
|
||||
self.callback("No tiddlers found in file \"" + self.params[0] + "\"");
|
||||
} else {
|
||||
for(var t=0; t<tiddlers.length; t++) {
|
||||
self.commander.wiki.addTiddler(new $tw.Tiddler(tiddlers[t]));
|
||||
}
|
||||
self.callback(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
return null;
|
||||
};
|
||||
|
||||
exports.Command = Command;
|
||||
|
||||
})();
|
||||
44
core/modules/commands/savetiddler.js
Normal file
44
core/modules/commands/savetiddler.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/*\
|
||||
title: $:/core/modules/commands/savetiddler.js
|
||||
type: application/javascript
|
||||
module-type: command
|
||||
|
||||
Save tiddlers command
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "savetiddler",
|
||||
synchronous: false
|
||||
};
|
||||
|
||||
var Command = function(params,commander,callback) {
|
||||
this.params = params;
|
||||
this.commander = commander;
|
||||
this.callback = callback;
|
||||
};
|
||||
|
||||
Command.prototype.execute = function() {
|
||||
if(this.params.length < 2) {
|
||||
return "Missing filename";
|
||||
}
|
||||
var self = this,
|
||||
fs = require("fs"),
|
||||
path = require("path"),
|
||||
title = this.params[0],
|
||||
filename = this.params[1],
|
||||
type = this.params[2] || "text/html";
|
||||
fs.writeFile(filename,this.commander.wiki.renderTiddler(type,title),"utf8",function(err) {
|
||||
self.callback(err);
|
||||
});
|
||||
return null;
|
||||
};
|
||||
|
||||
exports.Command = Command;
|
||||
|
||||
})();
|
||||
32
core/modules/commands/verbose.js
Normal file
32
core/modules/commands/verbose.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/*\
|
||||
title: $:/core/modules/commands/verbose.js
|
||||
type: application/javascript
|
||||
module-type: command
|
||||
|
||||
Verbose command
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "verbose",
|
||||
synchronous: true
|
||||
};
|
||||
|
||||
var Command = function(params,commander) {
|
||||
this.params = params;
|
||||
this.commander = commander;
|
||||
};
|
||||
|
||||
Command.prototype.execute = function() {
|
||||
this.commander.verbose = true;
|
||||
return null; // No error
|
||||
};
|
||||
|
||||
exports.Command = Command;
|
||||
|
||||
})();
|
||||
32
core/modules/commands/version.js
Normal file
32
core/modules/commands/version.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/*\
|
||||
title: $:/core/modules/commands/version.js
|
||||
type: application/javascript
|
||||
module-type: command
|
||||
|
||||
Version command
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "version",
|
||||
synchronous: true
|
||||
};
|
||||
|
||||
var Command = function(params,commander) {
|
||||
this.params = params;
|
||||
this.commander = commander;
|
||||
};
|
||||
|
||||
Command.prototype.execute = function() {
|
||||
this.commander.streams.output.write($tw.utils.getVersionString() + "\n");
|
||||
return null; // No error
|
||||
};
|
||||
|
||||
exports.Command = Command;
|
||||
|
||||
})();
|
||||
74
core/modules/commands/wikitest.js
Normal file
74
core/modules/commands/wikitest.js
Normal 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
31
core/modules/config.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/*\
|
||||
title: $:/core/modules/config.js
|
||||
type: application/javascript
|
||||
module-type: config
|
||||
|
||||
Core configuration constants
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.dateFormats = {
|
||||
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"],
|
||||
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
|
||||
shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||
shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
|
||||
// suffixes for dates, eg "1st","2nd","3rd"..."30th","31st"
|
||||
daySuffixes: ["st","nd","rd","th","th","th","th","th","th","th",
|
||||
"th","th","th","th","th","th","th","th","th","th",
|
||||
"st","nd","rd","th","th","th","th","th","th","th",
|
||||
"st"],
|
||||
am: "am",
|
||||
pm: "pm"
|
||||
};
|
||||
|
||||
exports.htmlEntities = {quot:34, amp:38, apos:39, lt:60, gt:62, nbsp:160, iexcl:161, cent:162, pound:163, curren:164, yen:165, brvbar:166, sect:167, uml:168, copy:169, ordf:170, laquo:171, not:172, shy:173, reg:174, macr:175, deg:176, plusmn:177, sup2:178, sup3:179, acute:180, micro:181, para:182, middot:183, cedil:184, sup1:185, ordm:186, raquo:187, frac14:188, frac12:189, frac34:190, iquest:191, Agrave:192, Aacute:193, Acirc:194, Atilde:195, Auml:196, Aring:197, AElig:198, Ccedil:199, Egrave:200, Eacute:201, Ecirc:202, Euml:203, Igrave:204, Iacute:205, Icirc:206, Iuml:207, ETH:208, Ntilde:209, Ograve:210, Oacute:211, Ocirc:212, Otilde:213, Ouml:214, times:215, Oslash:216, Ugrave:217, Uacute:218, Ucirc:219, Uuml:220, Yacute:221, THORN:222, szlig:223, agrave:224, aacute:225, acirc:226, atilde:227, auml:228, aring:229, aelig:230, ccedil:231, egrave:232, eacute:233, ecirc:234, euml:235, igrave:236, iacute:237, icirc:238, iuml:239, eth:240, ntilde:241, ograve:242, oacute:243, ocirc:244, otilde:245, ouml:246, divide:247, oslash:248, ugrave:249, uacute:250, ucirc:251, uuml:252, yacute:253, thorn:254, yuml:255, OElig:338, oelig:339, Scaron:352, scaron:353, Yuml:376, fnof:402, circ:710, tilde:732, Alpha:913, Beta:914, Gamma:915, Delta:916, Epsilon:917, Zeta:918, Eta:919, Theta:920, Iota:921, Kappa:922, Lambda:923, Mu:924, Nu:925, Xi:926, Omicron:927, Pi:928, Rho:929, Sigma:931, Tau:932, Upsilon:933, Phi:934, Chi:935, Psi:936, Omega:937, alpha:945, beta:946, gamma:947, delta:948, epsilon:949, zeta:950, eta:951, theta:952, iota:953, kappa:954, lambda:955, mu:956, nu:957, xi:958, omicron:959, pi:960, rho:961, sigmaf:962, sigma:963, tau:964, upsilon:965, phi:966, chi:967, psi:968, omega:969, thetasym:977, upsih:978, piv:982, ensp:8194, emsp:8195, thinsp:8201, zwnj:8204, zwj:8205, lrm:8206, rlm:8207, ndash:8211, mdash:8212, lsquo:8216, rsquo:8217, sbquo:8218, ldquo:8220, rdquo:8221, bdquo:8222, dagger:8224, Dagger:8225, bull:8226, hellip:8230, permil:8240, prime:8242, Prime:8243, lsaquo:8249, rsaquo:8250, oline:8254, frasl:8260, euro:8364, image:8465, weierp:8472, real:8476, trade:8482, alefsym:8501, larr:8592, uarr:8593, rarr:8594, darr:8595, harr:8596, crarr:8629, lArr:8656, uArr:8657, rArr:8658, dArr:8659, hArr:8660, forall:8704, part:8706, exist:8707, empty:8709, nabla:8711, isin:8712, notin:8713, ni:8715, prod:8719, sum:8721, minus:8722, lowast:8727, radic:8730, prop:8733, infin:8734, ang:8736, and:8743, or:8744, cap:8745, cup:8746, int:8747, there4:8756, sim:8764, cong:8773, asymp:8776, ne:8800, equiv:8801, le:8804, ge:8805, sub:8834, sup:8835, nsub:8836, sube:8838, supe:8839, oplus:8853, otimes:8855, perp:8869, sdot:8901, lceil:8968, rceil:8969, lfloor:8970, rfloor:8971, lang:9001, rang:9002, loz:9674, spades:9824, clubs:9827, hearts:9829, diams:9830 };
|
||||
|
||||
})();
|
||||
80
core/modules/dependencies.js
Normal file
80
core/modules/dependencies.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/*\
|
||||
title: $:/core/modules/dependencies.js
|
||||
type: application/javascript
|
||||
module-type: global
|
||||
|
||||
Represents the dependencies of a tiddler or a parser node as these fields:
|
||||
|
||||
tiddlers: A hashmap of explicitly tiddler titles, with the value `false` if the dependency is skinny, and `true` if it is fat
|
||||
dependentAll: True if there is an implicit skinny dependency on all available tiddlers
|
||||
dependentOnContextTiddler: True if the node has a fat dependency on the current context tiddler
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var Dependencies = function(skinnyTiddlers,fatTiddlers,dependentAll) {
|
||||
var t,title;
|
||||
this.tiddlers = {};
|
||||
this.dependentAll = dependentAll;
|
||||
if(skinnyTiddlers) {
|
||||
for(t=0; t<skinnyTiddlers.length; t++) {
|
||||
title = skinnyTiddlers[t];
|
||||
if(typeof title === "string" && title !== "") {
|
||||
this.tiddlers[title] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(fatTiddlers) {
|
||||
for(t=0; t<fatTiddlers.length; t++) {
|
||||
title = fatTiddlers[t];
|
||||
if(typeof title === "string" && title !== "") {
|
||||
this.tiddlers[title] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Adds a dependency to a given tiddler. Note how setting a dependency of fat=false on a tiddler that already has
|
||||
a dependency of fat=true will leave the fat setting as true
|
||||
*/
|
||||
Dependencies.prototype.addDependency = function(tiddlerTitle,fat) {
|
||||
if(!this.tiddlers[tiddlerTitle]) {
|
||||
this.tiddlers[tiddlerTitle] = fat;
|
||||
}
|
||||
};
|
||||
|
||||
Dependencies.prototype.mergeDependencies = function(dep) {
|
||||
this.dependentAll = dep.dependentAll || this.dependentAll;
|
||||
for(var t in dep.tiddlers) {
|
||||
this.addDependency(t,dep.tiddlers[t]);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Determine if these dependencies are impacted by the specified array of changes
|
||||
changes: Hashmap of {modified: bool, deleted: bool}
|
||||
contextTiddlerTitle: The title of the current context tiddler
|
||||
*/
|
||||
Dependencies.prototype.hasChanged = function(changes,contextTiddlerTitle) {
|
||||
if(this.dependentAll) {
|
||||
return true;
|
||||
}
|
||||
if(!!this.dependentOnContextTiddler && contextTiddlerTitle && changes.hasOwnProperty(contextTiddlerTitle)) {
|
||||
return true;
|
||||
}
|
||||
for(var c in changes) {
|
||||
if(this.tiddlers.hasOwnProperty(c)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
exports.Dependencies = Dependencies;
|
||||
|
||||
})();
|
||||
27
core/modules/deserializers.js
Normal file
27
core/modules/deserializers.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/*\
|
||||
title: $:/core/modules/deserializers.js
|
||||
type: application/javascript
|
||||
module-type: tiddlerdeserializer
|
||||
|
||||
Plugins to deserialise tiddlers from a block of text
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports["text/plain"] = function(text,fields) {
|
||||
fields.text = text;
|
||||
fields.type = "text/plain";
|
||||
return [fields];
|
||||
};
|
||||
|
||||
exports["text/html"] = function(text,fields) {
|
||||
fields.text = text;
|
||||
fields.type = "text/html";
|
||||
return [fields];
|
||||
};
|
||||
|
||||
})();
|
||||
45
core/modules/macros/button.js
Normal file
45
core/modules/macros/button.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/*\
|
||||
title: $:/core/modules/macros/button.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "button",
|
||||
params: {
|
||||
name: {byName: "default", type: "text"},
|
||||
label: {byName: true, type: "text"},
|
||||
"class": {byName: true, type: "text"}
|
||||
},
|
||||
events: ["click"]
|
||||
};
|
||||
|
||||
exports.handleEvent = function(event) {
|
||||
switch(event.type) {
|
||||
case "click":
|
||||
var buttonEvent = document.createEvent("Event");
|
||||
buttonEvent.initEvent("tw-" + this.params.name,true,true);
|
||||
buttonEvent.tiddlerTitle = this.tiddlerTitle;
|
||||
buttonEvent.commandOrigin = this;
|
||||
event.target.dispatchEvent(buttonEvent);
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.executeMacro = function() {
|
||||
var attributes = {};
|
||||
if(this.hasParameter("class")) {
|
||||
attributes["class"] = this.params["class"].split(" ");
|
||||
}
|
||||
return [$tw.Tree.Element("button",attributes,[$tw.Tree.Text(this.params.label)])];
|
||||
};
|
||||
|
||||
})();
|
||||
156
core/modules/macros/chooser.js
Normal file
156
core/modules/macros/chooser.js
Normal file
@@ -0,0 +1,156 @@
|
||||
/*\
|
||||
title: $:/core/modules/macros/chooser.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "chooser",
|
||||
wrapperTag: "div",
|
||||
params: {
|
||||
},
|
||||
events: ["touchstart","touchmove","touchend","mouseover","mousemove","mouseup","mouseout"]
|
||||
};
|
||||
|
||||
exports.showChooser = function() {
|
||||
if(!this.chooserDisplayed) {
|
||||
this.chooserDisplayed = true;
|
||||
var nodes = [];
|
||||
this.wiki.forEachTiddler("title",function(title,tiddler) {
|
||||
nodes.push($tw.Tree.Element("li",{
|
||||
"data-link": title
|
||||
},[
|
||||
$tw.Tree.Text(title)
|
||||
]));
|
||||
});
|
||||
var wrapper = $tw.Tree.Element("ul",{},nodes);
|
||||
wrapper.execute(this.parents,this.tiddlerTitle);
|
||||
this.children = [wrapper];
|
||||
this.children[0].renderInDom(this.domNode);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Select the appropriate chooser item given a touch/mouse position in screen coordinates
|
||||
*/
|
||||
exports.select = function(y) {
|
||||
if(this.children.length > 0) {
|
||||
var targetIndex = Math.floor(this.children[0].domNode.childNodes.length * (y/window.innerHeight)),
|
||||
target = this.children[0].domNode.childNodes[targetIndex];
|
||||
if(target) {
|
||||
this.deselect();
|
||||
this.selectedNode = target;
|
||||
$tw.utils.addClass(target,"selected");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.deselect = function() {
|
||||
if(this.selectedNode) {
|
||||
$tw.utils.removeClass(this.selectedNode,"selected");
|
||||
this.selectedNode = null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.action = function() {
|
||||
if(this.selectedNode) {
|
||||
var navEvent = document.createEvent("Event");
|
||||
navEvent.initEvent("tw-navigate",true,true);
|
||||
navEvent.navigateTo = this.selectedNode.getAttribute("data-link");
|
||||
this.domNode.dispatchEvent(navEvent);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Set the position of the chooser panel within its wrapper given a touch/mouse position in screen coordinates
|
||||
*/
|
||||
exports.hoverChooser = function(x,y) {
|
||||
if(this.chooserDisplayed) {
|
||||
// Get the target element that the touch/mouse is over
|
||||
this.select(y);
|
||||
// Things we need for sizing and positioning the chooser
|
||||
var domPanel = this.children[0].domNode,
|
||||
heightPanel = domPanel.offsetHeight,
|
||||
widthPanel = domPanel.offsetWidth;
|
||||
// Position the chooser div to account for scrolling
|
||||
this.children[0].domNode.style.top = window.pageYOffset + "px";
|
||||
// Scale the panel to fit
|
||||
var scaleFactor = window.innerHeight/heightPanel;
|
||||
// Scale up as we move right
|
||||
var expandFactor = x > 50 ? ((x+150)/200) : 1;
|
||||
// Set up the transform
|
||||
var scale = scaleFactor * expandFactor,
|
||||
translateX = x > 16 ? 0 : -(((16-x)/16) * widthPanel) / scale,
|
||||
translateY = (y / scale) - ((y/window.innerHeight) * heightPanel);
|
||||
domPanel.style.webkitTransformOrigin =
|
||||
domPanel.style.MozTransformOrigin = "0 0";
|
||||
domPanel.style.webkitTransform =
|
||||
domPanel.style.MozTransform = "scale(" + scale + ") translateX(" + translateX + "px) translateY(" + translateY + "px)";
|
||||
}
|
||||
};
|
||||
|
||||
exports.hideChooser = function() {
|
||||
if(this.chooserDisplayed) {
|
||||
this.deselect();
|
||||
this.chooserDisplayed = false;
|
||||
this.domNode.removeChild(this.children[0].domNode);
|
||||
this.children = [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.handleEvent = function(event) {
|
||||
switch(event.type) {
|
||||
case "touchstart":
|
||||
this.showChooser();
|
||||
this.hoverChooser(event.touches[0].clientX,event.touches[0].clientY);
|
||||
event.preventDefault();
|
||||
return false;
|
||||
case "touchmove":
|
||||
this.hoverChooser(event.touches[0].clientX,event.touches[0].clientY);
|
||||
event.preventDefault();
|
||||
return false;
|
||||
case "touchend":
|
||||
this.action();
|
||||
this.hideChooser();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
case "mouseover":
|
||||
if(event.target === this.domNode) {
|
||||
this.showChooser();
|
||||
this.hoverChooser(event.clientX,event.clientY);
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case "mousemove":
|
||||
this.hoverChooser(event.clientX,event.clientY);
|
||||
event.preventDefault();
|
||||
return false;
|
||||
case "mouseup":
|
||||
this.action();
|
||||
this.hideChooser();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
case "mouseout":
|
||||
if(!$tw.utils.domContains(this.domNode,event.relatedTarget)) {
|
||||
this.hideChooser();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.executeMacro = function() {
|
||||
this.chooserDisplayed = false;
|
||||
return [];
|
||||
};
|
||||
|
||||
})();
|
||||
25
core/modules/macros/comment.js
Normal file
25
core/modules/macros/comment.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/*\
|
||||
title: $:/core/modules/macros/comment.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
Comment macro, a no-op
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "!",
|
||||
params: {}
|
||||
};
|
||||
|
||||
exports.executeMacro = function() {
|
||||
return [];
|
||||
};
|
||||
|
||||
|
||||
})();
|
||||
25
core/modules/macros/echo.js
Normal file
25
core/modules/macros/echo.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/*\
|
||||
title: $:/core/modules/macros/echo.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "echo",
|
||||
params: {
|
||||
text: {byPos: 0, type: "text"}
|
||||
}
|
||||
};
|
||||
|
||||
exports.executeMacro = function() {
|
||||
return [$tw.Tree.Text(this.params.text)];
|
||||
};
|
||||
|
||||
|
||||
})();
|
||||
80
core/modules/macros/edit/edit.js
Normal file
80
core/modules/macros/edit/edit.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
167
core/modules/macros/edit/editors/bitmapeditor.js
Normal file
167
core/modules/macros/edit/editors/bitmapeditor.js
Normal 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;
|
||||
|
||||
})();
|
||||
106
core/modules/macros/edit/editors/texteditor.js
Normal file
106
core/modules/macros/edit/editors/texteditor.js
Normal 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;
|
||||
|
||||
})();
|
||||
46
core/modules/macros/image.js
Normal file
46
core/modules/macros/image.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/*\
|
||||
title: $:/core/modules/macros/image.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "image",
|
||||
params: {
|
||||
src: {byName: "default", type: "tiddler"},
|
||||
text: {byName: true, type: "text"},
|
||||
alignment: {byName: true, type: "text"}
|
||||
}
|
||||
};
|
||||
|
||||
exports.executeMacro = function() {
|
||||
if(this.wiki.tiddlerExists(this.params.src)) {
|
||||
var imageTree = this.wiki.parseTiddler(this.params.src).tree,
|
||||
cloneImage = [];
|
||||
for(var t=0; t<imageTree.length; t++) {
|
||||
cloneImage.push(imageTree[t].clone());
|
||||
}
|
||||
if(this.params.text) {
|
||||
return [$tw.Tree.Element("div",{
|
||||
alt: this.params.text,
|
||||
title: this.params.text
|
||||
},cloneImage)];
|
||||
} else {
|
||||
return cloneImage;
|
||||
}
|
||||
} else {
|
||||
return [$tw.Tree.Element("img",{
|
||||
src: this.params.src,
|
||||
alt: this.params.text,
|
||||
title: this.params.text
|
||||
})];
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
37
core/modules/macros/include.js
Normal file
37
core/modules/macros/include.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/*\
|
||||
title: $:/core/modules/macros/include.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "^",
|
||||
params: {
|
||||
filter: {byPos: 0, type: "filter"},
|
||||
as: {byPos: 1, as: "text"}
|
||||
}
|
||||
};
|
||||
|
||||
exports.executeMacro = function() {
|
||||
var as = this.params.as || "text/plain";
|
||||
if(this.hasParameter("filter")) {
|
||||
var titles = this.wiki.filterTiddlers(this.params.filter),
|
||||
result = [];
|
||||
for(var t=0; t<titles.length; t++) {
|
||||
result.push(this.wiki.serializeTiddler(titles[t],as));
|
||||
}
|
||||
return [$tw.Tree.Element("pre",{},[
|
||||
$tw.Tree.Text(result.join("\n"))
|
||||
])];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
|
||||
})();
|
||||
88
core/modules/macros/link.js
Normal file
88
core/modules/macros/link.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/*\
|
||||
title: $:/core/modules/macros/link.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
Implements the link macro.
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var isLinkExternal = function(to) {
|
||||
var externalRegExp = /(?:file|http|https|mailto|ftp|irc|news|data):[^\s'"]+(?:\/|\b)/i;
|
||||
return externalRegExp.test(to);
|
||||
};
|
||||
|
||||
exports.info = {
|
||||
name: "link",
|
||||
params: {
|
||||
to: {byName: "default", type: "tiddler", skinny: true},
|
||||
space: {byName: true, type: "text"}
|
||||
},
|
||||
events: ["click"]
|
||||
};
|
||||
|
||||
exports.handleEvent = function (event) {
|
||||
switch(event.type) {
|
||||
case "click":
|
||||
if(isLinkExternal(this.params.to)) {
|
||||
event.target.setAttribute("target","_blank");
|
||||
return true;
|
||||
} else {
|
||||
var navEvent = document.createEvent("Event");
|
||||
navEvent.initEvent("tw-navigate",true,true);
|
||||
navEvent.navigateTo = this.params.to;
|
||||
event.target.dispatchEvent(navEvent);
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.executeMacro = function() {
|
||||
// Assemble the information about the link
|
||||
var linkInfo = {
|
||||
to: this.params.to,
|
||||
space: this.params.space
|
||||
};
|
||||
// Generate the default link characteristics
|
||||
linkInfo.isExternal = isLinkExternal(linkInfo.to);
|
||||
if(!linkInfo.isExternal) {
|
||||
linkInfo.isMissing = !this.wiki.tiddlerExists(linkInfo.to);
|
||||
}
|
||||
linkInfo.attributes = {
|
||||
href: linkInfo.to
|
||||
};
|
||||
if(!linkInfo.isExternal) {
|
||||
linkInfo.attributes.href = encodeURIComponent(linkInfo.to);
|
||||
}
|
||||
// Generate the default classes for the link
|
||||
linkInfo.attributes["class"] = ["tw-tiddlylink"];
|
||||
if(linkInfo.isExternal) {
|
||||
linkInfo.attributes["class"].push("tw-tiddlylink-external");
|
||||
} else {
|
||||
linkInfo.attributes["class"].push("tw-tiddlylink-internal");
|
||||
if(linkInfo.isMissing) {
|
||||
linkInfo.attributes["class"].push("tw-tiddlylink-missing");
|
||||
} else {
|
||||
linkInfo.attributes["class"].push("tw-tiddlylink-resolves");
|
||||
}
|
||||
}
|
||||
// Create the link
|
||||
var children;
|
||||
if(linkInfo.suppressLink) {
|
||||
children = this.cloneContent();
|
||||
} else {
|
||||
children = [$tw.Tree.Element("a",linkInfo.attributes,this.cloneContent())];
|
||||
}
|
||||
for(var t=0; t<children.length; t++) {
|
||||
children[t].execute(this.parents,this.tiddlerTitle);
|
||||
}
|
||||
return children;
|
||||
};
|
||||
|
||||
})();
|
||||
80
core/modules/macros/list.js
Normal file
80
core/modules/macros/list.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/*\
|
||||
title: $:/core/modules/macros/list.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var handlers = {
|
||||
all: function(wiki) {
|
||||
return wiki.sortTiddlers("title","excludeLists");
|
||||
},
|
||||
missing: function(wiki) {
|
||||
return wiki.getMissingTitles();
|
||||
},
|
||||
orphans: function(wiki) {
|
||||
return wiki.getOrphanTitles();
|
||||
},
|
||||
shadowed: function(wiki) {
|
||||
return wiki.getShadowTitles();
|
||||
},
|
||||
touched: function(wiki) {
|
||||
// Server syncing isn't implemented yet
|
||||
return [];
|
||||
},
|
||||
filter: function(wiki) {
|
||||
// Filters aren't implemented yet
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
exports.info = {
|
||||
name: "list",
|
||||
dependentAll: true, // Tiddlers containing <<list>> macro are dependent on every tiddler
|
||||
params: {
|
||||
type: {byName: "default", type: "text"},
|
||||
template: {byName: true, type: "tiddler"},
|
||||
emptyMessage: {byName: true, type: "text"}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
exports.executeMacro = function() {
|
||||
var templateType = "text/x-tiddlywiki",
|
||||
templateText = "<<view title link>>",
|
||||
template = this.params.template ? this.wiki.getTiddler(this.params.template) : null,
|
||||
children = [],
|
||||
t,
|
||||
parents = this.parents;
|
||||
if(template) {
|
||||
parents = parents.slice(0);
|
||||
parents.push(template.title);
|
||||
templateType = template.type;
|
||||
templateText = template.text;
|
||||
}
|
||||
var handler = handlers[this.params.type];
|
||||
handler = handler || handlers.all;
|
||||
var tiddlers = handler(this.wiki);
|
||||
if(tiddlers.length === 0) {
|
||||
return [$tw.Tree.Text(this.params.emptyMessage || "")];
|
||||
} else {
|
||||
var templateTree = this.wiki.parseText(templateType,templateText).tree;
|
||||
for(t=0; t<tiddlers.length; t++) {
|
||||
var cloneTemplate = [];
|
||||
for(var c=0; c<templateTree.length; c++) {
|
||||
cloneTemplate.push(templateTree[c].clone());
|
||||
}
|
||||
var listNode = $tw.Tree.Element("li",null,cloneTemplate);
|
||||
listNode.execute(parents,tiddlers[t]);
|
||||
children.push(listNode);
|
||||
}
|
||||
return [$tw.Tree.Element("ul",null,children)];
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
177
core/modules/macros/slider.js
Normal file
177
core/modules/macros/slider.js
Normal file
@@ -0,0 +1,177 @@
|
||||
/*\
|
||||
title: $:/core/modules/macros/slider.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
!Introduction
|
||||
The slider macro is used to selectively reveal a chunk of text. By default, it renders as a button that may be clicked or touched to reveal the enclosed text.
|
||||
|
||||
The enclosed text can be a string of WikiText or be taken from a target tiddler.
|
||||
|
||||
The current state of the slider can be stored as the string "open" or "closed" in a specified tiddler. If the value of that tiddler changes then the slider is automatically updated. If no state tiddler is specified then the state of the slider isn't retained, but the slider still works as expected.
|
||||
!!Parameters
|
||||
|`state` //(defaults to 1st parameter)// |The title of the tiddler to contain the current state of the slider |
|
||||
|`default` |The initial state of the slider, either `open` or `closed` |
|
||||
|`class` |A CSS class to be applied to the slider root element |
|
||||
|`content` |The WikiText to be enclosed in the slider. Overrides the `target` parameter, if present |
|
||||
|`target` //(defaults to 2nd parameter)// |The title of the tiddler that contains the enclosed text. Ignored if the `content` parameter is specified |
|
||||
|`label` //(defaults to 3rd parameter)// |The plain text to be displayed as the label for the slider button |
|
||||
|`tooltip` //(defaults to 4th parameter)// |The plain text tooltip to be displayed when the mouse hovers over the slider button |
|
||||
!!Markup
|
||||
The markup generated by the slider macro is:
|
||||
{{{
|
||||
<span class="tw-slider {user defined class}">
|
||||
<a class="btn-info">{slider label}</a>
|
||||
<div class="tw-slider-body" style="display:{state}">{slider content}</div>
|
||||
</span>
|
||||
}}}
|
||||
!!Examples
|
||||
A minimal slider:
|
||||
{{{
|
||||
<<slider target:MyTiddler>>
|
||||
}}}
|
||||
!!Notes
|
||||
The slider is a good study example of a simple interactive macro.
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "slider",
|
||||
params: {
|
||||
state: {byPos: 0, type: "tiddler"},
|
||||
target: {byPos: 1, type: "tiddler"},
|
||||
label: {byPos: 2, type: "text"},
|
||||
tooltip: {byPos: 3, type: "text"},
|
||||
"default": {byName: true, type: "text"},
|
||||
"class": {byName: true, type: "text"},
|
||||
content: {byName: true, type: "text"}
|
||||
},
|
||||
events: ["click"]
|
||||
};
|
||||
|
||||
exports.getOpenState = function() {
|
||||
if(this.hasParameter("state")) {
|
||||
var stateTiddler = this.wiki.getTiddler(this.params.state);
|
||||
if(stateTiddler) {
|
||||
return stateTiddler.fields.text.trim() === "open";
|
||||
}
|
||||
}
|
||||
if(this.hasParameter("default")) {
|
||||
return this.params["default"] === "open";
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
exports.saveOpenState = function() {
|
||||
if(this.hasParameter("state")) {
|
||||
var stateTiddler = this.wiki.getTiddler(this.params.state) || {title: this.params.state, text: ""};
|
||||
this.wiki.addTiddler(new $tw.Tiddler(stateTiddler,{text: this.isOpen ? "open" : "closed"}));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
exports.getSliderChildren = function() {
|
||||
if(this.hasParameter("content")) {
|
||||
return this.wiki.parseText("text/x-tiddlywiki",this.params.content).tree;
|
||||
} else if(this.hasParameter("target")) {
|
||||
return [$tw.Tree.Macro(
|
||||
"tiddler",
|
||||
{target: this.params.target},
|
||||
null,
|
||||
this.wiki)];
|
||||
} else {
|
||||
return [$tw.Tree.errorNode("No content specified for slider")];
|
||||
}
|
||||
};
|
||||
|
||||
exports.handleEvent = function(event) {
|
||||
switch(event.type) {
|
||||
case "click":
|
||||
if(event.target === this.domNode.firstChild.firstChild) {
|
||||
this.isOpen = !this.isOpen;
|
||||
if(!this.saveOpenState()) {
|
||||
this.refreshInDom({});
|
||||
}
|
||||
event.preventDefault();
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.executeMacro = function() {
|
||||
this.isOpen = this.getOpenState();
|
||||
var sliderChildren = [];
|
||||
if(this.isOpen) {
|
||||
sliderChildren = this.getSliderChildren();
|
||||
}
|
||||
var attributes = {
|
||||
"class": ["tw-slider"]
|
||||
};
|
||||
if(this.hasParameter("class")) {
|
||||
attributes["class"].push(this.params["class"]);
|
||||
}
|
||||
if(this.hasParameter("state")) {
|
||||
attributes["data-tw-slider-type"] = this.params.state;
|
||||
}
|
||||
if(this.hasParameter("tooltip")) {
|
||||
attributes.alt = this.params.tooltip;
|
||||
attributes.title = this.params.tooltip;
|
||||
}
|
||||
var children = $tw.Tree.Element("span",
|
||||
attributes,
|
||||
[
|
||||
$tw.Tree.Element("a",
|
||||
{
|
||||
"class": ["btn","btn-info"]
|
||||
},[
|
||||
$tw.Tree.Text(this.params.label ? this.params.label : this.params.target)
|
||||
]
|
||||
),
|
||||
$tw.Tree.Element("div",
|
||||
{
|
||||
"class": ["tw-slider-body"],
|
||||
"style": {"display": this.isOpen ? "block" : "none"}
|
||||
},
|
||||
sliderChildren
|
||||
)
|
||||
]
|
||||
);
|
||||
children.execute(this.parents,this.tiddlerTitle);
|
||||
return [children];
|
||||
};
|
||||
|
||||
exports.refreshInDom = function(changes) {
|
||||
var needChildrenRefresh = true; // Avoid refreshing the children nodes if we don't need to
|
||||
// If the state tiddler has changed then reset the open state
|
||||
if(this.hasParameter("state") && changes.hasOwnProperty(this.params.state)) {
|
||||
this.isOpen = this.getOpenState();
|
||||
}
|
||||
// Render the children if the slider is open and we don't have any children yet
|
||||
if(this.isOpen && this.children[0].children[1].children.length === 0) {
|
||||
// Remove the existing dom node for the body
|
||||
this.children[0].domNode.removeChild(this.children[0].children[1].domNode);
|
||||
// Get the slider children and execute it
|
||||
this.children[0].children[1].children = this.getSliderChildren();
|
||||
this.children[0].children[1].execute(this.parents,this.tiddlerTitle);
|
||||
this.children[0].children[1].renderInDom(this.children[0].domNode,null);
|
||||
needChildrenRefresh = false; // Don't refresh the children if we've just created them
|
||||
}
|
||||
// Set the visibility of the slider children
|
||||
this.children[0].children[1].domNode.style.display = this.isOpen ? "block" : "none";
|
||||
// Refresh any children
|
||||
if(needChildrenRefresh) {
|
||||
for(var t=0; t<this.children.length; t++) {
|
||||
this.children[t].refreshInDom(changes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
195
core/modules/macros/story.js
Normal file
195
core/modules/macros/story.js
Normal file
@@ -0,0 +1,195 @@
|
||||
/*\
|
||||
title: $:/core/modules/macros/story.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
function scrollToTop(duration) {
|
||||
if (duration < 0) {
|
||||
return;
|
||||
}
|
||||
var delta = (-document.body.scrollTop/duration) * 10;
|
||||
window.setTimeout(function() {
|
||||
document.body.scrollTop = document.body.scrollTop + delta;
|
||||
scrollToTop(duration-10);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
exports.info = {
|
||||
name: "story",
|
||||
params: {
|
||||
story: {byName: "default", type: "tiddler"},
|
||||
defaultViewTemplate: {byName: true, type: "tiddler"},
|
||||
defaultEditTemplate: {byName: true, type: "tiddler"}
|
||||
},
|
||||
events: ["tw-navigate","tw-EditTiddler","tw-SaveTiddler"]
|
||||
};
|
||||
|
||||
exports.handleEvent = function(event) {
|
||||
var template, storyTiddler, story, storyRecord, tiddler, storyTiddlerModified, t;
|
||||
switch(event.type) {
|
||||
case "tw-navigate":
|
||||
// Navigate to a specified tiddler
|
||||
template = this.hasParameter("defaultViewTemplate") ? this.params.defaultViewTemplate : "ViewTemplate";
|
||||
storyTiddler = this.wiki.getTiddler(this.params.story);
|
||||
story = {tiddlers: []};
|
||||
if(storyTiddler && storyTiddler.fields.hasOwnProperty("text")) {
|
||||
story = JSON.parse(storyTiddler.fields.text);
|
||||
}
|
||||
story.tiddlers.unshift({title: event.navigateTo, template: template});
|
||||
this.wiki.addTiddler(new $tw.Tiddler(storyTiddler,{text: JSON.stringify(story)}));
|
||||
scrollToTop(400);
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
case "tw-EditTiddler":
|
||||
// Put the specified tiddler into edit mode
|
||||
template = this.hasParameter("defaultEditTemplate") ? this.params.defaultEditTemplate : "EditTemplate";
|
||||
storyTiddler = this.wiki.getTiddler(this.params.story);
|
||||
story = {tiddlers: []};
|
||||
if(storyTiddler && storyTiddler.fields.hasOwnProperty("text")) {
|
||||
story = JSON.parse(storyTiddler.fields.text);
|
||||
}
|
||||
for(t=0; t<story.tiddlers.length; t++) {
|
||||
storyRecord = story.tiddlers[t];
|
||||
if(storyRecord.title === event.tiddlerTitle && storyRecord.template !== template) {
|
||||
storyRecord.title = "Draft " + (new Date()) + " of " + event.tiddlerTitle;
|
||||
storyRecord.template = template;
|
||||
tiddler = this.wiki.getTiddler(event.tiddlerTitle);
|
||||
this.wiki.addTiddler(new $tw.Tiddler(
|
||||
{
|
||||
text: "Type the text for the tiddler '" + event.tiddlerTitle + "'"
|
||||
},
|
||||
tiddler,
|
||||
{
|
||||
title: storyRecord.title,
|
||||
"draft.title": event.tiddlerTitle,
|
||||
"draft.of": event.tiddlerTitle
|
||||
}));
|
||||
}
|
||||
}
|
||||
this.wiki.addTiddler(new $tw.Tiddler(storyTiddler,{text: JSON.stringify(story)}));
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
case "tw-SaveTiddler":
|
||||
template = this.hasParameter("defaultViewTemplate") ? this.params.defaultEditTemplate : "ViewTemplate";
|
||||
storyTiddler = this.wiki.getTiddler(this.params.story);
|
||||
story = {tiddlers: []};
|
||||
storyTiddlerModified = false;
|
||||
if(storyTiddler && storyTiddler.fields.hasOwnProperty("text")) {
|
||||
story = JSON.parse(storyTiddler.fields.text);
|
||||
}
|
||||
for(t=0; t<story.tiddlers.length; t++) {
|
||||
storyRecord = story.tiddlers[t];
|
||||
if(storyRecord.title === event.tiddlerTitle && storyRecord.template !== template) {
|
||||
tiddler = this.wiki.getTiddler(storyRecord.title);
|
||||
if(tiddler && tiddler.fields.hasOwnProperty("draft.title")) {
|
||||
// Save the draft tiddler as the real tiddler
|
||||
this.wiki.addTiddler(new $tw.Tiddler(tiddler,{title: tiddler.fields["draft.title"],"draft.title": undefined, "draft.of": undefined}));
|
||||
// Remove the draft tiddler
|
||||
this.wiki.deleteTiddler(storyRecord.title);
|
||||
// Remove the original tiddler if we're renaming it
|
||||
if(tiddler.fields["draft.of"] !== tiddler.fields["draft.title"]) {
|
||||
this.wiki.deleteTiddler(tiddler.fields["draft.of"]);
|
||||
}
|
||||
// Make the story record point to the newly saved tiddler
|
||||
storyRecord.title = tiddler.fields["draft.title"];
|
||||
storyRecord.template = template;
|
||||
// Check if we're modifying the story tiddler itself
|
||||
if(tiddler.fields["draft.title"] === this.params.story) {
|
||||
storyTiddlerModified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!storyTiddlerModified) {
|
||||
this.wiki.addTiddler(new $tw.Tiddler(storyTiddler,{text: JSON.stringify(story)}));
|
||||
}
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
exports.executeMacro = function() {
|
||||
var story = JSON.parse(this.wiki.getTiddlerText(this.params.story)),
|
||||
children = [];
|
||||
for(var t=0; t<story.tiddlers.length; t++) {
|
||||
var m = $tw.Tree.Macro("tiddler",
|
||||
{target: story.tiddlers[t].title,template: story.tiddlers[t].template},
|
||||
null,
|
||||
this.wiki);
|
||||
m.execute(this.parents,this.tiddlerTitle);
|
||||
children.push(m);
|
||||
}
|
||||
return children;
|
||||
};
|
||||
|
||||
exports.refreshInDom = function(changes) {
|
||||
var t;
|
||||
/*jslint browser: true */
|
||||
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
|
||||
// Get the tiddlers we're supposed to be displaying
|
||||
var self = this,
|
||||
story = JSON.parse(this.wiki.getTiddlerText(this.params.story)),
|
||||
template = this.params.template,
|
||||
n,domNode,
|
||||
findTiddler = function (childIndex,tiddlerTitle,templateTitle) {
|
||||
while(childIndex < self.children.length) {
|
||||
var params = self.children[childIndex].params;
|
||||
if(params.target === tiddlerTitle) {
|
||||
if(!templateTitle || params.template === templateTitle) {
|
||||
return childIndex;
|
||||
}
|
||||
}
|
||||
childIndex++;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
for(t=0; t<story.tiddlers.length; t++) {
|
||||
// See if the node we want is already there
|
||||
var tiddlerNode = findTiddler(t,story.tiddlers[t].title,story.tiddlers[t].template);
|
||||
if(tiddlerNode === null) {
|
||||
// If not, render the tiddler
|
||||
var m = $tw.Tree.Macro("tiddler",
|
||||
{target: story.tiddlers[t].title,template: story.tiddlers[t].template},
|
||||
null,
|
||||
this.wiki);
|
||||
m.execute(this.parents,this.tiddlerTitle);
|
||||
m.renderInDom(this.domNode,this.domNode.childNodes[t]);
|
||||
this.children.splice(t,0,m);
|
||||
} else {
|
||||
// Delete any nodes preceding the one we want
|
||||
if(tiddlerNode > t) {
|
||||
// First delete the DOM nodes
|
||||
for(n=t; n<tiddlerNode; n++) {
|
||||
domNode = this.children[n].domNode;
|
||||
domNode.parentNode.removeChild(domNode);
|
||||
}
|
||||
// Then delete the actual renderer nodes
|
||||
this.children.splice(t,tiddlerNode-t);
|
||||
}
|
||||
// Refresh the DOM node we're reusing
|
||||
this.children[t].refreshInDom(changes);
|
||||
}
|
||||
}
|
||||
// Remove any left over nodes
|
||||
if(this.children.length > story.tiddlers.length) {
|
||||
for(t=story.tiddlers.length; t<this.children.length; t++) {
|
||||
domNode = this.children[t].domNode;
|
||||
domNode.parentNode.removeChild(domNode);
|
||||
}
|
||||
this.children.splice(story.tiddlers.length,this.children.length-story.tiddlers.length);
|
||||
}
|
||||
} else {
|
||||
for(t=0; t<this.children.length; t++) {
|
||||
this.children[t].refreshInDom(changes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
158
core/modules/macros/tiddler.js
Normal file
158
core/modules/macros/tiddler.js
Normal file
@@ -0,0 +1,158 @@
|
||||
/*\
|
||||
title: $:/core/modules/macros/tiddler.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
The tiddler macros transcludes another macro into the tiddler being rendered.
|
||||
|
||||
Parameters:
|
||||
target: the title of the tiddler to transclude
|
||||
template: the title of the tiddler to use as a template for the transcluded tiddler
|
||||
with: optional parameters to be substituted into the rendered tiddler
|
||||
|
||||
The simplest case is to just supply a target tiddler:
|
||||
|
||||
<<tiddler Foo>> or <<transclude target:Foo>>
|
||||
|
||||
This will render the tiddler Foo within the current tiddler. If the tiddler Foo includes
|
||||
the view macro (or other macros that reference the fields of the current tiddler), then the
|
||||
fields of the tiddler Foo will be accessed.
|
||||
|
||||
If you want to transclude the tiddler as a template, so that the fields referenced by the view
|
||||
macro are those of the tiddler doing the transcluding, then you can instead specify the tiddler
|
||||
as a template:
|
||||
|
||||
<<tiddler template:Foo>>
|
||||
|
||||
The effect is the same as the previous example: the text of the tiddler Foo is rendered. The
|
||||
difference is that the view macro will access the fields of the tiddler doing the transcluding.
|
||||
|
||||
The `target` and `template` parameters may be combined:
|
||||
|
||||
<<tiddler target:Foo template:Bar>>
|
||||
|
||||
Here, the text of the tiddler `Bar` will be transcluded, with the macros within it accessing the fields
|
||||
of the tiddler `Foo`.
|
||||
|
||||
Finally, the `with` parameter is used to substitute values for the special markers $1, $2, $3 etc. The
|
||||
substitutions are performed on the tiddler whose text is being rendered: either the tiddler named in
|
||||
the `template` parameter or, if that parameter is missing, the tiddler named in the `target` parameter.
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "tiddler",
|
||||
cascadeParams: true, // Cascade names of named parameters to following anonymous parameters
|
||||
params: {
|
||||
target: {byName: "default", type: "tiddler"},
|
||||
template: {byName: true, type: "tiddler"},
|
||||
"with": {byName: true, type: "text", dependentAll: true}
|
||||
}
|
||||
};
|
||||
|
||||
exports.evaluateDependencies = function() {
|
||||
var dependencies = new $tw.Dependencies(),
|
||||
template = this.srcParams.template;
|
||||
if(template === undefined) {
|
||||
template = this.srcParams.target;
|
||||
}
|
||||
if(typeof template === "function") {
|
||||
dependencies.dependentAll = true;
|
||||
} else {
|
||||
dependencies.addDependency(template,true);
|
||||
}
|
||||
return dependencies;
|
||||
};
|
||||
|
||||
exports.executeMacro = function() {
|
||||
var renderTitle = this.params.target,
|
||||
renderTemplate = this.params.template,
|
||||
children,
|
||||
childrenClone = [],
|
||||
t,
|
||||
parents = this.parents.slice(0);
|
||||
// If there's no render title specified then use the current tiddler title
|
||||
if(typeof renderTitle !== "string") {
|
||||
renderTitle = this.tiddlerTitle;
|
||||
}
|
||||
// If there's no template specified then use the target tiddler title
|
||||
if(typeof renderTemplate !== "string") {
|
||||
renderTemplate = renderTitle;
|
||||
}
|
||||
// Check for recursion
|
||||
if(parents.indexOf(renderTemplate) !== -1) {
|
||||
children = [$tw.Tree.errorNode("Tiddler recursion error in <<tiddler>> macro")];
|
||||
} else {
|
||||
if(this.hasParameter("with")) {
|
||||
// Parameterised transclusion
|
||||
var targetTiddler = this.wiki.getTiddler(renderTemplate),
|
||||
text = targetTiddler.fields.text;
|
||||
var withTokens = [this.params["with"]]; // TODO: Allow for more than one with: parameter
|
||||
for(t=0; t<withTokens.length; t++) {
|
||||
var placeholderRegExp = new RegExp("\\$"+(t+1),"mg");
|
||||
text = text.replace(placeholderRegExp,withTokens[t]);
|
||||
}
|
||||
children = this.wiki.parseText(targetTiddler.fields.type,text).tree;
|
||||
} else {
|
||||
// There's no parameterisation, so we can just render the target tiddler directly
|
||||
var parseTree = this.wiki.parseTiddler(renderTemplate);
|
||||
children = parseTree ? parseTree.tree : [];
|
||||
}
|
||||
}
|
||||
// Update the stack of tiddler titles for recursion detection
|
||||
parents.push(renderTemplate);
|
||||
// Clone the children
|
||||
for(t=0; t<children.length; t++) {
|
||||
childrenClone.push(children[t].clone());
|
||||
}
|
||||
// Execute macros within the children
|
||||
for(t=0; t<childrenClone.length; t++) {
|
||||
childrenClone[t].execute(parents,renderTitle);
|
||||
}
|
||||
// Set up the attributes for the wrapper element
|
||||
var attributes = {
|
||||
"data-tiddler-target": renderTitle,
|
||||
"data-tiddler-template": renderTemplate,
|
||||
"class": ["tw-tiddler-frame"]
|
||||
};
|
||||
if(!this.wiki.tiddlerExists(renderTitle)) {
|
||||
attributes["class"].push("tw-tiddler-missing");
|
||||
}
|
||||
// Return the children
|
||||
return [$tw.Tree.Element("div",attributes,childrenClone)];
|
||||
};
|
||||
|
||||
exports.refreshInDom = function(changes) {
|
||||
var t;
|
||||
// Set the class for missing tiddlers
|
||||
var renderTitle = this.params.target;
|
||||
if(typeof renderTitle !== "string") {
|
||||
renderTitle = this.params.template;
|
||||
}
|
||||
if(renderTitle) {
|
||||
$tw.utils.toggleClass(this.children[0].domNode,"tw-tiddler-missing",!this.wiki.tiddlerExists(renderTitle));
|
||||
}
|
||||
// Rerender the tiddler if it is impacted by the changes
|
||||
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
|
||||
// Manually reexecute and rerender this macro
|
||||
while(this.domNode.hasChildNodes()) {
|
||||
this.domNode.removeChild(this.domNode.firstChild);
|
||||
}
|
||||
this.execute(this.parents,this.tiddlerTitle);
|
||||
for(t=0; t<this.children.length; t++) {
|
||||
this.children[t].renderInDom(this.domNode);
|
||||
}
|
||||
} else {
|
||||
// Refresh any children
|
||||
for(t=0; t<this.children.length; t++) {
|
||||
this.children[t].refreshInDom(changes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
23
core/modules/macros/version.js
Normal file
23
core/modules/macros/version.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/*\
|
||||
title: $:/core/modules/macros/version.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "version",
|
||||
params: {
|
||||
}
|
||||
};
|
||||
|
||||
exports.executeMacro = function() {
|
||||
return [$tw.Tree.Text($tw.utils.getVersionString())];
|
||||
};
|
||||
|
||||
})();
|
||||
56
core/modules/macros/video.js
Normal file
56
core/modules/macros/video.js
Normal 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
105
core/modules/macros/view.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/*\
|
||||
title: $:/core/modules/macros/view.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "view",
|
||||
dependentOnContextTiddler: true,
|
||||
params: {
|
||||
field: {byPos: 0, type: "text"},
|
||||
format: {byPos: 1, type: "text"},
|
||||
template: {byPos: 2, type: "text"}
|
||||
}
|
||||
};
|
||||
|
||||
exports.executeMacro = function() {
|
||||
var tiddler = this.wiki.getTiddler(this.tiddlerTitle),
|
||||
field = this.hasParameter("field") ? this.params.field : "title",
|
||||
value,
|
||||
children,
|
||||
t,
|
||||
childrenClone = [],
|
||||
parents = this.parents;
|
||||
if(tiddler) {
|
||||
value = tiddler.fields[field];
|
||||
} else {
|
||||
switch(field) {
|
||||
case "text":
|
||||
value = "The tiddler '" + this.tiddlerTitle + "' does not exist";
|
||||
break;
|
||||
case "title":
|
||||
value = this.tiddlerTitle;
|
||||
break;
|
||||
case "modified":
|
||||
case "created":
|
||||
value = new Date();
|
||||
break;
|
||||
default:
|
||||
value = "Missing tiddler '" + this.tiddlerTitle + "'";
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch(this.params.format) {
|
||||
case "link":
|
||||
if(value === undefined) {
|
||||
return [];
|
||||
} else {
|
||||
var link = $tw.Tree.Macro("link",
|
||||
{to: value},
|
||||
[$tw.Tree.Text(value)],
|
||||
this.wiki);
|
||||
link.execute(parents,this.tiddlerTitle);
|
||||
return [link];
|
||||
}
|
||||
break;
|
||||
case "wikified":
|
||||
if(tiddler && this.params.field === "text") {
|
||||
if(parents.indexOf(tiddler.fields.title) !== -1) {
|
||||
children = [$tw.Tree.errorNode("Tiddler recursion error in <<view>> macro")];
|
||||
} else {
|
||||
children = this.wiki.parseTiddler(tiddler.fields.title).tree;
|
||||
}
|
||||
parents = parents.slice(0);
|
||||
parents.push(tiddler.fields.title);
|
||||
} else {
|
||||
children = this.wiki.parseText("text/x-tiddlywiki",value).tree;
|
||||
}
|
||||
for(t=0; t<children.length; t++) {
|
||||
childrenClone.push(children[t].clone());
|
||||
}
|
||||
for(t=0; t<childrenClone.length; t++) {
|
||||
childrenClone[t].execute(parents,this.tiddlerTitle);
|
||||
}
|
||||
return childrenClone;
|
||||
case "date":
|
||||
var template = this.params.template || "DD MMM YYYY";
|
||||
if(value === undefined) {
|
||||
return [];
|
||||
} else {
|
||||
return [$tw.Tree.Text($tw.utils.formatDateString(value,template))];
|
||||
}
|
||||
break;
|
||||
default: // "text"
|
||||
// Get the stringified version of the field value
|
||||
if(field !== "text" && tiddler) {
|
||||
value = tiddler.getFieldString(field);
|
||||
}
|
||||
if(value === undefined || value === null) {
|
||||
return [];
|
||||
} else {
|
||||
return [$tw.Tree.Text(value)];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
105
core/modules/macros/zoomer.js
Normal file
105
core/modules/macros/zoomer.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/*\
|
||||
title: $:/core/modules/macros/zoomer.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.info = {
|
||||
name: "zoomer",
|
||||
wrapperTag: "div",
|
||||
params: {
|
||||
},
|
||||
events: ["touchstart","touchmove","touchend"]
|
||||
};
|
||||
|
||||
exports.startZoomer = function(x,y) {
|
||||
this.inZoomer = true;
|
||||
this.startX = x;
|
||||
this.startY = y;
|
||||
$tw.utils.addClass(document.body,"in-zoomer");
|
||||
};
|
||||
|
||||
exports.stopZoomer = function() {
|
||||
var newScrollY = this.yFactor * (this.bodyHeight - this.windowHeight);
|
||||
this.inZoomer = false;
|
||||
window.scrollTo(0,newScrollY);
|
||||
document.body.style.webkitTransform = "translateY(" + newScrollY * this.xFactor + "px) " +
|
||||
"scale(" + this.scale + ") " +
|
||||
"translateY(" + ((this.windowHeight / this.scale) - this.bodyHeight) * this.yFactor * this.xFactor + "px)";
|
||||
$tw.utils.removeClass(document.body,"in-zoomer");
|
||||
document.body.style.webkitTransform = "translateY(0) scale(1) translateY(0)";
|
||||
};
|
||||
|
||||
/*
|
||||
Zoom the body element given a touch/mouse position in screen coordinates
|
||||
*/
|
||||
exports.hoverZoomer = function(x,y) {
|
||||
// Put the transform origin at the top in the middle
|
||||
document.body.style.webkitTransformOrigin = "50% 0";
|
||||
// Some shortcuts
|
||||
this.bodyWidth = document.body.offsetWidth;
|
||||
this.bodyHeight = document.body.offsetHeight;
|
||||
this.windowWidth = window.innerWidth;
|
||||
this.windowHeight = window.innerHeight;
|
||||
// Compute the scale factor for fitting the entire page into the window. This is
|
||||
// the scale factor we'll use when the touch is far to the left
|
||||
this.minScale = this.windowHeight / this.bodyHeight;
|
||||
if(this.minScale < 0.1) {
|
||||
// Don't scale to less than 10% of original size
|
||||
this.minScale = 0.1;
|
||||
} else if(this.minScale > 1) {
|
||||
// Nor should we scale up if the body is shorter than the window
|
||||
this.minScale = 1;
|
||||
}
|
||||
// We divide the screen into two horizontal zones divided by the right edge of the body at maximum zoom (ie minimum scale)
|
||||
this.splitPos = this.windowWidth/2 + (this.bodyWidth * this.minScale)/2;
|
||||
// Compute the 0->1 ratio (from right to left) of the position of the touch within the right zone
|
||||
this.xFactor = (this.windowWidth - x) / (this.windowWidth - this.splitPos);
|
||||
if(this.xFactor > 1) {
|
||||
this.xFactor = 1;
|
||||
}
|
||||
// And the 0->1 ratio (from top to bottom) of the position of the touch down the screen
|
||||
this.yFactor = y/this.windowHeight;
|
||||
// Now interpolate the scale
|
||||
this.scale = (this.minScale - 1) * this.xFactor + 1;
|
||||
// Apply the transform. The malarkey with .toFixed() is because otherwise we might get numbers in
|
||||
// exponential notation (such as 5.1e-15) that are illegal in CSS
|
||||
var preTranslateY = window.scrollY * this.xFactor,
|
||||
scale = this.scale,
|
||||
postTranslateY = ((this.windowHeight / this.scale) - this.bodyHeight) * this.yFactor * this.xFactor;
|
||||
var transform = "translateY(" + preTranslateY.toFixed(8) + "px) " +
|
||||
"scale(" + scale.toFixed(8) + ") " +
|
||||
"translateY(" + postTranslateY.toFixed(8) + "px)";
|
||||
document.body.style.webkitTransform = transform;
|
||||
};
|
||||
|
||||
exports.handleEvent = function(event) {
|
||||
switch(event.type) {
|
||||
case "touchstart":
|
||||
this.startZoomer(event.touches[0].clientX,event.touches[0].clientY);
|
||||
this.hoverZoomer(event.touches[0].clientX,event.touches[0].clientY);
|
||||
event.preventDefault();
|
||||
return false;
|
||||
case "touchmove":
|
||||
this.hoverZoomer(event.touches[0].clientX,event.touches[0].clientY);
|
||||
event.preventDefault();
|
||||
return false;
|
||||
case "touchend":
|
||||
this.stopZoomer();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
exports.executeMacro = function() {
|
||||
this.inZoomer = false;
|
||||
return [];
|
||||
};
|
||||
|
||||
})();
|
||||
35
core/modules/parsers/imageparser.js
Normal file
35
core/modules/parsers/imageparser.js
Normal 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;
|
||||
|
||||
})();
|
||||
11
core/modules/parsers/javascriptparser/tiddlywiki.plugin
Normal file
11
core/modules/parsers/javascriptparser/tiddlywiki.plugin
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"tiddlers": [
|
||||
{
|
||||
"file": "../../../../../node_modules/esprima/esprima.js",
|
||||
"fields": {
|
||||
"title": "$:/core/modules/parsers/javascriptparser/esprima.js",
|
||||
"type": "application/javascript"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
50
core/modules/parsers/jsonparser.js
Normal file
50
core/modules/parsers/jsonparser.js
Normal file
@@ -0,0 +1,50 @@
|
||||
/*\
|
||||
title: $:/core/modules/parsers/jsonparser.js
|
||||
type: application/javascript
|
||||
module-type: parser
|
||||
|
||||
Parses a JSON object into a parse tree
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var renderObject = function(obj) {
|
||||
var children = [],t;
|
||||
if($tw.utils.isArray(obj)) {
|
||||
for(t=0; t<obj.length; t++) {
|
||||
children.push($tw.Tree.Element("li",{
|
||||
"class": ["jsonArrayMember"]
|
||||
},[renderObject(obj[t])]));
|
||||
}
|
||||
return $tw.Tree.Element("ul",{
|
||||
"class": ["jsonArray"]
|
||||
},children);
|
||||
} else if(typeof obj === "object") {
|
||||
for(t in obj) {
|
||||
children.push($tw.Tree.Element("li",{
|
||||
"class": ["jsonObjectMember"]
|
||||
},[$tw.Tree.splitLabelNode("JSON",[$tw.Tree.Text(t)],[renderObject(obj[t])])]));
|
||||
}
|
||||
return $tw.Tree.Element("ul",{
|
||||
"class": ["jsonObject"]
|
||||
},children);
|
||||
} else {
|
||||
return $tw.Tree.labelNode("JSON" + (typeof obj),[$tw.Tree.Text(JSON.stringify(obj))],["jsonValue"]);
|
||||
}
|
||||
};
|
||||
|
||||
var JSONParser = function(options) {
|
||||
this.wiki = options.wiki;
|
||||
};
|
||||
|
||||
JSONParser.prototype.parse = function(type,text) {
|
||||
return new $tw.Renderer([renderObject(JSON.parse(text))],new $tw.Dependencies());
|
||||
};
|
||||
|
||||
exports["application/json"] = JSONParser;
|
||||
|
||||
})();
|
||||
25
core/modules/parsers/plaintextparser.js
Normal file
25
core/modules/parsers/plaintextparser.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/*\
|
||||
title: $:/core/modules/parsers/plaintextparser.js
|
||||
type: application/javascript
|
||||
module-type: parser
|
||||
|
||||
Renders plain text tiddlers
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var PlainTextParser = function(options) {
|
||||
this.wiki = options.wiki;
|
||||
};
|
||||
|
||||
PlainTextParser.prototype.parse = function(type,text) {
|
||||
return new $tw.Renderer([$tw.Tree.Element("pre",{},[$tw.Tree.Text(text)])],new $tw.Dependencies());
|
||||
};
|
||||
|
||||
exports["text/plain"] = PlainTextParser;
|
||||
|
||||
})();
|
||||
59
core/modules/parsers/tiddlytextparser.js
Normal file
59
core/modules/parsers/tiddlytextparser.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/*\
|
||||
title: $:/core/modules/parsers/tiddlytextparser.js
|
||||
type: application/javascript
|
||||
module-type: parser
|
||||
|
||||
Parses a plain text block that can also contain macros and transclusions.
|
||||
|
||||
The syntax for transclusions is:
|
||||
|
||||
[[tiddlerTitle]]
|
||||
|
||||
The syntax for macros is:
|
||||
|
||||
<<macroName params>>
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var TiddlyTextParser = function(options) {
|
||||
this.wiki = options.wiki;
|
||||
};
|
||||
|
||||
TiddlyTextParser.prototype.parse = function(type,text) {
|
||||
var output = [],
|
||||
dependencies = new $tw.Dependencies(),
|
||||
macroRegExp = /(?:\[\[([^\]]+)\]\])|(?:<<(?:([!@£\$%\^\&\*\(\)`\~'"\|\\\/;\:\.\,\+\=\-\_\{\}])|([^>\s]+))(?:\s*)((?:[^>]|(?:>(?!>)))*)>>)/mg,
|
||||
lastMatchPos = 0,
|
||||
match,
|
||||
macroNode;
|
||||
do {
|
||||
match = macroRegExp.exec(text);
|
||||
if(match) {
|
||||
output.push($tw.Tree.Text(text.substring(lastMatchPos,match.index)));
|
||||
var macroName = match[2] || match[3];
|
||||
if(match[1]) { // Transclusion
|
||||
macroNode = $tw.Tree.Macro("tiddler",{
|
||||
target: match[1]
|
||||
},[],this.wiki);
|
||||
} else if(macroName) { // Macro call
|
||||
macroNode = $tw.Tree.Macro(macroName,match[4],[],this.wiki);
|
||||
}
|
||||
output.push(macroNode);
|
||||
dependencies.mergeDependencies(macroNode.dependencies);
|
||||
lastMatchPos = match.index + match[0].length;
|
||||
} else {
|
||||
output.push($tw.Tree.Text(text.substr(lastMatchPos)));
|
||||
}
|
||||
} while(match);
|
||||
return new $tw.Renderer(output,dependencies);
|
||||
};
|
||||
|
||||
exports["text/x-tiddlywiki-css"] = TiddlyTextParser;
|
||||
exports["text/x-tiddlywiki-html"] = TiddlyTextParser;
|
||||
|
||||
})();
|
||||
709
core/modules/parsers/wikitextparser/rules/wikitextrules.js
Executable file
709
core/modules/parsers/wikitextparser/rules/wikitextrules.js
Executable 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("—"));
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
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;
|
||||
|
||||
})();
|
||||
186
core/modules/parsers/wikitextparser/wikitextparser.js
Normal file
186
core/modules/parsers/wikitextparser/wikitextparser.js
Normal 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
48
core/modules/renderer.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/*\
|
||||
title: $:/core/modules/renderer.js
|
||||
type: application/javascript
|
||||
module-type: global
|
||||
|
||||
Represents a parse tree and associated data
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var Renderer = function(tree,dependencies) {
|
||||
this.tree = tree;
|
||||
this.dependencies = dependencies;
|
||||
};
|
||||
|
||||
Renderer.prototype.execute = function(parents,tiddlerTitle) {
|
||||
for(var t=0; t<this.tree.length; t++) {
|
||||
this.tree[t].execute(parents,tiddlerTitle);
|
||||
}
|
||||
};
|
||||
|
||||
Renderer.prototype.render = function(type) {
|
||||
var output = [];
|
||||
for(var t=0; t<this.tree.length; t++) {
|
||||
output.push(this.tree[t].render(type));
|
||||
}
|
||||
return output.join("");
|
||||
};
|
||||
|
||||
Renderer.prototype.renderInDom = function(parentDomNode,insertBefore) {
|
||||
for(var t=0; t<this.tree.length; t++) {
|
||||
this.tree[t].renderInDom(parentDomNode,insertBefore);
|
||||
}
|
||||
};
|
||||
|
||||
Renderer.prototype.refreshInDom = function(changes) {
|
||||
for(var t=0; t<this.tree.length; t++) {
|
||||
this.tree[t].refreshInDom(changes);
|
||||
}
|
||||
};
|
||||
|
||||
exports.Renderer = Renderer;
|
||||
|
||||
})();
|
||||
71
core/modules/serializers.js
Normal file
71
core/modules/serializers.js
Normal 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
70
core/modules/startup.js
Normal 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
34
core/modules/tiddler.js
Normal 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
28
core/modules/tree.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/*\
|
||||
title: $:/core/modules/tree.js
|
||||
type: application/javascript
|
||||
module-type: global
|
||||
|
||||
Renderer objects encapsulate a tree of nodes that are capable of rendering and selectively updating an
|
||||
HTML representation of themselves. The following node types are defined:
|
||||
* ''Macro'' - represents an invocation of a macro
|
||||
* ''Element'' - represents a single HTML element
|
||||
* ''Text'' - represents an HTML text node
|
||||
* ''Entity'' - represents an HTML entity node
|
||||
* ''Raw'' - represents a chunk of unparsed HTML text
|
||||
|
||||
These node types are implemented with prototypal inheritance from a base Node class. One
|
||||
unusual convenience in the implementation is that the node constructors can be called without an explicit
|
||||
`new` keyword: the constructors check for `this` not being an instance of themselves, and recursively invoke
|
||||
themselves with `new` when required.
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.Tree = {};
|
||||
|
||||
})();
|
||||
57
core/modules/tree.utils.js
Normal file
57
core/modules/tree.utils.js
Normal file
@@ -0,0 +1,57 @@
|
||||
/*\
|
||||
title: $:/core/modules/tree.utils.js
|
||||
type: application/javascript
|
||||
module-type: treeutils
|
||||
|
||||
Static utility methods for the $tw.Tree class
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
Construct an error message
|
||||
*/
|
||||
exports.errorNode = function(text) {
|
||||
return $tw.Tree.Element("span",{
|
||||
"class": ["label","label-important"]
|
||||
},[
|
||||
new $tw.Tree.Text(text)
|
||||
]);
|
||||
};
|
||||
|
||||
/*
|
||||
Construct a label
|
||||
*/
|
||||
exports.labelNode = function(type,value,classes) {
|
||||
classes = (classes || []).slice(0);
|
||||
classes.push("label");
|
||||
return $tw.Tree.Element("span",{
|
||||
"class": classes,
|
||||
"data-tw-label-type": type
|
||||
},value);
|
||||
};
|
||||
|
||||
/*
|
||||
Construct a split label
|
||||
*/
|
||||
exports.splitLabelNode = function(type,left,right,classes) {
|
||||
classes = (classes || []).slice(0);
|
||||
classes.push("splitLabel");
|
||||
return $tw.Tree.Element("span",{
|
||||
"class": classes
|
||||
},[
|
||||
$tw.Tree.Element("span",{
|
||||
"class": ["splitLabelLeft"],
|
||||
"data-tw-label-type": type
|
||||
},left),
|
||||
$tw.Tree.Element("span",{
|
||||
"class": ["splitLabelRight"]
|
||||
},right)
|
||||
]);
|
||||
};
|
||||
|
||||
})();
|
||||
144
core/modules/treenodes/element.js
Normal file
144
core/modules/treenodes/element.js
Normal file
@@ -0,0 +1,144 @@
|
||||
/*\
|
||||
title: $:/core/modules/treenodes/element.js
|
||||
type: application/javascript
|
||||
module-type: treenode
|
||||
|
||||
Element nodes
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var Node = require("./node.js").Node;
|
||||
|
||||
var Element = function(type,attributes,children) {
|
||||
if(this instanceof Element) {
|
||||
this.type = type;
|
||||
this.attributes = attributes || {};
|
||||
this.children = children;
|
||||
} else {
|
||||
return new Element(type,attributes,children);
|
||||
}
|
||||
};
|
||||
|
||||
Element.prototype = new Node();
|
||||
Element.prototype.constructor = Element;
|
||||
|
||||
Element.prototype.clone = function() {
|
||||
var childClones;
|
||||
if(this.children) {
|
||||
childClones = [];
|
||||
for(var t=0; t<this.children.length; t++) {
|
||||
childClones.push(this.children[t].clone());
|
||||
}
|
||||
}
|
||||
return new Element(this.type,this.attributes,childClones);
|
||||
};
|
||||
|
||||
Element.prototype.execute = function(parents,tiddlerTitle) {
|
||||
if(this.children) {
|
||||
for(var t=0; t<this.children.length; t++) {
|
||||
this.children[t].execute(parents,tiddlerTitle);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Element.prototype.render = function(type) {
|
||||
var isHtml = type === "text/html",
|
||||
output = [];
|
||||
if(isHtml) {
|
||||
output.push("<",this.type);
|
||||
if(this.attributes) {
|
||||
for(var a in this.attributes) {
|
||||
var v = this.attributes[a];
|
||||
if(v !== undefined) {
|
||||
if($tw.utils.isArray(v)) {
|
||||
v = v.join(" ");
|
||||
} else if(typeof v === "object") {
|
||||
var s = [];
|
||||
for(var p in v) {
|
||||
s.push(p + ":" + v[p] + ";");
|
||||
}
|
||||
v = s.join("");
|
||||
}
|
||||
output.push(" ",a,"='",$tw.utils.htmlEncode(v),"'");
|
||||
}
|
||||
}
|
||||
}
|
||||
output.push(">");
|
||||
}
|
||||
if(this.children) {
|
||||
for(var t=0; t<this.children.length; t++) {
|
||||
output.push(this.children[t].render(type));
|
||||
}
|
||||
if(isHtml) {
|
||||
output.push("</",this.type,">");
|
||||
}
|
||||
}
|
||||
return output.join("");
|
||||
};
|
||||
|
||||
Element.prototype.renderInDom = function(parentDomNode,insertBefore) {
|
||||
var element = document.createElement(this.type);
|
||||
if(this.attributes) {
|
||||
for(var a in this.attributes) {
|
||||
var v = this.attributes[a];
|
||||
if(v !== undefined) {
|
||||
if($tw.utils.isArray(v)) { // Ahem, could there be arrays other than className?
|
||||
element.className = v.join(" ");
|
||||
} else if (typeof v === "object") { // ...or objects other than style?
|
||||
for(var p in v) {
|
||||
element.style[p] = v[p];
|
||||
}
|
||||
} else {
|
||||
element.setAttribute(a,v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(insertBefore) {
|
||||
parentDomNode.insertBefore(element,insertBefore);
|
||||
} else {
|
||||
parentDomNode.appendChild(element);
|
||||
}
|
||||
this.domNode = element;
|
||||
if(this.children) {
|
||||
for(var t=0; t<this.children.length; t++) {
|
||||
this.children[t].renderInDom(element);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Element.prototype.refresh = function(changes) {
|
||||
if(this.children) {
|
||||
for(var t=0; t<this.children.length; t++) {
|
||||
this.children[t].refresh(changes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Element.prototype.refreshInDom = function(changes) {
|
||||
if(this.children) {
|
||||
for(var t=0; t<this.children.length; t++) {
|
||||
this.children[t].refreshInDom(changes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Element.prototype.broadcastEvent = function(event) {
|
||||
if(this.children) {
|
||||
for(var t=0; t<this.children.length; t++) {
|
||||
if(!this.children[t].broadcastEvent(event)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.Element = Element;
|
||||
|
||||
})();
|
||||
39
core/modules/treenodes/entity.js
Normal file
39
core/modules/treenodes/entity.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/*\
|
||||
title: $:/core/modules/treenodes/entity.js
|
||||
type: application/javascript
|
||||
module-type: treenode
|
||||
|
||||
Entity nodes
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var Node = require("./node.js").Node;
|
||||
|
||||
var Entity = function(entity) {
|
||||
if(this instanceof Entity) {
|
||||
this.entity = entity;
|
||||
} else {
|
||||
return new Entity(entity);
|
||||
}
|
||||
};
|
||||
|
||||
Entity.prototype = new Node();
|
||||
Entity.prototype.constructor = Entity;
|
||||
|
||||
Entity.prototype.render = function(type) {
|
||||
return type === "text/html" ? this.entity : $tw.utils.entityDecode(this.entity);
|
||||
};
|
||||
|
||||
Entity.prototype.renderInDom = function(domNode) {
|
||||
this.domNode = document.createTextNode($tw.utils.entityDecode(this.entity));
|
||||
domNode.appendChild(this.domNode);
|
||||
};
|
||||
|
||||
exports.Entity = Entity;
|
||||
|
||||
})();
|
||||
248
core/modules/treenodes/macro.js
Normal file
248
core/modules/treenodes/macro.js
Normal file
@@ -0,0 +1,248 @@
|
||||
/*\
|
||||
title: $:/core/modules/treenodes/macro.js
|
||||
type: application/javascript
|
||||
module-type: treenode
|
||||
|
||||
Macro node, used as the base class for macros
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jshint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var Node = require("./node.js").Node;
|
||||
|
||||
/*
|
||||
Construct a renderer node representing a macro invocation
|
||||
macroName: name of the macro
|
||||
srcParams: a string or a hashmap of parameters (each can be a string, or a fn(tiddler,wiki) for evaluated parameters)
|
||||
children: optional array of child nodes
|
||||
store: reference to the WikiStore associated with this macro
|
||||
dependencies: optional Dependencies object representing the dependencies of this macro
|
||||
|
||||
Note that the dependencies will be evaluated if not provided.
|
||||
*/
|
||||
var Macro = function(macroName,srcParams,content,wiki,dependencies) {
|
||||
var MacroClass = wiki ? wiki.macros[macroName] : null; // Get the macro class
|
||||
if(this instanceof Macro) {
|
||||
// Save the details
|
||||
this.macroName = macroName;
|
||||
this.srcParams = srcParams || {};
|
||||
this.content = content || [];
|
||||
this.wiki = wiki;
|
||||
this.dependencies = dependencies;
|
||||
// Parse the macro parameters if required
|
||||
if(typeof this.srcParams === "string") {
|
||||
this.srcParams = this.parseMacroParamString(srcParams);
|
||||
}
|
||||
// Evaluate the dependencies if required
|
||||
if(macroName && !this.dependencies) {
|
||||
this.dependencies = this.evaluateDependencies();
|
||||
}
|
||||
// Get a reference to the static information about this macro
|
||||
if(wiki && wiki.macros[macroName]) {
|
||||
this.MacroClass = MacroClass;
|
||||
this.info = MacroClass.prototype.info;
|
||||
}
|
||||
} else {
|
||||
// If Macro() has been called without 'new' then instantiate the right macro class
|
||||
if(!MacroClass) {
|
||||
throw "Unknown macro '" + macroName + "'";
|
||||
}
|
||||
return new MacroClass(macroName,srcParams,content,wiki,dependencies);
|
||||
}
|
||||
};
|
||||
|
||||
Macro.prototype = new Node();
|
||||
Macro.prototype.constructor = Macro;
|
||||
|
||||
/*
|
||||
Evaluate the dependencies of this macro invocation. If the macro provides an `evaluateDependencies` method
|
||||
then it is invoked to evaluate the dependencies. Otherwise it generates the dependencies based on the
|
||||
macro parameters provided
|
||||
*/
|
||||
Macro.prototype.evaluateDependencies = function() {
|
||||
// Figure out the dependencies from the metadata and parameters
|
||||
var dependencies = new $tw.Dependencies();
|
||||
if(this.info.dependentAll) {
|
||||
dependencies.dependentAll = true;
|
||||
}
|
||||
if(this.info.dependentOnContextTiddler) {
|
||||
dependencies.dependentOnContextTiddler = true;
|
||||
}
|
||||
for(var m in this.info.params) {
|
||||
var paramInfo = this.info.params[m];
|
||||
if(m in this.srcParams && paramInfo.type === "tiddler") {
|
||||
if(typeof this.srcParams[m] === "function") {
|
||||
dependencies.dependentAll = true;
|
||||
} else {
|
||||
dependencies.addDependency(this.srcParams[m],!paramInfo.skinny);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dependencies;
|
||||
};
|
||||
|
||||
Macro.prototype.parseMacroParamString = function(paramString) {
|
||||
/*jslint evil: true */
|
||||
var params = {},
|
||||
args = new $tw.utils.ArgParser(paramString,{defaultName: "anon", cascadeDefaults: this.info.cascadeDefaults}),
|
||||
self = this,
|
||||
insertParam = function(name,arg) {
|
||||
if(arg.evaluated) {
|
||||
params[name] = eval("(function(tiddler,wiki) {return " + arg.string + ";})");
|
||||
} else {
|
||||
params[name] = arg.string;
|
||||
}
|
||||
};
|
||||
for(var m in this.info.params) {
|
||||
var param = this.info.params[m],
|
||||
arg;
|
||||
if("byPos" in param && args.byPos[param.byPos] && (args.byPos[param.byPos].n === "anon" || args.byPos[param.byPos].n === m)) {
|
||||
arg = args.byPos[param.byPos].v;
|
||||
insertParam(m,arg);
|
||||
} else {
|
||||
arg = args.getValueByName(m);
|
||||
if(!arg && param.byName === "default") {
|
||||
arg = args.getValueByName("anon");
|
||||
}
|
||||
if(arg) {
|
||||
insertParam(m,arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
};
|
||||
|
||||
Macro.prototype.hasParameter = function(name) {
|
||||
return this.params.hasOwnProperty(name);
|
||||
};
|
||||
|
||||
Macro.prototype.cloneContent = function() {
|
||||
var contentClones = [];
|
||||
for(var t=0; t<this.content.length; t++) {
|
||||
contentClones.push(this.content[t].clone());
|
||||
}
|
||||
return contentClones;
|
||||
};
|
||||
|
||||
Macro.prototype.clone = function() {
|
||||
return new this.MacroClass(this.macroName,this.srcParams,this.cloneContent(),this.wiki,this.dependencies);
|
||||
};
|
||||
|
||||
Macro.prototype.execute = function(parents,tiddlerTitle) {
|
||||
parents = parents || [];
|
||||
// Evaluate macro parameters to get their values
|
||||
this.params = {};
|
||||
var tiddler = this.wiki.getTiddler(tiddlerTitle);
|
||||
if(!tiddler) {
|
||||
tiddler = {title: tiddlerTitle};
|
||||
}
|
||||
for(var p in this.srcParams) {
|
||||
if(typeof this.srcParams[p] === "function") {
|
||||
this.params[p] = this.srcParams[p](tiddler.fields,this.wiki);
|
||||
} else {
|
||||
this.params[p] = this.srcParams[p];
|
||||
}
|
||||
}
|
||||
// Save the context info for use when we're refreshing it
|
||||
this.tiddlerTitle = tiddlerTitle;
|
||||
this.parents = parents;
|
||||
// Execute the macro to generate its children, and recursively execute them
|
||||
this.children = this.executeMacro.call(this);
|
||||
};
|
||||
|
||||
Macro.prototype.render = function(type) {
|
||||
var output = [];
|
||||
for(var t=0; t<this.children.length; t++) {
|
||||
output.push(this.children[t].render(type));
|
||||
}
|
||||
return output.join("");
|
||||
};
|
||||
|
||||
Macro.prototype.renderInDom = function(parentDomNode,insertBefore) {
|
||||
// Create the wrapper node for the macro
|
||||
var domNode = document.createElement(this.info.wrapperTag || "span");
|
||||
this.domNode = domNode;
|
||||
if(insertBefore) {
|
||||
parentDomNode.insertBefore(domNode,insertBefore);
|
||||
} else {
|
||||
parentDomNode.appendChild(domNode);
|
||||
}
|
||||
// Add some debugging information to it
|
||||
domNode.setAttribute("data-tw-macro",this.macroName);
|
||||
// Ask the macro to add event handlers to the node
|
||||
this.addEventHandlers();
|
||||
// Render the content of the macro
|
||||
for(var t=0; t<this.children.length; t++) {
|
||||
this.children[t].renderInDom(domNode);
|
||||
}
|
||||
this.postRenderInDom();
|
||||
};
|
||||
|
||||
Macro.prototype.addEventHandlers = function() {
|
||||
if(this.info.events) {
|
||||
for(var t=0; t<this.info.events.length; t++) {
|
||||
// Register this macro node to handle the event via the handleEvent() method
|
||||
this.domNode.addEventListener(this.info.events[t],this,false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Macro.prototype.broadcastEvent = function(event) {
|
||||
if(!this.handleEvent(event)) {
|
||||
return false;
|
||||
}
|
||||
for(var t=0; t<this.children.length; t++) {
|
||||
if(!this.children[t].broadcastEvent(event)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
Macro.prototype.postRenderInDom = function() {
|
||||
// Do nothing, individual macros can override
|
||||
};
|
||||
|
||||
Macro.prototype.refresh = function(changes) {
|
||||
var t,
|
||||
self = this;
|
||||
// Check if any of the dependencies of this macro node have changed
|
||||
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
|
||||
// Re-execute the macro if so
|
||||
this.execute(this.parents,this.tiddlerTitle);
|
||||
} else {
|
||||
// Refresh any children
|
||||
for(t=0; t<this.children.length; t++) {
|
||||
this.children[t].refresh(changes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Macro.prototype.refreshInDom = function(changes) {
|
||||
var t,
|
||||
self = this;
|
||||
// Check if any of the dependencies of this macro node have changed
|
||||
if(this.dependencies.hasChanged(changes,this.tiddlerTitle)) {
|
||||
// Manually reexecute and rerender this macro
|
||||
while(this.domNode.hasChildNodes()) {
|
||||
this.domNode.removeChild(this.domNode.firstChild);
|
||||
}
|
||||
this.execute(this.parents,this.tiddlerTitle);
|
||||
for(t=0; t<this.children.length; t++) {
|
||||
this.children[t].renderInDom(this.domNode);
|
||||
}
|
||||
} else {
|
||||
// Refresh any children
|
||||
for(t=0; t<this.children.length; t++) {
|
||||
this.children[t].refreshInDom(changes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.Macro = Macro;
|
||||
|
||||
})();
|
||||
80
core/modules/treenodes/node.js
Normal file
80
core/modules/treenodes/node.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/*\
|
||||
title: $:/core/modules/treenodes/node.js
|
||||
type: application/javascript
|
||||
module-type: treenode
|
||||
|
||||
Base class for all other tree nodes
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
The virtual base class for all types of renderer nodes. It keeps track of child nodes but otherwise contains no functionality
|
||||
*/
|
||||
var Node = function() {
|
||||
if(this instanceof Node) {
|
||||
} else {
|
||||
return new Node();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Makes a copy of a node, or returns the node itself if it doesn't need cloning because it doesn't have any state
|
||||
*/
|
||||
Node.prototype.clone = function() {
|
||||
// By default we don't actually clone nodes, we just re-use them (we do clone macros and elements)
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Fire an event at a node which is also broadcast to any child nodes
|
||||
*/
|
||||
Node.prototype.broadcastEvent = function(event) {
|
||||
return true; // true means that the event wasn't handled by this node
|
||||
};
|
||||
|
||||
/*
|
||||
Execute a node and derive its child nodes
|
||||
parents: array of titles of each transcluded parent tiddler (used for detecting recursion)
|
||||
tiddlerTitle: title of the context tiddler within which this node is being executed
|
||||
*/
|
||||
Node.prototype.execute = function(parents,tiddlerTitle) {
|
||||
};
|
||||
|
||||
/*
|
||||
Render a node and its children to a particular MIME type
|
||||
type: Content type to which the node should be rendered
|
||||
*/
|
||||
Node.prototype.render = function(type) {
|
||||
};
|
||||
|
||||
/*
|
||||
Render a node and its children into the DOM
|
||||
parentDomNode: node that should become the parent of the rendered node
|
||||
insertBefore: optional node that the node should be inserted before
|
||||
*/
|
||||
Node.prototype.renderInDom = function(parentDomNode,insertBefore) {
|
||||
};
|
||||
|
||||
/*
|
||||
Re-execute a node if it is impacted by a set of tiddler changes
|
||||
changes: hashmap of tiddler changes in the format used by $tw.Wiki
|
||||
*/
|
||||
Node.prototype.refresh = function(changes) {
|
||||
};
|
||||
|
||||
/*
|
||||
Re-execute a node if it is impacted by a set of tiddler changes, and update the associated DOM elements as necessary
|
||||
changes: hashmap of tiddler changes in the format used by $tw.Wiki
|
||||
*/
|
||||
Node.prototype.refreshInDom = function(changes) {
|
||||
|
||||
};
|
||||
|
||||
exports.Node = Node;
|
||||
|
||||
})();
|
||||
40
core/modules/treenodes/raw.js
Normal file
40
core/modules/treenodes/raw.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/*\
|
||||
title: $:/core/modules/treenodes/raw.js
|
||||
type: application/javascript
|
||||
module-type: treenode
|
||||
|
||||
Raw, unparsed HTML nodes
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var Node = require("./node.js").Node;
|
||||
|
||||
var Raw = function(html) {
|
||||
if(this instanceof Raw) {
|
||||
this.html = html;
|
||||
} else {
|
||||
return new Raw(html);
|
||||
}
|
||||
};
|
||||
|
||||
Raw.prototype = new Node();
|
||||
Raw.prototype.constructor = Raw;
|
||||
|
||||
Raw.prototype.render = function(type) {
|
||||
return this.html;
|
||||
};
|
||||
|
||||
Raw.prototype.renderInDom = function(domNode) {
|
||||
this.domNode = document.createElement("div");
|
||||
this.domNode.innerHTML = this.html;
|
||||
domNode.appendChild(this.domNode);
|
||||
};
|
||||
|
||||
exports.Raw = Raw;
|
||||
|
||||
})();
|
||||
39
core/modules/treenodes/text.js
Normal file
39
core/modules/treenodes/text.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/*\
|
||||
title: $:/core/modules/treenodes/text.js
|
||||
type: application/javascript
|
||||
module-type: treenode
|
||||
|
||||
Text nodes
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var Node = require("./node.js").Node;
|
||||
|
||||
var Text = function(text) {
|
||||
if(this instanceof Text) {
|
||||
this.text = text;
|
||||
} else {
|
||||
return new Text(text);
|
||||
}
|
||||
};
|
||||
|
||||
Text.prototype = new Node();
|
||||
Text.prototype.constructor = Text;
|
||||
|
||||
Text.prototype.render = function(type) {
|
||||
return type === "text/html" ? $tw.utils.htmlEncode(this.text) : this.text;
|
||||
};
|
||||
|
||||
Text.prototype.renderInDom = function(domNode) {
|
||||
this.domNode = document.createTextNode(this.text);
|
||||
domNode.appendChild(this.domNode);
|
||||
};
|
||||
|
||||
exports.Text = Text;
|
||||
|
||||
})();
|
||||
117
core/modules/utils.argparser.js
Normal file
117
core/modules/utils.argparser.js
Normal 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
309
core/modules/utils.js
Normal 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 "&", < to "<", > to ">" and " to """
|
||||
exports.htmlEncode = function(s)
|
||||
{
|
||||
if(s) {
|
||||
return s.toString().replace(/&/mg,"&").replace(/</mg,"<").replace(/>/mg,">").replace(/\"/mg,""");
|
||||
} 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",
|
||||
' <style id="' + id + '" type="text/css">' + css + '</style>'); // fails without
|
||||
} else { // Modern browsers
|
||||
if(el) {
|
||||
el.replaceChild(document.createTextNode(css), el.firstChild);
|
||||
} else {
|
||||
el = document.createElement("style");
|
||||
el.type = "text/css";
|
||||
el.id = id;
|
||||
el.appendChild(document.createTextNode(css));
|
||||
document.getElementsByTagName("head")[0].appendChild(el);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Parse a version number string of the form "1.2.3", "1.2.3.a4", or "1.2.3.b4" into:
|
||||
{major: <number>, minor: <number>, revision: <number>, alpha: <number>, beta: <number>}
|
||||
*/
|
||||
exports.parseVersion = function(versionString) {
|
||||
versionString = versionString || "0.0.0";
|
||||
var components = versionString.split("."),
|
||||
version = {
|
||||
major: components[0] ? parseInt(components[0],10) : null,
|
||||
minor: components[1] ? parseInt(components[1],10) : null,
|
||||
revision: components[2] ? parseInt(components[2],10) : null
|
||||
};
|
||||
if(components[3]) {
|
||||
if(components[3].charAt(0) === "a") {
|
||||
version.alpha = parseInt(components[3].substr(1),10);
|
||||
} else if(components[3].charAt(0) === "b") {
|
||||
version.beta = parseInt(components[3].substr(1),10);
|
||||
}
|
||||
}
|
||||
return version;
|
||||
};
|
||||
|
||||
exports.getVersionString = function() {
|
||||
return ($tw.version.major || "0") + "." +
|
||||
($tw.version.minor || "0") + "." +
|
||||
($tw.version.revision || "0") +
|
||||
($tw.version.alpha ? ".a" + $tw.version.alpha : "") +
|
||||
($tw.version.beta ? ".b" + $tw.version.beta : "");
|
||||
};
|
||||
|
||||
/*
|
||||
Extract the version number from the meta tag or from the boot file
|
||||
*/
|
||||
|
||||
if($tw.isBrowser) {
|
||||
|
||||
// Browser version
|
||||
exports.extractVersionInfo = function() {
|
||||
var metatags = document.getElementsByTagName("meta");
|
||||
for(var t=0; t<metatags.length; t++) {
|
||||
var m = metatags[t];
|
||||
if(m.name === "tiddlywiki-version") {
|
||||
return $tw.utils.parseVersion(m.content);
|
||||
}
|
||||
}
|
||||
return $tw.utils.parseVersion();
|
||||
};
|
||||
|
||||
} else {
|
||||
|
||||
// Server version
|
||||
exports.extractVersionInfo = function() {
|
||||
var fs = require("fs");
|
||||
return $tw.utils.parseVersion(fs.readFileSync($tw.boot.bootPath + "/version.txt").toString("utf8"));
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
})();
|
||||
91
core/modules/wiki.filters.js
Normal file
91
core/modules/wiki.filters.js
Normal 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
339
core/modules/wiki.js
Normal 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);
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
26
core/static.template.html.tid
Normal file
26
core/static.template.html.tid
Normal 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>
|
||||
47
core/tiddlywiki5.template.html.tid
Normal file
47
core/tiddlywiki5.template.html.tid
Normal 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
1
core/version.txt
Normal file
@@ -0,0 +1 @@
|
||||
5.0.0.a2
|
||||
Reference in New Issue
Block a user