2012-04-30 11:23:03 +00:00
/ * \
2013-05-31 15:53:19 +00:00
title : $ : / b o o t / b o o t . j s
2012-04-30 11:23:03 +00:00
type : application / javascript
2012-11-14 11:23:43 +00:00
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 .
2012-04-30 11:23:03 +00:00
2014-10-18 16:46:19 +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 .
2012-04-30 11:23:03 +00:00
\ * /
2013-10-12 17:44:09 +00:00
var _boot = ( function ( $tw ) {
2012-04-30 11:23:03 +00:00
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" ;
2013-10-12 17:44:09 +00:00
// Include bootprefix if we're not given module data
if ( ! $tw ) {
$tw = require ( "./bootprefix.js" ) . bootprefix ( ) ;
2012-11-15 10:40:03 +00:00
}
2014-04-06 21:49:20 +00:00
$tw . utils = $tw . utils || Object . create ( null ) ;
2012-04-30 11:23:03 +00:00
2013-03-22 20:02:19 +00:00
/////////////////////////// Standard node.js libraries
var fs , path , vm ;
2014-01-14 14:09:04 +00:00
if ( $tw . node ) {
2013-03-22 20:02:19 +00:00
fs = require ( "fs" ) ;
path = require ( "path" ) ;
vm = require ( "vm" ) ;
}
2012-04-30 11:23:03 +00:00
/////////////////////////// Utility functions
2014-05-15 17:50:14 +00:00
$tw . boot . log = function ( str ) {
$tw . boot . logMessages = $tw . boot . logMessages || [ ] ;
$tw . boot . logMessages . push ( str ) ;
}
2012-10-10 15:32:37 +00:00
/ *
Check if an object has a property
* /
$tw . utils . hop = function ( object , property ) {
2013-05-15 16:32:17 +00:00
return object ? Object . prototype . hasOwnProperty . call ( object , property ) : false ;
2012-10-10 15:32:37 +00:00
} ;
2012-11-16 17:44:47 +00:00
/ *
2012-12-13 21:31:19 +00:00
Determine if a value is an array
* /
$tw . utils . isArray = function ( value ) {
return Object . prototype . toString . call ( value ) == "[object Array]" ;
} ;
2019-02-03 12:01:38 +00:00
/ *
Check if an array is equal by value and by reference .
* /
$tw . utils . isArrayEqual = function ( array1 , array2 ) {
if ( array1 === array2 ) {
return true ;
}
array1 = array1 || [ ] ;
array2 = array2 || [ ] ;
if ( array1 . length !== array2 . length ) {
return false ;
}
return array1 . every ( function ( value , index ) {
return value === array2 [ index ] ;
} ) ;
} ;
2022-02-21 15:34:05 +00:00
/ *
Add an entry to a sorted array if it doesn ' t already exist , while maintaining the sort order
* /
$tw . utils . insertSortedArray = function ( array , value ) {
var low = 0 , high = array . length - 1 , mid , cmp ;
while ( low <= high ) {
mid = ( low + high ) >> 1 ;
cmp = value . localeCompare ( array [ mid ] ) ;
if ( cmp > 0 ) {
low = mid + 1 ;
} else if ( cmp < 0 ) {
high = mid - 1 ;
} else {
return array ;
}
}
array . splice ( low , 0 , value ) ;
return array ;
} ;
2019-02-03 12:01:38 +00:00
/ *
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 )
value : a single value to push or an array of values to push
* /
$tw . utils . pushTop = function ( array , value ) {
var t , p ;
if ( $tw . utils . isArray ( value ) ) {
// Remove any array entries that are duplicated in the new values
if ( value . length !== 0 ) {
if ( array . length !== 0 ) {
if ( value . length < array . length ) {
for ( t = 0 ; t < value . length ; t ++ ) {
p = array . indexOf ( value [ t ] ) ;
if ( p !== - 1 ) {
array . splice ( p , 1 ) ;
}
}
} else {
for ( t = array . length - 1 ; t >= 0 ; t -- ) {
p = value . indexOf ( array [ t ] ) ;
if ( p !== - 1 ) {
array . splice ( t , 1 ) ;
}
}
}
}
// Push the values on top of the main array
array . push . apply ( array , value ) ;
}
} else {
p = array . indexOf ( value ) ;
if ( p !== - 1 ) {
array . splice ( p , 1 ) ;
}
array . push ( value ) ;
}
return array ;
} ;
2013-08-06 14:26:10 +00:00
/ *
Determine if a value is a date
* /
$tw . utils . isDate = function ( value ) {
return Object . prototype . toString . call ( value ) === "[object Date]" ;
} ;
2012-12-13 21:31:19 +00:00
/ *
Iterate through all the own properties of an object or array . Callback is invoked with ( element , title , object )
2012-11-16 17:44:47 +00:00
* /
$tw . utils . each = function ( object , callback ) {
2015-07-05 18:47:44 +00:00
var next , f , length ;
2012-11-16 17:44:47 +00:00
if ( object ) {
2014-04-05 16:31:36 +00:00
if ( Object . prototype . toString . call ( object ) == "[object Array]" ) {
2015-07-05 18:47:44 +00:00
for ( f = 0 , length = object . length ; f < length ; f ++ ) {
2014-12-02 11:47:19 +00:00
next = callback ( object [ f ] , f , object ) ;
2014-11-26 17:50:12 +00:00
if ( next === false ) {
break ;
}
2014-11-26 16:51:27 +00:00
}
2012-12-13 21:31:19 +00:00
} else {
2015-07-05 18:47:44 +00:00
var keys = Object . keys ( object ) ;
for ( f = 0 , length = keys . length ; f < length ; f ++ ) {
var key = keys [ f ] ;
next = callback ( object [ key ] , key , object ) ;
if ( next === false ) {
break ;
2012-12-13 21:31:19 +00:00
}
}
2012-11-16 17:44:47 +00:00
}
}
2012-05-04 17:49:04 +00:00
} ;
2012-04-30 11:23:03 +00:00
2013-06-26 22:52:21 +00:00
/ *
Helper for making DOM elements
tag : tag name
options : see below
Options include :
2019-02-01 10:43:14 +00:00
namespace : defaults to http : //www.w3.org/1999/xhtml
2013-06-26 22:52:21 +00:00
attributes : hashmap of attribute values
2019-02-01 10:43:14 +00:00
style : hashmap of styles
2013-06-26 22:52:21 +00:00
text : text to add as a child node
children : array of further child nodes
innerHTML : optional HTML for element
class : class name ( s )
document : defaults to current document
2013-10-26 12:13:45 +00:00
eventListeners : array of event listeners ( this option won ' t work until $tw . utils . addEventListeners ( ) has been loaded )
2013-06-26 22:52:21 +00:00
* /
$tw . utils . domMaker = function ( tag , options ) {
var doc = options . document || document ;
2019-02-01 10:43:14 +00:00
var element = doc . createElementNS ( options . namespace || "http://www.w3.org/1999/xhtml" , tag ) ;
2013-06-26 22:52:21 +00:00
if ( options [ "class" ] ) {
element . className = options [ "class" ] ;
}
if ( options . text ) {
2014-03-08 10:43:01 +00:00
element . appendChild ( doc . createTextNode ( options . text ) ) ;
2013-06-26 22:52:21 +00:00
}
$tw . utils . each ( options . children , function ( child ) {
element . appendChild ( child ) ;
} ) ;
if ( options . innerHTML ) {
element . innerHTML = options . innerHTML ;
}
$tw . utils . each ( options . attributes , function ( attribute , name ) {
element . setAttribute ( name , attribute ) ;
} ) ;
2019-02-01 10:43:14 +00:00
$tw . utils . each ( options . style , function ( value , name ) {
element . style [ name ] = value ;
} ) ;
2013-10-26 12:13:45 +00:00
if ( options . eventListeners ) {
$tw . utils . addEventListeners ( element , options . eventListeners ) ;
}
2013-06-26 22:52:21 +00:00
return element ;
} ;
/ *
Display an error and exit
* /
$tw . utils . error = function ( err ) {
// Prepare the error message
2015-10-18 08:26:42 +00:00
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" ) ) ;
2013-06-26 22:52:21 +00:00
// Log the error to the console
2014-10-21 18:30:27 +00:00
console . error ( $tw . node ? "\x1b[1;31m" + err + "\x1b[0m" : err ) ;
2014-05-07 08:59:21 +00:00
if ( $tw . browser && ! $tw . node ) {
2013-06-26 22:52:21 +00:00
// Display an error message to the user
var dm = $tw . utils . domMaker ,
heading = dm ( "h1" , { text : errHeading } ) ,
2014-08-28 16:39:32 +00:00
prompt = dm ( "div" , { text : promptMsg , "class" : "tc-error-prompt" } ) ,
2018-05-10 13:45:50 +00:00
message = dm ( "div" , { text : err , "class" : "tc-error-message" } ) ,
2018-05-10 14:29:31 +00:00
button = dm ( "div" , { children : [ dm ( "button" , { text : ( $tw . language == undefined ? "close" : $tw . language . getString ( "Buttons/Close/Caption" ) ) } ) ] , "class" : "tc-error-prompt" } ) ,
2014-08-28 16:39:32 +00:00
form = dm ( "form" , { children : [ heading , prompt , message , button ] , "class" : "tc-error-form" } ) ;
2013-06-26 22:52:21 +00:00
document . body . insertBefore ( form , document . body . firstChild ) ;
form . addEventListener ( "submit" , function ( event ) {
document . body . removeChild ( form ) ;
event . preventDefault ( ) ;
return false ;
} , true ) ;
return null ;
2014-05-07 08:59:21 +00:00
} else if ( ! $tw . browser ) {
2013-06-26 22:52:21 +00:00
// Exit if we're under node.js
process . exit ( 1 ) ;
}
} ;
/ *
Use our custom error handler if we ' re in the browser
* /
2014-05-07 11:51:16 +00:00
if ( $tw . boot . tasks . trapErrors ) {
2013-06-26 22:52:21 +00:00
window . onerror = function ( errorMsg , url , lineNumber ) {
$tw . utils . error ( errorMsg ) ;
return false ;
} ;
}
2013-03-22 19:27:09 +00:00
/ *
Extend an object with the properties from a list of source objects
* /
$tw . utils . extend = function ( object /*, sourceObjectList */ ) {
$tw . utils . each ( Array . prototype . slice . call ( arguments , 1 ) , function ( source ) {
if ( source ) {
for ( var p in source ) {
object [ p ] = source [ p ] ;
}
}
} ) ;
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
* /
$tw . utils . deepDefaults = function ( object /*, sourceObjectList */ ) {
$tw . utils . each ( Array . prototype . slice . call ( arguments , 1 ) , function ( source ) {
if ( source ) {
for ( var p in source ) {
2014-06-19 11:05:41 +00:00
if ( object [ p ] === null || object [ p ] === undefined ) {
2013-03-22 19:27:09 +00:00
object [ p ] = source [ p ] ;
}
if ( typeof object [ p ] === "object" && typeof source [ p ] === "object" ) {
2014-05-13 09:26:02 +00:00
$tw . utils . deepDefaults ( object [ p ] , source [ p ] ) ;
2013-03-22 19:27:09 +00:00
}
}
}
} ) ;
return object ;
} ;
2021-10-20 15:25:05 +00:00
/ *
Convert a URIComponent encoded string to a string safely
* /
$tw . utils . decodeURIComponentSafe = function ( s ) {
var v = s ;
try {
v = decodeURIComponent ( s ) ;
} catch ( e ) { }
return v ;
} ;
/ *
Convert a URI encoded string to a string safely
* /
$tw . utils . decodeURISafe = function ( s ) {
var v = s ;
try {
v = decodeURI ( s ) ;
} catch ( e ) { }
return v ;
} ;
2012-10-10 15:32:37 +00:00
/ *
2014-01-12 20:11:51 +00:00
Convert "&" to & , " " to nbsp , "<" to < , ">" to > and """ to "
2012-10-10 15:32:37 +00:00
* /
2012-04-30 11:23:03 +00:00
$tw . utils . htmlDecode = function ( s ) {
2014-01-28 15:23:58 +00:00
return s . toString ( ) . replace ( /</mg , "<" ) . replace ( / /mg , "\xA0" ) . replace ( />/mg , ">" ) . replace ( /"/mg , "\"" ) . replace ( /&/mg , "&" ) ;
2012-04-30 11:23:03 +00:00
} ;
2014-05-05 18:21:57 +00:00
/ *
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 ( ) {
2020-11-01 10:47:50 +00:00
var href = window . location . href ;
var idx = href . indexOf ( '#' ) ;
if ( idx === - 1 ) {
return "#" ;
} else if ( idx < href . length - 1 && href [ idx + 1 ] === '#' ) {
// Special case: ignore location hash if it itself starts with a #
return "#" ;
} else {
return href . substring ( idx ) ;
}
2014-05-05 18:21:57 +00:00
} ;
2012-04-30 11:23:03 +00:00
/ *
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 ;
} ;
2013-11-19 12:14:37 +00:00
// Convert a date into UTC YYYYMMDDHHMMSSmmm format
2012-04-30 11:23:03 +00:00
$tw . utils . stringifyDate = function ( value ) {
return value . getUTCFullYear ( ) +
2012-05-05 13:17:51 +00:00
$tw . utils . pad ( value . getUTCMonth ( ) + 1 ) +
2014-05-13 09:26:02 +00:00
$tw . utils . pad ( value . getUTCDate ( ) ) +
$tw . utils . pad ( value . getUTCHours ( ) ) +
2013-11-07 20:04:54 +00:00
$tw . utils . pad ( value . getUTCMinutes ( ) ) +
2013-11-19 12:14:37 +00:00
$tw . utils . pad ( value . getUTCSeconds ( ) ) +
2013-11-07 20:04:54 +00:00
$tw . utils . pad ( value . getUTCMilliseconds ( ) , 3 ) ;
2012-04-30 11:23:03 +00:00
} ;
2013-11-19 12:14:37 +00:00
// Parse a date from a UTC YYYYMMDDHHMMSSmmm format string
2012-04-30 11:23:03 +00:00
$tw . utils . parseDate = function ( value ) {
if ( typeof value === "string" ) {
2020-12-01 10:39:27 +00:00
var negative = 1 ;
if ( value . charAt ( 0 ) === "-" ) {
negative = - 1 ;
value = value . substr ( 1 ) ;
}
var year = parseInt ( value . substr ( 0 , 4 ) , 10 ) * negative ,
2020-11-30 18:56:52 +00:00
d = new Date ( Date . UTC ( year ,
2012-04-30 11:23:03 +00:00
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 ) ) ) ;
2020-11-30 18:56:52 +00:00
d . setUTCFullYear ( year ) ; // See https://stackoverflow.com/a/5870822
return d ;
2014-01-03 10:50:00 +00:00
} else if ( $tw . utils . isDate ( value ) ) {
2012-04-30 11:23:03 +00:00
return value ;
} else {
return null ;
}
} ;
2013-08-07 15:05:56 +00:00
// Stringify an array of tiddler titles into a list string
$tw . utils . stringifyList = function ( value ) {
2016-09-30 17:28:12 +00:00
if ( $tw . utils . isArray ( value ) ) {
2019-08-24 10:35:03 +00:00
var result = new Array ( value . length ) ;
for ( var t = 0 , l = value . length ; t < l ; t ++ ) {
2016-09-30 17:28:12 +00:00
var entry = value [ t ] || "" ;
2022-05-05 07:16:20 +00:00
if ( entry . indexOf ( " " ) !== - 1 || entry === "" ) {
2019-08-24 10:35:03 +00:00
result [ t ] = "[[" + entry + "]]" ;
2016-09-30 17:28:12 +00:00
} else {
2019-08-24 10:35:03 +00:00
result [ t ] = entry ;
2016-09-30 17:28:12 +00:00
}
2013-08-07 15:05:56 +00:00
}
2016-09-30 17:28:12 +00:00
return result . join ( " " ) ;
} else {
return value || "" ;
2013-08-07 15:05:56 +00:00
}
} ;
2012-08-30 13:43:13 +00:00
// Parse a string array from a bracketted list. For example "OneTiddler [[Another Tiddler]] LastOne"
2018-10-21 15:58:41 +00:00
$tw . utils . parseStringArray = function ( value , allowDuplicate ) {
2012-04-30 11:23:03 +00:00
if ( typeof value === "string" ) {
2014-05-06 17:10:27 +00:00
var memberRegExp = /(?:^|[^\S\xA0])(?:\[\[(.*?)\]\])(?=[^\S\xA0]|$)|([\S\xA0]+)/mg ,
2017-02-19 18:08:15 +00:00
results = [ ] , names = { } ,
2012-04-30 11:23:03 +00:00
match ;
do {
match = memberRegExp . exec ( value ) ;
if ( match ) {
2022-05-05 07:16:20 +00:00
var item = match [ 1 ] !== undefined ? match [ 1 ] : match [ 2 ] ;
2018-10-21 15:58:41 +00:00
if ( item !== undefined && ( ! $tw . utils . hop ( names , item ) || allowDuplicate ) ) {
2013-07-04 15:55:47 +00:00
results . push ( item ) ;
2017-02-19 18:08:15 +00:00
names [ item ] = true ;
2013-07-04 15:55:47 +00:00
}
2012-04-30 11:23:03 +00:00
}
} while ( match ) ;
return results ;
2014-01-03 10:50:00 +00:00
} else if ( $tw . utils . isArray ( value ) ) {
2012-04-30 11:23:03 +00:00
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 ) {
2014-04-06 21:49:20 +00:00
fields = fields || Object . create ( null ) ;
2012-05-09 12:48:34 +00:00
text . split ( /\r?\n/mg ) . forEach ( function ( line ) {
2012-11-17 20:18:03 +00:00
if ( line . charAt ( 0 ) !== "#" ) {
var p = line . indexOf ( ":" ) ;
if ( p !== - 1 ) {
var field = line . substr ( 0 , p ) . trim ( ) ,
value = line . substr ( p + 1 ) . trim ( ) ;
2015-05-07 18:24:58 +00:00
if ( field ) {
fields [ field ] = value ;
}
2012-11-17 20:18:03 +00:00
}
2012-04-30 11:23:03 +00:00
}
} ) ;
return fields ;
} ;
2022-02-21 15:29:25 +00:00
// Safely parse a string as JSON
$tw . utils . parseJSONSafe = function ( text , defaultJSON ) {
try {
return JSON . parse ( text ) ;
} catch ( e ) {
if ( typeof defaultJSON === "function" ) {
return defaultJSON ( e ) ;
} else {
return defaultJSON || { } ;
}
}
} ;
2012-04-30 11:23:03 +00:00
/ *
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
2013-10-12 17:44:09 +00:00
if ( rootpath ) {
var root = rootpath . split ( "/" ) ;
// Remove the filename part of the root
root . splice ( root . length - 1 , 1 ) ;
return root . join ( "/" ) + "/" + sourcepath ;
} else {
return sourcepath ;
}
2012-04-30 11:23:03 +00:00
}
} ;
2012-10-01 15:50:42 +00:00
/ *
2020-04-13 09:03:01 +00:00
Parse a semantic version string into its constituent parts -- see https : //semver.org
2012-10-01 15:50:42 +00:00
* /
2013-12-02 09:57:19 +00:00
$tw . utils . parseVersion = function ( version ) {
2020-04-13 09:03:01 +00:00
var match = /^v?((\d+)\.(\d+)\.(\d+))(?:-([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?(?:\+([\dA-Za-z\-]+(?:\.[\dA-Za-z\-]+)*))?$/ . exec ( version ) ;
2013-12-02 09:57:19 +00:00
if ( match ) {
return {
version : match [ 1 ] ,
major : parseInt ( match [ 2 ] , 10 ) ,
minor : parseInt ( match [ 3 ] , 10 ) ,
patch : parseInt ( match [ 4 ] , 10 ) ,
prerelease : match [ 5 ] ,
build : match [ 6 ]
} ;
} else {
return null ;
}
} ;
2020-04-13 09:03:01 +00:00
/ *
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
* /
$tw . utils . compareVersions = function ( versionStringA , versionStringB ) {
var defaultVersion = {
major : 0 ,
minor : 0 ,
patch : 0
} ,
versionA = $tw . utils . parseVersion ( versionStringA ) || defaultVersion ,
versionB = $tw . utils . parseVersion ( versionStringB ) || defaultVersion ,
diff = [
versionA . major - versionB . major ,
versionA . minor - versionB . minor ,
versionA . patch - versionB . patch
] ;
if ( ( diff [ 0 ] > 0 ) || ( diff [ 0 ] === 0 && diff [ 1 ] > 0 ) || ( diff [ 0 ] === 0 & diff [ 1 ] === 0 & diff [ 2 ] > 0 ) ) {
return + 1 ;
} else if ( ( diff [ 0 ] < 0 ) || ( diff [ 0 ] === 0 && diff [ 1 ] < 0 ) || ( diff [ 0 ] === 0 & diff [ 1 ] === 0 & diff [ 2 ] < 0 ) ) {
return - 1 ;
} else {
return 0 ;
}
} ;
2013-12-02 09:57:19 +00:00
/ *
2014-04-17 10:59:42 +00:00
Returns true if the version string A is greater than the version string B . Returns true if the versions are the same
2013-12-02 09:57:19 +00:00
* /
$tw . utils . checkVersions = function ( versionStringA , versionStringB ) {
2020-04-13 09:03:01 +00:00
return $tw . utils . compareVersions ( versionStringA , versionStringB ) !== - 1 ;
2012-10-01 15:50:42 +00:00
} ;
2013-03-22 21:12:39 +00:00
/ *
Register file type information
2014-04-08 21:26:01 +00:00
options : { flags : flags , deserializerType : deserializerType }
2014-04-08 21:20:51 +00:00
flags : "image" for image types
2014-04-08 21:26:01 +00:00
deserializerType : defaults to type if not specified
2013-03-22 21:12:39 +00:00
* /
2014-04-08 21:20:51 +00:00
$tw . utils . registerFileType = function ( type , encoding , extension , options ) {
options = options || { } ;
2014-09-14 19:35:01 +00:00
if ( $tw . utils . isArray ( extension ) ) {
$tw . utils . each ( extension , function ( extension ) {
$tw . config . fileExtensionInfo [ extension ] = { type : type } ;
} ) ;
extension = extension [ 0 ] ;
} else {
$tw . config . fileExtensionInfo [ extension ] = { type : type } ;
}
2014-05-13 09:26:02 +00:00
$tw . config . contentTypeInfo [ type ] = { encoding : encoding , extension : extension , flags : options . flags || [ ] , deserializerType : options . deserializerType || type } ;
2014-04-08 21:20:51 +00:00
} ;
2015-02-01 18:33:40 +00:00
/ *
Given an extension , always access the $tw . config . fileExtensionInfo
using a lowercase extension only .
* /
$tw . utils . getFileExtensionInfo = function ( ext ) {
return ext ? $tw . config . fileExtensionInfo [ ext . toLowerCase ( ) ] : null ;
}
2014-04-08 21:20:51 +00:00
/ *
Given an extension , get the correct encoding for that file .
defaults to utf8
* /
$tw . utils . getTypeEncoding = function ( ext ) {
2015-02-07 10:23:16 +00:00
var extensionInfo = $tw . utils . getFileExtensionInfo ( ext ) ,
2014-04-08 21:20:51 +00:00
type = extensionInfo ? extensionInfo . type : null ,
typeInfo = type ? $tw . config . contentTypeInfo [ type ] : null ;
return typeInfo ? typeInfo . encoding : "utf8" ;
2013-10-03 13:43:58 +00:00
} ;
2013-03-22 21:12:39 +00:00
2013-04-25 08:05:02 +00:00
/ *
Run code globally with specified context variables in scope
* /
$tw . utils . evalGlobal = function ( code , context , filename ) {
2014-04-06 21:49:20 +00:00
var contextCopy = $tw . utils . extend ( Object . create ( null ) , context ) ;
2013-04-25 08:05:02 +00:00
// Get the context variables as a pair of arrays of names and values
var contextNames = [ ] , contextValues = [ ] ;
$tw . utils . each ( contextCopy , function ( value , name ) {
contextNames . push ( name ) ;
contextValues . push ( value ) ;
} ) ;
// Add the code prologue and epilogue
2014-11-23 15:24:01 +00:00
code = "(function(" + contextNames . join ( "," ) + ") {(function(){\n" + code + "\n;})();\nreturn exports;\n})\n" ;
2013-04-25 08:05:02 +00:00
// Compile the code into a function
var fn ;
if ( $tw . browser ) {
2014-04-06 21:36:51 +00:00
fn = window [ "eval" ] ( code + "\n\n//# sourceURL=" + filename ) ;
2013-04-25 08:05:02 +00:00
} else {
2014-05-13 09:26:02 +00:00
fn = vm . runInThisContext ( code , filename ) ;
2013-04-25 08:05:02 +00:00
}
// Call the function and return the exports
return fn . apply ( null , contextValues ) ;
} ;
/ *
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 ) {
2014-04-06 21:49:20 +00:00
var sandbox = $tw . utils . extend ( Object . create ( null ) , context ) ;
2013-04-25 08:05:02 +00:00
vm . runInNewContext ( code , sandbox , filename ) ;
return sandbox . exports ;
} ;
2012-11-16 16:59:47 +00:00
/ *
Creates a PasswordPrompt object
* /
$tw . utils . PasswordPrompt = function ( ) {
// Store of pending password prompts
this . passwordPrompts = [ ] ;
// Create the wrapper
2014-08-28 16:38:53 +00:00
this . promptWrapper = $tw . utils . domMaker ( "div" , { "class" : "tc-password-wrapper" } ) ;
2012-11-16 16:59:47 +00:00
document . body . appendChild ( this . promptWrapper ) ;
// Hide the empty wrapper
this . setWrapperDisplay ( ) ;
} ;
/ *
Hides or shows the wrapper depending on whether there are any outstanding prompts
* /
$tw . utils . PasswordPrompt . prototype . setWrapperDisplay = function ( ) {
if ( this . passwordPrompts . length ) {
this . promptWrapper . style . display = "block" ;
} else {
this . promptWrapper . style . display = "none" ;
}
} ;
/ *
2013-03-08 17:50:28 +00:00
Adds a new password prompt . Options are :
submitText : text to use for submit button ( defaults to "Login" )
serviceName : text of the human readable service name
noUserName : set true to disable username prompt
2014-01-19 18:43:02 +00:00
canCancel : set true to enable a cancel button ( callback called with null )
2014-10-06 09:22:09 +00:00
repeatPassword : set true to prompt for the password twice
2013-03-08 17:50:28 +00:00
callback : function to be called on submission with parameter of object { username : , password : } . Callback must return ` true ` to remove the password prompt
2012-11-16 16:59:47 +00:00
* /
$tw . utils . PasswordPrompt . prototype . createPrompt = function ( options ) {
// Create and add the prompt to the DOM
2014-01-19 18:43:02 +00:00
var self = this ,
submitText = options . submitText || "Login" ,
2013-06-26 22:52:21 +00:00
dm = $tw . utils . domMaker ,
children = [ dm ( "h1" , { text : options . serviceName } ) ] ;
2012-11-16 16:59:47 +00:00
if ( ! options . noUserName ) {
2013-06-26 22:52:21 +00:00
children . push ( dm ( "input" , {
2015-03-18 17:52:33 +00:00
attributes : { type : "text" , name : "username" , placeholder : $tw . language . getString ( "Encryption/Username" ) }
2013-06-26 22:52:21 +00:00
} ) ) ;
2012-11-16 16:59:47 +00:00
}
2013-06-26 22:52:21 +00:00
children . push ( dm ( "input" , {
2015-03-18 17:52:33 +00:00
attributes : {
type : "password" ,
name : "password" ,
placeholder : ( $tw . language == undefined ? "Password" : $tw . language . getString ( "Encryption/Password" ) )
}
2013-06-26 22:52:21 +00:00
} ) ) ;
2014-10-06 09:22:09 +00:00
if ( options . repeatPassword ) {
children . push ( dm ( "input" , {
2015-03-18 17:52:33 +00:00
attributes : {
type : "password" ,
name : "password2" ,
placeholder : $tw . language . getString ( "Encryption/RepeatPassword" )
}
2014-10-06 09:22:09 +00:00
} ) ) ;
}
2014-01-19 18:43:02 +00:00
if ( options . canCancel ) {
children . push ( dm ( "button" , {
2015-03-18 17:52:33 +00:00
text : $tw . language . getString ( "Encryption/Cancel" ) ,
2016-08-29 18:13:25 +00:00
attributes : {
type : "button"
} ,
2014-01-19 18:43:02 +00:00
eventListeners : [ {
name : "click" ,
handlerFunction : function ( event ) {
self . removePrompt ( promptInfo ) ;
options . callback ( null ) ;
}
} ]
} ) ) ;
}
2013-06-26 22:52:21 +00:00
children . push ( dm ( "button" , {
attributes : { type : "submit" } ,
2014-08-28 16:17:50 +00:00
text : submitText
2013-06-26 22:52:21 +00:00
} ) ) ;
var form = dm ( "form" , {
attributes : { autocomplete : "off" } ,
children : children
} ) ;
2012-11-16 16:59:47 +00:00
this . promptWrapper . appendChild ( form ) ;
2012-11-16 17:23:10 +00:00
window . setTimeout ( function ( ) {
form . elements [ 0 ] . focus ( ) ;
} , 10 ) ;
2012-11-16 16:59:47 +00:00
// Add a submit event handler
var self = this ;
form . addEventListener ( "submit" , function ( event ) {
// Collect the form data
2012-11-16 22:40:56 +00:00
var data = { } , t ;
2013-03-22 21:12:39 +00:00
$tw . utils . each ( form . elements , function ( element ) {
if ( element . name && element . value ) {
data [ element . name ] = element . value ;
2012-11-16 16:59:47 +00:00
}
2013-03-22 21:12:39 +00:00
} ) ;
2014-10-06 09:22:09 +00:00
// Check that the passwords match
if ( options . repeatPassword && data . password !== data . password2 ) {
2015-03-18 17:52:33 +00:00
alert ( $tw . language . getString ( "Encryption/PasswordNoMatch" ) ) ;
2012-11-16 22:40:56 +00:00
} else {
2014-10-06 09:22:09 +00:00
// Call the callback
if ( options . callback ( data ) ) {
// Remove the prompt if the callback returned true
self . removePrompt ( promptInfo ) ;
} else {
// Clear the password if the callback returned false
$tw . utils . each ( form . elements , function ( element ) {
if ( element . name === "password" || element . name === "password2" ) {
element . value = "" ;
}
} ) ;
}
2015-03-18 17:52:33 +00:00
}
2012-11-16 16:59:47 +00:00
event . preventDefault ( ) ;
return false ;
} , true ) ;
// Add the prompt to the list
var promptInfo = {
serviceName : options . serviceName ,
callback : options . callback ,
2020-10-14 11:41:33 +00:00
form : form ,
owner : this
2012-11-16 16:59:47 +00:00
} ;
this . passwordPrompts . push ( promptInfo ) ;
// Make sure the wrapper is displayed
this . setWrapperDisplay ( ) ;
2020-10-14 11:41:33 +00:00
return promptInfo ;
2012-11-16 16:59:47 +00:00
} ;
2014-01-19 18:43:02 +00:00
$tw . utils . PasswordPrompt . prototype . removePrompt = function ( promptInfo ) {
var i = this . passwordPrompts . indexOf ( promptInfo ) ;
if ( i !== - 1 ) {
this . passwordPrompts . splice ( i , 1 ) ;
promptInfo . form . parentNode . removeChild ( promptInfo . form ) ;
this . setWrapperDisplay ( ) ;
}
}
2013-01-31 10:20:13 +00:00
/ *
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
* /
2012-11-16 16:59:47 +00:00
$tw . utils . Crypto = function ( ) {
2015-12-19 18:52:25 +00:00
var sjcl = $tw . node ? ( global . sjcl || require ( "./sjcl.js" ) ) : window . sjcl ,
2014-01-20 13:35:55 +00:00
currentPassword = null ,
callSjcl = function ( method , inputText , password ) {
password = password || currentPassword ;
2012-11-16 16:59:47 +00:00
var outputText ;
try {
2014-01-19 18:43:02 +00:00
if ( password ) {
outputText = sjcl [ method ] ( password , inputText ) ;
}
2012-11-16 16:59:47 +00:00
} catch ( ex ) {
console . log ( "Crypto error:" + ex ) ;
2014-05-13 09:26:02 +00:00
outputText = null ;
2012-11-16 16:59:47 +00:00
}
return outputText ;
} ;
this . setPassword = function ( newPassword ) {
2014-01-20 13:35:55 +00:00
currentPassword = newPassword ;
2013-01-31 10:20:13 +00:00
this . updateCryptoStateTiddler ( ) ;
} ;
this . updateCryptoStateTiddler = function ( ) {
2014-02-21 18:21:08 +00:00
if ( $tw . wiki ) {
var state = currentPassword ? "yes" : "no" ,
tiddler = $tw . wiki . getTiddler ( "$:/isEncrypted" ) ;
if ( ! tiddler || tiddler . fields . text !== state ) {
$tw . wiki . addTiddler ( new $tw . Tiddler ( { title : "$:/isEncrypted" , text : state } ) ) ;
}
2013-03-22 19:44:12 +00:00
}
2012-11-16 16:59:47 +00:00
} ;
2013-01-31 10:20:13 +00:00
this . hasPassword = function ( ) {
2014-01-20 13:35:55 +00:00
return ! ! currentPassword ;
2013-01-31 10:20:13 +00:00
}
2014-01-20 13:35:55 +00:00
this . encrypt = function ( text , password ) {
return callSjcl ( "encrypt" , text , password ) ;
2012-11-16 16:59:47 +00:00
} ;
2014-01-20 13:35:55 +00:00
this . decrypt = function ( text , password ) {
return callSjcl ( "decrypt" , text , password ) ;
2012-11-16 16:59:47 +00:00
} ;
} ;
2012-08-03 14:09:48 +00:00
/////////////////////////// Module mechanism
2012-04-30 11:23:03 +00:00
2013-04-25 08:05:02 +00:00
/ *
Execute the module named 'moduleName' . The name can optionally be relative to the module named 'moduleRoot'
* /
$tw . modules . execute = function ( moduleName , moduleRoot ) {
2016-11-23 18:13:26 +00:00
var name = moduleName ;
if ( moduleName . charAt ( 0 ) === "." ) {
name = $tw . utils . resolvePath ( moduleName , moduleRoot )
}
if ( ! $tw . modules . titles [ name ] ) {
if ( $tw . modules . titles [ name + ".js" ] ) {
name = name + ".js" ;
} else if ( $tw . modules . titles [ name + "/index.js" ] ) {
name = name + "/index.js" ;
} else if ( $tw . modules . titles [ moduleName ] ) {
name = moduleName ;
} else if ( $tw . modules . titles [ moduleName + ".js" ] ) {
name = moduleName + ".js" ;
} else if ( $tw . modules . titles [ moduleName + "/index.js" ] ) {
name = moduleName + "/index.js" ;
}
}
var moduleInfo = $tw . modules . titles [ name ] ,
tiddler = $tw . wiki . getTiddler ( name ) ,
2013-10-12 17:44:09 +00:00
_exports = { } ,
2013-04-25 08:05:02 +00:00
sandbox = {
2014-07-20 17:06:19 +00:00
module : { exports : _exports } ,
2013-10-12 17:44:09 +00:00
//moduleInfo: moduleInfo,
exports : _exports ,
2013-04-25 08:05:02 +00:00
console : console ,
setInterval : setInterval ,
clearInterval : clearInterval ,
setTimeout : setTimeout ,
clearTimeout : clearTimeout ,
2016-08-20 16:08:24 +00:00
Buffer : $tw . browser ? undefined : Buffer ,
2013-04-25 08:05:02 +00:00
$tw : $tw ,
require : function ( title ) {
2013-10-12 17:44:09 +00:00
return $tw . modules . execute ( title , name ) ;
2013-04-25 08:05:02 +00:00
}
} ;
2013-10-12 17:44:09 +00:00
Object . defineProperty ( sandbox . module , "id" , {
value : name ,
writable : false ,
enumerable : true ,
configurable : false
} ) ;
2013-04-25 08:05:02 +00:00
if ( ! $tw . browser ) {
$tw . utils . extend ( sandbox , {
process : process
} ) ;
2013-10-12 17:44:09 +00:00
} else {
/ *
CommonJS optional require . main property :
In a browser we offer a fake main module which points back to the boot function
( Theoretically , this may allow TW to eventually load itself as a module in the browser )
* /
Object . defineProperty ( sandbox . require , "main" , {
value : ( typeof ( require ) !== "undefined" ) ? require . main : { TiddlyWiki : _boot } ,
writable : false ,
enumerable : true ,
configurable : false
} ) ;
2013-04-25 08:05:02 +00:00
}
if ( ! moduleInfo ) {
2013-10-12 17:44:09 +00:00
// We could not find the module on this path
// Try to defer to browserify etc, or node
var deferredModule ;
2013-04-25 08:05:02 +00:00
if ( $tw . browser ) {
2013-10-12 17:44:09 +00:00
if ( window . require ) {
try {
return window . require ( moduleName ) ;
} catch ( e ) { }
}
2014-05-13 09:26:02 +00:00
throw "Cannot find module named '" + moduleName + "' required by module '" + moduleRoot + "', resolved to " + name ;
2013-04-25 08:05:02 +00:00
} else {
// If we don't have a module with that name, let node.js try to find it
return require ( moduleName ) ;
}
}
// Execute the module if we haven't already done so
if ( ! moduleInfo . exports ) {
try {
// Check the type of the definition
2013-07-03 17:36:51 +00:00
if ( typeof moduleInfo . definition === "function" ) { // Function
2013-10-12 17:44:09 +00:00
moduleInfo . exports = _exports ;
2013-07-03 17:36:51 +00:00
moduleInfo . definition ( moduleInfo , moduleInfo . exports , sandbox . require ) ;
} else if ( typeof moduleInfo . definition === "string" ) { // String
2013-10-12 17:44:09 +00:00
moduleInfo . exports = _exports ;
$tw . utils . evalSandboxed ( moduleInfo . definition , sandbox , tiddler . fields . title ) ;
2014-05-06 21:31:57 +00:00
if ( sandbox . module . exports ) {
moduleInfo . exports = sandbox . module . exports ; //more codemirror workaround
}
2013-04-25 08:05:02 +00:00
} else { // Object
moduleInfo . exports = moduleInfo . definition ;
}
} catch ( e ) {
2016-04-04 11:46:48 +00:00
if ( e instanceof SyntaxError ) {
var line = e . lineNumber || e . line ; // Firefox || Safari
if ( typeof ( line ) != "undefined" && line !== null ) {
$tw . utils . error ( "Syntax error in boot module " + name + ":" + line + ":\n" + e . stack ) ;
} else if ( ! $tw . browser ) {
// this is the only way to get node.js to display the line at which the syntax error appeared,
// and $tw.utils.error would exit anyway
// cf. https://bugs.chromium.org/p/v8/issues/detail?id=2589
throw e ;
} else {
// Opera: line number is included in e.message
// Chrome/IE: there's currently no way to get the line number
$tw . utils . error ( "Syntax error in boot module " + name + ": " + e . message + "\n" + e . stack ) ;
}
} else {
// line number should be included in e.stack for runtime errors
$tw . utils . error ( "Error executing boot module " + name + ": " + JSON . stringify ( e ) + "\n\n" + e . stack ) ;
}
2013-04-25 08:05:02 +00:00
}
}
// Return the exports of the module
return moduleInfo . exports ;
} ;
2012-04-30 11:23:03 +00:00
/ *
2012-11-14 11:23:43 +00:00
Apply a callback to each module of a particular type
moduleType : type of modules to enumerate
callback : function called as callback ( title , moduleExports ) for each module
2012-04-30 11:23:03 +00:00
* /
2012-11-14 11:23:43 +00:00
$tw . modules . forEachModuleOfType = function ( moduleType , callback ) {
var modules = $tw . modules . types [ moduleType ] ;
2014-03-16 21:23:10 +00:00
$tw . utils . each ( modules , function ( element , title ) {
2012-11-16 17:44:47 +00:00
callback ( title , $tw . modules . execute ( title ) ) ;
} ) ;
2012-11-14 11:23:43 +00:00
} ;
/ *
Get all the modules of a particular type in a hashmap by their ` name ` field
* /
$tw . modules . getModulesByTypeAsHashmap = function ( moduleType , nameField ) {
nameField = nameField || "name" ;
2014-04-06 21:49:20 +00:00
var results = Object . create ( null ) ;
2012-11-14 11:23:43 +00:00
$tw . modules . forEachModuleOfType ( moduleType , function ( title , module ) {
results [ module [ nameField ] ] = module ;
} ) ;
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-11-16 17:44:47 +00:00
$tw . modules . applyMethods = function ( moduleType , targetObject ) {
2012-12-13 21:31:19 +00:00
if ( ! targetObject ) {
2014-04-06 21:49:20 +00:00
targetObject = Object . create ( null ) ;
2012-12-13 21:31:19 +00:00
}
2012-11-14 11:23:43 +00:00
$tw . modules . forEachModuleOfType ( moduleType , function ( title , module ) {
2012-11-16 17:44:47 +00:00
$tw . utils . each ( module , function ( element , title , object ) {
targetObject [ title ] = module [ title ] ;
} ) ;
2012-11-14 11:23:43 +00:00
} ) ;
2012-12-13 21:31:19 +00:00
return targetObject ;
2012-04-30 11:23:03 +00:00
} ;
2021-02-03 15:13:56 +00:00
/ *
Return a class created from a modules . The module should export the properties to be added to those of the optional base class
* /
$tw . modules . createClassFromModule = function ( moduleExports , baseClass ) {
var newClass = function ( ) { } ;
if ( baseClass ) {
newClass . prototype = new baseClass ( ) ;
newClass . prototype . constructor = baseClass ;
}
$tw . utils . extend ( newClass . prototype , moduleExports ) ;
return newClass ;
} ;
2012-12-26 22:02:59 +00:00
/ *
Return an array of classes created from the modules of a specified type . Each module should export the properties to be added to those of the optional base class
* /
$tw . modules . createClassesFromModules = function ( moduleType , subType , baseClass ) {
2014-04-06 21:49:20 +00:00
var classes = Object . create ( null ) ;
2012-12-26 22:02:59 +00:00
$tw . modules . forEachModuleOfType ( moduleType , function ( title , moduleExports ) {
if ( ! subType || moduleExports . types [ subType ] ) {
2021-02-03 15:13:56 +00:00
classes [ moduleExports . name ] = $tw . modules . createClassFromModule ( moduleExports , baseClass ) ;
2012-12-26 22:02:59 +00:00
}
} ) ;
return classes ;
} ;
2012-04-30 11:23:03 +00:00
/////////////////////////// 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 */ ) {
2014-04-06 21:49:20 +00:00
this . fields = Object . create ( null ) ;
2017-02-21 08:31:05 +00:00
this . cache = Object . create ( null ) ;
2012-04-30 11:23:03 +00:00
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 ) {
2014-04-17 13:43:12 +00:00
if ( src [ t ] === undefined || src [ t ] === null ) {
2012-04-30 11:23:03 +00:00
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)
2014-03-17 08:58:42 +00:00
var fieldModule = $tw . Tiddler . fieldModules [ t ] ,
value ;
2013-06-13 08:16:07 +00:00
if ( fieldModule && fieldModule . parse ) {
2014-03-17 08:58:42 +00:00
value = fieldModule . parse . call ( this , src [ t ] ) ;
2012-04-30 11:23:03 +00:00
} else {
2014-03-17 08:58:42 +00:00
value = src [ t ] ;
2012-04-30 11:23:03 +00:00
}
2014-03-17 08:58:42 +00:00
// Freeze the field to keep it immutable
2015-03-07 19:47:43 +00:00
if ( value != null && typeof value === "object" ) {
2014-05-13 09:26:02 +00:00
Object . freeze ( value ) ;
2014-03-17 08:58:42 +00:00
}
this . fields [ t ] = value ;
2012-04-30 11:23:03 +00:00
}
}
}
2014-03-12 08:33:13 +00:00
// Freeze the tiddler against modification
Object . freeze ( this . fields ) ;
2016-02-05 17:39:02 +00:00
Object . freeze ( this ) ;
2012-04-30 11:23:03 +00:00
} ;
2012-11-12 22:16:49 +00:00
$tw . Tiddler . prototype . hasField = function ( field ) {
return $tw . utils . hop ( this . fields , field ) ;
} ;
2012-11-15 10:40:03 +00:00
2019-02-03 12:01:38 +00:00
/ *
Compare two tiddlers for equality
tiddler : the tiddler to compare
excludeFields : array of field names to exclude from the comparison
* /
$tw . Tiddler . prototype . isEqual = function ( tiddler , excludeFields ) {
if ( ! ( tiddler instanceof $tw . Tiddler ) ) {
return false ;
}
excludeFields = excludeFields || [ ] ;
var self = this ,
differences = [ ] ; // Fields that have differences
// Add to the differences array
function addDifference ( fieldName ) {
// Check for this field being excluded
if ( excludeFields . indexOf ( fieldName ) === - 1 ) {
// Save the field as a difference
$tw . utils . pushTop ( differences , fieldName ) ;
}
}
// Returns true if the two values of this field are equal
function isFieldValueEqual ( fieldName ) {
var valueA = self . fields [ fieldName ] ,
valueB = tiddler . fields [ fieldName ] ;
// Check for identical string values
if ( typeof ( valueA ) === "string" && typeof ( valueB ) === "string" && valueA === valueB ) {
return true ;
}
// Check for identical array values
if ( $tw . utils . isArray ( valueA ) && $tw . utils . isArray ( valueB ) && $tw . utils . isArrayEqual ( valueA , valueB ) ) {
return true ;
}
// Check for identical date values
if ( $tw . utils . isDate ( valueA ) && $tw . utils . isDate ( valueB ) && valueA . getTime ( ) === valueB . getTime ( ) ) {
return true ;
}
// Otherwise the fields must be different
return false ;
}
// Compare our fields
for ( var fieldName in this . fields ) {
if ( ! isFieldValueEqual ( fieldName ) ) {
addDifference ( fieldName ) ;
}
}
// There's a difference for every field in the other tiddler that we don't have
for ( fieldName in tiddler . fields ) {
if ( ! ( fieldName in this . fields ) ) {
addDifference ( fieldName ) ;
}
}
// Return whether there were any differences
return differences . length === 0 ;
} ;
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-11-15 10:40:03 +00:00
$tw . modules . define ( "$:/boot/tiddlerfields/modified" , "tiddlerfield" , {
2012-04-30 11:23:03 +00:00
name : "modified" ,
parse : $tw . utils . parseDate ,
stringify : $tw . utils . stringifyDate
} ) ;
2012-11-15 10:40:03 +00:00
$tw . modules . define ( "$:/boot/tiddlerfields/created" , "tiddlerfield" , {
2012-04-30 11:23:03 +00:00
name : "created" ,
parse : $tw . utils . parseDate ,
stringify : $tw . utils . stringifyDate
} ) ;
2013-06-13 08:16:07 +00:00
$tw . modules . define ( "$:/boot/tiddlerfields/color" , "tiddlerfield" , {
name : "color" ,
2013-11-07 19:39:50 +00:00
editTag : "input" ,
2013-06-13 08:16:07 +00:00
editType : "color"
} ) ;
2012-11-15 10:40:03 +00:00
$tw . modules . define ( "$:/boot/tiddlerfields/tags" , "tiddlerfield" , {
2012-04-30 11:23:03 +00:00
name : "tags" ,
parse : $tw . utils . parseStringArray ,
2013-08-07 15:05:56 +00:00
stringify : $tw . utils . stringifyList
} ) ;
$tw . modules . define ( "$:/boot/tiddlerfields/list" , "tiddlerfield" , {
name : "list" ,
parse : $tw . utils . parseStringArray ,
stringify : $tw . utils . stringifyList
2012-04-30 11:23:03 +00:00
} ) ;
/////////////////////////// Barebones wiki store
/ *
2014-03-17 10:50:18 +00:00
Wiki constructor . State is stored in private members that only a small number of privileged accessor methods have direct access . Methods added via the prototype have to use these accessors and cannot access the state data directly .
options include :
2019-07-16 15:53:37 +00:00
enableIndexers - Array of indexer names to enable , or null to use all available indexers
2012-04-30 11:23:03 +00:00
* /
2014-03-17 10:50:18 +00:00
$tw . Wiki = function ( options ) {
options = options || { } ;
2014-03-16 21:23:10 +00:00
var self = this ,
2014-04-06 21:43:10 +00:00
tiddlers = Object . create ( null ) , // Hashmap of tiddlers
2017-12-21 22:01:52 +00:00
tiddlerTitles = null , // Array of tiddler titles
getTiddlerTitles = function ( ) {
if ( ! tiddlerTitles ) {
2022-02-21 15:34:05 +00:00
tiddlerTitles = Object . keys ( tiddlers ) . sort ( function ( a , b ) { return a . localeCompare ( b ) ; } ) ;
2017-12-21 22:01:52 +00:00
}
return tiddlerTitles ;
} ,
2014-03-17 10:50:18 +00:00
pluginTiddlers = [ ] , // Array of tiddlers containing registered plugins, ordered by priority
2014-04-06 21:43:10 +00:00
pluginInfo = Object . create ( null ) , // Hashmap of parsed plugin content
2019-07-16 15:53:37 +00:00
shadowTiddlers = Object . create ( null ) , // Hashmap by title of {source:, tiddler:}
2017-12-21 22:01:52 +00:00
shadowTiddlerTitles = null ,
getShadowTiddlerTitles = function ( ) {
if ( ! shadowTiddlerTitles ) {
shadowTiddlerTitles = Object . keys ( shadowTiddlers ) ;
}
return shadowTiddlerTitles ;
2019-05-24 20:07:37 +00:00
} ,
2019-07-16 15:53:37 +00:00
enableIndexers = options . enableIndexers || null ,
2019-05-24 20:07:37 +00:00
indexers = [ ] ,
indexersByName = Object . create ( null ) ;
this . addIndexer = function ( indexer , name ) {
// Bail if this indexer is not enabled
if ( enableIndexers && enableIndexers . indexOf ( name ) === - 1 ) {
return ;
}
indexers . push ( indexer ) ;
indexersByName [ name ] = indexer ;
2019-07-16 15:53:37 +00:00
indexer . init ( ) ;
2019-05-24 20:07:37 +00:00
} ;
this . getIndexer = function ( name ) {
return indexersByName [ name ] || null ;
} ;
2014-03-17 10:50:18 +00:00
// Add a tiddler to the store
2014-03-16 21:23:10 +00:00
this . addTiddler = function ( tiddler ) {
if ( ! ( tiddler instanceof $tw . Tiddler ) ) {
tiddler = new $tw . Tiddler ( tiddler ) ;
}
// Save the tiddler
2014-03-17 10:50:18 +00:00
if ( tiddler ) {
var title = tiddler . fields . title ;
if ( title ) {
2015-02-10 11:30:36 +00:00
// Uncomment the following line for detailed logs of all tiddler writes
// console.log("Adding",title,tiddler)
2019-07-28 15:39:34 +00:00
// Record the old tiddler state
var updateDescriptor = {
old : {
tiddler : this . getTiddler ( title ) ,
shadow : this . isShadowTiddler ( title ) ,
exists : this . tiddlerExists ( title )
}
}
// Save the new tiddler
2014-03-17 10:50:18 +00:00
tiddlers [ title ] = tiddler ;
2022-02-21 15:34:05 +00:00
// Check we've got the title
tiddlerTitles = $tw . utils . insertSortedArray ( tiddlerTitles || [ ] , title ) ;
2019-07-28 15:39:34 +00:00
// Record the new tiddler state
updateDescriptor [ "new" ] = {
tiddler : tiddler ,
shadow : this . isShadowTiddler ( title ) ,
exists : this . tiddlerExists ( title )
}
// Update indexes
2014-03-17 10:50:18 +00:00
this . clearCache ( title ) ;
this . clearGlobalCache ( ) ;
2019-05-24 20:07:37 +00:00
$tw . utils . each ( indexers , function ( indexer ) {
2019-07-28 15:39:34 +00:00
indexer . update ( updateDescriptor ) ;
2019-05-24 20:07:37 +00:00
} ) ;
2019-07-28 15:39:34 +00:00
// Queue a change event
2014-05-13 09:26:02 +00:00
this . enqueueTiddlerEvent ( title ) ;
2014-03-17 10:50:18 +00:00
}
2014-03-16 21:23:10 +00:00
}
} ;
2014-03-17 10:50:18 +00:00
// Delete a tiddler
this . deleteTiddler = function ( title ) {
2015-05-13 17:31:28 +00:00
// Uncomment the following line for detailed logs of all tiddler deletions
2016-10-06 13:50:54 +00:00
// console.log("Deleting",title)
2015-08-31 15:25:03 +00:00
if ( $tw . utils . hop ( tiddlers , title ) ) {
2019-07-28 15:39:34 +00:00
// Record the old tiddler state
var updateDescriptor = {
old : {
tiddler : this . getTiddler ( title ) ,
shadow : this . isShadowTiddler ( title ) ,
exists : this . tiddlerExists ( title )
}
}
// Delete the tiddler
2015-08-31 14:48:32 +00:00
delete tiddlers [ title ] ;
2019-07-28 15:39:34 +00:00
// Delete it from the list of titles
2017-12-21 22:01:52 +00:00
if ( tiddlerTitles ) {
var index = tiddlerTitles . indexOf ( title ) ;
if ( index !== - 1 ) {
tiddlerTitles . splice ( index , 1 ) ;
2021-05-30 18:20:17 +00:00
}
2017-12-21 22:01:52 +00:00
}
2019-07-28 15:39:34 +00:00
// Record the new tiddler state
updateDescriptor [ "new" ] = {
tiddler : this . getTiddler ( title ) ,
shadow : this . isShadowTiddler ( title ) ,
exists : this . tiddlerExists ( title )
}
// Update indexes
2015-08-31 14:48:32 +00:00
this . clearCache ( title ) ;
this . clearGlobalCache ( ) ;
2019-05-24 20:07:37 +00:00
$tw . utils . each ( indexers , function ( indexer ) {
2019-07-28 15:39:34 +00:00
indexer . update ( updateDescriptor ) ;
2019-05-24 20:07:37 +00:00
} ) ;
2019-07-28 15:39:34 +00:00
// Queue a change event
2015-08-31 14:48:32 +00:00
this . enqueueTiddlerEvent ( title , true ) ;
}
2014-03-17 10:50:18 +00:00
} ;
// Get a tiddler from the store
2014-03-16 21:23:10 +00:00
this . getTiddler = function ( title ) {
2016-07-22 10:31:02 +00:00
if ( title ) {
var t = tiddlers [ title ] ;
if ( t instanceof $tw . Tiddler ) {
return t ;
2017-12-21 22:01:52 +00:00
} else if ( title !== undefined && shadowTiddlers [ title ] ) {
2016-07-22 10:31:02 +00:00
return shadowTiddlers [ title ] . tiddler ;
}
2014-03-16 21:23:10 +00:00
return undefined ;
}
} ;
2014-03-17 10:50:18 +00:00
2014-04-05 16:31:36 +00:00
// Get an array of all tiddler titles
this . allTitles = function ( ) {
2017-12-21 22:01:52 +00:00
return getTiddlerTitles ( ) . slice ( 0 ) ;
2014-03-16 21:23:10 +00:00
} ;
2014-03-17 10:50:18 +00:00
// Iterate through all tiddler titles
2014-03-16 21:23:10 +00:00
this . each = function ( callback ) {
2017-12-21 22:01:52 +00:00
var titles = getTiddlerTitles ( ) ,
index , titlesLength , title ;
for ( index = 0 , titlesLength = titles . length ; index < titlesLength ; index ++ ) {
title = titles [ index ] ;
2014-03-16 21:23:10 +00:00
callback ( tiddlers [ title ] , title ) ;
}
} ;
2014-03-17 10:50:18 +00:00
2014-04-05 16:31:36 +00:00
// Get an array of all shadow tiddler titles
this . allShadowTitles = function ( ) {
2017-12-21 22:01:52 +00:00
return getShadowTiddlerTitles ( ) . slice ( 0 ) ;
2014-04-05 16:31:36 +00:00
} ;
2014-03-17 10:50:18 +00:00
// Iterate through all shadow tiddler titles
this . eachShadow = function ( callback ) {
2017-12-21 22:01:52 +00:00
var titles = getShadowTiddlerTitles ( ) ,
index , titlesLength , title ;
for ( index = 0 , titlesLength = titles . length ; index < titlesLength ; index ++ ) {
title = titles [ index ] ;
2021-09-16 19:08:11 +00:00
if ( tiddlers [ title ] ) {
callback ( tiddlers [ title ] , title ) ;
} else {
var shadowInfo = shadowTiddlers [ title ] ;
callback ( shadowInfo . tiddler , title ) ;
}
2014-03-17 10:50:18 +00:00
}
} ;
2014-04-30 21:49:28 +00:00
// Iterate through all tiddlers and then the shadows
this . eachTiddlerPlusShadows = function ( callback ) {
2017-12-21 22:01:52 +00:00
var index , titlesLength , title ,
titles = getTiddlerTitles ( ) ;
for ( index = 0 , titlesLength = titles . length ; index < titlesLength ; index ++ ) {
title = titles [ index ] ;
2014-04-30 21:49:28 +00:00
callback ( tiddlers [ title ] , title ) ;
}
2017-12-21 22:01:52 +00:00
titles = getShadowTiddlerTitles ( ) ;
for ( index = 0 , titlesLength = titles . length ; index < titlesLength ; index ++ ) {
title = titles [ index ] ;
if ( ! tiddlers [ title ] ) {
2014-04-30 21:49:28 +00:00
var shadowInfo = shadowTiddlers [ title ] ;
callback ( shadowInfo . tiddler , title ) ;
}
}
} ;
// Iterate through all the shadows and then the tiddlers
this . eachShadowPlusTiddlers = function ( callback ) {
2017-12-21 22:01:52 +00:00
var index , titlesLength , title ,
titles = getShadowTiddlerTitles ( ) ;
for ( index = 0 , titlesLength = titles . length ; index < titlesLength ; index ++ ) {
title = titles [ index ] ;
if ( tiddlers [ title ] ) {
2014-05-17 00:12:08 +00:00
callback ( tiddlers [ title ] , title ) ;
} else {
var shadowInfo = shadowTiddlers [ title ] ;
callback ( shadowInfo . tiddler , title ) ;
}
2014-04-30 21:49:28 +00:00
}
2017-12-21 22:01:52 +00:00
titles = getTiddlerTitles ( ) ;
for ( index = 0 , titlesLength = titles . length ; index < titlesLength ; index ++ ) {
title = titles [ index ] ;
if ( ! shadowTiddlers [ title ] ) {
2014-04-30 21:49:28 +00:00
callback ( tiddlers [ title ] , title ) ;
}
}
} ;
2014-11-24 09:22:43 +00:00
// Test for the existence of a tiddler (excludes shadow tiddlers)
2014-03-16 21:23:10 +00:00
this . tiddlerExists = function ( title ) {
return ! ! $tw . utils . hop ( tiddlers , title ) ;
} ;
2014-03-17 10:50:18 +00:00
// Determines if a tiddler is a shadow tiddler, regardless of whether it has been overridden by a real tiddler
this . isShadowTiddler = function ( title ) {
return $tw . utils . hop ( shadowTiddlers , title ) ;
2014-03-16 21:23:10 +00:00
} ;
2012-04-30 11:23:03 +00:00
2014-03-17 10:50:18 +00:00
this . getShadowSource = function ( title ) {
if ( $tw . utils . hop ( shadowTiddlers , title ) ) {
return shadowTiddlers [ title ] . source ;
}
return null ;
} ;
2012-04-30 11:23:03 +00:00
2019-09-16 11:15:39 +00:00
// Get an array of all the currently recognised plugin types
this . getPluginTypes = function ( ) {
var types = [ ] ;
$tw . utils . each ( pluginTiddlers , function ( pluginTiddler ) {
var pluginType = pluginTiddler . fields [ "plugin-type" ] ;
if ( pluginType && types . indexOf ( pluginType ) === - 1 ) {
types . push ( pluginType ) ;
2014-03-17 10:50:18 +00:00
}
2019-09-16 11:15:39 +00:00
} ) ;
return types ;
} ;
2012-04-30 11:23:03 +00:00
2019-09-16 11:15:39 +00:00
// Read plugin info for all plugins, or just an array of titles. Returns the number of plugins updated or deleted
this . readPluginInfo = function ( titles ) {
var results = {
modifiedPlugins : [ ] ,
deletedPlugins : [ ]
} ;
$tw . utils . each ( titles || getTiddlerTitles ( ) , function ( title ) {
var tiddler = tiddlers [ title ] ;
if ( tiddler ) {
2020-10-23 14:37:20 +00:00
if ( tiddler . fields . type === "application/json" && tiddler . hasField ( "plugin-type" ) && tiddler . fields . text ) {
2022-02-21 15:29:25 +00:00
pluginInfo [ tiddler . fields . title ] = $tw . utils . parseJSONSafe ( tiddler . fields . text ) ;
2019-09-16 11:15:39 +00:00
results . modifiedPlugins . push ( tiddler . fields . title ) ;
}
} else {
if ( pluginInfo [ title ] ) {
2021-05-30 18:20:17 +00:00
delete pluginInfo [ title ] ;
2019-09-16 11:15:39 +00:00
results . deletedPlugins . push ( title ) ;
}
}
} ) ;
return results ;
2014-03-17 10:50:18 +00:00
} ;
2014-03-14 15:23:12 +00:00
2014-03-18 21:18:30 +00:00
// Get plugin info for a plugin
this . getPluginInfo = function ( title ) {
return pluginInfo [ title ] ;
} ;
2019-09-16 11:15:39 +00:00
// Register the plugin tiddlers of a particular type, or null/undefined for any type, optionally restricting registration to an array of tiddler titles. Return the array of titles affected
2014-03-17 10:50:18 +00:00
this . registerPluginTiddlers = function ( pluginType , titles ) {
var self = this ,
registeredTitles = [ ] ,
2014-08-15 20:10:40 +00:00
checkTiddler = function ( tiddler , title ) {
2019-09-16 11:15:39 +00:00
if ( tiddler && tiddler . fields . type === "application/json" && tiddler . fields [ "plugin-type" ] && ( ! pluginType || tiddler . fields [ "plugin-type" ] === pluginType ) ) {
2014-08-15 20:10:40 +00:00
var disablingTiddler = self . getTiddler ( "$:/config/Plugins/Disabled/" + title ) ;
if ( title === "$:/core" || ! disablingTiddler || ( disablingTiddler . fields . text || "" ) . trim ( ) !== "yes" ) {
2019-09-16 11:15:39 +00:00
self . unregisterPluginTiddlers ( null , [ title ] ) ; // Unregister the plugin if it's already registered
2014-08-15 20:10:40 +00:00
pluginTiddlers . push ( tiddler ) ;
registeredTitles . push ( tiddler . fields . title ) ;
}
2014-03-17 10:50:18 +00:00
}
} ;
if ( titles ) {
$tw . utils . each ( titles , function ( title ) {
2014-08-15 20:10:40 +00:00
checkTiddler ( self . getTiddler ( title ) , title ) ;
2014-03-17 10:50:18 +00:00
} ) ;
} else {
this . each ( function ( tiddler , title ) {
2014-08-15 20:10:40 +00:00
checkTiddler ( tiddler , title ) ;
2014-03-17 10:50:18 +00:00
} ) ;
2012-08-06 21:34:16 +00:00
}
2014-03-17 10:50:18 +00:00
return registeredTitles ;
2013-04-30 21:56:17 +00:00
} ;
2019-09-16 11:15:39 +00:00
// Unregister the plugin tiddlers of a particular type, or null/undefined for any type, optionally restricting unregistering to an array of tiddler titles. Returns an array of the titles affected
this . unregisterPluginTiddlers = function ( pluginType , titles ) {
2014-03-17 10:50:18 +00:00
var self = this ,
2019-09-16 11:15:39 +00:00
unregisteredTitles = [ ] ;
2014-03-17 10:50:18 +00:00
// Remove any previous registered plugins of this type
for ( var t = pluginTiddlers . length - 1 ; t >= 0 ; t -- ) {
var tiddler = pluginTiddlers [ t ] ;
2019-09-16 11:15:39 +00:00
if ( tiddler . fields [ "plugin-type" ] && ( ! pluginType || tiddler . fields [ "plugin-type" ] === pluginType ) && ( ! titles || titles . indexOf ( tiddler . fields . title ) !== - 1 ) ) {
unregisteredTitles . push ( tiddler . fields . title ) ;
2014-03-17 10:50:18 +00:00
pluginTiddlers . splice ( t , 1 ) ;
}
2013-04-30 21:56:17 +00:00
}
2019-09-16 11:15:39 +00:00
return unregisteredTitles ;
2014-03-17 10:50:18 +00:00
} ;
2013-04-30 21:56:17 +00:00
2014-03-17 10:50:18 +00:00
// Unpack the currently registered plugins, creating shadow tiddlers for their constituent tiddlers
this . unpackPluginTiddlers = function ( ) {
var self = this ;
// Sort the plugin titles by the `plugin-priority` field
pluginTiddlers . sort ( function ( a , b ) {
if ( "plugin-priority" in a . fields && "plugin-priority" in b . fields ) {
return a . fields [ "plugin-priority" ] - b . fields [ "plugin-priority" ] ;
} else if ( "plugin-priority" in a . fields ) {
return - 1 ;
} else if ( "plugin-priority" in b . fields ) {
return + 1 ;
} else if ( a . fields . title < b . fields . title ) {
return - 1 ;
} else if ( a . fields . title === b . fields . title ) {
return 0 ;
} else {
return + 1 ;
2014-01-12 21:48:18 +00:00
}
2013-04-08 17:37:49 +00:00
} ) ;
2014-03-17 10:50:18 +00:00
// Now go through the plugins in ascending order and assign the shadows
2014-04-06 21:49:20 +00:00
shadowTiddlers = Object . create ( null ) ;
2014-03-17 10:50:18 +00:00
$tw . utils . each ( pluginTiddlers , function ( tiddler ) {
// Extract the constituent tiddlers
2014-03-31 17:30:45 +00:00
if ( $tw . utils . hop ( pluginInfo , tiddler . fields . title ) ) {
$tw . utils . each ( pluginInfo [ tiddler . fields . title ] . tiddlers , function ( constituentTiddler , constituentTitle ) {
// Save the tiddler object
if ( constituentTitle ) {
shadowTiddlers [ constituentTitle ] = {
source : tiddler . fields . title ,
tiddler : new $tw . Tiddler ( constituentTiddler , { title : constituentTitle } )
} ;
}
} ) ;
}
2014-03-17 10:50:18 +00:00
} ) ;
2017-12-21 22:01:52 +00:00
shadowTiddlerTitles = null ;
2015-08-02 21:22:33 +00:00
this . clearCache ( null ) ;
this . clearGlobalCache ( ) ;
2019-05-24 20:07:37 +00:00
$tw . utils . each ( indexers , function ( indexer ) {
indexer . rebuild ( ) ;
} ) ;
2014-03-17 10:50:18 +00:00
} ;
2019-05-24 20:07:37 +00:00
if ( this . addIndexersToWiki ) {
this . addIndexersToWiki ( ) ;
}
2014-03-17 10:50:18 +00:00
} ;
// Dummy methods that will be filled in after boot
2014-05-13 09:26:02 +00:00
$tw . Wiki . prototype . clearCache =
$tw . Wiki . prototype . clearGlobalCache =
2014-03-17 10:50:18 +00:00
$tw . Wiki . prototype . enqueueTiddlerEvent = function ( ) { } ;
// Add an array of tiddlers
$tw . Wiki . prototype . addTiddlers = function ( tiddlers ) {
for ( var t = 0 ; t < tiddlers . length ; t ++ ) {
this . addTiddler ( tiddlers [ t ] ) ;
2014-05-13 09:26:02 +00:00
}
2012-08-06 21:34:16 +00:00
} ;
2012-08-30 14:00:08 +00:00
/ *
2012-11-15 10:40:03 +00:00
Define all modules stored in ordinary tiddlers
2012-08-30 14:00:08 +00:00
* /
2012-11-15 10:40:03 +00:00
$tw . Wiki . prototype . defineTiddlerModules = function ( ) {
2014-03-16 21:23:10 +00:00
this . each ( function ( tiddler , title ) {
2012-11-17 13:08:25 +00:00
if ( tiddler . hasField ( "module-type" ) ) {
switch ( tiddler . fields . type ) {
case "application/javascript" :
2013-04-10 15:56:17 +00:00
// We only define modules that haven't already been defined, because in the browser modules in system tiddlers are defined in inline script
if ( ! $tw . utils . hop ( $tw . modules . titles , tiddler . fields . title ) ) {
2012-11-17 13:08:25 +00:00
$tw . modules . define ( tiddler . fields . title , tiddler . fields [ "module-type" ] , tiddler . fields . text ) ;
}
break ;
case "application/json" :
2022-02-21 15:29:25 +00:00
$tw . modules . define ( tiddler . fields . title , tiddler . fields [ "module-type" ] , $tw . utils . parseJSONSafe ( tiddler . fields . text ) ) ;
2012-11-17 13:08:25 +00:00
break ;
case "application/x-tiddler-dictionary" :
$tw . modules . define ( tiddler . fields . title , tiddler . fields [ "module-type" ] , $tw . utils . parseFields ( tiddler . fields . text ) ) ;
break ;
}
2012-08-31 11:34:39 +00:00
}
2012-11-16 17:44:47 +00:00
} ) ;
2012-11-15 10:40:03 +00:00
} ;
/ *
Register all the module tiddlers that have a module type
* /
2013-04-30 21:56:17 +00:00
$tw . Wiki . prototype . defineShadowModules = function ( ) {
2012-11-16 17:44:47 +00:00
var self = this ;
2014-03-17 10:50:18 +00:00
this . eachShadow ( function ( tiddler , title ) {
// Don't define the module if it is overidden by an ordinary tiddler
if ( ! self . tiddlerExists ( title ) && tiddler . hasField ( "module-type" ) ) {
// Define the module
$tw . modules . define ( tiddler . fields . title , tiddler . fields [ "module-type" ] , tiddler . fields . text ) ;
2012-08-30 14:00:08 +00:00
}
2012-11-16 17:44:47 +00:00
} ) ;
2012-08-30 14:00:08 +00:00
} ;
2014-04-19 08:36:08 +00:00
/ *
Enable safe mode by deleting any tiddlers that override a shadow tiddler
* /
$tw . Wiki . prototype . processSafeMode = function ( ) {
var self = this ,
overrides = [ ] ;
// Find the overriding tiddlers
this . each ( function ( tiddler , title ) {
if ( self . isShadowTiddler ( title ) ) {
2014-05-15 17:50:14 +00:00
console . log ( title ) ;
2014-04-19 08:36:08 +00:00
overrides . push ( title ) ;
}
} ) ;
// Assemble a report tiddler
var titleReportTiddler = "TiddlyWiki Safe Mode" ,
report = [ ] ;
2017-11-11 11:56:20 +00:00
report . push ( "TiddlyWiki has been started in [[safe mode|https://tiddlywiki.com/static/SafeMode.html]]. All plugins are temporarily disabled. Most customisations have been disabled by renaming the following tiddlers:" )
2014-04-19 08:36:08 +00:00
// Delete the overrides
overrides . forEach ( function ( title ) {
var tiddler = self . getTiddler ( title ) ,
newTitle = "SAFE: " + title ;
self . deleteTiddler ( title ) ;
self . addTiddler ( new $tw . Tiddler ( tiddler , { title : newTitle } ) ) ;
report . push ( "* [[" + title + "|" + newTitle + "]]" ) ;
} ) ;
report . push ( )
this . addTiddler ( new $tw . Tiddler ( { title : titleReportTiddler , text : report . join ( "\n\n" ) } ) ) ;
// Set $:/DefaultTiddlers to point to our report
this . addTiddler ( new $tw . Tiddler ( { title : "$:/DefaultTiddlers" , text : "[[" + titleReportTiddler + "]]" } ) ) ;
} ;
2012-04-30 11:23:03 +00:00
/ *
Extracts tiddlers from a typed block of text , specifying default field values
* /
2017-07-12 15:42:16 +00:00
$tw . Wiki . prototype . deserializeTiddlers = function ( type , text , srcFields , options ) {
2014-04-06 21:49:20 +00:00
srcFields = srcFields || Object . create ( null ) ;
2017-07-12 15:42:16 +00:00
options = options || { } ;
var deserializer = $tw . Wiki . tiddlerDeserializerModules [ options . deserializer ] ,
2014-04-06 21:49:20 +00:00
fields = Object . create ( null ) ;
2017-07-12 15:42:16 +00:00
if ( ! deserializer ) {
deserializer = $tw . Wiki . tiddlerDeserializerModules [ type ] ;
}
2015-02-01 18:33:40 +00:00
if ( ! deserializer && $tw . utils . getFileExtensionInfo ( 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
2015-02-01 18:33:40 +00:00
type = $tw . utils . getFileExtensionInfo ( type ) . type ;
2012-08-03 14:09:48 +00:00
deserializer = $tw . Wiki . tiddlerDeserializerModules [ type ] ;
2012-04-30 11:23:03 +00:00
}
2014-04-08 21:20:51 +00:00
if ( ! deserializer && $tw . config . contentTypeInfo [ type ] ) {
// see if this type has a different deserializer registered with it
2014-04-08 21:26:01 +00:00
type = $tw . config . contentTypeInfo [ type ] . deserializerType ;
2014-04-08 21:20:51 +00:00
deserializer = $tw . Wiki . tiddlerDeserializerModules [ type ] ;
}
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 ) {
2013-04-01 17:55:49 +00:00
return deserializer . call ( this , text , fields , type ) ;
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
* /
2018-05-04 16:38:30 +00:00
var deserializeHeaderComment = 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 ] ;
2018-05-04 16:38:30 +00:00
} ;
$tw . modules . define ( "$:/boot/tiddlerdeserializer/js" , "tiddlerdeserializer" , {
"application/javascript" : deserializeHeaderComment
} ) ;
$tw . modules . define ( "$:/boot/tiddlerdeserializer/css" , "tiddlerdeserializer" , {
2018-05-04 16:49:21 +00:00
"text/css" : deserializeHeaderComment
2012-04-30 11:23:03 +00:00
} ) ;
2012-11-15 10:40:03 +00:00
$tw . modules . define ( "$:/boot/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 ) ;
2012-12-20 09:20:44 +00:00
if ( split . length >= 1 ) {
2012-05-09 12:48:34 +00:00
fields = $tw . utils . parseFields ( split [ 0 ] , fields ) ;
2012-12-20 09:20:44 +00:00
}
if ( split . length >= 2 ) {
2012-05-09 12:48:34 +00:00
fields . text = split . slice ( 1 ) . join ( "\n\n" ) ;
2012-04-30 11:23:03 +00:00
}
return [ fields ] ;
}
} ) ;
2014-02-09 20:34:42 +00:00
$tw . modules . define ( "$:/boot/tiddlerdeserializer/tids" , "tiddlerdeserializer" , {
"application/x-tiddlers" : function ( text , fields ) {
2014-02-21 09:24:36 +00:00
var titles = [ ] ,
tiddlers = [ ] ,
match = /\r?\n\r?\n/mg . exec ( text ) ;
if ( match ) {
fields = $tw . utils . parseFields ( text . substr ( 0 , match . index ) , fields ) ;
var lines = text . substr ( match . index + match [ 0 ] . length ) . split ( /\r?\n/mg ) ;
2014-02-09 20:34:42 +00:00
for ( var t = 0 ; t < lines . length ; t ++ ) {
var line = lines [ t ] ;
if ( line . charAt ( 0 ) !== "#" ) {
2016-08-18 07:58:54 +00:00
var colonPos = line . indexOf ( ":" ) ;
2014-02-21 09:24:36 +00:00
if ( colonPos !== - 1 ) {
2014-04-06 21:49:20 +00:00
var tiddler = $tw . utils . extend ( Object . create ( null ) , fields ) ;
2016-08-18 07:58:54 +00:00
tiddler . title = ( tiddler . title || "" ) + line . substr ( 0 , colonPos ) . trim ( ) ;
2014-02-21 09:24:36 +00:00
if ( titles . indexOf ( tiddler . title ) !== - 1 ) {
console . log ( "Warning: .multids file contains multiple definitions for " + tiddler . title ) ;
}
titles . push ( tiddler . title ) ;
2016-08-18 07:58:54 +00:00
tiddler . text = line . substr ( colonPos + 2 ) . trim ( ) ;
2014-02-09 20:34:42 +00:00
tiddlers . push ( tiddler ) ;
}
}
}
}
return tiddlers ;
}
} ) ;
2012-11-15 10:40:03 +00:00
$tw . modules . define ( "$:/boot/tiddlerdeserializer/txt" , "tiddlerdeserializer" , {
2013-04-01 17:55:49 +00:00
"text/plain" : function ( text , fields , type ) {
2012-06-06 20:42:14 +00:00
fields . text = text ;
2013-04-01 17:55:49 +00:00
fields . type = type || "text/plain" ;
2012-06-06 20:42:14 +00:00
return [ fields ] ;
}
} ) ;
2012-11-15 10:40:03 +00:00
$tw . modules . define ( "$:/boot/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-11-15 10:40:03 +00:00
$tw . modules . define ( "$:/boot/tiddlerdeserializer/json" , "tiddlerdeserializer" , {
2012-10-27 13:51:43 +00:00
"application/json" : function ( text , fields ) {
2019-04-13 13:47:27 +00:00
var isTiddlerValid = function ( data ) {
// Not valid if it's not an object with a title property
if ( typeof ( data ) !== "object" || ! $tw . utils . hop ( data , "title" ) ) {
return false ;
}
for ( var f in data ) {
if ( $tw . utils . hop ( data , f ) ) {
2021-07-22 15:55:17 +00:00
// Check field name doesn't contain control characters
if ( typeof ( data [ f ] ) !== "string" || /[\x00-\x1F]/ . test ( f ) ) {
2019-04-13 13:47:27 +00:00
return false ;
}
}
}
return true ;
} ,
isTiddlerArrayValid = function ( data ) {
for ( var t = 0 ; t < data . length ; t ++ ) {
if ( ! isTiddlerValid ( data [ t ] ) ) {
return false ;
}
}
return true ;
} ,
2022-02-21 15:29:25 +00:00
data = $tw . utils . parseJSONSafe ( text ) ;
2019-04-13 13:47:27 +00:00
if ( $tw . utils . isArray ( data ) && isTiddlerArrayValid ( data ) ) {
return data ;
} else if ( isTiddlerValid ( data ) ) {
return [ data ] ;
} else {
// Plain JSON file
fields . text = text ;
fields . type = "application/json" ;
return [ fields ] ;
}
2012-10-27 13:51:43 +00:00
}
} ) ;
2012-06-06 20:42:14 +00:00
2012-04-30 11:23:03 +00:00
/////////////////////////// Browser definitions
2014-05-07 08:59:21 +00:00
if ( $tw . browser && ! $tw . node ) {
2012-04-30 11:23:03 +00:00
2012-11-16 16:59:47 +00:00
/ *
2013-01-31 10:20:13 +00:00
Decrypt any tiddlers stored within the element with the ID "encryptedArea" . The function is asynchronous to allow the user to be prompted for a password
callback : function to be called the decryption is complete
2012-11-16 16:59:47 +00:00
* /
2012-11-16 17:23:10 +00:00
$tw . boot . decryptEncryptedTiddlers = function ( callback ) {
2013-01-31 10:20:13 +00:00
var encryptedArea = document . getElementById ( "encryptedStoreArea" ) ;
2012-11-16 16:59:47 +00:00
if ( encryptedArea ) {
2014-10-06 09:02:34 +00:00
var encryptedText = encryptedArea . innerHTML ,
prompt = "Enter a password to decrypt this TiddlyWiki" ;
2012-11-16 17:23:10 +00:00
// Prompt for the password
2014-10-06 09:02:34 +00:00
if ( $tw . utils . hop ( $tw . boot , "encryptionPrompts" ) ) {
prompt = $tw . boot . encryptionPrompts . decrypt ;
}
2012-11-16 17:23:10 +00:00
$tw . passwordPrompt . createPrompt ( {
2014-10-06 09:02:34 +00:00
serviceName : prompt ,
2012-11-16 17:23:10 +00:00
noUserName : true ,
submitText : "Decrypt" ,
callback : function ( data ) {
// Attempt to decrypt the tiddlers
$tw . crypto . setPassword ( data . password ) ;
2013-01-31 10:20:13 +00:00
var decryptedText = $tw . crypto . decrypt ( encryptedText ) ;
if ( decryptedText ) {
2022-02-21 15:29:25 +00:00
var json = $tw . utils . parseJSONSafe ( decryptedText ) ;
2013-01-31 10:20:13 +00:00
for ( var title in json ) {
$tw . preloadTiddler ( json [ title ] ) ;
2012-11-16 17:23:10 +00:00
}
// Call the callback
callback ( ) ;
// Exit and remove the password prompt
return true ;
} else {
// We didn't decrypt everything, so continue to prompt for password
return false ;
}
}
} ) ;
2012-11-16 16:59:47 +00:00
} else {
2013-01-31 10:20:13 +00:00
// Just invoke the callback straight away if there weren't any encrypted tiddlers
2012-11-16 17:23:10 +00:00
callback ( ) ;
2012-11-16 16:59:47 +00:00
}
} ;
2012-04-30 11:23:03 +00:00
/ *
Register a deserializer that can extract tiddlers from the DOM
* /
2012-11-15 10:40:03 +00:00
$tw . modules . define ( "$:/boot/tiddlerdeserializer/dom" , "tiddlerdeserializer" , {
2012-05-03 20:47:16 +00:00
"(DOM)" : function ( node ) {
2012-09-02 19:29:47 +00:00
var extractTextTiddlers = function ( node ) {
2012-04-30 11:23:03 +00:00
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 -- ) {
2012-08-31 10:38:30 +00:00
tiddler [ attrs [ i ] . name ] = attrs [ i ] . value ;
2012-04-30 11:23:03 +00:00
}
2012-09-02 19:29:47 +00:00
return [ tiddler ] ;
2012-04-30 11:23:03 +00:00
} else {
return null ;
}
} ,
2012-09-02 19:29:47 +00:00
extractModuleTiddlers = function ( node ) {
2012-04-30 11:23:03 +00:00
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-" ) {
2012-08-31 10:38:30 +00:00
fields [ attributes [ a ] . nodeName . substr ( 13 ) ] = attributes [ a ] . value ;
2012-05-05 16:42:42 +00:00
}
}
2012-09-02 19:29:47 +00:00
return [ fields ] ;
} else {
return null ;
}
} ,
t , result = [ ] ;
2012-10-10 15:32:37 +00:00
if ( node ) {
2021-07-14 08:15:30 +00:00
var type = ( node . getAttribute && node . getAttribute ( "type" ) ) || null ;
if ( type ) {
// A new-style container with an explicit deserialization type
result = $tw . wiki . deserializeTiddlers ( type , node . textContent ) ;
} else {
// An old-style container of classic DIV-based tiddlers
for ( t = 0 ; t < node . childNodes . length ; t ++ ) {
2013-06-27 08:50:46 +00:00
var childNode = node . childNodes [ t ] ,
tiddlers = extractTextTiddlers ( childNode ) ;
2012-10-10 15:32:37 +00:00
tiddlers = tiddlers || extractModuleTiddlers ( childNode ) ;
if ( tiddlers ) {
result . push . apply ( result , tiddlers ) ;
}
2021-07-14 08:15:30 +00:00
}
2012-10-10 15:32:37 +00:00
}
2012-04-30 11:23:03 +00:00
}
2012-09-02 19:29:47 +00:00
return result ;
2012-04-30 11:23:03 +00:00
}
} ) ;
2012-11-15 10:40:03 +00:00
2014-01-14 14:09:04 +00:00
$tw . loadTiddlersBrowser = function ( ) {
2012-11-15 10:40:03 +00:00
// In the browser, we load tiddlers from certain elements
2021-07-14 08:15:30 +00:00
var containerSelectors = [
// IDs for old-style v5.1.x tiddler stores
"#libraryModules" ,
"#modules" ,
"#bootKernelPrefix" ,
"#bootKernel" ,
"#styleArea" ,
"#storeArea" ,
"#systemArea" ,
// Classes for new-style v5.2.x JSON tiddler stores
"script.tiddlywiki-tiddler-store"
2012-11-15 10:40:03 +00:00
] ;
2021-07-14 08:15:30 +00:00
for ( var t = 0 ; t < containerSelectors . length ; t ++ ) {
var nodes = document . querySelectorAll ( containerSelectors [ t ] ) ;
for ( var n = 0 ; n < nodes . length ; n ++ ) {
$tw . wiki . addTiddlers ( $tw . wiki . deserializeTiddlers ( "(DOM)" , nodes [ n ] ) ) ;
}
2012-11-15 10:40:03 +00:00
}
} ;
2012-04-30 11:23:03 +00:00
2014-05-07 08:59:21 +00:00
} else {
2012-04-30 11:23:03 +00:00
/////////////////////////// Server definitions
2012-11-16 16:59:47 +00:00
/ *
Get any encrypted tiddlers
* /
2012-11-16 17:23:10 +00:00
$tw . boot . decryptEncryptedTiddlers = function ( callback ) {
2012-11-16 16:59:47 +00:00
// Storing encrypted tiddlers on the server isn't supported yet
2012-11-16 17:23:10 +00:00
callback ( ) ;
2012-11-16 16:59:47 +00:00
} ;
2014-05-07 08:59:21 +00:00
} // End of if($tw.browser && !$tw.node)
2014-01-14 14:09:04 +00:00
/////////////////////////// Node definitions
if ( $tw . node ) {
2012-04-30 11:23:03 +00:00
/ *
2013-03-23 22:11:23 +00:00
Load the tiddlers contained in a particular file ( and optionally extract fields from the accompanying . meta file ) returned as { filepath : , type : , tiddlers : [ ] , hasMetaFile : }
2012-04-30 11:23:03 +00:00
* /
2012-11-12 22:16:49 +00:00
$tw . loadTiddlersFromFile = function ( filepath , fields ) {
2012-08-31 15:36:57 +00:00
var ext = path . extname ( filepath ) ,
2015-02-01 18:33:40 +00:00
extensionInfo = $tw . utils . getFileExtensionInfo ( ext ) ,
2013-03-23 22:11:23 +00:00
type = extensionInfo ? extensionInfo . type : null ,
typeInfo = type ? $tw . config . contentTypeInfo [ type ] : null ,
2012-11-13 08:46:44 +00:00
data = fs . readFileSync ( filepath , typeInfo ? typeInfo . encoding : "utf8" ) ,
2012-04-30 11:23:03 +00:00
tiddlers = $tw . wiki . deserializeTiddlers ( ext , data , fields ) ,
2016-10-15 15:21:51 +00:00
metadata = $tw . loadMetadataForFile ( filepath ) ;
2019-04-13 13:47:27 +00:00
if ( metadata ) {
if ( type === "application/json" ) {
tiddlers = [ { text : data , type : "application/json" } ] ;
}
2017-02-12 12:14:04 +00:00
tiddlers = [ $tw . utils . extend ( { } , tiddlers [ 0 ] , metadata ) ] ;
2012-04-30 11:23:03 +00:00
}
2013-03-23 22:11:23 +00:00
return { filepath : filepath , type : type , tiddlers : tiddlers , hasMetaFile : ! ! metadata } ;
2012-04-30 11:23:03 +00:00
} ;
2016-10-15 15:21:51 +00:00
/ *
Load the metadata fields in the . meta file corresponding to a particular file
* /
$tw . loadMetadataForFile = function ( filepath ) {
var metafilename = filepath + ".meta" ;
if ( fs . existsSync ( metafilename ) ) {
return $tw . utils . parseFields ( fs . readFileSync ( metafilename , "utf8" ) || "" ) ;
} else {
return null ;
}
} ;
2013-10-13 16:58:10 +00:00
/ *
A default set of files for TiddlyWiki to ignore during load .
This matches what NPM ignores , and adds "*.meta" to ignore tiddler
metadata files .
* /
$tw . boot . excludeRegExp = /^\.DS_Store$|^.*\.meta$|^\..*\.swp$|^\._.*$|^\.git$|^\.hg$|^\.lock-wscript$|^\.svn$|^\.wafpickle-.*$|^CVS$|^npm-debug\.log$/ ;
2012-04-30 11:23:03 +00:00
/ *
2013-03-23 22:43:05 +00:00
Load all the tiddlers recursively from a directory , including honouring ` tiddlywiki.files ` files for drawing in external files . Returns an array of { filepath : , type : , tiddlers : [ { . . fields ... } ] , hasMetaFile : } . Note that no file information is returned for externally loaded tiddlers , just the ` tiddlers ` property .
2012-04-30 11:23:03 +00:00
* /
2012-11-12 22:16:49 +00:00
$tw . loadTiddlersFromPath = function ( filepath , excludeRegExp ) {
2013-10-13 16:58:10 +00:00
excludeRegExp = excludeRegExp || $tw . boot . excludeRegExp ;
2013-03-23 21:37:48 +00:00
var tiddlers = [ ] ;
2012-07-13 16:08:15 +00:00
if ( fs . existsSync ( filepath ) ) {
2013-03-23 21:37:48 +00:00
var stat = fs . statSync ( filepath ) ;
2012-05-04 17:24:54 +00:00
if ( stat . isDirectory ( ) ) {
2013-03-23 21:37:48 +00:00
var files = fs . readdirSync ( filepath ) ;
2012-08-31 14:04:08 +00:00
// Look for a tiddlywiki.files file
2012-11-12 22:16:49 +00:00
if ( files . indexOf ( "tiddlywiki.files" ) !== - 1 ) {
2016-10-18 12:33:54 +00:00
Array . prototype . push . apply ( tiddlers , $tw . loadTiddlersFromSpecification ( filepath , excludeRegExp ) ) ;
2012-05-05 10:21:59 +00:00
} else {
// If not, read all the files in the directory
2013-03-23 21:37:48 +00:00
$tw . utils . each ( files , function ( file ) {
2014-06-18 07:34:49 +00:00
if ( ! excludeRegExp . test ( file ) && file !== "plugin.info" ) {
2014-03-31 16:17:36 +00:00
tiddlers . push . apply ( tiddlers , $tw . loadTiddlersFromPath ( filepath + path . sep + file , excludeRegExp ) ) ;
2012-05-05 10:21:59 +00:00
}
2013-03-23 21:37:48 +00:00
} ) ;
2012-04-30 11:23:03 +00:00
}
2012-05-04 17:24:54 +00:00
} else if ( stat . isFile ( ) ) {
2017-11-05 14:14:00 +00:00
tiddlers . push ( $tw . loadTiddlersFromFile ( filepath , { title : filepath } ) ) ;
2012-04-30 11:23:03 +00:00
}
}
2012-08-31 15:36:57 +00:00
return tiddlers ;
} ;
2016-10-15 15:21:51 +00:00
/ *
Load all the tiddlers defined by a ` tiddlywiki.files ` specification file
filepath : pathname of the directory containing the specification file
* /
2016-10-18 12:33:54 +00:00
$tw . loadTiddlersFromSpecification = function ( filepath , excludeRegExp ) {
2016-10-15 15:21:51 +00:00
var tiddlers = [ ] ;
// Read the specification
2022-02-21 15:29:25 +00:00
var filesInfo = $tw . utils . parseJSONSafe ( fs . readFileSync ( filepath + path . sep + "tiddlywiki.files" , "utf8" ) ) ;
2016-10-15 15:21:51 +00:00
// Helper to process a file
2020-10-26 17:36:50 +00:00
var processFile = function ( filename , isTiddlerFile , fields , isEditableFile ) {
2016-10-15 15:21:51 +00:00
var extInfo = $tw . config . fileExtensionInfo [ path . extname ( filename ) ] ,
type = ( extInfo || { } ) . type || fields . type || "text/plain" ,
typeInfo = $tw . config . contentTypeInfo [ type ] || { } ,
pathname = path . resolve ( filepath , filename ) ,
text = fs . readFileSync ( pathname , typeInfo . encoding || "utf8" ) ,
metadata = $tw . loadMetadataForFile ( pathname ) || { } ,
fileTiddlers ;
if ( isTiddlerFile ) {
fileTiddlers = $tw . wiki . deserializeTiddlers ( path . extname ( pathname ) , text , metadata ) || [ ] ;
} else {
fileTiddlers = [ $tw . utils . extend ( { text : text } , metadata ) ] ;
}
var combinedFields = $tw . utils . extend ( { } , fields , metadata ) ;
$tw . utils . each ( fileTiddlers , function ( tiddler ) {
$tw . utils . each ( combinedFields , function ( fieldInfo , name ) {
if ( typeof fieldInfo === "string" || $tw . utils . isArray ( fieldInfo ) ) {
tiddler [ name ] = fieldInfo ;
} else {
var value = tiddler [ name ] ;
switch ( fieldInfo . source ) {
case "filename" :
value = path . basename ( filename ) ;
break ;
2016-12-12 16:30:12 +00:00
case "filename-uri-decoded" :
2021-08-29 12:39:32 +00:00
value = $tw . utils . decodeURIComponentSafe ( path . basename ( filename ) ) ;
2016-12-12 16:30:12 +00:00
break ;
2016-10-15 15:21:51 +00:00
case "basename" :
value = path . basename ( filename , path . extname ( filename ) ) ;
break ;
2016-11-14 15:14:25 +00:00
case "basename-uri-decoded" :
2021-08-29 12:39:32 +00:00
value = $tw . utils . decodeURIComponentSafe ( path . basename ( filename , path . extname ( filename ) ) ) ;
2016-11-14 15:14:25 +00:00
break ;
2016-10-15 15:21:51 +00:00
case "extname" :
value = path . extname ( filename ) ;
break ;
case "created" :
value = new Date ( fs . statSync ( pathname ) . birthtime ) ;
break ;
case "modified" :
value = new Date ( fs . statSync ( pathname ) . mtime ) ;
break ;
}
if ( fieldInfo . prefix ) {
value = fieldInfo . prefix + value ;
}
if ( fieldInfo . suffix ) {
value = value + fieldInfo . suffix ;
}
tiddler [ name ] = value ;
}
} ) ;
} ) ;
2020-10-26 17:36:50 +00:00
if ( isEditableFile ) {
2020-12-02 09:47:51 +00:00
tiddlers . push ( { filepath : pathname , hasMetaFile : ! ! metadata && ! isTiddlerFile , isEditableFile : true , tiddlers : fileTiddlers } ) ;
2020-10-26 17:36:50 +00:00
} else {
tiddlers . push ( { tiddlers : fileTiddlers } ) ;
}
2016-10-15 15:21:51 +00:00
} ;
2021-10-27 10:16:05 +00:00
// Helper to recursively search subdirectories
var getAllFiles = function ( dirPath , recurse , arrayOfFiles ) {
recurse = recurse || false ;
arrayOfFiles = arrayOfFiles || [ ] ;
var files = fs . readdirSync ( dirPath ) ;
files . forEach ( function ( file ) {
if ( recurse && fs . statSync ( dirPath + path . sep + file ) . isDirectory ( ) ) {
arrayOfFiles = getAllFiles ( dirPath + path . sep + file , recurse , arrayOfFiles ) ;
} else if ( fs . statSync ( dirPath + path . sep + file ) . isFile ( ) ) {
arrayOfFiles . push ( path . join ( dirPath , path . sep , file ) ) ;
}
} ) ;
return arrayOfFiles ;
}
2016-10-15 15:21:51 +00:00
// Process the listed tiddlers
$tw . utils . each ( filesInfo . tiddlers , function ( tidInfo ) {
if ( tidInfo . prefix && tidInfo . suffix ) {
tidInfo . fields . text = { prefix : tidInfo . prefix , suffix : tidInfo . suffix } ;
} else if ( tidInfo . prefix ) {
tidInfo . fields . text = { prefix : tidInfo . prefix } ;
} else if ( tidInfo . suffix ) {
tidInfo . fields . text = { suffix : tidInfo . suffix } ;
}
processFile ( tidInfo . file , tidInfo . isTiddlerFile , tidInfo . fields ) ;
} ) ;
// Process any listed directories
$tw . utils . each ( filesInfo . directories , function ( dirSpec ) {
// Read literal directories directly
if ( typeof dirSpec === "string" ) {
var pathname = path . resolve ( filepath , dirSpec ) ;
if ( fs . existsSync ( pathname ) && fs . statSync ( pathname ) . isDirectory ( ) ) {
tiddlers . push . apply ( tiddlers , $tw . loadTiddlersFromPath ( pathname , excludeRegExp ) ) ;
}
} else {
// Process directory specifier
2020-11-30 22:31:48 +00:00
var dirPath = path . resolve ( filepath , dirSpec . path ) ;
if ( fs . existsSync ( dirPath ) && fs . statSync ( dirPath ) . isDirectory ( ) ) {
2021-10-27 10:16:05 +00:00
var files = getAllFiles ( dirPath , dirSpec . searchSubdirectories ) ,
2020-11-30 22:31:48 +00:00
fileRegExp = new RegExp ( dirSpec . filesRegExp || "^.*$" ) ,
metaRegExp = /^.*\.meta$/ ;
for ( var t = 0 ; t < files . length ; t ++ ) {
2021-10-27 10:16:05 +00:00
var thisPath = path . relative ( filepath , files [ t ] ) ,
filename = path . basename ( thisPath ) ;
2020-11-30 22:31:48 +00:00
if ( filename !== "tiddlywiki.files" && ! metaRegExp . test ( filename ) && fileRegExp . test ( filename ) ) {
2021-10-27 10:16:05 +00:00
processFile ( thisPath , dirSpec . isTiddlerFile , dirSpec . fields , dirSpec . isEditableFile ) ;
2020-11-30 22:31:48 +00:00
}
2016-10-15 15:21:51 +00:00
}
2020-11-30 22:31:48 +00:00
} else {
console . log ( "Warning: a directory in a tiddlywiki.files file does not exist." ) ;
2021-05-30 18:20:17 +00:00
console . log ( "dirPath: " + dirPath ) ;
2020-11-30 22:31:48 +00:00
console . log ( "tiddlywiki.files location: " + filepath ) ;
2016-10-15 15:21:51 +00:00
}
}
} ) ;
return tiddlers ;
} ;
2012-08-31 15:36:57 +00:00
/ *
2013-03-23 16:34:12 +00:00
Load the tiddlers from a plugin folder , and package them up into a proper JSON plugin tiddler
2012-08-31 15:36:57 +00:00
* /
2013-03-23 16:34:12 +00:00
$tw . loadPluginFolder = function ( filepath , excludeRegExp ) {
2013-10-13 16:58:10 +00:00
excludeRegExp = excludeRegExp || $tw . boot . excludeRegExp ;
2017-09-12 14:53:52 +00:00
var infoPath = filepath + path . sep + "plugin.info" ;
2014-06-18 07:34:49 +00:00
if ( fs . existsSync ( filepath ) && fs . statSync ( filepath ) . isDirectory ( ) ) {
// Read the plugin information
2017-09-12 14:53:52 +00:00
if ( ! fs . existsSync ( infoPath ) || ! fs . statSync ( infoPath ) . isFile ( ) ) {
console . log ( "Warning: missing plugin.info file in " + filepath ) ;
return null ;
}
2022-02-21 15:29:25 +00:00
var pluginInfo = $tw . utils . parseJSONSafe ( fs . readFileSync ( infoPath , "utf8" ) ) ;
2014-06-18 07:34:49 +00:00
// Read the plugin files
var pluginFiles = $tw . loadTiddlersFromPath ( filepath , excludeRegExp ) ;
// Save the plugin tiddlers into the plugin info
pluginInfo . tiddlers = pluginInfo . tiddlers || Object . create ( null ) ;
for ( var f = 0 ; f < pluginFiles . length ; f ++ ) {
var tiddlers = pluginFiles [ f ] . tiddlers ;
for ( var t = 0 ; t < tiddlers . length ; t ++ ) {
var tiddler = tiddlers [ t ] ;
if ( tiddler . title ) {
pluginInfo . tiddlers [ tiddler . title ] = tiddler ;
2014-01-12 21:48:18 +00:00
}
2012-11-13 08:46:44 +00:00
}
2012-11-12 22:16:49 +00:00
}
2014-06-18 07:34:49 +00:00
// Give the plugin the same version number as the core if it doesn't have one
if ( ! ( "version" in pluginInfo ) ) {
pluginInfo . version = $tw . packageInfo . version ;
}
2014-06-18 07:52:31 +00:00
// Use "plugin" as the plugin-type if we don't have one
if ( ! ( "plugin-type" in pluginInfo ) ) {
pluginInfo [ "plugin-type" ] = "plugin" ;
}
pluginInfo . dependents = pluginInfo . dependents || [ ] ;
pluginInfo . type = "application/json" ;
// Set plugin text
2021-06-29 21:17:16 +00:00
pluginInfo . text = JSON . stringify ( { tiddlers : pluginInfo . tiddlers } ) ;
2014-06-18 07:52:31 +00:00
delete pluginInfo . tiddlers ;
// Deserialise array fields (currently required for the dependents field)
for ( var field in pluginInfo ) {
if ( $tw . utils . isArray ( pluginInfo [ field ] ) ) {
pluginInfo [ field ] = $tw . utils . stringifyList ( pluginInfo [ field ] ) ;
2014-06-18 07:34:49 +00:00
}
2013-04-08 17:37:49 +00:00
}
2014-06-18 07:52:31 +00:00
return pluginInfo ;
} else {
return null ;
2013-04-08 17:37:49 +00:00
}
2012-05-04 17:49:04 +00:00
} ;
2012-04-30 11:23:03 +00:00
2014-06-17 20:33:44 +00:00
/ *
2014-06-17 20:59:20 +00:00
name : Name of the plugin to find
2014-06-17 20:33:44 +00:00
paths : array of file paths to search for it
2014-06-17 20:59:20 +00:00
Returns the path of the plugin folder
2014-06-17 20:33:44 +00:00
* /
2014-06-17 20:59:20 +00:00
$tw . findLibraryItem = function ( name , paths ) {
2014-06-17 20:33:44 +00:00
var pathIndex = 0 ;
do {
2014-06-17 20:59:20 +00:00
var pluginPath = path . resolve ( paths [ pathIndex ] , "./" + name )
if ( fs . existsSync ( pluginPath ) && fs . statSync ( pluginPath ) . isDirectory ( ) ) {
return pluginPath ;
}
} while ( ++ pathIndex < paths . length ) ;
return null ;
} ;
/ *
name : Name of the plugin to load
paths : array of file paths to search for it
* /
$tw . loadPlugin = function ( name , paths ) {
var pluginPath = $tw . findLibraryItem ( name , paths ) ;
if ( pluginPath ) {
var pluginFields = $tw . loadPluginFolder ( pluginPath ) ;
2014-06-17 20:33:44 +00:00
if ( pluginFields ) {
$tw . wiki . addTiddler ( pluginFields ) ;
2019-04-12 17:09:16 +00:00
return ;
2014-06-17 20:33:44 +00:00
}
2014-06-17 20:59:20 +00:00
}
2019-04-12 17:09:16 +00:00
console . log ( "Warning: Cannot find plugin '" + name + "'" ) ;
2014-06-17 20:59:20 +00:00
} ;
/ *
libraryPath : Path of library folder for these plugins ( relative to core path )
envVar : Environment variable name for these plugins
Returns an array of search paths
* /
$tw . getLibraryItemSearchPaths = function ( libraryPath , envVar ) {
var pluginPaths = [ path . resolve ( $tw . boot . corePath , libraryPath ) ] ,
env = process . env [ envVar ] ;
if ( env ) {
2015-02-12 19:03:20 +00:00
env . split ( path . delimiter ) . map ( function ( item ) {
2015-02-12 13:44:10 +00:00
if ( item ) {
2019-04-12 17:09:16 +00:00
pluginPaths . push ( item ) ;
2015-02-12 13:44:10 +00:00
}
} ) ;
2014-06-17 20:59:20 +00:00
}
return pluginPaths ;
2014-06-17 20:33:44 +00:00
} ;
/ *
plugins : Array of names of plugins ( eg , "tiddlywiki/filesystemadaptor" )
libraryPath : Path of library folder for these plugins ( relative to core path )
envVar : Environment variable name for these plugins
* /
$tw . loadPlugins = function ( plugins , libraryPath , envVar ) {
if ( plugins ) {
2014-06-17 20:59:20 +00:00
var pluginPaths = $tw . getLibraryItemSearchPaths ( libraryPath , envVar ) ;
2014-06-17 20:33:44 +00:00
for ( var t = 0 ; t < plugins . length ; t ++ ) {
$tw . loadPlugin ( plugins [ t ] , pluginPaths ) ;
}
}
} ;
2013-03-25 18:55:41 +00:00
/ *
path : path of wiki directory
2014-11-13 12:28:51 +00:00
options :
parentPaths : array of parent paths that we mustn ' t recurse into
readOnly : true if the tiddler file paths should not be retained
2013-03-25 18:55:41 +00:00
* /
2014-11-13 12:28:51 +00:00
$tw . loadWikiTiddlers = function ( wikiPath , options ) {
options = options || { } ;
var parentPaths = options . parentPaths || [ ] ,
wikiInfoPath = path . resolve ( wikiPath , $tw . config . wikiInfo ) ,
2013-12-18 21:11:00 +00:00
wikiInfo ,
2013-03-25 18:55:41 +00:00
pluginFields ;
2013-03-28 14:06:50 +00:00
// Bail if we don't have a wiki info file
2013-12-18 21:11:00 +00:00
if ( fs . existsSync ( wikiInfoPath ) ) {
2022-02-21 15:29:25 +00:00
wikiInfo = $tw . utils . parseJSONSafe ( fs . readFileSync ( wikiInfoPath , "utf8" ) ) ;
2013-12-18 21:11:00 +00:00
} else {
2014-02-22 16:38:34 +00:00
return null ;
2013-03-28 14:06:50 +00:00
}
2020-12-02 09:47:51 +00:00
// Save the path to the tiddlers folder for the filesystemadaptor
var config = wikiInfo . config || { } ;
if ( $tw . boot . wikiPath == wikiPath ) {
$tw . boot . wikiTiddlersPath = path . resolve ( $tw . boot . wikiPath , config [ "default-tiddler-location" ] || $tw . config . wikiTiddlersSubDir ) ;
}
2013-03-28 14:06:50 +00:00
// Load any parent wikis
if ( wikiInfo . includeWikis ) {
parentPaths = parentPaths . slice ( 0 ) ;
parentPaths . push ( wikiPath ) ;
2014-11-13 12:28:51 +00:00
$tw . utils . each ( wikiInfo . includeWikis , function ( info ) {
if ( typeof info === "string" ) {
info = { path : info } ;
}
var resolvedIncludedWikiPath = path . resolve ( wikiPath , info . path ) ;
2013-03-28 14:06:50 +00:00
if ( parentPaths . indexOf ( resolvedIncludedWikiPath ) === - 1 ) {
2014-11-13 12:28:51 +00:00
var subWikiInfo = $tw . loadWikiTiddlers ( resolvedIncludedWikiPath , {
parentPaths : parentPaths ,
readOnly : info [ "read-only" ]
} ) ;
2014-10-14 14:14:50 +00:00
// Merge the build targets
wikiInfo . build = $tw . utils . extend ( [ ] , subWikiInfo . build , wikiInfo . build ) ;
2013-03-28 14:06:50 +00:00
} else {
$tw . utils . error ( "Cannot recursively include wiki " + resolvedIncludedWikiPath ) ;
}
} ) ;
}
2014-06-17 20:33:44 +00:00
// Load any plugins, themes and languages listed in the wiki info file
$tw . loadPlugins ( wikiInfo . plugins , $tw . config . pluginsPath , $tw . config . pluginsEnvVar ) ;
$tw . loadPlugins ( wikiInfo . themes , $tw . config . themesPath , $tw . config . themesEnvVar ) ;
$tw . loadPlugins ( wikiInfo . languages , $tw . config . languagesPath , $tw . config . languagesEnvVar ) ;
2013-03-23 22:11:23 +00:00
// Load the wiki files, registering them as writable
2013-03-25 18:55:41 +00:00
var resolvedWikiPath = path . resolve ( wikiPath , $tw . config . wikiTiddlersSubDir ) ;
$tw . utils . each ( $tw . loadTiddlersFromPath ( resolvedWikiPath ) , function ( tiddlerFile ) {
2014-11-13 12:28:51 +00:00
if ( ! options . readOnly && tiddlerFile . filepath ) {
2013-03-23 22:43:05 +00:00
$tw . utils . each ( tiddlerFile . tiddlers , function ( tiddler ) {
$tw . boot . files [ tiddler . title ] = {
filepath : tiddlerFile . filepath ,
type : tiddlerFile . type ,
2020-12-02 09:47:51 +00:00
hasMetaFile : tiddlerFile . hasMetaFile ,
isEditableFile : config [ "retain-original-tiddler-path" ] || tiddlerFile . isEditableFile || tiddlerFile . filepath . indexOf ( $tw . boot . wikiTiddlersPath ) !== 0
2013-03-23 22:43:05 +00:00
} ;
} ) ;
}
2014-02-27 16:30:05 +00:00
$tw . wiki . addTiddlers ( tiddlerFile . tiddlers ) ;
2013-03-23 21:37:48 +00:00
} ) ;
2020-12-02 09:47:51 +00:00
if ( $tw . boot . wikiPath == wikiPath ) {
// Save the original tiddler file locations if requested
var output = { } , relativePath , fileInfo ;
2014-02-27 16:30:05 +00:00
for ( var title in $tw . boot . files ) {
2020-12-02 09:47:51 +00:00
fileInfo = $tw . boot . files [ title ] ;
if ( fileInfo . isEditableFile ) {
relativePath = path . relative ( $tw . boot . wikiTiddlersPath , fileInfo . filepath ) ;
2021-03-26 08:39:32 +00:00
fileInfo . originalpath = relativePath ;
2020-12-02 09:47:51 +00:00
output [ title ] =
path . sep === "/" ?
relativePath :
relativePath . split ( path . sep ) . join ( "/" ) ;
}
}
if ( Object . keys ( output ) . length > 0 ) {
$tw . wiki . addTiddler ( { title : "$:/config/OriginalTiddlerPaths" , type : "application/json" , text : JSON . stringify ( output ) } ) ;
2014-02-27 16:30:05 +00:00
}
}
2012-11-14 11:23:43 +00:00
// Load any plugins within the wiki folder
2013-03-25 18:55:41 +00:00
var wikiPluginsPath = path . resolve ( wikiPath , $tw . config . wikiPluginsSubDir ) ;
2012-11-14 11:23:43 +00:00
if ( fs . existsSync ( wikiPluginsPath ) ) {
var pluginFolders = fs . readdirSync ( wikiPluginsPath ) ;
2014-06-18 10:56:27 +00:00
for ( var t = 0 ; t < pluginFolders . length ; t ++ ) {
2013-03-23 16:34:12 +00:00
pluginFields = $tw . loadPluginFolder ( path . resolve ( wikiPluginsPath , "./" + pluginFolders [ t ] ) ) ;
if ( pluginFields ) {
$tw . wiki . addTiddler ( pluginFields ) ;
2012-11-14 11:23:43 +00:00
}
2012-11-13 08:46:44 +00:00
}
}
2013-07-14 22:08:04 +00:00
// Load any themes within the wiki folder
var wikiThemesPath = path . resolve ( wikiPath , $tw . config . wikiThemesSubDir ) ;
if ( fs . existsSync ( wikiThemesPath ) ) {
var themeFolders = fs . readdirSync ( wikiThemesPath ) ;
2014-06-18 10:56:27 +00:00
for ( var t = 0 ; t < themeFolders . length ; t ++ ) {
2013-07-14 22:08:04 +00:00
pluginFields = $tw . loadPluginFolder ( path . resolve ( wikiThemesPath , "./" + themeFolders [ t ] ) ) ;
if ( pluginFields ) {
$tw . wiki . addTiddler ( pluginFields ) ;
}
}
}
2014-02-09 19:18:46 +00:00
// Load any languages within the wiki folder
var wikiLanguagesPath = path . resolve ( wikiPath , $tw . config . wikiLanguagesSubDir ) ;
if ( fs . existsSync ( wikiLanguagesPath ) ) {
var languageFolders = fs . readdirSync ( wikiLanguagesPath ) ;
2014-06-18 10:56:27 +00:00
for ( var t = 0 ; t < languageFolders . length ; t ++ ) {
2014-02-09 19:18:46 +00:00
pluginFields = $tw . loadPluginFolder ( path . resolve ( wikiLanguagesPath , "./" + languageFolders [ t ] ) ) ;
if ( pluginFields ) {
$tw . wiki . addTiddler ( pluginFields ) ;
}
}
}
2013-03-25 18:55:41 +00:00
return wikiInfo ;
} ;
2014-01-14 14:09:04 +00:00
$tw . loadTiddlersNode = function ( ) {
2013-03-28 17:07:30 +00:00
// Load the boot tiddlers
2013-03-25 18:55:41 +00:00
$tw . utils . each ( $tw . loadTiddlersFromPath ( $tw . boot . bootPath ) , function ( tiddlerFile ) {
$tw . wiki . addTiddlers ( tiddlerFile . tiddlers ) ;
} ) ;
2013-03-28 17:07:30 +00:00
// Load the core tiddlers
$tw . wiki . addTiddler ( $tw . loadPluginFolder ( $tw . boot . corePath ) ) ;
2019-04-12 17:09:16 +00:00
// Load any extra plugins
$tw . utils . each ( $tw . boot . extraPlugins , function ( name ) {
2019-05-24 11:06:11 +00:00
if ( name . charAt ( 0 ) === "+" ) { // Relative path to plugin
2020-12-02 09:47:51 +00:00
var pluginFields = $tw . loadPluginFolder ( name . substring ( 1 ) ) ;
2019-05-24 11:06:11 +00:00
if ( pluginFields ) {
$tw . wiki . addTiddler ( pluginFields ) ;
}
} else {
var parts = name . split ( "/" ) ,
type = parts [ 0 ] ;
if ( parts . length === 3 && [ "plugins" , "themes" , "languages" ] . indexOf ( type ) !== - 1 ) {
$tw . loadPlugins ( [ parts [ 1 ] + "/" + parts [ 2 ] ] , $tw . config [ type + "Path" ] , $tw . config [ type + "EnvVar" ] ) ;
2021-05-30 18:20:17 +00:00
}
2019-04-12 17:09:16 +00:00
}
} ) ;
2013-03-25 18:55:41 +00:00
// Load the tiddlers from the wiki directory
2013-08-21 08:42:51 +00:00
if ( $tw . boot . wikiPath ) {
$tw . boot . wikiInfo = $tw . loadWikiTiddlers ( $tw . boot . wikiPath ) ;
}
2012-11-15 10:40:03 +00:00
} ;
2014-05-13 09:26:02 +00:00
// End of if($tw.node)
2012-11-12 22:16:49 +00:00
}
2013-03-22 20:02:19 +00:00
/////////////////////////// Main startup function called once tiddlers have been decrypted
2012-08-06 21:34:16 +00:00
2014-01-14 14:09:04 +00:00
/ *
2014-05-07 11:51:16 +00:00
Startup TiddlyWiki
2014-01-14 14:09:04 +00:00
* /
2020-08-28 14:43:42 +00:00
$tw . boot . initStartup = function ( options ) {
2014-05-02 18:21:32 +00:00
// Get the URL hash and check for safe mode
$tw . locationHash = "#" ;
2014-05-07 08:59:21 +00:00
if ( $tw . browser && ! $tw . node ) {
2014-05-02 18:21:32 +00:00
if ( location . hash === "#:safe" ) {
$tw . safeMode = true ;
} else {
2014-05-05 18:21:57 +00:00
$tw . locationHash = $tw . utils . getLocationHash ( ) ;
2014-05-02 18:21:32 +00:00
}
}
2013-03-25 10:43:18 +00:00
// Initialise some more $tw properties
$tw . utils . deepDefaults ( $tw , {
modules : { // Information about each module
2014-04-06 21:49:20 +00:00
titles : Object . create ( null ) , // hashmap by module title of {fn:, exports:, moduleType:}
2013-03-25 10:43:18 +00:00
types : { } // hashmap by module type of hashmap of exports
} ,
config : { // Configuration overridables
pluginsPath : "../plugins/" ,
2013-04-28 08:42:48 +00:00
themesPath : "../themes/" ,
2014-02-09 19:18:46 +00:00
languagesPath : "../languages/" ,
2014-02-23 22:58:17 +00:00
editionsPath : "../editions/" ,
2013-03-25 10:43:18 +00:00
wikiInfo : "./tiddlywiki.info" ,
wikiPluginsSubDir : "./plugins" ,
2013-07-14 22:08:04 +00:00
wikiThemesSubDir : "./themes" ,
2014-02-09 19:18:46 +00:00
wikiLanguagesSubDir : "./languages" ,
2013-03-25 10:43:18 +00:00
wikiTiddlersSubDir : "./tiddlers" ,
2014-04-25 21:41:59 +00:00
wikiOutputSubDir : "./output" ,
2013-06-27 08:43:04 +00:00
jsModuleHeaderRegExpString : "^\\/\\*\\\\(?:\\r?\\n)((?:^[^\\r\\n]*(?:\\r?\\n))+?)(^\\\\\\*\\/$(?:\\r?\\n)?)" ,
2014-04-06 21:49:20 +00:00
fileExtensionInfo : Object . create ( null ) , // Map file extension to {type:}
2014-06-17 20:33:44 +00:00
contentTypeInfo : Object . create ( null ) , // Map type to {encoding:,extension:}
pluginsEnvVar : "TIDDLYWIKI_PLUGIN_PATH" ,
themesEnvVar : "TIDDLYWIKI_THEME_PATH" ,
2014-06-17 20:59:20 +00:00
languagesEnvVar : "TIDDLYWIKI_LANGUAGE_PATH" ,
editionsEnvVar : "TIDDLYWIKI_EDITION_PATH"
2014-11-08 08:37:08 +00:00
} ,
2015-05-03 15:23:35 +00:00
log : { } , // Log flags
unloadTasks : [ ]
2013-03-25 10:43:18 +00:00
} ) ;
2014-05-07 11:51:16 +00:00
if ( ! $tw . boot . tasks . readBrowserTiddlers ) {
2013-03-23 22:11:23 +00:00
// For writable tiddler files, a hashmap of title to {filepath:,type:,hasMetaFile:}
2014-04-06 21:49:20 +00:00
$tw . boot . files = Object . create ( null ) ;
2013-03-22 20:02:19 +00:00
// System paths and filenames
2018-04-08 08:38:28 +00:00
$tw . boot . bootPath = options . bootPath || path . dirname ( module . filename ) ;
2013-03-28 17:07:30 +00:00
$tw . boot . corePath = path . resolve ( $tw . boot . bootPath , "../core" ) ;
2014-03-10 18:20:07 +00:00
// If there's no arguments then default to `--help`
if ( $tw . boot . argv . length === 0 ) {
$tw . boot . argv = [ "--help" ] ;
}
2019-04-12 17:09:16 +00:00
// Parse any extra plugin references
$tw . boot . extraPlugins = $tw . boot . extraPlugins || [ ] ;
while ( $tw . boot . argv [ 0 ] && $tw . boot . argv [ 0 ] . indexOf ( "+" ) === 0 ) {
$tw . boot . extraPlugins . push ( $tw . boot . argv [ 0 ] . substring ( 1 ) ) ;
$tw . boot . argv . splice ( 0 , 1 ) ;
}
2013-03-22 20:02:19 +00:00
// If the first command line argument doesn't start with `--` then we
// interpret it as the path to the wiki folder, which will otherwise default
// to the current folder
2014-02-22 16:38:34 +00:00
if ( $tw . boot . argv [ 0 ] && $tw . boot . argv [ 0 ] . indexOf ( "--" ) !== 0 ) {
2013-03-22 20:02:19 +00:00
$tw . boot . wikiPath = $tw . boot . argv [ 0 ] ;
$tw . boot . argv = $tw . boot . argv . slice ( 1 ) ;
} else {
$tw . boot . wikiPath = process . cwd ( ) ;
}
// Read package info
2015-12-19 18:52:25 +00:00
$tw . packageInfo = $tw . packageInfo || require ( "../package.json" ) ;
2013-03-22 20:02:19 +00:00
// Check node version number
2015-02-12 23:04:20 +00:00
if ( ! $tw . utils . checkVersions ( process . version . substr ( 1 ) , $tw . packageInfo . engines . node . substr ( 2 ) ) ) {
2013-07-21 20:11:48 +00:00
$tw . utils . error ( "TiddlyWiki5 requires node.js version " + $tw . packageInfo . engines . node ) ;
2013-03-22 20:02:19 +00:00
}
}
2013-03-22 21:12:39 +00:00
// Add file extension information
$tw . utils . registerFileType ( "text/vnd.tiddlywiki" , "utf8" , ".tid" ) ;
$tw . utils . registerFileType ( "application/x-tiddler" , "utf8" , ".tid" ) ;
2014-02-20 10:27:02 +00:00
$tw . utils . registerFileType ( "application/x-tiddlers" , "utf8" , ".multids" ) ;
2013-03-22 21:12:39 +00:00
$tw . utils . registerFileType ( "application/x-tiddler-html-div" , "utf8" , ".tiddler" ) ;
2013-05-13 16:42:07 +00:00
$tw . utils . registerFileType ( "text/vnd.tiddlywiki2-recipe" , "utf8" , ".recipe" ) ;
2013-03-22 21:12:39 +00:00
$tw . utils . registerFileType ( "text/plain" , "utf8" , ".txt" ) ;
$tw . utils . registerFileType ( "text/css" , "utf8" , ".css" ) ;
2014-09-16 19:05:13 +00:00
$tw . utils . registerFileType ( "text/html" , "utf8" , [ ".html" , ".htm" ] ) ;
2014-04-08 21:26:01 +00:00
$tw . utils . registerFileType ( "application/hta" , "utf16le" , ".hta" , { deserializerType : "text/html" } ) ;
2013-03-22 21:12:39 +00:00
$tw . utils . registerFileType ( "application/javascript" , "utf8" , ".js" ) ;
$tw . utils . registerFileType ( "application/json" , "utf8" , ".json" ) ;
2014-04-08 21:20:51 +00:00
$tw . utils . registerFileType ( "application/pdf" , "base64" , ".pdf" , { flags : [ "image" ] } ) ;
2015-02-23 10:57:16 +00:00
$tw . utils . registerFileType ( "application/zip" , "base64" , ".zip" ) ;
2020-01-31 14:08:28 +00:00
$tw . utils . registerFileType ( "application/x-zip-compressed" , "base64" , ".zip" ) ;
2014-09-14 19:35:01 +00:00
$tw . utils . registerFileType ( "image/jpeg" , "base64" , [ ".jpg" , ".jpeg" ] , { flags : [ "image" ] } ) ;
2020-10-26 17:47:19 +00:00
$tw . utils . registerFileType ( "image/jpg" , "base64" , [ ".jpg" , ".jpeg" ] , { flags : [ "image" ] } ) ;
2014-04-08 21:20:51 +00:00
$tw . utils . registerFileType ( "image/png" , "base64" , ".png" , { flags : [ "image" ] } ) ;
$tw . utils . registerFileType ( "image/gif" , "base64" , ".gif" , { flags : [ "image" ] } ) ;
2018-08-21 10:46:46 +00:00
$tw . utils . registerFileType ( "image/webp" , "base64" , ".webp" , { flags : [ "image" ] } ) ;
$tw . utils . registerFileType ( "image/heic" , "base64" , ".heic" , { flags : [ "image" ] } ) ;
$tw . utils . registerFileType ( "image/heif" , "base64" , ".heif" , { flags : [ "image" ] } ) ;
2014-04-08 21:20:51 +00:00
$tw . utils . registerFileType ( "image/svg+xml" , "utf8" , ".svg" , { flags : [ "image" ] } ) ;
2021-01-03 10:04:52 +00:00
$tw . utils . registerFileType ( "image/vnd.microsoft.icon" , "base64" , ".ico" , { flags : [ "image" ] } ) ;
2014-04-08 21:20:51 +00:00
$tw . utils . registerFileType ( "image/x-icon" , "base64" , ".ico" , { flags : [ "image" ] } ) ;
2013-05-01 12:04:27 +00:00
$tw . utils . registerFileType ( "application/font-woff" , "base64" , ".woff" ) ;
2017-12-21 22:02:04 +00:00
$tw . utils . registerFileType ( "application/x-font-ttf" , "base64" , ".woff" ) ;
2020-04-06 16:35:10 +00:00
$tw . utils . registerFileType ( "application/font-woff2" , "base64" , ".woff2" ) ;
2014-10-19 20:09:17 +00:00
$tw . utils . registerFileType ( "audio/ogg" , "base64" , ".ogg" ) ;
2020-01-04 11:27:13 +00:00
$tw . utils . registerFileType ( "video/ogg" , "base64" , [ ".ogm" , ".ogv" , ".ogg" ] ) ;
$tw . utils . registerFileType ( "video/webm" , "base64" , ".webm" ) ;
2015-10-01 11:13:38 +00:00
$tw . utils . registerFileType ( "video/mp4" , "base64" , ".mp4" ) ;
2014-10-18 13:50:07 +00:00
$tw . utils . registerFileType ( "audio/mp3" , "base64" , ".mp3" ) ;
$tw . utils . registerFileType ( "audio/mp4" , "base64" , [ ".mp4" , ".m4a" ] ) ;
2017-02-11 12:48:41 +00:00
$tw . utils . registerFileType ( "text/markdown" , "utf8" , [ ".md" , ".markdown" ] , { deserializerType : "text/x-markdown" } ) ;
2014-09-16 19:06:23 +00:00
$tw . utils . registerFileType ( "text/x-markdown" , "utf8" , [ ".md" , ".markdown" ] ) ;
2016-08-20 16:08:44 +00:00
$tw . utils . registerFileType ( "application/enex+xml" , "utf8" , ".enex" ) ;
2017-03-17 14:20:17 +00:00
$tw . utils . registerFileType ( "application/vnd.openxmlformats-officedocument.wordprocessingml.document" , "base64" , ".docx" ) ;
2016-08-20 16:08:44 +00:00
$tw . utils . registerFileType ( "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" , "base64" , ".xlsx" ) ;
2017-03-17 14:20:17 +00:00
$tw . utils . registerFileType ( "application/vnd.openxmlformats-officedocument.presentationml.presentation" , "base64" , ".pptx" ) ;
2018-05-16 13:20:51 +00:00
$tw . utils . registerFileType ( "text/x-bibtex" , "utf8" , ".bib" , { deserializerType : "application/x-bibtex" } ) ;
2016-10-18 17:00:01 +00:00
$tw . utils . registerFileType ( "application/x-bibtex" , "utf8" , ".bib" ) ;
2016-11-14 15:14:33 +00:00
$tw . utils . registerFileType ( "application/epub+zip" , "base64" , ".epub" ) ;
2018-05-18 16:53:07 +00:00
$tw . utils . registerFileType ( "application/octet-stream" , "base64" , ".octet-stream" ) ;
2013-03-22 20:02:19 +00:00
// Create the wiki store for the app
2013-03-22 19:44:12 +00:00
$tw . wiki = new $tw . Wiki ( ) ;
// Install built in tiddler fields modules
$tw . Tiddler . fieldModules = $tw . modules . getModulesByTypeAsHashmap ( "tiddlerfield" ) ;
// Install the tiddler deserializer modules
2014-04-06 21:49:20 +00:00
$tw . Wiki . tiddlerDeserializerModules = Object . create ( null ) ;
2013-03-22 19:44:12 +00:00
$tw . modules . applyMethods ( "tiddlerdeserializer" , $tw . Wiki . tiddlerDeserializerModules ) ;
2015-05-03 15:23:35 +00:00
// Call unload handlers in the browser
if ( $tw . browser ) {
window . onbeforeunload = function ( event ) {
event = event || { } ;
var result ;
$tw . utils . each ( $tw . unloadTasks , function ( task ) {
var r = task ( event ) ;
if ( r ) {
result = r ;
}
} ) ;
return result ;
}
}
2020-08-28 14:43:42 +00:00
} ;
$tw . boot . loadStartup = function ( options ) {
2013-03-22 19:44:12 +00:00
// Load tiddlers
2014-05-07 11:51:16 +00:00
if ( $tw . boot . tasks . readBrowserTiddlers ) {
2014-01-14 14:09:04 +00:00
$tw . loadTiddlersBrowser ( ) ;
} else {
$tw . loadTiddlersNode ( ) ;
}
2015-12-19 18:52:25 +00:00
// Load any preloaded tiddlers
if ( $tw . preloadTiddlers ) {
$tw . wiki . addTiddlers ( $tw . preloadTiddlers ) ;
}
2019-02-03 12:01:57 +00:00
// Give hooks a chance to modify the store
$tw . hooks . invokeHook ( "th-boot-tiddlers-loaded" ) ;
2020-08-28 14:43:42 +00:00
}
$tw . boot . execStartup = function ( options ) {
2013-03-23 16:34:12 +00:00
// Unpack plugin tiddlers
2014-03-14 15:23:12 +00:00
$tw . wiki . readPluginInfo ( ) ;
2014-09-08 18:50:48 +00:00
$tw . wiki . registerPluginTiddlers ( "plugin" , $tw . safeMode ? [ "$:/core" ] : undefined ) ;
2013-04-30 21:56:17 +00:00
$tw . wiki . unpackPluginTiddlers ( ) ;
2014-04-19 08:36:08 +00:00
// Process "safe mode"
if ( $tw . safeMode ) {
$tw . wiki . processSafeMode ( ) ;
}
2013-03-22 19:44:12 +00:00
// Register typed modules from the tiddlers we've just loaded
$tw . wiki . defineTiddlerModules ( ) ;
2013-03-23 16:34:12 +00:00
// And any modules within plugins
2013-04-30 21:56:17 +00:00
$tw . wiki . defineShadowModules ( ) ;
2013-03-22 19:44:12 +00:00
// Make sure the crypto state tiddler is up to date
2014-01-14 14:09:04 +00:00
if ( $tw . crypto ) {
$tw . crypto . updateCryptoStateTiddler ( ) ;
}
2014-05-03 15:32:18 +00:00
// Gather up any startup modules
$tw . boot . remainingStartupModules = [ ] ; // Array of startup modules
2013-03-22 19:44:12 +00:00
$tw . modules . forEachModuleOfType ( "startup" , function ( title , module ) {
if ( module . startup ) {
2014-05-03 15:32:18 +00:00
$tw . boot . remainingStartupModules . push ( module ) ;
2013-03-22 19:44:12 +00:00
}
} ) ;
2014-05-03 15:32:18 +00:00
// Keep track of the startup tasks that have been executed
$tw . boot . executedStartupModules = Object . create ( null ) ;
2014-05-07 11:51:16 +00:00
$tw . boot . disabledStartupModules = $tw . boot . disabledStartupModules || [ ] ;
2014-05-03 15:32:18 +00:00
// Repeatedly execute the next eligible task
2017-06-09 15:20:12 +00:00
$tw . boot . executeNextStartupTask ( options . callback ) ;
2020-08-28 14:43:42 +00:00
}
/ *
Startup TiddlyWiki
* /
$tw . boot . startup = function ( options ) {
options = options || { } ;
// Get the URL hash and check for safe mode
$tw . boot . initStartup ( options ) ;
$tw . boot . loadStartup ( options ) ;
$tw . boot . execStartup ( options ) ;
2014-05-03 15:32:18 +00:00
} ;
2015-05-03 15:23:35 +00:00
/ *
Add another unload task
* /
$tw . addUnloadTask = function ( task ) {
if ( $tw . unloadTasks . indexOf ( task ) === - 1 ) {
$tw . unloadTasks . push ( task ) ;
}
}
2014-05-03 15:32:18 +00:00
/ *
Execute the remaining eligible startup tasks
* /
2017-06-09 15:20:12 +00:00
$tw . boot . executeNextStartupTask = function ( callback ) {
2014-05-03 15:32:18 +00:00
// Find the next eligible task
2014-06-19 11:05:41 +00:00
var taskIndex = 0 , task ,
asyncTaskCallback = function ( ) {
if ( task . name ) {
$tw . boot . executedStartupModules [ task . name ] = true ;
}
2017-06-09 15:20:12 +00:00
return $tw . boot . executeNextStartupTask ( callback ) ;
2014-06-19 11:05:41 +00:00
} ;
2014-05-03 15:32:18 +00:00
while ( taskIndex < $tw . boot . remainingStartupModules . length ) {
2014-06-19 11:05:41 +00:00
task = $tw . boot . remainingStartupModules [ taskIndex ] ;
2014-05-03 15:32:18 +00:00
if ( $tw . boot . isStartupTaskEligible ( task ) ) {
// Remove this task from the list
$tw . boot . remainingStartupModules . splice ( taskIndex , 1 ) ;
2014-05-15 17:50:14 +00:00
// Assemble log message
var s = [ "Startup task:" , task . name ] ;
if ( task . platforms ) {
s . push ( "platforms:" , task . platforms . join ( "," ) ) ;
}
if ( task . after ) {
s . push ( "after:" , task . after . join ( "," ) ) ;
}
if ( task . before ) {
s . push ( "before:" , task . before . join ( "," ) ) ;
}
$tw . boot . log ( s . join ( " " ) ) ;
// Execute task
2014-05-03 15:32:18 +00:00
if ( ! $tw . utils . hop ( task , "synchronous" ) || task . synchronous ) {
task . startup ( ) ;
if ( task . name ) {
$tw . boot . executedStartupModules [ task . name ] = true ;
}
2017-06-09 15:20:12 +00:00
return $tw . boot . executeNextStartupTask ( callback ) ;
2014-05-03 15:32:18 +00:00
} else {
2014-06-19 11:05:41 +00:00
task . startup ( asyncTaskCallback ) ;
2014-05-03 15:32:18 +00:00
return true ;
}
}
taskIndex ++ ;
}
2017-06-09 15:21:39 +00:00
if ( typeof callback === 'function' ) {
callback ( ) ;
}
2014-05-03 15:32:18 +00:00
return false ;
} ;
2014-05-05 09:17:50 +00:00
/ *
2021-04-02 08:25:01 +00:00
Returns true if we are running on one of the platforms specified in taskModule ' s
` platforms ` array ; or if ` platforms ` property is not defined .
2014-05-05 09:17:50 +00:00
* /
$tw . boot . doesTaskMatchPlatform = function ( taskModule ) {
2014-05-03 16:49:20 +00:00
var platforms = taskModule . platforms ;
if ( platforms ) {
2014-05-05 09:17:50 +00:00
for ( var t = 0 ; t < platforms . length ; t ++ ) {
2021-04-02 08:25:01 +00:00
switch ( platforms [ t ] ) {
case "browser" :
if ( $tw . browser ) {
return true ;
}
break ;
case "node" :
if ( $tw . node ) {
return true ;
}
break ;
default :
$tw . utils . error ( "Module " + taskModule . name + ": '" + platforms [ t ] + "' in export.platforms invalid" ) ;
2014-05-03 16:49:20 +00:00
}
}
2021-04-02 08:25:01 +00:00
return false ;
2014-05-03 16:49:20 +00:00
}
2014-05-05 09:17:50 +00:00
return true ;
} ;
$tw . boot . isStartupTaskEligible = function ( taskModule ) {
var t ;
// Check that the platform is correct
if ( ! $tw . boot . doesTaskMatchPlatform ( taskModule ) ) {
return false ;
}
2014-05-03 18:49:50 +00:00
var name = taskModule . name ,
remaining = $tw . boot . remainingStartupModules ;
if ( name ) {
2014-05-07 11:51:16 +00:00
// Fail if this module is disabled
if ( $tw . boot . disabledStartupModules . indexOf ( name ) !== - 1 ) {
return false ;
}
// Check that no other outstanding tasks must be executed before this one
2014-05-03 18:49:50 +00:00
for ( t = 0 ; t < remaining . length ; t ++ ) {
2014-05-05 09:17:50 +00:00
var task = remaining [ t ] ;
if ( task . before && task . before . indexOf ( name ) !== - 1 ) {
2022-04-01 10:38:40 +00:00
if ( $tw . boot . doesTaskMatchPlatform ( task ) && ( ! task . name || $tw . boot . disabledStartupModules . indexOf ( task . name ) === - 1 ) ) {
2014-05-05 09:17:50 +00:00
return false ;
}
2014-05-03 18:49:50 +00:00
}
}
}
2014-05-03 16:49:20 +00:00
// Check that all of the tasks that we must be performed after has been done
2014-05-03 16:10:55 +00:00
var after = taskModule . after ;
if ( after ) {
2014-05-03 16:49:20 +00:00
for ( t = 0 ; t < after . length ; t ++ ) {
2014-05-03 16:10:55 +00:00
if ( ! $tw . boot . executedStartupModules [ after [ t ] ] ) {
2014-05-03 15:32:18 +00:00
return false ;
}
}
}
return true ;
2013-03-22 19:44:12 +00:00
} ;
2014-11-22 16:11:56 +00:00
/ *
Global Hooks mechanism which allows plugins to modify default functionality
* /
$tw . hooks = $tw . hooks || { names : { } } ;
2015-03-18 17:52:33 +00:00
2014-11-22 16:11:56 +00:00
/ *
2015-03-18 17:52:33 +00:00
Add hooks to the hashmap
2014-11-22 16:11:56 +00:00
* /
$tw . hooks . addHook = function ( hookName , definition ) {
if ( $tw . utils . hop ( $tw . hooks . names , hookName ) ) {
$tw . hooks . names [ hookName ] . push ( definition ) ;
}
else {
$tw . hooks . names [ hookName ] = [ definition ] ;
}
} ;
2015-03-18 17:52:33 +00:00
2014-11-22 16:11:56 +00:00
/ *
2015-03-18 17:52:33 +00:00
Invoke the hook by key
2014-11-22 16:11:56 +00:00
* /
2017-02-09 15:42:21 +00:00
$tw . hooks . invokeHook = function ( hookName /*, value,... */ ) {
var args = Array . prototype . slice . call ( arguments , 1 ) ;
2014-11-22 16:11:56 +00:00
if ( $tw . utils . hop ( $tw . hooks . names , hookName ) ) {
for ( var i = 0 ; i < $tw . hooks . names [ hookName ] . length ; i ++ ) {
2017-02-09 15:42:21 +00:00
args [ 0 ] = $tw . hooks . names [ hookName ] [ i ] . apply ( null , args ) ;
2014-11-22 16:11:56 +00:00
}
}
2017-02-09 15:42:21 +00:00
return args [ 0 ] ;
2014-11-22 16:11:56 +00:00
} ;
2013-08-20 14:17:57 +00:00
/////////////////////////// Main boot function to decrypt tiddlers and then startup
2017-06-09 15:20:12 +00:00
$tw . boot . boot = function ( callback ) {
2013-08-20 14:17:57 +00:00
// Initialise crypto object
$tw . crypto = new $tw . utils . Crypto ( ) ;
// Initialise password prompter
2014-05-07 08:59:21 +00:00
if ( $tw . browser && ! $tw . node ) {
2013-08-20 14:17:57 +00:00
$tw . passwordPrompt = new $tw . utils . PasswordPrompt ( ) ;
}
// Preload any encrypted tiddlers
$tw . boot . decryptEncryptedTiddlers ( function ( ) {
// Startup
2017-06-09 15:21:39 +00:00
$tw . boot . startup ( { callback : callback } ) ;
2013-08-20 14:17:57 +00:00
} ) ;
} ;
/////////////////////////// Autoboot in the browser
2013-03-22 19:27:09 +00:00
2014-01-14 14:09:04 +00:00
if ( $tw . browser && ! $tw . boot . suppressBoot ) {
2013-08-20 14:17:57 +00:00
$tw . boot . boot ( ) ;
2012-11-16 16:59:47 +00:00
}
2013-06-12 11:40:48 +00:00
2013-10-12 17:44:09 +00:00
return $tw ;
} ) ;
if ( typeof ( exports ) !== "undefined" ) {
exports . TiddlyWiki = _boot ;
} else {
_boot ( window . $tw ) ;
2014-12-19 18:24:13 +00:00
}
2022-01-03 17:25:40 +00:00
//# sourceURL=$:/boot/boot.js