1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2025-02-04 05:09:10 +00:00

Update TiddlyWeb syncer

Includes the beginnings of support for deleting tiddlers. It also
allows the recipe to be optional.
This commit is contained in:
Jeremy Ruston 2013-03-10 19:27:37 +00:00
parent fc33df8829
commit 672fa4b38c

View File

@ -3,7 +3,53 @@ title: $:/plugins/tiddlywiki/tiddlyweb/tiddlyweb.js
type: application/javascript type: application/javascript
module-type: syncer module-type: syncer
Main TiddlyWeb syncer module 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.
\*/ \*/
(function(){ (function(){
@ -17,14 +63,25 @@ Creates a TiddlyWebSyncer object
*/ */
var TiddlyWebSyncer = function(options) { var TiddlyWebSyncer = function(options) {
this.wiki = options.wiki; this.wiki = options.wiki;
this.tiddlerInfo = {}; // Hashmap of {revision:,changeCount:} // Hashmap of {revision:,changeCount:}
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"],
changeCount: self.wiki.getChangeCount(title)
}
}
});
// Tasks are {type: "load"/"save", title:, queueTime:, lastModificationTime:} // Tasks are {type: "load"/"save", title:, queueTime:, lastModificationTime:}
this.taskQueue = {}; // Hashmap of tasks to be performed this.taskQueue = {}; // Hashmap of tasks to be performed
this.taskInProgress = {}; // Hash of tasks in progress this.taskInProgress = {}; // Hash of tasks in progress
this.taskTimerId = null; // Sync timer this.taskTimerId = null; // Sync timer
// Compute the host and recipe // Compute the host and recipe
this.host = document.location.protocol + "//" + document.location.host + "/"; this.host = document.location.protocol + "//" + document.location.host + "/";
this.recipe = undefined; // Filled in by getStatus() this.recipe = undefined; // Filled in by getStatus() to be either "" or "recipes/<recipename>/"
// Mark us as not logged in // Mark us as not logged in
this.wiki.addTiddler({title: TiddlyWebSyncer.titleIsLoggedIn,text: "no"}); this.wiki.addTiddler({title: TiddlyWebSyncer.titleIsLoggedIn,text: "no"});
// Listen out for changes to tiddlers // Listen out for changes to tiddlers
@ -33,7 +90,6 @@ var TiddlyWebSyncer = function(options) {
}); });
this.log("Initialising with host:",this.host); this.log("Initialising with host:",this.host);
// Get the login status // Get the login status
var self = this;
this.getStatus(function (err,isLoggedIn,json) { this.getStatus(function (err,isLoggedIn,json) {
if(isLoggedIn) { if(isLoggedIn) {
// Do a sync // Do a sync
@ -99,7 +155,11 @@ TiddlyWebSyncer.prototype.getStatus = function(callback) {
} }
if(json) { if(json) {
// Record the recipe // Record the recipe
self.recipe = json.space.recipe; if(json.space) {
self.recipe = "recipes/" + json.space.recipe + "/";
} else {
self.recipe = "";
}
// Check if we're logged in // Check if we're logged in
isLoggedIn = json.username !== "GUEST"; isLoggedIn = json.username !== "GUEST";
// Set the various status tiddlers // Set the various status tiddlers
@ -204,7 +264,7 @@ TiddlyWebSyncer.prototype.syncFromServer = function() {
this.log("Retrieving skinny tiddler list"); this.log("Retrieving skinny tiddler list");
var self = this; var self = this;
this.httpRequest({ this.httpRequest({
url: this.host + "recipes/" + this.recipe + "/tiddlers.json", url: this.host + this.recipe + "tiddlers.json",
callback: function(err,data) { callback: function(err,data) {
// Check for errors // Check for errors
if(err) { if(err) {
@ -231,7 +291,7 @@ TiddlyWebSyncer.prototype.syncFromServer = function() {
}); });
} else { } else {
// Load the skinny version of the tiddler // Load the skinny version of the tiddler
self.storeTiddler(tiddlerFields,incomingRevision); self.storeTiddler(tiddlerFields);
} }
} }
} }
@ -249,10 +309,10 @@ Synchronise a set of changes to the server
TiddlyWebSyncer.prototype.syncToServer = function(changes) { TiddlyWebSyncer.prototype.syncToServer = function(changes) {
var self = this, var self = this,
now = new Date(); now = new Date();
$tw.utils.each(changes,function(element,title,object) { $tw.utils.each(changes,function(change,title,object) {
// Queue a task to sync this tiddler // Queue a task to sync this tiddler
self.enqueueSyncTask({ self.enqueueSyncTask({
type: "save", type: change.deleted ? "delete" : "save",
title: title title: title
}); });
}); });
@ -388,7 +448,7 @@ TiddlyWebSyncer.prototype.dispatchTask = function(task,callback) {
var changeCount = this.wiki.getChangeCount(task.title); var changeCount = this.wiki.getChangeCount(task.title);
this.log("Dispatching 'save' task:",task.title); this.log("Dispatching 'save' task:",task.title);
this.httpRequest({ this.httpRequest({
url: this.host + "recipes/" + this.recipe + "/tiddlers/" + task.title, url: this.host + this.recipe + "tiddlers/" + task.title,
type: "PUT", type: "PUT",
headers: { headers: {
"Content-type": "application/json" "Content-type": "application/json"
@ -410,13 +470,27 @@ TiddlyWebSyncer.prototype.dispatchTask = function(task,callback) {
// Load the tiddler // Load the tiddler
this.log("Dispatching 'load' task:",task.title); this.log("Dispatching 'load' task:",task.title);
this.httpRequest({ this.httpRequest({
url: this.host + "recipes/" + this.recipe + "/tiddlers/" + task.title, url: this.host + this.recipe + "tiddlers/" + task.title,
callback: function(err,data,request) { callback: function(err,data,request) {
if(err) { if(err) {
return callback(err); return callback(err);
} }
// Store the tiddler and revision number // Store the tiddler and revision number
self.storeTiddler(JSON.parse(data),self.getRevisionFromEtag(request)); 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);
this.httpRequest({
url: this.host + this.recipe + "tiddlers/" + task.title,
type: "DELETE",
callback: function(err,data,request) {
if(err) {
return callback(err);
}
// Invoke the callback // Invoke the callback
callback(null); callback(null);
} }
@ -427,7 +501,7 @@ TiddlyWebSyncer.prototype.dispatchTask = function(task,callback) {
/* /*
Convert a TiddlyWeb JSON tiddler into a TiddlyWiki5 tiddler and save it in the store. Returns true if the tiddler was actually stored Convert a TiddlyWeb JSON tiddler into a TiddlyWiki5 tiddler and save it in the store. Returns true if the tiddler was actually stored
*/ */
TiddlyWebSyncer.prototype.storeTiddler = function(tiddlerFields,revision) { TiddlyWebSyncer.prototype.storeTiddler = function(tiddlerFields) {
var self = this, var self = this,
result = {}; result = {};
// Transfer the fields, pulling down the `fields` hashmap // Transfer the fields, pulling down the `fields` hashmap
@ -450,7 +524,7 @@ TiddlyWebSyncer.prototype.storeTiddler = function(tiddlerFields,revision) {
self.wiki.addTiddler(new $tw.Tiddler(self.wiki.getTiddler(result.title),result)); self.wiki.addTiddler(new $tw.Tiddler(self.wiki.getTiddler(result.title),result));
// Save the tiddler revision and changeCount details // Save the tiddler revision and changeCount details
self.tiddlerInfo[result.title] = { self.tiddlerInfo[result.title] = {
revision: revision, revision: tiddlerFields.revision,
changeCount: self.wiki.getChangeCount(result.title) changeCount: self.wiki.getChangeCount(result.title)
}; };
}; };