2012-04-30 11:23:03 +00:00
|
|
|
/*\
|
2012-05-03 20:47:16 +00:00
|
|
|
title: $:/core/boot.js
|
2012-04-30 11:23:03 +00:00
|
|
|
type: application/javascript
|
|
|
|
|
|
|
|
The main boot kernel for TiddlyWiki. This single file creates a barebones TW environment that is just
|
2012-08-22 11:33:21 +00:00
|
|
|
sufficient to bootstrap the modules containing the main logic of the application.
|
2012-04-30 11:23:03 +00:00
|
|
|
|
|
|
|
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() {
|
|
|
|
|
2012-05-04 17:49:04 +00:00
|
|
|
/*jslint node: true, browser: true */
|
|
|
|
/*global modules: false, $tw: false */
|
2012-04-30 11:23:03 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
/////////////////////////// Setting up $tw
|
|
|
|
|
|
|
|
// Set up $tw global for the server
|
|
|
|
if(typeof(window) === "undefined" && !global.$tw) {
|
2012-08-22 11:33:21 +00:00
|
|
|
global.$tw = {}; // No `browser` member for the server
|
2012-07-13 17:17:46 +00:00
|
|
|
exports.$tw = $tw;
|
2012-04-30 11:23:03 +00:00
|
|
|
}
|
|
|
|
|
2012-05-02 10:02:47 +00:00
|
|
|
// Boot information
|
|
|
|
$tw.boot = {};
|
|
|
|
|
2012-08-02 13:32:38 +00:00
|
|
|
// Server initialisation
|
2012-08-02 21:24:37 +00:00
|
|
|
var fs, path, vm;
|
2012-08-02 13:32:38 +00:00
|
|
|
if(!$tw.browser) {
|
|
|
|
// Standard node libraries
|
2012-08-02 21:24:37 +00:00
|
|
|
fs = require("fs");
|
|
|
|
path = require("path");
|
|
|
|
vm = require("vm");
|
2012-08-02 13:32:38 +00:00
|
|
|
// System paths and filenames
|
|
|
|
$tw.boot.bootFile = path.basename(module.filename);
|
|
|
|
$tw.boot.bootPath = path.dirname(module.filename);
|
|
|
|
$tw.boot.wikiPath = process.cwd();
|
|
|
|
// Read package info
|
|
|
|
$tw.packageInfo = JSON.parse(fs.readFileSync($tw.boot.bootPath + "/../package.json"));
|
|
|
|
// Check node version number
|
|
|
|
var targetVersion = $tw.packageInfo.engine.node.substr(2).split("."),
|
|
|
|
currVersion = process.version.substr(1).split(".");
|
|
|
|
if(targetVersion[0] > currVersion[0] || targetVersion[1] > currVersion[1] || targetVersion[2] > currVersion[2]) {
|
|
|
|
throw "TiddlyWiki5 requires node.js version " + $tw.packageInfo.engine.node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-04-30 11:23:03 +00:00
|
|
|
// Modules store registers all the modules the system has seen
|
2012-04-30 18:14:39 +00:00
|
|
|
$tw.modules = $tw.modules || {};
|
2012-05-04 17:49:04 +00:00
|
|
|
$tw.modules.titles = $tw.modules.titles || {}; // hashmap by module title of {fn:, exports:, moduleType:}
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.types = $tw.modules.types || {}; // hashmap by module type of array of exports
|
2012-04-30 11:23:03 +00:00
|
|
|
|
|
|
|
// Config object
|
|
|
|
$tw.config = $tw.config || {};
|
|
|
|
|
|
|
|
// Constants
|
2012-06-06 20:42:14 +00:00
|
|
|
$tw.config.root = $tw.config.root || "$:"; // Root for module titles (eg, "$:/core/boot.js")
|
2012-05-04 17:24:54 +00:00
|
|
|
$tw.config.bootModuleSubDir = $tw.config.bootModuleSubDir || "./modules";
|
|
|
|
$tw.config.wikiPluginsSubDir = $tw.config.wikiPluginsSubDir || "./plugins";
|
2012-06-06 19:52:30 +00:00
|
|
|
$tw.config.wikiShadowsSubDir = $tw.config.wikiShadowsSubDir || "./wiki";
|
2012-05-20 14:19:38 +00:00
|
|
|
$tw.config.wikiTiddlersSubDir = $tw.config.wikiTiddlersSubDir || "./tiddlers";
|
2012-04-30 11:23:03 +00:00
|
|
|
|
2012-08-22 11:33:21 +00:00
|
|
|
$tw.config.jsModuleHeaderRegExpString = "^\\/\\*\\\\\\n((?:^[^\\n]*\\n)+?)(^\\\\\\*\\/$\\n?)"
|
|
|
|
|
2012-06-08 10:47:05 +00:00
|
|
|
// File extension mappings
|
|
|
|
$tw.config.fileExtensionInfo = {
|
|
|
|
".tid": {type: "application/x-tiddler"},
|
|
|
|
".tiddler": {type: "application/x-tiddler-html-div"},
|
|
|
|
".recipe": {type: "application/x-tiddlywiki-recipe"},
|
|
|
|
".txt": {type: "text/plain"},
|
|
|
|
".css": {type: "text/css"},
|
|
|
|
".html": {type: "text/html"},
|
|
|
|
".js": {type: "application/javascript"},
|
|
|
|
".json": {type: "application/json"},
|
|
|
|
".jpg": {type: "image/jpeg"},
|
|
|
|
".jpeg": {type: "image/jpeg"},
|
|
|
|
".png": {type: "image/png"},
|
|
|
|
".gif": {type: "image/gif"},
|
|
|
|
".svg": {type: "image/svg+xml"}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Content type mappings
|
|
|
|
$tw.config.contentTypeInfo = {
|
|
|
|
"application/x-tiddler": {encoding: "utf8"},
|
|
|
|
"application/x-tiddler-html-div": {encoding: "utf8"},
|
|
|
|
"application/x-tiddlywiki-recipe": {encoding: "utf8"},
|
|
|
|
"text/plain": {encoding: "utf8"},
|
|
|
|
"text/css": {encoding: "utf8"},
|
|
|
|
"text/html": {encoding: "utf8"},
|
|
|
|
"application/javascript": {encoding: "utf8"},
|
|
|
|
"application/json": {encoding: "utf8"},
|
|
|
|
"image/jpeg": {encoding: "base64"},
|
|
|
|
"image/png": {encoding: "base64"},
|
|
|
|
"image/gif": {encoding: "base64"},
|
|
|
|
"image/svg+xml": {encoding: "utf8"}
|
2012-04-30 11:23:03 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/////////////////////////// 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]";
|
2012-05-04 17:49:04 +00:00
|
|
|
};
|
2012-04-30 11:23:03 +00:00
|
|
|
|
|
|
|
// 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();
|
2012-05-04 17:49:04 +00:00
|
|
|
if(s.length < length) {
|
2012-04-30 11:23:03 +00:00
|
|
|
s = "000000000000000000000000000".substr(0,length - s.length) + s;
|
2012-05-04 17:49:04 +00:00
|
|
|
}
|
2012-04-30 11:23:03 +00:00
|
|
|
return s;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Convert a date into YYYYMMDDHHMM format
|
|
|
|
$tw.utils.stringifyDate = function(value) {
|
|
|
|
return value.getUTCFullYear() +
|
2012-05-05 13:17:51 +00:00
|
|
|
$tw.utils.pad(value.getUTCMonth() + 1) +
|
|
|
|
$tw.utils.pad(value.getUTCDate()) +
|
|
|
|
$tw.utils.pad(value.getUTCHours()) +
|
|
|
|
$tw.utils.pad(value.getUTCMinutes());
|
2012-04-30 11:23:03 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// 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") {
|
2012-05-09 12:48:34 +00:00
|
|
|
var memberRegExp = /(?:\[\[([^\]]+)\]\])|([^\s]+)/mg,
|
2012-04-30 11:23:03 +00:00
|
|
|
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) {
|
2012-05-09 12:48:34 +00:00
|
|
|
text.split(/\r?\n/mg).forEach(function(line) {
|
2012-04-30 11:23:03 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-08-03 14:09:48 +00:00
|
|
|
/////////////////////////// Module mechanism
|
2012-04-30 11:23:03 +00:00
|
|
|
|
|
|
|
/*
|
2012-08-03 14:09:48 +00:00
|
|
|
Register a single module in the $tw.modules.types hashmap
|
2012-04-30 11:23:03 +00:00
|
|
|
*/
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.registerTypedModule = function(name,moduleType,moduleExports) {
|
|
|
|
if(!(moduleType in $tw.modules.types)) {
|
|
|
|
$tw.modules.types[moduleType] = [];
|
2012-04-30 11:23:03 +00:00
|
|
|
}
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.types[moduleType].push(moduleExports);
|
2012-04-30 11:23:03 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
2012-08-03 14:09:48 +00:00
|
|
|
Register all the module tiddlers that have a module type
|
2012-04-30 11:23:03 +00:00
|
|
|
*/
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.registerTypedModules = function() {
|
2012-08-06 21:34:16 +00:00
|
|
|
var title, tiddler;
|
|
|
|
// Execute and register any modules from plugins
|
|
|
|
for(title in $tw.wiki.pluginTiddlers) {
|
|
|
|
tiddler = $tw.wiki.getTiddler(title);
|
|
|
|
if(!(title in $tw.wiki.tiddlers)) {
|
|
|
|
if(tiddler.fields.type === "application/javascript" && tiddler.fields["module-type"] !== undefined) {
|
|
|
|
// Execute the module
|
|
|
|
var source = [
|
|
|
|
"(function(module,exports,require) {",
|
|
|
|
tiddler.fields.text,
|
|
|
|
"})"
|
|
|
|
];
|
|
|
|
$tw.modules.define(tiddler.fields.text,tiddler.fields["module-type"],window.eval(source.join("")));
|
|
|
|
// Register the module
|
|
|
|
$tw.modules.registerTypedModule(title,tiddler.fields["module-type"],$tw.modules.execute(title));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Register any modules in ordinary tiddlers
|
|
|
|
for(title in $tw.wiki.tiddlers) {
|
|
|
|
tiddler = $tw.wiki.getTiddler(title);
|
2012-04-30 11:23:03 +00:00
|
|
|
if(tiddler.fields.type === "application/javascript" && tiddler.fields["module-type"] !== undefined) {
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.registerTypedModule(title,tiddler.fields["module-type"],$tw.modules.execute(title));
|
2012-04-30 11:23:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
2012-08-03 14:09:48 +00:00
|
|
|
Get all the modules of a particular type in a hashmap by their `name` field
|
2012-04-30 11:23:03 +00:00
|
|
|
*/
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.getModulesByTypeAsHashmap = function(moduleType,nameField) {
|
2012-04-30 11:23:03 +00:00
|
|
|
nameField = nameField || "name";
|
2012-08-03 14:09:48 +00:00
|
|
|
var modules = $tw.modules.types[moduleType],
|
2012-04-30 11:23:03 +00:00
|
|
|
results = {};
|
2012-08-03 14:09:48 +00:00
|
|
|
if(modules) {
|
|
|
|
for(var t=0; t<modules.length; t++) {
|
|
|
|
results[modules[t][nameField]] = modules[t];
|
2012-04-30 11:23:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return results;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
2012-08-03 14:09:48 +00:00
|
|
|
Apply the exports of the modules of a particular type to a target object
|
2012-04-30 11:23:03 +00:00
|
|
|
*/
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.applyMethods = function(moduleType,object) {
|
|
|
|
var modules = $tw.modules.types[moduleType],
|
2012-04-30 11:23:03 +00:00
|
|
|
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],
|
2012-06-06 11:20:48 +00:00
|
|
|
src = (arg instanceof $tw.Tiddler) ? arg.fields : arg;
|
2012-04-30 11:23:03 +00:00
|
|
|
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 {
|
2012-08-03 14:09:48 +00:00
|
|
|
// Parse the field with the associated field module (if any)
|
|
|
|
var fieldModule = $tw.Tiddler.fieldModules[t];
|
|
|
|
if(fieldModule) {
|
|
|
|
this.fields[t] = fieldModule.parse.call(this,src[t]);
|
2012-04-30 11:23:03 +00:00
|
|
|
} else {
|
|
|
|
this.fields[t] = src[t];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
2012-08-03 14:09:48 +00:00
|
|
|
Hashmap of field modules by field name
|
2012-04-30 11:23:03 +00:00
|
|
|
*/
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.Tiddler.fieldModules = {};
|
2012-04-30 11:23:03 +00:00
|
|
|
|
|
|
|
/*
|
2012-08-03 14:09:48 +00:00
|
|
|
Register and install the built in tiddler field modules
|
2012-04-30 11:23:03 +00:00
|
|
|
*/
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.registerTypedModule($tw.config.root + "/kernel/tiddlerfields/modified","tiddlerfield",{
|
2012-04-30 11:23:03 +00:00
|
|
|
name: "modified",
|
|
|
|
parse: $tw.utils.parseDate,
|
|
|
|
stringify: $tw.utils.stringifyDate
|
|
|
|
});
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.registerTypedModule($tw.config.root + "/kernel/tiddlerfields/created","tiddlerfield",{
|
2012-04-30 11:23:03 +00:00
|
|
|
name: "created",
|
|
|
|
parse: $tw.utils.parseDate,
|
|
|
|
stringify: $tw.utils.stringifyDate
|
|
|
|
});
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.registerTypedModule($tw.config.root + "/kernel/tiddlerfields/tags","tiddlerfield",{
|
2012-04-30 11:23:03 +00:00
|
|
|
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(" ");
|
|
|
|
}
|
|
|
|
});
|
2012-08-03 14:09:48 +00:00
|
|
|
// Install built in tiddler fields module so that they are available immediately
|
|
|
|
$tw.Tiddler.fieldModules = $tw.modules.getModulesByTypeAsHashmap("tiddlerfield");
|
2012-04-30 11:23:03 +00:00
|
|
|
|
|
|
|
/////////////////////////// Barebones wiki store
|
|
|
|
|
|
|
|
/*
|
2012-06-06 11:07:33 +00:00
|
|
|
Construct a wiki store object
|
2012-04-30 11:23:03 +00:00
|
|
|
*/
|
2012-06-06 11:07:33 +00:00
|
|
|
$tw.Wiki = function() {
|
2012-04-30 11:23:03 +00:00
|
|
|
this.tiddlers = {};
|
|
|
|
};
|
|
|
|
|
2012-06-06 12:21:20 +00:00
|
|
|
$tw.Wiki.prototype.addTiddler = function(tiddler,isShadow) {
|
2012-04-30 11:23:03 +00:00
|
|
|
if(!(tiddler instanceof $tw.Tiddler)) {
|
|
|
|
tiddler = new $tw.Tiddler(tiddler);
|
|
|
|
}
|
2012-06-06 12:21:20 +00:00
|
|
|
if(isShadow) {
|
|
|
|
tiddler.isShadow = true;
|
|
|
|
}
|
|
|
|
this.tiddlers[tiddler.fields.title] = tiddler;
|
2012-04-30 11:23:03 +00:00
|
|
|
};
|
|
|
|
|
2012-06-06 12:21:20 +00:00
|
|
|
$tw.Wiki.prototype.addTiddlers = function(tiddlers,isShadow) {
|
2012-04-30 11:23:03 +00:00
|
|
|
for(var t=0; t<tiddlers.length; t++) {
|
2012-06-06 12:21:20 +00:00
|
|
|
this.addTiddler(tiddlers[t],isShadow);
|
2012-04-30 11:23:03 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-08-06 21:34:16 +00:00
|
|
|
/*
|
|
|
|
Install tiddlers contained in plugin tiddlers
|
|
|
|
*/
|
|
|
|
$tw.Wiki.prototype.installPlugins = function() {
|
|
|
|
this.plugins = {}; // Hashmap of plugin information by title
|
|
|
|
this.pluginTiddlers = {}; // Hashmap of constituent tiddlers from plugins by title
|
|
|
|
// Collect up all the plugin tiddlers
|
|
|
|
for(var title in this.tiddlers) {
|
|
|
|
var tiddler = this.tiddlers[title];
|
|
|
|
if(tiddler.fields.type === "application/x-tiddlywiki-plugin") {
|
|
|
|
// Save the plugin information
|
|
|
|
var pluginInfo = this.plugins[title] = JSON.parse(tiddler.fields.text);
|
|
|
|
// Extract the constituent tiddlers
|
|
|
|
for(var t=0; t<pluginInfo.tiddlers.length; t++) {
|
|
|
|
var constituentTiddler = pluginInfo.tiddlers[t];
|
|
|
|
// Don't overwrite tiddlers that already exist
|
|
|
|
if(!(constituentTiddler.title in this.pluginTiddlers)) {
|
|
|
|
// Save the tiddler object
|
|
|
|
this.pluginTiddlers[constituentTiddler.title] = new $tw.Tiddler(constituentTiddler);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-04-30 11:23:03 +00:00
|
|
|
$tw.Wiki.prototype.getTiddler = function(title) {
|
|
|
|
var t = this.tiddlers[title];
|
|
|
|
if(t instanceof $tw.Tiddler) {
|
|
|
|
return t;
|
2012-08-06 21:34:16 +00:00
|
|
|
} else if(title in this.pluginTiddlers) {
|
|
|
|
return this.pluginTiddlers[title];
|
2012-04-30 11:23:03 +00:00
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
2012-08-03 14:09:48 +00:00
|
|
|
Hashmap of field modules by serializer name
|
2012-04-30 11:23:03 +00:00
|
|
|
*/
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.Wiki.tiddlerDeserializerModules = {};
|
2012-04-30 11:23:03 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
Extracts tiddlers from a typed block of text, specifying default field values
|
|
|
|
*/
|
|
|
|
$tw.Wiki.prototype.deserializeTiddlers = function(type,text,srcFields) {
|
|
|
|
srcFields = srcFields || {};
|
2012-08-03 14:09:48 +00:00
|
|
|
var deserializer = $tw.Wiki.tiddlerDeserializerModules[type],
|
2012-04-30 11:23:03 +00:00
|
|
|
fields = {};
|
2012-06-08 10:47:05 +00:00
|
|
|
if(!deserializer && $tw.config.fileExtensionInfo[type]) {
|
2012-04-30 11:23:03 +00:00
|
|
|
// If we didn't find the serializer, try converting it from an extension to a content type
|
2012-06-08 10:47:05 +00:00
|
|
|
type = $tw.config.fileExtensionInfo[type].type;
|
2012-08-03 14:09:48 +00:00
|
|
|
deserializer = $tw.Wiki.tiddlerDeserializerModules[type];
|
2012-04-30 11:23:03 +00:00
|
|
|
}
|
2012-05-05 13:17:51 +00:00
|
|
|
if(!deserializer) {
|
|
|
|
// If we still don't have a deserializer, treat it as plain text
|
2012-08-03 14:09:48 +00:00
|
|
|
deserializer = $tw.Wiki.tiddlerDeserializerModules["text/plain"];
|
2012-05-05 13:17:51 +00:00
|
|
|
}
|
2012-04-30 11:23:03 +00:00
|
|
|
for(var f in srcFields) {
|
2012-05-04 17:49:04 +00:00
|
|
|
fields[f] = srcFields[f];
|
|
|
|
}
|
2012-04-30 11:23:03 +00:00
|
|
|
if(deserializer) {
|
2012-05-03 20:47:16 +00:00
|
|
|
return deserializer.call(this,text,fields);
|
2012-04-30 11:23:03 +00:00
|
|
|
} else {
|
|
|
|
// Return a raw tiddler for unknown types
|
|
|
|
fields.text = text;
|
|
|
|
return [fields];
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
2012-08-03 14:09:48 +00:00
|
|
|
Register the built in tiddler deserializer modules
|
2012-04-30 11:23:03 +00:00
|
|
|
*/
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.registerTypedModule($tw.config.root + "/kernel/tiddlerdeserializer/js","tiddlerdeserializer",{
|
2012-05-03 20:47:16 +00:00
|
|
|
"application/javascript": function(text,fields) {
|
2012-08-22 11:33:21 +00:00
|
|
|
var headerCommentRegExp = new RegExp($tw.config.jsModuleHeaderRegExpString,"mg"),
|
2012-04-30 11:23:03 +00:00
|
|
|
match = headerCommentRegExp.exec(text);
|
|
|
|
fields.text = text;
|
|
|
|
if(match) {
|
2012-05-09 12:48:34 +00:00
|
|
|
fields = $tw.utils.parseFields(match[1].split(/\r?\n\r?\n/mg)[0],fields);
|
2012-04-30 11:23:03 +00:00
|
|
|
}
|
|
|
|
return [fields];
|
|
|
|
}
|
|
|
|
});
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.registerTypedModule($tw.config.root + "/kernel/tiddlerdeserializer/tid","tiddlerdeserializer",{
|
2012-05-03 20:47:16 +00:00
|
|
|
"application/x-tiddler": function(text,fields) {
|
2012-05-09 12:48:34 +00:00
|
|
|
var split = text.split(/\r?\n\r?\n/mg);
|
|
|
|
if(split.length > 1) {
|
|
|
|
fields = $tw.utils.parseFields(split[0],fields);
|
|
|
|
fields.text = split.slice(1).join("\n\n");
|
2012-04-30 11:23:03 +00:00
|
|
|
} else {
|
|
|
|
fields.text = text;
|
|
|
|
}
|
|
|
|
return [fields];
|
|
|
|
}
|
|
|
|
});
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.registerTypedModule($tw.config.root + "/kernel/tiddlerdeserializer/txt","tiddlerdeserializer",{
|
2012-06-06 20:42:14 +00:00
|
|
|
"text/plain": function(text,fields) {
|
|
|
|
fields.text = text;
|
|
|
|
fields.type = "text/plain";
|
|
|
|
return [fields];
|
|
|
|
}
|
|
|
|
});
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.registerTypedModule($tw.config.root + "/kernel/tiddlerdeserializer/html","tiddlerdeserializer",{
|
2012-06-06 20:42:14 +00:00
|
|
|
"text/html": function(text,fields) {
|
|
|
|
fields.text = text;
|
|
|
|
fields.type = "text/html";
|
|
|
|
return [fields];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2012-08-03 14:09:48 +00:00
|
|
|
// Install the tiddler deserializer modules so they are immediately available
|
|
|
|
$tw.modules.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerModules);
|
2012-04-30 11:23:03 +00:00
|
|
|
|
|
|
|
/////////////////////////// Intermediate initialisation
|
|
|
|
|
|
|
|
/*
|
|
|
|
Create the wiki store for the app
|
|
|
|
*/
|
|
|
|
$tw.wiki = new $tw.Wiki();
|
|
|
|
|
|
|
|
/////////////////////////// Browser definitions
|
|
|
|
|
2012-05-19 10:29:51 +00:00
|
|
|
if($tw.browser) {
|
2012-04-30 11:23:03 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
Execute the module named 'moduleName'. The name can optionally be relative to the module named 'moduleRoot'
|
|
|
|
*/
|
2012-04-30 18:14:39 +00:00
|
|
|
$tw.modules.execute = function(moduleName,moduleRoot) {
|
2012-04-30 11:23:03 +00:00
|
|
|
var name = moduleRoot ? $tw.utils.resolvePath(moduleName,moduleRoot) : moduleName,
|
|
|
|
require = function(modRequire) {
|
2012-04-30 18:14:39 +00:00
|
|
|
return $tw.modules.execute(modRequire,name);
|
2012-04-30 11:23:03 +00:00
|
|
|
},
|
|
|
|
exports = {},
|
2012-04-30 18:14:39 +00:00
|
|
|
module = $tw.modules.titles[name];
|
2012-04-30 11:23:03 +00:00
|
|
|
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
|
|
|
|
*/
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.modules.registerTypedModule($tw.config.root + "/kernel/tiddlerdeserializer/dom","tiddlerdeserializer",{
|
2012-05-03 20:47:16 +00:00
|
|
|
"(DOM)": function(node) {
|
2012-04-30 11:23:03 +00:00
|
|
|
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("}");
|
2012-05-05 16:42:42 +00:00
|
|
|
if(node.hasAttribute("data-module") && s !== -1 && e !== -1) {
|
2012-07-10 22:18:07 +00:00
|
|
|
text = text.substring(s+1,e);
|
2012-04-30 11:23:03 +00:00
|
|
|
}
|
2012-05-05 16:42:42 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2012-04-30 11:23:03 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
});
|
2012-08-03 14:09:48 +00:00
|
|
|
// Install the tiddler deserializer modules
|
|
|
|
$tw.modules.applyMethods("tiddlerdeserializer",$tw.Wiki.tiddlerDeserializerModules);
|
2012-04-30 11:23:03 +00:00
|
|
|
|
|
|
|
// Load the JavaScript system tiddlers from the DOM
|
2012-06-08 09:41:58 +00:00
|
|
|
$tw.wiki.addTiddlers($tw.wiki.deserializeTiddlers("(DOM)",document.getElementById("libraryModules")),true);
|
2012-08-03 14:09:48 +00:00
|
|
|
$tw.wiki.addTiddlers($tw.wiki.deserializeTiddlers("(DOM)",document.getElementById("modules")),true);
|
2012-06-06 12:21:20 +00:00
|
|
|
$tw.wiki.addTiddlers($tw.wiki.deserializeTiddlers("(DOM)",document.getElementById("bootKernelPrefix")),true);
|
|
|
|
$tw.wiki.addTiddlers($tw.wiki.deserializeTiddlers("(DOM)",document.getElementById("bootKernel")),true);
|
2012-05-06 13:14:27 +00:00
|
|
|
// Load the stylesheet tiddlers from the DOM
|
2012-06-06 12:21:20 +00:00
|
|
|
$tw.wiki.addTiddlers($tw.wiki.deserializeTiddlers("(DOM)",document.getElementById("styleArea")),true);
|
2012-04-30 11:23:03 +00:00
|
|
|
// Load the main store tiddlers from the DOM
|
2012-06-06 12:21:20 +00:00
|
|
|
$tw.wiki.addTiddlers($tw.wiki.deserializeTiddlers("(DOM)",document.getElementById("storeArea")));
|
2012-04-30 11:23:03 +00:00
|
|
|
// Load the shadow tiddlers from the DOM
|
2012-06-06 12:21:20 +00:00
|
|
|
$tw.wiki.addTiddlers($tw.wiki.deserializeTiddlers("(DOM)",document.getElementById("shadowArea")),true);
|
2012-04-30 11:23:03 +00:00
|
|
|
|
2012-05-19 10:29:51 +00:00
|
|
|
// End of if($tw.browser)
|
2012-04-30 11:23:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////// Server definitions
|
|
|
|
|
2012-05-19 10:29:51 +00:00
|
|
|
if(!$tw.browser) {
|
2012-04-30 11:23:03 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
Load the tiddlers contained in a particular file (and optionally the accompanying .meta file)
|
|
|
|
*/
|
2012-06-06 12:21:20 +00:00
|
|
|
$tw.loadTiddlersFromFile = function(file,fields,isShadow) {
|
2012-04-30 11:23:03 +00:00
|
|
|
var ext = path.extname(file),
|
2012-06-08 10:47:05 +00:00
|
|
|
extensionInfo = $tw.config.fileExtensionInfo[ext],
|
|
|
|
typeInfo = extensionInfo ? $tw.config.contentTypeInfo[extensionInfo.type] : null,
|
|
|
|
data = fs.readFileSync(file).toString(typeInfo ? typeInfo.encoding : "utf8"),
|
2012-04-30 11:23:03 +00:00
|
|
|
tiddlers = $tw.wiki.deserializeTiddlers(ext,data,fields),
|
|
|
|
metafile = file + ".meta";
|
2012-07-13 16:08:15 +00:00
|
|
|
if(ext !== ".json" && tiddlers.length === 1 && fs.existsSync(metafile)) {
|
2012-04-30 11:23:03 +00:00
|
|
|
var metadata = fs.readFileSync(metafile).toString("utf8");
|
|
|
|
if(metadata) {
|
|
|
|
tiddlers = [$tw.utils.parseFields(metadata,tiddlers[0])];
|
|
|
|
}
|
|
|
|
}
|
2012-06-06 12:21:20 +00:00
|
|
|
$tw.wiki.addTiddlers(tiddlers,isShadow);
|
2012-04-30 11:23:03 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
2012-08-03 14:09:48 +00:00
|
|
|
Load all the tiddlers from a directory
|
2012-04-30 11:23:03 +00:00
|
|
|
*/
|
2012-06-06 12:21:20 +00:00
|
|
|
$tw.loadTiddlersFromFolder = function(filepath,basetitle,excludeRegExp,isShadow) {
|
2012-04-30 11:23:03 +00:00
|
|
|
basetitle = basetitle || "$:/plugins";
|
2012-05-03 20:47:16 +00:00
|
|
|
excludeRegExp = excludeRegExp || /^\.DS_Store$|.meta$/;
|
2012-07-13 16:08:15 +00:00
|
|
|
if(fs.existsSync(filepath)) {
|
2012-05-04 17:24:54 +00:00
|
|
|
var stat = fs.statSync(filepath);
|
|
|
|
if(stat.isDirectory()) {
|
|
|
|
var files = fs.readdirSync(filepath);
|
2012-05-05 10:21:59 +00:00
|
|
|
// 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++) {
|
2012-06-08 10:47:05 +00:00
|
|
|
var tidInfo = pluginInfo.tiddlers[p],
|
|
|
|
typeInfo = $tw.config.contentTypeInfo[tidInfo.fields.type || "text/plain"],
|
|
|
|
text = fs.readFileSync(path.resolve(filepath,tidInfo.file)).toString(typeInfo ? typeInfo.encoding : "utf8");
|
|
|
|
$tw.wiki.addTiddler(new $tw.Tiddler({text: text},tidInfo.fields),isShadow);
|
2012-05-05 10:21:59 +00:00
|
|
|
}
|
|
|
|
} 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)) {
|
2012-06-06 12:21:20 +00:00
|
|
|
$tw.loadTiddlersFromFolder(filepath + "/" + file,basetitle + "/" + file,excludeRegExp,isShadow);
|
2012-05-05 10:21:59 +00:00
|
|
|
}
|
2012-05-04 17:24:54 +00:00
|
|
|
}
|
2012-04-30 11:23:03 +00:00
|
|
|
}
|
2012-05-04 17:24:54 +00:00
|
|
|
} else if(stat.isFile()) {
|
2012-06-06 12:21:20 +00:00
|
|
|
$tw.loadTiddlersFromFile(filepath,{title: basetitle},isShadow);
|
2012-04-30 11:23:03 +00:00
|
|
|
}
|
|
|
|
}
|
2012-05-04 17:49:04 +00:00
|
|
|
};
|
2012-04-30 11:23:03 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
Execute the module named 'moduleName'. The name can optionally be relative to the module named 'moduleRoot'
|
|
|
|
*/
|
2012-04-30 18:14:39 +00:00
|
|
|
$tw.modules.execute = function(moduleName,moduleRoot) {
|
2012-04-30 11:23:03 +00:00
|
|
|
var name = moduleRoot ? $tw.utils.resolvePath(moduleName,moduleRoot) : moduleName,
|
2012-04-30 18:14:39 +00:00
|
|
|
module = $tw.modules.titles[name],
|
2012-04-30 11:23:03 +00:00
|
|
|
tiddler = $tw.wiki.getTiddler(name),
|
|
|
|
sandbox = {
|
|
|
|
module: module,
|
|
|
|
exports: {},
|
|
|
|
console: console,
|
2012-05-02 10:02:47 +00:00
|
|
|
process: process,
|
2012-04-30 11:23:03 +00:00
|
|
|
$tw: $tw,
|
|
|
|
require: function(title) {
|
2012-04-30 18:14:39 +00:00
|
|
|
return $tw.modules.execute(title,name);
|
2012-04-30 11:23:03 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
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"]
|
|
|
|
};
|
2012-04-30 18:14:39 +00:00
|
|
|
$tw.modules.titles[name] = module;
|
2012-04-30 11:23:03 +00:00
|
|
|
// Execute it to get its exports if we haven't already done so
|
|
|
|
if(!module.exports) {
|
2012-05-26 22:37:03 +00:00
|
|
|
try {
|
|
|
|
vm.runInNewContext(tiddler.fields.text,sandbox,tiddler.fields.title);
|
|
|
|
} catch(e) {
|
|
|
|
throw "Error executing boot module " + tiddler.fields.title + ":\n" + e;
|
|
|
|
}
|
2012-04-30 11:23:03 +00:00
|
|
|
module.exports = sandbox.exports;
|
|
|
|
}
|
|
|
|
// Return the exports of the module
|
|
|
|
return module.exports;
|
2012-05-04 17:49:04 +00:00
|
|
|
};
|
2012-04-30 11:23:03 +00:00
|
|
|
|
2012-08-03 14:09:48 +00:00
|
|
|
// Load modules from the modules directory
|
2012-06-06 12:21:20 +00:00
|
|
|
$tw.loadTiddlersFromFolder(path.resolve($tw.boot.bootPath,$tw.config.bootModuleSubDir),null,null,true);
|
2012-05-04 17:24:54 +00:00
|
|
|
|
2012-06-06 20:42:14 +00:00
|
|
|
// Load up the shadow tiddlers in the root of the core directory
|
|
|
|
$tw.loadTiddlersFromFolder($tw.boot.bootPath,"$:/core",/^\.DS_Store$|.meta$|^modules$/,true);
|
|
|
|
|
2012-05-04 17:24:54 +00:00
|
|
|
// Load any plugins in the wiki plugins directory
|
2012-06-06 12:21:20 +00:00
|
|
|
$tw.loadTiddlersFromFolder(path.resolve($tw.boot.wikiPath,$tw.config.wikiPluginsSubDir),null,null,true);
|
2012-05-04 16:45:36 +00:00
|
|
|
|
2012-05-05 13:17:51 +00:00
|
|
|
// HACK: to be replaced when we re-establish sync plugins
|
|
|
|
// Load shadow tiddlers from wiki shadows directory
|
2012-06-06 12:21:20 +00:00
|
|
|
$tw.loadTiddlersFromFolder(path.resolve($tw.boot.wikiPath,$tw.config.wikiShadowsSubDir),null,null,true);
|
2012-05-05 13:17:51 +00:00
|
|
|
// Load tiddlers from wiki tiddlers directory
|
2012-06-06 11:07:33 +00:00
|
|
|
$tw.loadTiddlersFromFolder(path.resolve($tw.boot.wikiPath,$tw.config.wikiTiddlersSubDir));
|
2012-05-05 13:17:51 +00:00
|
|
|
|
2012-05-19 10:29:51 +00:00
|
|
|
// End of if(!$tw.browser)
|
2012-04-30 11:23:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////// Final initialisation
|
|
|
|
|
2012-08-06 21:34:16 +00:00
|
|
|
// Install plugins
|
|
|
|
$tw.wiki.installPlugins();
|
|
|
|
|
2012-08-03 14:09:48 +00:00
|
|
|
// Register typed modules from the tiddlers we've just loaded
|
|
|
|
$tw.modules.registerTypedModules();
|
2012-04-30 11:23:03 +00:00
|
|
|
|
2012-08-03 14:09:48 +00:00
|
|
|
// Run any startup modules
|
|
|
|
var mainModules = $tw.modules.types.startup;
|
2012-04-30 11:23:03 +00:00
|
|
|
for(var m=0; m<mainModules.length; m++) {
|
|
|
|
mainModules[m].startup.call($tw);
|
|
|
|
}
|
|
|
|
|
|
|
|
})();
|