/*\ title: $:/boot.js type: application/javascript The main boot kernel for TiddlyWiki. This single file creates a barebones TW environment that is just sufficient to bootstrap the plugins containing the main logic of the applicaiton. On the server this file is executed directly to boot TiddlyWiki. In the browser, this file is packed into a single HTML file along with other elements: # bootprefix.js # # boot.js The module definitions on the browser look like this: $tw.defineModule("MyModule","moduletype",function(module,exports,require) { // Module code inserted here return exports; }); In practice, each module is wrapped in a separate script block. \*/ (function() { /*jslint node: true */ /*global modules: false */ "use strict"; /////////////////////////// Setting up $tw // Set up $tw global for the server if(typeof(window) === "undefined" && !global.$tw) { global.$tw = {isBrowser: false}; } // Modules store registers all the modules the system has seen $tw.modules = $tw.modules || {}; // hashmap by module name of {fn:, exports:, moduleType:} // Plugins store organises module exports by module type $tw.plugins = $tw.plugins || {}; // hashmap by module type of array of exports // Config object $tw.config = $tw.config || {}; // Constants $tw.config.root = $tw.config.root || "$:"; // Root for module titles (eg, "$:/kernel/boot.js") $tw.config.pluginSubDir = $tw.config.pluginSubDir || "./modules"; // File extensions $tw.config.fileExtensions = { ".tid": {type: "application/x-tiddler", encoding: "utf8"}, ".js": {type: "application/javascript", encoding: "utf8"}, ".jpg": {type: "image/jpeg", encoding: "base64"}, ".jpeg": {type: "image/jpeg", encoding: "base64"}, ".png": {type: "image/png", encoding: "base64"}, ".gif": {type: "image/gif", encoding: "base64"} }; /////////////////////////// Utility functions $tw.utils = $tw.utils || {}; /* Determine if a value is an array */ $tw.utils.isArray = function(value) { return Object.prototype.toString.call(value) == "[object Array]"; } // Convert "&" 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.pad2(value.getUTCMonth() + 1) + $tw.utils.pad2(value.getUTCDate()) + $tw.utils.pad2(value.getUTCHours()) + $tw.utils.pad2(value.getUTCMinutes()); }; // Parse a date from a YYYYMMDDHHMMSSMMM format string $tw.utils.parseDate = function(value) { if(typeof value === "string") { return new Date(Date.UTC(parseInt(value.substr(0,4),10), parseInt(value.substr(4,2),10)-1, parseInt(value.substr(6,2),10), parseInt(value.substr(8,2)||"00",10), parseInt(value.substr(10,2)||"00",10), parseInt(value.substr(12,2)||"00",10), parseInt(value.substr(14,3)||"000",10))); } else if (value instanceof Date) { return value; } else { return null; } }; // Parse a string array from a bracketted list $tw.utils.parseStringArray = function(value) { if(typeof value === "string") { var memberRegExp = /(?:\[\[([^\]]+)\]\])|([^\s$]+)/mg, results = [], match; do { match = memberRegExp.exec(value); if(match) { results.push(match[1] || match[2]); } } while(match); return results; } else if ($tw.utils.isArray(value)) { return value; } else { return null; } }; // Parse a block of name:value fields. The `fields` object is used as the basis for the return value $tw.utils.parseFields = function(text,fields) { text.split("\n").forEach(function(line) { var p = line.indexOf(":"); if(p !== -1) { var field = line.substr(0, p).trim(), value = line.substr(p+1).trim(); fields[field] = value; } }); return fields; }; /* Resolves a source filepath delimited with `/` relative to a specified absolute root filepath. In relative paths, the special folder name `..` refers to immediate parent directory, and the name `.` refers to the current directory */ $tw.utils.resolvePath = function(sourcepath,rootpath) { // If the source path starts with ./ or ../ then it is relative to the root if(sourcepath.substr(0,2) === "./" || sourcepath.substr(0,3) === "../" ) { var src = sourcepath.split("/"), root = rootpath.split("/"); // Remove the filename part of the root root.splice(root.length-1,1); // Process the source path bit by bit onto the end of the root path while(src.length > 0) { var c = src.shift(); if(c === "..") { // Slice off the last root entry for a double dot if(root.length > 0) { root.splice(root.length-1,1); } } else if(c !== ".") { // Ignore dots root.push(c); // Copy other elements across } } return root.join("/"); } else { // If it isn't relative, just return the path return sourcepath; } }; /////////////////////////// Plugin mechanism /* Register a single plugin module in the $tw.plugins hashmap */ $tw.registerPlugin = function(name,moduleType,moduleExports) { if(!(moduleType in $tw.plugins)) { $tw.plugins[moduleType] = []; } $tw.plugins[moduleType].push(moduleExports); }; /* Register all plugin module tiddlers */ $tw.registerPlugins = function() { for(var title in $tw.wiki.shadows.tiddlers) { var tiddler = $tw.wiki.shadows.getTiddler(title); if(tiddler.fields.type === "application/javascript" && tiddler.fields["module-type"] !== undefined) { $tw.registerPlugin(title,tiddler.fields["module-type"],$tw.executeModule(title)); } } }; /* Get all the plugins of a particular type in a hashmap by their `name` field */ $tw.getPluginsByTypeAsHashmap = function(moduleType,nameField) { nameField = nameField || "name"; var plugins = $tw.plugins[moduleType], results = {}; if(plugins) { for(var t=0; t= 0; i--) { if(attrs[i].specified) { tiddler[attrs[i].name] = attrs[i].value; } } return tiddler; } else { return null; } }, extractModuleTiddler = function(node) { if(node.hasAttribute && node.hasAttribute("data-tiddler-title")) { var text = node.innerHTML, s = text.indexOf("{"), e = text.lastIndexOf("}"); if(s !== -1 && e !== -1) { text = text.substring(s+1,e-1); } var fields = $tw.wiki.deserializeTiddlers("application/javascript",text)[0]; fields.title = node.getAttribute("data-tiddler-title"); return fields; } else { return null; } }, t,tiddlers = []; for(t = 0; t < node.childNodes.length; t++) { var tiddler = extractTextTiddler(node.childNodes[t]); tiddler = tiddler || extractModuleTiddler(node.childNodes[t]); if(tiddler) { tiddlers.push(tiddler); } } return tiddlers; } }); // Install the tiddler deserializer plugin $tw.Wiki.installPlugins(); // Load the JavaScript system tiddlers from the DOM $tw.wiki.shadows.addTiddlers( $tw.wiki.deserializeTiddlers("(DOM)",document.getElementById("pluginModules")) ); // Load the main store tiddlers from the DOM $tw.wiki.addTiddlers( $tw.wiki.deserializeTiddlers("(DOM)",document.getElementById("storeArea")) ); // Load the shadow tiddlers from the DOM $tw.wiki.shadows.addTiddlers( $tw.wiki.deserializeTiddlers("(DOM)",document.getElementById("shadowArea")) ); // End of if($tw.isBrowser) } /////////////////////////// Server definitions if(!$tw.isBrowser) { var fs = require("fs"), path = require("path"), vm = require("vm"); /* Load the tiddlers contained in a particular file (and optionally the accompanying .meta file) */ $tw.loadTiddlersFromFile = function(file,basetitle) { var ext = path.extname(file), fields = { title: basetitle }, extensionInfo = $tw.config.fileExtensions[ext], data = fs.readFileSync(file).toString(extensionInfo ? extensionInfo.encoding : "utf8"), tiddlers = $tw.wiki.deserializeTiddlers(ext,data,fields), metafile = file + ".meta"; if(ext !== ".json" && tiddlers.length === 1 && path.existsSync(metafile)) { var metadata = fs.readFileSync(metafile).toString("utf8"); if(metadata) { tiddlers = [$tw.utils.parseFields(metadata,tiddlers[0])]; } } $tw.wiki.shadows.addTiddlers(tiddlers); }; /* Load all the plugins from the plugins directory */ $tw.loadPlugins = function(filepath,basetitle) { basetitle = basetitle || "$:/plugins"; var stat = fs.statSync(filepath); if(stat.isDirectory()) { var files = fs.readdirSync(filepath); for(var f=0; f