mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2025-04-21 18:23:13 +00:00
Add Node.js support, refactor modelling of jobs and sitemaps, and use modals framework
This commit is contained in:
parent
42e10d030a
commit
509356c696
5
core/language/en-GB/Publishing/Modal.tid
Normal file
5
core/language/en-GB/Publishing/Modal.tid
Normal 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.
|
46
core/modules/commands/publish.js
Normal file
46
core/modules/commands/publish.js
Normal 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;
|
||||
|
||||
})();
|
||||
|
@ -12,223 +12,266 @@ The publisher manages publishing extracts of wikis as external files
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var PUBLISHING_MODAL_TITLE = "$:/language/Publishing/Modal";
|
||||
|
||||
/*
|
||||
Instantiate the publisher manager with the following options
|
||||
widget: optional widget for attaching event handlers
|
||||
wiki: wiki object to be used
|
||||
commander: commander object to be used for output
|
||||
*/
|
||||
function PublisherHandler(options) {
|
||||
this.widget = options.widget;
|
||||
this.wiki = options.wiki;
|
||||
this.reset();
|
||||
if(this.widget) {
|
||||
this.widget.addEventListener("tm-publish-start",this.onPublishStart.bind(this));
|
||||
this.widget.addEventListener("tm-publish-route",this.onPublishRoute.bind(this));
|
||||
this.widget.addEventListener("tm-publish-end",this.onPublishEnd.bind(this));
|
||||
}
|
||||
this.commander = options.commander;
|
||||
}
|
||||
|
||||
PublisherHandler.prototype.onPublishStart = function(event) {
|
||||
var publisherName = event.paramObject["publisher-name"],
|
||||
publisherParamsTitle = event.paramObject["publish-params-title"],
|
||||
publisherParamsTiddler = publisherParamsTitle && this.wiki.getTiddler(publisherParamsTitle);
|
||||
if(publisherName && publisherParamsTiddler) {
|
||||
this.publisherName = publisherName;
|
||||
this.publisherParamsTitle = publisherParamsTitle;
|
||||
this.publisherParams = publisherParamsTiddler.fields;
|
||||
this.routes = [];
|
||||
/*
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
PublisherHandler.prototype.onPublishEnd = function(event) {
|
||||
if(this.publisherName && this.publisherParams && this.routes) {
|
||||
this.publish();
|
||||
}
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
PublisherHandler.prototype.onPublishRoute = function(event) {
|
||||
if(this.publisherName && this.publisherParams && this.routes) {
|
||||
this.routes.push(event.paramObject);
|
||||
/*
|
||||
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 the job variables
|
||||
this.jobVariables = this.extractVariables(this.jobTiddler);
|
||||
// Get publisher
|
||||
this.publisher = this.getPublisher(this.jobTiddler.fields.publisher);
|
||||
if(this.publisher) {
|
||||
// Get the sitemap
|
||||
this.sitemap = this.publisherHandler.wiki.getTiddler(this.jobTiddler.fields.sitemap);
|
||||
if(this.sitemap) {
|
||||
// Get the sitemap variables
|
||||
this.sitemapVariables = this.extractVariables(this.sitemap);
|
||||
// Collect the operations from each route
|
||||
this.operations = [];
|
||||
$tw.utils.each(this.sitemap.fields.list,function(routeTitle) {
|
||||
var routeTiddler = self.publisherHandler.wiki.getTiddler(routeTitle);
|
||||
if(routeTiddler) {
|
||||
Array.prototype.push.apply(self.operations,self.getOperationsForRoute(routeTiddler));
|
||||
}
|
||||
});
|
||||
// Display the progress modal
|
||||
if($tw.modal) {
|
||||
self.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("Missing sitemap");
|
||||
}
|
||||
} else {
|
||||
return callback("Unrecognised publisher");
|
||||
}
|
||||
} else {
|
||||
return callback("Missing or disabled job tiddler");
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Instantiate the required publisher object
|
||||
*/
|
||||
PublisherHandler.prototype.getPublisher = function() {
|
||||
var self = this,
|
||||
publisher;
|
||||
PublishingJob.prototype.getPublisher = function(publisherName) {
|
||||
var publisher;
|
||||
$tw.modules.forEachModuleOfType("publisher",function(title,module) {
|
||||
if(module.name === self.publisherName) {
|
||||
if(module.name === publisherName) {
|
||||
publisher = module;
|
||||
}
|
||||
});
|
||||
return publisher && publisher.create(this.publisherParams);
|
||||
return publisher && publisher.create(this.jobTiddler.fields,this.publisherHandler,this);
|
||||
};
|
||||
|
||||
/*
|
||||
Expand publish routes to separate commands
|
||||
Extract the variables from tiddler fields prefixed "var-"
|
||||
*/
|
||||
PublisherHandler.prototype.expandRoutes = function(routes) {
|
||||
PublishingJob.prototype.extractVariables = function(tiddler) {
|
||||
var variables = {};
|
||||
$tw.utils.each(tiddler.getFieldStrings(),function(value,name) {
|
||||
if(name.substring(0,4) === "var-") {
|
||||
variables[name.substring(4)] = value;
|
||||
}
|
||||
});
|
||||
return variables;
|
||||
};
|
||||
|
||||
/*
|
||||
Expand publish routes to separate operations
|
||||
*/
|
||||
PublishingJob.prototype.getOperationsForRoute = function(routeTiddler) {
|
||||
var self = this,
|
||||
commands = [];
|
||||
$tw.utils.each(routes,function(route) {
|
||||
var filter = route.filter || "DUMMY_RESULT"; // If no filter is provided, use a dummy filter that returns a single result
|
||||
switch(route["route-type"]) {
|
||||
operations = [],
|
||||
routeFilter = routeTiddler.fields["route-tiddler-filter"] || "DUMMY_RESULT", // If no filter is provided, use a dummy filter that returns a single result
|
||||
tiddlers = self.publisherHandler.wiki.filterTiddlers(routeFilter,null,self.publisherHandler.wiki.makeTiddlerIterator(this.exportList));
|
||||
if(routeFilter) {
|
||||
switch(routeTiddler.fields["route-type"]) {
|
||||
case "save":
|
||||
if(filter && route.path) {
|
||||
$tw.utils.each(self.wiki.filterTiddlers(filter),function(title) {
|
||||
commands.push({
|
||||
if(routeTiddler.fields["route-path-filter"]) {
|
||||
$tw.utils.each(tiddlers,function(title) {
|
||||
operations.push({
|
||||
"route-type": "save",
|
||||
path: self.resolveParameterisedPath(route.path,title),
|
||||
path: self.resolvePathFilter(routeTiddler.fields["route-path-filter"],title),
|
||||
title: title
|
||||
});
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "render":
|
||||
if(filter && route.path && route.template) {
|
||||
$tw.utils.each(self.wiki.filterTiddlers(filter),function(title) {
|
||||
commands.push({
|
||||
if(routeTiddler.fields["route-path-filter"] && routeTiddler.fields["route-template"]) {
|
||||
var routeVariables = $tw.utils.extend({},this.publishVariables,this.jobVariables,this.sitemapVariables,this.extractVariables(routeTiddler));
|
||||
$tw.utils.each(tiddlers,function(title) {
|
||||
operations.push({
|
||||
"route-type": "render",
|
||||
path: self.resolveParameterisedPath(route.path,title),
|
||||
path: self.resolvePathFilter(routeTiddler.fields["route-path-filter"],title),
|
||||
title: title,
|
||||
template: route.template
|
||||
template: routeTiddler.fields["route-template"],
|
||||
variables: routeVariables
|
||||
});
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
return commands;
|
||||
}
|
||||
return operations;
|
||||
};
|
||||
|
||||
/*
|
||||
Apply a tiddler to a parameterised path to create a usable path
|
||||
Apply a tiddler to a filter to create a usable path
|
||||
*/
|
||||
PublisherHandler.prototype.resolveParameterisedPath = function(route,title) {
|
||||
var self = this;
|
||||
// Split the route on $$ markers
|
||||
var tiddler = this.wiki.getTiddler(title),
|
||||
output = [];
|
||||
$tw.utils.each(route.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;
|
||||
PublishingJob.prototype.resolvePathFilter = function(pathFilter,title) {
|
||||
var tiddler = this.publisherHandler.wiki.getTiddler(title);
|
||||
return this.publisherHandler.wiki.filterTiddlers(pathFilter,{
|
||||
getVariable: function(name) {
|
||||
switch(name) {
|
||||
case "currentTiddler":
|
||||
return "" + this.imageSource;
|
||||
case "extension":
|
||||
value = ($tw.config.contentTypeInfo[value] || {extension: "."}).extension.slice(1);
|
||||
break;
|
||||
return "" + ($tw.config.contentTypeInfo[tiddler.fields.type || "text/vnd.tiddlywiki"] || {extension: ""}).extension;
|
||||
default:
|
||||
return $tw.rootWidget.getVariable(name);
|
||||
}
|
||||
output.push(value);
|
||||
} else {
|
||||
output.push(part);
|
||||
}
|
||||
});
|
||||
return output.join("");
|
||||
},this.publisherHandler.wiki.makeTiddlerIterator([title]))[0];
|
||||
};
|
||||
|
||||
/*
|
||||
Publish the routes in this.routes[]
|
||||
Execute the operations for this job
|
||||
*/
|
||||
PublisherHandler.prototype.publish = function(callback) {
|
||||
PublishingJob.prototype.executeOperations = function(callback) {
|
||||
var self = this,
|
||||
report = {overwrites: []},
|
||||
commands = this.expandRoutes(this.routes),
|
||||
nextCommand = 0,
|
||||
publisher = this.getPublisher(),
|
||||
performNextCommand = function() {
|
||||
// Set progress
|
||||
self.setProgress(nextCommand,commands.length);
|
||||
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(nextCommand >= commands.length) {
|
||||
publisher.publishEnd(function() {
|
||||
self.saveReport(report);
|
||||
self.reset();
|
||||
self.hideProgress();
|
||||
if(callback) {
|
||||
$tw.utils.nextTick(callback);
|
||||
}
|
||||
if(nextOperation >= self.operations.length) {
|
||||
$tw.utils.nextTick(function() {
|
||||
self.publisher.publishEnd(callback);
|
||||
});
|
||||
} else {
|
||||
// Execute this command
|
||||
var fileDetails = self.prepareCommand(commands[nextCommand]);
|
||||
nextCommand += 1;
|
||||
publisher.publishFile(fileDetails,function() {
|
||||
$tw.utils.nextTick(performNextCommand);
|
||||
// Execute this operation
|
||||
var fileDetails = self.prepareOperation(self.operations[nextOperation]);
|
||||
nextOperation += 1;
|
||||
self.publisher.publishFile(fileDetails,function() {
|
||||
$tw.utils.nextTick(performNextOperation);
|
||||
});
|
||||
}
|
||||
};
|
||||
// Fail if we didn't get a publisher
|
||||
if(!publisher) {
|
||||
alert("Publisher " + this.publisherName + " not found");
|
||||
return;
|
||||
}
|
||||
this.displayProgress("Publishing");
|
||||
// Tell the publisher to start, and get back an array of the existing paths
|
||||
publisher.publishStart(function(existingPaths) {
|
||||
self.publisher.publishStart(function(existingPaths) {
|
||||
var paths = {};
|
||||
$tw.utils.each(commands,function(command) {
|
||||
if(command.path in paths) {
|
||||
report.overwrites.push(command.path);
|
||||
$tw.utils.each(self.operations,function(operation) {
|
||||
if(operation.path in paths) {
|
||||
report.overwrites.push(operation.path);
|
||||
}
|
||||
paths[command.path] = true;
|
||||
paths[operation.path] = true;
|
||||
});
|
||||
// Run the commands
|
||||
$tw.utils.nextTick(performNextCommand);
|
||||
// Run the operations
|
||||
performNextOperation();
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
Construct a file details object from a command object
|
||||
Construct a file details object from an operation object
|
||||
*/
|
||||
PublisherHandler.prototype.prepareCommand = function(command) {
|
||||
var tiddler = this.wiki.getTiddler(command.title),
|
||||
PublishingJob.prototype.prepareOperation = function(operation) {
|
||||
var tiddler = this.publisherHandler.wiki.getTiddler(operation.title),
|
||||
fileDetails = {
|
||||
path: command.path
|
||||
};
|
||||
switch(command["route-type"]) {
|
||||
path: operation.path
|
||||
};
|
||||
switch(operation["route-type"]) {
|
||||
case "save":
|
||||
fileDetails.text = tiddler.fields.text || "";
|
||||
fileDetails.type = tiddler.fields.type || "";
|
||||
fileDetails.isBase64 = ($tw.config.contentTypeInfo[tiddler.fields.type] || {}).encoding === "base64";
|
||||
break;
|
||||
case "render":
|
||||
fileDetails.text = this.wiki.renderTiddler("text/plain",command.template,{variables: {currentTiddler: command.title}});
|
||||
fileDetails.text = this.publisherHandler.wiki.renderTiddler("text/plain",operation.template,{
|
||||
variables: $tw.utils.extend(
|
||||
{currentTiddler: operation.title},
|
||||
operation.variables
|
||||
)
|
||||
});
|
||||
fileDetails.type = "text/html";
|
||||
break;
|
||||
}
|
||||
return fileDetails;
|
||||
};
|
||||
|
||||
/*
|
||||
*/
|
||||
PublisherHandler.prototype.reset = function() {
|
||||
this.publisherName = null;
|
||||
this.publisherParams = null;
|
||||
this.routes = null;
|
||||
};
|
||||
|
||||
|
||||
PublisherHandler.prototype.saveReport = function(report) {
|
||||
PublishingJob.prototype.saveReport = function(report) {
|
||||
// Create the report tiddler
|
||||
var reportTitle = this.wiki.generateNewTitle("$:/temp/publish-report");
|
||||
$tw.wiki.addTiddler({
|
||||
@ -242,45 +285,6 @@ PublisherHandler.prototype.saveReport = function(report) {
|
||||
$tw.wiki.addTiddler(new $tw.Tiddler(paramsTiddler,{list: list}));
|
||||
};
|
||||
|
||||
PublisherHandler.prototype.displayProgress = function(message) {
|
||||
if($tw.browser) {
|
||||
this.progressWrapper = document.createElement("div");
|
||||
this.progressWrapper.className = "tc-progress-bar-wrapper";
|
||||
this.progressText = document.createElement("div");
|
||||
this.progressText.className = "tc-progress-bar-text";
|
||||
this.progressText.appendChild(document.createTextNode(message));
|
||||
this.progressWrapper.appendChild(this.progressText);
|
||||
this.progressBar = document.createElement("div");
|
||||
this.progressBar.className = "tc-progress-bar";
|
||||
this.progressWrapper.appendChild(this.progressBar);
|
||||
this.progressPercent = document.createElement("div");
|
||||
this.progressPercent.className = "tc-progress-bar-percent";
|
||||
this.progressWrapper.appendChild(this.progressPercent);
|
||||
document.body.appendChild(this.progressWrapper);
|
||||
}
|
||||
};
|
||||
|
||||
PublisherHandler.prototype.hideProgress = function() {
|
||||
if($tw.browser && this.progressWrapper) {
|
||||
this.progressWrapper.parentNode.removeChild(this.progressWrapper);
|
||||
this.progressWrapper = null;
|
||||
}
|
||||
};
|
||||
|
||||
PublisherHandler.prototype.setProgress = function(numerator,denominator) {
|
||||
if($tw.browser && this.progressWrapper) {
|
||||
// Remove old progress
|
||||
while(this.progressPercent.hasChildNodes()) {
|
||||
this.progressPercent.removeChild(this.progressPercent.firstChild);
|
||||
}
|
||||
// Set new text
|
||||
var percent = (numerator * 100 /denominator).toFixed(2) + "%";
|
||||
this.progressPercent.appendChild(document.createTextNode(percent));
|
||||
// Set bar width
|
||||
this.progressBar.style.width = percent;
|
||||
}
|
||||
};
|
||||
|
||||
exports.PublisherHandler = PublisherHandler;
|
||||
|
||||
})();
|
||||
|
52
core/modules/publishers/filesystem.js
Normal file
52
core/modules/publishers/filesystem.js
Normal 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);
|
||||
};
|
||||
|
||||
})();
|
||||
|
@ -72,10 +72,11 @@ exports.startup = function() {
|
||||
}
|
||||
});
|
||||
}
|
||||
// Install the publisher handler
|
||||
$tw.publisherHandler = new $tw.PublisherHandler({
|
||||
wiki: $tw.wiki,
|
||||
widget: $tw.rootWidget
|
||||
// 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:") {
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -43,6 +43,7 @@ ImageWidget.prototype = new Widget();
|
||||
Render this widget into the DOM
|
||||
*/
|
||||
ImageWidget.prototype.render = function(parent,nextSibling) {
|
||||
var self = this;
|
||||
this.parentDomNode = parent;
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
@ -58,9 +59,24 @@ ImageWidget.prototype.render = function(parent,nextSibling) {
|
||||
if(this.wiki.isImageTiddler(this.imageSource)) {
|
||||
var type = tiddler.fields.type,
|
||||
text = tiddler.fields.text,
|
||||
_canonical_uri = tiddler.fields._canonical_uri;
|
||||
_canonical_uri = tiddler.fields._canonical_uri,
|
||||
imageTemplateFilter = this.getVariable("tv-image-template-filter");
|
||||
// If present, use var-tv-image-template-filter to generate a URL;
|
||||
if(imageTemplateFilter) {
|
||||
src = this.wiki.filterTiddlers(imageTemplateFilter,{
|
||||
getVariable: function(name) {
|
||||
switch(name) {
|
||||
case "currentTiddler":
|
||||
return "" + this.imageSource;
|
||||
case "extension":
|
||||
return "" + ($tw.config.contentTypeInfo[type] || {extension: ""}).extension;
|
||||
default:
|
||||
return self.getVariable(name);
|
||||
}
|
||||
}
|
||||
},this.wiki.makeTiddlerIterator([this.imageSource]))[0];
|
||||
// If the tiddler has body text then it doesn't need to be lazily loaded
|
||||
if(text) {
|
||||
} else if(text) {
|
||||
// Render the appropriate element for the image type
|
||||
switch(type) {
|
||||
case "application/pdf":
|
||||
|
@ -6,7 +6,7 @@ caption: {{$:/language/ControlPanel/Publishing/Caption}}
|
||||
|
||||
<div class="tc-publishing-job-listing">
|
||||
|
||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/Publish/Jobs]]" variable="job">
|
||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/PublishingJob]]" variable="job">
|
||||
|
||||
<div class="tc-publishing-job">
|
||||
|
||||
@ -42,21 +42,25 @@ Logs: <$view tiddler=<<job>> field="list"/>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="tc-publishing-job-routes">
|
||||
<$vars sitemap={{{ [<job>get[sitemap]] }}}>
|
||||
|
||||
<$list filter="[all[shadows+tiddlers]tag<job>]" variable="route">
|
||||
<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"/></$link></h2>
|
||||
<h2>Route: <$link to=<<route>>><$view tiddler=<<route>> field="caption"><$text text=<<route>>/></$view></$link></h2>
|
||||
|
||||
job-type: <$view tiddler=<<route>> field="job-type"/>
|
||||
job-type: <$view tiddler=<<route>> field="route-type"/>
|
||||
|
||||
path: <$edit-text tiddler=<<route>> size="50" field="path"/>
|
||||
path: <$edit-text tiddler=<<route>> size="50" field="route-path-filter"/>
|
||||
|
||||
filter: <$edit-text tiddler=<<route>> size="50" field="filter"/>
|
||||
filter: <$edit-text tiddler=<<route>> size="50" field="route-tiddler-filter"/>
|
||||
|
||||
template: <$edit-text tiddler=<<route>> size="50" field="template"/>
|
||||
template: <$edit-text tiddler=<<route>> size="50" field="route-template"/>
|
||||
|
||||
</div>
|
||||
|
||||
@ -64,6 +68,8 @@ template: <$edit-text tiddler=<<route>> size="50" field="template"/>
|
||||
|
||||
</div>
|
||||
|
||||
</$vars>
|
||||
|
||||
</$set>
|
||||
|
||||
</$set>
|
||||
|
@ -4,20 +4,8 @@ caption: {{$:/core/images/publish}} {{$:/language/Buttons/Publish/Caption}}
|
||||
description: {{$:/language/Buttons/Publish/Hint}}
|
||||
|
||||
\define publish-actions()
|
||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/Publish/Jobs]]" variable="job">
|
||||
<$set name="publisher-name" value={{{ [<job>get[publisher]] }}}>
|
||||
<$action-sendmessage $message="tm-publish-start" publisher-name=<<publisher-name>> publish-params-title=<<job>>/>
|
||||
<$list filter="[all[shadows+tiddlers]tag<job>]" variable="route">
|
||||
<$action-sendmessage $message="tm-publish-route"
|
||||
caption={{{ [<route>get[caption]] }}}
|
||||
route-type={{{ [<route>get[job-type]] }}}
|
||||
path={{{ [<route>get[path]] }}}
|
||||
filter={{{ [<route>get[filter]] }}}
|
||||
template={{{ [<route>get[template]] }}}
|
||||
/>
|
||||
</$list>
|
||||
<$action-sendmessage $message="tm-publish-end"/>
|
||||
</$set>
|
||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/PublishingJob]has[enabled]field:enabled[yes]]" variable="job">
|
||||
<$action-sendmessage $message="tm-publish" job=<<job>>/>
|
||||
</$list>
|
||||
\end
|
||||
|
||||
|
9
core/wiki/publishing-jobs/Default.tid
Normal file
9
core/wiki/publishing-jobs/Default.tid
Normal 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]prefix[T]] [is[image]!is[system]]
|
7
core/wiki/routes/StaticSite/HTML.tid
Normal file
7
core/wiki/routes/StaticSite/HTML.tid
Normal file
@ -0,0 +1,7 @@
|
||||
title: $:/core/routes/StaticSite/HTML
|
||||
caption: Static HTML
|
||||
tags: $:/tags/Route
|
||||
route-type: render
|
||||
route-path-filter: [addprefix[static/]addsuffix[.html]]
|
||||
route-tiddler-filter: [!is[system]!is[image]]
|
||||
route-template: $:/core/templates/static.tiddler.html
|
@ -1,6 +1,6 @@
|
||||
title: $:/core/publishing-jobs/Static/Images
|
||||
title: $:/core/routes/StaticSite/Images
|
||||
caption: Images
|
||||
tags: $:/core/publishing-jobs/Static/Job
|
||||
job-type: save
|
||||
path: images/$title_slugify$.$type_extension$
|
||||
filter: [is[image]]
|
||||
tags: $:/tags/Route
|
||||
route-type: save
|
||||
route-path-filter: [slugify[]addprefix[images/]addsuffix<extension>]
|
||||
route-tiddler-filter: [is[image]]
|
||||
|
@ -1,6 +1,6 @@
|
||||
title: $:/core/publishing-jobs/Static/Index
|
||||
title: $:/core/routes/StaticSite/Index
|
||||
caption: Index
|
||||
tags: $:/core/publishing-jobs/Static/Job
|
||||
job-type: render
|
||||
path: index.html
|
||||
template: $:/core/save/all
|
||||
tags: $:/tags/Route
|
||||
route-type: render
|
||||
route-path-filter: index.html
|
||||
route-template: $:/core/save/all
|
||||
|
@ -1,6 +0,0 @@
|
||||
title: $:/core/publishing-jobs/Static/Job
|
||||
tags: $:/tags/Publish/Jobs
|
||||
caption: Demo Static Site
|
||||
publisher: jszip
|
||||
jszip-output-filename: myzipfile.zip
|
||||
baseurl: https://example.com
|
@ -1,9 +0,0 @@
|
||||
title: $:/core/publishing-jobs/Static/StaticHTML
|
||||
caption: Static HTML
|
||||
tags: $:/core/publishing-jobs/Static/Job
|
||||
job-type: render
|
||||
path: static/$title_slugify$.html
|
||||
filter: [!is[system]!is[image]]
|
||||
template: $:/core/templates/static.tiddler.html
|
||||
tv-wikilink-template: $title_slugify$.html
|
||||
tv-image-template: images/$title_slugify$.$type_extension$
|
@ -1,6 +1,6 @@
|
||||
title: $:/core/publishing-jobs/Static/Styles
|
||||
title: $:/core/routes/StaticSite/Styles
|
||||
caption: Styles
|
||||
tags: $:/core/publishing-jobs/Static/Job
|
||||
job-type: render
|
||||
path: static/static.css
|
||||
template: $:/core/templates/static.template.css
|
||||
tags: $:/tags/Route
|
||||
route-type: render
|
||||
route-path-filter: static/static.css
|
||||
route-template: $:/core/templates/static.template.css
|
||||
|
7
core/wiki/sitemaps/StaticSite.tid
Normal file
7
core/wiki/sitemaps/StaticSite.tid
Normal file
@ -0,0 +1,7 @@
|
||||
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
|
||||
var-tv-wikilink-template-filter: [slugify[]addsuffix[.html]]
|
||||
var-tv-image-template-filter: [slugify[]addprefix[../images/]addsuffix<extension>]
|
@ -12,23 +12,38 @@ TiddlyWiki has pluggable ''publisher modules'' that provide the means to publish
|
||||
* The JSZip publisher packs the output files in a ZIP file which is automatically downloaded by the browser
|
||||
* The GitHub publisher uploads the output files to a GitHub repository suitable for GitHub Pages
|
||||
|
||||
A ''job'' defines a self-contained group of files to be published, and specifies the ''publisher module'' to be used along with any parameters.
|
||||
A ''publishing job'' describes a self-contained publishing operation. Jobs are defined as configuration tiddlers with the following fields:
|
||||
|
||||
Jobs consist of one or more ''routes'' which each define a related set of files that are to be treated in the same way.
|
||||
* ''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
|
||||
* ''<publisher-name>-<parameter-name>'' -- parameters required by the publisher module
|
||||
* ''sitemap'' -- title of the site map tiddler to be used
|
||||
* ''var-<variable-name>'' -- custom variables to be provided to the output templates
|
||||
|
||||
Routes consist of the following information:
|
||||
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:
|
||||
|
||||
* A filter defining the tiddlers included in the route
|
||||
* A ''parameterised path'' defining how the ''output path'' is derived from the field values of a particular tiddler
|
||||
* A ''route type'' which can be set to "save" or "render":
|
||||
* ''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
|
||||
* ''var-<variable-name>'' -- custom variables to be provided to the output templates
|
||||
|
||||
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-filter'' - a filter 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 "save" or "render":
|
||||
** ''"save"'' 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''
|
||||
** ''"render"'' indicates that the tiddler is to be rendered through a specified template
|
||||
* ''var-<variable-name>'' -- custom variables to be provided to the output template
|
||||
|
||||
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".
|
||||
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.
|
||||
|
@ -30,7 +30,7 @@ exports.create = function(params) {
|
||||
return new JSZipPublisher(params);
|
||||
};
|
||||
|
||||
function JSZipPublisher(params) {
|
||||
function JSZipPublisher(params,publisherHandler,publishingJob,) {
|
||||
this.params = params;
|
||||
this.zip = new JSZip();
|
||||
console.log("JSZipPublisher",params);
|
||||
@ -38,12 +38,13 @@ function 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);
|
||||
callback();
|
||||
this.zip.file(item.path,item.text,{base64: item.isBase64});
|
||||
callback(null);
|
||||
};
|
||||
|
||||
JSZipPublisher.prototype.publishEnd = function(callback) {
|
||||
@ -54,7 +55,7 @@ JSZipPublisher.prototype.publishEnd = function(callback) {
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
callback();
|
||||
callback(null);
|
||||
};
|
||||
|
||||
})();
|
||||
|
@ -1819,6 +1819,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;
|
||||
@ -2934,7 +2948,7 @@ Publishing UI
|
||||
background-color: #ffdddd;
|
||||
}
|
||||
|
||||
.tc-publishing-job-routes {
|
||||
.tc-publishing-sitemap {
|
||||
margin: 0.25em;
|
||||
padding: 0.25em;
|
||||
border: 1px solid black;
|
||||
@ -2948,31 +2962,6 @@ Publishing UI
|
||||
background-color: #ddffdd;
|
||||
}
|
||||
|
||||
/*
|
||||
Progress bar
|
||||
*/
|
||||
|
||||
.tc-progress-bar-wrapper {
|
||||
position: fixed;
|
||||
width: 50%;
|
||||
height: 50px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -25px;
|
||||
margin-left: -25%;
|
||||
background: #ffffee;
|
||||
}
|
||||
|
||||
.tc-progress-bar-text,
|
||||
.tc-progress-bar-percent {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tc-progress-bar {
|
||||
height: 10px;
|
||||
background: red;
|
||||
}
|
||||
|
||||
/*
|
||||
** Utility classes for SVG icons
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user