Add Node.js support, refactor modelling of jobs and sitemaps, and use modals framework

publishing-framework
jeremy@jermolene.com 2021-04-04 13:18:41 +01:00
rodzic 42e10d030a
commit 509356c696
21 zmienionych plików z 451 dodań i 282 usunięć

Wyświetl plik

@ -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.

Wyświetl plik

@ -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;
})();

Wyświetl plik

@ -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;
})();

Wyświetl plik

@ -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);
};
})();

Wyświetl plik

@ -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:") {

Wyświetl plik

@ -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

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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":

Wyświetl plik

@ -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>

Wyświetl plik

@ -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

Wyświetl plik

@ -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]]

Wyświetl plik

@ -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

Wyświetl plik

@ -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]]

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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$

Wyświetl plik

@ -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

Wyświetl plik

@ -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>]

Wyświetl plik

@ -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
* ''&lt;publisher-name&gt;-&lt;parameter-name&gt;'' -- parameters required by the publisher module
* ''sitemap'' -- title of the site map tiddler to be used
* ''var-&lt;variable-name&gt;'' -- 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-&lt;variable-name&gt;'' -- 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-&lt;variable-name&gt;'' -- 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.

Wyświetl plik

@ -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);
};
})();

Wyświetl plik

@ -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
*/