Comparing sensitive data, confidential files or internal emails?

Most legal and privacy policies prohibit uploading sensitive data online. Diffchecker Desktop ensures your confidential information never leaves your computer. Work offline and compare documents securely.

TW 5.3.0 vs TD 20

Created Diff never expires
The two files are identical
There is no difference to show between these two files
0 removals
871 lines
0 additions
871 lines
/*\
/*\
title: $:/boot/boot.js
title: $:/boot/boot.js
type: application/javascript
type: application/javascript


The main boot kernel for TiddlyWiki. This single file creates a barebones TW environment that is just sufficient to bootstrap the modules containing the main logic of the application.
The main boot kernel for TiddlyWiki. This single file creates a barebones TW environment that is just sufficient to bootstrap the modules containing the main logic of the application.


On the server this file is executed directly to boot TiddlyWiki. In the browser, this file is packed into a single HTML file.
On the server this file is executed directly to boot TiddlyWiki. In the browser, this file is packed into a single HTML file.


\*/
\*/


var _boot = (function($tw) {
var _boot = (function($tw) {


/*jslint node: true, browser: true */
/*jslint node: true, browser: true */
/*global modules: false, $tw: false */
/*global modules: false, $tw: false */
"use strict";
"use strict";


// Include bootprefix if we're not given module data
// Include bootprefix if we're not given module data
if(!$tw) {
if(!$tw) {
$tw = require("./bootprefix.js").bootprefix();
$tw = require("./bootprefix.js").bootprefix();
}
}


$tw.utils = $tw.utils || Object.create(null);
$tw.utils = $tw.utils || Object.create(null);


/////////////////////////// Standard node.js libraries
/////////////////////////// Standard node.js libraries


var fs, path, vm;
var fs, path, vm;
if($tw.node) {
if($tw.node) {
fs = require("fs");
fs = require("fs");
path = require("path");
path = require("path");
vm = require("vm");
vm = require("vm");
}
}


/////////////////////////// Utility functions
/////////////////////////// Utility functions


$tw.boot.log = function(str) {
$tw.boot.log = function(str) {
$tw.boot.logMessages = $tw.boot.logMessages || [];
$tw.boot.logMessages = $tw.boot.logMessages || [];
$tw.boot.logMessages.push(str);
$tw.boot.logMessages.push(str);
}
}


/*
/*
Check if an object has a property
Check if an object has a property
*/
*/
$tw.utils.hop = function(object,property) {
$tw.utils.hop = function(object,property) {
return object ? Object.prototype.hasOwnProperty.call(object,property) : false;
return object ? Object.prototype.hasOwnProperty.call(object,property) : false;
};
};


/*
/*
Determine if a value is an array
Determine if a value is an array
*/
*/
$tw.utils.isArray = function(value) {
$tw.utils.isArray = function(value) {
return Object.prototype.toString.call(value) == "[object Array]";
return Object.prototype.toString.call(value) == "[object Array]";
};
};


/*
/*
Check if an array is equal by value and by reference.
Check if an array is equal by value and by reference.
*/
*/
$tw.utils.isArrayEqual = function(array1,array2) {
$tw.utils.isArrayEqual = function(array1,array2) {
if(array1 === array2) {
if(array1 === array2) {
return true;
return true;
}
}
array1 = array1 || [];
array1 = array1 || [];
array2 = array2 || [];
array2 = array2 || [];
if(array1.length !== array2.length) {
if(array1.length !== array2.length) {
return false;
return false;
}
}
return array1.every(function(value,index) {
return array1.every(function(value,index) {
return value === array2[index];
return value === array2[index];
});
});
};
};


/*
/*
Add an entry to a sorted array if it doesn't already exist, while maintaining the sort order
Add an entry to a sorted array if it doesn't already exist, while maintaining the sort order
*/
*/
$tw.utils.insertSortedArray = function(array,value) {
$tw.utils.insertSortedArray = function(array,value) {
var low = 0, high = array.length - 1, mid, cmp;
var low = 0, high = array.length - 1, mid, cmp;
while(low <= high) {
while(low <= high) {
mid = (low + high) >> 1;
mid = (low + high) >> 1;
cmp = value.localeCompare(array[mid]);
cmp = value.localeCompare(array[mid]);
if(cmp > 0) {
if(cmp > 0) {
low = mid + 1;
low = mid + 1;
} else if(cmp < 0) {
} else if(cmp < 0) {
high = mid - 1;
high = mid - 1;
} else {
} else {
return array;
return array;
}
}
}
}
array.splice(low,0,value);
array.splice(low,0,value);
return array;
return array;
};
};


/*
/*
Push entries onto an array, removing them first if they already exist in the array
Push entries onto an array, removing them first if they already exist in the array
array: array to modify (assumed to be free of duplicates)
array: array to modify (assumed to be free of duplicates)
value: a single value to push or an array of values to push
value: a single value to push or an array of values to push
*/
*/
$tw.utils.pushTop = function(array,value) {
$tw.utils.pushTop = function(array,value) {
var t,p;
var t,p;
if($tw.utils.isArray(value)) {
if($tw.utils.isArray(value)) {
// Remove any array entries that are duplicated in the new values
// Remove any array entries that are duplicated in the new values
if(value.length !== 0) {
if(value.length !== 0) {
if(array.length !== 0) {
if(array.length !== 0) {
if(value.length < array.length) {
if(value.length < array.length) {
for(t=0; t<value.length; t++) {
for(t=0; t<value.length; t++) {
p = array.indexOf(value[t]);
p = array.indexOf(value[t]);
if(p !== -1) {
if(p !== -1) {
array.splice(p,1);
array.splice(p,1);
}
}
}
}
} else {
} else {
for(t=array.length-1; t>=0; t--) {
for(t=array.length-1; t>=0; t--) {
p = value.indexOf(array[t]);
p = value.indexOf(array[t]);
if(p !== -1) {
if(p !== -1) {
array.splice(t,1);
array.splice(t,1);
}
}
}
}
}
}
}
}
// Push the values on top of the main array
// Push the values on top of the main array
array.push.apply(array,value);
array.push.apply(array,value);
}
}
} else {
} else {
p = array.indexOf(value);
p = array.indexOf(value);
if(p !== -1) {
if(p !== -1) {
array.splice(p,1);
array.splice(p,1);
}
}
array.push(value);
array.push(value);
}
}
return array;
return array;
};
};


/*
/*
Determine if a value is a date
Determine if a value is a date
*/
*/
$tw.utils.isDate = function(value) {
$tw.utils.isDate = function(value) {
return Object.prototype.toString.call(value) === "[object Date]";
return Object.prototype.toString.call(value) === "[object Date]";
};
};


/*
/*
Iterate through all the own properties of an object or array. Callback is invoked with (element,title,object)
Iterate through all the own properties of an object or array. Callback is invoked with (element,title,object)
*/
*/
$tw.utils.each = function(object,callback) {
$tw.utils.each = function(object,callback) {
var next,f,length;
var next,f,length;
if(object) {
if(object) {
if(Object.prototype.toString.call(object) == "[object Array]") {
if(Object.prototype.toString.call(object) == "[object Array]") {
for (f=0, length=object.length; f<length; f++) {
for (f=0, length=object.length; f<length; f++) {
next = callback(object[f],f,object);
next = callback(object[f],f,object);
if(next === false) {
if(next === false) {
break;
break;
}
}
}
}
} else {
} else {
var keys = Object.keys(object);
var keys = Object.keys(object);
for (f=0, length=keys.length; f<length; f++) {
for (f=0, length=keys.length; f<length; f++) {
var key = keys[f];
var key = keys[f];
next = callback(object[key],key,object);
next = callback(object[key],key,object);
if(next === false) {
if(next === false) {
break;
break;
}
}
}
}
}
}
}
}
};
};


/*
/*
Helper for making DOM elements
Helper for making DOM elements
tag: tag name
tag: tag name
options: see below
options: see below
Options include:
Options include:
namespace: defaults to http://www.w3.org/1999/xhtml
namespace: defaults to http://www.w3.org/1999/xhtml
attributes: hashmap of attribute values
attributes: hashmap of attribute values
style: hashmap of styles
style: hashmap of styles
text: text to add as a child node
text: text to add as a child node
children: array of further child nodes
children: array of further child nodes
innerHTML: optional HTML for element
innerHTML: optional HTML for element
class: class name(s)
class: class name(s)
document: defaults to current document
document: defaults to current document
eventListeners: array of event listeners (this option won't work until $tw.utils.addEventListeners() has been loaded)
eventListeners: array of event listeners (this option won't work until $tw.utils.addEventListeners() has been loaded)
*/
*/
$tw.utils.domMaker = function(tag,options) {
$tw.utils.domMaker = function(tag,options) {
var doc = options.document || document;
var doc = options.document || document;
var element = doc.createElementNS(options.namespace || "http://www.w3.org/1999/xhtml",tag);
var element = doc.createElementNS(options.namespace || "http://www.w3.org/1999/xhtml",tag);
if(options["class"]) {
if(options["class"]) {
element.className = options["class"];
element.className = options["class"];
}
}
if(options.text) {
if(options.text) {
element.appendChild(doc.createTextNode(options.text));
element.appendChild(doc.createTextNode(options.text));
}
}
$tw.utils.each(options.children,function(child) {
$tw.utils.each(options.children,function(child) {
element.appendChild(child);
element.appendChild(child);
});
});
if(options.innerHTML) {
if(options.innerHTML) {
element.innerHTML = options.innerHTML;
element.innerHTML = options.innerHTML;
}
}
$tw.utils.each(options.attributes,function(attribute,name) {
$tw.utils.each(options.attributes,function(attribute,name) {
element.setAttribute(name,attribute);
element.setAttribute(name,attribute);
});
});
$tw.utils.each(options.style,function(value,name) {
$tw.utils.each(options.style,function(value,name) {
element.style[name] = value;
element.style[name] = value;
});
});
if(options.eventListeners) {
if(options.eventListeners) {
$tw.utils.addEventListeners(element,options.eventListeners);
$tw.utils.addEventListeners(element,options.eventListeners);
}
}
return element;
return element;
};
};


/*
/*
Display an error and exit
Display an error and exit
*/
*/
$tw.utils.error = function(err) {
$tw.utils.error = function(err) {
// Prepare the error message
// Prepare the error message
var errHeading = ( $tw.language == undefined ? "Internal JavaScript Error" : $tw.language.getString("InternalJavaScriptError/Title") ),
var errHeading = ( $tw.language == undefined ? "Internal JavaScript Error" : $tw.language.getString("InternalJavaScriptError/Title") ),
promptMsg = ( $tw.language == undefined ? "Well, this is embarrassing. It is recommended that you restart TiddlyWiki by refreshing your browser" : $tw.language.getString("InternalJavaScriptError/Hint") );
promptMsg = ( $tw.language == undefined ? "Well, this is embarrassing. It is recommended that you restart TiddlyWiki by refreshing your browser" : $tw.language.getString("InternalJavaScriptError/Hint") );
// Log the error to the console
// Log the error to the console
console.error($tw.node ? "\x1b[1;31m" + err + "\x1b[0m" : err);
console.error($tw.node ? "\x1b[1;31m" + err + "\x1b[0m" : err);
if($tw.browser && !$tw.node) {
if($tw.browser && !$tw.node) {
// Display an error message to the user
// Display an error message to the user
var dm = $tw.utils.domMaker,
var dm = $tw.utils.domMaker,
heading = dm("h1",{text: errHeading}),
heading = dm("h1",{text: errHeading}),
prompt = dm("div",{text: promptMsg, "class": "tc-error-prompt"}),
prompt = dm("div",{text: promptMsg, "class": "tc-error-prompt"}),
message = dm("div",{text: err, "class":"tc-error-message"}),
message = dm("div",{text: err, "class":"tc-error-message"}),
button = dm("div",{children: [dm("button",{text: ( $tw.language == undefined ? "close" : $tw.language.getString("Buttons/Close/Caption") )})], "class": "tc-error-prompt"}),
button = dm("div",{children: [dm("button",{text: ( $tw.language == undefined ? "close" : $tw.language.getString("Buttons/Close/Caption") )})], "class": "tc-error-prompt"}),
form = dm("form",{children: [heading,prompt,message,button], "class": "tc-error-form"});
form = dm("form",{children: [heading,prompt,message,button], "class": "tc-error-form"});
document.body.insertBefore(form,document.body.firstChild);
document.body.insertBefore(form,document.body.firstChild);
form.addEventListener("submit",function(event) {
form.addEventListener("submit",function(event) {
document.body.removeChild(form);
document.body.removeChild(form);
event.preventDefault();
event.preventDefault();
return false;
return false;
},true);
},true);
return null;
return null;
} else if(!$tw.browser) {
} else if(!$tw.browser) {
// Exit if we're under node.js
// Exit if we're under node.js
process.exit(1);
process.exit(1);
}
}
};
};


/*
/*
Use our custom error handler if we're in the browser
Use our custom error handler if we're in the browser
*/
*/
if($tw.boot.tasks.trapErrors) {
if($tw.boot.tasks.trapErrors) {
window.onerror = function(errorMsg,url,lineNumber) {
window.onerror = function(errorMsg,url,lineNumber) {
$tw.utils.error(errorMsg);
$tw.utils.error(errorMsg);
return false;
return false;
};
};
}
}


/*
/*
Extend an object with the properties from a list of source objects
Extend an object with the properties from a list of source objects
*/
*/
$tw.utils.extend = function(object /*, sourceObjectList */) {
$tw.utils.extend = function(object /*, sourceObjectList */) {
$tw.utils.each(Array.prototype.slice.call(arguments,1),function(source) {
$tw.utils.each(Array.prototype.slice.call(arguments,1),function(source) {
if(source) {
if(source) {
for (var p in source) {
for (var p in source) {
object[p] = source[p];
object[p] = source[p];
}
}
}
}
});
});
return object;
return object;
};
};


/*
/*
Fill in any null or undefined properties of an object with the properties from a list of source objects. Each property that is an object is called recursively
Fill in any null or undefined properties of an object with the properties from a list of source objects. Each property that is an object is called recursively
*/
*/
$tw.utils.deepDefaults = function(object /*, sourceObjectList */) {
$tw.utils.deepDefaults = function(object /*, sourceObjectList */) {
$tw.utils.each(Array.prototype.slice.call(arguments,1),function(source) {
$tw.utils.each(Array.prototype.slice.call(arguments,1),function(source) {
if(source) {
if(source) {
for (var p in source) {
for (var p in source) {
if(object[p] === null || object[p] === undefined) {
if(object[p] === null || object[p] === undefined) {
object[p] = source[p];
object[p] = source[p];
}
}
if(typeof object[p] === "object" && typeof source[p] === "object") {
if(typeof object[p] === "object" && typeof source[p] === "object") {
$tw.utils.deepDefaults(object[p],source[p]);
$tw.utils.deepDefaults(object[p],source[p]);
}
}
}
}
}
}
});
});
return object;
return object;
};
};


/*
/*
Convert a URIComponent encoded string to a string safely
Convert a URIComponent encoded string to a string safely
*/
*/
$tw.utils.decodeURIComponentSafe = function(s) {
$tw.utils.decodeURIComponentSafe = function(s) {
var v = s;
var v = s;
try {
try {
v = decodeURIComponent(s);
v = decodeURIComponent(s);
} catch(e) {}
} catch(e) {}
return v;
return v;
};
};


/*
/*
Convert a URI encoded string to a string safely
Convert a URI encoded string to a string safely
*/
*/
$tw.utils.decodeURISafe = function(s) {
$tw.utils.decodeURISafe = function(s) {
var v = s;
var v = s;
try {
try {
v = decodeURI(s);
v = decodeURI(s);
} catch(e) {}
} catch(e) {}
return v;
return v;
};
};


/*
/*
Convert "&amp;" to &, "&nbsp;" to nbsp, "&lt;" to <, "&gt;" to > and "&quot;" to "
Convert "&amp;" to &, "&nbsp;" to nbsp, "&lt;" to <, "&gt;" to > and "&quot;" to "
*/
*/
$tw.utils.htmlDecode = function(s) {
$tw.utils.htmlDecode = function(s) {
return s.toString().replace(/&lt;/mg,"<").replace(/&nbsp;/mg,"\xA0").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
return s.toString().replace(/&lt;/mg,"<").replace(/&nbsp;/mg,"\xA0").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
};
};


/*
/*
Get the browser location.hash. We don't use location.hash because of the way that Firefox auto-urldecodes it (see http://stackoverflow.com/questions/1703552/encoding-of-window-location-hash)
Get the browser location.hash. We don't use location.hash because of the way that Firefox auto-urldecodes it (see http://stackoverflow.com/questions/1703552/encoding-of-window-location-hash)
*/
*/
$tw.utils.getLocationHash = function() {
$tw.utils.getLocationHash = function() {
var href = window.location.href;
var href = window.location.href;
var idx = href.indexOf('#');
var idx = href.indexOf('#');
if(idx === -1) {
if(idx === -1) {
return "#";
return "#";
} else if(href.substr(idx + 1,1) === "#" || href.substr(idx + 1,3) === "%23") {
} else if(href.substr(idx + 1,1) === "#" || href.substr(idx + 1,3) === "%23") {
// Special case: ignore location hash if it itself starts with a #
// Special case: ignore location hash if it itself starts with a #
return "#";
return "#";
} else {
} else {
return href.substring(idx);
return href.substring(idx);
}
}
};
};


/*
/*
Pad a string to a given length with "0"s. Length defaults to 2
Pad a string to a given length with "0"s. Length defaults to 2
*/
*/
$tw.utils.pad = function(value,length) {
$tw.utils.pad = function(value,length) {
length = length || 2;
length = length || 2;
var s = value.toString();
var s = value.toString();
if(s.length < length) {
if(s.length < length) {
s = "000000000000000000000000000".substr(0,length - s.length) + s;
s = "000000000000000000000000000".substr(0,length - s.length) + s;
}
}
return s;
return s;
};
};


// Convert a date into UTC YYYYMMDDHHMMSSmmm format
// Convert a date into UTC YYYYMMDDHHMMSSmmm format
$tw.utils.stringifyDate = function(value) {
$tw.utils.stringifyDate = function(value) {
return value.getUTCFullYear() +
return value.getUTCFullYear() +
$tw.utils.pad(value.getUTCMonth() + 1) +
$tw.utils.pad(value.getUTCMonth() + 1) +
$tw.utils.pad(value.getUTCDate()) +
$tw.utils.pad(value.getUTCDate()) +
$tw.utils.pad(value.getUTCHours()) +
$tw.utils.pad(value.getUTCHours()) +
$tw.utils.pad(value.getUTCMinutes()) +
$tw.utils.pad(value.getUTCMinutes()) +
$tw.utils.pad(value.getUTCSeconds()) +
$tw.utils.pad(value.getUTCSeconds()) +
$tw.utils.pad(value.getUTCMilliseconds(),3);
$tw.utils.pad(value.getUTCMilliseconds(),3);
};
};


// Parse a date from a UTC YYYYMMDDHHMMSSmmm format string
// Parse a date from a UTC YYYYMMDDHHMMSSmmm format string
$tw.utils.parseDate = function(value) {
$tw.utils.parseDate = function(value) {
if(typeof value === "string") {
if(typeof value === "string") {
var negative = 1;
var negative = 1;
if(value.charAt(0) === "-") {
if(value.charAt(0) === "-") {
negative = -1;
negative = -1;
value = value.substr(1);
value = value.substr(1);
}
}
var year = parseInt(value.substr(0,4),10) * negative,
var year = parseInt(value.substr(0,4),10) * negative,
d = new Date(Date.UTC(year,
d = new Date(Date.UTC(year,
parseInt(value.substr(4,2),10)-1,
parseInt(value.substr(4,2),10)-1,
parseInt(value.substr(6,2),10),
parseInt(value.substr(6,2),10),
parseInt(value.substr(8,2)||"00",10),
parseInt(value.substr(8,2)||"00",10),
parseInt(value.substr(10,2)||"00",10),
parseInt(value.substr(10,2)||"00",10),
parseInt(value.substr(12,2)||"00",10),
parseInt(value.substr(12,2)||"00",10),
parseInt(value.substr(14,3)||"000",10)));
parseInt(value.substr(14,3)||"000",10)));
d.setUTCFullYear(year); // See https://stackoverflow.com/a/5870822
d.setUTCFullYear(year); // See https://stackoverflow.com/a/5870822
return d;
return d;
} else if($tw.utils.isDate(value)) {
} else if($tw.utils.isDate(value)) {
return value;
return value;
} else {
} else {
return null;
return null;
}
}
};
};


// Stringify an array of tiddler titles into a list string
// Stringify an array of tiddler titles into a list string
$tw.utils.stringifyList = function(value) {
$tw.utils.stringifyList = function(value) {
if($tw.utils.isArray(value)) {
if($tw.utils.isArray(value)) {
var result = new Array(value.length);
var result = new Array(value.length);
for(var t=0, l=value.length; t<l; t++) {
for(var t=0, l=value.length; t<l; t++) {
var entry = value[t] || "";
var entry = value[t] || "";
if(entry.match(/[^\S\xA0]/mg)) {
if(entry.match(/[^\S\xA0]/mg)) {
result[t] = "[[" + entry + "]]";
result[t] = "[[" + entry + "]]";
} else {
} else {
result[t] = entry;
result[t] = entry;
}
}
}
}
return result.join(" ");
return result.join(" ");
} else {
} else {
return value || "";
return value || "";
}
}
};
};


// Parse a string array from a bracketted list. For example "OneTiddler [[Another Tiddler]] LastOne"
// Parse a string array from a bracketted list. For example "OneTiddler [[Another Tiddler]] LastOne"
$tw.utils.parseStringArray = function(value, allowDuplicate) {
$tw.utils.parseStringArray = function(value, allowDuplicate) {
if(typeof value === "string") {
if(typeof value === "string") {
var memberRegExp = /(?:^|[^\S\xA0])(?:\[\[(.*?)\]\])(?=[^\S\xA0]|$)|([\S\xA0]+)/mg,
var memberRegExp = /(?:^|[^\S\xA0])(?:\[\[(.*?)\]\])(?=[^\S\xA0]|$)|([\S\xA0]+)/mg,
results = [], names = {},
results = [], names = {},
match;
match;
do {
do {
match = memberRegExp.exec(value);
match = memberRegExp.exec(value);
if(match) {
if(match) {
var item = match[1] || match[2];
var item = match[1] || match[2];
if(item !== undefined && (!$tw.utils.hop(names,item) || allowDuplicate)) {
if(item !== undefined && (!$tw.utils.hop(names,item) || allowDuplicate)) {
results.push(item);
results.push(item);
names[item] = true;
names[item] = true;
}
}
}
}
} while(match);
} while(match);
return results;
return results;
} else if($tw.utils.isArray(value)) {
} else if($tw.utils.isArray(value)) {
return value;
return value;
} else {
} else {
return null;
return null;
}
}
};
};


// Parse a block of name:value fields. The `fields` object is used as the basis for the return value
// 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) {
$tw.utils.parseFields = function(text,fields) {
fields = fields || Object.create(null);
fields = fields || Object.create(null);
text.split(/\r?\n/mg).forEach(function(line) {
text.split(/\r?\n/mg).forEach(function(line) {
if(line.charAt(0) !== "#") {
if(line.charAt(0) !== "#") {
var p = line.indexOf(":");
var p = line.indexOf(":");
if(p !== -1) {
if(p !== -1) {
var field = line.substr(0, p).trim(),
var field = line.substr(0, p).trim(),
value = line.substr(p+1).trim();
value = line.substr(p+1).trim();
if(field) {
if(field) {
fields[field] = value;
fields[field] = value;
}
}
}
}
}
}
});
});
return fields;
return fields;
};
};


// Safely parse a string as JSON
// Safely parse a string as JSON
$tw.utils.parseJSONSafe = function(text,defaultJSON) {
$tw.utils.parseJSONSafe = function(text,defaultJSON) {
try {
try {
return JSON.parse(text);
return JSON.parse(text);
} catch(e) {
} catch(e) {
if(typeof defaultJSON === "function") {
if(typeof defaultJSON === "function") {
return defaultJSON(e);
return defaultJSON(e);
} else {
} else {
return defaultJSON || {};
return defaultJSON || {};
}
}
}
}
};
};


/*
/*
Resolves a source filepath delimited with `/` relative to a specified absolute root filepath.
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
In relative paths, the special folder name `..` refers to immediate parent directory, and the
name `.` refers to the current directory
name `.` refers to the current directory
*/
*/
$tw.utils.resolvePath = function(sourcepath,rootpath) {
$tw.utils.resolvePath = function(sourcepath,rootpath) {
// If the source path starts with ./ or ../ then it is relative to the root
// If the source path starts with ./ or ../ then it is relative to the root
if(sourcepath.substr(0,2) === "./" || sourcepath.substr(0,3) === "../" ) {
if(sourcepath.substr(0,2) === "./" || sourcepath.substr(0,3) === "../" ) {
var src = sourcepath.split("/"),
var src = sourcepath.split("/"),
root = rootpath.split("/");
root = rootpath.split("/");
// Remove the filename part of the root
// Remove the filename part of the root
root.splice(root.length-1,1);
root.splice(root.length-1,1);
// Process the source path bit by bit onto the end of the root path
// Process the source path bit by bit onto the end of the root path
while(src.length > 0) {
while(src.length > 0) {
var c = src.shift();
var c = src.shift();
if(c === "..") { // Slice off the last root entry for a double dot
if(c === "..") { // Slice off the last root entry for a double dot
if(root.length > 0) {
if(root.length > 0) {
root.splice(root.length-1,1);
root.splice(root.length-1,1);
}
}
} else if(c !== ".") { // Ignore dots
} else if(c !== ".") { // Ignore dots
root.push(c); // Copy other elements across
root.push(c); // Copy other elements across
}
}
}
}
return root.join("/");
return root.join("/");
} else {
} else {
// If it isn't relative, just return the path
// If it isn't relative, just return the path
if(rootpath) {
if(rootpath) {
var root = rootpath.split("/");
var root = rootpath.split("/");
// Remove the filename part of the root
// Remove the filename part of the root
root.splice(root.length - 1, 1);
root.splice(root.length - 1, 1);
return root.join("/") + "/" + sourcepath;
return root.join("/") + "/" + sourcepath;
} else {
} else {
return sourcepath;
return sourcepath;
}
}
}
}
};
};


/*
/*
Parse a semantic version string into its constituent parts -- see https://semver.org
Parse a semantic version string into its constituent parts -- see https://semver.org
*/
*/
$tw.utils.parseVersion = function(version) {
$tw.utils.parseVersion = function(version) {
var match = /^v?((\d+)\.(\d+)\.(\d+))(?:-([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?(?:\+([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?$/.exec(version);
var match = /^v?((\d+)\.(\d+)\.(\d+))(?:-([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?(?:\+([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?$/.exec(version);
if(match) {
if(match) {
return {
return {
version: match[1],
version: match[1],
major: parseInt(match[2],10),
major: parseInt(match[2],10),
minor: parseInt(match[3],10),
minor: parseInt(match[3],10),
patch: parseInt(match[4],10),
patch: parseInt(match[4],10),
prerelease: match[5],
prerelease: match[5],
build: match[6]
build: match[6]
};
};
} else {
} else {
return null;
return null;
}
}
};
};


/*
/*
Returns +1 if the version string A is greater than the version string B, 0 if they are the same, and +1 if B is greater than A.
Returns +1 if the version string A is greater than the version string B, 0 if they are the same, and +1 if B is greater than A.
Missing or malformed version strings are parsed as 0.0.0
Missing or malformed version strings are parsed as 0.0.0
*/
*/
$tw.utils.compareVersions = function(versionStringA,versionStringB) {
$tw.utils.compareVersions = function(versionStringA,versionStringB) {
var defaultVersion = {
var defaultVersion = {
major: 0,
major: 0,
minor: 0,
minor: 0,
patch: 0
patch: 0
},
},
versionA = $tw.utils.parseVersion(versionStringA) || defaultVersion,
versionA = $tw.utils.parseVersion(versionStringA) || defaultVersion,
versionB = $tw.utils.parseVersion(versionStringB) || defaultVersion,
versionB = $tw.utils.parseVersion(versionStringB) || defaultVersion,
diff = [
diff = [
versionA.major - versionB.major,
versionA.major - versionB.major,
versionA.minor - versionB.minor,
versionA.minor - versionB.minor,
versionA.patch - versionB.patch
versionA.patch - versionB.patch
];
];
if((diff[0] > 0) || (diff[0] === 0 && diff[1] > 0) || (diff[0] === 0 & diff[1] === 0 & diff[2] > 0)) {
if((diff[0] > 0) || (diff[0] === 0 && diff[1] > 0) || (diff[0] === 0 & diff[1] === 0 & diff[2] > 0)) {
return +1;
return +1;
} else if((diff[0] < 0) || (diff[0] === 0 && diff[1] < 0) || (diff[0] === 0 & diff[1] === 0 & diff[2] < 0)) {
} else if((diff[0] < 0) || (diff[0] === 0 && diff[1] < 0) || (diff[0] === 0 & diff[1] === 0 & diff[2] < 0)) {
return -1;
return -1;
} else {
} else {
return 0;
return 0;
}
}
};
};


/*
/*
Returns true if the version string A is greater than the version string B. Returns true if the versions are the same
Returns true if the version string A is greater than the version string B. Returns true if the versions are the same
*/
*/
$tw.utils.checkVersions = function(versionStringA,versionStringB) {
$tw.utils.checkVersions = function(versionStringA,versionStringB) {
return $tw.utils.compareVersions(versionStringA,versionStringB) !== -1;
return $tw.utils.compareVersions(versionStringA,versionStringB) !== -1;
};
};


/*
/*
Register file type information
Register file type information
options: {flags: flags,deserializerType: deserializerType}
options: {flags: flags,deserializerType: deserializerType}
flags:"image" for image types
flags:"image" for image types
deserializerType: defaults to type if not specified
deserializerType: defaults to type if not specified
*/
*/
$tw.utils.registerFileType = function(type,encoding,extension,options) {
$tw.utils.registerFileType = function(type,encoding,extension,options) {
options = options || {};
options = options || {};
if($tw.utils.isArray(extension)) {
if($tw.utils.isArray(extension)) {
$tw.utils.each(extension,function(extension) {
$tw.utils.each(extension,function(extension) {
$tw.config.fileExtensionInfo[extension] = {type: type};
$tw.config.fileExtensionInfo[extension] = {type: type};
});
});
extension = extension[0];
extension = extension[0];
} else {
} else {
$tw.config.fileExtensionInfo[extension] = {type: type};
$tw.config.fileExtensionInfo[extension] = {type: type};
}
}
$tw.config.contentTypeInfo[type] = {encoding: encoding, extension: extension, flags: options.flags || [], deserializerType: options.deserializerType || type};
$tw.config.contentTypeInfo[type] = {encoding: encoding, extension: extension, flags: options.flags || [], deserializerType: options.deserializerType || type};
};
};


/*
/*
Given an extension, always access the $tw.config.fileExtensionInfo
Given an extension, always access the $tw.config.fileExtensionInfo
using a lowercase extension only.
using a lowercase extension only.
*/
*/
$tw.utils.getFileExtensionInfo = function(ext) {
$tw.utils.getFileExtensionInfo = function(ext) {
return ext ? $tw.config.fileExtensionInfo[ext.toLowerCase()] : null;
return ext ? $tw.config.fileExtensionInfo[ext.toLowerCase()] : null;
}
}


/*
/*
Given an extension, get the correct encoding for that file.
Given an extension, get the correct encoding for that file.
defaults to utf8
defaults to utf8
*/
*/
$tw.utils.getTypeEncoding = function(ext) {
$tw.utils.getTypeEncoding = function(ext) {
var extensionInfo = $tw.utils.getFileExtensionInfo(ext),
var extensionInfo = $tw.utils.getFileExtensionInfo(ext),
type = extensionInfo ? extensionInfo.type : null,
type = extensionInfo ? extensionInfo.type : null,
typeInfo = type ? $tw.config.contentTypeInfo[type] : null;
typeInfo = type ? $tw.config.contentTypeInfo[type] : null;
return typeInfo ? typeInfo.encoding : "utf8";
return typeInfo ? typeInfo.encoding : "utf8";
};
};


var globalCheck =[
var globalCheck =[
" Object.defineProperty(Object.prototype, '__temp__', {",
" Object.defineProperty(Object.prototype, '__temp__', {",
" get: function () { return this; },",
" get: function () { return this; },",
" configurable: true",
" configurable: true",
" });",
" });",
" if(Object.keys(__temp__).length){",
" if(Object.keys(__temp__).length){",
" console.log(Object.keys(__temp__));",
" console.log(Object.keys(__temp__));",
" delete Object.prototype.__temp__;",
" delete Object.prototype.__temp__;",
" throw \"Global assignment is not allowed within modules on node.\";",
" throw \"Global assignment is not allowed within modules on node.\";",
" }",
" }",
" delete Object.prototype.__temp__;",
" delete Object.prototype.__temp__;",
].join('\n');
].join('\n');


/*
/*
Run code globally with specified context variables in scope
Run code globally with specified context variables in scope
*/
*/
$tw.utils.evalGlobal = function(code,context,filename,sandbox,allowGlobals) {
$tw.utils.evalGlobal = function(code,context,filename,sandbox,allowGlobals) {
var contextCopy = $tw.utils.extend(Object.create(null),context);
var contextCopy = $tw.utils.extend(Object.create(null),context);
// Get the context variables as a pair of arrays of names and values
// Get the context variables as a pair of arrays of names and values
var contextNames = [], contextValues = [];
var contextNames = [], contextValues = [];
$tw.utils.each(contextCopy,function(value,name) {
$tw.utils.each(contextCopy,function(value,name) {
contextNames.push(name);
contextNames.push(name);
contextValues.push(value);
contextValues.push(value);
});
});
// Add the code prologue and epilogue
// Add the code prologue and epilogue
code = [
code = [
"(function(" + contextNames.join(",") + ") {",
"(function(" + contextNames.join(",") + ") {",
" (function(){\n" + code + "\n;})();",
" (function(){\n" + code + "\n;})();",
(!$tw.browser && sandbox && !allowGlobals) ? globalCheck : "",
(!$tw.browser && sandbox && !allowGlobals) ? globalCheck : "",
" return exports;\n",
" return exports;\n",
"})"
"})"
].join("\n");
].join("\n");


// Compile the code into a function
// Compile the code into a function
var fn;
var fn;
if($tw.browser) {
if($tw.browser) {
fn = window["eval"](code + "\n\n//# sourceURL=" + filename);
fn = window["eval"](code + "\n\n//# sourceURL=" + filename);
} else {
} else {
if(sandbox){
if(sandbox){
fn = vm.runInContext(code,sandbox,filename)
fn = vm.runInContext(code,sandbox,filename)
} else {
} else {
fn = vm.runInThisContext(code,filename);
fn = vm.runInThisContext(code,filename);
}
}
}
}
// Call the function and return the exports
// Call the function and return the exports
return fn.apply(null,contextValues);
return fn.apply(null,contextValues);
};
};
$tw.utils.sandbox = !$tw.browser ? vm.createContext({}) : undefined;
$tw.utils.sandbox = !$tw.browser ? vm.createContext({}) : undefined;
/*
/*
Run code in a sandbox with only the specified context variables in scope
Run code in a sandbox with only the specified context variables in scope
*/
*/
$tw.utils.evalSandboxed = $tw.browser ? $tw.utils.evalGlobal : function(code,context,filename,allowGlobals) {
$tw.utils.evalSandboxed = $tw.browser ? $tw.utils.evalGlobal : function(code,context,filename,allowGlobals) {
return $tw.utils.evalGlobal(
return $tw.utils.evalGlobal(
code,context,filename,
code,context,filename,
allowGlobals ? vm.createContext({}) : $tw.utils.sandbox,
allowGlobals ? vm.createContext({}) : $tw.utils.sandbox,
allowGlobals
allowGlobals
);
);
};
};


/*
/*
Creates a PasswordPrompt object
Creates a PasswordPrompt object
*/
*/
$tw.utils.PasswordPrompt = function() {
$tw.utils.PasswordPrompt = function() {
// Store of pending password prompts
// Store of pending password prompts
this.passwordPrompts = [];
this.passwordPrompts = [];
// Create the wrapper
// Create the wrapper
this.promptWrapper = $tw.utils.domMaker("div",{"class":"tc-password-wrapper"});
this.promptWrapper = $tw.utils.domMaker("div",{"class":"tc-password-wrapper"});
document.body.appendChild(this.promptWrapper);
document.body.appendChild(this.promptWrapper);
// Hide the empty wrapper
// Hide the empty wrapper
this.setWrapperDisplay();
this.setWrapperDisplay();
};
};


/*
/*
Hides or shows the wrapper depending on whether there are any outstanding prompts
Hides or shows the wrapper depending on whether there are any outstanding prompts
*/
*/
$tw.utils.PasswordPrompt.prototype.setWrapperDisplay = function() {
$tw.utils.PasswordPrompt.prototype.setWrapperDisplay = function() {
if(this.passwordPrompts.length) {
if(this.passwordPrompts.length) {
this.promptWrapper.style.display = "block";
this.promptWrapper.style.display = "block";
} else {
} else {
this.promptWrapper.style.display = "none";
this.promptWrapper.style.display = "none";
}
}
};
};


/*
/*
Adds a new password prompt. Options are:
Adds a new password prompt. Options are:
submitText: text to use for submit button (defaults to "Login")
submitText: text to use for submit button (defaults to "Login")
serviceName: text of the human readable service name
serviceName: text of the human readable service name
noUserName: set true to disable username prompt
noUserName: set true to disable username prompt
canCancel: set true to enable a cancel button (callback called with null)
canCancel: set true to enable a cancel button (callback called with null)
repeatPassword: set true to prompt for the password twice
repeatPassword: set true to prompt for the password twice
callback: function to be called on submission with parameter of object {username:,password:}. Callback must return `true` to remove the password prompt
callback: function to be called on submission with parameter of object {username:,password:}. Callback must return `true` to remove the password prompt
*/
*/
$tw.utils.PasswordPrompt.prototype.createPrompt = function(options) {
$tw.utils.PasswordPrompt.prototype.createPrompt = function(options) {
// Create and add the prompt to the DOM
// Create and add the prompt to the DOM
var self = this,
var self = this,
submitText = options.submitText || "Login",
submitText = options.submitText || "Login",
dm = $tw.utils.domMaker,
dm = $tw.utils.domMaker,
children = [dm("h1",{text: options.serviceName})];
children = [dm("h1",{text: options.serviceName})];
if(!options.noUserName) {
if(!options.noUserName) {
children.push(dm("input",{
children.push(dm("input",{
attributes: {type: "text", name: "username", placeholder: $tw.language.getString("Encryption/Username")}
attributes: {type: "text", name: "username", placeholder: $tw.language.getString("Encryption/Username")}
}));
}));
}
}
children.push(dm("input",{
children.push(dm("input",{
attributes: {
attributes: {
type: "password",
type: "password",
name: "password",
name: "password",
placeholder: ( $tw.language == undefined ? "Password" : $tw.language.getString("Encryption/Password") )
placeholder: ( $tw.language == undefined ? "Password" : $tw.language.getString("Encryption/Password") )
}
}
}));
}));
if(options.repeatPassword) {
if(options.repeatPassword) {
children.push(dm("input",{
children.push(dm("input",{
attributes: {
attributes: {
type: "password",
type: "password",
name: "password2",
name: "password2",
placeholder: $tw.language.getString("Encryption/RepeatPassword")
placeholder: $tw.language.getString("Encryption/RepeatPassword")
}
}
}));
}));
}
}
if(options.canCancel) {
if(options.canCancel) {
children.push(dm("button",{
children.push(dm("button",{
text: $tw.language.getString("Encryption/Cancel"),
text: $tw.language.getString("Encryption/Cancel"),
attributes: {
attributes: {
type: "button"
type: "button"
},
},
eventListeners: [{
eventListeners: [{
name: "click",
name: "click",
handlerFunction: function(event) {
handlerFunction: function(event) {
self.removePrompt(promptInfo);
self.removePrompt(promptInfo);
options.callback(null);
options.callback(null);
}
}
}]
}]
}));
}));
}
}
children.push(dm("button",{
children.push(dm("button",{
attributes: {type: "submit"},
attributes: {type: "submit"},
text: submitText
text: submitText
}));
}));
var form = dm("form",{
var form = dm("form",{
attributes: {autocomplete: "off"},
attributes: {autocomplete: "off"},
children: children
children: children
});
});
this.promptWrapper.appendChild(form);
this.promptWrapper.appendChild(form);
window.setTimeout(function() {
window.setTimeout(function() {
form.elements[0].focus();
form.elements[0].focus();
},10);
},10);
// Add a submit event handler
// Add a submit event handler
var self = this;
var self = this;
form.addEventListener("submit",function(event) {
form.addEventListener("submit",function(event) {
// Collect the form data
// Collect the form data
var data = {},t;
var data = {},t;
$tw.utils.each(form.elements,function(element) {
$tw.utils.each(form.elements,function(element) {
if(element.name && element.value) {
if(element.name && element.value) {
data[element.name] = element.value;
data[element.name] = element.value;
}
}
});
});
// Check that the passwords match
// Check that the passwords match
if(options.repeatPassword && data.password !== data.password2) {
if(options.repeatPassword && data.password !== data.password2) {
alert($tw.language.getString("Encryption/PasswordNoMatch"));
alert($tw.language.getString("Encryption/PasswordNoMatch"));
} else {
} else {
// Call the callback
// Call the callback
if(options.callback(data)) {
if(options.callback(data)) {
// Remove the prompt if the callback returned true
// Remove the prompt if the callback returned true
self.removePrompt(promptInfo);
self.removePrompt(promptInfo);
} else {
} else {
// Clear the password if the callback returned false
// Clear the password if the callback returned false
$tw.utils.each(form.elements,function(element) {
$tw.utils.each(form.elements,function(element) {
if(element.name === "password" || element.name === "password2") {
if(element.name === "password" || element.name === "password2") {
element.value = "";
element.value = "";
}
}
});
});
}
}
}
}
event.preventDefault();
event.preventDefault();
return false;
return false;
},true);
},true);
// Add the prompt to the list
// Add the prompt to the list
var promptInfo = {
var promptInfo = {
serviceName: options.serviceName,
serviceName: options.serviceName,
callback: options.callback,
callback: options.callback,
form: form,
form: form,
owner: this
owner: this
};
};
this.passwordPrompts.push(promptInfo);
this.passwordPrompts.push(promptInfo);
// Make sure the wrapper is displayed
// Make sure the wrapper is displayed
this.setWrapperDisplay();
this.setWrapperDisplay();
return promptInfo;
return promptInfo;
};
};


$tw.utils.PasswordPrompt.prototype.removePrompt = function(promptInfo) {
$tw.utils.PasswordPrompt.prototype.removePrompt = function(promptInfo) {
var i = this.passwordPrompts.indexOf(promptInfo);
var i = this.passwordPrompts.indexOf(promptInfo);
if(i !== -1) {
if(i !== -1) {
this.passwordPrompts.splice(i,1);
this.passwordPrompts.splice(i,1);
promptInfo.form.parentNode.removeChild(promptInfo.form);
promptInfo.form.parentNode.removeChild(promptInfo.form);
this.setWrapperDisplay();
this.setWrapperDisplay();
}
}
}
}


/*
/*
Crypto helper object for encrypted content. It maintains the password text in a closure, and provides methods to change
Crypto helper object for encrypted content. It maintains the password text in a closure, and provides methods to change
the password, and to encrypt/decrypt a block of text
the password, and to encrypt/decrypt a block of text
*/
*/
$tw.utils.Crypto = function() {
$tw.utils.Crypto = function() {
var sjcl = $tw.node ? (global.sjcl || require("./sjcl.js")) : window.sjcl,
var sjcl = $tw.node ? (global.sjcl || require("./sjcl.js")) : window.sjcl,
currentPassword = null,
currentPassword = null,
callSjcl = function(method,inputText,password) {
callSjcl = function(method,inputText,password) {
password = password || currentPassword;
password = password || currentPassword;
var outputText;
var outputText;
try {
try {
if(password) {
if(password) {
outputText = sjcl[method](password,inputText);
outputText = sjcl[method](password,inputText);
}
}
} catch(ex) {
} catch(ex) {
console.log("Crypto error:" + ex);
console.log("Crypto error:" + ex);
outputText = null;
outputText = null;
}
}
return outputText;
return outputText;
};
};
this.setPassword = function(newPassword) {
this.setPassword = function(newPassword) {
currentPassword = newPassword;
currentPassword = newPassword;
this.updateCryptoStateTiddler();
this.updateCryptoStateTiddler();
};
};
this.updateCryptoStateTiddler = function() {
this.updateCryptoStateTiddler = function() {
if($tw.wiki) {
if($tw.wiki) {
var state = currentPassword ? "yes" : "no",
var state = currentPassword ? "yes" : "no",
tiddler = $tw.wiki.getTiddler("$:/isEncrypted");
tiddler = $tw.wiki.getTiddler("$:/isEncrypted");
if(!tiddler || tiddler.fields.text !== state) {
if(!tiddler || tiddler.fields.text !== state) {
$tw.wiki.addTiddler(new $tw.Tiddler({title: "$:/isEncrypted", text: state}));
$tw.wiki.addTiddler(new $tw.Tiddler({title: "$:/isEncrypted", text: state}));
}
}
}
}
};
};
this.hasPassword = function() {
this.hasPassword = function() {
return !!currentPassword;
return !!currentPassword;
}
}
this.encrypt = function(text,password) {
this.encrypt = function(text,password) {
return callSjcl("encrypt",text,password);
return callSjcl("encrypt",text,password);
};
};
this.decrypt = function(text,password) {
this.decrypt = function(text,password) {
return callSjcl("decrypt",text,password);
return callSjcl("decrypt",text,password);
};
};
};
};


/////////////////////////// Module mechanism
/////////////////////////// Module mechanism


/*
/*
Execute the module named 'moduleName'. The name can optionally be relative to the module named 'moduleRoot'
Execute the module named 'moduleName'. The name can optionally be relative to the module named 'moduleRoot'
*/
*/
$tw.modules.execute = function(moduleName,moduleRoot) {
$tw.modules.execute = function(moduleName,moduleRoot) {
var name = moduleName;
var name = moduleName;
if(moduleName.charAt(0) === ".") {
if(moduleName.charAt(0) === ".") {
name = $tw.utils.resolvePath(moduleName,moduleRoot)
name = $tw.utils.resolvePath(moduleName,moduleRoot)
}
}
if(!$tw.modules.titles[name]) {
if(!$tw.modules.titles[name]) {
if($tw.modules.titles[name + ".js"]) {
if($tw.modules.titles[name + ".js"]) {
name = name + ".js";
name = name + ".js";
} else if($tw.modules.titles[name + "/index.js"]) {
} else if($tw.modules.titles[name + "/index.js"]) {
name = name + "/index.js";
name = name + "/index.js";
} else if($tw.modules.titles[moduleName]) {
} else if($tw.modules.titles[moduleName]) {
name = moduleName;
name = moduleName;
} else if($tw.modules.titles[moduleName + ".js"]) {
} else if($tw.modules.titles[moduleName + ".js"]) {
name = moduleName + ".js";
name = moduleName + ".js";
} else if($tw.modules.titles[moduleName + "/index.js"]) {
} else if($tw.modules.titles[moduleName + "/index.js"]) {
name = moduleName + "/index.js";
name = moduleName + "/index.js";
}
}
}
}
var moduleInfo = $tw.modules.titles[name],
var moduleInfo = $tw.modules.titles[name],
tiddler = $tw.wiki.getTiddler(name),
tiddler = $tw.wiki.getTiddler(name),
_exports = {},
_exports = {},
sandbox = {
sandbox = {
module: {exports: _exports},
module: {exports: _exports},
//moduleInfo: moduleInfo,
//moduleInfo: moduleInfo,
exports: _exports,
exports: _exports,
console: console,
console: console,
setInterval: setInterval,
setInterval: setInterval,
clearInterval: clearInterval,
clearInterval: clearInterval,
setTimeout: setTimeout,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
clearTimeout: clearTimeout,
Buffer: $tw.browser ? undefined : Buffer,
Buffer: $tw.browser ? undefined : Buffer,
$tw: $tw,
$tw: $tw,
require: function(title) {
require: function(title) {
return $tw.modules.execute(title, name);
return $tw.modules.execute(title, name);
}
}
};
};


Object.defineProperty(sandbox.module, "id", {
Object.defineProperty(sandbox.module, "id", {
value: name,
value: name,
writable: false,
writable: false,
enumerable: true,
enumerable: true,
configurable: false
configurable: false
});
});


if(!$tw.browser) {
if(!$tw.browser) {
$tw.utils.extend(sandbox,{
$tw.utils.extend(sandbox,{
process: process
process: process
});
});
} else {
} else {
/*
/*
CommonJS optional require.main property:
CommonJS optional require.main property:
In a browser we offer a fake main module which points back to the boot function
In a browser we offer a fake main module which points back to the boot function
(Theoretically, this
(Theoretically, this