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
2013-08-04 13:02:07 +00:00
var USER _NAME _TITLE = "$:/status/UserName" ;
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 , tiddler , fields ;
// Check if it is a reference to a tiddler
if ( tr . title && ! tr . field ) {
tiddler = this . getTiddler ( tr . title ) ;
2013-08-04 13:02:07 +00:00
this . addTiddler ( new $tw . Tiddler ( tiddler , { title : tr . title , text : value } , this . getModificationFields ( ) ) ) ;
2012-06-19 15:47:25 +00:00
// Else check for a field reference
} else if ( tr . field ) {
title = tr . title || currTiddlerTitle ;
tiddler = this . getTiddler ( title ) ;
if ( tiddler ) {
2012-06-19 15:56:40 +00:00
fields = { } ;
2012-06-19 15:47:25 +00:00
fields [ tr . field ] = value ;
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
} ;
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 ) ) {
2012-06-19 15:56:40 +00:00
fields = { } ;
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 ] || [ ] ;
this . eventListeners [ type ] . push ( listener ) ;
} ;
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 ) ,
true if the tiddler has been created
* /
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
this . changedTiddlers = this . changedTiddlers || { } ;
2013-07-03 15:00:48 +00:00
this . changedTiddlers [ title ] = this . changedTiddlers [ title ] || { } ;
2012-04-30 11:23:03 +00:00
this . changedTiddlers [ title ] [ isDeleted ? "deleted" : "modified" ] = true ;
// Increment the change count
this . changeCount = this . changeCount || { } ;
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
this . eventListeners = this . eventListeners || [ ] ;
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 ;
self . changedTiddlers = { } ;
self . eventsTriggered = false ;
self . dispatchEvent ( "change" , changes ) ;
2012-04-30 11:23:03 +00:00
} ) ;
this . eventsTriggered = true ;
}
} ;
exports . getChangeCount = function ( title ) {
this . changeCount = this . changeCount || { } ;
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 ;
}
} ;
exports . deleteTiddler = function ( title ) {
delete this . tiddlers [ title ] ;
this . clearCache ( title ) ;
2013-11-13 23:33:32 +00:00
this . clearGlobalCache ( ) ;
2013-03-05 19:59:55 +00:00
this . enqueueTiddlerEvent ( title , true ) ;
2012-04-30 11:23:03 +00:00
} ;
exports . tiddlerExists = function ( title ) {
2012-06-06 11:07:33 +00:00
return ! ! this . tiddlers [ title ] ;
2012-04-30 11:23:03 +00:00
} ;
2013-10-25 20:15:20 +00:00
/ *
Generate an unused title from the specified base
* /
exports . generateNewTitle = function ( baseTitle ) {
var c = 0 ;
do {
var title = baseTitle + ( c ? " " + ( c + 1 ) : "" ) ;
c ++ ;
} while ( this . tiddlerExists ( title ) ) ;
return title ;
} ;
2013-04-03 13:29:12 +00:00
exports . isSystemTiddler = function ( title ) {
return title . indexOf ( "$:/" ) === 0 ;
} ;
exports . isTemporaryTiddler = function ( title ) {
return title . indexOf ( "$:/temp/" ) === 0 ;
} ;
2013-04-30 21:35:52 +00:00
/ *
Determines if a tiddler is a shadow tiddler , regardless of whether it has been overridden by a real tiddler
* /
2013-04-03 13:29:12 +00:00
exports . isShadowTiddler = function ( title ) {
return $tw . utils . hop ( this . shadowTiddlers , title ) ;
} ;
2012-11-11 14:31:45 +00:00
exports . addTiddler = function ( tiddler ) {
2012-04-30 11:23:03 +00:00
// Check if we're passed a fields hashmap instead of a tiddler
if ( ! ( tiddler instanceof $tw . Tiddler ) ) {
tiddler = new $tw . Tiddler ( tiddler ) ;
}
2012-12-13 21:34:31 +00:00
// Get the title
var title = tiddler . fields . title ;
2012-06-07 10:31:44 +00:00
// Save the tiddler
2012-04-30 11:23:03 +00:00
this . tiddlers [ title ] = tiddler ;
this . clearCache ( title ) ;
2013-11-13 23:33:32 +00:00
this . clearGlobalCache ( ) ;
2013-03-05 19:59:55 +00:00
this . enqueueTiddlerEvent ( title ) ;
2012-04-30 11:23:03 +00:00
} ;
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 ( ) {
var fields = {
created : new Date ( )
} ,
creator = this . getTiddlerText ( USER _NAME _TITLE ) ;
if ( creator ) {
fields . creator = creator ;
}
return fields ;
} ;
/ *
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 ( ) {
2013-08-06 14:27:02 +00:00
var fields = { } ,
2013-08-04 13:02:07 +00:00
modifier = this . getTiddlerText ( USER _NAME _TITLE ) ;
2013-08-06 14:27:02 +00:00
fields . modified = new Date ( ) ;
2013-08-04 13:02:07 +00:00
if ( modifier ) {
fields . modifier = modifier ;
}
return fields ;
} ;
2012-04-30 11:23:03 +00:00
/ *
2013-03-15 22:00:19 +00:00
Return a sorted array of non - system tiddler titles , optionally filtered by a tag
2012-04-30 11:23:03 +00:00
* /
2012-05-08 15:02:24 +00:00
exports . getTiddlers = function ( sortField , excludeTag ) {
2012-04-30 11:23:03 +00:00
sortField = sortField || "title" ;
var tiddlers = [ ] , t , titles = [ ] ;
for ( t in this . tiddlers ) {
2013-04-03 13:29:12 +00:00
if ( $tw . utils . hop ( this . tiddlers , t ) && ! this . isSystemTiddler ( t ) && ( ! excludeTag || ! this . tiddlers [ t ] . hasTag ( excludeTag ) ) ) {
2012-06-06 12:21:20 +00:00
tiddlers . push ( this . tiddlers [ t ] ) ;
}
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 ) {
var tiddlers = this . getTiddlers ( null , excludeTag ) ;
return $tw . utils . count ( tiddlers ) ;
} ;
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
* /
2012-06-14 10:36:26 +00:00
exports . sortTiddlers = function ( titles , sortField , isDescending , isCaseSensitive ) {
2012-05-08 15:02:24 +00:00
var self = this ;
titles . sort ( function ( a , b ) {
2012-06-19 09:40:05 +00:00
if ( sortField !== "title" ) {
2012-10-16 20:41:12 +00:00
a = self . getTiddler ( a ) . fields [ sortField ] || "" ;
b = self . getTiddler ( b ) . fields [ sortField ] || "" ;
2012-06-19 09:40:05 +00:00
}
2012-06-14 10:36:26 +00:00
if ( ! isCaseSensitive ) {
2012-10-23 15:28:38 +00:00
if ( typeof a === "string" ) {
a = a . toLowerCase ( ) ;
}
if ( typeof b === "string" ) {
b = b . toLowerCase ( ) ;
}
2012-06-14 10:36:26 +00:00
}
2012-06-19 09:40:05 +00:00
if ( a < b ) {
2012-05-08 15:02:24 +00:00
return isDescending ? + 1 : - 1 ;
} else {
2012-06-19 09:40:05 +00:00
if ( a > b ) {
2012-05-08 15:02:24 +00:00
return isDescending ? - 1 : + 1 ;
} else {
return 0 ;
}
}
} ) ;
} ;
2012-04-30 11:23:03 +00:00
exports . forEachTiddler = function ( /* [sortField,[excludeTag,]]callback */ ) {
var arg = 0 ,
sortField = arguments . length > 1 ? arguments [ arg ++ ] : null ,
excludeTag = arguments . length > 2 ? arguments [ arg ++ ] : null ,
callback = arguments [ arg ++ ] ,
2012-05-08 15:02:24 +00:00
titles = this . getTiddlers ( sortField , excludeTag ) ,
2012-04-30 11:23:03 +00:00
t , tiddler ;
for ( t = 0 ; t < titles . length ; t ++ ) {
tiddler = this . tiddlers [ titles [ t ] ] ;
if ( tiddler ) {
callback . call ( this , tiddler . fields . title , tiddler ) ;
}
}
} ;
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
// Count up the links
var links = [ ] ,
checkParseTree = function ( parseTree ) {
for ( var t = 0 ; t < parseTree . length ; t ++ ) {
var parseTreeNode = parseTree [ t ] ;
2013-10-27 22:55:36 +00:00
if ( parseTreeNode . type === "link" && parseTreeNode . attributes . to && parseTreeNode . attributes . to . type === "string" ) {
2013-03-19 10:14:44 +00:00
var value = parseTreeNode . attributes . to . value ;
if ( links . indexOf ( value ) === - 1 ) {
links . push ( value ) ;
}
}
if ( parseTreeNode . children ) {
checkParseTree ( parseTreeNode . children ) ;
}
}
} ;
if ( parser ) {
2013-03-21 22:21:00 +00:00
checkParseTree ( parser . tree ) ;
2013-03-19 10:14:44 +00:00
}
return links ;
} ) ;
} ;
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 ,
backlinks = [ ] ;
this . forEachTiddler ( function ( title , tiddler ) {
var links = self . getTiddlerLinks ( title ) ;
if ( links . indexOf ( targetTitle ) !== - 1 ) {
backlinks . push ( title ) ;
}
} ) ;
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
} ;
2013-03-15 22:00:19 +00:00
exports . getSystemTitles = function ( ) {
2012-06-06 12:21:20 +00:00
var titles = [ ] ;
for ( var title in this . tiddlers ) {
2013-04-03 13:29:12 +00:00
if ( this . isSystemTiddler ( title ) ) {
2012-06-06 12:21:20 +00:00
titles . push ( title ) ;
}
}
titles . sort ( ) ;
return titles ;
2012-04-30 11:23:03 +00:00
} ;
2013-04-03 13:29:12 +00:00
exports . getShadowTitles = function ( ) {
var titles = [ ] ;
for ( var title in this . shadowTiddlers ) {
titles . push ( title ) ;
}
titles . sort ( ) ;
return titles ;
} ;
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 ) {
2013-11-13 23:33:32 +00:00
var self = this ;
return this . getGlobalCache ( "taglist-" + tag , function ( ) {
var tagmap = self . getTagMap ( ) ;
return self . sortByList ( tagmap [ tag ] , tag ) ;
} ) ;
} ;
/ *
Get a hashmap by tag of arrays of tiddler titles
* /
exports . getTagMap = function ( ) {
var self = this ;
return this . getGlobalCache ( "tagmap" , function ( ) {
var tags = { } ;
// Collect up all the tags
for ( var title in self . tiddlers ) {
var tiddler = self . tiddlers [ title ] ;
if ( tiddler . fields . tags ) {
for ( var index = 0 ; index < tiddler . fields . tags . length ; index ++ ) {
var tag = tiddler . fields . tags [ index ] ;
if ( tags [ tag ] ) {
tags [ tag ] . push ( title )
} else {
tags [ tag ] = [ title ] ;
}
}
}
2012-06-13 08:10:03 +00:00
}
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
/ *
Lookup a given tiddler and return a list of all the tiddlers that include it in their list
* /
exports . findListingsOfTiddler = function ( targetTitle ) {
// Get the list associated with the tag
var titles = [ ] ;
for ( var title in this . tiddlers ) {
var tiddler = this . tiddlers [ title ] ;
if ( $tw . utils . isArray ( tiddler . fields . list ) && tiddler . fields . list . indexOf ( targetTitle ) !== - 1 ) {
titles . push ( title ) ;
}
}
return titles ;
} ;
2013-08-08 16:39:34 +00:00
/ *
Sorts an array of tiddler titles according to an ordered list
* /
exports . sortByList = function ( array , listTitle ) {
var list = this . getTiddlerList ( listTitle ) ;
2013-11-13 23:33:32 +00:00
if ( ! array || array . length === 0 ) {
return [ ] ;
} else if ( list ) {
2013-08-08 16:39:34 +00:00
var titles = [ ] , t , title ;
// 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 ) ;
}
}
// Then place any remaining entries
for ( t = 0 ; t < array . length ; t ++ ) {
title = array [ t ] ;
if ( list . indexOf ( title ) === - 1 ) {
titles . push ( title ) ;
}
}
return titles ;
} else {
return array ;
}
2012-06-13 08:10:03 +00:00
} ;
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 ) {
var fields = { } ;
$tw . utils . each ( tiddler . fields , function ( value , name ) {
fields [ name ] = tiddler . getFieldString ( name ) ;
} ) ;
return JSON . stringify ( fields ) ;
} else {
return JSON . stringify ( { title : title } ) ;
}
} ;
2012-07-22 21:03:06 +00:00
/ *
Get a tiddlers content as a JavaScript object . How this is done depends on the type of the tiddler :
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 .
* /
exports . getTiddlerData = function ( title , defaultData ) {
2013-03-28 17:07:30 +00:00
var tiddler = this . getTiddler ( title ) ,
2012-07-22 21:03:06 +00:00
data ;
2013-01-15 17:50:47 +00:00
if ( tiddler && tiddler . fields . text ) {
switch ( tiddler . fields . type ) {
case "application/json" :
// JSON tiddler
try {
data = JSON . parse ( tiddler . fields . text ) ;
} catch ( ex ) {
return defaultData ;
}
return data ;
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
* /
exports . extractTiddlerDataItem = function ( title , index , defaultText ) {
var data = this . getTiddlerData ( title , { } ) ,
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 .
* /
exports . setTiddlerData = function ( title , data ) {
var tiddler = this . getTiddler ( title ) ;
2013-08-04 13:02:07 +00:00
this . addTiddler ( new $tw . Tiddler ( tiddler , { title : title , type : "application/json" , text : JSON . stringify ( data , null , $tw . config . preferences . jsonSpaces ) } , this . getModificationFields ( ) ) ) ;
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
* /
exports . getTiddlerList = function ( title ) {
var tiddler = this . getTiddler ( title ) ;
2013-08-07 16:54:27 +00:00
if ( tiddler && $tw . utils . isArray ( tiddler . fields . list ) ) {
2013-09-18 11:12:29 +00:00
return tiddler . fields . list . 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 ) {
this . globalCache = this . globalCache || { } ;
if ( $tw . utils . hop ( this . globalCache , cacheName ) ) {
return this . globalCache [ cacheName ] ;
} else {
this . globalCache [ cacheName ] = initializer ( ) ;
return this . globalCache [ cacheName ] ;
}
} ;
exports . clearGlobalCache = function ( ) {
this . globalCache = { } ;
}
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 ) {
2013-10-12 16:05:13 +00:00
// Temporarily disable caching so that tweakParseTreeNode() works
return initializer ( ) ;
2012-04-30 11:23:03 +00:00
this . caches = this . caches || { } ;
var caches = this . caches [ title ] ;
if ( caches && caches [ cacheName ] ) {
return caches [ cacheName ] ;
} else {
if ( ! caches ) {
caches = { } ;
this . caches [ title ] = caches ;
}
caches [ cacheName ] = initializer ( ) ;
return caches [ cacheName ] ;
}
} ;
// Clear all caches associated with a particular tiddler
exports . clearCache = function ( title ) {
this . caches = this . caches || { } ;
2012-05-09 07:51:16 +00:00
if ( $tw . utils . hop ( this . caches , title ) ) {
2012-04-30 11:23:03 +00:00
delete this . caches [ title ] ;
}
} ;
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
}
}
} ) ;
} ;
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
2012-12-13 21:34:31 +00:00
* /
2013-10-27 22:55:36 +00:00
exports . old _parseText = function ( type , text , options ) {
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 ] ;
2012-12-27 17:08:29 +00:00
if ( ! Parser && $tw . config . fileExtensionInfo [ type ] ) {
2013-04-25 16:40:12 +00:00
Parser = $tw . Wiki . parsers [ $tw . config . fileExtensionInfo [ 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 ,
2012-12-27 17:08:29 +00:00
wiki : this
2012-12-26 22:02:59 +00:00
} ) ;
2012-12-13 21:34:31 +00:00
} ;
/ *
Parse a tiddler according to its MIME type
* /
2013-10-27 22:55:36 +00:00
exports . old _parseTiddler = function ( title , options ) {
2012-12-20 15:07:38 +00:00
options = options || { } ;
var cacheType = options . parseAsInline ? "newInlineParseTree" : "newBlockParseTree" ,
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 ( ) {
2013-10-27 22:55:36 +00:00
return self . old _parseText ( tiddler . fields . type , tiddler . fields . text , options ) ;
2012-12-13 21:34:31 +00:00
} ) : null ;
} ;
2013-10-12 16:05:13 +00:00
// We need to tweak parse trees generated by the existing parser because of the change from {type:"element",tag:"$tiddler",...} to {type:"tiddler",...}
var tweakParseTreeNode = function ( node ) {
2013-10-13 21:39:05 +00:00
if ( node . type === "element" && node . tag . charAt ( 0 ) === "$" ) {
node . type = node . tag . substr ( 1 ) ;
2013-10-27 22:55:36 +00:00
delete node . tag ;
2013-10-13 21:39:05 +00:00
}
tweakParseTreeNodes ( node . children ) ;
} ;
var tweakParseTreeNodes = function ( nodeList ) {
$tw . utils . each ( nodeList , tweakParseTreeNode ) ;
} ;
var tweakMacroDefinition = function ( nodeList ) {
if ( nodeList && nodeList [ 0 ] && nodeList [ 0 ] . type === "macrodef" ) {
2013-11-15 18:31:39 +00:00
nodeList [ 0 ] . type = "set" ;
2013-10-13 22:40:11 +00:00
nodeList [ 0 ] . attributes = {
name : { type : "string" , value : nodeList [ 0 ] . name } ,
value : { type : "string" , value : nodeList [ 0 ] . text }
} ;
2013-10-13 21:39:05 +00:00
nodeList [ 0 ] . children = nodeList . slice ( 1 ) ;
nodeList . splice ( 1 , nodeList . length - 1 ) ;
2013-10-21 17:32:29 +00:00
tweakMacroDefinition ( nodeList [ 0 ] . children ) ;
2013-10-13 21:39:05 +00:00
}
} ;
var tweakParser = function ( parser ) {
// Move any macro definitions to contain the body tree
tweakMacroDefinition ( parser . tree ) ;
// Tweak widgets
tweakParseTreeNodes ( parser . tree ) ;
} ;
2013-10-12 16:05:13 +00:00
2013-11-08 08:51:14 +00:00
exports . parseText = function ( type , text , options ) {
2013-10-27 22:55:36 +00:00
var parser = this . old _parseText ( type , text , options ) ;
2013-10-12 16:05:13 +00:00
if ( parser ) {
2013-10-13 21:39:05 +00:00
tweakParser ( parser )
2013-10-12 16:05:13 +00:00
} ;
return parser ;
} ;
2013-11-08 08:51:14 +00:00
exports . parseTiddler = function ( title , options ) {
2013-10-27 22:55:36 +00:00
var parser = this . old _parseTiddler ( title , options ) ;
2013-10-12 16:05:13 +00:00
if ( parser ) {
2013-10-13 21:39:05 +00:00
tweakParser ( parser )
2013-10-12 16:05:13 +00:00
} ;
return parser ;
} ;
2013-11-08 08:51:14 +00:00
exports . parseTextReference = function ( title , field , index , options ) {
2013-10-12 16:05:13 +00:00
if ( field === "text" || ( ! field && ! index ) ) {
2013-11-08 20:18:26 +00:00
// Force the tiddler to be lazily loaded
this . getTiddlerText ( title ) ;
// Parse it
2013-11-08 08:51:14 +00:00
return this . parseTiddler ( title , options ) ;
2013-10-12 16:05:13 +00:00
} else {
var tiddler , text ;
if ( field ) {
tiddler = this . getTiddler ( title ) ;
text = tiddler ? tiddler . fields [ field ] : "" ;
if ( text === undefined ) {
text = "" ;
}
2013-11-08 08:51:14 +00:00
return this . parseText ( "text/vnd.tiddlywiki" , text , options ) ;
2013-10-12 16:05:13 +00:00
} else if ( index ) {
text = this . extractTiddlerDataItem ( title , index , "" ) ;
2013-11-08 08:51:14 +00:00
return this . parseText ( "text/vnd.tiddlywiki" , text , options ) ;
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" ,
children : [ ]
} ,
currWidgetNode = widgetNode ;
2013-11-15 18:31:39 +00:00
// Create set variable widgets for each variable
2013-10-29 14:48:24 +00:00
$tw . utils . each ( options . variables , function ( value , name ) {
var setVariableWidget = {
2013-11-15 18:31:39 +00:00
type : "set" ,
2013-10-29 14:48:24 +00:00
attributes : {
name : { type : "string" , value : name } ,
value : { type : "string" , value : value }
} ,
children : [ ]
} ;
currWidgetNode . children = [ setVariableWidget ] ;
currWidgetNode = setVariableWidget ;
} ) ;
// Add in the supplied parse tree nodes
currWidgetNode . children = parser ? parser . tree : [ ] ;
// Create the widget
return new widget . widget ( widgetNode , {
wiki : this ,
document : options . document || $tw . document ,
parentWidget : options . parentWidget
} ) ;
} ;
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 ) ;
2013-10-12 16:05:13 +00:00
var container = $tw . document . createElement ( "div" ) ;
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 || { } ;
2013-11-08 08:51:14 +00:00
var parser = this . parseTiddler ( title ) ,
2013-10-29 14:48:24 +00:00
widgetNode = this . makeWidget ( parser , options ) ;
2013-10-12 16:05:13 +00:00
var container = $tw . document . createElement ( "div" ) ;
widgetNode . render ( container , null ) ;
return outputType === "text/html" ? container . innerHTML : container . textContent ;
} ;
2012-07-10 22:18:44 +00:00
/ *
Select the appropriate saver modules and set them up
* /
exports . initSavers = function ( moduleType ) {
moduleType = moduleType || "saver" ;
// Instantiate the available savers
this . savers = [ ] ;
2012-11-14 11:23:43 +00:00
var self = this ;
$tw . modules . forEachModuleOfType ( moduleType , function ( title , module ) {
if ( module . canSave ( self ) ) {
self . savers . push ( module . create ( self ) ) ;
2012-07-10 22:18:44 +00:00
}
2012-11-14 11:23:43 +00:00
} ) ;
2012-07-10 22:18:44 +00:00
// Sort the savers into priority order
this . savers . sort ( function ( a , b ) {
if ( a . info . priority < b . info . priority ) {
return - 1 ;
} else {
if ( a . info . priority > b . info . priority ) {
return + 1 ;
} else {
return 0 ;
}
}
} ) ;
} ;
/ *
Invoke the highest priority saver that successfully handles a method
* /
exports . callSaver = function ( method /*, args */ ) {
for ( var t = this . savers . length - 1 ; t >= 0 ; t -- ) {
var saver = this . savers [ t ] ;
if ( saver [ method ] . apply ( saver , Array . prototype . slice . call ( arguments , 1 ) ) ) {
return true ;
}
}
return false ;
} ;
/ *
2012-08-25 16:32:43 +00:00
Save the wiki contents . Options are :
2013-11-27 20:51:08 +00:00
method : "save" or "download"
2012-07-10 22:18:44 +00:00
template : the tiddler containing the template to save
downloadType : the content type for the saved file
* /
exports . saveWiki = function ( options ) {
options = options || { } ;
2013-11-27 20:51:08 +00:00
var method = options . method || "save" ,
template = options . template || "$:/core/save/all" ,
2012-11-16 22:40:25 +00:00
downloadType = options . downloadType || "text/plain" ;
2013-11-08 08:51:14 +00:00
var text = this . renderTiddler ( downloadType , template ) ;
2013-11-27 20:51:08 +00:00
this . callSaver ( "save" , text , method , function ( err ) {
2013-11-26 12:50:40 +00:00
if ( err ) {
alert ( "Error while saving:\n\n" + err ) ;
} else {
$tw . notifier . display ( "$:/messages/Saved" ) ;
}
2013-05-07 17:09:15 +00:00
} ) ;
2012-07-10 22:18:44 +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 :
titles : Hashmap or array of tiddler titles to limit search
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
literal : If true , searches for literal string , rather than separate search terms
2012-10-17 13:34:59 +00:00
* /
2012-10-17 13:57:13 +00:00
exports . search = function ( text , options ) {
options = options || { } ;
2013-03-17 15:06:09 +00:00
var self = this , t ;
2012-10-17 19:38:36 +00:00
// Convert the search string into a regexp for each term
var terms , searchTermsRegExps ,
flags = options . caseSensitive ? "" : "i" ;
if ( options . literal ) {
if ( text . length === 0 ) {
2013-08-24 15:51:54 +00:00
searchTermsRegExps = null ;
} else {
searchTermsRegExps = [ new RegExp ( "(" + $tw . utils . escapeRegExp ( text ) + ")" , flags ) ] ;
2012-10-17 19:38:36 +00:00
}
} else {
terms = text . replace ( /( +)/g , " " ) . 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 ++ ) {
searchTermsRegExps . push ( new RegExp ( "(" + $tw . utils . escapeRegExp ( terms [ t ] ) + ")" , flags ) ) ;
}
2012-10-17 19:38:36 +00:00
}
}
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
}
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" ] ,
match ;
2013-10-17 15:34:50 +00:00
for ( var t = 0 ; t < searchTermsRegExps . length ; t ++ ) {
2013-10-18 15:09:10 +00:00
// Search title, tags and body
match = false ;
if ( contentTypeInfo . encoding === "utf8" ) {
match = match || searchTermsRegExps [ t ] . test ( tiddler . fields . text ) ;
}
var tags = tiddler . fields . tags ? tiddler . fields . tags . join ( "\0" ) : "" ;
match = match || searchTermsRegExps [ t ] . test ( tags ) || searchTermsRegExps [ t ] . test ( tiddler . fields . title ) ;
if ( ! match ) {
return false ;
2012-10-17 19:38:36 +00:00
}
}
2013-10-18 15:09:10 +00:00
return true ;
2012-11-06 17:21:56 +00:00
} ;
2012-10-17 13:34:59 +00:00
// Loop through all the tiddlers doing the search
2012-10-17 19:38:36 +00:00
var results = [ ] ;
2012-10-17 13:57:13 +00:00
if ( $tw . utils . isArray ( options . titles ) ) {
for ( t = 0 ; t < options . titles . length ; t ++ ) {
2013-10-18 15:09:10 +00:00
if ( ! ! searchTiddler ( options . titles [ t ] ) === ! options . invert ) {
2012-10-17 13:57:13 +00:00
results . push ( options . titles [ t ] ) ;
2012-10-17 13:34:59 +00:00
}
}
} else {
2012-10-17 13:57:13 +00:00
var source = options . titles || this . tiddlers ;
for ( t in source ) {
2013-10-18 15:09:10 +00:00
if ( ! ! searchTiddler ( t ) === ! options . invert ) {
2012-10-17 13:34:59 +00:00
results . push ( t ) ;
}
}
}
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
}
2012-11-23 13:08:10 +00:00
if ( tiddler . fields . text !== undefined ) {
2012-11-18 13:14:28 +00:00
// Just return the text if we've got it
return tiddler . fields . text ;
} 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 ;
}
} ;
2013-10-25 20:15:20 +00:00
/ *
Read an array of browser File objects , invoking callback ( tiddlerFields ) for each loaded file
* /
exports . readFiles = function ( files , callback ) {
for ( var f = 0 ; f < files . length ; f ++ ) {
this . readFile ( files [ f ] , callback ) ;
} ;
} ;
/ *
Read a browser File object , invoking callback ( tiddlerFields ) with the tiddler fields object
* /
exports . readFile = function ( file , callback ) {
// 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 ) {
var fileExtensionInfo = $tw . config . fileExtensionInfo [ file . name . substr ( dotPos ) ] ;
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 ;
// Create the FileReader
var reader = new FileReader ( ) ;
// Onload
reader . onload = function ( event ) {
// Deserialise the file contents
var tiddlerFields = { title : file . name || "Untitled" , type : type } ;
// Are we binary?
if ( isBinary ) {
// The base64 section starts after the first comma in the data URI
var commaPos = event . target . result . indexOf ( "," ) ;
if ( commaPos !== - 1 ) {
tiddlerFields . text = event . target . result . substr ( commaPos + 1 ) ;
callback ( tiddlerFields ) ;
}
} else {
var tiddlers = self . deserializeTiddlers ( type , event . target . result , tiddlerFields ) ;
if ( tiddlers ) {
$tw . utils . each ( tiddlers , function ( tiddlerFields ) {
callback ( tiddlerFields ) ;
} ) ;
}
}
} ;
// Kick off the read
if ( isBinary ) {
reader . readAsDataURL ( file ) ;
} else {
reader . readAsText ( file ) ;
}
} ;
2012-04-30 11:23:03 +00:00
} ) ( ) ;