2012-04-30 11:23:03 +00:00
/ * \
2012-05-03 20:47:16 +00:00
title : $ : / c o r e / m o d u l e s / w i k i . j s
2012-04-30 11:23:03 +00:00
type : application / javascript
module - type : wikimethod
Extension methods for the $tw . Wiki object
Adds the following properties to the wiki object :
2013-03-16 10:42:46 +00:00
* ` eventListeners ` is a hashmap by type of arrays of listener functions
2013-11-13 23:33:32 +00:00
* ` changedTiddlers ` is a hashmap describing changes to named tiddlers since wiki change events were last dispatched . Each entry is a hashmap containing two fields :
2012-04-30 11:23:03 +00:00
modified : true / false
deleted : true / false
2013-11-13 23:33:32 +00:00
* ` changeCount ` is a hashmap by tiddler title containing a numerical index that starts at zero and is incremented each time a tiddler is created changed or deleted
* ` caches ` is a hashmap by tiddler title containing a further hashmap of named cache objects . Caches are automatically cleared when a tiddler is modified or deleted
* ` globalCache ` is a hashmap by cache name of cache objects that are cleared whenever any tiddler change occurs
2012-04-30 11:23:03 +00:00
\ * /
( function ( ) {
2012-05-04 17:49:04 +00:00
/*jslint node: true, browser: true */
/*global $tw: false */
2012-04-30 11:23:03 +00:00
"use strict" ;
2013-11-08 08:47:00 +00:00
var widget = require ( "$:/core/modules/widgets/widget.js" ) ;
2013-10-12 16:05:13 +00:00
2016-11-23 18:20:31 +00:00
var USER _NAME _TITLE = "$:/status/UserName" ,
TIMESTAMP _DISABLE _TITLE = "$:/config/TimestampDisable" ;
2013-08-04 13:02:07 +00:00
2019-05-24 20:07:37 +00:00
/ *
Add available indexers to this wiki
* /
exports . addIndexersToWiki = function ( ) {
var self = this ;
$tw . utils . each ( $tw . modules . applyMethods ( "indexer" ) , function ( Indexer , name ) {
self . addIndexer ( new Indexer ( self ) , name ) ;
} ) ;
} ;
2012-06-19 07:56:15 +00:00
/ *
2012-10-18 22:20:27 +00:00
Get the value of a text reference . Text references can have any of these forms :
< tiddlertitle >
2013-01-15 17:50:47 +00:00
< tiddlertitle > ! ! < fieldname >
! ! < fieldname > - specifies a field of the current tiddlers
2013-07-08 20:44:12 +00:00
< tiddlertitle > # # < index >
2012-06-19 07:56:15 +00:00
* /
exports . getTextReference = function ( textRef , defaultText , currTiddlerTitle ) {
2012-12-13 21:34:31 +00:00
var tr = $tw . utils . parseTextReference ( textRef ) ,
2013-01-15 17:50:47 +00:00
title = tr . title || currTiddlerTitle ;
if ( tr . field ) {
var tiddler = this . getTiddler ( title ) ;
2013-07-08 14:16:55 +00:00
if ( tr . field === "title" ) { // Special case so we can return the title of a non-existent tiddler
return title ;
} else if ( tiddler && $tw . utils . hop ( tiddler . fields , tr . field ) ) {
2013-05-27 16:55:23 +00:00
return tiddler . getFieldString ( tr . field ) ;
2013-01-15 17:50:47 +00:00
} else {
return defaultText ;
}
} else if ( tr . index ) {
return this . extractTiddlerDataItem ( title , tr . index , defaultText ) ;
2012-06-19 07:56:15 +00:00
} else {
2013-03-27 13:48:17 +00:00
return this . getTiddlerText ( title , defaultText ) ;
2012-06-19 07:56:15 +00:00
}
} ;
2012-11-11 14:31:45 +00:00
exports . setTextReference = function ( textRef , value , currTiddlerTitle ) {
2012-12-13 21:34:31 +00:00
var tr = $tw . utils . parseTextReference ( textRef ) ,
2012-06-19 15:47:25 +00:00
title = tr . title || currTiddlerTitle ;
2014-05-31 17:37:27 +00:00
this . setText ( title , tr . field , tr . index , value ) ;
} ;
2015-08-06 17:17:06 +00:00
exports . setText = function ( title , field , index , value , options ) {
options = options || { } ;
2015-08-09 10:09:02 +00:00
var creationFields = options . suppressTimestamp ? { } : this . getCreationFields ( ) ,
modificationFields = options . suppressTimestamp ? { } : this . getModificationFields ( ) ;
2014-03-12 21:54:43 +00:00
// Check if it is a reference to a tiddler field
2014-05-31 17:37:27 +00:00
if ( index ) {
2014-04-06 21:43:10 +00:00
var data = this . getTiddlerData ( title , Object . create ( null ) ) ;
2015-09-25 13:20:37 +00:00
if ( value !== undefined ) {
data [ index ] = value ;
} else {
delete data [ index ] ;
}
2021-10-24 19:19:42 +00:00
this . setTiddlerData ( title , data , { } , { suppressTimestamp : options . suppressTimestamp } ) ;
2014-03-12 21:54:43 +00:00
} else {
var tiddler = this . getTiddler ( title ) ,
fields = { title : title } ;
2014-05-31 17:37:27 +00:00
fields [ field || "text" ] = value ;
2015-08-06 17:17:06 +00:00
this . addTiddler ( new $tw . Tiddler ( creationFields , tiddler , fields , modificationFields ) ) ;
2012-06-19 15:47:25 +00:00
}
2012-06-19 07:56:15 +00:00
} ;
2012-06-19 15:47:25 +00:00
exports . deleteTextReference = function ( textRef , currTiddlerTitle ) {
2012-12-13 21:34:31 +00:00
var tr = $tw . utils . parseTextReference ( textRef ) ,
2012-06-19 15:47:25 +00:00
title , tiddler , fields ;
// Check if it is a reference to a tiddler
if ( tr . title && ! tr . field ) {
this . deleteTiddler ( tr . title ) ;
// Else check for a field reference
} else if ( tr . field ) {
title = tr . title || currTiddlerTitle ;
tiddler = this . getTiddler ( title ) ;
if ( tiddler && $tw . utils . hop ( tiddler . fields , tr . field ) ) {
2014-04-06 21:43:10 +00:00
fields = Object . create ( null ) ;
2012-06-19 15:47:25 +00:00
fields [ tr . field ] = undefined ;
2013-08-04 13:02:07 +00:00
this . addTiddler ( new $tw . Tiddler ( tiddler , fields , this . getModificationFields ( ) ) ) ;
2012-06-19 15:47:25 +00:00
}
}
2012-06-19 07:56:15 +00:00
} ;
2013-03-16 10:42:46 +00:00
exports . addEventListener = function ( type , listener ) {
this . eventListeners = this . eventListeners || { } ;
this . eventListeners [ type ] = this . eventListeners [ type ] || [ ] ;
2021-05-30 18:20:17 +00:00
this . eventListeners [ type ] . push ( listener ) ;
2013-03-16 10:42:46 +00:00
} ;
exports . removeEventListener = function ( type , listener ) {
var listeners = this . eventListeners [ type ] ;
if ( listeners ) {
var p = listeners . indexOf ( listener ) ;
if ( p !== - 1 ) {
listeners . splice ( p , 1 ) ;
}
}
2012-04-30 11:23:03 +00:00
} ;
2013-03-16 10:42:46 +00:00
exports . dispatchEvent = function ( type /*, args */ ) {
var args = Array . prototype . slice . call ( arguments , 1 ) ,
listeners = this . eventListeners [ type ] ;
if ( listeners ) {
for ( var p = 0 ; p < listeners . length ; p ++ ) {
var listener = listeners [ p ] ;
listener . apply ( listener , args ) ;
2012-04-30 11:23:03 +00:00
}
}
} ;
/ *
Causes a tiddler to be marked as changed , incrementing the change count , and triggers event handlers .
This method should be called after the changes it describes have been made to the wiki . tiddlers [ ] array .
title : Title of tiddler
isDeleted : defaults to false ( meaning the tiddler has been created or modified ) ,
2014-03-18 21:18:37 +00:00
true if the tiddler has been deleted
2012-04-30 11:23:03 +00:00
* /
2013-03-05 19:59:55 +00:00
exports . enqueueTiddlerEvent = function ( title , isDeleted ) {
2012-04-30 11:23:03 +00:00
// Record the touch in the list of changed tiddlers
2014-04-06 21:43:10 +00:00
this . changedTiddlers = this . changedTiddlers || Object . create ( null ) ;
this . changedTiddlers [ title ] = this . changedTiddlers [ title ] || Object . create ( null ) ;
2012-04-30 11:23:03 +00:00
this . changedTiddlers [ title ] [ isDeleted ? "deleted" : "modified" ] = true ;
// Increment the change count
2014-04-06 21:43:10 +00:00
this . changeCount = this . changeCount || Object . create ( null ) ;
2012-05-09 07:51:16 +00:00
if ( $tw . utils . hop ( this . changeCount , title ) ) {
2012-04-30 11:23:03 +00:00
this . changeCount [ title ] ++ ;
} else {
this . changeCount [ title ] = 1 ;
}
// Trigger events
2015-05-02 16:22:23 +00:00
this . eventListeners = this . eventListeners || { } ;
2012-04-30 11:23:03 +00:00
if ( ! this . eventsTriggered ) {
2013-03-16 10:42:46 +00:00
var self = this ;
2012-04-30 11:23:03 +00:00
$tw . utils . nextTick ( function ( ) {
2013-03-16 10:42:46 +00:00
var changes = self . changedTiddlers ;
2014-04-06 21:43:10 +00:00
self . changedTiddlers = Object . create ( null ) ;
2013-03-16 10:42:46 +00:00
self . eventsTriggered = false ;
2014-04-30 21:48:36 +00:00
if ( $tw . utils . count ( changes ) > 0 ) {
self . dispatchEvent ( "change" , changes ) ;
}
2012-04-30 11:23:03 +00:00
} ) ;
this . eventsTriggered = true ;
}
} ;
2014-08-29 08:58:30 +00:00
exports . getSizeOfTiddlerEventQueue = function ( ) {
return $tw . utils . count ( this . changedTiddlers ) ;
} ;
2014-04-30 21:49:02 +00:00
exports . clearTiddlerEventQueue = function ( ) {
this . changedTiddlers = Object . create ( null ) ;
2014-08-30 19:44:26 +00:00
this . changeCount = Object . create ( null ) ;
2014-04-30 21:49:02 +00:00
} ;
2012-04-30 11:23:03 +00:00
exports . getChangeCount = function ( title ) {
2014-04-06 21:43:10 +00:00
this . changeCount = this . changeCount || Object . create ( null ) ;
2012-05-09 07:51:16 +00:00
if ( $tw . utils . hop ( this . changeCount , title ) ) {
2012-04-30 11:23:03 +00:00
return this . changeCount [ title ] ;
} else {
return 0 ;
}
} ;
2013-10-25 20:15:20 +00:00
/ *
Generate an unused title from the specified base
2021-05-02 18:26:50 +00:00
options . prefix must be a string
2013-10-25 20:15:20 +00:00
* /
2014-02-14 07:53:41 +00:00
exports . generateNewTitle = function ( baseTitle , options ) {
options = options || { } ;
2013-12-19 19:38:59 +00:00
var c = 0 ,
2021-05-02 18:26:50 +00:00
title = baseTitle ,
template = options . template ,
prefix = ( typeof ( options . prefix ) === "string" ) ? options . prefix : " " ;
if ( template ) {
// "count" is important to avoid an endless loop in while(...)!!
template = ( /\$count:?(\d+)?\$/i . test ( template ) ) ? template : template + "$count$" ;
title = $tw . utils . formatTitleString ( template , { "base" : baseTitle , "separator" : prefix , "counter" : c } ) ;
while ( this . tiddlerExists ( title ) || this . isShadowTiddler ( title ) || this . findDraft ( title ) ) {
title = $tw . utils . formatTitleString ( template , { "base" : baseTitle , "separator" : prefix , "counter" : ( ++ c ) } ) ;
}
} else {
while ( this . tiddlerExists ( title ) || this . isShadowTiddler ( title ) || this . findDraft ( title ) ) {
title = baseTitle + prefix + ( ++ c ) ;
}
2014-08-30 19:44:26 +00:00
}
2013-10-25 20:15:20 +00:00
return title ;
} ;
2013-04-03 13:29:12 +00:00
exports . isSystemTiddler = function ( title ) {
2016-01-05 18:48:04 +00:00
return title && title . indexOf ( "$:/" ) === 0 ;
2013-04-03 13:29:12 +00:00
} ;
exports . isTemporaryTiddler = function ( title ) {
2016-01-05 18:48:04 +00:00
return title && title . indexOf ( "$:/temp/" ) === 0 ;
2013-04-03 13:29:12 +00:00
} ;
2021-05-21 07:51:15 +00:00
exports . isVolatileTiddler = function ( title ) {
return title && title . indexOf ( "$:/temp/volatile/" ) === 0 ;
} ;
2014-01-29 09:04:41 +00:00
exports . isImageTiddler = function ( title ) {
var tiddler = this . getTiddler ( title ) ;
2021-05-30 18:20:17 +00:00
if ( tiddler ) {
2014-01-29 09:04:41 +00:00
var contentTypeInfo = $tw . config . contentTypeInfo [ tiddler . fields . type || "text/vnd.tiddlywiki" ] ;
return ! ! contentTypeInfo && contentTypeInfo . flags . indexOf ( "image" ) !== - 1 ;
} else {
return null ;
}
} ;
2019-09-16 15:15:26 +00:00
exports . isBinaryTiddler = function ( title ) {
var tiddler = this . getTiddler ( title ) ;
2021-05-30 18:20:17 +00:00
if ( tiddler ) {
2019-09-16 15:15:26 +00:00
var contentTypeInfo = $tw . config . contentTypeInfo [ tiddler . fields . type || "text/vnd.tiddlywiki" ] ;
return ! ! contentTypeInfo && contentTypeInfo . encoding === "base64" ;
} else {
return null ;
}
} ;
2013-12-02 09:59:17 +00:00
/ *
Like addTiddler ( ) except it will silently reject any plugin tiddlers that are older than the currently loaded version . Returns true if the tiddler was imported
* /
exports . importTiddler = function ( tiddler ) {
var existingTiddler = this . getTiddler ( tiddler . fields . title ) ;
// Check if we're dealing with a plugin
if ( tiddler && tiddler . hasField ( "plugin-type" ) && tiddler . hasField ( "version" ) && existingTiddler && existingTiddler . hasField ( "plugin-type" ) && existingTiddler . hasField ( "version" ) ) {
// Reject the incoming plugin if it is older
2015-02-12 23:04:20 +00:00
if ( ! $tw . utils . checkVersions ( tiddler . fields . version , existingTiddler . fields . version ) ) {
2013-12-02 09:59:17 +00:00
return false ;
}
}
// Fall through to adding the tiddler
this . addTiddler ( tiddler ) ;
return true ;
} ;
2013-08-04 13:02:07 +00:00
/ *
2013-08-06 14:27:02 +00:00
Return a hashmap of the fields that should be set when a tiddler is created
2013-08-04 13:02:07 +00:00
* /
exports . getCreationFields = function ( ) {
2016-11-23 18:20:31 +00:00
if ( this . getTiddlerText ( TIMESTAMP _DISABLE _TITLE , "" ) . toLowerCase ( ) !== "yes" ) {
var fields = {
created : new Date ( )
} ,
creator = this . getTiddlerText ( USER _NAME _TITLE ) ;
if ( creator ) {
fields . creator = creator ;
}
return fields ;
} else {
return { } ;
2013-08-04 13:02:07 +00:00
}
} ;
/ *
2013-08-06 14:27:02 +00:00
Return a hashmap of the fields that should be set when a tiddler is modified
2013-08-04 13:02:07 +00:00
* /
exports . getModificationFields = function ( ) {
2016-11-23 18:20:31 +00:00
if ( this . getTiddlerText ( TIMESTAMP _DISABLE _TITLE , "" ) . toLowerCase ( ) !== "yes" ) {
var fields = Object . create ( null ) ,
modifier = this . getTiddlerText ( USER _NAME _TITLE ) ;
fields . modified = new Date ( ) ;
if ( modifier ) {
fields . modifier = modifier ;
}
return fields ;
} else {
return { } ;
2013-08-04 13:02:07 +00:00
}
} ;
2012-04-30 11:23:03 +00:00
/ *
2013-12-11 22:01:02 +00:00
Return a sorted array of tiddler titles . Options include :
sortField : field to sort by
excludeTag : tag to exclude
includeSystem : whether to include system tiddlers ( defaults to false )
2012-04-30 11:23:03 +00:00
* /
2013-12-11 22:01:02 +00:00
exports . getTiddlers = function ( options ) {
2014-04-06 21:43:10 +00:00
options = options || Object . create ( null ) ;
2013-12-11 22:01:02 +00:00
var self = this ,
sortField = options . sortField || "title" ,
tiddlers = [ ] , t , titles = [ ] ;
2014-03-16 21:23:10 +00:00
this . each ( function ( tiddler , title ) {
if ( options . includeSystem || ! self . isSystemTiddler ( title ) ) {
if ( ! options . excludeTag || ! tiddler . hasTag ( options . excludeTag ) ) {
tiddlers . push ( tiddler ) ;
2013-12-11 22:01:02 +00:00
}
2012-06-06 12:21:20 +00:00
}
2014-03-16 21:23:10 +00:00
} ) ;
2012-04-30 11:23:03 +00:00
tiddlers . sort ( function ( a , b ) {
2012-10-16 20:41:12 +00:00
var aa = a . fields [ sortField ] . toLowerCase ( ) || "" ,
bb = b . fields [ sortField ] . toLowerCase ( ) || "" ;
2012-04-30 11:23:03 +00:00
if ( aa < bb ) {
return - 1 ;
} else {
if ( aa > bb ) {
return 1 ;
} else {
return 0 ;
}
}
} ) ;
for ( t = 0 ; t < tiddlers . length ; t ++ ) {
2013-03-15 20:02:31 +00:00
titles . push ( tiddlers [ t ] . fields . title ) ;
2012-04-30 11:23:03 +00:00
}
return titles ;
} ;
2013-03-21 22:21:00 +00:00
exports . countTiddlers = function ( excludeTag ) {
2013-12-11 22:01:02 +00:00
var tiddlers = this . getTiddlers ( { excludeTag : excludeTag } ) ;
2013-03-21 22:21:00 +00:00
return $tw . utils . count ( tiddlers ) ;
} ;
2014-04-03 19:49:16 +00:00
/ *
Returns a function iterator ( callback ) that iterates through the specified titles , and invokes the callback with callback ( tiddler , title )
* /
exports . makeTiddlerIterator = function ( titles ) {
var self = this ;
if ( ! $tw . utils . isArray ( titles ) ) {
titles = Object . keys ( titles ) ;
} else {
titles = titles . slice ( 0 ) ;
}
return function ( callback ) {
titles . forEach ( function ( title ) {
callback ( self . getTiddler ( title ) , title ) ;
} ) ;
} ;
} ;
2012-05-08 15:02:24 +00:00
/ *
Sort an array of tiddler titles by a specified field
titles : array of titles ( sorted in place )
sortField : name of field to sort by
isDescending : true if the sort should be descending
2012-06-14 10:36:26 +00:00
isCaseSensitive : true if the sort should consider upper and lower case letters to be different
2012-05-08 15:02:24 +00:00
* /
2018-03-05 11:09:25 +00:00
exports . sortTiddlers = function ( titles , sortField , isDescending , isCaseSensitive , isNumeric , isAlphaNumeric ) {
2015-01-23 11:24:47 +00:00
var self = this ;
2021-09-22 12:44:35 +00:00
if ( sortField === "title" ) {
if ( ! isNumeric && ! isAlphaNumeric ) {
if ( isCaseSensitive ) {
if ( isDescending ) {
titles . sort ( function ( a , b ) {
return b . localeCompare ( a ) ;
} ) ;
} else {
titles . sort ( function ( a , b ) {
return a . localeCompare ( b ) ;
} ) ;
}
2014-09-17 11:17:43 +00:00
} else {
2021-09-22 12:44:35 +00:00
if ( isDescending ) {
titles . sort ( function ( a , b ) {
return b . toLowerCase ( ) . localeCompare ( a . toLowerCase ( ) ) ;
} ) ;
} else {
titles . sort ( function ( a , b ) {
return a . toLowerCase ( ) . localeCompare ( b . toLowerCase ( ) ) ;
} ) ;
}
2014-09-17 11:17:43 +00:00
}
2012-05-08 15:02:24 +00:00
} else {
2021-09-22 12:44:35 +00:00
titles . sort ( function ( a , b ) {
var x , y ;
if ( isNumeric ) {
x = Number ( a ) ;
y = Number ( b ) ;
if ( isNaN ( x ) ) {
if ( isNaN ( y ) ) {
// If neither value is a number then fall through to a textual comparison
} else {
return isDescending ? - 1 : 1 ;
}
} else {
if ( isNaN ( y ) ) {
return isDescending ? 1 : - 1 ;
} else {
return isDescending ? y - x : x - y ;
}
}
}
if ( isAlphaNumeric ) {
return isDescending ? b . localeCompare ( a , undefined , { numeric : true , sensitivity : "base" } ) : a . localeCompare ( b , undefined , { numeric : true , sensitivity : "base" } ) ;
}
if ( ! isCaseSensitive ) {
a = a . toLowerCase ( ) ;
b = b . toLowerCase ( ) ;
}
return isDescending ? b . localeCompare ( a ) : a . localeCompare ( b ) ;
} ) ;
}
} else {
titles . sort ( function ( a , b ) {
var x , y ;
if ( sortField !== "title" ) {
var tiddlerA = self . getTiddler ( a ) ,
tiddlerB = self . getTiddler ( b ) ;
if ( tiddlerA ) {
a = tiddlerA . fields [ sortField ] || "" ;
} else {
a = "" ;
}
if ( tiddlerB ) {
b = tiddlerB . fields [ sortField ] || "" ;
} else {
b = "" ;
}
}
if ( isNumeric ) {
x = Number ( a ) ;
y = Number ( b ) ;
if ( isNaN ( x ) ) {
if ( isNaN ( y ) ) {
// If neither value is a number then fall through to a textual comparison
} else {
return isDescending ? - 1 : 1 ;
}
} else {
if ( isNaN ( y ) ) {
return isDescending ? 1 : - 1 ;
} else {
return isDescending ? y - x : x - y ;
}
}
}
if ( Object . prototype . toString . call ( a ) === "[object Date]" && Object . prototype . toString . call ( b ) === "[object Date]" ) {
return isDescending ? b - a : a - b ;
}
2014-04-17 19:15:52 +00:00
a = String ( a ) ;
b = String ( b ) ;
2021-09-22 12:44:35 +00:00
if ( isAlphaNumeric ) {
return isDescending ? b . localeCompare ( a , undefined , { numeric : true , sensitivity : "base" } ) : a . localeCompare ( b , undefined , { numeric : true , sensitivity : "base" } ) ;
}
2014-04-17 19:15:52 +00:00
if ( ! isCaseSensitive ) {
a = a . toLowerCase ( ) ;
b = b . toLowerCase ( ) ;
2012-05-08 15:02:24 +00:00
}
2014-04-17 19:15:52 +00:00
return isDescending ? b . localeCompare ( a ) : a . localeCompare ( b ) ;
2021-09-22 12:44:35 +00:00
} ) ;
}
2012-05-08 15:02:24 +00:00
} ;
2013-12-11 22:01:02 +00:00
/ *
For every tiddler invoke a callback ( title , tiddler ) with ` this ` set to the wiki object . Options include :
sortField : field to sort by
excludeTag : tag to exclude
includeSystem : whether to include system tiddlers ( defaults to false )
* /
exports . forEachTiddler = function ( /* [options,]callback */ ) {
2012-04-30 11:23:03 +00:00
var arg = 0 ,
2013-12-11 22:01:02 +00:00
options = arguments . length >= 2 ? arguments [ arg ++ ] : { } ,
2012-04-30 11:23:03 +00:00
callback = arguments [ arg ++ ] ,
2013-12-11 22:01:02 +00:00
titles = this . getTiddlers ( options ) ,
2012-04-30 11:23:03 +00:00
t , tiddler ;
for ( t = 0 ; t < titles . length ; t ++ ) {
2014-03-16 21:23:10 +00:00
tiddler = this . getTiddler ( titles [ t ] ) ;
2012-04-30 11:23:03 +00:00
if ( tiddler ) {
callback . call ( this , tiddler . fields . title , tiddler ) ;
}
}
} ;
2020-03-26 13:15:02 +00:00
/ *
Return an array of tiddler titles that are directly linked within the given parse tree
* /
exports . extractLinks = function ( parseTreeRoot ) {
// Count up the links
var links = [ ] ,
checkParseTree = function ( parseTree ) {
for ( var t = 0 ; t < parseTree . length ; t ++ ) {
var parseTreeNode = parseTree [ t ] ;
if ( parseTreeNode . type === "link" && parseTreeNode . attributes . to && parseTreeNode . attributes . to . type === "string" ) {
var value = parseTreeNode . attributes . to . value ;
if ( links . indexOf ( value ) === - 1 ) {
links . push ( value ) ;
}
}
if ( parseTreeNode . children ) {
checkParseTree ( parseTreeNode . children ) ;
}
}
} ;
checkParseTree ( parseTreeRoot ) ;
return links ;
} ;
2013-03-19 10:14:44 +00:00
/ *
Return an array of tiddler titles that are directly linked from the specified tiddler
* /
exports . getTiddlerLinks = function ( title ) {
var self = this ;
// We'll cache the links so they only get computed if the tiddler changes
return this . getCacheForTiddler ( title , "links" , function ( ) {
// Parse the tiddler
2013-11-08 08:51:14 +00:00
var parser = self . parseTiddler ( title ) ;
2013-03-19 10:14:44 +00:00
if ( parser ) {
2020-03-26 13:15:02 +00:00
return self . extractLinks ( parser . tree ) ;
2013-03-19 10:14:44 +00:00
}
2020-03-26 13:15:02 +00:00
return [ ] ;
2013-03-19 10:14:44 +00:00
} ) ;
} ;
2013-03-19 16:45:07 +00:00
/ *
Return an array of tiddler titles that link to the specified tiddler
* /
exports . getTiddlerBacklinks = function ( targetTitle ) {
var self = this ,
2020-03-26 13:15:02 +00:00
backlinksIndexer = this . getIndexer ( "BacklinksIndexer" ) ,
backlinks = backlinksIndexer && backlinksIndexer . lookup ( targetTitle ) ;
if ( ! backlinks ) {
2013-03-19 16:45:07 +00:00
backlinks = [ ] ;
2020-03-26 13:15:02 +00:00
this . forEachTiddler ( function ( title , tiddler ) {
var links = self . getTiddlerLinks ( title ) ;
if ( links . indexOf ( targetTitle ) !== - 1 ) {
backlinks . push ( title ) ;
}
} ) ;
}
2013-03-19 16:45:07 +00:00
return backlinks ;
} ;
2013-03-19 10:14:44 +00:00
/ *
Return a hashmap of tiddler titles that are referenced but not defined . Each value is the number of times the missing tiddler is referenced
* /
2012-04-30 11:23:03 +00:00
exports . getMissingTitles = function ( ) {
2013-03-19 10:14:44 +00:00
var self = this ,
missing = [ ] ;
// We should cache the missing tiddler list, even if we recreate it every time any tiddler is modified
this . forEachTiddler ( function ( title , tiddler ) {
var links = self . getTiddlerLinks ( title ) ;
$tw . utils . each ( links , function ( link ) {
2013-05-31 16:53:11 +00:00
if ( ( ! self . tiddlerExists ( link ) && ! self . isShadowTiddler ( link ) ) && missing . indexOf ( link ) === - 1 ) {
2013-03-19 10:14:44 +00:00
missing . push ( link ) ;
}
} ) ;
} ) ;
return missing ;
2012-04-30 11:23:03 +00:00
} ;
exports . getOrphanTitles = function ( ) {
2013-03-19 10:14:44 +00:00
var self = this ,
orphans = this . getTiddlers ( ) ;
this . forEachTiddler ( function ( title , tiddler ) {
var links = self . getTiddlerLinks ( title ) ;
$tw . utils . each ( links , function ( link ) {
var p = orphans . indexOf ( link ) ;
if ( p !== - 1 ) {
orphans . splice ( p , 1 ) ;
}
} ) ;
} ) ;
return orphans ; // Todo
2012-04-30 11:23:03 +00:00
} ;
2012-06-13 08:10:03 +00:00
/ *
Retrieves a list of the tiddler titles that are tagged with a given tag
* /
exports . getTiddlersWithTag = function ( tag ) {
2019-05-24 20:07:37 +00:00
// Try to use the indexer
var self = this ,
tagIndexer = this . getIndexer ( "TagIndexer" ) ,
2019-07-28 15:39:34 +00:00
results = tagIndexer && tagIndexer . subIndexers [ 3 ] . lookup ( tag ) ;
2019-05-24 20:07:37 +00:00
if ( ! results ) {
// If not available, perform a manual scan
results = this . getGlobalCache ( "taglist-" + tag , function ( ) {
var tagmap = self . getTagMap ( ) ;
return self . sortByList ( tagmap [ tag ] , tag ) ;
} ) ;
}
return results ;
2013-11-13 23:33:32 +00:00
} ;
/ *
Get a hashmap by tag of arrays of tiddler titles
* /
exports . getTagMap = function ( ) {
var self = this ;
return this . getGlobalCache ( "tagmap" , function ( ) {
2014-04-06 21:43:10 +00:00
var tags = Object . create ( null ) ,
2014-03-16 21:23:10 +00:00
storeTags = function ( tagArray , title ) {
2014-03-10 19:41:38 +00:00
if ( tagArray ) {
for ( var index = 0 ; index < tagArray . length ; index ++ ) {
var tag = tagArray [ index ] ;
if ( $tw . utils . hop ( tags , tag ) ) {
2014-08-30 19:44:26 +00:00
tags [ tag ] . push ( title ) ;
2014-03-10 19:41:38 +00:00
} else {
tags [ tag ] = [ title ] ;
}
2013-11-13 23:33:32 +00:00
}
}
2014-03-10 19:41:38 +00:00
} ,
title , tiddler ;
// Collect up all the tags
2014-03-17 10:50:18 +00:00
self . eachShadow ( function ( tiddler , title ) {
2014-03-16 21:23:10 +00:00
if ( ! self . tiddlerExists ( title ) ) {
2014-03-17 10:50:18 +00:00
tiddler = self . getTiddler ( title ) ;
2014-03-16 21:23:10 +00:00
storeTags ( tiddler . fields . tags , title ) ;
2013-11-13 23:33:32 +00:00
}
2014-03-17 10:50:18 +00:00
} ) ;
2014-03-16 21:23:10 +00:00
self . each ( function ( tiddler , title ) {
storeTags ( tiddler . fields . tags , title ) ;
} ) ;
2013-11-13 23:33:32 +00:00
return tags ;
} ) ;
2013-08-08 16:39:34 +00:00
} ;
2013-08-16 08:31:05 +00:00
/ *
2014-06-13 09:58:19 +00:00
Lookup a given tiddler and return a list of all the tiddlers that include it in the specified list field
2013-08-16 08:31:05 +00:00
* /
2014-06-13 09:58:19 +00:00
exports . findListingsOfTiddler = function ( targetTitle , fieldName ) {
fieldName = fieldName || "list" ;
2022-02-21 15:07:30 +00:00
var wiki = this ;
var listings = this . getGlobalCache ( "listings-" + fieldName , function ( ) {
var listings = Object . create ( null ) ;
wiki . each ( function ( tiddler , title ) {
var list = $tw . utils . parseStringArray ( tiddler . fields [ fieldName ] ) ;
if ( list ) {
for ( var i = 0 ; i < list . length ; i ++ ) {
var listItem = list [ i ] ,
listing = listings [ listItem ] || [ ] ;
if ( listing . indexOf ( title ) === - 1 ) {
listing . push ( title ) ;
}
listings [ listItem ] = listing ;
}
}
} ) ;
return listings ;
2014-03-16 21:23:10 +00:00
} ) ;
2022-02-21 15:07:30 +00:00
return listings [ targetTitle ] || [ ] ;
2013-08-16 08:31:05 +00:00
} ;
2013-08-08 16:39:34 +00:00
/ *
Sorts an array of tiddler titles according to an ordered list
* /
exports . sortByList = function ( array , listTitle ) {
2018-11-20 11:50:12 +00:00
var self = this ,
replacedTitles = Object . create ( null ) ;
2020-04-14 16:49:10 +00:00
// Given a title, this function will place it in the correct location
// within titles.
function moveItemInList ( title ) {
2018-11-20 11:50:12 +00:00
if ( ! $tw . utils . hop ( replacedTitles , title ) ) {
replacedTitles [ title ] = true ;
var newPos = - 1 ,
tiddler = self . getTiddler ( title ) ;
if ( tiddler ) {
var beforeTitle = tiddler . fields [ "list-before" ] ,
afterTitle = tiddler . fields [ "list-after" ] ;
if ( beforeTitle === "" ) {
newPos = 0 ;
} else if ( afterTitle === "" ) {
newPos = titles . length ;
} else if ( beforeTitle ) {
2020-04-14 16:49:10 +00:00
// if this title is placed relative
// to another title, make sure that
// title is placed before we place
// this one.
moveItemInList ( beforeTitle ) ;
2018-11-20 11:50:12 +00:00
newPos = titles . indexOf ( beforeTitle ) ;
} else if ( afterTitle ) {
2020-04-14 16:49:10 +00:00
// Same deal
moveItemInList ( afterTitle ) ;
2018-11-20 11:50:12 +00:00
newPos = titles . indexOf ( afterTitle ) ;
if ( newPos >= 0 ) {
++ newPos ;
}
}
2020-04-14 16:49:10 +00:00
// If a new position is specified, let's move it
if ( newPos !== - 1 ) {
// get its current Pos, and make sure
// sure that it's _actually_ in the list
// and that it would _actually_ move
// (#4275) We don't bother calling
// indexOf unless we have a new
// position to work with
var currPos = titles . indexOf ( title ) ;
if ( currPos >= 0 && newPos !== currPos ) {
// move it!
titles . splice ( currPos , 1 ) ;
if ( newPos >= currPos ) {
newPos -- ;
}
titles . splice ( newPos , 0 , title ) ;
2018-11-20 11:50:12 +00:00
}
}
}
}
}
2013-08-08 16:39:34 +00:00
var list = this . getTiddlerList ( listTitle ) ;
2013-11-13 23:33:32 +00:00
if ( ! array || array . length === 0 ) {
return [ ] ;
2014-04-14 20:30:30 +00:00
} else {
2015-03-11 19:17:14 +00:00
var titles = [ ] , t , title ;
2013-08-08 16:39:34 +00:00
// First place any entries that are present in the list
for ( t = 0 ; t < list . length ; t ++ ) {
title = list [ t ] ;
if ( array . indexOf ( title ) !== - 1 ) {
titles . push ( title ) ;
}
}
2015-03-11 19:17:14 +00:00
// Then place any remaining entries
2013-08-08 16:39:34 +00:00
for ( t = 0 ; t < array . length ; t ++ ) {
title = array [ t ] ;
if ( list . indexOf ( title ) === - 1 ) {
2015-03-11 19:17:14 +00:00
titles . push ( title ) ;
2014-04-14 20:30:30 +00:00
}
}
// Finally obey the list-before and list-after fields of each tiddler in turn
2018-11-20 11:50:12 +00:00
var sortedTitles = titles . slice ( 0 ) ;
2018-10-07 11:15:33 +00:00
for ( t = 0 ; t < sortedTitles . length ; t ++ ) {
title = sortedTitles [ t ] ;
2020-04-14 16:49:10 +00:00
moveItemInList ( title ) ;
2013-08-08 16:39:34 +00:00
}
return titles ;
}
2012-06-13 08:10:03 +00:00
} ;
2014-07-17 17:41:20 +00:00
exports . getSubTiddler = function ( title , subTiddlerTitle ) {
2015-07-10 15:43:50 +00:00
var bundleInfo = this . getPluginInfo ( title ) || this . getTiddlerDataCached ( title ) ;
2014-09-26 20:33:47 +00:00
if ( bundleInfo && bundleInfo . tiddlers ) {
2014-07-17 17:41:20 +00:00
var subTiddler = bundleInfo . tiddlers [ subTiddlerTitle ] ;
if ( subTiddler ) {
return new $tw . Tiddler ( subTiddler ) ;
}
}
return null ;
} ;
2013-04-08 17:47:46 +00:00
/ *
Retrieve a tiddler as a JSON string of the fields
* /
exports . getTiddlerAsJson = function ( title ) {
var tiddler = this . getTiddler ( title ) ;
if ( tiddler ) {
2014-04-06 21:43:10 +00:00
var fields = Object . create ( null ) ;
2013-04-08 17:47:46 +00:00
$tw . utils . each ( tiddler . fields , function ( value , name ) {
fields [ name ] = tiddler . getFieldString ( name ) ;
} ) ;
return JSON . stringify ( fields ) ;
} else {
return JSON . stringify ( { title : title } ) ;
}
} ;
2020-02-04 13:57:24 +00:00
exports . getTiddlersAsJson = function ( filter , spaces ) {
2018-10-01 10:27:45 +00:00
var tiddlers = this . filterTiddlers ( filter ) ,
2020-02-04 13:57:24 +00:00
spaces = ( spaces === undefined ) ? $tw . config . preferences . jsonSpaces : spaces ,
2018-10-01 10:27:45 +00:00
data = [ ] ;
for ( var t = 0 ; t < tiddlers . length ; t ++ ) {
var tiddler = this . getTiddler ( tiddlers [ t ] ) ;
if ( tiddler ) {
var fields = new Object ( ) ;
for ( var field in tiddler . fields ) {
fields [ field ] = tiddler . getFieldString ( field ) ;
}
data . push ( fields ) ;
}
}
2020-02-04 13:57:24 +00:00
return JSON . stringify ( data , null , spaces ) ;
2018-10-01 10:27:45 +00:00
} ;
2012-07-22 21:03:06 +00:00
/ *
2014-07-17 17:41:20 +00:00
Get the content of a tiddler as a JavaScript object . How this is done depends on the type of the tiddler :
2012-07-22 21:03:06 +00:00
application / json : the tiddler JSON is parsed into an object
2013-01-15 17:50:47 +00:00
application / x - tiddler - dictionary : the tiddler is parsed as sequence of name : value pairs
2012-07-22 21:03:06 +00:00
Other types currently just return null .
2014-07-17 17:41:20 +00:00
titleOrTiddler : string tiddler title or a tiddler object
defaultData : default data to be returned if the tiddler is missing or doesn ' t contain data
2015-07-10 15:43:50 +00:00
Note that the same value is returned for repeated calls for the same tiddler data . The value is frozen to prevent modification ; otherwise modifications would be visible to all callers
* /
exports . getTiddlerDataCached = function ( titleOrTiddler , defaultData ) {
var self = this ,
tiddler = titleOrTiddler ;
if ( ! ( tiddler instanceof $tw . Tiddler ) ) {
2021-05-30 18:20:17 +00:00
tiddler = this . getTiddler ( tiddler ) ;
2015-07-10 15:43:50 +00:00
}
if ( tiddler ) {
return this . getCacheForTiddler ( tiddler . fields . title , "data" , function ( ) {
// Return the frozen value
2017-02-27 22:39:37 +00:00
var value = self . getTiddlerData ( tiddler . fields . title , undefined ) ;
2015-07-10 15:43:50 +00:00
$tw . utils . deepFreeze ( value ) ;
return value ;
2017-02-27 22:39:37 +00:00
} ) || defaultData ;
2015-07-10 15:43:50 +00:00
} else {
return defaultData ;
}
} ;
/ *
Alternative , uncached version of getTiddlerDataCached ( ) . The return value can be mutated freely and reused
2012-07-22 21:03:06 +00:00
* /
2014-07-17 17:41:20 +00:00
exports . getTiddlerData = function ( titleOrTiddler , defaultData ) {
var tiddler = titleOrTiddler ,
2012-07-22 21:03:06 +00:00
data ;
2014-07-17 17:41:20 +00:00
if ( ! ( tiddler instanceof $tw . Tiddler ) ) {
2021-05-30 18:20:17 +00:00
tiddler = this . getTiddler ( tiddler ) ;
2014-07-17 17:41:20 +00:00
}
2013-01-15 17:50:47 +00:00
if ( tiddler && tiddler . fields . text ) {
2015-07-10 15:43:50 +00:00
switch ( tiddler . fields . type ) {
case "application/json" :
// JSON tiddler
2022-02-21 15:29:25 +00:00
return $tw . utils . parseJSONSafe ( tiddler . fields . text , defaultData ) ;
2015-07-10 15:43:50 +00:00
case "application/x-tiddler-dictionary" :
return $tw . utils . parseFields ( tiddler . fields . text ) ;
}
2012-07-22 21:03:06 +00:00
}
return defaultData ;
} ;
2013-01-15 17:50:47 +00:00
/ *
Extract an indexed field from within a data tiddler
* /
2014-07-17 17:41:20 +00:00
exports . extractTiddlerDataItem = function ( titleOrTiddler , index , defaultText ) {
2017-02-27 22:40:09 +00:00
var data = this . getTiddlerDataCached ( titleOrTiddler , Object . create ( null ) ) ,
2013-01-15 17:50:47 +00:00
text ;
if ( data && $tw . utils . hop ( data , index ) ) {
text = data [ index ] ;
}
if ( typeof text === "string" || typeof text === "number" ) {
return text . toString ( ) ;
} else {
return defaultText ;
}
} ;
2012-07-22 21:03:06 +00:00
/ *
Set a tiddlers content to a JavaScript object . Currently this is done by setting the tiddler ' s type to "application/json" and setting the text to the JSON text of the data .
2014-02-13 17:59:33 +00:00
title : title of tiddler
data : object that can be serialised to JSON
fields : optional hashmap of additional tiddler fields to be set
2021-10-24 19:19:42 +00:00
options : optional hashmap of options including :
suppressTimestamp : if true , don ' t set the creation / modification timestamps
2012-07-22 21:03:06 +00:00
* /
2021-10-24 19:19:42 +00:00
exports . setTiddlerData = function ( title , data , fields , options ) {
options = options || { } ;
2014-02-23 12:33:00 +00:00
var existingTiddler = this . getTiddler ( title ) ,
2021-10-24 19:19:42 +00:00
creationFields = options . suppressTimestamp ? { } : this . getCreationFields ( ) ,
modificationFields = options . suppressTimestamp ? { } : this . getModificationFields ( ) ,
2014-02-23 12:33:00 +00:00
newFields = {
title : title
2021-10-24 19:19:42 +00:00
} ;
2014-02-23 12:33:00 +00:00
if ( existingTiddler && existingTiddler . fields . type === "application/x-tiddler-dictionary" ) {
newFields . text = $tw . utils . makeTiddlerDictionary ( data ) ;
} else {
newFields . type = "application/json" ;
newFields . text = JSON . stringify ( data , null , $tw . config . preferences . jsonSpaces ) ;
}
2021-10-24 19:19:42 +00:00
this . addTiddler ( new $tw . Tiddler ( creationFields , existingTiddler , fields , newFields , modificationFields ) ) ;
2012-07-22 21:03:06 +00:00
} ;
2012-10-25 13:57:33 +00:00
/ *
Return the content of a tiddler as an array containing each line
* /
2014-01-25 18:14:30 +00:00
exports . getTiddlerList = function ( title , field , index ) {
if ( index ) {
return $tw . utils . parseStringArray ( this . extractTiddlerDataItem ( title , index , "" ) ) ;
}
field = field || "list" ;
2012-10-25 13:57:33 +00:00
var tiddler = this . getTiddler ( title ) ;
2014-01-25 18:14:30 +00:00
if ( tiddler ) {
return ( $tw . utils . parseStringArray ( tiddler . fields [ field ] ) || [ ] ) . slice ( 0 ) ;
2012-10-25 13:57:33 +00:00
}
return [ ] ;
} ;
2013-11-13 23:33:32 +00:00
// Return a named global cache object. Global cache objects are cleared whenever a tiddler change occurs
exports . getGlobalCache = function ( cacheName , initializer ) {
2014-04-06 21:43:10 +00:00
this . globalCache = this . globalCache || Object . create ( null ) ;
2013-11-13 23:33:32 +00:00
if ( $tw . utils . hop ( this . globalCache , cacheName ) ) {
return this . globalCache [ cacheName ] ;
} else {
this . globalCache [ cacheName ] = initializer ( ) ;
return this . globalCache [ cacheName ] ;
}
} ;
exports . clearGlobalCache = function ( ) {
2014-04-06 21:43:10 +00:00
this . globalCache = Object . create ( null ) ;
2014-08-30 19:44:26 +00:00
} ;
2013-11-13 23:33:32 +00:00
2012-04-30 11:23:03 +00:00
// Return the named cache object for a tiddler. If the cache doesn't exist then the initializer function is invoked to create it
exports . getCacheForTiddler = function ( title , cacheName , initializer ) {
2015-07-05 16:48:18 +00:00
this . caches = this . caches || Object . create ( null ) ;
var caches = this . caches [ title ] ;
2021-09-10 20:17:35 +00:00
if ( caches && caches [ cacheName ] !== undefined ) {
2015-07-05 16:48:18 +00:00
return caches [ cacheName ] ;
} else {
if ( ! caches ) {
caches = Object . create ( null ) ;
this . caches [ title ] = caches ;
}
caches [ cacheName ] = initializer ( ) ;
return caches [ cacheName ] ;
}
2012-04-30 11:23:03 +00:00
} ;
2015-08-02 21:22:33 +00:00
// Clear all caches associated with a particular tiddler, or, if the title is null, clear all the caches for all the tiddlers
2012-04-30 11:23:03 +00:00
exports . clearCache = function ( title ) {
2015-08-02 21:22:33 +00:00
if ( title ) {
this . caches = this . caches || Object . create ( null ) ;
if ( $tw . utils . hop ( this . caches , title ) ) {
delete this . caches [ title ] ;
}
} else {
this . caches = Object . create ( null ) ;
2012-04-30 11:23:03 +00:00
}
} ;
2012-12-28 22:08:32 +00:00
exports . initParsers = function ( moduleType ) {
2013-04-25 16:40:12 +00:00
// Install the parser modules
$tw . Wiki . parsers = { } ;
2012-12-28 22:08:32 +00:00
var self = this ;
2013-01-16 13:56:11 +00:00
$tw . modules . forEachModuleOfType ( "parser" , function ( title , module ) {
2012-12-28 22:08:32 +00:00
for ( var f in module ) {
if ( $tw . utils . hop ( module , f ) ) {
2013-04-25 16:40:12 +00:00
$tw . Wiki . parsers [ f ] = module [ f ] ; // Store the parser class
2012-12-28 22:08:32 +00:00
}
}
} ) ;
2018-05-18 16:53:07 +00:00
// Use the generic binary parser for any binary types not registered so far
if ( $tw . Wiki . parsers [ "application/octet-stream" ] ) {
Object . keys ( $tw . config . contentTypeInfo ) . forEach ( function ( type ) {
if ( ! $tw . utils . hop ( $tw . Wiki . parsers , type ) && $tw . config . contentTypeInfo [ type ] . encoding === "base64" ) {
$tw . Wiki . parsers [ type ] = $tw . Wiki . parsers [ "application/octet-stream" ] ;
}
2021-05-30 18:20:17 +00:00
} ) ;
2018-05-18 16:53:07 +00:00
}
2012-12-28 22:08:32 +00:00
} ;
2012-12-13 21:34:31 +00:00
/ *
Parse a block of text of a specified MIME type
2012-12-20 15:07:38 +00:00
type : content type of text to be parsed
text : text
options : see below
Options include :
parseAsInline : if true , the text of the tiddler will be parsed as an inline run
2014-06-12 07:36:30 +00:00
_canonical _uri : optional string of the canonical URI of this content
2012-12-13 21:34:31 +00:00
* /
2015-07-05 16:48:18 +00:00
exports . parseText = function ( type , text , options ) {
2016-05-02 09:12:08 +00:00
text = text || "" ;
2012-12-26 22:02:59 +00:00
options = options || { } ;
2012-12-27 17:08:29 +00:00
// Select a parser
2013-04-25 16:40:12 +00:00
var Parser = $tw . Wiki . parsers [ type ] ;
2015-02-01 18:33:40 +00:00
if ( ! Parser && $tw . utils . getFileExtensionInfo ( type ) ) {
Parser = $tw . Wiki . parsers [ $tw . utils . getFileExtensionInfo ( type ) . type ] ;
2012-12-27 17:08:29 +00:00
}
if ( ! Parser ) {
2013-04-25 16:40:12 +00:00
Parser = $tw . Wiki . parsers [ options . defaultType || "text/vnd.tiddlywiki" ] ;
2012-12-27 17:08:29 +00:00
}
if ( ! Parser ) {
return null ;
}
// Return the parser instance
return new Parser ( type , text , {
2012-12-26 22:02:59 +00:00
parseAsInline : options . parseAsInline ,
2014-06-12 07:36:30 +00:00
wiki : this ,
2022-05-23 14:30:33 +00:00
_canonical _uri : options . _canonical _uri ,
configTrimWhiteSpace : options . configTrimWhiteSpace
2012-12-26 22:02:59 +00:00
} ) ;
2012-12-13 21:34:31 +00:00
} ;
/ *
Parse a tiddler according to its MIME type
* /
2015-07-05 16:48:18 +00:00
exports . parseTiddler = function ( title , options ) {
2014-06-12 07:36:30 +00:00
options = $tw . utils . extend ( { } , options ) ;
2015-07-08 07:26:19 +00:00
var cacheType = options . parseAsInline ? "inlineParseTree" : "blockParseTree" ,
2012-12-20 15:07:38 +00:00
tiddler = this . getTiddler ( title ) ,
2012-12-13 21:34:31 +00:00
self = this ;
2012-12-20 15:07:38 +00:00
return tiddler ? this . getCacheForTiddler ( title , cacheType , function ( ) {
2014-06-12 07:36:30 +00:00
if ( tiddler . hasField ( "_canonical_uri" ) ) {
options . _canonical _uri = tiddler . fields . _canonical _uri ;
}
2015-07-05 16:48:18 +00:00
return self . parseText ( tiddler . fields . type , tiddler . fields . text , options ) ;
2012-12-13 21:34:31 +00:00
} ) : null ;
} ;
2013-11-08 08:51:14 +00:00
exports . parseTextReference = function ( title , field , index , options ) {
2021-06-02 20:45:06 +00:00
var tiddler ,
text ,
parserInfo ;
if ( ! options . subTiddler ) {
2014-07-17 17:41:20 +00:00
tiddler = this . getTiddler ( title ) ;
if ( field === "text" || ( ! field && ! index ) ) {
this . getTiddlerText ( title ) ; // Force the tiddler to be lazily loaded
return this . parseTiddler ( title , options ) ;
}
2021-06-02 20:45:06 +00:00
}
parserInfo = this . getTextReferenceParserInfo ( title , field , index , options ) ;
if ( parserInfo . sourceText !== null ) {
return this . parseText ( parserInfo . parserType , parserInfo . sourceText , options ) ;
} else {
return null ;
}
} ;
exports . getTextReferenceParserInfo = function ( title , field , index , options ) {
var tiddler ,
parserInfo = {
sourceText : null ,
parserType : "text/vnd.tiddlywiki"
} ;
if ( options . subTiddler ) {
tiddler = this . getSubTiddler ( title , options . subTiddler ) ;
} else {
tiddler = this . getTiddler ( title ) ;
2014-07-17 17:41:20 +00:00
}
if ( field === "text" || ( ! field && ! index ) ) {
2014-07-31 17:07:52 +00:00
if ( tiddler && tiddler . fields ) {
2021-06-02 20:45:06 +00:00
parserInfo . sourceText = tiddler . fields . text || "" ;
if ( tiddler . fields . type ) {
parserInfo . parserType = tiddler . fields . type ;
}
2014-07-31 17:07:52 +00:00
}
2014-07-17 17:41:20 +00:00
} else if ( field ) {
if ( field === "title" ) {
2021-06-02 20:45:06 +00:00
parserInfo . sourceText = title ;
} else if ( tiddler && tiddler . fields ) {
2021-06-29 11:07:14 +00:00
parserInfo . sourceText = tiddler . hasField ( field ) ? tiddler . fields [ field ] . toString ( ) : null ;
2014-07-17 17:41:20 +00:00
}
} else if ( index ) {
2014-10-31 07:24:49 +00:00
this . getTiddlerText ( title ) ; // Force the tiddler to be lazily loaded
2021-06-02 20:45:06 +00:00
parserInfo . sourceText = this . extractTiddlerDataItem ( tiddler , index , null ) ;
2021-06-02 12:58:30 +00:00
}
2021-06-02 20:45:06 +00:00
if ( parserInfo . sourceText === null ) {
parserInfo . parserType = null ;
}
return parserInfo ;
}
2013-10-12 16:05:13 +00:00
2013-10-29 14:48:24 +00:00
/ *
Make a widget tree for a parse tree
parser : parser object
options : see below
Options include :
document : optional document to use
variables : hashmap of variables to set
parentWidget : optional parent widget for the root node
* /
exports . makeWidget = function ( parser , options ) {
options = options || { } ;
var widgetNode = {
type : "widget" ,
2022-05-26 20:11:53 +00:00
children : [ {
type : "importvariables" ,
attributes : {
filter : {
name : "filter" ,
type : "string" ,
value : "[all[shadows+tiddlers]tag[$:/tags/Global]!is[draft]]"
}
} ,
isBlock : false ,
children : [ ]
} ]
2013-10-29 14:48:24 +00:00
} ,
2022-05-26 20:11:53 +00:00
currWidgetNode = widgetNode . children [ 0 ] ;
// Create let variable widget for variables
if ( $tw . utils . count ( options . variables ) > 0 ) {
var letVariableWidget = {
type : "let" ,
2013-10-29 14:48:24 +00:00
attributes : {
} ,
children : [ ]
} ;
2022-05-26 20:11:53 +00:00
$tw . utils . each ( options . variables , function ( value , name ) {
letVariableWidget . attributes [ name ] = {
name : name ,
type : "string" ,
value : "" + value
}
} ) ;
currWidgetNode . children = [ letVariableWidget ] ;
currWidgetNode = letVariableWidget ;
}
2013-10-29 14:48:24 +00:00
// Add in the supplied parse tree nodes
currWidgetNode . children = parser ? parser . tree : [ ] ;
// Create the widget
return new widget . widget ( widgetNode , {
wiki : this ,
2014-01-15 14:57:35 +00:00
document : options . document || $tw . fakeDocument ,
2013-10-29 14:48:24 +00:00
parentWidget : options . parentWidget
} ) ;
} ;
2014-05-06 09:14:22 +00:00
/ *
Make a widget tree for transclusion
title : target tiddler title
2014-07-04 20:03:11 +00:00
options : as for wiki . makeWidget ( ) plus :
options . field : optional field to transclude ( defaults to "text" )
2015-03-18 11:39:50 +00:00
options . mode : transclusion mode "inline" or "block"
2020-11-07 09:51:01 +00:00
options . recursionMarker : optional flag to set a recursion marker , defaults to "yes"
2014-07-04 20:03:11 +00:00
options . children : optional array of children for the transclude widget
2016-10-18 15:39:18 +00:00
options . importVariables : optional importvariables filter string for macros to be included
options . importPageMacros : optional boolean ; if true , equivalent to passing "[[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]" to options . importVariables
2014-05-06 09:14:22 +00:00
* /
exports . makeTranscludeWidget = function ( title , options ) {
options = options || { } ;
2016-10-18 15:39:18 +00:00
var parseTreeDiv = { tree : [ {
2014-07-04 20:03:11 +00:00
type : "element" ,
tag : "div" ,
2016-10-18 15:39:18 +00:00
children : [ ] } ] } ,
parseTreeImportVariables = {
type : "importvariables" ,
attributes : {
filter : {
name : "filter" ,
type : "string"
}
} ,
isBlock : false ,
children : [ ] } ,
parseTreeTransclude = {
type : "transclude" ,
attributes : {
2020-11-07 09:51:01 +00:00
recursionMarker : {
name : "recursionMarker" ,
type : "string" ,
value : options . recursionMarker || "yes"
} ,
2016-10-18 15:39:18 +00:00
tiddler : {
name : "tiddler" ,
type : "string" ,
2020-11-07 09:51:01 +00:00
value : title
}
} ,
2016-10-18 15:39:18 +00:00
isBlock : ! options . parseAsInline } ;
if ( options . importVariables || options . importPageMacros ) {
if ( options . importVariables ) {
parseTreeImportVariables . attributes . filter . value = options . importVariables ;
} else if ( options . importPageMacros ) {
parseTreeImportVariables . attributes . filter . value = "[[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]" ;
}
parseTreeDiv . tree [ 0 ] . children . push ( parseTreeImportVariables ) ;
parseTreeImportVariables . children . push ( parseTreeTransclude ) ;
} else {
parseTreeDiv . tree [ 0 ] . children . push ( parseTreeTransclude ) ;
}
2014-07-04 20:03:11 +00:00
if ( options . field ) {
2016-10-18 15:39:18 +00:00
parseTreeTransclude . attributes . field = { type : "string" , value : options . field } ;
2014-07-04 20:03:11 +00:00
}
2015-03-18 11:39:50 +00:00
if ( options . mode ) {
2016-10-18 15:39:18 +00:00
parseTreeTransclude . attributes . mode = { type : "string" , value : options . mode } ;
2015-03-18 11:39:50 +00:00
}
2014-07-04 20:03:11 +00:00
if ( options . children ) {
2016-10-18 15:39:18 +00:00
parseTreeTransclude . children = options . children ;
2014-07-04 20:03:11 +00:00
}
2020-05-06 10:27:50 +00:00
return this . makeWidget ( parseTreeDiv , options ) ;
2014-05-06 09:14:22 +00:00
} ;
2013-10-12 16:05:13 +00:00
/ *
Parse text in a specified format and render it into another format
outputType : content type for the output
textType : content type of the input text
text : input text
2013-10-29 14:48:24 +00:00
options : see below
Options include :
variables : hashmap of variables to set
parentWidget : optional parent widget for the root node
2013-10-12 16:05:13 +00:00
* /
2013-11-08 08:51:14 +00:00
exports . renderText = function ( outputType , textType , text , options ) {
2013-10-29 14:48:24 +00:00
options = options || { } ;
2013-11-12 19:52:25 +00:00
var parser = this . parseText ( textType , text , options ) ,
2013-10-29 14:48:24 +00:00
widgetNode = this . makeWidget ( parser , options ) ;
2014-01-15 14:57:35 +00:00
var container = $tw . fakeDocument . createElement ( "div" ) ;
2013-10-12 16:05:13 +00:00
widgetNode . render ( container , null ) ;
return outputType === "text/html" ? container . innerHTML : container . textContent ;
} ;
/ *
Parse text from a tiddler and render it into another format
outputType : content type for the output
title : title of the tiddler to be rendered
2013-10-29 14:48:24 +00:00
options : see below
Options include :
variables : hashmap of variables to set
parentWidget : optional parent widget for the root node
2013-10-12 16:05:13 +00:00
* /
2013-11-08 08:51:14 +00:00
exports . renderTiddler = function ( outputType , title , options ) {
2013-10-29 14:48:24 +00:00
options = options || { } ;
2014-02-25 14:49:07 +00:00
var parser = this . parseTiddler ( title , options ) ,
2013-10-29 14:48:24 +00:00
widgetNode = this . makeWidget ( parser , options ) ;
2014-01-15 14:57:35 +00:00
var container = $tw . fakeDocument . createElement ( "div" ) ;
2013-10-12 16:05:13 +00:00
widgetNode . render ( container , null ) ;
2014-02-25 14:49:07 +00:00
return outputType === "text/html" ? container . innerHTML : ( outputType === "text/plain-formatted" ? container . formattedTextContent : container . textContent ) ;
2013-10-12 16:05:13 +00:00
} ;
2012-10-17 13:34:59 +00:00
/ *
Return an array of tiddler titles that match a search string
text : The text string to search for
2012-10-17 13:57:13 +00:00
options : see below
Options available :
2022-02-21 15:05:34 +00:00
source : an iterator function for the source tiddlers , called source ( iterator ) ,
where iterator is called as iterator ( tiddler , title )
2012-10-17 13:57:13 +00:00
exclude : An array of tiddler titles to exclude from the search
invert : If true returns tiddlers that do not contain the specified string
2012-10-17 19:38:36 +00:00
caseSensitive : If true forces a case sensitive search
2018-10-30 17:39:18 +00:00
field : If specified , restricts the search to the specified field , or an array of field names
2019-07-31 20:36:12 +00:00
anchored : If true , forces all but regexp searches to be anchored to the start of text
2018-10-30 17:39:18 +00:00
excludeField : If true , the field options are inverted to specify the fields that are not to be searched
2022-02-21 15:05:34 +00:00
2018-10-30 17:39:18 +00:00
The search mode is determined by the first of these boolean flags to be true
literal : searches for literal string
whitespace : same as literal except runs of whitespace are treated as a single space
regexp : treats the search term as a regular expression
2022-02-21 15:05:34 +00:00
words : ( default ) treats search string as a list of tokens , and matches if all tokens are found ,
regardless of adjacency or ordering
some : treats search string as a list of tokens , and matches if at least ONE token is found
2012-10-17 13:34:59 +00:00
* /
2012-10-17 13:57:13 +00:00
exports . search = function ( text , options ) {
options = options || { } ;
2014-08-30 19:44:26 +00:00
var self = this ,
t ,
2022-02-21 15:05:34 +00:00
regExpStr = "" ,
2014-08-30 19:44:26 +00:00
invert = ! ! options . invert ;
2012-10-17 19:38:36 +00:00
// Convert the search string into a regexp for each term
var terms , searchTermsRegExps ,
2019-07-31 20:36:12 +00:00
flags = options . caseSensitive ? "" : "i" ,
anchor = options . anchored ? "^" : "" ;
2012-10-17 19:38:36 +00:00
if ( options . literal ) {
if ( text . length === 0 ) {
2013-08-24 15:51:54 +00:00
searchTermsRegExps = null ;
} else {
2019-07-31 20:36:12 +00:00
searchTermsRegExps = [ new RegExp ( "(" + anchor + $tw . utils . escapeRegExp ( text ) + ")" , flags ) ] ;
2012-10-17 19:38:36 +00:00
}
2018-10-30 17:39:18 +00:00
} else if ( options . whitespace ) {
terms = [ ] ;
$tw . utils . each ( text . split ( /\s+/g ) , function ( term ) {
if ( term ) {
terms . push ( $tw . utils . escapeRegExp ( term ) ) ;
}
} ) ;
2019-07-31 20:36:12 +00:00
searchTermsRegExps = [ new RegExp ( "(" + anchor + terms . join ( "\\s+" ) + ")" , flags ) ] ;
2018-10-30 17:39:18 +00:00
} else if ( options . regexp ) {
try {
2021-05-30 18:20:17 +00:00
searchTermsRegExps = [ new RegExp ( "(" + text + ")" , flags ) ] ;
2018-10-30 17:39:18 +00:00
} catch ( e ) {
searchTermsRegExps = null ;
console . log ( "Regexp error parsing /(" + text + ")/" + flags + ": " , e ) ;
}
2022-02-21 15:05:34 +00:00
} else if ( options . some ) {
terms = text . trim ( ) . split ( / +/ ) ;
if ( terms . length === 1 && terms [ 0 ] === "" ) {
searchTermsRegExps = null ;
} else {
searchTermsRegExps = [ ] ;
for ( t = 0 ; t < terms . length ; t ++ ) {
regExpStr += ( t === 0 ) ? anchor + $tw . utils . escapeRegExp ( terms [ t ] ) : "|" + anchor + $tw . utils . escapeRegExp ( terms [ t ] ) ;
}
searchTermsRegExps . push ( new RegExp ( "(" + regExpStr + ")" , flags ) ) ;
}
} else { // default: words
2013-12-20 18:14:11 +00:00
terms = text . split ( / +/ ) ;
2013-08-24 15:51:54 +00:00
if ( terms . length === 1 && terms [ 0 ] === "" ) {
searchTermsRegExps = null ;
} else {
searchTermsRegExps = [ ] ;
for ( t = 0 ; t < terms . length ; t ++ ) {
2019-07-31 20:36:12 +00:00
searchTermsRegExps . push ( new RegExp ( "(" + anchor + $tw . utils . escapeRegExp ( terms [ t ] ) + ")" , flags ) ) ;
2013-08-24 15:51:54 +00:00
}
2012-10-17 19:38:36 +00:00
}
}
2022-02-21 15:05:34 +00:00
// Accumulate the array of fields to be searched or excluded from the search
2018-10-30 17:39:18 +00:00
var fields = [ ] ;
if ( options . field ) {
if ( $tw . utils . isArray ( options . field ) ) {
$tw . utils . each ( options . field , function ( fieldName ) {
2018-11-01 12:56:56 +00:00
if ( fieldName ) {
2021-05-30 18:20:17 +00:00
fields . push ( fieldName ) ;
2018-11-01 12:56:56 +00:00
}
2018-10-30 17:39:18 +00:00
} ) ;
} else {
fields . push ( options . field ) ;
}
}
// Use default fields if none specified and we're not excluding fields (excluding fields with an empty field array is the same as searching all fields)
if ( fields . length === 0 && ! options . excludeField ) {
fields . push ( "title" ) ;
fields . push ( "tags" ) ;
fields . push ( "text" ) ;
}
2012-10-17 13:34:59 +00:00
// Function to check a given tiddler for the search term
var searchTiddler = function ( title ) {
2013-08-24 15:51:54 +00:00
if ( ! searchTermsRegExps ) {
2013-11-11 15:26:41 +00:00
return true ;
2013-08-24 15:51:54 +00:00
}
2018-12-17 11:19:48 +00:00
var notYetFound = searchTermsRegExps . slice ( ) ;
2013-03-17 15:06:09 +00:00
var tiddler = self . getTiddler ( title ) ;
2012-10-17 19:38:36 +00:00
if ( ! tiddler ) {
2013-08-24 15:51:54 +00:00
tiddler = new $tw . Tiddler ( { title : title , text : "" , type : "text/vnd.tiddlywiki" } ) ;
2012-10-17 19:38:36 +00:00
}
2013-10-18 15:09:10 +00:00
var contentTypeInfo = $tw . config . contentTypeInfo [ tiddler . fields . type ] || $tw . config . contentTypeInfo [ "text/vnd.tiddlywiki" ] ,
2018-10-30 17:39:18 +00:00
searchFields ;
// Get the list of fields we're searching
if ( options . excludeField ) {
searchFields = Object . keys ( tiddler . fields ) ;
$tw . utils . each ( fields , function ( fieldName ) {
var p = searchFields . indexOf ( fieldName ) ;
if ( p !== - 1 ) {
searchFields . splice ( p , 1 ) ;
}
} ) ;
} else {
searchFields = fields ;
}
2018-12-17 11:19:48 +00:00
for ( var fieldIndex = 0 ; notYetFound . length > 0 && fieldIndex < searchFields . length ; fieldIndex ++ ) {
2018-10-30 17:39:18 +00:00
// Don't search the text field if the content type is binary
var fieldName = searchFields [ fieldIndex ] ;
if ( fieldName === "text" && contentTypeInfo . encoding !== "utf8" ) {
break ;
}
2018-12-17 11:19:48 +00:00
var str = tiddler . fields [ fieldName ] ,
2018-10-30 17:39:18 +00:00
t ;
if ( str ) {
if ( $tw . utils . isArray ( str ) ) {
// If the field value is an array, test each regexp against each field array entry and fail if each regexp doesn't match at least one field array entry
2018-12-17 11:19:48 +00:00
for ( var s = 0 ; s < str . length ; s ++ ) {
for ( t = 0 ; t < notYetFound . length ; ) {
if ( notYetFound [ t ] . test ( str [ s ] ) ) {
notYetFound . splice ( t , 1 ) ;
} else {
t ++ ;
2018-10-30 17:39:18 +00:00
}
}
}
} else {
// If the field isn't an array, force it to a string and test each regexp against it and fail if any do not match
str = tiddler . getFieldString ( fieldName ) ;
2018-12-17 11:19:48 +00:00
for ( t = 0 ; t < notYetFound . length ; ) {
if ( notYetFound [ t ] . test ( str ) ) {
notYetFound . splice ( t , 1 ) ;
} else {
t ++ ;
2018-10-30 17:39:18 +00:00
}
}
2014-11-06 20:56:32 +00:00
}
2012-10-17 19:38:36 +00:00
}
2018-10-30 17:39:18 +00:00
} ;
2018-12-17 11:19:48 +00:00
return notYetFound . length == 0 ;
2012-11-06 17:21:56 +00:00
} ;
2012-10-17 13:34:59 +00:00
// Loop through all the tiddlers doing the search
2014-04-03 19:49:16 +00:00
var results = [ ] ,
source = options . source || this . each ;
source ( function ( tiddler , title ) {
2021-03-12 21:12:06 +00:00
if ( searchTiddler ( title ) !== invert ) {
2014-04-03 19:49:16 +00:00
results . push ( title ) ;
2012-10-17 13:34:59 +00:00
}
2014-04-03 19:49:16 +00:00
} ) ;
2012-10-17 13:57:13 +00:00
// Remove any of the results we have to exclude
if ( options . exclude ) {
for ( t = 0 ; t < options . exclude . length ; t ++ ) {
var p = results . indexOf ( options . exclude [ t ] ) ;
if ( p !== - 1 ) {
results . splice ( p , 1 ) ;
}
}
}
2012-10-17 13:34:59 +00:00
return results ;
} ;
2012-11-18 13:14:28 +00:00
/ *
Trigger a load for a tiddler if it is skinny . Returns the text , or undefined if the tiddler is missing , null if the tiddler is being lazily loaded .
* /
2013-01-15 17:50:47 +00:00
exports . getTiddlerText = function ( title , defaultText ) {
2012-11-18 13:14:28 +00:00
var tiddler = this . getTiddler ( title ) ;
// Return undefined if the tiddler isn't found
if ( ! tiddler ) {
2013-01-15 17:50:47 +00:00
return defaultText ;
2012-11-18 13:14:28 +00:00
}
2020-03-30 14:24:05 +00:00
if ( ! tiddler . hasField ( "_is_skinny" ) ) {
2012-11-18 13:14:28 +00:00
// Just return the text if we've got it
2020-03-30 14:24:05 +00:00
return tiddler . fields . text || "" ;
2012-11-18 13:14:28 +00:00
} else {
2013-03-16 10:50:36 +00:00
// Tell any listeners about the need to lazily load this tiddler
this . dispatchEvent ( "lazyLoad" , title ) ;
2012-11-18 13:14:28 +00:00
// Indicate that the text is being loaded
return null ;
}
} ;
2017-02-09 15:43:28 +00:00
/ *
Check whether the text of a tiddler matches a given value . By default , the comparison is case insensitive , and any spaces at either end of the tiddler text is trimmed
* /
exports . checkTiddlerText = function ( title , targetText , options ) {
options = options || { } ;
var text = this . getTiddlerText ( title , "" ) ;
if ( ! options . noTrim ) {
text = text . trim ( ) ;
}
if ( ! options . caseSensitive ) {
text = text . toLowerCase ( ) ;
targetText = targetText . toLowerCase ( ) ;
}
return text === targetText ;
}
2013-10-25 20:15:20 +00:00
/ *
2013-12-23 08:55:11 +00:00
Read an array of browser File objects , invoking callback ( tiddlerFieldsArray ) once they ' re all read
2013-10-25 20:15:20 +00:00
* /
2017-07-12 15:42:16 +00:00
exports . readFiles = function ( files , options ) {
var callback ;
if ( typeof options === "function" ) {
callback = options ;
options = { } ;
} else {
callback = options . callback ;
}
2013-12-23 08:55:11 +00:00
var result = [ ] ,
2017-07-12 15:42:16 +00:00
outstanding = files . length ,
readFileCallback = function ( tiddlerFieldsArray ) {
2013-12-23 08:55:11 +00:00
result . push . apply ( result , tiddlerFieldsArray ) ;
if ( -- outstanding === 0 ) {
callback ( result ) ;
}
2017-07-12 15:42:16 +00:00
} ;
for ( var f = 0 ; f < files . length ; f ++ ) {
2018-09-21 09:56:01 +00:00
this . readFile ( files [ f ] , $tw . utils . extend ( { } , options , { callback : readFileCallback } ) ) ;
2014-08-30 19:44:26 +00:00
}
2014-01-24 14:09:06 +00:00
return files . length ;
2013-10-25 20:15:20 +00:00
} ;
/ *
2013-12-23 08:55:11 +00:00
Read a browser File object , invoking callback ( tiddlerFieldsArray ) with an array of tiddler fields objects
2013-10-25 20:15:20 +00:00
* /
2017-07-12 15:42:16 +00:00
exports . readFile = function ( file , options ) {
var callback ;
if ( typeof options === "function" ) {
callback = options ;
options = { } ;
} else {
callback = options . callback ;
}
2013-10-25 20:15:20 +00:00
// Get the type, falling back to the filename extension
var self = this ,
type = file . type ;
if ( type === "" || ! type ) {
var dotPos = file . name . lastIndexOf ( "." ) ;
if ( dotPos !== - 1 ) {
2015-02-01 18:33:40 +00:00
var fileExtensionInfo = $tw . utils . getFileExtensionInfo ( file . name . substr ( dotPos ) ) ;
2013-10-25 20:15:20 +00:00
if ( fileExtensionInfo ) {
type = fileExtensionInfo . type ;
}
}
}
// Figure out if we're reading a binary file
var contentTypeInfo = $tw . config . contentTypeInfo [ type ] ,
isBinary = contentTypeInfo ? contentTypeInfo . encoding === "base64" : false ;
2014-11-08 08:37:08 +00:00
// Log some debugging information
2014-11-14 10:33:41 +00:00
if ( $tw . log . IMPORT ) {
2014-11-08 08:37:08 +00:00
console . log ( "Importing file '" + file . name + "', type: '" + type + "', isBinary: " + isBinary ) ;
}
2017-10-11 16:52:37 +00:00
// Give the hook a chance to process the drag
if ( $tw . hooks . invokeHook ( "th-importing-file" , {
file : file ,
type : type ,
isBinary : isBinary ,
callback : callback
} ) !== true ) {
this . readFileContent ( file , type , isBinary , options . deserializer , callback ) ;
}
} ;
/ *
Lower level utility to read the content of a browser File object , invoking callback ( tiddlerFieldsArray ) with an array of tiddler fields objects
* /
exports . readFileContent = function ( file , type , isBinary , deserializer , callback ) {
var self = this ;
2013-10-25 20:15:20 +00:00
// Create the FileReader
var reader = new FileReader ( ) ;
// Onload
reader . onload = function ( event ) {
2014-01-19 18:43:02 +00:00
var text = event . target . result ,
2020-11-14 12:05:35 +00:00
tiddlerFields = { title : file . name || "Untitled" } ;
2013-10-25 20:15:20 +00:00
if ( isBinary ) {
2014-01-19 18:43:02 +00:00
var commaPos = text . indexOf ( "," ) ;
2013-10-25 20:15:20 +00:00
if ( commaPos !== - 1 ) {
2016-08-20 16:09:22 +00:00
text = text . substr ( commaPos + 1 ) ;
2013-10-25 20:15:20 +00:00
}
2016-08-20 16:09:22 +00:00
}
// Check whether this is an encrypted TiddlyWiki file
var encryptedJson = $tw . utils . extractEncryptedStoreArea ( text ) ;
if ( encryptedJson ) {
// If so, attempt to decrypt it with the current password
$tw . utils . decryptStoreAreaInteractive ( encryptedJson , function ( tiddlers ) {
callback ( tiddlers ) ;
} ) ;
2013-10-25 20:15:20 +00:00
} else {
2016-08-20 16:09:22 +00:00
// Otherwise, just try to deserialise any tiddlers in the file
2017-10-11 16:52:37 +00:00
callback ( self . deserializeTiddlers ( type , text , tiddlerFields , { deserializer : deserializer } ) ) ;
2013-10-25 20:15:20 +00:00
}
} ;
// Kick off the read
if ( isBinary ) {
reader . readAsDataURL ( file ) ;
} else {
reader . readAsText ( file ) ;
}
} ;
2014-10-09 16:30:53 +00:00
/ *
Find any existing draft of a specified tiddler
* /
exports . findDraft = function ( targetTitle ) {
var draftTitle = undefined ;
this . forEachTiddler ( { includeSystem : true } , function ( title , tiddler ) {
if ( tiddler . fields [ "draft.title" ] && tiddler . fields [ "draft.of" ] === targetTitle ) {
draftTitle = title ;
}
} ) ;
return draftTitle ;
}
2014-04-28 14:16:31 +00:00
/ *
2015-12-28 10:29:43 +00:00
Check whether the specified draft tiddler has been modified .
If the original tiddler doesn ' t exist , create a vanilla tiddler variable ,
2015-12-28 10:31:36 +00:00
to check if additional fields have been added .
2014-04-28 14:16:31 +00:00
* /
2014-05-03 10:32:55 +00:00
exports . isDraftModified = function ( title ) {
2014-06-23 07:10:32 +00:00
var tiddler = this . getTiddler ( title ) ;
2014-04-28 14:16:31 +00:00
if ( ! tiddler . isDraft ( ) ) {
return false ;
}
2014-06-23 22:28:22 +00:00
var ignoredFields = [ "created" , "modified" , "title" , "draft.title" , "draft.of" ] ,
2015-12-28 10:29:43 +00:00
origTiddler = this . getTiddler ( tiddler . fields [ "draft.of" ] ) || new $tw . Tiddler ( { text : "" , tags : [ ] } ) ,
titleModified = tiddler . fields [ "draft.title" ] !== tiddler . fields [ "draft.of" ] ;
return titleModified || ! tiddler . isEqual ( origTiddler , ignoredFields ) ;
2014-04-28 14:16:31 +00:00
} ;
2014-05-03 10:32:55 +00:00
/ *
Add a new record to the top of the history stack
title : a title string or an array of title strings
fromPageRect : page coordinates of the origin of the navigation
historyTitle : title of history tiddler ( defaults to $ : / H i s t o r y L i s t )
* /
exports . addToHistory = function ( title , fromPageRect , historyTitle ) {
2020-01-27 17:59:13 +00:00
var story = new $tw . Story ( { wiki : this , historyTitle : historyTitle } ) ;
2021-05-30 18:20:17 +00:00
story . addToHistory ( title , fromPageRect ) ;
2020-11-02 22:52:02 +00:00
console . log ( "$tw.wiki.addToHistory() is deprecated since V5.1.23! Use the this.story.addToHistory() from the story-object!" )
2014-05-03 10:32:55 +00:00
} ;
2018-08-23 17:31:48 +00:00
/ *
Add a new tiddler to the story river
title : a title string or an array of title strings
fromTitle : the title of the tiddler from which the navigation originated
storyTitle : title of story tiddler ( defaults to $ : / S t o r y L i s t )
options : see story . js
* /
exports . addToStory = function ( title , fromTitle , storyTitle , options ) {
2020-01-27 17:59:13 +00:00
var story = new $tw . Story ( { wiki : this , storyTitle : storyTitle } ) ;
2020-11-02 22:52:02 +00:00
story . addToStory ( title , fromTitle , options ) ;
console . log ( "$tw.wiki.addToStory() is deprecated since V5.1.23! Use the this.story.addToStory() from the story-object!" )
2018-08-23 17:31:48 +00:00
} ;
2019-06-04 11:33:01 +00:00
/ *
Generate a title for the draft of a given tiddler
* /
exports . generateDraftTitle = function ( title ) {
var c = 0 ,
draftTitle ,
username = this . getTiddlerText ( "$:/status/UserName" ) ,
attribution = username ? " by " + username : "" ;
do {
draftTitle = "Draft " + ( c ? ( c + 1 ) + " " : "" ) + "of '" + title + "'" + attribution ;
c ++ ;
} while ( this . tiddlerExists ( draftTitle ) ) ;
return draftTitle ;
} ;
2014-07-12 08:09:36 +00:00
/ *
Invoke the available upgrader modules
titles : array of tiddler titles to be processed
tiddlers : hashmap by title of tiddler fields of pending import tiddlers . These can be modified by the upgraders . An entry with no fields indicates a tiddler that was pending import has been suppressed . When entries are added to the pending import the tiddlers hashmap may have entries that are not present in the titles array
Returns a hashmap of messages keyed by tiddler title .
* /
exports . invokeUpgraders = function ( titles , tiddlers ) {
// Collect up the available upgrader modules
var self = this ;
if ( ! this . upgraderModules ) {
this . upgraderModules = [ ] ;
$tw . modules . forEachModuleOfType ( "upgrader" , function ( title , module ) {
if ( module . upgrade ) {
self . upgraderModules . push ( module ) ;
}
} ) ;
}
// Invoke each upgrader in turn
var messages = { } ;
for ( var t = 0 ; t < this . upgraderModules . length ; t ++ ) {
var upgrader = this . upgraderModules [ t ] ,
upgraderMessages = upgrader . upgrade ( this , titles , tiddlers ) ;
$tw . utils . extend ( messages , upgraderMessages ) ;
}
return messages ;
} ;
2019-09-27 15:47:55 +00:00
// Determine whether a plugin by title is dynamically loadable
exports . doesPluginRequireReload = function ( title ) {
2021-05-23 10:19:46 +00:00
var tiddler = this . getTiddler ( title ) ;
if ( tiddler && tiddler . fields . type === "application/json" && tiddler . fields [ "plugin-type" ] ) {
if ( tiddler . fields [ "plugin-type" ] === "import" ) {
// The import plugin never requires reloading
return false ;
}
}
2019-09-27 15:47:55 +00:00
return this . doesPluginInfoRequireReload ( this . getPluginInfo ( title ) || this . getTiddlerDataCached ( title ) ) ;
2019-09-16 11:15:39 +00:00
} ;
2019-09-27 15:47:55 +00:00
// Determine whether a plugin info structure is dynamically loadable
exports . doesPluginInfoRequireReload = function ( pluginInfo ) {
2019-09-16 11:15:39 +00:00
if ( pluginInfo ) {
var foundModule = false ;
$tw . utils . each ( pluginInfo . tiddlers , function ( tiddler ) {
if ( tiddler . type === "application/javascript" && $tw . utils . hop ( tiddler , "module-type" ) ) {
foundModule = true ;
}
} ) ;
return foundModule ;
} else {
return null ;
}
} ;
2020-05-09 14:54:44 +00:00
exports . slugify = function ( title , options ) {
var tiddler = this . getTiddler ( title ) ,
slug ;
if ( tiddler && tiddler . fields . slug ) {
slug = tiddler . fields . slug ;
} else {
slug = $tw . utils . transliterate ( title . toString ( ) . toLowerCase ( ) ) // Replace diacritics with basic lowercase ASCII
. replace ( /\s+/g , "-" ) // Replace spaces with -
. replace ( /[^\w\-\.]+/g , "" ) // Remove all non-word chars except dash and dot
. replace ( /\-\-+/g , "-" ) // Replace multiple - with single -
. replace ( /^-+/ , "" ) // Trim - from start of text
. replace ( /-+$/ , "" ) ; // Trim - from end of text
}
// If the resulting slug is blank (eg because the title is just punctuation characters)
if ( ! slug ) {
// ...then just use the character codes of the title
var result = [ ] ;
$tw . utils . each ( title . split ( "" ) , function ( char ) {
result . push ( char . charCodeAt ( 0 ) . toString ( ) ) ;
} ) ;
slug = result . join ( "-" ) ;
}
return slug ;
} ;
2016-08-20 16:09:22 +00:00
} ) ( ) ;