1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-01-22 10:54:46 +00:00

Compare commits

...

17 Commits

Author SHA1 Message Date
jeremy@jermolene.com
bbdf9bae89 Merge branch 'master' into publishing-framework 2021-07-14 15:48:54 +01:00
jeremy@jermolene.com
f1d76a1eee Merge branch 'master' into publishing-framework 2021-06-01 13:17:47 +01:00
jeremy@jermolene.com
160c154ef1 Merge branch 'master' into publishing-framework 2021-05-20 13:53:29 +01:00
jeremy@jermolene.com
184083ad1a Use variable definition in the job/sitemap/route text instead of from var-* fields 2021-04-10 17:42:35 +01:00
jeremy@jermolene.com
9c62bd8030 Fix typo in link widget 2021-04-10 17:41:41 +01:00
jeremy@jermolene.com
b7419dec3a Finish refactoring of server <-> sitemap interface
Continuing c4cdb1ed8c
2021-04-10 13:51:07 +01:00
jeremy@jermolene.com
02d390a673 Add support for slugify to the link widget's tv-wikilink-template handling 2021-04-10 10:58:55 +01:00
jeremy@jermolene.com
c4cdb1ed8c Refactor sitemap route handling
To avoid duplicating the rendering code
2021-04-10 10:58:26 +01:00
jeremy@jermolene.com
5fcff1f1a3 Refactor server route handling to handle default documents properly 2021-04-10 10:57:52 +01:00
jeremy@jermolene.com
a8770d7645 Revert changes to image widget 2021-04-08 14:27:30 +01:00
jeremy@jermolene.com
df2a3fdefd Refactor sitemap handling so we can reuse it in the webserver 2021-04-07 12:16:12 +01:00
jeremy@jermolene.com
a8c248eb3d Revert to using parameterised paths instead of filters
Because we will be able to run parameterised paths in reverse for HTTP serving, which we can't do with filters
2021-04-06 12:19:49 +01:00
jeremy@jermolene.com
96103d5d4c Link to the PR 2021-04-05 17:09:43 +01:00
jeremy@jermolene.com
a025bce21f Clean up before publishing a snapshot to TiddlyHost 2021-04-05 17:07:19 +01:00
jeremy@jermolene.com
45cdd7bdf7 Merge branch 'master' into publishing-framework 2021-04-05 12:31:46 +01:00
jeremy@jermolene.com
509356c696 Add Node.js support, refactor modelling of jobs and sitemaps, and use modals framework 2021-04-04 13:18:41 +01:00
jeremy@jermolene.com
42e10d030a First commit 2021-03-22 09:34:32 +00:00
37 changed files with 1115 additions and 39 deletions

View File

@@ -757,7 +757,12 @@ $tw.modules.execute = function(moduleName,moduleRoot) {
tiddler = $tw.wiki.getTiddler(name),
_exports = {},
sandbox = {
module: {exports: _exports},
module: {
exports: _exports,
setStringHandler: function(handler) {
moduleInfo.stringHandler = handler;
}
},
//moduleInfo: moduleInfo,
exports: _exports,
console: console,
@@ -821,7 +826,7 @@ $tw.modules.execute = function(moduleName,moduleRoot) {
moduleInfo.definition(moduleInfo,moduleInfo.exports,sandbox.require);
} else if(typeof moduleInfo.definition === "string") { // String
moduleInfo.exports = _exports;
$tw.utils.evalSandboxed(moduleInfo.definition,sandbox,tiddler.fields.title);
$tw.utils.evalSandboxed(moduleInfo.definition,sandbox,name);
if(sandbox.module.exports) {
moduleInfo.exports = sandbox.module.exports; //more codemirror workaround
}
@@ -918,6 +923,20 @@ $tw.modules.createClassesFromModules = function(moduleType,subType,baseClass) {
return classes;
};
/*
Return a specified module string for a module, null if the module or string is missing
*/
$tw.modules.getModuleString = function(moduleName,stringName,language) {
if(moduleName in $tw.modules.titles) {
$tw.modules.execute(moduleName);
var stringHandler = $tw.modules.titles[moduleName].stringHandler;
if(stringHandler) {
return stringHandler(stringName,language);
}
}
return null;
};
/////////////////////////// Barebones tiddler object
/*

7
core/images/publish.tid Normal file
View File

@@ -0,0 +1,7 @@
title: $:/core/images/publish
tags: $:/tags/Image
<svg width="22pt" height="22pt" class="tc-image-publish tc-image-button" viewBox="0 0 128 128"><g fill-rule="evenodd">
<path d="M64.0434107,46.2358498 C65.8048912,45.8955184 67.6195684,46.7809274 68.4102078,48.4458716 L68.4937877,48.6340507 L98.8972485,122.034498 C99.7426494,124.075476 98.7734424,126.415349 96.7324641,127.26075 C94.7552664,128.079732 92.4975633,127.195747 91.5897922,125.284145 L91.5062123,125.095966 L88.403,117.605598 L79.5048497,126.50485 C78.775871,127.233828 77.8355254,127.622617 76.8810886,127.671216 L76.6764226,127.676423 C75.6527333,127.676423 74.6290441,127.285898 73.8479955,126.50485 L63.999,116.656598 L54.1520045,126.50485 C53.4230259,127.233828 52.4826802,127.622617 51.5282434,127.671216 L51.3235774,127.676423 C50.2998881,127.676423 49.2761989,127.285898 48.4951503,126.50485 L39.596,117.605598 L36.4937877,125.095966 C35.6483868,127.136944 33.3085142,128.106151 31.2675359,127.26075 C29.2265576,126.415349 28.2573506,124.075476 29.1027515,122.034498 L59.5062123,48.6340507 C60.2518688,46.8338766 62.1601511,45.867488 64.0005148,46.2445933 L64.0434107,46.2358498 Z M77.679,102.976598 L69.656,110.999598 L76.676,118.018598 L84.699,109.996598 L77.679,102.976598 Z M50.326,102.983598 L43.307,110.002598 L51.323,118.018598 L58.342,110.999598 L50.326,102.983598 Z M64.006,89.3035977 L55.983,97.3265977 L63.999,105.342598 L72.022,97.3195977 L64.006,89.3035977 Z M55.035,80.3325977 L50.348,91.6475977 L58.349,83.6465977 L55.035,80.3325977 Z M72.968,80.3415977 L69.663,83.6465977 L77.642,91.6255977 L72.968,80.3415977 Z M64,58.6895977 L58.3344072,72.3699713 C58.4295481,72.4423583 58.5221874,72.5195778 58.6119911,72.6016299 L58.7445283,72.7283325 L64.006,77.9895977 L69.2684411,72.7283325 C69.3957231,72.6010505 69.5294489,72.4841397 69.6685683,72.3775999 L64,58.6895977 Z M38.5026635,2.59571369 C40.7118025,2.59571369 42.5026635,4.38657469 42.5026635,6.59571369 C42.5026635,7.7643162 42.0015346,8.8158738 41.2024517,9.54721162 L41.372583,9.372583 C35.581722,15.163444 32,23.163444 32,32 C32,40.8370609 35.5821314,48.8374703 41.3735757,54.6284097 C42.0884306,55.3520538 42.531226,56.3471171 42.531226,57.4456008 C42.531226,59.6547398 40.740365,61.4456008 38.531226,61.4456008 C37.4327422,61.4456008 36.4376789,61.0028055 35.7147885,60.2859673 L35.7048234,60.2963403 C28.472656,53.0586276 24,43.0631255 24,32.0229786 C24,21.1046577 28.3744907,11.2080539 35.4664167,3.99022262 C36.2004724,3.13678606 37.2883808,2.59571369 38.5026635,2.59571369 Z M89.3688013,2.48959773 C90.5097745,2.48959773 91.5391719,2.96731026 92.2678917,3.73363348 L92.2733617,3.72780197 C99.5183488,10.9672382 104,20.9717355 104,32.0229786 C104,42.8488024 99.6993143,52.6701472 92.7132398,59.8717162 C91.9816831,60.8152368 90.8371876,61.4223982 89.5508819,61.4223982 C87.3417429,61.4223982 85.5508819,59.6315372 85.5508819,57.4223982 C85.5508819,56.3502862 85.9726717,55.3766885 86.6593633,54.6584932 L86.627417,54.627417 C92.418278,48.836556 96,40.836556 96,32 C96,23.2702193 92.5043135,15.3568999 86.8363661,9.5834674 C85.9399594,8.85136737 85.3688013,7.73718311 85.3688013,6.48959773 C85.3688013,4.28045873 87.1596623,2.48959773 89.3688013,2.48959773 Z M50.0871028,13.6297119 C52.2962418,13.6297119 54.0871028,15.4205729 54.0871028,17.6297119 C54.0871028,18.8506134 53.5401157,19.9437593 52.6778337,20.6774573 L52.6862915,20.6862915 C49.790861,23.581722 48,27.581722 48,32 C48,36.4187838 49.791271,40.4191937 52.6872859,43.3147028 L52.6777105,43.3251493 C53.4423082,44.0537041 53.918794,45.0819221 53.918794,46.2214294 C53.918794,48.4305684 52.127933,50.2214294 49.918794,50.2214294 C48.6900999,50.2214294 47.5907975,49.6674376 46.8570439,48.7956109 L47.0304281,48.9715536 C42.6867001,44.6283295 40,38.627921 40,32 C40,25.372583 42.6862915,19.372583 47.0294373,15.0294373 L47.0395567,15.0387468 C47.7732533,14.1766 48.8663118,13.6297119 50.0871028,13.6297119 Z M77.9823819,13.700452 C79.1679628,13.700452 80.2330731,14.2162483 80.9655529,15.0356811 L80.9715536,15.0304281 C85.314117,19.3734956 88,25.373087 88,32 C88,38.4619387 85.4461804,44.3274009 81.2927766,48.6421512 L80.9933974,48.9476975 C80.2638884,49.7186971 79.2309977,50.2000547 78.08568,50.2000547 C75.876541,50.2000547 74.08568,48.4091937 74.08568,46.2000547 C74.08568,45.0665777 74.557136,44.0432152 75.314649,43.3153662 L75.3137085,43.3137085 C78.209139,40.418278 80,36.418278 80,32 C80,27.5822253 78.209547,23.5826334 75.314698,20.6872811 C74.4981782,19.9511432 73.9823819,18.8860329 73.9823819,17.700452 C73.9823819,15.491313 75.7732429,13.700452 77.9823819,13.700452 Z M64,24 C68.418278,24 72,27.581722 72,32 C72,36.418278 68.418278,40 64,40 C59.581722,40 56,36.418278 56,32 C56,27.581722 59.581722,24 64,24 Z"></path>
</g>
</svg>

View File

@@ -85,6 +85,8 @@ Permaview/Caption: permaview
Permaview/Hint: Set browser address bar to a direct link to all the tiddlers in this story
Print/Caption: print page
Print/Hint: Print the current page
Publish/Caption: publish
Publish/Hint: Publish from the wiki
Refresh/Caption: refresh
Refresh/Hint: Perform a full refresh of the wiki
Save/Caption: ok

View File

@@ -96,6 +96,8 @@ Plugins/Updates/Caption: Updates
Plugins/Updates/Hint: Available updates to installed plugins
Plugins/Updates/UpdateAll/Caption: Update <<update-count>> plugins
Plugins/SubPluginPrompt: With <<count>> sub-plugins available
Publishing/Caption: Publishing
Publishing/Hint: Settings used for publishing extracts from this TiddlyWiki as separate files through a "publisher" module
Saving/Caption: Saving
Saving/DownloadSaver/AutoSave/Description: Permit automatic saving for the download saver
Saving/DownloadSaver/AutoSave/Hint: Enable Autosave for Download Saver

View File

@@ -23,6 +23,7 @@ All parameters are optional with safe defaults, and can be specified in any orde
* ''writers'' - comma-separated list of principals allowed to write to this wiki
* ''csrf-disable'' - set to "yes" to disable CSRF checks (defaults to "no")
* ''sse-enabled'' - set to "yes" to enable Server-sent events (defaults to "no")
* ''sitemap'' - optional sitemap describing how the tiddlers will be served. See [[Publishing]] for more details
* ''root-tiddler'' - the tiddler to serve at the root (defaults to "$:/core/save/all")
* ''root-render-type'' - the content type to which the root tiddler should be rendered (defaults to "text/plain")
* ''root-serve-type'' - the content type with which the root tiddler should be served (defaults to "text/html")

View File

@@ -0,0 +1,5 @@
title: $:/language/Publishing/Modal
subtitle: Publishing: ''<$transclude field="caption"><$view field="title"/></$transclude>''
footer: <$button message="tm-close-tiddler">Cancel</$button>
Publishing <$text text=<<totalFiles>>/> files via the "{{!!publisher}}" publisher.

View File

@@ -0,0 +1,46 @@
/*\
title: $:/core/modules/commands/publish.js
type: application/javascript
module-type: command
Publish static files
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "publish",
synchronous: false
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
if(this.params.length < 1) {
return "Missing filename filter";
}
var self = this,
wiki = this.commander.wiki,
jobTiddler = this.params[0],
variableList = this.params.slice(1),
variables = Object.create(null);
while(variableList.length >= 2) {
variables[variableList[0]] = variableList[1];
variableList = variableList.slice(2);
}
$tw.publisherHandler.publish(jobTiddler,this.callback,{commander: this.commander,variables: variables});
return null;
};
exports.Command = Command;
})();

View File

@@ -40,10 +40,10 @@ Command.prototype.execute = function() {
variableList = this.params.slice(4),
tiddlers = wiki.filterTiddlers(tiddlerFilter),
variables = Object.create(null);
while(variableList.length >= 2) {
variables[variableList[0]] = variableList[1];
variableList = variableList.slice(2);
}
while(variableList.length >= 2) {
variables[variableList[0]] = variableList[1];
variableList = variableList.slice(2);
}
$tw.utils.each(tiddlers,function(title) {
var parser = wiki.parseTiddler(template || title);
var widgetNode = wiki.makeWidget(parser,{variables: $tw.utils.extend({},variables,{currentTiddler: title})}),

View File

@@ -0,0 +1,30 @@
/*\
title: $:/core/modules/filters/moduleproperty.js
type: application/javascript
module-type: filteroperator
Filter [[module-name]moduleproperty[name]] retrieve a module property
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.moduleproperty = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
var value = require(title)[operator.operand || ""];
if(value !== undefined) {
results.push(value);
}
});
results.sort();
return results;
};
})();

View File

@@ -17,11 +17,23 @@ Export our filter function
*/
exports.modules = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
$tw.utils.each($tw.modules.types[title],function(moduleInfo,moduleName) {
results.push(moduleName);
if(operator.operands.length === 1) {
// Return all the module names without filtering
source(function(tiddler,title) {
$tw.utils.each($tw.modules.types[title],function(moduleInfo,moduleName) {
results.push(moduleName);
});
});
});
} else if(operator.operands.length >= 2) {
// Return the modules that have the module property specified in the first operand with the value in the second operand
source(function(tiddler,title) {
$tw.utils.each($tw.modules.types[title],function(moduleInfo,moduleName) {
if(require(moduleName)[operator.operands[0]] === operator.operands[1]) {
results.push(moduleName);
}
});
});
}
results.sort();
return results;
};

View File

@@ -0,0 +1,30 @@
/*\
title: $:/core/modules/filters/modulestring.js
type: application/javascript
module-type: filteroperator
Filter [[module-name]modulestring[en-gb]] retrieve a module strings in a particular language
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.modulestring = function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
var s = $tw.modules.getModuleString(title,operator.operands[0] || "",operator.operands[1] || "");
if(s !== null) {
results.push(s);
}
});
results.sort();
return results;
};
})();

View File

@@ -0,0 +1,181 @@
/*\
title: $:/core/modules/publisherhandler.js
type: application/javascript
module-type: global
The publisher manages publishing extracts of wikis as external files
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var PUBLISHING_MODAL_TITLE = "$:/language/Publishing/Modal";
/*
Instantiate the publisher manager with the following options
wiki: wiki object to be used
commander: commander object to be used for output
*/
function PublisherHandler(options) {
this.wiki = options.wiki;
this.commander = options.commander;
}
/*
Publish a job
jobTitle: title of tiddler containing details of the job
callback: completion callback invoked callback(err)
options: Include:
commander: commander object associated with publishing under Node.js
variables: hashmap of variables to be passed to renderings
*/
PublisherHandler.prototype.publish = function(jobTitle,callback,options) {
if(jobTitle) {
var job = new PublishingJob(jobTitle,this,options);
job.publish(callback);
}
};
function PublishingJob(jobTitle,publisherHandler,options) {
options = options || {};
// Save params
this.jobTitle = jobTitle;
this.publisherHandler = publisherHandler;
this.commander = options.commander;
this.publishVariables = options.variables || Object.create(null);
}
/*
Start publishing
*/
PublishingJob.prototype.publish = function(callback) {
var self = this;
// Get the job tiddler and check it is enabled
this.jobTiddler = this.publisherHandler.wiki.getTiddler(this.jobTitle);
if(this.jobTiddler && this.jobTiddler.fields.enabled === "yes") {
// Get the list of tiddlers to be exported, defaulting to all non-system tiddlers
this.exportList = this.publisherHandler.wiki.filterTiddlers(this.jobTiddler.fields["export-filter"] || "[!is[system]]");
// Get publisher
this.publisher = this.getPublisher(this.jobTiddler.fields.publisher);
if(this.publisher) {
// Get the sitemap
this.sitemap = new $tw.Sitemap(this.jobTiddler.fields.sitemap,{
wiki: this.publisherHandler.wiki,
variables: this.publishVariables
});
this.sitemap.load();
// Get the output operations
this.operations = this.sitemap.getAllFileDetails(this.exportList);
// Display the progress modal
if($tw.modal) {
this.progressModal = $tw.modal.display(PUBLISHING_MODAL_TITLE,{
progress: true,
variables: {
currentTiddler: this.jobTitle,
totalFiles: this.operations.length + ""
},
onclose: function(event) {
if(event !== self) {
// The modal was closed other than by us programmatically
self.isCancelled = true;
}
}
});
}
// Send the operations to the publisher
this.executeOperations(function(err) {
if(self.progressModal) {
self.progressModal.closeHandler(self);
}
callback(err);
});
} else {
return callback("Unrecognised publisher");
}
} else {
return callback("Missing or disabled job tiddler");
}
};
/*
Instantiate the required publisher object
*/
PublishingJob.prototype.getPublisher = function(publisherName) {
var publisher;
$tw.modules.forEachModuleOfType("publisher",function(title,module) {
if(module.name === publisherName) {
publisher = module;
}
});
return publisher && publisher.create(this.jobTiddler.fields,this.publisherHandler,this);
};
/*
Execute the operations for this job
*/
PublishingJob.prototype.executeOperations = function(callback) {
var self = this,
report = {overwrites: []},
nextOperation = 0,
performNextOperation = function() {
// Check for having been cancelled
if(self.isCancelled) {
if(self.publisher.publishCancel) {
self.publisher.publishCancel();
}
return callback("CANCELLED");
}
// Update progress
if(self.progressModal) {
self.progressModal.setProgress(nextOperation,self.operations.length);
}
// Check for having finished
if(nextOperation >= self.operations.length) {
$tw.utils.nextTick(function() {
self.publisher.publishEnd(callback);
});
} else {
// Execute this operation
var fileDetails = self.operations[nextOperation]();
nextOperation += 1;
self.publisher.publishFile(fileDetails,function() {
$tw.utils.nextTick(performNextOperation);
});
}
};
// Tell the publisher to start, and get back an array of the existing paths
self.publisher.publishStart(function(existingPaths) {
var paths = {};
$tw.utils.each(self.operations,function(operation) {
if(operation.path in paths) {
report.overwrites.push(operation.path);
}
paths[operation.path] = true;
});
// Run the operations
performNextOperation();
});
};
PublishingJob.prototype.saveReport = function(report) {
// Create the report tiddler
var reportTitle = this.wiki.generateNewTitle("$:/temp/publish-report");
$tw.wiki.addTiddler({
title: reportTitle,
text: "* " + report.overwrites.join("\n* ")
});
// Add the report tiddler title to the list field of the publisher parameters tiddler
var paramsTiddler = $tw.wiki.getTiddler(this.publisherParamsTitle),
list = (paramsTiddler.fields.list || []).slice(0);
list.unshift(reportTitle);
$tw.wiki.addTiddler(new $tw.Tiddler(paramsTiddler,{list: list}));
};
exports.PublisherHandler = PublisherHandler;
})();

View File

@@ -0,0 +1,52 @@
/*\
title: $:/core/modules/publishers/filesystem.js
type: application/javascript
module-type: publisher
Handles publishing to the Node.js filesystem
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.name = "filesystem";
exports.create = function(params,publisherHandler,publishingJob) {
return new FileSystemPublisher(params,publisherHandler,publishingJob);
};
function FileSystemPublisher(params,publisherHandler,publishingJob) {
this.params = params;
this.publisherHandler = publisherHandler;
this.publishingJob = publishingJob;
};
FileSystemPublisher.prototype.publishStart = function(callback) {
console.log("publishStart");
// Returns a list of the previously published files
callback([]);
};
FileSystemPublisher.prototype.publishFile = function(item,callback) {
var fs = require("fs"),
path = require("path"),
filepath = path.resolve(this.publishingJob.commander.outputPath,item.path);
$tw.utils.createFileDirectories(filepath);
fs.writeFile(filepath,item.text,item.isBase64 ? "base64" : "utf8",function(err) {
if(err) {
console.log("File writing error",err)
}
callback(err);
});
};
FileSystemPublisher.prototype.publishEnd = function(callback) {
console.log("publishEnd");
callback(null);
};
})();

View File

@@ -14,7 +14,7 @@ GET /
exports.method = "GET";
exports.path = /^\/$/;
exports.path = /^\/index.html$/;
exports.handler = function(request,response,state) {
var text = state.wiki.renderTiddler(state.server.get("root-render-type"),state.server.get("root-tiddler")),

View File

@@ -63,10 +63,7 @@ function Server(options) {
self.addAuthenticator(authenticatorDefinition.AuthenticatorClass);
});
// Load route handlers
$tw.modules.forEachModuleOfType("route", function(title,routeDefinition) {
// console.log("Loading server route " + title);
self.addRoute(routeDefinition);
});
this.addRouteHandlers();
// Initialise the http vs https
this.listenOptions = null;
this.protocol = "http";
@@ -182,11 +179,43 @@ Server.prototype.addAuthenticator = function(AuthenticatorClass) {
}
};
Server.prototype.findMatchingRoute = function(request,state) {
Server.prototype.addRouteHandlers = function() {
var self = this;
// Load route handlers from sitemap if present, or just load all route modules
if(this.variables.sitemap) {
this.sitemap = new $tw.Sitemap(this.variables.sitemap,{
wiki: this.wiki,
variables: {}
});
this.sitemap.load();
$tw.utils.each(this.sitemap.getServerRoutes(),function(routeInfo) {
self.addRoute({
method: "GET",
path: routeInfo.regexp,
handler: function(request,response,state) {
var fileDetails = routeInfo.handler(state.params);
if(fileDetails) {
response.writeHead(200, {"Content-Type": fileDetails.type});
response.end(fileDetails.text,fileDetails.isBase64 ? "base64" : "utf8");
} else {
response.writeHead(404);
response.end();
}
}
});
});
} else {
$tw.modules.forEachModuleOfType("route",function(title,routeDefinition) {
self.addRoute(routeDefinition);
});
}
};
Server.prototype.findMatchingRoute = function(request,state,options) {
options = options || {};
for(var t=0; t<this.routes.length; t++) {
var potentialRoute = this.routes[t],
pathRegExp = potentialRoute.path,
pathname = state.urlInfo.pathname,
pathname = options.pathname || state.urlInfo.pathname,
match;
if(state.pathPrefix) {
if(pathname.substr(0,state.pathPrefix.length) === state.pathPrefix) {
@@ -263,6 +292,15 @@ Server.prototype.requestHandler = function(request,response,options) {
}
// Find the route that matches this path
var route = self.findMatchingRoute(request,state);
if(!route) {
// Try with the default document
var defaultDocumentPathname = state.urlInfo.pathname;
if(defaultDocumentPathname.substr(-1) !== "/") {
defaultDocumentPathname = defaultDocumentPathname + "/";
}
defaultDocumentPathname = defaultDocumentPathname + "index.html";
route = self.findMatchingRoute(request,state,{pathname: defaultDocumentPathname});
}
// Optionally output debug info
if(self.get("debug-level") !== "none") {
console.log("Request path:",JSON.stringify(state.urlInfo));

236
core/modules/sitemap.js Normal file
View File

@@ -0,0 +1,236 @@
/*\
title: $:/core/modules/sitemap.js
type: application/javascript
module-type: global
Sitemaps are used for static publishing and web serving
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
function Sitemap(sitemapTitle,options) {
options = options || {};
this.sitemapTitle = sitemapTitle;
this.wiki = options.wiki;
this.routes = [];
this.variables = $tw.utils.extend({},options.variables);
}
Sitemap.prototype.load = function(sitemapTitle) {
var self = this;
// Get the sitemap
var sitemapTiddler = this.wiki.getTiddler(this.sitemapTitle);
if(sitemapTiddler) {
// Collect each route
$tw.utils.each(sitemapTiddler.fields.list,function(routeTitle) {
var routeTiddler = self.wiki.getTiddler(routeTitle);
if(routeTiddler) {
// Convert the path into a regexp and an array of {field:,function:} for each capture group
var regexpurgatedParameterisedPath = self.regexpurgateParameterisedPath(routeTiddler.fields["route-path"]);
self.routes.push({
title: routeTitle,
params: routeTiddler.getFieldStrings({prefix: "route-"}),
variables: routeTiddler.getFieldStrings({prefix: "var-"}),
regexp: regexpurgatedParameterisedPath.regexp,
captureGroups: regexpurgatedParameterisedPath.captureGroups
});
}
});
}
};
Sitemap.prototype.renderRoute = function(title,route) {
var tiddler = this.wiki.getTiddler(title);
switch(route.params.type) {
case "raw":
return {
path: this.resolveParameterisedPath(route.params.path,title),
text: tiddler.fields.text || "",
type: tiddler.fields.type || "",
isBase64: ($tw.config.contentTypeInfo[tiddler.fields.type] || {}).encoding === "base64"
};
break;
case "render":
var parser = {
tree: [
{
"type": "importvariables",
"attributes": {
"tiddler": {
"name": "tiddler",
"type": "string",
"value": this.sitemapTitle,
}
},
"tag": "$importvariables",
"isBlock": false,
"children": [
{
"type": "importvariables",
"attributes": {
"tiddler": {
"name": "tiddler",
"type": "string",
"value": route.title,
}
},
"tag": "$importvariables",
"isBlock": false,
"children": this.wiki.parseTiddler(route.params.template,{parseAsInline: true}).tree
}
]
}
]
},
widgetNode = this.wiki.makeWidget(parser,{
variables: $tw.utils.extend({},this.variables,{currentTiddler: title})
}),
container = $tw.fakeDocument.createElement("div");
widgetNode.render(container,null);
return {
path: this.resolveParameterisedPath(route.params.path,title),
text: container.textContent,
type: route.params["output-type"] || "text/html"
};
break;
}
};
/*
Returns an array of functions that return {path:,text:,type:,isBase64:} for each path
*/
Sitemap.prototype.getAllFileDetails = function(exportTiddlers) {
var self = this,
output = [];
$tw.utils.each(this.routes,function(route) {
var routeFilter = route.params["tiddler-filter"] || "DUMMY_RESULT", // If no filter is provided, use a dummy filter that returns a single result
routeTiddlers = self.wiki.filterTiddlers(routeFilter,null,self.wiki.makeTiddlerIterator(exportTiddlers));
$tw.utils.each(routeTiddlers,function(title) {
output.push(self.renderRoute.bind(self,title,route));
});
});
return output;
};
/*
Returns an array of server routes {regexp:, handler:}
*/
Sitemap.prototype.getServerRoutes = function() {
var self = this,
output = [];
$tw.utils.each(this.routes,function(route) {
output.push({
regexp: route.regexp,
handler: function(params) {
// Locate the tiddler identified by the capture groups, if any
var title = null,
nextParam = 0;
$tw.utils.each(route.captureGroups,function(captureGroup) {
var param = params[nextParam++];
if(captureGroup.field === "title") {
switch(captureGroup.function) {
case "slugify":
var titles = self.wiki.unslugify(param);
if(titles && titles.length > 0) {
title = titles[0];
}
break;
}
}
})
// Check that the tiddler passes the route filter
if(route.params["tiddler-filter"]) {
if(!title) {
return null;
}
var routeTiddlers = self.wiki.filterTiddlers(route.params["tiddler-filter"],null,self.wiki.makeTiddlerIterator([title]));
if(routeTiddlers.indexOf(title) === -1) {
return null;
}
}
// Return the rendering or raw tiddler
return self.renderRoute(title,route);
}
});
});
return output;
};
/*
Apply a tiddler to a parameterised path to create a usable path
*/
Sitemap.prototype.resolveParameterisedPath = function(parameterisedPath,title) {
var self = this;
// Split the path on $*_*$ markers
var tiddler = this.wiki.getTiddler(title),
output = [];
$tw.utils.each(parameterisedPath.split(/(\$[a-z_]+\$)/),function(part) {
var match = part.match(/\$([a-z]+)_([a-z]+)\$/);
if(match) {
var value;
// Get the base value
switch(match[1]) {
case "uri":
case "title":
value = title;
break;
case "type":
value = tiddler.fields.type || "text/vnd.tiddlywiki";
break;
}
// Apply the encoding function
switch(match[2]) {
case "encoded":
value = encodeURIComponent(value);
break;
case "doubleencoded":
value = encodeURIComponent(encodeURIComponent(value));
break;
case "slugify":
value = self.wiki.slugify(value);
break;
case "extension":
value = ($tw.config.contentTypeInfo[value] || {extension: "."}).extension.slice(1);
break;
}
output.push(value);
} else {
output.push(part);
}
});
return output.join("");
};
/*
// Convert the path into a regexp and an array of {field:,function:} for each capture group
*/
Sitemap.prototype.regexpurgateParameterisedPath = function(parameterisedPath) {
var regexpParts = ["\\/"],
captureGroups = [];
$tw.utils.each(parameterisedPath.split(/(\$[a-z_]+\$)/),function(part) {
var match = part.match(/\$([a-z]+)_([a-z]+)\$/);
if(match) {
regexpParts.push("(.+)");
captureGroups.push({
field: match[1],
function: match[2]
});
} else {
regexpParts.push($tw.utils.escapeRegExp(part));
}
});
return {
regexp: new RegExp("^" + regexpParts.join("") + "$"),
captureGroups: captureGroups
};
};
exports.Sitemap = Sitemap;
})();

View File

@@ -73,6 +73,12 @@ exports.startup = function() {
}
});
}
// Hook up events for the publisher handler
$tw.rootWidget.addEventListener("tm-publish",function(event) {
$tw.publisherHandler.publish(event.paramObject.job,function(err) {
console.log("Finished publishing with result:",err);
});
});
// If we're being viewed on a data: URI then give instructions for how to save
if(document.location.protocol === "data:") {
$tw.rootWidget.dispatchEvent({

View File

@@ -129,6 +129,10 @@ exports.startup = function() {
dirtyTracking: !$tw.syncadaptor,
preloadDirty: $tw.boot.preloadDirty || []
});
// Install the publisher handler
$tw.publisherHandler = new $tw.PublisherHandler({
wiki: $tw.wiki
});
// Host-specific startup
if($tw.browser) {
// Install the popup manager

View File

@@ -54,15 +54,27 @@ exports.getFieldList = function(field) {
/*
Get all the fields as a hashmap of strings. Options:
exclude: an array of field names to exclude
prefix: an optional field name prefix. Only fields with the prefix are included, and the prefix is stripped from the name
*/
exports.getFieldStrings = function(options) {
options = options || {};
var exclude = options.exclude || [];
var fields = {};
for(var field in this.fields) {
if($tw.utils.hop(this.fields,field)) {
if(exclude.indexOf(field) === -1) {
fields[field] = this.getFieldString(field);
var exclude = options.exclude || [],
fields = {},
field;
if(options.prefix) {
for(field in this.fields) {
if($tw.utils.hop(this.fields,field)) {
if(exclude.indexOf(field) === -1 && field.substring(0,options.prefix.length) === options.prefix) {
fields[field.substring(options.prefix.length)] = this.getFieldString(field);
}
}
}
} else {
for(field in this.fields) {
if($tw.utils.hop(this.fields,field)) {
if(exclude.indexOf(field) === -1) {
fields[field] = this.getFieldString(field);
}
}
}
}

View File

@@ -12,8 +12,9 @@ Modal message mechanism
/*global $tw: false */
"use strict";
var widget = require("$:/core/modules/widgets/widget.js");
var navigator = require("$:/core/modules/widgets/navigator.js");
var widget = require("$:/core/modules/widgets/widget.js"),
navigator = require("$:/core/modules/widgets/navigator.js"),
dm = $tw.utils.domMaker;
var Modal = function(wiki) {
this.wiki = wiki;
@@ -26,6 +27,10 @@ Display a modal dialogue
options: see below
Options include:
downloadLink: Text of a big download link to include
variables: variables to be passed to the modal
event: optional DOM event that initiated the modal
progress: set to true to add a progress bar
onclose: callback for when the modal is closed
*/
Modal.prototype.display = function(title,options) {
options = options || {};
@@ -47,7 +52,6 @@ Modal.prototype.display = function(title,options) {
"tv-story-list": (options.event && options.event.widget ? options.event.widget.getVariable("tv-story-list") : ""),
"tv-history-list": (options.event && options.event.widget ? options.event.widget.getVariable("tv-history-list") : "")
},options.variables);
// Create the wrapper divs
var wrapper = this.srcDocument.createElement("div"),
modalBackdrop = this.srcDocument.createElement("div"),
@@ -55,6 +59,7 @@ Modal.prototype.display = function(title,options) {
modalHeader = this.srcDocument.createElement("div"),
headerTitle = this.srcDocument.createElement("h3"),
modalBody = this.srcDocument.createElement("div"),
modalProgress = this.srcDocument.createElement("div"),
modalLink = this.srcDocument.createElement("a"),
modalFooter = this.srcDocument.createElement("div"),
modalFooterHelp = this.srcDocument.createElement("span"),
@@ -71,6 +76,7 @@ Modal.prototype.display = function(title,options) {
$tw.utils.addClass(modalWrapper,"tc-modal");
$tw.utils.addClass(modalHeader,"tc-modal-header");
$tw.utils.addClass(modalBody,"tc-modal-body");
$tw.utils.addClass(modalProgress,"tc-modal-progress");
$tw.utils.addClass(modalFooter,"tc-modal-footer");
// Join them together
wrapper.appendChild(modalBackdrop);
@@ -78,6 +84,15 @@ Modal.prototype.display = function(title,options) {
modalHeader.appendChild(headerTitle);
modalWrapper.appendChild(modalHeader);
modalWrapper.appendChild(modalBody);
if(options.progress) {
var modalProgressBar = this.srcDocument.createElement("div");
modalProgressBar.className = "tc-modal-progress-bar";
modalProgress.appendChild(modalProgressBar);
var modalProgressPercent = this.srcDocument.createElement("div");
modalProgressPercent.className = "tc-modal-progress-percent";
modalProgress.appendChild(modalProgressPercent);
modalWrapper.appendChild(modalProgress);
}
modalFooter.appendChild(modalFooterHelp);
modalFooter.appendChild(modalFooterButtons);
modalWrapper.appendChild(modalFooter);
@@ -105,7 +120,6 @@ Modal.prototype.display = function(title,options) {
parentWidget: $tw.rootWidget
});
navigatorWidgetNode.render(modalBody,null);
// Render the title of the message
var headerWidgetNode = this.wiki.makeTranscludeWidget(title,{
field: "subtitle",
@@ -182,6 +196,10 @@ Modal.prototype.display = function(title,options) {
this.wiki.addEventListener("change",refreshHandler);
// Add the close event handler
var closeHandler = function(event) {
// Call the onclose handler
if(options.onclose) {
options.onclose(event);
}
// Remove our refresh handler
self.wiki.removeEventListener("change",refreshHandler);
// Decrease the modal count and adjust the body class
@@ -236,6 +254,22 @@ Modal.prototype.display = function(title,options) {
$tw.utils.setStyle(modalWrapper,[
{transform: "translateY(0px)"}
]);
// Return the wrapper node
return {
domNode: wrapper,
closeHandler: closeHandler,
setProgress: function(numerator,denominator) {
// Remove old progress
while(modalProgressPercent.hasChildNodes()) {
modalProgressPercent.removeChild(modalProgressPercent.firstChild);
}
// Set new text
var percent = (numerator * 100 /denominator).toFixed(2) + "%";
modalProgressPercent.appendChild(self.srcDocument.createTextNode(percent));
// Set bar width
modalProgressBar.style.width = percent;
}
};
};
Modal.prototype.adjustPageClass = function() {

View File

@@ -40,10 +40,8 @@ ImportVariablesWidget.prototype.execute = function(tiddlerList) {
var widgetPointer = this;
// Got to flush all the accumulated variables
this.variables = new this.variablesConstructor();
// Get our parameters
this.filter = this.getAttribute("filter");
// Compute the filter
this.tiddlerList = tiddlerList || this.wiki.filterTiddlers(this.filter,this);
this.tiddlerList = tiddlerList || this.getTiddlerList();
// Accumulate the <$set> widgets from each tiddler
$tw.utils.each(this.tiddlerList,function(title) {
var parser = widgetPointer.wiki.parseTiddler(title);
@@ -86,7 +84,6 @@ ImportVariablesWidget.prototype.execute = function(tiddlerList) {
}
}
});
if (widgetPointer != this) {
widgetPointer.parseTreeNode.children = this.parseTreeNode.children;
} else {
@@ -94,13 +91,21 @@ ImportVariablesWidget.prototype.execute = function(tiddlerList) {
}
};
ImportVariablesWidget.prototype.getTiddlerList = function() {
var filter = this.getAttribute("filter"),
title = this.getAttribute("tiddler");
return (filter && this.wiki.filterTiddlers(filter,this)) || (title && [title]) || [];
};
/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
ImportVariablesWidget.prototype.refresh = function(changedTiddlers) {
// Recompute our attributes and the filter list
var changedAttributes = this.computeAttributes(),
tiddlerList = this.wiki.filterTiddlers(this.getAttribute("filter"),this);
filter = this.getAttribute("filter"),
title = this.getAttribute("tiddler"),
tiddlerList = this.getTiddlerList();
// Refresh if the filter has changed, or the list of tiddlers has changed, or any of the tiddlers in the list has changed
function haveListedTiddlersChanged() {
var changed = false;
@@ -111,7 +116,7 @@ ImportVariablesWidget.prototype.refresh = function(changedTiddlers) {
});
return changed;
}
if(changedAttributes.filter || !$tw.utils.isArrayEqual(this.tiddlerList,tiddlerList) || haveListedTiddlersChanged()) {
if(changedAttributes.filter || changedAttributes.tiddler || !$tw.utils.isArrayEqual(this.tiddlerList,tiddlerList) || haveListedTiddlersChanged()) {
// Compute the filter
this.removeChildDomNodes();
this.execute(tiddlerList);

View File

@@ -98,6 +98,7 @@ LinkWidget.prototype.renderLink = function(parent,nextSibling) {
var wikiLinkTemplateMacro = this.getVariable("tv-wikilink-template"),
wikiLinkTemplate = wikiLinkTemplateMacro ? wikiLinkTemplateMacro.trim() : "#$uri_encoded$";
wikiLinkText = $tw.utils.replaceString(wikiLinkTemplate,"$uri_encoded$",encodeURIComponent(this.to));
wikiLinkText = $tw.utils.replaceString(wikiLinkText,"$title_slugify$",this.wiki.slugify(this.to));
wikiLinkText = $tw.utils.replaceString(wikiLinkText,"$uri_doubleencoded$",encodeURIComponent(encodeURIComponent(this.to)));
}
// Override with the value of tv-get-export-link if defined

View File

@@ -1575,4 +1575,23 @@ exports.slugify = function(title,options) {
return slug;
};
/*
Return an array of the titles that would generate a specified slug, if any. Options include:
*/
exports.unslugify = function(slug) {
var self = this,
slugToTitle = this.getGlobalCache("slugs",function() {
var map = {};
$tw.utils.each($tw.wiki.allTitles(),function(title) {
var slug = self.slugify(title);
if(!(slug in map)) {
map[slug] = [];
}
map[slug].push(title);
});
return map;
});
return slugToTitle[slug];
};
})();

View File

@@ -0,0 +1,81 @@
title: $:/core/ui/ControlPanel/Publishing
tags: $:/tags/ControlPanel
caption: {{$:/language/ControlPanel/Publishing/Caption}}
{{$:/language/ControlPanel/Publishing/Hint}}
<div class="tc-publishing-job-listing">
<$list filter="[all[shadows+tiddlers]tag[$:/tags/PublishingJob]]" variable="job">
<div class="tc-publishing-job">
<h2>Job: <$link to=<<job>>><$transclude tiddler=<<job>> field="caption" mode="inline"/></$link></h2>
Publisher: <$select tiddler=<<job>> field="publisher">
<$list filter="[[publisher]modules[]moduleproperty[name]]">
<option value=<<currentTiddler>>><$text text=<<currentTiddler>>/></option>
</$list>
</$select>
Base URL: <$edit-text tiddler=<<job>> size="50" field="baseurl"/>
Logs: <$view tiddler=<<job>> field="list"/>
<$set name="publisher-name" value={{{ [<job>get[publisher]] }}}>
<$set name="publisher-module" value={{{ [[publisher]modules[name],<publisher-name>] }}}>
<div class="tc-publishing-job-publisher">
<h2>Publisher: <$text text=<<publisher-name>>/></h2>
<$set name="publisher-ui" value={{{ [<publisher-module>modulestring[ui],{$:/language}] }}}>
<$tiddler tiddler=<<job>>>
<<publisher-ui>>
</$tiddler>
</$set>
</div>
<$vars sitemap={{{ [<job>get[sitemap]] }}}>
<div class="tc-publishing-sitemap">
<h2>Sitemap: <$link to=<<sitemap>>><$view tiddler=<<sitemap>> field="caption"><$text text=<<sitemap>>/></$view></$link></h2>
<$list filter="[<sitemap>get[list]enlist-input[]]" variable="route">
<div class="tc-publishing-route">
<h2>Route: <$link to=<<route>>><$view tiddler=<<route>> field="caption"><$text text=<<route>>/></$view></$link></h2>
route type: <$view tiddler=<<route>> field="route-type"/>
path: <$edit-text tiddler=<<route>> size="50" field="route-path"/>
filter: <$edit-text tiddler=<<route>> size="50" field="route-tiddler-filter"/>
template: <$edit-text tiddler=<<route>> size="50" field="route-template"/>
</div>
</$list>
</div>
</$vars>
</$set>
</$set>
</div>
</$list>
</div>

View File

@@ -0,0 +1,19 @@
title: $:/core/ui/Buttons/publish
tags: $:/tags/PageControls
caption: {{$:/core/images/publish}} {{$:/language/Buttons/Publish/Caption}}
description: {{$:/language/Buttons/Publish/Hint}}
\define publish-actions()
<$list filter="[all[shadows+tiddlers]tag[$:/tags/PublishingJob]has[enabled]field:enabled[yes]]" variable="job">
<$action-sendmessage $message="tm-publish" job=<<job>>/>
</$list>
\end
<$button actions=<<publish-actions>> tooltip={{$:/language/Buttons/Publish/Hint}} aria-label={{$:/language/Buttons/Publish/Caption}} class=<<tv-config-toolbar-class>>>
<$list filter="[<tv-config-toolbar-icons>match[yes]]">
{{$:/core/images/publish}}
</$list>
<$list filter="[<tv-config-toolbar-text>match[yes]]">
<span class="tc-btn-text"><$text text={{$:/language/Buttons/Publish/Caption}}/></span>
</$list>
</$button>

View File

@@ -0,0 +1,9 @@
title: $:/config/PublishingJobs/Default
tags: $:/tags/PublishingJob
caption: Demo static site
publisher: filesystem
sitemap: $:/core/sitemaps/StaticSite
jszip-output-filename: myzipfile.zip
baseurl: https://example.com
enabled: yes
export-filter: [!is[image]!is[system]] [is[image]!is[system]]

View File

@@ -0,0 +1,7 @@
title: $:/core/routes/StaticSite/HTML
caption: Static HTML
tags: $:/tags/Route
route-type: render
route-path: static/$title_slugify$.html
route-tiddler-filter: [!is[system]!is[image]]
route-template: $:/core/templates/static.tiddler.html

View File

@@ -0,0 +1,6 @@
title: $:/core/routes/StaticSite/Images
caption: Images
tags: $:/tags/Route
route-type: raw
route-path: images/$title_slugify$.$type_extension$
route-tiddler-filter: [is[image]]

View File

@@ -0,0 +1,6 @@
title: $:/core/routes/StaticSite/Index
caption: Index
tags: $:/tags/Route
route-type: render
route-path: index.html
route-template: $:/core/save/all

View File

@@ -0,0 +1,7 @@
title: $:/core/routes/StaticSite/Styles
caption: Styles
tags: $:/tags/Route
route-type: render
route-path: static/static.css
route-template: $:/core/templates/static.template.css
route-output-type: text/css

View File

@@ -0,0 +1,9 @@
title: $:/core/sitemaps/StaticSite
caption: Static site map
description: The original TiddlyWiki 5 static file layout
tags: $:/tags/SiteMap
list: $:/core/routes/StaticSite/Index $:/core/routes/StaticSite/HTML $:/core/routes/StaticSite/Images $:/core/routes/StaticSite/Styles
\define tv-wikilink-template()
$title_slugify$.html
\end

View File

@@ -2,7 +2,5 @@ created: 20131127215321439
modified: 20140912135951542
title: $:/DefaultTiddlers
[[TiddlyWiki Pre-release]]
HelloThere
GettingStarted
Community
Publishing
$:/core/ui/ControlPanel/Publishing

View File

@@ -0,0 +1,69 @@
created: 20210315092524275
modified: 20210315093103563
tags: [[Working with TiddlyWiki]]
title: Publishing
You can publish extracts from a wiki as a collection of interconnected static files that can be hosted on the Internet independently from TiddlyWiki.
This is a work in progress: see the Pull Request for more details: https://github.com/Jermolene/TiddlyWiki5/pull/5559
!! Usage
In the browser, publishing jobs are configured in [[$:/ControlPanel]]. Clicking the ''publish'' button in the sidebar initiates publishing of all enabled publishing jobs.
Under Node.js, the PublishCommand is used to initiate publishing.
!! Terminology
TiddlyWiki has pluggable ''publisher modules'' that provide the means to publish to specific platforms, referred to by a short name:
* ''jszip'': The JSZip publisher packs the output files in a ZIP file which is automatically downloaded by the browser
* ''filesystem'': The filesystem publisher saves the output files directly to the file system (it is only available under Node.js)
* ''github'': The GitHub publisher uploads the output files to a GitHub repository suitable for GitHub Pages
Publisher modules can optionally present a configuration user interface. For example, the JSZip publisher requires the name of the zip file to be configured.
A ''publishing job'' describes a self-contained publishing operation. Jobs are defined as configuration tiddlers with the following fields:
* ''title'' -- by convention, prefixed with `$:/config/PublishingJobs/`
* ''caption'' -- human readable short name for the publishing job
* ''tags'' -- `$:/tags/PublishingJob`
* ''enabled'' -- must be set to `yes` for the publishing job to be recognised
* ''export-filter'' -- a filter defining which tiddlers are to be exported
* ''publisher'' -- the name of the publisher module to be used
* ''&lt;publisher-name&gt;-&lt;parameter-name&gt;'' -- parameters required by the publisher module
* ''sitemap'' -- title of the site map tiddler to be used
* ''text'' -- optional variable/macro definitions as sequential `\define` pragmas or nested `<$set>` widgets (the same format as global macros)
A ''site map'' describes the layout and types of files in a publishing job. Site maps are defined as configuration tiddlers with the following fields:
* ''title'' -- by convention, the prefix `$:/core/sitemaps/` is used for sitemaps defined in the core, `$:/plugins/<publisher-name>/<plugin-name>/sitemaps/` is used for sitemaps defined in other plugins, and `$:/config/sitemaps/` for user defined sitemaps
* ''caption'' -- human readable short name for the sitemap
* ''description'' -- longer human readable description for the sitemap
* ''tags'' -- `$:/tags/SiteMap`
* ''list'' -- list of titles of routes making up this sitemap
* ''text'' -- optional variable/macro definitions as sequential `\define` pragmas or nested `<$set>` widgets (the same format as global macros)
A ''route'' describes how a group of one or more files is to be created during the export. Routes are defined as configuration tiddlers with the following fields:
* ''title'' -- by convention, the prefix `$:/core/routes/` is used for sitemaps defined in the core, `$:/plugins/<publisher-name>/<plugin-name>/routes/` is used for sitemaps defined in other plugins, and `$:/config/routes/` for user defined sitemaps
* ''caption'' -- human readable short name for the route
* ''tags'' -- `$:/tags/Route`
* ''route-tiddler-filter'' -- a filter defining the tiddlers included in the route
* ''route-path'' - a parameterised path defining how the ''output path'' is derived from the field values of a particular tiddler
* ''route-template'' -- optional title of a tiddler used as a template for "render" route types
* ''route-type'' which can be set to "raw" or "render":
** ''"raw"'' indicates that the raw tiddler is to be saved, without any rendering
** ''"render"'' indicates that the tiddler is to be rendered through a specified template
* ''text'' -- optional variable/macro definitions as sequential `\define` pragmas or nested `<$set>` widgets (the same format as global macros)
The route tiddler filter is passed the tiddlers resulting from the job export filter. In order to respect the restrictions of the job export filter, route filters must be carefully constructed to ensure they pull their titles from the incoming list.
Parameterised paths are strings which may contain optional tokens of the format `fieldname_functionname`. These tokens are replaced by the value of the specified field passed through the specified encoding function. The available encoding functions are:
* ''encoded'' -- applies URI encoding to the value
* ''doubleencoded'' -- applies double URI encoding to the value
* ''slugify'' -- applies the [[slugify Operator]] to the value
* ''extension'' -- interprets the value as a content type and returns the associated file extension
For backwards compatibility, the field "uri" is accepted as a synonym for "title".

View File

@@ -0,0 +1,8 @@
caption: sitemap
created: 20210407121456100
modified: 20210407121456100
tags: [[WebServer Parameters]]
title: WebServer Parameter: sitemap
type: text/vnd.tiddlywiki
The optional [[web server configuration parameter|WebServer Parameters]] ''sitemap'' specifies the sitemap that determines how tiddlers are mapped to URLs. See [[Publishing]] for more details.

View File

@@ -1,6 +1,6 @@
caption: importvariables
created: 20140612142500000
modified: 20180928150043777
modified: 20210410110618869
tags: Widgets
title: ImportVariablesWidget
type: text/vnd.tiddlywiki
@@ -21,6 +21,7 @@ The content of the importvariables widget is the scope within which the imported
|!Attribute |!Description |
|filter |[[Tiddler filter|Filters]] defining the tiddlers from which macro definitions will be imported |
|tiddler |<<.from-version "5.1.24">> Optional title of a single tiddler from which macro definitions will be imported (only used if the ''filter'' attribute is missing or blank) |
! Global Macros

View File

@@ -0,0 +1,61 @@
/*\
title: $:/plugins/tiddlywiki/jszip/jszip-publisher.js
type: application/javascript
module-type: publisher
Handles publishing to a ZIP file
\*/
(function(){
if(module.setStringHandler) {
module.setStringHandler(function(name,language) {
switch(name) {
case "ui":
return "User interface of the JSZip publisher\n\nOutput filename: <$edit-text field=jszip-output-filename/>";
}
return null;
});
}
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.name = "jszip";
var JSZip = require("$:/plugins/tiddlywiki/jszip/jszip.js");
exports.create = function(params) {
return new JSZipPublisher(params);
};
function JSZipPublisher(params,publisherHandler,publishingJob,) {
this.params = params;
this.zip = new JSZip();
console.log("JSZipPublisher",params);
};
JSZipPublisher.prototype.publishStart = function(callback) {
console.log("publishStart");
// Returns a list of the previously published files, but always "none" for ZIP files
callback([]);
};
JSZipPublisher.prototype.publishFile = function(item,callback) {
this.zip.file(item.path,item.text,{base64: item.isBase64});
callback(null);
};
JSZipPublisher.prototype.publishEnd = function(callback) {
var data = this.zip.generate({type: "base64"}),
link = document.createElement("a");
link.setAttribute("href","data:application/zip;base64," + encodeURIComponent(data));
link.setAttribute("download",this.params["jszip-output-filename"] || "site.zip");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
callback(null);
};
})();

View File

@@ -1885,6 +1885,20 @@ html body.tc-body.tc-single-tiddler-window {
border: 1px solid <<colour modal-border>>;
}
.tc-modal-progress {
padding-left: 1em;
padding-right: 1em;
}
.tc-modal-progress-percent {
text-align: center;
}
.tc-modal-progress-bar {
height: 10px;
background: red;
}
@media (max-width: 55em) {
.tc-modal {
position: fixed;
@@ -2978,6 +2992,45 @@ select {
background: <<colour select-tag-background>>;
}
/*
Publishing UI
*/
.tc-publishing-job-listing {
margin: 0.25em;
padding: 0.25em;
border: 1px solid black;
background-color: #ddffff;
}
.tc-publishing-job {
margin: 0.25em;
padding: 0.25em;
border: 1px solid black;
background-color: #ffddff;
}
.tc-publishing-job-publisher {
margin: 0.25em;
padding: 0.25em;
border: 1px solid black;
background-color: #ffdddd;
}
.tc-publishing-sitemap {
margin: 0.25em;
padding: 0.25em;
border: 1px solid black;
background-color: #ffffdd;
}
.tc-publishing-route {
margin: 0.25em;
padding: 0.25em;
border: 1px solid black;
background-color: #ddffdd;
}
/*
** Utility classes for SVG icons
*/