2012-11-16 17:01:04 +00:00
/ * \
title : $ : / p l u g i n s / t i d d l y w i k i / t i d d l y w e b / t i d d l y w e b . j s
type : application / javascript
2012-11-17 20:18:36 +00:00
module - type : syncer
2012-11-16 17:01:04 +00:00
2013-03-10 19:27:37 +00:00
Syncer module for TiddlyWeb - compatible web servers . It is used for working with TiddlyWeb , TiddlySpace and with TiddlyWiki5 ' s built in web server .
The subset of TiddlyWeb features that are required are described below .
! TiddlyWeb format JSON tiddlers
TiddlyWeb uses JSON to represent tiddlers as a hashmap object with the wrinkle that fields other than the standard ones are stored in a special ` fields ` object . For example :
` ` `
{
creator : "jermolene" ,
fields : {
_hash : "da39a3ee5e6b4b0d3255bfef95601890afd80709" ,
customField : "Some custom value"
} ,
created : "20130309145404" ,
recipe : "spacename_private" ,
modified : "20130309145414" ,
text : "" ,
title : "Testing times" ,
modifier : "jermolene" ,
type : null ,
tags : [ ] ,
revision : 1139558
}
` ` `
The revision field is treated as an opaque string by TiddlyWiki5 , and only tested for equality . If it is passed as a number it is converted to a string before use .
! Get Status
` GET <protocol>//<host>/status ` returns a JSON object that has the following fields :
* ` username ` , a string containing the username of the currently logged - in user , or the special value ` GUEST ` for non - authenticated users
* optionally , ` space ` , an object , may be present containing a field ` recipe ` that contains the name o the recipe that generated this wiki
! Get Skinny Tiddlers
` GET <protocol>//<host>/tiddlers.json ` or , if a recipe was specified in the results of the status request , ` GET <protocol>//<host>/recipes/<recipe>/tiddlers.json ` , returns a JSON array of skinny tiddler objects in TiddlyWeb format . "Skinny" means that the tiddlers lack a ` text ` field .
! Get Tiddler
` GET <protocol>//<host>/tiddlers/<title> ` or , if a recipe was specified in the results of the status request , ` GET <protocol>//<host>/recipes/<recipe>/tiddlers/<title> ` , returns a tiddler in TiddlyWeb format .
! Put Tiddler
` PUT <protocol>//<host>/tiddlers/<title> ` or , if a recipe was specified in the results of the status request , ` PUT <protocol>//<host>/recipes/<recipe>/tiddlers/<title> ` , saves a tiddler in TiddlyWeb format .
2012-11-16 17:01:04 +00:00
\ * /
( function ( ) {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict" ;
/ *
2012-11-17 20:18:36 +00:00
Creates a TiddlyWebSyncer object
2012-11-16 17:01:04 +00:00
* /
2012-11-17 20:18:36 +00:00
var TiddlyWebSyncer = function ( options ) {
2012-11-18 10:24:20 +00:00
this . wiki = options . wiki ;
2013-03-11 10:05:01 +00:00
// Hashmap of {revision:,bag:,changeCount:}
2013-03-10 19:27:37 +00:00
this . tiddlerInfo = { } ;
var self = this ;
// Record information for known tiddlers
this . wiki . forEachTiddler ( function ( title , tiddler ) {
if ( tiddler . fields [ "revision" ] ) {
self . tiddlerInfo [ title ] = {
revision : tiddler . fields [ "revision" ] ,
2013-03-11 10:05:01 +00:00
bag : tiddler . fields [ "bag" ] ,
2013-03-10 19:27:37 +00:00
changeCount : self . wiki . getChangeCount ( title )
}
}
} ) ;
2012-11-19 09:04:35 +00:00
// Tasks are {type: "load"/"save", title:, queueTime:, lastModificationTime:}
this . taskQueue = { } ; // Hashmap of tasks to be performed
2012-11-19 12:56:54 +00:00
this . taskInProgress = { } ; // Hash of tasks in progress
this . taskTimerId = null ; // Sync timer
2013-03-09 09:54:01 +00:00
// Compute the host and recipe
this . host = document . location . protocol + "//" + document . location . host + "/" ;
2013-03-10 19:27:37 +00:00
this . recipe = undefined ; // Filled in by getStatus() to be either "" or "recipes/<recipename>/"
2013-03-09 09:54:01 +00:00
// Mark us as not logged in
this . wiki . addTiddler ( { title : TiddlyWebSyncer . titleIsLoggedIn , text : "no" } ) ;
// Listen out for changes to tiddlers
this . wiki . addEventListener ( "" , function ( changes ) {
self . syncToServer ( changes ) ;
} ) ;
this . log ( "Initialising with host:" , this . host ) ;
// Get the login status
this . getStatus ( function ( err , isLoggedIn , json ) {
if ( isLoggedIn ) {
// Do a sync
self . syncFromServer ( ) ;
}
} ) ;
2012-11-17 12:32:51 +00:00
} ;
2012-11-17 20:18:36 +00:00
TiddlyWebSyncer . titleIsLoggedIn = "$:/plugins/tiddlyweb/IsLoggedIn" ;
TiddlyWebSyncer . titleUserName = "$:/plugins/tiddlyweb/UserName" ;
2012-11-19 12:56:54 +00:00
TiddlyWebSyncer . taskTimerInterval = 1 * 1000 ; // Interval for sync timer
TiddlyWebSyncer . throttleInterval = 1 * 1000 ; // Defer saving tiddlers if they've changed in the last 1s...
TiddlyWebSyncer . fallbackInterval = 10 * 1000 ; // Unless the task is older than 10s
2012-11-19 13:54:25 +00:00
TiddlyWebSyncer . pollTimerInterval = 60 * 1000 ; // Interval for polling for changes on the server
2012-11-17 20:18:36 +00:00
2012-11-17 12:32:51 +00:00
/ *
Error handling
* /
2012-11-17 20:18:36 +00:00
TiddlyWebSyncer . prototype . showError = function ( error ) {
2012-11-17 12:32:51 +00:00
alert ( "TiddlyWeb error: " + error ) ;
console . log ( "TiddlyWeb error: " + error ) ;
} ;
2012-11-19 16:55:57 +00:00
/ *
Message logging
* /
TiddlyWebSyncer . prototype . log = function ( /* arguments */ ) {
var args = Array . prototype . slice . call ( arguments , 0 ) ;
args [ 0 ] = "TiddlyWeb: " + args [ 0 ] ;
$tw . utils . log . apply ( null , args ) ;
} ;
2012-11-17 12:32:51 +00:00
/ *
2012-11-19 14:18:21 +00:00
Lazily load a skinny tiddler if we can
2012-11-17 12:32:51 +00:00
* /
2013-03-08 20:29:13 +00:00
TiddlyWebSyncer . prototype . lazyLoad = function ( title , tiddler ) {
2012-11-19 14:18:21 +00:00
// Queue up a sync task to load this tiddler
this . enqueueSyncTask ( {
type : "load" ,
title : title
2012-11-16 17:01:04 +00:00
} ) ;
2012-11-17 12:32:51 +00:00
} ;
2012-11-19 14:18:21 +00:00
/ *
Get the current status of the TiddlyWeb connection
* /
2012-11-17 20:18:36 +00:00
TiddlyWebSyncer . prototype . getStatus = function ( callback ) {
2012-11-16 17:01:04 +00:00
// Get status
2012-11-18 21:07:14 +00:00
var self = this ;
2012-11-19 16:55:57 +00:00
this . log ( "Getting status" ) ;
2012-11-17 20:18:36 +00:00
this . httpRequest ( {
2013-03-09 09:54:01 +00:00
url : this . host + "status" ,
2012-11-16 17:01:04 +00:00
callback : function ( err , data ) {
2012-11-18 10:24:20 +00:00
if ( err ) {
return callback ( err ) ;
}
2012-11-17 12:32:51 +00:00
// Decode the status JSON
2012-11-19 14:18:21 +00:00
var json = null ,
isLoggedIn = false ;
2012-11-16 17:01:04 +00:00
try {
json = JSON . parse ( data ) ;
} catch ( e ) {
}
if ( json ) {
2013-03-09 09:54:01 +00:00
// Record the recipe
2013-03-10 19:27:37 +00:00
if ( json . space ) {
self . recipe = "recipes/" + json . space . recipe + "/" ;
} else {
self . recipe = "" ;
}
2012-11-17 12:32:51 +00:00
// Check if we're logged in
2012-11-19 14:18:21 +00:00
isLoggedIn = json . username !== "GUEST" ;
2012-11-17 12:32:51 +00:00
// Set the various status tiddlers
2012-11-18 21:07:14 +00:00
self . wiki . addTiddler ( { title : TiddlyWebSyncer . titleIsLoggedIn , text : isLoggedIn ? "yes" : "no" } ) ;
2012-11-16 17:01:04 +00:00
if ( isLoggedIn ) {
2012-11-18 21:07:14 +00:00
self . wiki . addTiddler ( { title : TiddlyWebSyncer . titleUserName , text : json . username } ) ;
2012-11-16 17:01:04 +00:00
} else {
2012-11-18 21:07:14 +00:00
self . wiki . deleteTiddler ( TiddlyWebSyncer . titleUserName ) ;
2012-11-16 17:01:04 +00:00
}
}
2012-11-17 12:32:51 +00:00
// Invoke the callback if present
2012-11-16 17:01:04 +00:00
if ( callback ) {
2012-11-18 10:24:20 +00:00
callback ( null , isLoggedIn , json ) ;
2012-11-16 17:01:04 +00:00
}
}
} ) ;
} ;
/ *
2012-11-17 12:32:51 +00:00
Dispay a password prompt and allow the user to login
2012-11-16 17:01:04 +00:00
* /
2013-03-08 20:29:13 +00:00
TiddlyWebSyncer . prototype . handleLoginEvent = function ( ) {
2012-11-17 20:18:36 +00:00
var self = this ;
this . getStatus ( function ( isLoggedIn , json ) {
2012-11-17 12:32:51 +00:00
if ( ! isLoggedIn ) {
$tw . passwordPrompt . createPrompt ( {
serviceName : "Login to TiddlySpace" ,
callback : function ( data ) {
2012-11-18 10:24:20 +00:00
self . login ( data . username , data . password , function ( err , isLoggedIn ) {
self . syncFromServer ( ) ;
} ) ;
2012-11-17 12:32:51 +00:00
return true ; // Get rid of the password prompt
}
} ) ;
}
} ) ;
2012-11-16 17:01:04 +00:00
} ;
/ *
2012-11-17 12:32:51 +00:00
Attempt to login to TiddlyWeb .
username : username
password : password
callback : invoked with arguments ( err , isLoggedIn )
2012-11-16 17:01:04 +00:00
* /
2012-11-17 20:18:36 +00:00
TiddlyWebSyncer . prototype . login = function ( username , password , callback ) {
2012-11-19 16:55:57 +00:00
this . log ( "Attempting to login as" , username ) ;
2012-11-19 12:56:54 +00:00
var self = this ,
httpRequest = this . httpRequest ( {
2013-03-09 09:54:01 +00:00
url : this . host + "challenge/tiddlywebplugins.tiddlyspace.cookie_form" ,
2012-11-19 12:56:54 +00:00
type : "POST" ,
data : {
user : username ,
password : password ,
tiddlyweb _redirect : "/status" // workaround to marginalize automatic subsequent GET
} ,
callback : function ( err , data ) {
if ( err ) {
2012-11-17 12:32:51 +00:00
if ( callback ) {
2012-11-19 12:56:54 +00:00
callback ( err ) ;
2012-11-17 12:32:51 +00:00
}
2012-11-19 12:56:54 +00:00
} else {
2012-11-19 16:55:57 +00:00
self . log ( "Returned from logging in with data:" , data ) ;
2012-11-19 12:56:54 +00:00
self . getStatus ( function ( err , isLoggedIn , json ) {
if ( callback ) {
callback ( null , isLoggedIn ) ;
}
} ) ;
}
2012-11-17 12:32:51 +00:00
}
2012-11-19 12:56:54 +00:00
} ) ;
2012-11-16 17:01:04 +00:00
} ;
/ *
2012-11-17 12:32:51 +00:00
Attempt to log out of TiddlyWeb
2012-11-16 17:01:04 +00:00
* /
2013-03-08 20:29:13 +00:00
TiddlyWebSyncer . prototype . handleLogoutEvent = function ( options ) {
2012-11-16 17:01:04 +00:00
options = options || { } ;
2012-11-19 16:55:57 +00:00
this . log ( "Attempting to logout" ) ;
var self = this ,
httpRequest = this . httpRequest ( {
2013-03-09 09:54:01 +00:00
url : this . host + "logout" ,
2012-11-16 17:01:04 +00:00
type : "POST" ,
data : {
2012-11-17 20:18:36 +00:00
csrf _token : this . getCsrfToken ( ) ,
2012-11-16 17:01:04 +00:00
tiddlyweb _redirect : "/status" // workaround to marginalize automatic subsequent GET
} ,
callback : function ( err , data ) {
if ( err ) {
2012-11-17 20:31:30 +00:00
self . showError ( "logout error: " + err ) ;
2012-11-16 17:01:04 +00:00
} else {
2012-11-19 16:55:57 +00:00
self . log ( "Returned from logging out with data:" , data ) ;
2012-11-17 20:31:30 +00:00
self . getStatus ( ) ;
2012-11-16 17:01:04 +00:00
}
}
} ) ;
} ;
2012-11-18 10:24:20 +00:00
/ *
Synchronise from the server by reading the tiddler list from the recipe and queuing up GETs for any tiddlers that we don ' t already have
* /
TiddlyWebSyncer . prototype . syncFromServer = function ( ) {
2012-11-19 16:55:57 +00:00
this . log ( "Retrieving skinny tiddler list" ) ;
2012-11-18 10:24:20 +00:00
var self = this ;
this . httpRequest ( {
2013-03-10 19:27:37 +00:00
url : this . host + this . recipe + "tiddlers.json" ,
2012-11-18 10:24:20 +00:00
callback : function ( err , data ) {
2012-11-19 13:54:25 +00:00
// Check for errors
2012-11-18 10:24:20 +00:00
if ( err ) {
2012-11-19 16:55:57 +00:00
self . log ( "Error retrieving skinny tiddler list:" , err ) ;
2012-11-18 10:24:20 +00:00
return ;
}
2012-11-19 22:14:25 +00:00
// Process each incoming tiddler
var json = JSON . parse ( data ) ;
2012-11-18 10:24:20 +00:00
for ( var t = 0 ; t < json . length ; t ++ ) {
2012-11-19 22:14:25 +00:00
// Get the incoming tiddler fields, and the existing tiddler
var tiddlerFields = json [ t ] ,
incomingRevision = tiddlerFields . revision . toString ( ) ,
tiddler = self . wiki . getTiddler ( tiddlerFields . title ) ,
tiddlerInfo = self . tiddlerInfo [ tiddlerFields . title ] ,
currRevision = tiddlerInfo ? tiddlerInfo . revision : null ;
// Ignore the incoming tiddler if it's the same as the revision we've already got
if ( currRevision !== incomingRevision ) {
// Do a full load if we've already got a fat version of the tiddler
2012-11-23 13:08:10 +00:00
if ( tiddler && tiddler . fields . text !== undefined ) {
2012-11-19 22:14:25 +00:00
// Do a full load of this tiddler
self . enqueueSyncTask ( {
type : "load" ,
title : tiddlerFields . title
} ) ;
} else {
// Load the skinny version of the tiddler
2013-03-10 19:27:37 +00:00
self . storeTiddler ( tiddlerFields ) ;
2012-11-19 22:14:25 +00:00
}
2012-11-19 13:54:25 +00:00
}
2012-11-18 10:24:20 +00:00
}
2012-11-19 13:59:32 +00:00
// Trigger another sync
window . setTimeout ( function ( ) {
self . syncFromServer . call ( self ) ;
} , TiddlyWebSyncer . pollTimerInterval ) ;
2012-11-18 10:24:20 +00:00
}
} ) ;
} ;
2012-11-18 21:07:14 +00:00
/ *
Synchronise a set of changes to the server
* /
TiddlyWebSyncer . prototype . syncToServer = function ( changes ) {
2012-11-19 09:04:35 +00:00
var self = this ,
now = new Date ( ) ;
2013-03-10 19:27:37 +00:00
$tw . utils . each ( changes , function ( change , title , object ) {
2012-11-19 09:04:35 +00:00
// Queue a task to sync this tiddler
self . enqueueSyncTask ( {
2013-03-10 19:27:37 +00:00
type : change . deleted ? "delete" : "save" ,
2012-11-19 12:56:54 +00:00
title : title
2012-11-19 09:04:35 +00:00
} ) ;
} ) ;
} ;
/ *
Queue up a sync task . If there is already a pending task for the tiddler , just update the last modification time
* /
TiddlyWebSyncer . prototype . enqueueSyncTask = function ( task ) {
2012-11-19 12:56:54 +00:00
var self = this ,
now = new Date ( ) ;
// Set the timestamps on this task
task . queueTime = now ;
task . lastModificationTime = now ;
2012-11-19 09:04:35 +00:00
// Bail if it's not a tiddler we know about
if ( ! $tw . utils . hop ( this . tiddlerInfo , task . title ) ) {
return ;
}
2012-11-19 12:56:54 +00:00
// Bail if this is a save and the tiddler is already at the changeCount that the server has
if ( task . type === "save" && this . wiki . getChangeCount ( task . title ) <= this . tiddlerInfo [ task . title ] . changeCount ) {
2012-11-19 09:04:35 +00:00
return ;
}
// Check if this tiddler is already in the queue
if ( $tw . utils . hop ( this . taskQueue , task . title ) ) {
2012-11-19 16:55:57 +00:00
this . log ( "Re-queueing up sync task with type:" , task . type , "title:" , task . title ) ;
2012-11-19 12:56:54 +00:00
var existingTask = this . taskQueue [ task . title ] ;
// If so, just update the last modification time
existingTask . lastModificationTime = task . lastModificationTime ;
// If the new task is a save then we upgrade the existing task to a save. Thus a pending GET is turned into a PUT if the tiddler changes locally in the meantime. But a pending save is not modified to become a GET
if ( task . type === "save" ) {
existingTask . type = "save" ;
}
2012-11-19 09:04:35 +00:00
} else {
2012-11-19 16:55:57 +00:00
this . log ( "Queuing up sync task with type:" , task . type , "title:" , task . title ) ;
2012-11-19 09:04:35 +00:00
// If it is not in the queue, insert it
this . taskQueue [ task . title ] = task ;
}
2012-11-19 12:56:54 +00:00
// Process the queue
$tw . utils . nextTick ( function ( ) { self . processTaskQueue . call ( self ) ; } ) ;
2012-11-18 21:07:14 +00:00
} ;
2012-11-16 17:01:04 +00:00
/ *
2012-11-19 12:56:54 +00:00
Return the number of tasks in progress
2012-11-18 13:14:28 +00:00
* /
2012-11-19 12:56:54 +00:00
TiddlyWebSyncer . prototype . numTasksInProgress = function ( ) {
return $tw . utils . count ( this . taskInProgress ) ;
} ;
/ *
Return the number of tasks in the queue
* /
TiddlyWebSyncer . prototype . numTasksInQueue = function ( ) {
return $tw . utils . count ( this . taskQueue ) ;
} ;
/ *
Trigger a timeout if one isn ' t already outstanding
* /
TiddlyWebSyncer . prototype . triggerTimeout = function ( ) {
2012-11-18 13:14:28 +00:00
var self = this ;
2012-11-19 12:56:54 +00:00
if ( ! this . taskTimerId ) {
this . taskTimerId = window . setTimeout ( function ( ) {
self . taskTimerId = null ;
self . processTaskQueue . call ( self ) ;
} , TiddlyWebSyncer . taskTimerInterval ) ;
}
} ;
/ *
Process the task queue , performing the next task if appropriate
* /
TiddlyWebSyncer . prototype . processTaskQueue = function ( ) {
var self = this ;
// Only process a task if we're not already performing a task. If we are already performing a task then we'll dispatch the next one when it completes
if ( this . numTasksInProgress ( ) === 0 ) {
// Choose the next task to perform
var task = this . chooseNextTask ( ) ;
// Perform the task if we had one
if ( task ) {
// Remove the task from the queue and add it to the in progress list
delete this . taskQueue [ task . title ] ;
this . taskInProgress [ task . title ] = task ;
// Dispatch the task
this . dispatchTask ( task , function ( err ) {
// Mark that this task is no longer in progress
delete self . taskInProgress [ task . title ] ;
// Process the next task
self . processTaskQueue . call ( self ) ;
} ) ;
} else {
// Make sure we've set a time if there wasn't a task to perform, but we've still got tasks in the queue
if ( this . numTasksInQueue ( ) > 0 ) {
this . triggerTimeout ( ) ;
2012-11-18 13:14:28 +00:00
}
}
2012-11-19 12:56:54 +00:00
}
} ;
/ *
Choose the next applicable task
* /
TiddlyWebSyncer . prototype . chooseNextTask = function ( ) {
var self = this ,
candidateTask = null ,
now = new Date ( ) ;
// Select the best candidate task
$tw . utils . each ( this . taskQueue , function ( task , title ) {
// Exclude the task if there's one of the same name in progress
if ( $tw . utils . hop ( self . taskInProgress , title ) ) {
return ;
}
// Exclude the task if it is a save and the tiddler has been modified recently, but not hit the fallback time
if ( task . type === "save" && ( now - task . lastModificationTime ) < TiddlyWebSyncer . throttleInterval &&
( now - task . queueTime ) < TiddlyWebSyncer . fallbackInterval ) {
return ;
}
// Exclude the task if it is newer than the current best candidate
if ( candidateTask && candidateTask . queueTime < task . queueTime ) {
return ;
}
// Now this is our best candidate
candidateTask = task ;
} ) ;
return candidateTask ;
} ;
/ *
Dispatch a task and invoke the callback
* /
TiddlyWebSyncer . prototype . dispatchTask = function ( task , callback ) {
var self = this ;
if ( task . type === "save" ) {
var changeCount = this . wiki . getChangeCount ( task . title ) ;
2012-11-19 16:55:57 +00:00
this . log ( "Dispatching 'save' task:" , task . title ) ;
2012-11-19 12:56:54 +00:00
this . httpRequest ( {
2013-03-10 19:27:37 +00:00
url : this . host + this . recipe + "tiddlers/" + task . title ,
2012-11-19 12:56:54 +00:00
type : "PUT" ,
headers : {
"Content-type" : "application/json"
} ,
data : this . convertTiddlerToTiddlyWebFormat ( task . title ) ,
callback : function ( err , data , request ) {
if ( err ) {
return callback ( err ) ;
}
// Save the details of the new revision of the tiddler
var tiddlerInfo = self . tiddlerInfo [ task . title ] ;
tiddlerInfo . changeCount = changeCount ;
tiddlerInfo . revision = self . getRevisionFromEtag ( request ) ;
// Invoke the callback
callback ( null ) ;
}
} ) ;
} else if ( task . type === "load" ) {
// Load the tiddler
2012-11-19 16:55:57 +00:00
this . log ( "Dispatching 'load' task:" , task . title ) ;
2012-11-19 12:56:54 +00:00
this . httpRequest ( {
2013-03-10 19:27:37 +00:00
url : this . host + this . recipe + "tiddlers/" + task . title ,
2012-11-19 12:56:54 +00:00
callback : function ( err , data , request ) {
if ( err ) {
return callback ( err ) ;
}
// Store the tiddler and revision number
2013-03-10 19:27:37 +00:00
self . storeTiddler ( JSON . parse ( data ) ) ;
// Invoke the callback
callback ( null ) ;
}
} ) ;
} else if ( task . type === "delete" ) {
// Delete the tiddler
this . log ( "Dispatching 'delete' task:" , task . title ) ;
2013-03-11 10:05:01 +00:00
var bag = this . tiddlerInfo [ task . title ] . bag ,
bagFragment = bag ? "bags/" + bag + "/tiddlers/" : "tiddlers/" ;
2013-03-10 19:27:37 +00:00
this . httpRequest ( {
2013-03-11 10:05:01 +00:00
url : this . host + bagFragment + task . title ,
2013-03-10 19:27:37 +00:00
type : "DELETE" ,
callback : function ( err , data , request ) {
if ( err ) {
return callback ( err ) ;
}
2012-11-19 12:56:54 +00:00
// Invoke the callback
callback ( null ) ;
}
} ) ;
}
} ;
2012-11-19 14:18:21 +00:00
/ *
Convert a TiddlyWeb JSON tiddler into a TiddlyWiki5 tiddler and save it in the store . Returns true if the tiddler was actually stored
* /
2013-03-10 19:27:37 +00:00
TiddlyWebSyncer . prototype . storeTiddler = function ( tiddlerFields ) {
2012-11-19 14:18:21 +00:00
var self = this ,
result = { } ;
// Transfer the fields, pulling down the `fields` hashmap
$tw . utils . each ( tiddlerFields , function ( element , title , object ) {
if ( title === "fields" ) {
$tw . utils . each ( element , function ( element , subTitle , object ) {
result [ subTitle ] = element ;
} ) ;
} else {
result [ title ] = tiddlerFields [ title ] ;
}
} ) ;
// Some unholy freaking of content types
if ( result . type === "text/javascript" ) {
result . type = "application/javascript" ;
} else if ( ! result . type || result . type === "None" ) {
2012-11-19 16:58:16 +00:00
result . type = "text/x-tiddlywiki" ;
2012-11-19 14:18:21 +00:00
}
// Save the tiddler
self . wiki . addTiddler ( new $tw . Tiddler ( self . wiki . getTiddler ( result . title ) , result ) ) ;
// Save the tiddler revision and changeCount details
self . tiddlerInfo [ result . title ] = {
2013-03-10 19:27:37 +00:00
revision : tiddlerFields . revision ,
2013-03-11 10:05:01 +00:00
bag : tiddlerFields . bag ,
2012-11-19 14:18:21 +00:00
changeCount : self . wiki . getChangeCount ( result . title )
} ;
} ;
2012-11-19 12:56:54 +00:00
/ *
Convert a tiddler to a field set suitable for PUTting to TiddlyWeb
* /
TiddlyWebSyncer . prototype . convertTiddlerToTiddlyWebFormat = function ( title ) {
var result = { } ,
tiddler = this . wiki . getTiddler ( title ) ,
knownFields = [
"bag" , "created" , "creator" , "modified" , "modifier" , "permissions" , "recipe" , "revision" , "tags" , "text" , "title" , "type" , "uri"
] ;
if ( tiddler ) {
$tw . utils . each ( tiddler . fields , function ( fieldValue , fieldName ) {
2012-11-19 16:55:57 +00:00
var fieldString = fieldName === "tags" ?
tiddler . fields . tags :
tiddler . getFieldString ( fieldName ) ; // Tags must be passed as an array, not a string
2012-11-19 12:56:54 +00:00
if ( knownFields . indexOf ( fieldName ) !== - 1 ) {
// If it's a known field, just copy it across
result [ fieldName ] = fieldString ;
} else {
// If it's unknown, put it in the "fields" field
result . fields = result . fields || { } ;
result . fields [ fieldName ] = fieldString ;
}
} ) ;
}
2012-11-19 17:35:12 +00:00
// Convert the type "text/x-tiddlywiki" into null
if ( result . type === "text/x-tiddlywiki" ) {
result . type = null ;
}
2013-01-28 19:27:22 +00:00
return JSON . stringify ( result , null , $tw . config . preferences . jsonSpaces ) ;
2012-11-19 12:56:54 +00:00
} ;
/ *
Extract the revision from the Etag header of a request
* /
TiddlyWebSyncer . prototype . getRevisionFromEtag = function ( request ) {
var etag = request . getResponseHeader ( "Etag" ) ;
if ( etag ) {
return etag . split ( "/" ) [ 2 ] . split ( ":" ) [ 0 ] ; // etags are like "system-images_public/unsyncedIcon/946151:9f11c278ccde3a3149f339f4a1db80dd4369fc04"
} else {
return 0 ;
}
} ;
2012-11-18 13:14:28 +00:00
/ *
A quick and dirty HTTP function ; to be refactored later . Options are :
2012-11-16 17:01:04 +00:00
url : URL to retrieve
type : GET , PUT , POST etc
callback : function invoked with ( err , data )
* /
2012-11-17 20:18:36 +00:00
TiddlyWebSyncer . prototype . httpRequest = function ( options ) {
2012-11-16 17:01:04 +00:00
var type = options . type || "GET" ,
2012-11-18 13:14:28 +00:00
headers = options . headers || { accept : "application/json" } ,
2012-11-19 09:04:35 +00:00
request = new XMLHttpRequest ( ) ,
2012-11-16 17:01:04 +00:00
data = "" ,
f , results ;
2012-11-18 13:14:28 +00:00
// Massage the data hashmap into a string
2012-11-16 17:01:04 +00:00
if ( options . data ) {
if ( typeof options . data === "string" ) { // Already a string
data = options . data ;
} else { // A hashmap of strings
results = [ ] ;
2012-11-19 14:18:21 +00:00
$tw . utils . each ( options . data , function ( dataItem , dataItemTitle ) {
results . push ( dataItemTitle + "=" + encodeURIComponent ( dataItem ) ) ;
2012-11-19 09:04:35 +00:00
} ) ;
2012-11-19 14:18:21 +00:00
data = results . join ( "&" ) ;
2012-11-16 17:01:04 +00:00
}
}
2012-11-18 13:14:28 +00:00
// Set up the state change handler
2012-11-19 09:04:35 +00:00
request . onreadystatechange = function ( ) {
2012-11-16 17:01:04 +00:00
if ( this . readyState === 4 ) {
if ( this . status === 200 ) {
2012-11-19 14:18:21 +00:00
// Success!
2012-11-19 09:04:35 +00:00
options . callback ( null , this . responseText , this ) ;
2012-11-16 17:01:04 +00:00
return ;
}
2012-11-19 14:18:21 +00:00
// Something went wrong
2012-11-16 17:01:04 +00:00
options . callback ( new Error ( "XMLHttpRequest error: " + this . status ) ) ;
}
} ;
2012-11-18 13:14:28 +00:00
// Make the request
2012-11-19 09:04:35 +00:00
request . open ( type , options . url , true ) ;
2012-11-18 13:14:28 +00:00
if ( headers ) {
2012-11-19 14:18:21 +00:00
$tw . utils . each ( headers , function ( header , headerTitle , object ) {
request . setRequestHeader ( headerTitle , header ) ;
2012-11-19 09:04:35 +00:00
} ) ;
2012-11-18 13:14:28 +00:00
}
2012-11-19 12:56:54 +00:00
if ( data && ! $tw . utils . hop ( headers , "Content-type" ) ) {
2012-11-19 09:04:35 +00:00
request . setRequestHeader ( "Content-type" , "application/x-www-form-urlencoded; charset=UTF-8" ) ;
2012-11-16 17:01:04 +00:00
}
2012-11-19 09:04:35 +00:00
request . send ( data ) ;
return request ;
2012-11-16 17:01:04 +00:00
} ;
2012-11-19 14:18:21 +00:00
/ *
Retrieve the CSRF token from its cookie
* /
TiddlyWebSyncer . prototype . getCsrfToken = function ( ) {
var regex = /^(?:.*; )?csrf_token=([^(;|$)]*)(?:;|$)/ ,
match = regex . exec ( document . cookie ) ,
csrf = null ;
if ( match && ( match . length === 2 ) ) {
csrf = match [ 1 ] ;
}
return csrf ;
} ;
2012-11-17 20:18:36 +00:00
// Only export anything on the browser
if ( $tw . browser ) {
exports . name = "tiddlywebsyncer" ;
exports . syncer = TiddlyWebSyncer ;
}
2012-11-16 17:01:04 +00:00
} ) ( ) ;