mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2025-11-21 17:54:51 +00:00
Module-ize server routes and add static file support (#2510)
* Refactor server routes to modules New module type: serverroute Caveats: Loading order is not deterministic but this would only matter if two route modules attempted to use the same path regexp (that would be silly). * Add static assets plugin This plugin allows the node server to fetch static assets in the /assets directory. I felt that this was a feature that goes above the core functionality. That is why I added it as a plugin. with the modular route extensions this was a breeze. * Add serverroute description to ModuleTypes
This commit is contained in:
committed by
Jeremy Ruston
parent
1da8a32837
commit
1ae3927c28
@@ -9,6 +9,7 @@ isfilteroperator: Operands for the ''is'' filter operator.
|
||||
macro: JavaScript macro definitions.
|
||||
parser: Parsers for different content types.
|
||||
saver: Savers handle different methods for saving files from the browser.
|
||||
serverroute: Server routes handle different HTTP requests when using the `--server` command.
|
||||
startup: Startup functions.
|
||||
storyview: Story views customise the animation and behaviour of list widgets.
|
||||
tiddlerdeserializer: Converts different content types into tiddlers.
|
||||
|
||||
@@ -145,6 +145,7 @@ SimpleServer.prototype.listen = function(port,host) {
|
||||
};
|
||||
|
||||
var Command = function(params,commander,callback) {
|
||||
var _this = this;
|
||||
this.params = params;
|
||||
this.commander = commander;
|
||||
this.callback = callback;
|
||||
@@ -153,127 +154,9 @@ var Command = function(params,commander,callback) {
|
||||
wiki: this.commander.wiki
|
||||
});
|
||||
// Add route handlers
|
||||
this.server.addRoute({
|
||||
method: "PUT",
|
||||
path: /^\/recipes\/default\/tiddlers\/(.+)$/,
|
||||
handler: function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]),
|
||||
fields = JSON.parse(state.data);
|
||||
// Pull up any subfields in the `fields` object
|
||||
if(fields.fields) {
|
||||
$tw.utils.each(fields.fields,function(field,name) {
|
||||
fields[name] = field;
|
||||
});
|
||||
delete fields.fields;
|
||||
}
|
||||
// Remove any revision field
|
||||
if(fields.revision) {
|
||||
delete fields.revision;
|
||||
}
|
||||
state.wiki.addTiddler(new $tw.Tiddler(state.wiki.getCreationFields(),fields,{title: title},state.wiki.getModificationFields()));
|
||||
var changeCount = state.wiki.getChangeCount(title).toString();
|
||||
response.writeHead(204, "OK",{
|
||||
Etag: "\"default/" + encodeURIComponent(title) + "/" + changeCount + ":\"",
|
||||
"Content-Type": "text/plain"
|
||||
});
|
||||
response.end();
|
||||
}
|
||||
});
|
||||
this.server.addRoute({
|
||||
method: "DELETE",
|
||||
path: /^\/bags\/default\/tiddlers\/(.+)$/,
|
||||
handler: function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]);
|
||||
state.wiki.deleteTiddler(title);
|
||||
response.writeHead(204, "OK", {
|
||||
"Content-Type": "text/plain"
|
||||
});
|
||||
response.end();
|
||||
}
|
||||
});
|
||||
this.server.addRoute({
|
||||
method: "GET",
|
||||
path: /^\/$/,
|
||||
handler: function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": state.server.get("serveType")});
|
||||
var text = state.wiki.renderTiddler(state.server.get("renderType"),state.server.get("rootTiddler"));
|
||||
response.end(text,"utf8");
|
||||
}
|
||||
});
|
||||
this.server.addRoute({
|
||||
method: "GET",
|
||||
path: /^\/status$/,
|
||||
handler: function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
var text = JSON.stringify({
|
||||
username: state.server.get("username"),
|
||||
space: {
|
||||
recipe: "default"
|
||||
},
|
||||
tiddlywiki_version: $tw.version
|
||||
});
|
||||
response.end(text,"utf8");
|
||||
}
|
||||
});
|
||||
this.server.addRoute({
|
||||
method: "GET",
|
||||
path: /^\/favicon.ico$/,
|
||||
handler: function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": "image/x-icon"});
|
||||
var buffer = state.wiki.getTiddlerText("$:/favicon.ico","");
|
||||
response.end(buffer,"base64");
|
||||
}
|
||||
});
|
||||
this.server.addRoute({
|
||||
method: "GET",
|
||||
path: /^\/recipes\/default\/tiddlers.json$/,
|
||||
handler: function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
var tiddlers = [];
|
||||
state.wiki.forEachTiddler({sortField: "title"},function(title,tiddler) {
|
||||
var tiddlerFields = {};
|
||||
$tw.utils.each(tiddler.fields,function(field,name) {
|
||||
if(name !== "text") {
|
||||
tiddlerFields[name] = tiddler.getFieldString(name);
|
||||
}
|
||||
});
|
||||
tiddlerFields.revision = state.wiki.getChangeCount(title);
|
||||
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
|
||||
tiddlers.push(tiddlerFields);
|
||||
});
|
||||
var text = JSON.stringify(tiddlers);
|
||||
response.end(text,"utf8");
|
||||
}
|
||||
});
|
||||
this.server.addRoute({
|
||||
method: "GET",
|
||||
path: /^\/recipes\/default\/tiddlers\/(.+)$/,
|
||||
handler: function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]),
|
||||
tiddler = state.wiki.getTiddler(title),
|
||||
tiddlerFields = {},
|
||||
knownFields = [
|
||||
"bag", "created", "creator", "modified", "modifier", "permissions", "recipe", "revision", "tags", "text", "title", "type", "uri"
|
||||
];
|
||||
if(tiddler) {
|
||||
$tw.utils.each(tiddler.fields,function(field,name) {
|
||||
var value = tiddler.getFieldString(name);
|
||||
if(knownFields.indexOf(name) !== -1) {
|
||||
tiddlerFields[name] = value;
|
||||
} else {
|
||||
tiddlerFields.fields = tiddlerFields.fields || {};
|
||||
tiddlerFields.fields[name] = value;
|
||||
}
|
||||
});
|
||||
tiddlerFields.revision = state.wiki.getChangeCount(title);
|
||||
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
response.end(JSON.stringify(tiddlerFields),"utf8");
|
||||
} else {
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
}
|
||||
}
|
||||
$tw.modules.forEachModuleOfType("serverroute", function(title,routeDefinition) {
|
||||
// console.log("Loading server route " + title);
|
||||
_this.server.addRoute(routeDefinition);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
23
core/modules/serverroute/delete-tiddlers.js
Normal file
23
core/modules/serverroute/delete-tiddlers.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/*\
|
||||
title: $:/core/modules/serverroute/delete-tiddlers.js
|
||||
type: application/javascript
|
||||
module-type: serverroute
|
||||
|
||||
DELETE /recipes/default/tiddlers/:title
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
module.exports = {
|
||||
method: "DELETE",
|
||||
path: /^\/bags\/default\/tiddlers\/(.+)$/,
|
||||
|
||||
handler: function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]);
|
||||
state.wiki.deleteTiddler(title);
|
||||
response.writeHead(204, "OK", {
|
||||
"Content-Type": "text/plain"
|
||||
});
|
||||
response.end();
|
||||
}
|
||||
};
|
||||
}());
|
||||
20
core/modules/serverroute/get-favicon.js
Normal file
20
core/modules/serverroute/get-favicon.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/*\
|
||||
title: $:/core/modules/serverroute/get-favicon.js
|
||||
type: application/javascript
|
||||
module-type: serverroute
|
||||
|
||||
GET /favicon.ico
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
module.exports = {
|
||||
method: "GET",
|
||||
path: /^\/favicon.ico$/,
|
||||
|
||||
handler: function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": "image/x-icon"});
|
||||
var buffer = state.wiki.getTiddlerText("$:/favicon.ico","");
|
||||
response.end(buffer,"base64");
|
||||
}
|
||||
};
|
||||
}());
|
||||
20
core/modules/serverroute/get-index.js
Normal file
20
core/modules/serverroute/get-index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/*\
|
||||
title: $:/core/modules/serverroute/get-index.js
|
||||
type: application/javascript
|
||||
module-type: serverroute
|
||||
|
||||
GET /
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
module.exports = {
|
||||
method: "GET",
|
||||
path: /^\/$/,
|
||||
|
||||
handler: function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": state.server.get("serveType")});
|
||||
var text = state.wiki.renderTiddler(state.server.get("renderType"),state.server.get("rootTiddler"));
|
||||
response.end(text,"utf8");
|
||||
}
|
||||
};
|
||||
}());
|
||||
26
core/modules/serverroute/get-status.js
Normal file
26
core/modules/serverroute/get-status.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/*\
|
||||
title: $:/core/modules/serverroute/get-status.js
|
||||
type: application/javascript
|
||||
module-type: serverroute
|
||||
|
||||
GET /status
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
module.exports = {
|
||||
method: "GET",
|
||||
path: /^\/status$/,
|
||||
|
||||
handler: function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
var text = JSON.stringify({
|
||||
username: state.server.get("username"),
|
||||
space: {
|
||||
recipe: "default"
|
||||
},
|
||||
tiddlywiki_version: $tw.version
|
||||
});
|
||||
response.end(text,"utf8");
|
||||
}
|
||||
};
|
||||
}());
|
||||
41
core/modules/serverroute/get-tiddler.js
Normal file
41
core/modules/serverroute/get-tiddler.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/*\
|
||||
title: $:/core/modules/serverroute/get-tiddler.js
|
||||
type: application/javascript
|
||||
module-type: serverroute
|
||||
|
||||
GET /recipes/default/tiddlers/:title
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
module.exports = {
|
||||
method: "GET",
|
||||
path: /^\/recipes\/default\/tiddlers\/(.+)$/,
|
||||
|
||||
handler: function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]),
|
||||
tiddler = state.wiki.getTiddler(title),
|
||||
tiddlerFields = {},
|
||||
knownFields = [
|
||||
"bag", "created", "creator", "modified", "modifier", "permissions", "recipe", "revision", "tags", "text", "title", "type", "uri"
|
||||
];
|
||||
if(tiddler) {
|
||||
$tw.utils.each(tiddler.fields,function(field,name) {
|
||||
var value = tiddler.getFieldString(name);
|
||||
if(knownFields.indexOf(name) !== -1) {
|
||||
tiddlerFields[name] = value;
|
||||
} else {
|
||||
tiddlerFields.fields = tiddlerFields.fields || {};
|
||||
tiddlerFields.fields[name] = value;
|
||||
}
|
||||
});
|
||||
tiddlerFields.revision = state.wiki.getChangeCount(title);
|
||||
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
response.end(JSON.stringify(tiddlerFields),"utf8");
|
||||
} else {
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
}
|
||||
}
|
||||
};
|
||||
}());
|
||||
32
core/modules/serverroute/get-tiddlers-json.js
Normal file
32
core/modules/serverroute/get-tiddlers-json.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/*\
|
||||
title: $:/core/modules/serverroute/get-tiddlers-json.js
|
||||
type: application/javascript
|
||||
module-type: serverroute
|
||||
|
||||
GET /recipes/default/tiddlers/tiddlers.json
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
module.exports = {
|
||||
method: "GET",
|
||||
path: /^\/recipes\/default\/tiddlers.json$/,
|
||||
|
||||
handler: function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
var tiddlers = [];
|
||||
state.wiki.forEachTiddler({sortField: "title"},function(title,tiddler) {
|
||||
var tiddlerFields = {};
|
||||
$tw.utils.each(tiddler.fields,function(field,name) {
|
||||
if(name !== "text") {
|
||||
tiddlerFields[name] = tiddler.getFieldString(name);
|
||||
}
|
||||
});
|
||||
tiddlerFields.revision = state.wiki.getChangeCount(title);
|
||||
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
|
||||
tiddlers.push(tiddlerFields);
|
||||
});
|
||||
var text = JSON.stringify(tiddlers);
|
||||
response.end(text,"utf8");
|
||||
}
|
||||
};
|
||||
}());
|
||||
37
core/modules/serverroute/put-tiddler.js
Normal file
37
core/modules/serverroute/put-tiddler.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/*\
|
||||
title: $:/core/modules/serverroute/put-tiddler.js
|
||||
type: application/javascript
|
||||
module-type: serverroute
|
||||
|
||||
PUT /recipes/default/tiddlers/:title
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
module.exports = {
|
||||
method: "PUT",
|
||||
path: /^\/recipes\/default\/tiddlers\/(.+)$/,
|
||||
|
||||
handler: function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]),
|
||||
fields = JSON.parse(state.data);
|
||||
// Pull up any subfields in the `fields` object
|
||||
if(fields.fields) {
|
||||
$tw.utils.each(fields.fields,function(field,name) {
|
||||
fields[name] = field;
|
||||
});
|
||||
delete fields.fields;
|
||||
}
|
||||
// Remove any revision field
|
||||
if(fields.revision) {
|
||||
delete fields.revision;
|
||||
}
|
||||
state.wiki.addTiddler(new $tw.Tiddler(state.wiki.getCreationFields(),fields,{title: title},state.wiki.getModificationFields()));
|
||||
var changeCount = state.wiki.getChangeCount(title).toString();
|
||||
response.writeHead(204, "OK",{
|
||||
Etag: "\"default/" + encodeURIComponent(title) + "/" + changeCount + ":\"",
|
||||
"Content-Type": "text/plain"
|
||||
});
|
||||
response.end();
|
||||
}
|
||||
};
|
||||
}());
|
||||
60
plugins/tiddlywiki/server-static-assets/get-server-asset.js
Normal file
60
plugins/tiddlywiki/server-static-assets/get-server-asset.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/*\
|
||||
title: $:/plugins/tiddlywiki/server-static-assets/get-server-asset.js
|
||||
type: application/javascript
|
||||
module-type: serverroute
|
||||
|
||||
GET /assets/:server_asset
|
||||
|
||||
\*/
|
||||
/*jshint maxlen:false */
|
||||
(function() {
|
||||
if(!$tw.node) { return; }
|
||||
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var util = require("util");
|
||||
|
||||
var RESPONSES = {
|
||||
_ok: function(filename,content) {
|
||||
var extension = path.extname(filename);
|
||||
return {
|
||||
status: 200,
|
||||
content: content,
|
||||
type: $tw.config.fileExtensionInfo[extension] || "application/octet-stream"
|
||||
};
|
||||
},
|
||||
_error: function(filename,err) {
|
||||
return {status: 500, content: err.toString(), type: "text/plain"};
|
||||
},
|
||||
ENOENT: function(filename) {
|
||||
return {
|
||||
status: 404,
|
||||
content: "File '" + filename + "' not found.",
|
||||
type: "text/plain"
|
||||
};
|
||||
},
|
||||
EACCES: function(filename) {
|
||||
return {
|
||||
status: 403,
|
||||
content: "File '" + filename + "' is forbidden (permissions).",
|
||||
type: "text/plain"
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
method: "GET",
|
||||
path: /^\/assets\/(.+)$/,
|
||||
|
||||
handler: function(request,response,state) {
|
||||
var filename = path.join($tw.boot.wikiPath, "assets", state.params[0]);
|
||||
var extension = path.extname(filename);
|
||||
fs.readFile(filename,function(err,content) {
|
||||
var contentInfo = err ? RESPONSES[err.code] || RESPONSES._error : RESPONSES._ok;
|
||||
var responseData = contentInfo(filename, err || content);
|
||||
response.writeHead(responseData.status, {"Content-Type": responseData.type});
|
||||
response.end(responseData.content);
|
||||
});
|
||||
}
|
||||
};
|
||||
}());
|
||||
7
plugins/tiddlywiki/server-static-assets/plugin.info
Normal file
7
plugins/tiddlywiki/server-static-assets/plugin.info
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"title": "$:/plugins/tiddlywiki/server-static-assets",
|
||||
"description": "Static Server Assets",
|
||||
"author": "Sukima",
|
||||
"core-version": ">=5.0.0",
|
||||
"list": "readme"
|
||||
}
|
||||
18
plugins/tiddlywiki/server-static-assets/readme.tid
Normal file
18
plugins/tiddlywiki/server-static-assets/readme.tid
Normal file
@@ -0,0 +1,18 @@
|
||||
title: $:/plugins/tiddlywiki/server-static-assets/readme
|
||||
|
||||
This plugin allows the Node.JS server to serve static files from the filesystem.
|
||||
|
||||
Any files that are placed in `assets` can be served to the browser directly.
|
||||
|
||||
For example, if you had a wiki editions with the following directory structure:
|
||||
|
||||
```
|
||||
editions/tw5.com-server
|
||||
├── assets
|
||||
│ └── pony.png
|
||||
├── tiddlers
|
||||
│ └── (All the tiddler files)
|
||||
└── tiddlywiki.info
|
||||
```
|
||||
|
||||
And the command `tiddlywiki editions/tw5.com-server --server` then pointing your browser to `http://localhost:8080/assets/pony.png` would display the image.
|
||||
Reference in New Issue
Block a user