1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2025-06-03 23:24:07 +00:00

Update TiddlyWeb support

Lots of changes:
* Make the built-in server support recipes and bags, albeit there's
just one of each, called "default"
* Correctly parse returned Etag to get bag of freshly PUT tiddlers
* URI encoding for tiddler titles, so that tiddlers with slashes and so
on work OK
This commit is contained in:
Jeremy Ruston 2013-03-12 19:18:56 +00:00
parent 7e57c422dc
commit 71ecb022ef
3 changed files with 56 additions and 28 deletions

View File

@ -44,7 +44,7 @@ Command.prototype.execute = function() {
data += chunk.toString(); data += chunk.toString();
}); });
request.on("end",function() { request.on("end",function() {
var prefix = "/tiddlers/"; var prefix = "/recipes/default/tiddlers/";
if(requestPath.indexOf(prefix) === 0) { if(requestPath.indexOf(prefix) === 0) {
var title = decodeURIComponent(requestPath.substr(prefix.length)), var title = decodeURIComponent(requestPath.substr(prefix.length)),
fields = JSON.parse(data); fields = JSON.parse(data);
@ -65,7 +65,10 @@ Command.prototype.execute = function() {
} }
console.log("PUT tiddler",title,fields) console.log("PUT tiddler",title,fields)
// self.commander.wiki.addTiddler(new $tw.Tiddler(JSON.parse(data),{title: title})); // self.commander.wiki.addTiddler(new $tw.Tiddler(JSON.parse(data),{title: title}));
response.writeHead(204, "OK"); var changeCount = self.commander.wiki.getChangeCount(title).toString();
response.writeHead(204, "OK",{
Etag: "\"default/" + title + "/" + changeCount + ":\""
});
response.end(); response.end();
} else { } else {
response.writeHead(404); response.writeHead(404);
@ -74,10 +77,11 @@ console.log("PUT tiddler",title,fields)
}); });
break; break;
case "DELETE": case "DELETE":
var prefix = "/tiddlers/"; var prefix = "/bags/default/tiddlers/";
if(requestPath.indexOf(prefix) === 0) { if(requestPath.indexOf(prefix) === 0) {
console.log("DELETE tiddler",requestPath.substr(prefix.length)) var title = decodeURIComponent(requestPath.substr(prefix.length));
// self.commander.wiki.deleteTiddler(decodeURIComponent(requestPath.substr(prefix.length))); console.log("DELETE tiddler",title)
// self.commander.wiki.deleteTiddler(decodeURIComponent(title));
response.writeHead(204, "OK"); response.writeHead(204, "OK");
response.end(); response.end();
} else { } else {
@ -94,10 +98,13 @@ console.log("DELETE tiddler",requestPath.substr(prefix.length))
response.writeHead(200, {"Content-Type": "application/json"}); response.writeHead(200, {"Content-Type": "application/json"});
text = JSON.stringify({ text = JSON.stringify({
username: "ANONYMOUS", username: "ANONYMOUS",
space: {
recipe: "default"
},
tiddlywiki_version: $tw.version tiddlywiki_version: $tw.version
}); });
response.end(text,"utf8"); response.end(text,"utf8");
} else if(requestPath === "/tiddlers.json") { } else if(requestPath === "/recipes/default/tiddlers.json") {
response.writeHead(200, {"Content-Type": "application/json"}); response.writeHead(200, {"Content-Type": "application/json"});
var tiddlers = []; var tiddlers = [];
$tw.wiki.forEachTiddler("title",function(title,tiddler) { $tw.wiki.forEachTiddler("title",function(title,tiddler) {

View File

@ -4,6 +4,6 @@ title: $:/core/templates/html-div-tiddler
This template is used for saving tiddlers as an HTML DIV tag with attributes representing the tiddler fields. This version includes the tiddler changecount as the field `revision`. This template is used for saving tiddlers as an HTML DIV tag with attributes representing the tiddler fields. This version includes the tiddler changecount as the field `revision`.
-->`<div`<$fields exclude='text revision' template=' $name$="$encoded_value$"'></$fields>` revision="`<$info type='changecount'/>`"> -->`<div`<$fields exclude='text revision bag' template=' $name$="$encoded_value$"'></$fields>` revision="`<$info type='changecount'/>`" bag="default">
<pre>`<$view field="text" format="htmlencoded" />`</pre> <pre>`<$view field="text" format="htmlencoded" />`</pre>
</div>` </div>`

View File

@ -76,7 +76,7 @@ var TiddlyWebSyncer = function(options) {
} }
} }
}); });
// Tasks are {type: "load"/"save", title:, queueTime:, lastModificationTime:} // Tasks are {type: "load"/"save"/"delete", 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
@ -157,9 +157,7 @@ TiddlyWebSyncer.prototype.getStatus = function(callback) {
if(json) { if(json) {
// Record the recipe // Record the recipe
if(json.space) { if(json.space) {
self.recipe = "recipes/" + json.space.recipe + "/"; self.recipe = 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";
@ -265,7 +263,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 + this.recipe + "tiddlers.json", url: this.host + "recipes/" + this.recipe + "/tiddlers.json",
callback: function(err,data) { callback: function(err,data) {
// Check for errors // Check for errors
if(err) { if(err) {
@ -328,9 +326,13 @@ TiddlyWebSyncer.prototype.enqueueSyncTask = function(task) {
// Set the timestamps on this task // Set the timestamps on this task
task.queueTime = now; task.queueTime = now;
task.lastModificationTime = now; task.lastModificationTime = now;
// Bail if it's not a tiddler we know about // Fill in some tiddlerInfo if the tiddler is one we haven't seen before
if(!$tw.utils.hop(this.tiddlerInfo,task.title)) { if(!$tw.utils.hop(this.tiddlerInfo,task.title)) {
return; this.tiddlerInfo[task.title] = {
revision: "0",
bag: "bag-not-set",
changeCount: -1
}
} }
// Bail if this is a save and the tiddler is already at the changeCount that the server has // 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) { if(task.type === "save" && this.wiki.getChangeCount(task.title) <= this.tiddlerInfo[task.title].changeCount) {
@ -449,7 +451,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 + this.recipe + "tiddlers/" + task.title, url: this.host + "recipes/" + encodeURIComponent(this.recipe) + "/tiddlers/" + encodeURIComponent(task.title),
type: "PUT", type: "PUT",
headers: { headers: {
"Content-type": "application/json" "Content-type": "application/json"
@ -460,9 +462,11 @@ TiddlyWebSyncer.prototype.dispatchTask = function(task,callback) {
return callback(err); return callback(err);
} }
// Save the details of the new revision of the tiddler // Save the details of the new revision of the tiddler
var tiddlerInfo = self.tiddlerInfo[task.title]; var etagInfo = self.parseEtag(request.getResponseHeader("Etag")),
tiddlerInfo = self.tiddlerInfo[task.title];
tiddlerInfo.changeCount = changeCount; tiddlerInfo.changeCount = changeCount;
tiddlerInfo.revision = self.getRevisionFromEtag(request); tiddlerInfo.bag = etagInfo.bag;
tiddlerInfo.revision = etagInfo.revision;
// Invoke the callback // Invoke the callback
callback(null); callback(null);
} }
@ -471,7 +475,7 @@ 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 + this.recipe + "tiddlers/" + task.title, url: this.host + "recipes/" + encodeURIComponent(this.recipe) + "/tiddlers/" + encodeURIComponent(task.title),
callback: function(err,data,request) { callback: function(err,data,request) {
if(err) { if(err) {
return callback(err); return callback(err);
@ -485,10 +489,9 @@ TiddlyWebSyncer.prototype.dispatchTask = function(task,callback) {
} else if(task.type === "delete") { } else if(task.type === "delete") {
// Delete the tiddler // Delete the tiddler
this.log("Dispatching 'delete' task:",task.title); this.log("Dispatching 'delete' task:",task.title);
var bag = this.tiddlerInfo[task.title].bag, var bag = this.tiddlerInfo[task.title].bag;
bagFragment = bag ? "bags/" + bag + "/tiddlers/" : "tiddlers/";
this.httpRequest({ this.httpRequest({
url: this.host + bagFragment + task.title, url: this.host + "bags/" + encodeURIComponent(bag) + "/tiddlers/" + encodeURIComponent(task.title),
type: "DELETE", type: "DELETE",
callback: function(err,data,request) { callback: function(err,data,request) {
if(err) { if(err) {
@ -566,14 +569,32 @@ TiddlyWebSyncer.prototype.convertTiddlerToTiddlyWebFormat = function(title) {
}; };
/* /*
Extract the revision from the Etag header of a request Split a TiddlyWeb Etag into its constituent parts. For example:
```
"system-images_public/unsyncedIcon/946151:9f11c278ccde3a3149f339f4a1db80dd4369fc04"
```
Note that the value includes the opening and closing double quotes.
The parts are:
```
<bag>/<title>/<revision>:<hash>
```
*/ */
TiddlyWebSyncer.prototype.getRevisionFromEtag = function(request) { TiddlyWebSyncer.prototype.parseEtag = function(etag) {
var etag = request.getResponseHeader("Etag"); var firstSlash = etag.indexOf("/"),
if(etag) { lastSlash = etag.lastIndexOf("/"),
return etag.split("/")[2].split(":")[0]; // etags are like "system-images_public/unsyncedIcon/946151:9f11c278ccde3a3149f339f4a1db80dd4369fc04" colon = etag.lastIndexOf(":");
if(firstSlash === -1 || lastSlash === -1 || colon === -1) {
return null;
} else { } else {
return 0; return {
bag: decodeURIComponent(etag.substring(1,firstSlash)),
title: decodeURIComponent(etag.substring(firstSlash + 1,lastSlash)),
revision: etag.substring(lastSlash + 1,colon)
}
} }
}; };
@ -604,7 +625,7 @@ TiddlyWebSyncer.prototype.httpRequest = function(options) {
// Set up the state change handler // Set up the state change handler
request.onreadystatechange = function() { request.onreadystatechange = function() {
if(this.readyState === 4) { if(this.readyState === 4) {
if(this.status === 200) { if(this.status === 200 || this.status === 204) {
// Success! // Success!
options.callback(null,this.responseText,this); options.callback(null,this.responseText,this);
return; return;