1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-01-23 11:24:40 +00:00

Compare commits

..

29 Commits

Author SHA1 Message Date
jeremy@jermolene.com
75c1e4b4d8 Fix typo 2021-10-27 12:46:25 +01:00
Jeremy Ruston
979f079c7a Merge branch 'master' into external-tasks 2021-10-27 12:27:35 +01:00
jeremy@jermolene.com
894fb1ad35 Fix merging error 2021-03-30 21:28:48 +01:00
jeremy@jermolene.com
9a3c60173a Merge branch 'master' into external-tasks 2021-03-30 21:26:01 +01:00
Jeremy Ruston
2b4619dcbe Merge branch 'master' into external-tasks 2019-10-22 21:52:41 +01:00
Jeremy Ruston
bb20d43cbf Merge branch 'master' into external-tasks 2019-09-24 13:23:42 +01:00
Jeremy Ruston
9c29b15fcd Merge branch 'master' into external-tasks 2019-08-06 21:16:52 +01:00
Jermolene
994a91001d Merge branch 'master' into external-tasks 2019-01-06 17:33:20 +00:00
Jermolene
c85c172fe4 Merge branch 'master' into external-tasks 2018-10-24 10:33:34 +01:00
Jermolene
eeb06a75a6 Add new tagger demo 2018-10-24 10:31:58 +01:00
Jermolene
ff9539059a Refactor pipe command
To make it easier to handle state, and enable us to add support for the "incomingTitle" parameter
2018-10-24 10:31:42 +01:00
Jermolene
5831df9ece Docs updates 2018-10-24 10:30:41 +01:00
Jermolene
b7f758bbbe Docs improvements 2018-10-19 17:56:37 +01:00
Jermolene
36509509bc Decrease server polling interval
5 * 1000 millseconds, instead of the default 60 * 1000
2018-10-19 17:25:41 +01:00
Jermolene
e83d0fb6f5 Merge branch 'master' into external-tasks 2018-10-19 16:32:53 +01:00
Jermolene
a65e9a7b42 @joearm's fixes 2018-10-19 16:29:45 +01:00
Jermolene
9b912f6d65 Update socket-erlang example 2018-10-19 10:18:08 +01:00
Jermolene
4b45afc11f Improvements to sample text 2018-10-19 10:17:38 +01:00
Jermolene
e714693cfe Preliminary support for length+type+message 2018-10-18 17:15:59 +01:00
Jermolene
dffd4e56b5 Bring "Alice in Wonderland" properly into the wiki
We were referencing it in the tw5.com edition, but that makes it harder to use the externalpipesdemo edition independently.
2018-10-18 14:21:59 +01:00
Jermolene
a0b3e1a564 Docs improvements 2018-10-17 21:27:46 +01:00
Jermolene
4efd5288d0 Merge branch 'master' into external-tasks 2018-10-14 18:35:11 +01:00
Jermolene
a2d3778465 Improve demo 2018-10-14 18:35:07 +01:00
Jermolene
f2917c3355 Add support for remote commands
Makes it possible to invoke any of TW5's Node.js commands from the browser. Useful for the new pipe command, but also handy for e.g. rendering tiddlers on the server, fetching remote files, or importing local files etc

Docs to come.
2018-10-14 18:34:55 +01:00
Jermolene
4fcbaa2b12 Mimic - don't crash if there's no source text 2018-10-14 18:32:31 +01:00
Jermolene
d29b9c2726 Merge branch 'master' into external-tasks 2018-10-13 17:25:05 +01:00
Jermolene
97bb1fa8c9 Rename external tasks to external pipes
And add support for connecting via sockets to existing processers/tasks
2018-10-13 14:50:21 +01:00
Jermolene
2e5035ec2a Merge branch 'master' into external-tasks 2018-10-09 21:31:22 +01:00
Jermolene
b292985df3 Initial commit 2018-10-01 19:44:48 +01:00
144 changed files with 5649 additions and 1294 deletions

View File

@@ -27,15 +27,10 @@ Basics/Tiddlers/Prompt: Number of tiddlers
Basics/Title/Prompt: Title of this ~TiddlyWiki
Basics/Username/Prompt: Username for signing edits
Basics/Version/Prompt: ~TiddlyWiki version
Cascades/Caption: Cascades
Cascades/Hint: These global rules are used to dynamically choose certain templates. The result of the cascade is the result of the first filter in the sequence that returns a result
Cascades/TagPrompt: Filters tagged <$macrocall $name="tag" tag=<<currentTiddler>>/>
EditorTypes/Caption: Editor Types
EditorTypes/Editor/Caption: Editor
EditorTypes/Hint: These tiddlers determine which editor is used to edit specific tiddler types.
EditorTypes/Type/Caption: Type
EditTemplateBody/Caption: Edit Template Body
EditTemplateBody/Hint: This rule cascade is used by the default edit template to dynamically choose the template for editing the body of a tiddler.
Info/Caption: Info
Info/Hint: Information about this TiddlyWiki
KeyboardShortcuts/Add/Prompt: Type shortcut here
@@ -196,8 +191,6 @@ Settings/TitleLinks/Yes/Description: Display tiddler titles as links
Settings/MissingLinks/Caption: Wiki Links
Settings/MissingLinks/Hint: Choose whether to link to tiddlers that do not exist yet
Settings/MissingLinks/Description: Enable links to missing tiddlers
StoryTiddler/Caption: Story Tiddler
StoryTiddler/Hint: This rule cascade is used to dynamically choose the template for displaying a tiddler in the story river.
StoryView/Caption: Story View
StoryView/Prompt: Current view:
Stylesheets/Caption: Stylesheets
@@ -208,8 +201,6 @@ Theme/Caption: Theme
Theme/Prompt: Current theme:
TiddlerFields/Caption: Tiddler Fields
TiddlerFields/Hint: This is the full set of TiddlerFields in use in this wiki (including system tiddlers but excluding shadow tiddlers).
TiddlerIcon/Caption: Tiddler Icon
TiddlerIcon/Hint: This rules cascade is used to dynamically choose the icon for a tiddler.
Toolbars/Caption: Toolbars
Toolbars/EditToolbar/Caption: Edit Toolbar
Toolbars/EditToolbar/Hint: Choose which buttons are displayed for tiddlers in edit mode. Drag and drop to change the ordering
@@ -221,7 +212,3 @@ Toolbars/EditorToolbar/Hint: Choose which buttons are displayed in the editor to
Toolbars/ViewToolbar/Caption: View Toolbar
Toolbars/ViewToolbar/Hint: Choose which buttons are displayed for tiddlers in view mode. Drag and drop to change the ordering
Tools/Download/Full/Caption: Download full wiki
ViewTemplateBody/Caption: View Template Body
ViewTemplateBody/Hint: This rule cascade is used by the default view template to dynamically choose the template for displaying the body of a tiddler.
ViewTemplateTitle/Caption: View Template Title
ViewTemplateTitle/Hint: This rule cascade is used by the default view template to dynamically choose the template for displaying the title of a tiddler.

View File

@@ -0,0 +1,13 @@
title: $:/language/Help/pipe
description: Pipe data to/from external processes
Pipe data to/from external processes.
```
--pipe <pipename> <filter> <incomingTitle> <arguments>...
```
* ''pipename'': identifies the pipe, matching the name defined in the `tiddlywiki.info` file
* ''filter'': identifies the tiddlers to be passed to the pipe
* ''incomingTitle'': provides a title to be applied to incoming tiddlers for the `raw-text` output format
* ''arguments'': passed through to external task pipes, and ignored for other types of pipe

View File

@@ -0,0 +1,278 @@
/*\
title: $:/core/modules/commands/pipe.js
type: application/javascript
module-type: command
Command to execute an external task
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.info = {
name: "pipe",
synchronous: false
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this;
if(this.params.length < 2) {
return "Missing parameters";
}
var name = self.params[0], // External pipe name
outgoingFilter = self.params[1], // Filter of tiddlers to write to the pipe
incomingTitle = self.params[2],
args = self.params.slice(3); // Remaining arguments are passed on as tasks arguments
// Find the pipe information
var pipeInfo = ($tw.boot.wikiInfo["external-pipes"] || {})[name];
if(!pipeInfo) {
return this.callback("External pipe \"" + name + "\" not found");
}
// Create the pipe instance and process a message
var pipe = new Pipe({
name: name,
pipeInfo: pipeInfo,
outgoingFilter: outgoingFilter,
incomingTitle: incomingTitle,
args: args,
command: this
});
pipe.processMessage(this.callback);
};
function Pipe(options) {
this.name = options.name;
this.pipeInfo = options.pipeInfo;
this.outgoingFilter = options.outgoingFilter;
this.incomingTitle = options.incomingTitle;
this.args = options.args;
this.command = options.command;
}
Pipe.prototype.processMessage = function(callback) {
// Get the outgoing data
var data = this.composeOutgoingData(this.outgoingFilter);
// Connect to the pipe
switch(this.pipeInfo.type) {
case "task":
this.pipeExternalTask(data,callback);
break;
case "socket":
this.pipeSocket(data,callback);
break;
case "socket-erlang":
this.pipeSocketErlang(data,callback);
break;
default:
callback("Invalid pipe specifier '" + this.name + "': " + this.pipeInfo.type);
break;
}
};
Pipe.prototype.log = function(args) {
this.command.commander.log("Pipe: " + Array.prototype.slice.call(arguments,0).join(" "));
};
Pipe.prototype.pipeExternalTask = function(data,callback) {
var self = this,
spawn = require("child_process").spawn,
path = require("path"),
childProcess = spawn(path.resolve($tw.boot.wikiPath,this.pipeInfo.path),this.args,{
stdio: ["pipe","pipe",process.stderr],
shell: true,
env: $tw.utils.extend({},process.env,this.pipeInfo.environment)
});
// Pass the tiddlers over the outgoing stream
childProcess.stdin.on("error",function(err) {
self.log("Task stdin error",err)
});
childProcess.stdin.write(data);
childProcess.stdin.end();
// Catch the output
var chunks = [];
childProcess.stdout.on("data",function(chunk) {
chunks.push(chunk.toString());
});
childProcess.stdout.on("close",function() {
self.log("Task stdout close");
self.processIncomingData(chunks.join(""));
});
childProcess.stdout.on("error",function(err) {
self.log("Task stdout error",err)
});
// Pick up the output when the process ends
childProcess.on("error",function(err) {
self.log("Task error",err)
});
childProcess.on("exit",function(code,signal) {
self.log("Task exit",code,signal)
if(code !== 0) {
return callback("Error executing external task: " + code);
}
// Exit successfully
callback(null);
});
};
Pipe.prototype.pipeSocket = function(data,callback) {
var self = this,
net = require("net"),
socket = new net.Socket({
allowHalfOpen: true
}),
chunks = [];
socket.connect(this.pipeInfo.port,this.pipeInfo.host || 8081,function() {
self.log("Socket connection",this.pipeInfo.port,this.pipeInfo.host);
socket.write(data);
socket.end();
});
socket.on("error",function(e) {
self.log("Socket error",e)
});
socket.on("data",function(data) {
chunks.push(data.toString());
});
socket.on("end",function() {
self.processIncomingData(chunks.join(""));
self.log("Socket end");
socket.destroy();
});
// Add a "close" event handler for the client socket
socket.on("close",function() {
self.log("Socket closed");
return callback(null);
});
return null;
};
Pipe.prototype.pipeSocketErlang = function(data,callback) {
var self = this,
encoding = this.pipeInfo.encoding || "utf8",
net = require("net"),
socket = new net.Socket(),
accumulator = Buffer.alloc(0);
socket.connect(this.pipeInfo.port,this.pipeInfo.host || 8081,function() {
self.log("Socket connection",self.pipeInfo.port,self.pipeInfo.host);
var dataBytes = Buffer.from(data,encoding);
// Write 32-bit big endian message length
var lengthBytes = Buffer.alloc(4);
lengthBytes.writeUInt32BE(dataBytes.length + 1,0)
console.log("Writing bytes",dataBytes.length + 1);
socket.write(lengthBytes);
// Write 8-bit type
var typeByte = Buffer.alloc(1);
typeByte.writeUInt8(1,0);
socket.write(typeByte);
// Write data
socket.write(dataBytes);
});
socket.on("error",function(e) {
self.log("Socket error",e)
});
socket.on("data",function(data) {
console.log("Received data",data.length)
accumulator = Buffer.concat([accumulator,data]);
while(accumulator.length > 4) {
var length = accumulator.readInt32BE(0);
if(accumulator.length >= (length + 4)) {
if(length < 1) {
throw "ERROR: Incoming message length field is less than 1";
}
var type = accumulator.readUInt8(4),
dataLength = length - 1,
data = accumulator.toString(encoding,5,dataLength + 5);
console.log("Got message",length,type)
self.processIncomingData(data);
accumulator = accumulator.slice(length + 4);
socket.end();
return callback(null);
} else {
break;
}
}
});
socket.on("end",function() {
self.log("Socket end");
socket.destroy();
});
// Add a "close" event handler for the client socket
socket.on("close",function() {
self.log("Socket closed");
return callback(null);
});
return null;
};
Pipe.prototype.composeOutgoingData = function(outgoingFilter) {
var self = this,
pipeInfoInput = this.pipeInfo.input || {},
data;
switch(pipeInfoInput.format || "json-raw-tiddlers") {
case "rendered-text":
var titles = self.command.commander.wiki.filterTiddlers(outgoingFilter),
output = [];
$tw.utils.each(titles,function(title) {
output.push(self.command.commander.wiki.renderTiddler("text/plain",title));
});
data = output.join("");
break;
case "json-rendered-text-tiddlers":
var titles = self.command.commander.wiki.filterTiddlers(outgoingFilter),
tiddlers = [];
$tw.utils.each(titles,function(title) {
tiddlers.push({
title: title,
text: self.command.commander.wiki.renderTiddler("text/plain",title)
})
});
data = JSON.stringify(tiddlers);
break;
case "json-raw-tiddlers":
// Intentional fall-through
default:
data = this.command.commander.wiki.getTiddlersAsJson(outgoingFilter);
break;
}
return data;
};
Pipe.prototype.processIncomingData = function(data) {
var pipeInfoOutput = this.pipeInfo.output || {},
jsonData;
switch(pipeInfoOutput.format || "text") {
case "json-raw-tiddlers":
try {
jsonData = JSON.parse(data);
} catch(e) {
this.log("Error parsing returned JSON: " + e + "\n\n\n->\n" + data);
}
// Add the tiddlers
if(jsonData) {
this.command.commander.wiki.addTiddlers(jsonData);
}
break;
case "text":
// Intentional fall-through
default:
console.log("Writing tiddler",pipeInfoOutput.tiddler,{
text: data, title: this.incomingTitle
})
this.command.commander.wiki.addTiddler(new $tw.Tiddler(pipeInfoOutput.tiddler,{
text: data, title: this.incomingTitle || pipeInfoOutput.tiddler.title
}));
break;
}
};
exports.Command = Command;
})();

View File

@@ -205,7 +205,7 @@ FramedEngine.prototype.handleInputEvent = function(event) {
this.widget.saveChanges(this.getText());
this.fixHeight();
if(this.widget.editInputActions) {
this.widget.invokeActionString(this.widget.editInputActions,this,event,{actionValue: this.getText()});
this.widget.invokeActionString(this.widget.editInputActions);
}
return true;
};

View File

@@ -133,7 +133,7 @@ SimpleEngine.prototype.handleInputEvent = function(event) {
this.widget.saveChanges(this.getText());
this.fixHeight();
if(this.widget.editInputActions) {
this.widget.invokeActionString(this.widget.editInputActions,this,event,{actionValue: this.getText()});
this.widget.invokeActionString(this.widget.editInputActions);
}
return true;
};

View File

@@ -1,51 +0,0 @@
/*\
title: $:/core/modules/filterrunprefixes/cascade.js
type: application/javascript
module-type: filterrunprefix
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter prefix function
*/
exports.cascade = function(operationSubFunction,options) {
return function(results,source,widget) {
if(results.length !== 0) {
var filterList = operationSubFunction(source,widget),
filterFnList = [];
var inputResults = results.toArray();
results.clear();
$tw.utils.each(inputResults,function(title) {
var result = ""; // If no filter matches, we return an empty string
$tw.utils.each(filterList,function(filter,index) {
if(!filterFnList[index]) {
filterFnList[index] = options.wiki.compileFilter(filter);
}
var output = filterFnList[index](options.wiki.makeTiddlerIterator([title]),{
getVariable: function(name) {
switch(name) {
case "currentTiddler":
return "" + title;
case "..currentTiddler":
return widget.getVariable("currentTiddler");
default:
return widget.getVariable(name);
}
}
});
if(output.length !== 0) {
result = output[0];
return false;
}
});
results.push(result);
});
}
}
};
})();

View File

@@ -15,8 +15,7 @@ Export our filter prefix function
exports.map = function(operationSubFunction,options) {
return function(results,source,widget) {
if(results.length > 0) {
var inputTitles = results.toArray(),
index = 0;
var inputTitles = results.toArray();
results.clear();
$tw.utils.each(inputTitles,function(title) {
var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
@@ -26,19 +25,12 @@ exports.map = function(operationSubFunction,options) {
return "" + title;
case "..currentTiddler":
return widget.getVariable("currentTiddler");
case "index":
return "" + index;
case "revIndex":
return "" + (inputTitles.length - 1 - index);
case "length":
return "" + inputTitles.length;
default:
return widget.getVariable(name);
}
}
});
results.push(filtered[0] || "");
++index;
});
}
}

View File

@@ -103,16 +103,4 @@ exports.nth = function(source,operator,options) {
return results.slice(count - 1,count);
};
/*
The zero based nth member of the list
*/
exports.zth = function(source,operator,options) {
var count = $tw.utils.getInt(operator.operand,0),
results = [];
source(function(tiddler,title) {
results.push(title);
});
return results.slice(count,count + 1);
};
})();

View File

@@ -121,23 +121,21 @@ exports["search-replace"] = function(source,operator,options) {
flagSuffix = (suffixes[0] ? (suffixes[0][0] || "") : ""),
flags = (flagSuffix.indexOf("g") !== -1 ? "g" : "") + (flagSuffix.indexOf("i") !== -1 ? "i" : "") + (flagSuffix.indexOf("m") !== -1 ? "m" : ""),
isRegExp = (suffixes[1] && suffixes[1][0] === "regexp") ? true : false,
//Escape regexp characters if the operand is not a regular expression
searchTerm = isRegExp ? operator.operand : $tw.utils.escapeRegExp(operator.operand),
//Escape $ character in replacement string if not in regular expression mode
replacement = isRegExp ? operator.operands[1] : (operator.operands[1]||"").replace(/\$/g,"$$$$"),
searchTerm,
regExp;
try {
regExp = new RegExp(searchTerm,flags);
} catch(ex) {
return ["RegExp error: " + ex];
}
source(function(tiddler,title) {
if(title && (operator.operands.length > 1)) {
//Escape regexp characters if the operand is not a regular expression
searchTerm = isRegExp ? operator.operand : $tw.utils.escapeRegExp(operator.operand);
try {
regExp = new RegExp(searchTerm,flags);
} catch(ex) {
return ["RegExp error: " + ex];
}
results.push(
title.replace(regExp,replacement)
title.replace(regExp,operator.operands[1])
);
regExp.lastIndex = 0;
} else {
results.push(title);
}

View File

@@ -0,0 +1,55 @@
/*\
title: $:/core/modules/server/routes/post-commands.js
type: application/javascript
module-type: route
POST /commands/
\*/
(function() {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.method = "POST";
exports.path = /^\/commands\/$/;
exports.handler = function(request,response,state) {
// Check we're enabled
if(!($tw.boot.wikiInfo.config || {})["allow-remote-commands"]) {
response.writeHead(404);
response.end();
return;
}
// Get the job descriptor
var jobDescriptor = JSON.parse(state.data);
console.log("JOB START:",jobDescriptor)
// Respond OK
response.writeHead(204, "OK",{
"Content-Type": "application/json"
});
// Maintain status
var setStatus = function(status,message) {
if(jobDescriptor.statusTitle) {
state.wiki.addTiddler(new $tw.Tiddler({title: jobDescriptor.statusTitle,text: status,message: message}));
}
}
setStatus("started");
// Initiate the commands
var commander = new $tw.Commander(
jobDescriptor.commands || [],
function(err) {
setStatus(err ? "error" : "ok",err ? err : undefined);
console.log("JOB END:",err)
},
state.wiki,
{output: process.stdout, error: process.stderr}
);
commander.execute();
// Return results
response.end(JSON.stringify({}),"utf8"); // Nothing useful for us to return
};
}());

View File

@@ -130,6 +130,11 @@ function Syncer(options) {
$tw.rootWidget.addEventListener("tm-server-refresh",function() {
self.handleRefreshEvent();
});
$tw.rootWidget.addEventListener("tm-execute-job",function(event) {
if(self.syncadaptor && self.syncadaptor.executeJob) {
self.syncadaptor.executeJob(event);
}
});
$tw.rootWidget.addEventListener("tm-copy-syncer-logs-to-clipboard",function() {
$tw.utils.copyToClipboard($tw.utils.getSystemInfo() + "\n\nLog:\n" + self.logger.getBuffer());
});

View File

@@ -87,7 +87,7 @@ SendMessageWidget.prototype.invokeAction = function(triggeringWidget,event) {
param: param,
paramObject: paramObject,
event: event,
tiddlerTitle: this.getVariable("currentTiddler"),
currentTiddler: this.getVariable("currentTiddler"),
navigateFromTitle: this.getVariable("storyTiddler")
};
this.dispatchEvent(params);

View File

@@ -52,13 +52,7 @@ CodeBlockWidget.prototype.execute = function() {
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
CodeBlockWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.code || changedAttributes.language) {
this.refreshSelf();
return true;
} else {
return false;
}
return false;
};
exports.codeblock = CodeBlockWidget;

View File

@@ -1,96 +0,0 @@
/*\
title: $:/core/modules/widgets/let.js
type: application/javascript
module-type: widget
This widget allows defining multiple variables at once, while allowing
the later variables to depend upon the earlier ones.
```
\define helloworld() Hello world!
<$let currentTiddler="target" value={{!!value}} currentTiddler="different">
{{!!value}} will be different from <<value>>
</$let>
```
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var LetWidget = function(parseTreeNode,options) {
// Initialise
this.initialise(parseTreeNode,options);
};
/*
Inherit from the base widget class
*/
LetWidget.prototype = new Widget();
/*
Render this widget into the DOM
*/
LetWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
LetWidget.prototype.computeAttributes = function() {
// Before computing attributes, we must make clear that none of the
// existing attributes are staged for lookup, even on a refresh
var changedAttributes = {},
self = this;
this.currentValueFor = Object.create(null);
$tw.utils.each(this.parseTreeNode.orderedAttributes,function(attribute,index) {
var value = self.computeAttribute(attribute),
name = attribute.name;
if(name.charAt(0) !== "$") {
// Now that it's prepped, we're allowed to look this variable up
// when defining later variables
self.currentValueFor[name] = value;
}
});
// Run through again, setting variables and looking for differences
$tw.utils.each(this.currentValueFor,function(value,name) {
if (self.attributes[name] !== value) {
self.attributes[name] = value;
self.setVariable(name,value);
changedAttributes[name] = true;
}
});
return changedAttributes;
};
LetWidget.prototype.getVariableInfo = function(name,options) {
// Special handling: If this variable exists in this very $let, we can
// use it, but only if it's been staged.
if ($tw.utils.hop(this.currentValueFor,name)) {
return {
text: this.currentValueFor[name]
};
}
return Widget.prototype.getVariableInfo.call(this,name,options);
};
/*
Refresh the widget by ensuring our attributes are up to date
*/
LetWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if($tw.utils.count(changedAttributes) > 0) {
this.refreshSelf();
return true;
}
return this.refreshChildren(changedTiddlers);
};
exports["let"] = LetWidget;
})();

View File

@@ -29,12 +29,14 @@ var VarsWidget = function(parseTreeNode,options) {
/*
Inherit from the base widget class
*/
VarsWidget.prototype = new Widget();
VarsWidget.prototype = Object.create(Widget.prototype);
/*
Render this widget into the DOM
*/
VarsWidget.prototype.render = function(parent,nextSibling) {
// Call the constructor
Widget.call(this);
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
@@ -61,7 +63,7 @@ Refresh the widget by ensuring our attributes are up to date
*/
VarsWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if($tw.utils.count(changedAttributes) > 0) {
if(Object.keys(changedAttributes).length) {
this.refreshSelf();
return true;
}

View File

@@ -263,9 +263,19 @@ Compute the current values of the attributes of the widget. Returns a hashmap of
*/
Widget.prototype.computeAttributes = function() {
var changedAttributes = {},
self = this;
self = this,
value;
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
var value = self.computeAttribute(attribute);
if(attribute.type === "filtered") {
value = self.wiki.filterTiddlers(attribute.filter,self)[0] || "";
} else if(attribute.type === "indirect") {
value = self.wiki.getTextReference(attribute.textReference,"",self.getVariable("currentTiddler"));
} else if(attribute.type === "macro") {
value = self.getVariable(attribute.value.name,{params: attribute.value.params});
} else { // String attribute
value = attribute.value;
}
// Check whether the attribute has changed
if(self.attributes[name] !== value) {
self.attributes[name] = value;
changedAttributes[name] = true;
@@ -274,20 +284,6 @@ Widget.prototype.computeAttributes = function() {
return changedAttributes;
};
Widget.prototype.computeAttribute = function(attribute) {
var value;
if(attribute.type === "filtered") {
value = this.wiki.filterTiddlers(attribute.filter,this)[0] || "";
} else if(attribute.type === "indirect") {
value = this.wiki.getTextReference(attribute.textReference,"",this.getVariable("currentTiddler"));
} else if(attribute.type === "macro") {
value = this.getVariable(attribute.value.name,{params: attribute.value.params});
} else { // String attribute
value = attribute.value;
}
return value;
};
/*
Check for the presence of an attribute
*/

View File

@@ -1,9 +0,0 @@
title: $:/core/ui/ControlPanel/Cascades
tags: $:/tags/ControlPanel/Advanced
caption: {{$:/language/ControlPanel/Cascades/Caption}}
{{$:/language/ControlPanel/Cascades/Hint}}
<div class="tc-control-panel">
<$macrocall $name="tabs" tabsList="[all[shadows+tiddlers]tag[$:/tags/ControlPanel/Cascades]!has[draft.of]]" default="$:/core/ui/ControlPanel/StoryTiddler"/>
</div>

View File

@@ -1,9 +0,0 @@
title: $:/core/ui/ControlPanel/EditTemplateBody
tags: $:/tags/ControlPanel/Cascade
caption: {{$:/language/ControlPanel/EditTemplateBody/Caption}}
\define lingo-base() $:/language/ControlPanel/EditTemplateBody/
<<lingo Hint>>
{{$:/tags/EditTemplateBodyFilter||$:/snippets/ListTaggedCascade}}

View File

@@ -1,9 +0,0 @@
title: $:/core/ui/ControlPanel/StoryTiddler
tags: $:/tags/ControlPanel/Cascades
caption: {{$:/language/ControlPanel/StoryTiddler/Caption}}
\define lingo-base() $:/language/ControlPanel/StoryTiddler/
<<lingo Hint>>
{{$:/tags/StoryTiddlerTemplateFilter||$:/snippets/ListTaggedCascade}}

View File

@@ -1,9 +0,0 @@
title: $:/core/ui/ControlPanel/TiddlerIcon
tags: $:/tags/ControlPanel/Cascades
caption: {{$:/language/ControlPanel/TiddlerIcon/Caption}}
\define lingo-base() $:/language/ControlPanel/TiddlerIcon/
<<lingo Hint>>
{{$:/tags/TiddlerIconFilter||$:/snippets/ListTaggedCascade}}

View File

@@ -1,9 +0,0 @@
title: $:/core/ui/ControlPanel/ViewTemplateBody
tags: $:/tags/ControlPanel/Cascades
caption: {{$:/language/ControlPanel/ViewTemplateBody/Caption}}
\define lingo-base() $:/language/ControlPanel/ViewTemplateBody/
<<lingo Hint>>
{{$:/tags/ViewTemplateBodyFilter||$:/snippets/ListTaggedCascade}}

View File

@@ -1,9 +0,0 @@
title: $:/core/ui/ControlPanel/ViewTemplateTitle
tags: $:/tags/ControlPanel/Cascades
caption: {{$:/language/ControlPanel/ViewTemplateTitle/Caption}}
\define lingo-base() $:/language/ControlPanel/ViewTemplateTitle/
<<lingo Hint>>
{{$:/tags/ViewTemplateTitleFilter||$:/snippets/ListTaggedCascade}}

View File

@@ -1,4 +1,54 @@
title: $:/core/ui/EditTemplate/body
tags: $:/tags/EditTemplate
<$transclude tiddler={{{ [<currentTiddler>] :cascade[all[shadows+tiddlers]tag[$:/tags/EditTemplateBodyFilter]get[text]] :and[!is[blank]else[$:/core/ui/EditTemplate/body/default]] }}} />
\define lingo-base() $:/language/EditTemplate/Body/
\define config-visibility-title()
$:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
\end
\define importFileActions()
<$action-popup $state=<<importState>> $coords="(0,0,0,0)" $floating="yes"/>
\end
<$list filter="[all[current]has[_canonical_uri]]">
<div class="tc-message-box">
<<lingo External/Hint>>
<a href={{!!_canonical_uri}}><$text text={{!!_canonical_uri}}/></a>
<$edit-text field="_canonical_uri" class="tc-edit-fields" tabindex={{$:/config/EditTabIndex}} cancelPopups="yes"></$edit-text>
</div>
</$list>
<$list filter="[all[current]!has[_canonical_uri]]">
<$vars importTitle=<<qualify $:/ImportImage>> importState=<<qualify $:/state/ImportImage>> >
<$dropzone importTitle=<<importTitle>> autoOpenOnImport="no" contentTypesFilter={{$:/config/Editor/ImportContentTypesFilter}} class="tc-dropzone-editor" enable={{{ [{$:/config/DragAndDrop/Enable}match[no]] :else[subfilter{$:/config/Editor/EnableImportFilter}then[yes]else[no]] }}} filesOnly="yes" actions=<<importFileActions>> ><$reveal state="$:/state/showeditpreview" type="match" text="yes">
<div class="tc-tiddler-preview">
<$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/>
<div class="tc-tiddler-preview-preview">
<$transclude tiddler={{$:/state/editpreviewtype}} mode="inline">
<$transclude tiddler="$:/core/ui/EditTemplate/body/preview/output" mode="inline"/>
</$transclude>
</div>
</div>
</$reveal>
<$reveal state="$:/state/showeditpreview" type="nomatch" text="yes">
<$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/>
</$reveal>
</$dropzone>
</$vars>
</$list>

View File

@@ -1,13 +0,0 @@
title: $:/core/ui/EditTemplate/body/canonical-uri
\define lingo-base() $:/language/EditTemplate/Body/
<div class="tc-message-box">
<<lingo External/Hint>>
<a href={{!!_canonical_uri}}><$text text={{!!_canonical_uri}}/></a>
<$edit-text field="_canonical_uri" class="tc-edit-fields" tabindex={{$:/config/EditTabIndex}} cancelPopups="yes"></$edit-text>
</div>

View File

@@ -1,38 +0,0 @@
title: $:/core/ui/EditTemplate/body/default
\define config-visibility-title()
$:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
\end
\define importFileActions()
<$action-popup $state=<<importState>> $coords="(0,0,0,0)" $floating="yes"/>
\end
<$set name="edit-preview-state" value={{{ [{$:/config/ShowEditPreview/PerTiddler}!match[yes]then[$:/state/showeditpreview]] :else[<qualify "$:/state/showeditpreview">] }}}>
<$vars importTitle=<<qualify $:/ImportImage>> importState=<<qualify $:/state/ImportImage>> >
<$dropzone importTitle=<<importTitle>> autoOpenOnImport="no" contentTypesFilter={{$:/config/Editor/ImportContentTypesFilter}} class="tc-dropzone-editor" enable={{{ [{$:/config/DragAndDrop/Enable}match[no]] :else[subfilter{$:/config/Editor/EnableImportFilter}then[yes]else[no]] }}} filesOnly="yes" actions=<<importFileActions>> ><$reveal stateTitle=<<edit-preview-state>> type="match" text="yes">
<div class="tc-tiddler-preview">
<$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/>
<div class="tc-tiddler-preview-preview">
<$transclude tiddler={{$:/state/editpreviewtype}} mode="inline">
<$transclude tiddler="$:/core/ui/EditTemplate/body/preview/output" mode="inline"/>
</$transclude>
</div>
</div>
</$reveal>
<$reveal stateTitle=<<edit-preview-state>> type="nomatch" text="yes">
<$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/>
</$reveal>
</$dropzone>
</$vars>
</$set>

View File

@@ -8,11 +8,11 @@ condition: [<targetTiddler>]
button-classes: tc-text-editor-toolbar-item-start-group
shortcuts: ((preview))
<$reveal state=<<edit-preview-state>> type="match" text="yes" tag="span">
<$reveal state="$:/state/showeditpreview" type="match" text="yes" tag="span">
{{$:/core/images/preview-open}}
<$action-setfield $tiddler=<<edit-preview-state>> $value="no"/>
<$action-setfield $tiddler="$:/state/showeditpreview" $value="no"/>
</$reveal>
<$reveal state=<<edit-preview-state>> type="nomatch" text="yes" tag="span">
<$reveal state="$:/state/showeditpreview" type="nomatch" text="yes" tag="span">
{{$:/core/images/preview-closed}}
<$action-setfield $tiddler=<<edit-preview-state>> $value="yes"/>
<$action-setfield $tiddler="$:/state/showeditpreview" $value="yes"/>
</$reveal>

View File

@@ -1,14 +0,0 @@
title: $:/snippets/ListTaggedCascade
{{||$:/language/ControlPanel/Cascades/TagPrompt}}
<ol>
<$list filter="[all[shadows+tiddlers]tag<currentTiddler>]">
<li>
<div>
<$link><$text text=<<currentTiddler>>/></$link>
</div>
<$codeblock code={{!!text}}/>
</li>
</$list>
</ol>

View File

@@ -14,7 +14,7 @@ tags: $:/tags/PageTemplate
</section>
<$list filter="[list[$:/StoryList]]" history="$:/HistoryList" template="$:/core/ui/StoryTiddlerTemplate" storyview={{$:/view}} emptyMessage={{$:/config/EmptyStoryMessage}}/>
<$list filter="[list[$:/StoryList]]" history="$:/HistoryList" template={{$:/config/ui/ViewTemplate}} editTemplate={{$:/config/ui/EditTemplate}} storyview={{$:/view}} emptyMessage={{$:/config/EmptyStoryMessage}}/>
<section class="story-frontdrop">

View File

@@ -1,3 +0,0 @@
title: $:/core/ui/StoryTiddlerTemplate
<$transclude tiddler={{{ [<currentTiddler>] :cascade[all[shadows+tiddlers]tag[$:/tags/StoryTiddlerTemplateFilter]get[text]] :and[!is[blank]else{$:/config/ui/ViewTemplate}] }}} />

View File

@@ -16,7 +16,7 @@ title: $:/core/ui/TagPickerTagTemplate
<$set name="backgroundColor" value={{!!color}}>
<$wikify name="foregroundColor" text="""<$macrocall $name="contrastcolour" target={{!!color}} fallbackTarget=<<fallbackTarget>> colourA=<<colourA>> colourB=<<colourB>>/>""">
<span class="tc-tag-label tc-btn-invisible" style=<<tag-pill-styles>>>
{{||$:/core/ui/TiddlerIcon}}<$view field="title" format="text"/>
<$transclude tiddler={{!!icon}}/><$view field="title" format="text"/>
</span>
</$wikify>
</$set>

View File

@@ -3,7 +3,7 @@ title: $:/core/ui/TagTemplate
\whitespace trim
<span class="tc-tag-list-item" data-tag-title=<<currentTiddler>>>
<$set name="transclusion" value=<<currentTiddler>>>
<$macrocall $name="tag-pill-body" tag=<<currentTiddler>> colour={{!!color}} palette={{$:/palette}} element-tag="""$button""" element-attributes="""popup=<<qualify "$:/state/popup/tag">> dragFilter='[all[current]tagging[]]' tag='span'"""/>
<$macrocall $name="tag-pill-body" tag=<<currentTiddler>> icon={{!!icon}} colour={{!!color}} palette={{$:/palette}} element-tag="""$button""" element-attributes="""popup=<<qualify "$:/state/popup/tag">> dragFilter='[all[current]tagging[]]' tag='span'"""/>
<$reveal state=<<qualify "$:/state/popup/tag">> type="popup" position="below" animate="yes" class="tc-drop-down">
<$set name="tv-show-missing-links" value="yes">
<$transclude tiddler="$:/core/ui/ListItemTemplate"/>

View File

@@ -1,15 +0,0 @@
title: $:/core/ui/TiddlerIcon
\whitespace trim
\define title-styles()
fill:$(foregroundColor)$;
\end
<$let tiddlerIcon={{{ [<currentTiddler>] :cascade[all[shadows+tiddlers]tag[$:/tags/TiddlerIconFilter]get[text]] }}}>
<$list filter="[<tiddlerIcon>!is[blank]]" variable="ignore">
<$let foregroundColor={{!!color}}>
<span class=<<iconSpanClass>> style=<<title-styles>>>
<$transclude tiddler=<<tiddlerIcon>>/>
</span>
</$let>
</$list>
</$let>

View File

@@ -3,6 +3,14 @@ tags: $:/tags/ViewTemplate
<$reveal tag="div" class="tc-tiddler-body" type="nomatch" stateTitle=<<folded-state>> text="hide" retain="yes" animate="yes">
<$transclude tiddler={{{ [<currentTiddler>] :cascade[all[shadows+tiddlers]tag[$:/tags/ViewTemplateBodyFilter]get[text]] :and[!is[blank]else[$:/core/ui/ViewTemplate/body/default]] }}} />
<$list filter="[all[current]!has[plugin-type]!field:hide-body[yes]]">
<$transclude>
<$transclude tiddler="$:/language/MissingTiddler/Hint"/>
</$transclude>
</$list>
</$reveal>

View File

@@ -1,3 +0,0 @@
title: $:/core/ui/ViewTemplate/body/blank
<!-- Intentionally blank -->

View File

@@ -1,3 +0,0 @@
title: $:/core/ui/ViewTemplate/body/code
<$codeblock code={{{ [<currentTiddler>get[text]] }}} language={{{ [<currentTiddler>get[type]else[text/vnd.tiddlywiki]] }}}/>

View File

@@ -1,7 +0,0 @@
title: $:/core/ui/ViewTemplate/body/default
<$transclude>
<$transclude tiddler="$:/language/MissingTiddler/Hint"/>
</$transclude>

View File

@@ -1,10 +0,0 @@
title: $:/core/ui/ViewTemplate/body/plugin
<div class="tc-tiddler-plugin-info">
<$let plugin-type={{!!plugin-type}}
default-popup-state="yes"
qualified-state=<<qualify "$:/state/plugin-info">>
>
{{||$:/core/ui/Components/plugin-info}}
</$let>
</div>

View File

@@ -1,4 +1,5 @@
title: $:/core/ui/ViewTemplate/body/import
title: $:/core/ui/ViewTemplate/import
tags: $:/tags/ViewTemplate
\define lingo-base() $:/language/Import/

View File

@@ -0,0 +1,15 @@
title: $:/core/ui/ViewTemplate/plugin
tags: $:/tags/ViewTemplate
<$reveal tag="div" class="tc-tiddler-plugin-info" type="nomatch" stateTitle=<<folded-state>> text="hide" retain="yes" animate="yes">
<$list filter="[all[current]has[plugin-type]] -[all[current]field:plugin-type[import]]">
<$set name="plugin-type" value={{!!plugin-type}}>
<$set name="default-popup-state" value="yes">
<$set name="qualified-state" value=<<qualify "$:/state/plugin-info">>>
{{||$:/core/ui/Components/plugin-info}}
</$set>
</$set>
</$set>
</$list>
</$reveal>

View File

@@ -2,6 +2,9 @@ title: $:/core/ui/ViewTemplate/title
tags: $:/tags/ViewTemplate
\whitespace trim
\define title-styles()
fill:$(foregroundColor)$;
\end
<div class="tc-tiddler-title">
<div class="tc-titlebar">
<span class="tc-tiddler-controls">
@@ -9,10 +12,25 @@ tags: $:/tags/ViewTemplate
</span>
<$set name="tv-wikilinks" value={{$:/config/Tiddlers/TitleLinks}}>
<$link>
<$let iconSpanClass="tc-tiddler-title-icon">
{{||$:/core/ui/TiddlerIcon}}
</$let>
<$transclude tiddler={{{ [<currentTiddler>] :cascade[all[shadows+tiddlers]tag[$:/tags/ViewTemplateTitleFilter]get[text]] :and[!is[blank]else[$:/core/ui/ViewTemplate/title/default]] }}} />
<$set name="foregroundColor" value={{!!color}}>
<$list filter="[all[current]has[icon]]~[[$:/config/DefaultTiddlerIcon]has[text]]">
<span class="tc-tiddler-title-icon" style=<<title-styles>>>
<$transclude tiddler={{!!icon}}>
<$transclude tiddler={{$:/config/DefaultTiddlerIcon}}/>
</$transclude>
</span>
</$list>
</$set>
<$list filter="[all[current]removeprefix[$:/]]">
<h2 class="tc-title" title={{$:/language/SystemTiddler/Tooltip}}>
<span class="tc-system-title-prefix">$:/</span><$text text=<<currentTiddler>>/>
</h2>
</$list>
<$list filter="[all[current]!prefix[$:/]]">
<h2 class="tc-title">
<$view field="title"/>
</h2>
</$list>
</$link>
</$set>
</div>

View File

@@ -1,6 +0,0 @@
title: $:/core/ui/ViewTemplate/title/default
\whitespace trim
<h2 class="tc-title">
<$view field="title"/>
</h2>

View File

@@ -1,6 +0,0 @@
title: $:/core/ui/ViewTemplate/title/system
\whitespace trim
<h2 class="tc-title" title={{$:/language/SystemTiddler/Tooltip}}>
<span class="tc-system-title-prefix">$:/</span><$text text={{{ [<currentTiddler>removeprefix[$:/]] }}}/>
</h2>

View File

@@ -1,5 +0,0 @@
title: $:/config/EditTemplateBodyFilters/
tags: $:/tags/EditTemplateBodyFilter
canonical-uri: [has[_canonical_uri]then[$:/core/ui/EditTemplate/body/canonical-uri]]
default: [[$:/core/ui/EditTemplate/body/default]]

View File

@@ -1,5 +0,0 @@
title: $:/config/StoryTiddlerTemplateFilters/
tags: $:/tags/StoryTiddlerTemplateFilter
draft: [is[draft]then{$:/config/ui/EditTemplate}]
default: [{$:/config/ui/ViewTemplate}]

View File

@@ -1,5 +0,0 @@
title: $:/config/TiddlerIconFilters/
tags: $:/tags/TiddlerIconFilter
icon-field: [has[icon]then{!!icon}]
default: [{$:/config/DefaultTiddlerIcon}has[text]]

View File

@@ -1,8 +0,0 @@
title: $:/config/ViewTemplateBodyFilters/
tags: $:/tags/ViewTemplateBodyFilter
system: [prefix[$:/boot/]] [prefix[$:/config/]] [prefix[$:/core/]!field:title[$:/core/readme]!field:title[$:/core/icon]] [prefix[$:/info/]] [prefix[$:/language/]] [prefix[$:/languages/]] [prefix[$:/snippets/]] [prefix[$:/state/]] [prefix[$:/status/]] [prefix[$:/info/]] [prefix[$:/temp/]] +[limit[1]then[$:/core/ui/ViewTemplate/body/code]]
import: [field:plugin-type[import]then[$:/core/ui/ViewTemplate/body/import]]
plugin: [has[plugin-type]then[$:/core/ui/ViewTemplate/body/plugin]]
hide-body: [field:hide-body[yes]then[$:/core/ui/ViewTemplate/body/blank]]
default: [[$:/core/ui/ViewTemplate/body/default]]

View File

@@ -1,5 +0,0 @@
title: $:/config/ViewTemplateTitleFilters/
tags: $:/tags/ViewTemplateTitleFilter
system: [prefix[$:/]then[$:/core/ui/ViewTemplate/title/system]]
default: [[$:/core/ui/ViewTemplate/title/default]]

View File

@@ -10,18 +10,18 @@ color:$(foregroundColor)$;
\define tag-pill-inner(tag,icon,colour,fallbackTarget,colourA,colourB,element-tag,element-attributes,actions)
<$vars foregroundColor=<<contrastcolour target:"""$colour$""" fallbackTarget:"""$fallbackTarget$""" colourA:"""$colourA$""" colourB:"""$colourB$""">> backgroundColor="""$colour$""">
<$element-tag$ $element-attributes$ class="tc-tag-label tc-btn-invisible" style=<<tag-pill-styles>>>
$actions${{||$:/core/ui/TiddlerIcon}}<$view tiddler=<<__tag__>> field="title" format="text" />
$actions$<$transclude tiddler="""$icon$"""/><$view tiddler=<<__tag__>> field="title" format="text" />
</$element-tag$>
</$vars>
\end
\define tag-pill-body(tag,icon,colour,palette,element-tag,element-attributes,actions)
<$macrocall $name="tag-pill-inner" tag=<<__tag__>> colour="""$colour$""" fallbackTarget={{$palette$##tag-background}} colourA={{$palette$##foreground}} colourB={{$palette$##background}} element-tag="""$element-tag$""" element-attributes="""$element-attributes$""" actions="""$actions$"""/>
<$macrocall $name="tag-pill-inner" tag=<<__tag__>> icon="""$icon$""" colour="""$colour$""" fallbackTarget={{$palette$##tag-background}} colourA={{$palette$##foreground}} colourB={{$palette$##background}} element-tag="""$element-tag$""" element-attributes="""$element-attributes$""" actions="""$actions$"""/>
\end
\define tag-pill(tag,element-tag:"span",element-attributes:"",actions:"")
<span class="tc-tag-list-item" data-tag-title=<<__tag__>>>
<$macrocall $name="tag-pill-body" tag=<<__tag__>> colour={{{ [<__tag__>get[color]] }}} palette={{$:/palette}} element-tag="""$element-tag$""" element-attributes="""$element-attributes$""" actions="""$actions$"""/>
<$macrocall $name="tag-pill-body" tag=<<__tag__>> icon={{{ [<__tag__>get[icon]] }}} colour={{{ [<__tag__>get[color]] }}} palette={{$:/palette}} element-tag="""$element-tag$""" element-attributes="""$element-attributes$""" actions="""$actions$"""/>
</span>
\end

View File

@@ -1,2 +0,0 @@
title: $:/tags/EditTemplateBodyFilter
list: $:/config/EditTemplateBodyFilters/canonical-uri $:/config/EditTemplateBodyFilters/default

View File

@@ -1,2 +0,0 @@
title: $:/tags/StoryTiddlerTemplateFilter
list: $:/config/StoryTiddlerTemplateFilters/draft $:/config/StoryTiddlerTemplateFilters/default

View File

@@ -1,3 +0,0 @@
title: $:/tags/TiddlerIconFilter
list: $:/config/TiddlerIconFilters/icon-field $:/config/TiddlerIconFilters/default

View File

@@ -1,3 +0,0 @@
title: $:/tags/ViewTemplateBodyFilter
list: $:/config/ViewTemplateBodyFilters/system $:/config/ViewTemplateBodyFilters/import $:/config/ViewTemplateBodyFilters/plugin $:/config/ViewTemplateBodyFilters/hide-body $:/config/ViewTemplateBodyFilters/default

View File

@@ -1,3 +0,0 @@
title: $:/tags/ViewTemplateTitleFilter
list: $:/config/ViewTemplateTitleFilters/system $:/config/ViewTemplateTitleFilters/default

View File

@@ -0,0 +1,123 @@
created: 20181001171604072
modified: 20181001184306738
title: External Pipes
type: text/vnd.tiddlywiki
!! Introduction
The external pipes mechanism is intended to address the fact that it can be difficult for experienced developers to quickly get started working with TiddlyWiki's code because it requires a good understanding of JavaScript, and some relatively unusual concepts and techniques that are likely to be unfamiliar to most.
Many software developers are comfortable using and building text-based command line tools that follow the Unix philosophy of using stdin/stdout or TCP sockets for input and output, allowing tools to be chained together in powerful ways.
External pipes support data transfer with external programs via two techniques:
* By directly executing the program and communicating with it via stdin and stdout ([[mimic.js|External Pipes mimic.js]] and [[stats.js|External Pipes stats.js]])
* By connecting to an existing TCP socket via a host address and port ([[reverser.js|External Pipes reverser.js]])
Pipes are registered and executed under Node.js. Execution can be triggered via a server command or via a message propagated from the browser.
Pipes currently support a simple question/response model:
* TiddlyWiki opens a connection to the host/port, or starts the external task
* TiddlyWiki sends the outgoing data through the pipe
* TiddlyWiki receives the incoming data through the pipe
* The connection is close, or the external task exits
!! `externalpipesdemo` Edition
The edition at `editions/externalpipesdemo` in the TiddlyWiki 5 repo contains the example tasks referenced in this documentation.
!! Configuration
Pipes must be registered in the `tiddlywiki.info` file of a wiki folder. For example:
{{External Pipes tiddlywiki.info}}
!!! Task Configuration Properties
TBD
!!! Input Formats
The available input formats are:
* `raw-text` - concatenated raw text of the tiddlers
* `rendered-text` - concatenated rendered text of the tiddlers
* `rendered-html` - concatenated rendered html of the tiddlers
* `json-raw-tiddlers` - raw tiddlers in JSON
* `json-rendered-text-tiddlers` - rendered tiddlers in JSON
!!! Output Formats
The available output formats include:
* `raw-text` - raw text
* `json-raw-tiddlers` - raw tiddlers in JSON
!! Usage
Pipes can be invoked via the `--pipe` command, or via a widget message propagated from the browser to the server.
!!! `--pipe` Command
The `--pipe` command triggers the invocation of a pipe task. The parameters are:
```
tiddlywiki <wikifolder> --pipe <pipename> <filter> <incomingTitle> <arguments>...
```
The ''pipename'' identifies the pipe, and must match the name defined in the `tiddlywiki.info` file.
The ''filter'' identifies the tiddlers to be passed to the pipe
The ''incomingTitle'' provides a title to be applied to incoming tiddlers for the `raw-text` output format
The remaining ''arguments'' are passed through to external task pipes, and ignored for other types of pipe.
!!! `tm-exec-task` Message
TBD
!! Examples
The edition `editions/externalpipesdemo` in the TiddlyWiki5 repo contains a demo wiki that invokes sample tasks.
!!! Mimic
The `mimic.js` example is a simple command line tool that accepts source text via stdin and outputs a sort of statistical parody of the input text. It is written without any knowledge of TiddlyWiki, and so serves as an example of using an existing text-based tool with TiddlyWiki.
```
tiddlywiki editions/externalpipesdemo/ --verbose --pipe mimic '[[Alice in Wonderland]]' HelloThere 5 4000 --build index
```
View the resulting wiki at `editions/externaltaskdemo/output/index.html`: the tiddler "HelloThere" will contain a garbled version of the "Alice in Wonderland" text.
!!! Stats
The `stats.js` example is a simple command line tool that accepts source text via stdin and outputs a sort of statistical parody of the input text. It is written without any knowledge of TiddlyWiki, and so serves as an example of using an existing text-based tool with TiddlyWiki.
```
tiddlywiki editions/externalpipesdemo/ --verbose --pipe stats '[[Alice in Wonderland]]' HelloThere --build index
```
View the resulting wiki at `editions/externaltaskdemo/output/index.html`: the tiddler "HelloThere" will contain statistics about the "Alice in Wonderland" text.
!!! Reverser
The `reverser.js` example is an simple command line tool that listens for connections on a host address and port, accepts source text and then returns the same text with the characters reversed.
Two separate commands are needed to run the sample: one to get the server running, and the second to run TiddlyWiki and invoke the server.
To run the server, open a command prompt in the `demo-tasks` directory and run:
```
../reverser.js 8081
```
And then in another command prompt in the root of the repo run:
```
tiddlywiki editions/externalpipesdemo/ --verbose --pipe reverser '[[Alice in Wonderland]]' HelloThere --build index
```
View the resulting wiki at `editions/externaltaskdemo/output/index.html`: the tiddler "HelloThere" will contain the reversed text of "Alice in Wonderland".

View File

@@ -0,0 +1,32 @@
{
"tiddlers": [
{
"file": "../../../../../externalpipesdemo/tiddlywiki.info",
"fields": {
"type": "text/plain",
"title": "External Pipes tiddlywiki.info"
}
},
{
"file": "../../../../../externalpipesdemo/demo-tasks/mimic.js",
"fields": {
"type": "application/javascript",
"title": "External Pipes mimic.js"
}
},
{
"file": "../../../../../externalpipesdemo/demo-tasks/reverser.js",
"fields": {
"type": "application/javascript",
"title": "External Pipes reverser.js"
}
},
{
"file": "../../../../../externalpipesdemo/demo-tasks/stats.js",
"fields": {
"type": "application/javascript",
"title": "External Pipes stats.js"
}
}
]
}

View File

@@ -0,0 +1,80 @@
#!/usr/bin/env node
/*
A socket server that listens on a host/port for connections and suggests tags for the incoming text
badtagger.js <port> <host>
This utility is provided as an example of using an external task that doesn't have any prior knowledge of
TiddlyWiki. Like many Unix utilities, it just reads and writes to a socket.
*/
var net = require("net"),
port = parseInt(process.argv[2] || "",10) || 8081, // Port
host = process.argv[3] || "127.0.0.1"; // Host
var server = net.createServer();
server.listen(port,host);
server.on("connection", function(sock) {
console.log("CONNECTED: " + sock.remoteAddress +":"+ sock.remotePort);
// Trap errors
sock.on("error",function(e) {
console.log("ERROR: " + e);
});
// Read data until the end
var accumulator = Buffer.alloc(0);
sock.on("data",function(data) {
console.log("DATA " + sock.remoteAddress + ": " + data.length);
accumulator = Buffer.concat([accumulator,Buffer.from(data)]);
while(accumulator.length > 4) {
var length = accumulator.readInt32BE(0);
if(accumulator.length >= (length + 4)) {
if(length < 1) {
throw "ERROR: Incoming message length field is less than 1";
}
var type = accumulator.readUInt8(4),
dataLength = length - 1,
data = accumulator.toString("latin1",5,dataLength + 5);
accumulator = accumulator.slice(length + 4);
// Recase it
console.log("MESSAGE",length,type);
var suggestedTags = Buffer.from(suggestTags(data),"latin1");
// Send it back
var lengthBytes = Buffer.alloc(4);
lengthBytes.writeUInt32BE(suggestedTags.length + 1,0)
console.log("RESPONSE",1,suggestedTags.length)
sock.write(lengthBytes);
var typeByte = Buffer.alloc(1);
typeByte.writeUInt8(1,0);
sock.write(typeByte);
sock.write(suggestedTags);
} else {
break;
}
}
});
sock.on("end",function() {
console.log("END")
sock.end();
});
sock.on("close", function(data) {
console.log("CLOSED: " + sock.remoteAddress +" "+ sock.remotePort);
});
});
function suggestTags(str) {
var tags = [];
if(/e/mi.test(str)) {
tags.push("elephant");
}
if(/s/mi.test(str)) {
tags.push("snake");
}
if(/c/mi.test(str)) {
tags.push("cow");
}
return tags.join("\n");
}

View File

@@ -0,0 +1,109 @@
#!/usr/bin/env node
/*
Reads source text from stdin and mimics it to stdout to stdout using a simple statistical analysis of ngram frequency
mimic.js <ngram-length> <output-length>
This utility is provided as an example of using an external task that doesn't have any prior knowledge of
TiddlyWiki. Like many Unix utilities, it just reads input from stdin and writes its output to stdout.
*/
var paramNgramLength = parseInt(process.argv[2] || "",10) || 3, // Size of ngrams for mimicing
paramOutputLength = parseInt(process.argv[3] || "",10) || 1000;
process.stdin.resume();
process.stdin.setEncoding("utf8");
var inputChunks = [];
process.stdin.on("data",function(chunk) {
inputChunks.push(chunk);
});
process.stdin.on("end",function() {
// Do the mimicry
var output = mimic(inputChunks.join(""),paramNgramLength);
// Output the result
process.stdout.write(output);
});
function mimic(sourceText,paramNgramLength) {
if(!sourceText) {
return "";
}
var tree = {};
scanText(tree,sourceText,paramNgramLength);
return generateText(tree,sourceText,paramNgramLength,paramOutputLength);
}
/*
The source text is scanned to build a tree of the ngram prefixes as follows:
{
"abc": { // The ngram prefix described by this record
count: 42, // The number of times the prefix is found in the source text
next: [ // An array of information about each subsequent character encountered after the prefix
{char: "d", count: 41},
{char: " ", count: 1}
]
},
"def": ... etc
}
*/
// Process the source text into the specified tree with the chosen ngram size
function scanText(tree,sourceText,size) {
var currgram = [],ptr,c,ngram,branch,n;
if(sourceText.length <= size*2)
return tree;
sourceText += sourceText.substring(0,size*2-1); // Wrap the text around
for(ptr=0; ptr<size; ptr++) {
currgram.push(sourceText.substr(ptr,1));
}
while(ptr < sourceText.length) {
ngram = currgram.join("");
c = sourceText.substr(ptr++,1);
branch = tree[ngram];
if(branch === undefined) {
branch = tree[ngram] = {count: 0,next: []};
}
for(n = 0; n<branch.next.length; n++) {
if(branch.next[n].char === c)
break;
}
if(branch.next[n] === undefined) {
branch.next[n] = {char: c, count: 1};
} else {
branch.next[n].count++;
}
branch.count++;
currgram.push(c)
currgram.shift();
}
return tree;
}
// Use the tree to generate mimicry
function generateText(tree,sourceText,size,length) {
var currgram = [];
for(var t=0; t<size; t++) {
currgram.push(sourceText.substr(t,1));
}
var result = [];
var c,ngram,branch,r,n;
for(t=0; t<length; t++) {
ngram = currgram.join("");
branch = tree[ngram];
n = 0;
r = Math.floor(Math.random() * branch.count);
while(r >= branch.next[n].count) {
r = r - branch.next[n].count;
n++;
}
c = branch.next[n].char;
result.push(c);
currgram.push(c)
currgram.shift();
}
return result.join("");
}

View File

@@ -0,0 +1,3 @@
# Sample Processes for TiddlyWiki External Pipes
"External Pipes" are connections enabling a wiki to communicate with an external process to perform arbitrary processing on a group of tiddlers.

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env escript
%% run with sh ./recase_erl.sh
-mode(compile).
main(_) ->
{ok, Listen} = gen_tcp:listen(8081, [binary,{packet,4},
{active,true}]),
spawn(fun() -> par_connect(Listen) end),
receive after infinity -> void end.
par_connect(Listen) ->
{ok, Socket} = gen_tcp:accept(Listen),
io:format("connected ~n"),
spawn(fun() -> par_connect(Listen) end),
loop(Socket).
loop(Socket) ->
receive
{tcp,Socket,Bin} ->
io:format("received ~p bytes ~s ~n",[size(Bin), Bin]),
Return = recase_binary(Bin),
io:format("sending: ~p bytes~n",[size(Return)]),
gen_tcp:send(Socket, Return),
loop(Socket);
Other ->
io:format("received ~p~n",[Other])
end.
recase_binary(<<1,B/binary>>) ->
L = binary_to_list(B),
L1 = [recase(I) || I <- L],
B1 = list_to_binary(L1),
<<1,B1/binary>>.
recase(I) when I >= $a, I =< $z -> I - $a + $A;
recase(I) when I >= $A, I =< $Z -> I - $A + $a;
recase(I) -> I.

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env node
/*
A socket server that listens on a host/port for connections and reverses the case of incoming text
recaser.js <port> <host>
This utility is provided as an example of using an external task that doesn't have any prior knowledge of
TiddlyWiki. Like many Unix utilities, it just reads and writes to a socket.
*/
var net = require("net"),
port = parseInt(process.argv[2] || "",10) || 8081, // Port
host = process.argv[3] || "127.0.0.1"; // Host
var server = net.createServer();
server.listen(port,host);
server.on("connection", function(sock) {
console.log("CONNECTED: " + sock.remoteAddress +":"+ sock.remotePort);
// Trap errors
sock.on("error",function(e) {
console.log("ERROR: " + e);
});
// Read data until the end
var accumulator = Buffer.alloc(0);
sock.on("data",function(data) {
console.log("DATA " + sock.remoteAddress + ": " + data.length);
accumulator = Buffer.concat([accumulator,Buffer.from(data)]);
while(accumulator.length > 4) {
var length = accumulator.readInt32BE(0);
if(accumulator.length >= (length + 4)) {
if(length < 1) {
throw "ERROR: Incoming message length field is less than 1";
}
var type = accumulator.readUInt8(4),
dataLength = length - 1,
data = accumulator.toString("latin1",5,dataLength + 5);
accumulator = accumulator.slice(length + 4);
// Recase it
console.log("MESSAGE",length,type);
var recasedData = Buffer.from(recase(data),"latin1");
// Send it back
var lengthBytes = Buffer.alloc(4);
lengthBytes.writeUInt32BE(recasedData.length + 1,0)
console.log("RESPONSE",1,recasedData.length)
sock.write(lengthBytes);
var typeByte = Buffer.alloc(1);
typeByte.writeUInt8(1,0);
sock.write(typeByte);
sock.write(recasedData);
} else {
break;
}
}
});
sock.on("end",function() {
console.log("END")
sock.end();
});
sock.on("close", function(data) {
console.log("CLOSED: " + sock.remoteAddress +" "+ sock.remotePort);
});
});
function recase(str) {
return str.split("").map(function(char) {
if(char >= "A" && char <= "Z") {
return char.toLowerCase();
} else {
return char.toUpperCase();
}
}).join("");
}

View File

@@ -0,0 +1,49 @@
#!/usr/bin/env node
/*
A socket server that listens on a host/port for connections and reverses the order of incoming text
reverser.js <port> <host>
This utility is provided as an example of using an external task that doesn't have any prior knowledge of
TiddlyWiki. Like many Unix utilities, it just reads and writes to a socket.
*/
var net = require("net"),
port = parseInt(process.argv[2] || "",10) || 8081, // Port
host = process.argv[3] || "127.0.0.1"; // Host
var server = net.createServer({
allowHalfOpen: true
});
server.listen(port,host);
server.on("connection", function(sock) {
console.log("CONNECTED: " + sock.remoteAddress +":"+ sock.remotePort);
// Trap errors
sock.on("error",function(e) {
console.log("ERROR: " + e);
});
// Read data until the end
var chunks = [];
sock.on("data",function(data) {
console.log("DATA " + sock.remoteAddress + ": " + data);
chunks.push(data.toString());
});
sock.on("end",function() {
console.log("END")
// Write the data back to the socket
sock.write(reverse(chunks.join("")));
sock.end();
});
sock.on("close", function(data) {
console.log("CLOSED: " + sock.remoteAddress +" "+ sock.remotePort);
});
});
function reverse(str) {
return str.split("").reverse().join("");
}

View File

@@ -0,0 +1,82 @@
#!/usr/bin/env node
/*
Reads JSON tiddlers from stdin and outputs stats to stdout
stats.js
This utility is provided as an example of an external task that understands tiddler objects encoded in JSON.
It expects to read an array of tiddler objects from stdin in this format:
[
{
"title": "Tiddler Title",
"text": "Text of tiddler",
"tags": "MyTag [[My Other Tag]]"
},
...
]
The output is in the same format.
*/
process.stdin.resume();
process.stdin.setEncoding("utf8");
var inputChunks = [];
process.stdin.on("data",function(chunk) {
inputChunks.push(chunk);
});
process.stdin.on("end",function() {
// Read the JSON input
var json = inputChunks.join(""),
data;
try {
data = JSON.parse(json);
} catch(e) {
throw "Malformed JSON: " + e + "\n\n" + json;
}
// Compute some stats
var output = computeStats(data);
// Output the result
process.stdout.write(JSON.stringify(output));
});
function computeStats(tiddlers) {
var numTiddlers = tiddlers.length,
wordCount = 0,
wordFrequency = {};
tiddlers.forEach(function(tiddler) {
var matches = (tiddler.text || "").match(/[A-Za-z0-9\u00c0-\u00d6\u00d8-\u00de\u00df-\u00f6\u00f8-\u00ff\u0150\u0170\u0151\u0171]+/g);
if(matches) {
wordCount += matches.length;
matches.forEach(function(word) {
word = word.toLowerCase();
wordFrequency[word] = wordFrequency[word] || 0;
wordFrequency[word] += 1;
});
}
});
var sortedWords = Object.keys(wordFrequency).sort(function(a,b) {
if(wordFrequency[a] > wordFrequency[b]) {
return -1;
} else if(wordFrequency[a] < wordFrequency[b]) {
return +1;
} else {
return 0;
}
});
// Output
return [
{
title: "PipeOutput",
text: numTiddlers + " tiddlers in sample.\n" + wordCount + " words in sample.\n" + sortedWords.filter(function(word) {
return word.length > 1 && wordFrequency[word] > 1;
}).map(function(word) {
return word + " " + wordFrequency[word] + "\n";
}).join(""),
type: "text/plain"
}
];
};

View File

@@ -0,0 +1,360 @@
title: Alice in Wonderland License
tags: text/plain
End of Project Gutenberg's Alice's Adventures in Wonderland, by Lewis Carroll
*** END OF THIS PROJECT GUTENBERG EBOOK ALICE'S ADVENTURES IN WONDERLAND ***
***** This file should be named 11.txt or 11.zip *****
This and all associated files of various formats will be found in:
http://www.gutenberg.org/1/11/
Updated editions will replace the previous one--the old editions
will be renamed.
Creating the works from public domain print editions means that no
one owns a United States copyright in these works, so the Foundation
(and you!) can copy and distribute it in the United States without
permission and without paying copyright royalties. Special rules,
set forth in the General Terms of Use part of this license, apply to
copying and distributing Project Gutenberg-tm electronic works to
protect the PROJECT GUTENBERG-tm concept and trademark. Project
Gutenberg is a registered trademark, and may not be used if you
charge for the eBooks, unless you receive specific permission. If you
do not charge anything for copies of this eBook, complying with the
rules is very easy. You may use this eBook for nearly any purpose
such as creation of derivative works, reports, performances and
research. They may be modified and printed and given away--you may do
practically ANYTHING with public domain eBooks. Redistribution is
subject to the trademark license, especially commercial
redistribution.
*** START: FULL LICENSE ***
THE FULL PROJECT GUTENBERG LICENSE
PLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK
To protect the Project Gutenberg-tm mission of promoting the free
distribution of electronic works, by using or distributing this work
(or any other work associated in any way with the phrase "Project
Gutenberg"), you agree to comply with all the terms of the Full Project
Gutenberg-tm License (available with this file or online at
http://gutenberg.org/license).
Section 1. General Terms of Use and Redistributing Project Gutenberg-tm
electronic works
1.A. By reading or using any part of this Project Gutenberg-tm
electronic work, you indicate that you have read, understand, agree to
and accept all the terms of this license and intellectual property
(trademark/copyright) agreement. If you do not agree to abide by all
the terms of this agreement, you must cease using and return or destroy
all copies of Project Gutenberg-tm electronic works in your possession.
If you paid a fee for obtaining a copy of or access to a Project
Gutenberg-tm electronic work and you do not agree to be bound by the
terms of this agreement, you may obtain a refund from the person or
entity to whom you paid the fee as set forth in paragraph 1.E.8.
1.B. "Project Gutenberg" is a registered trademark. It may only be
used on or associated in any way with an electronic work by people who
agree to be bound by the terms of this agreement. There are a few
things that you can do with most Project Gutenberg-tm electronic works
even without complying with the full terms of this agreement. See
paragraph 1.C below. There are a lot of things you can do with Project
Gutenberg-tm electronic works if you follow the terms of this agreement
and help preserve free future access to Project Gutenberg-tm electronic
works. See paragraph 1.E below.
1.C. The Project Gutenberg Literary Archive Foundation ("the Foundation"
or PGLAF), owns a compilation copyright in the collection of Project
Gutenberg-tm electronic works. Nearly all the individual works in the
collection are in the public domain in the United States. If an
individual work is in the public domain in the United States and you are
located in the United States, we do not claim a right to prevent you from
copying, distributing, performing, displaying or creating derivative
works based on the work as long as all references to Project Gutenberg
are removed. Of course, we hope that you will support the Project
Gutenberg-tm mission of promoting free access to electronic works by
freely sharing Project Gutenberg-tm works in compliance with the terms of
this agreement for keeping the Project Gutenberg-tm name associated with
the work. You can easily comply with the terms of this agreement by
keeping this work in the same format with its attached full Project
Gutenberg-tm License when you share it without charge with others.
1.D. The copyright laws of the place where you are located also govern
what you can do with this work. Copyright laws in most countries are in
a constant state of change. If you are outside the United States, check
the laws of your country in addition to the terms of this agreement
before downloading, copying, displaying, performing, distributing or
creating derivative works based on this work or any other Project
Gutenberg-tm work. The Foundation makes no representations concerning
the copyright status of any work in any country outside the United
States.
1.E. Unless you have removed all references to Project Gutenberg:
1.E.1. The following sentence, with active links to, or other immediate
access to, the full Project Gutenberg-tm License must appear prominently
whenever any copy of a Project Gutenberg-tm work (any work on which the
phrase "Project Gutenberg" appears, or with which the phrase "Project
Gutenberg" is associated) is accessed, displayed, performed, viewed,
copied or distributed:
This eBook is for the use of anyone anywhere at no cost and with
almost no restrictions whatsoever. You may copy it, give it away or
re-use it under the terms of the Project Gutenberg License included
with this eBook or online at www.gutenberg.org
1.E.2. If an individual Project Gutenberg-tm electronic work is derived
from the public domain (does not contain a notice indicating that it is
posted with permission of the copyright holder), the work can be copied
and distributed to anyone in the United States without paying any fees
or charges. If you are redistributing or providing access to a work
with the phrase "Project Gutenberg" associated with or appearing on the
work, you must comply either with the requirements of paragraphs 1.E.1
through 1.E.7 or obtain permission for the use of the work and the
Project Gutenberg-tm trademark as set forth in paragraphs 1.E.8 or
1.E.9.
1.E.3. If an individual Project Gutenberg-tm electronic work is posted
with the permission of the copyright holder, your use and distribution
must comply with both paragraphs 1.E.1 through 1.E.7 and any additional
terms imposed by the copyright holder. Additional terms will be linked
to the Project Gutenberg-tm License for all works posted with the
permission of the copyright holder found at the beginning of this work.
1.E.4. Do not unlink or detach or remove the full Project Gutenberg-tm
License terms from this work, or any files containing a part of this
work or any other work associated with Project Gutenberg-tm.
1.E.5. Do not copy, display, perform, distribute or redistribute this
electronic work, or any part of this electronic work, without
prominently displaying the sentence set forth in paragraph 1.E.1 with
active links or immediate access to the full terms of the Project
Gutenberg-tm License.
1.E.6. You may convert to and distribute this work in any binary,
compressed, marked up, nonproprietary or proprietary form, including any
word processing or hypertext form. However, if you provide access to or
distribute copies of a Project Gutenberg-tm work in a format other than
"Plain Vanilla ASCII" or other format used in the official version
posted on the official Project Gutenberg-tm web site (www.gutenberg.org),
you must, at no additional cost, fee or expense to the user, provide a
copy, a means of exporting a copy, or a means of obtaining a copy upon
request, of the work in its original "Plain Vanilla ASCII" or other
form. Any alternate format must include the full Project Gutenberg-tm
License as specified in paragraph 1.E.1.
1.E.7. Do not charge a fee for access to, viewing, displaying,
performing, copying or distributing any Project Gutenberg-tm works
unless you comply with paragraph 1.E.8 or 1.E.9.
1.E.8. You may charge a reasonable fee for copies of or providing
access to or distributing Project Gutenberg-tm electronic works provided
that
- You pay a royalty fee of 20% of the gross profits you derive from
the use of Project Gutenberg-tm works calculated using the method
you already use to calculate your applicable taxes. The fee is
owed to the owner of the Project Gutenberg-tm trademark, but he
has agreed to donate royalties under this paragraph to the
Project Gutenberg Literary Archive Foundation. Royalty payments
must be paid within 60 days following each date on which you
prepare (or are legally required to prepare) your periodic tax
returns. Royalty payments should be clearly marked as such and
sent to the Project Gutenberg Literary Archive Foundation at the
address specified in Section 4, "Information about donations to
the Project Gutenberg Literary Archive Foundation."
- You provide a full refund of any money paid by a user who notifies
you in writing (or by e-mail) within 30 days of receipt that s/he
does not agree to the terms of the full Project Gutenberg-tm
License. You must require such a user to return or
destroy all copies of the works possessed in a physical medium
and discontinue all use of and all access to other copies of
Project Gutenberg-tm works.
- You provide, in accordance with paragraph 1.F.3, a full refund of any
money paid for a work or a replacement copy, if a defect in the
electronic work is discovered and reported to you within 90 days
of receipt of the work.
- You comply with all other terms of this agreement for free
distribution of Project Gutenberg-tm works.
1.E.9. If you wish to charge a fee or distribute a Project Gutenberg-tm
electronic work or group of works on different terms than are set
forth in this agreement, you must obtain permission in writing from
both the Project Gutenberg Literary Archive Foundation and Michael
Hart, the owner of the Project Gutenberg-tm trademark. Contact the
Foundation as set forth in Section 3 below.
1.F.
1.F.1. Project Gutenberg volunteers and employees expend considerable
effort to identify, do copyright research on, transcribe and proofread
public domain works in creating the Project Gutenberg-tm
collection. Despite these efforts, Project Gutenberg-tm electronic
works, and the medium on which they may be stored, may contain
"Defects," such as, but not limited to, incomplete, inaccurate or
corrupt data, transcription errors, a copyright or other intellectual
property infringement, a defective or damaged disk or other medium, a
computer virus, or computer codes that damage or cannot be read by
your equipment.
1.F.2. LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the "Right
of Replacement or Refund" described in paragraph 1.F.3, the Project
Gutenberg Literary Archive Foundation, the owner of the Project
Gutenberg-tm trademark, and any other party distributing a Project
Gutenberg-tm electronic work under this agreement, disclaim all
liability to you for damages, costs and expenses, including legal
fees. YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT
LIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE
PROVIDED IN PARAGRAPH F3. YOU AGREE THAT THE FOUNDATION, THE
TRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE
LIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR
INCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH
DAMAGE.
1.F.3. LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a
defect in this electronic work within 90 days of receiving it, you can
receive a refund of the money (if any) you paid for it by sending a
written explanation to the person you received the work from. If you
received the work on a physical medium, you must return the medium with
your written explanation. The person or entity that provided you with
the defective work may elect to provide a replacement copy in lieu of a
refund. If you received the work electronically, the person or entity
providing it to you may choose to give you a second opportunity to
receive the work electronically in lieu of a refund. If the second copy
is also defective, you may demand a refund in writing without further
opportunities to fix the problem.
1.F.4. Except for the limited right of replacement or refund set forth
in paragraph 1.F.3, this work is provided to you 'AS-IS' WITH NO OTHER
WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
WARRANTIES OF MERCHANTIBILITY OR FITNESS FOR ANY PURPOSE.
1.F.5. Some states do not allow disclaimers of certain implied
warranties or the exclusion or limitation of certain types of damages.
If any disclaimer or limitation set forth in this agreement violates the
law of the state applicable to this agreement, the agreement shall be
interpreted to make the maximum disclaimer or limitation permitted by
the applicable state law. The invalidity or unenforceability of any
provision of this agreement shall not void the remaining provisions.
1.F.6. INDEMNITY - You agree to indemnify and hold the Foundation, the
trademark owner, any agent or employee of the Foundation, anyone
providing copies of Project Gutenberg-tm electronic works in accordance
with this agreement, and any volunteers associated with the production,
promotion and distribution of Project Gutenberg-tm electronic works,
harmless from all liability, costs and expenses, including legal fees,
that arise directly or indirectly from any of the following which you do
or cause to occur: (a) distribution of this or any Project Gutenberg-tm
work, (b) alteration, modification, or additions or deletions to any
Project Gutenberg-tm work, and (c) any Defect you cause.
Section 2. Information about the Mission of Project Gutenberg-tm
Project Gutenberg-tm is synonymous with the free distribution of
electronic works in formats readable by the widest variety of computers
including obsolete, old, middle-aged and new computers. It exists
because of the efforts of hundreds of volunteers and donations from
people in all walks of life.
Volunteers and financial support to provide volunteers with the
assistance they need, is critical to reaching Project Gutenberg-tm's
goals and ensuring that the Project Gutenberg-tm collection will
remain freely available for generations to come. In 2001, the Project
Gutenberg Literary Archive Foundation was created to provide a secure
and permanent future for Project Gutenberg-tm and future generations.
To learn more about the Project Gutenberg Literary Archive Foundation
and how your efforts and donations can help, see Sections 3 and 4
and the Foundation web page at http://www.pglaf.org.
Section 3. Information about the Project Gutenberg Literary Archive
Foundation
The Project Gutenberg Literary Archive Foundation is a non profit
501(c)(3) educational corporation organized under the laws of the
state of Mississippi and granted tax exempt status by the Internal
Revenue Service. The Foundation's EIN or federal tax identification
number is 64-6221541. Its 501(c)(3) letter is posted at
http://pglaf.org/fundraising. Contributions to the Project Gutenberg
Literary Archive Foundation are tax deductible to the full extent
permitted by U.S. federal laws and your state's laws.
The Foundation's principal office is located at 4557 Melan Dr. S.
Fairbanks, AK, 99712., but its volunteers and employees are scattered
throughout numerous locations. Its business office is located at
809 North 1500 West, Salt Lake City, UT 84116, (801) 596-1887, email
business@pglaf.org. Email contact links and up to date contact
information can be found at the Foundation's web site and official
page at http://pglaf.org
For additional contact information:
Dr. Gregory B. Newby
Chief Executive and Director
gbnewby@pglaf.org
Section 4. Information about Donations to the Project Gutenberg
Literary Archive Foundation
Project Gutenberg-tm depends upon and cannot survive without wide
spread public support and donations to carry out its mission of
increasing the number of public domain and licensed works that can be
freely distributed in machine readable form accessible by the widest
array of equipment including outdated equipment. Many small donations
($1 to $5,000) are particularly important to maintaining tax exempt
status with the IRS.
The Foundation is committed to complying with the laws regulating
charities and charitable donations in all 50 states of the United
States. Compliance requirements are not uniform and it takes a
considerable effort, much paperwork and many fees to meet and keep up
with these requirements. We do not solicit donations in locations
where we have not received written confirmation of compliance. To
SEND DONATIONS or determine the status of compliance for any
particular state visit http://pglaf.org
While we cannot and do not solicit contributions from states where we
have not met the solicitation requirements, we know of no prohibition
against accepting unsolicited donations from donors in such states who
approach us with offers to donate.
International donations are gratefully accepted, but we cannot make
any statements concerning tax treatment of donations received from
outside the United States. U.S. laws alone swamp our small staff.
Please check the Project Gutenberg Web pages for current donation
methods and addresses. Donations are accepted in a number of other
ways including checks, online payments and credit card donations.
To donate, please visit: http://pglaf.org/donate
Section 5. General Information About Project Gutenberg-tm electronic
works.
Professor Michael S. Hart is the originator of the Project Gutenberg-tm
concept of a library of electronic works that could be freely shared
with anyone. For thirty years, he produced and distributed Project
Gutenberg-tm eBooks with only a loose network of volunteer support.
Project Gutenberg-tm eBooks are often created from several printed
editions, all of which are confirmed as Public Domain in the U.S.
unless a copyright notice is included. Thus, we do not necessarily
keep eBooks in compliance with any particular paper edition.
Most people start at our Web site which has the main PG search facility:
http://www.gutenberg.org
This Web site includes information about Project Gutenberg-tm,
including how to make donations to the Project Gutenberg Literary
Archive Foundation, how to help produce our new eBooks, and how to
subscribe to our email newsletter to hear about new eBooks.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
title: $:/plugins/tiddlywiki/externaltasksdemo/EditorToolbar/SuggestTags
tags: $:/tags/EditorToolbar
icon: $:/core/images/tag-button
caption: Suggest Tags
description: Invoke external programme to suggest tags for this tiddler
condition: [<targetTiddler>!has[type]] [<targetTiddler>type[text/vnd.tiddlywiki]]
button-classes: tc-text-editor-toolbar-item-start-group
shortcuts: ((suggest-tags))
dropdown: $:/plugins/tiddlywiki/externaltasksdemo/EditorToolbar/SuggestTagsDropdown
<$set name="inputFilter" filter="[<targetTiddler>]">
<$action-sendmessage $message="tm-execute-job" 1="--pipe" 2="badtagger" 3=<<inputFilter>> 4="PipeOutput" statusTitle="JobStatus"/>
</$set>
Suggest Tags - <$text text=<<targetTiddler>>/>

View File

@@ -0,0 +1,7 @@
title: $:/plugins/tiddlywiki/externaltasksdemo/EditorToolbar/SuggestTagsDropdown
<$button message="tm-server-refresh">Refresh</$button>
Status: {{JobStatus}} {{JobStatus!!message}}
Results: {{PipeOutput}}

View File

@@ -0,0 +1,6 @@
title: Sample Text
Aliquo déditum habet huius, non tempora. Affectus captet declarant retinere támque. Desiderabile dolor posuit státim uterque.
Arbitraretur civium epicureum extremo, gerendarum málá pacuvii. Accumsan alteram amaret conclusionémqué definitiones événiunt firmám homo noctesque quibus refert triá utens. Chóró egestas fugiamus fuissent, impénsa littéras magna ménté quia scientiam sensibus tibi. Bonas eas fieri i, lacinia legantur oratione urbes.

View File

@@ -0,0 +1,3 @@
title: $:/DefaultTiddlers
HelloThere

View File

@@ -0,0 +1,54 @@
title: HelloThere
! External Pipe Demos
Note that the browser polls the server every {{$:/config/SyncPollingInterval}}ms. You can trigger an instaneous poll: <$button message="tm-server-refresh">Refresh</$button>
Status: {{JobStatus}} {{JobStatus!!message}}
!! Reverser
This demo requires the example task `reverser.js 8081` to be running in a separate command window. You can edit the [[Sample Text]].
<$button>
<$action-sendmessage $message="tm-execute-job" 1="--pipe" 2="reverser" 3="[[Sample Text]]" 4="PipeOutput" statusTitle="JobStatus"/>
Reverser
</$button>
!! Recaser
This demo requires the example task `./recaser.js 8081` or `./recase_erl.sh` to be running in a separate command window. You can edit the [[Sample Text]].
<$button>
<$action-sendmessage $message="tm-execute-job" 1="--pipe" 2="recaser" 3="[[Sample Text]]" 4="PipeOutput" statusTitle="JobStatus"/>
Recaser
</$button>
!! Tagger
This demo requires the example task `./badtagger.js 8081` to be running in a separate command window. You can edit the [[Sample Text]].
<$button>
<$action-sendmessage $message="tm-execute-job" 1="--pipe" 2="badtagger" 3="[[Sample Text]]" 4="PipeOutput" statusTitle="JobStatus"/>
Tagger
</$button>
!! Mimic
<$button>
<$action-sendmessage $message="tm-execute-job" 1="--pipe" 2="mimic" 3="[[Alice in Wonderland]]" 4="PipeOutput" 5="5" 6="5000" statusTitle="JobStatus"/>
Mimic
</$button>
!! Stats
<$button>
<$action-sendmessage $message="tm-execute-job" 1="--pipe" 2="stats" 3="[[Alice in Wonderland]]" 4="PipeOutput" statusTitle="JobStatus"/>
Stats
</$button>
!! Demo Output
The output generated by the demos appears here:
{{PipeOutput}}

View File

@@ -0,0 +1,2 @@
title: $:/config/SyncPollingInterval
text: 5000

View File

@@ -0,0 +1,119 @@
{
"description": "Edition demonstrating external pipes",
"plugins": [
"tiddlywiki/tiddlyweb"
],
"themes": [
"tiddlywiki/vanilla",
"tiddlywiki/snowwhite"
],
"build": {
"index": [
"--rendertiddler","$:/core/save/all","index.html","text/plain"]
},
"config": {
"allow-remote-commands": true
},
"external-pipes": {
"reverser": {
"type": "socket",
"host": "127.0.0.1",
"port": 8081,
"input": {
"format": "rendered-text"
},
"output": {
"format": "text",
"tiddler": {
"title": "PipeOutput",
"type": "text/plain"
}
},
"environment": {
"MY_VARIABLE": "value"
},
"timeout": 100
},
"stats": {
"type": "task",
"path": "./demo-tasks/stats.js",
"input": {
"format": "json-raw-tiddlers"
},
"output": {
"format": "json-raw-tiddlers"
},
"error": {
"format": "text",
"tiddler": {
"title": "PipeOutput",
"type": "text/plain",
"result": "error"
}
},
"environment": {
"MY_VARIABLE": "value"
},
"timeout": 100
},
"mimic": {
"type": "task",
"path": "./demo-tasks/mimic.js",
"arguments": ["ngram-length"],
"input": {
"format": "rendered-text"
},
"output": {
"format": "text",
"tiddler": {
"title": "PipeOutput",
"type": "text/plain"
}
},
"environment": {
"MY_VARIABLE": "value"
},
"timeout": 100
},
"recaser": {
"type": "socket-erlang",
"host": "127.0.0.1",
"port": 8081,
"encoding": "latin1",
"input": {
"format": "rendered-text"
},
"output": {
"format": "text",
"tiddler": {
"title": "PipeOutput",
"type": "text/plain"
}
},
"environment": {
"MY_VARIABLE": "value"
},
"timeout": 100
},
"badtagger": {
"type": "socket-erlang",
"host": "127.0.0.1",
"port": 8081,
"encoding": "latin1",
"input": {
"format": "rendered-text"
},
"output": {
"format": "text",
"tiddler": {
"title": "PipeOutput",
"type": "text/plain"
}
},
"environment": {
"MY_VARIABLE": "value"
},
"timeout": 100
}
}
}

View File

@@ -19,28 +19,19 @@ type: text/vnd.tiddlywiki
! Usability Improvements
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/6015">> image picker in theme tweaks to not dismiss when an image is selected
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/5998">> a [[new hidden setting|Hidden Setting: Show Edit Preview per Tiddler]] for controlling the visibility of the editor preview pane on a per-tiddler basis
*
! Widget Improvements
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/6148">> new LetWidget, a more flexible alternative to the SetWidget and the VarsWidget
*
! Filter improvements
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/6150">> new [[zth Operator]] that works like [[nth Operator]] but counts from zero instead of one
* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/pull/6149">> [[Map Filter Run Prefix]] to provide additional variables to the filter
*
! Hackability Improvements
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/6012/files">> new `focus-editor` operation to [[WidgetMessage: tm-edit-text-operation]]
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/6130">> new ActionSetMultipleFieldsWidget, new SetMultipleVariablesWidget and extended ActionSendMessageWidget for working with multiple variables/fields/indexes/parameters in one operation
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/6127">> new trigonometric operators: [[acos|acos Operator]], [[asin|asin Operator]], [[atan|atan Operator]], [[cos|cos Operator]], [[sin|sin Operator]] and [[tan|tan Operator]]
* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/pull/6145">> EditTextWidget to provide a new ''actionValue'' variable to action strings that contains the value of the input
! Developer Improvements
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/6132">> support for widgets to access the order in which attributes are defined
*
! Client-server Improvements
@@ -48,7 +39,7 @@ type: text/vnd.tiddlywiki
! Node.js Improvements
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/5275">> support for recursively loading subdirectories with [[tiddlywiki.files Files]]
*
! Plugin Improvements
@@ -58,25 +49,14 @@ type: text/vnd.tiddlywiki
! Translation improvements
* Polish
*
! Other Bug Fixes
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/6013">> refreshing of LinkWidget attributes
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/6107">> crash when using "source=basename-uri-decoded" in tiddlywiki.files
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/commit/8ae4428332e03a1fdaee26f777a0c3a372fff401">> ''$timestamp'' attribute ignored when using ActionSetFieldWidget to set the value of an index
*
! Acknowledgements
[[@Jermolene|https://github.com/Jermolene]] would like to thank the contributors to this release who have generously given their time to help improve TiddlyWiki:
* <<contributor btheado>>
* <<contributor BurningTreeC>>
* <<contributor EvidentlyCube>>
* <<contributor flibbles>>
* <<contributor joshuafontany>>
* <<contributor Marxsal>>
* <<contributor pmario>>
* <<contributor saqimtiaz>>
* <<contributor Telumire>>
* <<contributor Jermolene>>

View File

@@ -815,10 +815,6 @@ Tests the filtering mechanism.
expect(wiki.filterTiddlers("[[Unlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data]search-replace:g:regexp<myregexp2>,[]]",anchorWidget).join(",")).toBe("conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data");
expect(wiki.filterTiddlers("[[Unlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data]search-replace:gm:regexp<myregexp2>,[]]",anchorWidget).join(",")).toBe("conventional online services, TiddlyWiki lets you choose where to keep your data\nconventional online services, TiddlyWiki lets you choose where to keep your data\nconventional online services, TiddlyWiki lets you choose where to keep your data\nconventional online services, TiddlyWiki lets you choose where to keep your data");
expect(wiki.filterTiddlers("[[Hello There\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\nguaranteeing that in the decades to come you will still be able to use the notes you take today.]search-replace:gm:regexp<myregexp3>,[]]",anchorWidget).join(",")).toBe("\nUnlike conventional online services, TiddlyWiki lets you choose where to keep your data\n");
expect(wiki.filterTiddlers("[[This is equation $$x$$ end.]search-replace[equation $$x$$ end.],[relation $$x$$ finish.]]").join(",")).toBe("This is relation $$x$$ finish.");
expect(wiki.filterTiddlers("[[This is an amazing TiddlyWiki]] [[How old is TiddlyWiki?. TiddlyWiki is great]] [[My TiddlyWiki is so fast.]] +[search-replace:g:regexp[TiddlyWiki],[TW]]").join(",")).toBe("This is an amazing TW,How old is TW?. TW is great,My TW is so fast.");
expect(wiki.filterTiddlers("[[This is an amazing TiddlyWiki]] [[How old is TiddlyWiki?. TiddlyWiki is great]] [[My TiddlyWiki is so fast.]] +[search-replace::regexp[TiddlyWiki],[TW]]").join(",")).toBe("This is an amazing TW,How old is TW?. TiddlyWiki is great,My TW is so fast.");
expect(wiki.filterTiddlers("[[This is an amazing TiddlyWiki]] [[How old is TiddlyWiki?. TiddlyWiki is great]] [[My TiddlyWiki is so fast.]] +[search-replace:g[TiddlyWiki],[TW]]").join(",")).toBe("This is an amazing TW,How old is TW?. TW is great,My TW is so fast.");
});
it("should handle the pad operator", function() {

View File

@@ -288,38 +288,6 @@ describe("'reduce' and 'intersection' filter prefix tests", function() {
tags: ["cakes","with tea"],
text: "Does anyone eat pound cake?"
});
wiki.addTiddler({
title: "$:/filter1",
text: "[tag[cakes]then[It is customary]]",
tags: "$:/tags/Filter $:/tags/SecondFilter"
});
wiki.addTiddler({
title: "$:/filter2",
text: "[<currentTiddler>tag[shopping]then[It is not customary]]",
tags: "$:/tags/Filter $:/tags/SecondFilter"
});
wiki.addTiddler({
title: "$:/filter3",
text: "[[Just a default]]",
tags: "$:/tags/Filter"
});
wiki.addTiddler({
title: "$:/tags/Filter",
list: "$:/filter1 $:/filter2 $:/filter3"
});
wiki.addTiddler({
title: "$:/tags/SecondFilter",
list: "$:/filter1 $:/filter2"
});
it("should handle the :cascade filter prefix", function() {
expect(wiki.filterTiddlers("[[Rice Pudding]] :cascade[all[shadows+tiddlers]tag[$:/tags/Filter]get[text]]").join(",")).toBe("It is not customary");
expect(wiki.filterTiddlers("[[chocolate cake]] :cascade[all[shadows+tiddlers]tag[$:/tags/Filter]get[text]]").join(",")).toBe("It is customary");
expect(wiki.filterTiddlers("[[Sparkling water]] :cascade[all[shadows+tiddlers]tag[$:/tags/Filter]get[text]]").join(",")).toBe("Just a default");
expect(wiki.filterTiddlers("[[Rice Pudding]] :cascade[all[shadows+tiddlers]tag[$:/tags/SecondFilter]get[text]]").join(",")).toBe("It is not customary");
expect(wiki.filterTiddlers("[[chocolate cake]] :cascade[all[shadows+tiddlers]tag[$:/tags/SecondFilter]get[text]]").join(",")).toBe("It is customary");
expect(wiki.filterTiddlers("[[Sparkling water]] :cascade[all[shadows+tiddlers]tag[$:/tags/SecondFilter]get[text]]").join(",")).toBe("");
});
it("should handle the :reduce filter prefix", function() {
expect(wiki.filterTiddlers("[tag[shopping]] :reduce[get[quantity]add<accumulator>]").join(",")).toBe("22");
@@ -416,8 +384,6 @@ describe("'reduce' and 'intersection' filter prefix tests", function() {
expect(wiki.filterTiddlers("[tag[shopping]] :map[get[description]else{!!title}]").join(",")).toBe("A square of rich chocolate cake,a round yellow seed,Milk,Rice Pudding");
// Return the first title from :map if the filter returns more than one result
expect(wiki.filterTiddlers("[tag[shopping]] :map[tags[]]").join(",")).toBe("shopping,shopping,shopping,shopping");
// Prepend the position in the list using the index and length variables
expect(wiki.filterTiddlers("[tag[shopping]] :map[get[title]addprefix[-]addprefix<length>addprefix[of]addprefix<index>]").join(",")).toBe("0of4-Brownies,1of4-Chick Peas,2of4-Milk,3of4-Rice Pudding");
});
});

View File

@@ -247,40 +247,6 @@ describe("Widget module", function() {
expect(wrapper.children[0].children[2].sequenceNumber).toBe(4);
});
it("should deal with the let widget", function() {
var wiki = new $tw.Wiki();
wiki.addTiddlers([
{title: "TiddlerOne", text: "lookup"},
{title: "TiddlerTwo", lookup: "value", newlookup: "value", wrong: "wrong"},
{title: "TiddlerThree", text: "wrong", value: "Happy Result", wrong: "ALL WRONG!!"}
]);
var text="\\define macro() TiddlerThree\n"+
"\\define currentTiddler() TiddlerOne\n"+
"<$let "+
"field={{!!text}} "+
"currentTiddler='TiddlerTwo' "+
"field={{{ [all[current]get<field>] }}} "+
"currentTiddler=<<macro>>>"+
"<$transclude field=<<field>>/></$let>";
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
var wrapper = renderWidgetNode(widgetNode);
expect(wrapper.innerHTML).toBe("<p>Happy Result</p>");
// This is important. $Let needs to be aware enough not to let its
// own variables interfere with its ability to recognize no change.
// Doesn't matter that nothing has changed, we just need to make sure
// it recognizes that that its outward facing variables are unchanged
// EVEN IF some intermediate variables did change, there's no need to
// refresh.
wiki.addTiddler({title: "TiddlerOne", text: "newlookup"});
expect(widgetNode.refresh({})).toBe(false);
// But if we make a change that might result in different outfacing
// variables, then it should refresh
wiki.addTiddler({title: "TiddlerOne", text: "badlookup"});
expect(widgetNode.refresh({})).toBe(true);
});
it("should deal with attributes specified as macro invocations", function() {
var wiki = new $tw.Wiki();
// Construct the widget node

View File

@@ -1,14 +0,0 @@
created: 20211013132515594
modified: 20211018102307833
tags: Learning
title: Icon Gallery
type: text/vnd.tiddlywiki
\define copyActions() <$action-sendmessage $message="tm-copy-to-clipboard" $param=<<imageTitle>>/>
<<.tip "Click an icon to copy the title to the clipboard">>
<div class="tc-image-chooser">
<$macrocall $name="image-picker-list" filter="[all[shadows+tiddlers]tag[$:/tags/Image]]" actions=<<copyActions>> />
</div>

View File

@@ -1,12 +0,0 @@
created: 20211031174746965
modified: 20211031181800684
title: MessageHandlerWidgets
type: text/vnd.tiddlywiki
Message handler widgets are those widgets which can react to one or more [[widget messages|Messages]].
<<.tip "Widget messages are similar to low-level DOM events, except they are higher-level and specific to TiddlyWiki. To handle DOM events see the EventCatcherWidget">>
The following message handler widgets are provided:
<<list-links "[tag[MessageHandlerWidgets]]">>

View File

@@ -1,12 +0,0 @@
created: 20211031172716741
modified: 20211031174029381
title: TriggeringWidgets
type: text/vnd.tiddlywiki
Triggering widgets are a type of widget which can trigger ActionWidgets. Typically these widgets (such as the ButtonWidget) will trigger actions based on interaction from the user.
The following triggering widgets are provided:
<<list-links "[tag[TriggeringWidgets]]">>
See ActionWidgets for more information and examples of how TriggeringWidgets and ActionWidgets work together.

View File

@@ -0,0 +1,24 @@
caption: tm-execute-job
created: 20181017140932589
modified: 20181017171440568
tags: Messages
title: WidgetMessage: tm-execute-job
type: text/vnd.tiddlywiki
<<.from-version "5.1.18">> The "execute job" message triggers the execution of [[remote commands|Using remote commands]]. It uses the message parameters as follows:
|!Name |!Description |
|$param |Not used |
|commands |Command string to be executed |
|0...n |Command tokens to be executed |
|statusTitle |Optional title of tiddler in which to store status updates as the commands are executed |
Note that the command string to be executed can either be passed as a single string, or as a sequence of tokens:
* A single string:
*> `<$action-sendmessage $message="tm-execute-job" commands="--version"/>`
* Separate command tokens with numeric indices:
*> `<$action-sendmessage $message="tm-execute-job" 0="--version" 1="--version"/>`
** Note that the numeric indices must be non-negative integers with no leading zeros
The "execute job" message is handled by the TiddlyWiki core SyncMechanism which invokes the current [[SyncAdaptorModule|SyncAdaptorModules]] (typically the ''tiddlywiki/tiddlywebadaptor'' plugin).

View File

@@ -0,0 +1,360 @@
title: Alice in Wonderland License
tags: text/plain
End of Project Gutenberg's Alice's Adventures in Wonderland, by Lewis Carroll
*** END OF THIS PROJECT GUTENBERG EBOOK ALICE'S ADVENTURES IN WONDERLAND ***
***** This file should be named 11.txt or 11.zip *****
This and all associated files of various formats will be found in:
http://www.gutenberg.org/1/11/
Updated editions will replace the previous one--the old editions
will be renamed.
Creating the works from public domain print editions means that no
one owns a United States copyright in these works, so the Foundation
(and you!) can copy and distribute it in the United States without
permission and without paying copyright royalties. Special rules,
set forth in the General Terms of Use part of this license, apply to
copying and distributing Project Gutenberg-tm electronic works to
protect the PROJECT GUTENBERG-tm concept and trademark. Project
Gutenberg is a registered trademark, and may not be used if you
charge for the eBooks, unless you receive specific permission. If you
do not charge anything for copies of this eBook, complying with the
rules is very easy. You may use this eBook for nearly any purpose
such as creation of derivative works, reports, performances and
research. They may be modified and printed and given away--you may do
practically ANYTHING with public domain eBooks. Redistribution is
subject to the trademark license, especially commercial
redistribution.
*** START: FULL LICENSE ***
THE FULL PROJECT GUTENBERG LICENSE
PLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK
To protect the Project Gutenberg-tm mission of promoting the free
distribution of electronic works, by using or distributing this work
(or any other work associated in any way with the phrase "Project
Gutenberg"), you agree to comply with all the terms of the Full Project
Gutenberg-tm License (available with this file or online at
http://gutenberg.org/license).
Section 1. General Terms of Use and Redistributing Project Gutenberg-tm
electronic works
1.A. By reading or using any part of this Project Gutenberg-tm
electronic work, you indicate that you have read, understand, agree to
and accept all the terms of this license and intellectual property
(trademark/copyright) agreement. If you do not agree to abide by all
the terms of this agreement, you must cease using and return or destroy
all copies of Project Gutenberg-tm electronic works in your possession.
If you paid a fee for obtaining a copy of or access to a Project
Gutenberg-tm electronic work and you do not agree to be bound by the
terms of this agreement, you may obtain a refund from the person or
entity to whom you paid the fee as set forth in paragraph 1.E.8.
1.B. "Project Gutenberg" is a registered trademark. It may only be
used on or associated in any way with an electronic work by people who
agree to be bound by the terms of this agreement. There are a few
things that you can do with most Project Gutenberg-tm electronic works
even without complying with the full terms of this agreement. See
paragraph 1.C below. There are a lot of things you can do with Project
Gutenberg-tm electronic works if you follow the terms of this agreement
and help preserve free future access to Project Gutenberg-tm electronic
works. See paragraph 1.E below.
1.C. The Project Gutenberg Literary Archive Foundation ("the Foundation"
or PGLAF), owns a compilation copyright in the collection of Project
Gutenberg-tm electronic works. Nearly all the individual works in the
collection are in the public domain in the United States. If an
individual work is in the public domain in the United States and you are
located in the United States, we do not claim a right to prevent you from
copying, distributing, performing, displaying or creating derivative
works based on the work as long as all references to Project Gutenberg
are removed. Of course, we hope that you will support the Project
Gutenberg-tm mission of promoting free access to electronic works by
freely sharing Project Gutenberg-tm works in compliance with the terms of
this agreement for keeping the Project Gutenberg-tm name associated with
the work. You can easily comply with the terms of this agreement by
keeping this work in the same format with its attached full Project
Gutenberg-tm License when you share it without charge with others.
1.D. The copyright laws of the place where you are located also govern
what you can do with this work. Copyright laws in most countries are in
a constant state of change. If you are outside the United States, check
the laws of your country in addition to the terms of this agreement
before downloading, copying, displaying, performing, distributing or
creating derivative works based on this work or any other Project
Gutenberg-tm work. The Foundation makes no representations concerning
the copyright status of any work in any country outside the United
States.
1.E. Unless you have removed all references to Project Gutenberg:
1.E.1. The following sentence, with active links to, or other immediate
access to, the full Project Gutenberg-tm License must appear prominently
whenever any copy of a Project Gutenberg-tm work (any work on which the
phrase "Project Gutenberg" appears, or with which the phrase "Project
Gutenberg" is associated) is accessed, displayed, performed, viewed,
copied or distributed:
This eBook is for the use of anyone anywhere at no cost and with
almost no restrictions whatsoever. You may copy it, give it away or
re-use it under the terms of the Project Gutenberg License included
with this eBook or online at www.gutenberg.org
1.E.2. If an individual Project Gutenberg-tm electronic work is derived
from the public domain (does not contain a notice indicating that it is
posted with permission of the copyright holder), the work can be copied
and distributed to anyone in the United States without paying any fees
or charges. If you are redistributing or providing access to a work
with the phrase "Project Gutenberg" associated with or appearing on the
work, you must comply either with the requirements of paragraphs 1.E.1
through 1.E.7 or obtain permission for the use of the work and the
Project Gutenberg-tm trademark as set forth in paragraphs 1.E.8 or
1.E.9.
1.E.3. If an individual Project Gutenberg-tm electronic work is posted
with the permission of the copyright holder, your use and distribution
must comply with both paragraphs 1.E.1 through 1.E.7 and any additional
terms imposed by the copyright holder. Additional terms will be linked
to the Project Gutenberg-tm License for all works posted with the
permission of the copyright holder found at the beginning of this work.
1.E.4. Do not unlink or detach or remove the full Project Gutenberg-tm
License terms from this work, or any files containing a part of this
work or any other work associated with Project Gutenberg-tm.
1.E.5. Do not copy, display, perform, distribute or redistribute this
electronic work, or any part of this electronic work, without
prominently displaying the sentence set forth in paragraph 1.E.1 with
active links or immediate access to the full terms of the Project
Gutenberg-tm License.
1.E.6. You may convert to and distribute this work in any binary,
compressed, marked up, nonproprietary or proprietary form, including any
word processing or hypertext form. However, if you provide access to or
distribute copies of a Project Gutenberg-tm work in a format other than
"Plain Vanilla ASCII" or other format used in the official version
posted on the official Project Gutenberg-tm web site (www.gutenberg.org),
you must, at no additional cost, fee or expense to the user, provide a
copy, a means of exporting a copy, or a means of obtaining a copy upon
request, of the work in its original "Plain Vanilla ASCII" or other
form. Any alternate format must include the full Project Gutenberg-tm
License as specified in paragraph 1.E.1.
1.E.7. Do not charge a fee for access to, viewing, displaying,
performing, copying or distributing any Project Gutenberg-tm works
unless you comply with paragraph 1.E.8 or 1.E.9.
1.E.8. You may charge a reasonable fee for copies of or providing
access to or distributing Project Gutenberg-tm electronic works provided
that
- You pay a royalty fee of 20% of the gross profits you derive from
the use of Project Gutenberg-tm works calculated using the method
you already use to calculate your applicable taxes. The fee is
owed to the owner of the Project Gutenberg-tm trademark, but he
has agreed to donate royalties under this paragraph to the
Project Gutenberg Literary Archive Foundation. Royalty payments
must be paid within 60 days following each date on which you
prepare (or are legally required to prepare) your periodic tax
returns. Royalty payments should be clearly marked as such and
sent to the Project Gutenberg Literary Archive Foundation at the
address specified in Section 4, "Information about donations to
the Project Gutenberg Literary Archive Foundation."
- You provide a full refund of any money paid by a user who notifies
you in writing (or by e-mail) within 30 days of receipt that s/he
does not agree to the terms of the full Project Gutenberg-tm
License. You must require such a user to return or
destroy all copies of the works possessed in a physical medium
and discontinue all use of and all access to other copies of
Project Gutenberg-tm works.
- You provide, in accordance with paragraph 1.F.3, a full refund of any
money paid for a work or a replacement copy, if a defect in the
electronic work is discovered and reported to you within 90 days
of receipt of the work.
- You comply with all other terms of this agreement for free
distribution of Project Gutenberg-tm works.
1.E.9. If you wish to charge a fee or distribute a Project Gutenberg-tm
electronic work or group of works on different terms than are set
forth in this agreement, you must obtain permission in writing from
both the Project Gutenberg Literary Archive Foundation and Michael
Hart, the owner of the Project Gutenberg-tm trademark. Contact the
Foundation as set forth in Section 3 below.
1.F.
1.F.1. Project Gutenberg volunteers and employees expend considerable
effort to identify, do copyright research on, transcribe and proofread
public domain works in creating the Project Gutenberg-tm
collection. Despite these efforts, Project Gutenberg-tm electronic
works, and the medium on which they may be stored, may contain
"Defects," such as, but not limited to, incomplete, inaccurate or
corrupt data, transcription errors, a copyright or other intellectual
property infringement, a defective or damaged disk or other medium, a
computer virus, or computer codes that damage or cannot be read by
your equipment.
1.F.2. LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the "Right
of Replacement or Refund" described in paragraph 1.F.3, the Project
Gutenberg Literary Archive Foundation, the owner of the Project
Gutenberg-tm trademark, and any other party distributing a Project
Gutenberg-tm electronic work under this agreement, disclaim all
liability to you for damages, costs and expenses, including legal
fees. YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT
LIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE
PROVIDED IN PARAGRAPH F3. YOU AGREE THAT THE FOUNDATION, THE
TRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE
LIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR
INCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH
DAMAGE.
1.F.3. LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a
defect in this electronic work within 90 days of receiving it, you can
receive a refund of the money (if any) you paid for it by sending a
written explanation to the person you received the work from. If you
received the work on a physical medium, you must return the medium with
your written explanation. The person or entity that provided you with
the defective work may elect to provide a replacement copy in lieu of a
refund. If you received the work electronically, the person or entity
providing it to you may choose to give you a second opportunity to
receive the work electronically in lieu of a refund. If the second copy
is also defective, you may demand a refund in writing without further
opportunities to fix the problem.
1.F.4. Except for the limited right of replacement or refund set forth
in paragraph 1.F.3, this work is provided to you 'AS-IS' WITH NO OTHER
WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
WARRANTIES OF MERCHANTIBILITY OR FITNESS FOR ANY PURPOSE.
1.F.5. Some states do not allow disclaimers of certain implied
warranties or the exclusion or limitation of certain types of damages.
If any disclaimer or limitation set forth in this agreement violates the
law of the state applicable to this agreement, the agreement shall be
interpreted to make the maximum disclaimer or limitation permitted by
the applicable state law. The invalidity or unenforceability of any
provision of this agreement shall not void the remaining provisions.
1.F.6. INDEMNITY - You agree to indemnify and hold the Foundation, the
trademark owner, any agent or employee of the Foundation, anyone
providing copies of Project Gutenberg-tm electronic works in accordance
with this agreement, and any volunteers associated with the production,
promotion and distribution of Project Gutenberg-tm electronic works,
harmless from all liability, costs and expenses, including legal fees,
that arise directly or indirectly from any of the following which you do
or cause to occur: (a) distribution of this or any Project Gutenberg-tm
work, (b) alteration, modification, or additions or deletions to any
Project Gutenberg-tm work, and (c) any Defect you cause.
Section 2. Information about the Mission of Project Gutenberg-tm
Project Gutenberg-tm is synonymous with the free distribution of
electronic works in formats readable by the widest variety of computers
including obsolete, old, middle-aged and new computers. It exists
because of the efforts of hundreds of volunteers and donations from
people in all walks of life.
Volunteers and financial support to provide volunteers with the
assistance they need, is critical to reaching Project Gutenberg-tm's
goals and ensuring that the Project Gutenberg-tm collection will
remain freely available for generations to come. In 2001, the Project
Gutenberg Literary Archive Foundation was created to provide a secure
and permanent future for Project Gutenberg-tm and future generations.
To learn more about the Project Gutenberg Literary Archive Foundation
and how your efforts and donations can help, see Sections 3 and 4
and the Foundation web page at http://www.pglaf.org.
Section 3. Information about the Project Gutenberg Literary Archive
Foundation
The Project Gutenberg Literary Archive Foundation is a non profit
501(c)(3) educational corporation organized under the laws of the
state of Mississippi and granted tax exempt status by the Internal
Revenue Service. The Foundation's EIN or federal tax identification
number is 64-6221541. Its 501(c)(3) letter is posted at
http://pglaf.org/fundraising. Contributions to the Project Gutenberg
Literary Archive Foundation are tax deductible to the full extent
permitted by U.S. federal laws and your state's laws.
The Foundation's principal office is located at 4557 Melan Dr. S.
Fairbanks, AK, 99712., but its volunteers and employees are scattered
throughout numerous locations. Its business office is located at
809 North 1500 West, Salt Lake City, UT 84116, (801) 596-1887, email
business@pglaf.org. Email contact links and up to date contact
information can be found at the Foundation's web site and official
page at http://pglaf.org
For additional contact information:
Dr. Gregory B. Newby
Chief Executive and Director
gbnewby@pglaf.org
Section 4. Information about Donations to the Project Gutenberg
Literary Archive Foundation
Project Gutenberg-tm depends upon and cannot survive without wide
spread public support and donations to carry out its mission of
increasing the number of public domain and licensed works that can be
freely distributed in machine readable form accessible by the widest
array of equipment including outdated equipment. Many small donations
($1 to $5,000) are particularly important to maintaining tax exempt
status with the IRS.
The Foundation is committed to complying with the laws regulating
charities and charitable donations in all 50 states of the United
States. Compliance requirements are not uniform and it takes a
considerable effort, much paperwork and many fees to meet and keep up
with these requirements. We do not solicit donations in locations
where we have not received written confirmation of compliance. To
SEND DONATIONS or determine the status of compliance for any
particular state visit http://pglaf.org
While we cannot and do not solicit contributions from states where we
have not met the solicitation requirements, we know of no prohibition
against accepting unsolicited donations from donors in such states who
approach us with offers to donate.
International donations are gratefully accepted, but we cannot make
any statements concerning tax treatment of donations received from
outside the United States. U.S. laws alone swamp our small staff.
Please check the Project Gutenberg Web pages for current donation
methods and addresses. Donations are accepted in a number of other
ways including checks, online payments and credit card donations.
To donate, please visit: http://pglaf.org/donate
Section 5. General Information About Project Gutenberg-tm electronic
works.
Professor Michael S. Hart is the originator of the Project Gutenberg-tm
concept of a library of electronic works that could be freely shared
with anyone. For thirty years, he produced and distributed Project
Gutenberg-tm eBooks with only a loose network of volunteer support.
Project Gutenberg-tm eBooks are often created from several printed
editions, all of which are confirmed as Public Domain in the U.S.
unless a copyright notice is included. Thus, we do not necessarily
keep eBooks in compliance with any particular paper edition.
Most people start at our Web site which has the main PG search facility:
http://www.gutenberg.org
This Web site includes information about Project Gutenberg-tm,
including how to make donations to the Project Gutenberg Literary
Archive Foundation, how to help produce our new eBooks, and how to
subscribe to our email newsletter to hear about new eBooks.

View File

@@ -1,15 +1,14 @@
title: Alice in Wonderland
tags: external-text
license: Alice in Wonderland License
//Included here as a demonstration of ExternalText text support. See the bottom for the license from Project Gutenberg//
---
# ALICE'S ADVENTURES IN WONDERLAND
! ALICE'S ADVENTURES IN WONDERLAND
## Lewis Carroll
THE MILLENNIUM FULCRUM EDITION 3.0
!! Lewis Carroll
!!! CHAPTER I. Down the Rabbit-Hole
@@ -3341,369 +3340,3 @@ all their simple sorrows, and find a pleasure in all their simple joys,
remembering her own child-life, and the happy summer days.
THE END
End of Project Gutenberg's Alice's Adventures in Wonderland, by Lewis Carroll
*** END OF THIS PROJECT GUTENBERG EBOOK ALICE'S ADVENTURES IN WONDERLAND ***
***** This file should be named 11.txt or 11.zip *****
This and all associated files of various formats will be found in:
http://www.gutenberg.org/1/11/
Updated editions will replace the previous one--the old editions
will be renamed.
Creating the works from public domain print editions means that no
one owns a United States copyright in these works, so the Foundation
(and you!) can copy and distribute it in the United States without
permission and without paying copyright royalties. Special rules,
set forth in the General Terms of Use part of this license, apply to
copying and distributing Project Gutenberg-tm electronic works to
protect the PROJECT GUTENBERG-tm concept and trademark. Project
Gutenberg is a registered trademark, and may not be used if you
charge for the eBooks, unless you receive specific permission. If you
do not charge anything for copies of this eBook, complying with the
rules is very easy. You may use this eBook for nearly any purpose
such as creation of derivative works, reports, performances and
research. They may be modified and printed and given away--you may do
practically ANYTHING with public domain eBooks. Redistribution is
subject to the trademark license, especially commercial
redistribution.
*** START: FULL LICENSE ***
THE FULL PROJECT GUTENBERG LICENSE
PLEASE READ THIS BEFORE YOU DISTRIBUTE OR USE THIS WORK
To protect the Project Gutenberg-tm mission of promoting the free
distribution of electronic works, by using or distributing this work
(or any other work associated in any way with the phrase "Project
Gutenberg"), you agree to comply with all the terms of the Full Project
Gutenberg-tm License (available with this file or online at
http://gutenberg.org/license).
Section 1. General Terms of Use and Redistributing Project Gutenberg-tm
electronic works
1.A. By reading or using any part of this Project Gutenberg-tm
electronic work, you indicate that you have read, understand, agree to
and accept all the terms of this license and intellectual property
(trademark/copyright) agreement. If you do not agree to abide by all
the terms of this agreement, you must cease using and return or destroy
all copies of Project Gutenberg-tm electronic works in your possession.
If you paid a fee for obtaining a copy of or access to a Project
Gutenberg-tm electronic work and you do not agree to be bound by the
terms of this agreement, you may obtain a refund from the person or
entity to whom you paid the fee as set forth in paragraph 1.E.8.
1.B. "Project Gutenberg" is a registered trademark. It may only be
used on or associated in any way with an electronic work by people who
agree to be bound by the terms of this agreement. There are a few
things that you can do with most Project Gutenberg-tm electronic works
even without complying with the full terms of this agreement. See
paragraph 1.C below. There are a lot of things you can do with Project
Gutenberg-tm electronic works if you follow the terms of this agreement
and help preserve free future access to Project Gutenberg-tm electronic
works. See paragraph 1.E below.
1.C. The Project Gutenberg Literary Archive Foundation ("the Foundation"
or PGLAF), owns a compilation copyright in the collection of Project
Gutenberg-tm electronic works. Nearly all the individual works in the
collection are in the public domain in the United States. If an
individual work is in the public domain in the United States and you are
located in the United States, we do not claim a right to prevent you from
copying, distributing, performing, displaying or creating derivative
works based on the work as long as all references to Project Gutenberg
are removed. Of course, we hope that you will support the Project
Gutenberg-tm mission of promoting free access to electronic works by
freely sharing Project Gutenberg-tm works in compliance with the terms of
this agreement for keeping the Project Gutenberg-tm name associated with
the work. You can easily comply with the terms of this agreement by
keeping this work in the same format with its attached full Project
Gutenberg-tm License when you share it without charge with others.
1.D. The copyright laws of the place where you are located also govern
what you can do with this work. Copyright laws in most countries are in
a constant state of change. If you are outside the United States, check
the laws of your country in addition to the terms of this agreement
before downloading, copying, displaying, performing, distributing or
creating derivative works based on this work or any other Project
Gutenberg-tm work. The Foundation makes no representations concerning
the copyright status of any work in any country outside the United
States.
1.E. Unless you have removed all references to Project Gutenberg:
1.E.1. The following sentence, with active links to, or other immediate
access to, the full Project Gutenberg-tm License must appear prominently
whenever any copy of a Project Gutenberg-tm work (any work on which the
phrase "Project Gutenberg" appears, or with which the phrase "Project
Gutenberg" is associated) is accessed, displayed, performed, viewed,
copied or distributed:
This eBook is for the use of anyone anywhere at no cost and with
almost no restrictions whatsoever. You may copy it, give it away or
re-use it under the terms of the Project Gutenberg License included
with this eBook or online at www.gutenberg.org
1.E.2. If an individual Project Gutenberg-tm electronic work is derived
from the public domain (does not contain a notice indicating that it is
posted with permission of the copyright holder), the work can be copied
and distributed to anyone in the United States without paying any fees
or charges. If you are redistributing or providing access to a work
with the phrase "Project Gutenberg" associated with or appearing on the
work, you must comply either with the requirements of paragraphs 1.E.1
through 1.E.7 or obtain permission for the use of the work and the
Project Gutenberg-tm trademark as set forth in paragraphs 1.E.8 or
1.E.9.
1.E.3. If an individual Project Gutenberg-tm electronic work is posted
with the permission of the copyright holder, your use and distribution
must comply with both paragraphs 1.E.1 through 1.E.7 and any additional
terms imposed by the copyright holder. Additional terms will be linked
to the Project Gutenberg-tm License for all works posted with the
permission of the copyright holder found at the beginning of this work.
1.E.4. Do not unlink or detach or remove the full Project Gutenberg-tm
License terms from this work, or any files containing a part of this
work or any other work associated with Project Gutenberg-tm.
1.E.5. Do not copy, display, perform, distribute or redistribute this
electronic work, or any part of this electronic work, without
prominently displaying the sentence set forth in paragraph 1.E.1 with
active links or immediate access to the full terms of the Project
Gutenberg-tm License.
1.E.6. You may convert to and distribute this work in any binary,
compressed, marked up, nonproprietary or proprietary form, including any
word processing or hypertext form. However, if you provide access to or
distribute copies of a Project Gutenberg-tm work in a format other than
"Plain Vanilla ASCII" or other format used in the official version
posted on the official Project Gutenberg-tm web site (www.gutenberg.org),
you must, at no additional cost, fee or expense to the user, provide a
copy, a means of exporting a copy, or a means of obtaining a copy upon
request, of the work in its original "Plain Vanilla ASCII" or other
form. Any alternate format must include the full Project Gutenberg-tm
License as specified in paragraph 1.E.1.
1.E.7. Do not charge a fee for access to, viewing, displaying,
performing, copying or distributing any Project Gutenberg-tm works
unless you comply with paragraph 1.E.8 or 1.E.9.
1.E.8. You may charge a reasonable fee for copies of or providing
access to or distributing Project Gutenberg-tm electronic works provided
that
- You pay a royalty fee of 20% of the gross profits you derive from
the use of Project Gutenberg-tm works calculated using the method
you already use to calculate your applicable taxes. The fee is
owed to the owner of the Project Gutenberg-tm trademark, but he
has agreed to donate royalties under this paragraph to the
Project Gutenberg Literary Archive Foundation. Royalty payments
must be paid within 60 days following each date on which you
prepare (or are legally required to prepare) your periodic tax
returns. Royalty payments should be clearly marked as such and
sent to the Project Gutenberg Literary Archive Foundation at the
address specified in Section 4, "Information about donations to
the Project Gutenberg Literary Archive Foundation."
- You provide a full refund of any money paid by a user who notifies
you in writing (or by e-mail) within 30 days of receipt that s/he
does not agree to the terms of the full Project Gutenberg-tm
License. You must require such a user to return or
destroy all copies of the works possessed in a physical medium
and discontinue all use of and all access to other copies of
Project Gutenberg-tm works.
- You provide, in accordance with paragraph 1.F.3, a full refund of any
money paid for a work or a replacement copy, if a defect in the
electronic work is discovered and reported to you within 90 days
of receipt of the work.
- You comply with all other terms of this agreement for free
distribution of Project Gutenberg-tm works.
1.E.9. If you wish to charge a fee or distribute a Project Gutenberg-tm
electronic work or group of works on different terms than are set
forth in this agreement, you must obtain permission in writing from
both the Project Gutenberg Literary Archive Foundation and Michael
Hart, the owner of the Project Gutenberg-tm trademark. Contact the
Foundation as set forth in Section 3 below.
1.F.
1.F.1. Project Gutenberg volunteers and employees expend considerable
effort to identify, do copyright research on, transcribe and proofread
public domain works in creating the Project Gutenberg-tm
collection. Despite these efforts, Project Gutenberg-tm electronic
works, and the medium on which they may be stored, may contain
"Defects," such as, but not limited to, incomplete, inaccurate or
corrupt data, transcription errors, a copyright or other intellectual
property infringement, a defective or damaged disk or other medium, a
computer virus, or computer codes that damage or cannot be read by
your equipment.
1.F.2. LIMITED WARRANTY, DISCLAIMER OF DAMAGES - Except for the "Right
of Replacement or Refund" described in paragraph 1.F.3, the Project
Gutenberg Literary Archive Foundation, the owner of the Project
Gutenberg-tm trademark, and any other party distributing a Project
Gutenberg-tm electronic work under this agreement, disclaim all
liability to you for damages, costs and expenses, including legal
fees. YOU AGREE THAT YOU HAVE NO REMEDIES FOR NEGLIGENCE, STRICT
LIABILITY, BREACH OF WARRANTY OR BREACH OF CONTRACT EXCEPT THOSE
PROVIDED IN PARAGRAPH F3. YOU AGREE THAT THE FOUNDATION, THE
TRADEMARK OWNER, AND ANY DISTRIBUTOR UNDER THIS AGREEMENT WILL NOT BE
LIABLE TO YOU FOR ACTUAL, DIRECT, INDIRECT, CONSEQUENTIAL, PUNITIVE OR
INCIDENTAL DAMAGES EVEN IF YOU GIVE NOTICE OF THE POSSIBILITY OF SUCH
DAMAGE.
1.F.3. LIMITED RIGHT OF REPLACEMENT OR REFUND - If you discover a
defect in this electronic work within 90 days of receiving it, you can
receive a refund of the money (if any) you paid for it by sending a
written explanation to the person you received the work from. If you
received the work on a physical medium, you must return the medium with
your written explanation. The person or entity that provided you with
the defective work may elect to provide a replacement copy in lieu of a
refund. If you received the work electronically, the person or entity
providing it to you may choose to give you a second opportunity to
receive the work electronically in lieu of a refund. If the second copy
is also defective, you may demand a refund in writing without further
opportunities to fix the problem.
1.F.4. Except for the limited right of replacement or refund set forth
in paragraph 1.F.3, this work is provided to you 'AS-IS' WITH NO OTHER
WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
WARRANTIES OF MERCHANTIBILITY OR FITNESS FOR ANY PURPOSE.
1.F.5. Some states do not allow disclaimers of certain implied
warranties or the exclusion or limitation of certain types of damages.
If any disclaimer or limitation set forth in this agreement violates the
law of the state applicable to this agreement, the agreement shall be
interpreted to make the maximum disclaimer or limitation permitted by
the applicable state law. The invalidity or unenforceability of any
provision of this agreement shall not void the remaining provisions.
1.F.6. INDEMNITY - You agree to indemnify and hold the Foundation, the
trademark owner, any agent or employee of the Foundation, anyone
providing copies of Project Gutenberg-tm electronic works in accordance
with this agreement, and any volunteers associated with the production,
promotion and distribution of Project Gutenberg-tm electronic works,
harmless from all liability, costs and expenses, including legal fees,
that arise directly or indirectly from any of the following which you do
or cause to occur: (a) distribution of this or any Project Gutenberg-tm
work, (b) alteration, modification, or additions or deletions to any
Project Gutenberg-tm work, and (c) any Defect you cause.
Section 2. Information about the Mission of Project Gutenberg-tm
Project Gutenberg-tm is synonymous with the free distribution of
electronic works in formats readable by the widest variety of computers
including obsolete, old, middle-aged and new computers. It exists
because of the efforts of hundreds of volunteers and donations from
people in all walks of life.
Volunteers and financial support to provide volunteers with the
assistance they need, is critical to reaching Project Gutenberg-tm's
goals and ensuring that the Project Gutenberg-tm collection will
remain freely available for generations to come. In 2001, the Project
Gutenberg Literary Archive Foundation was created to provide a secure
and permanent future for Project Gutenberg-tm and future generations.
To learn more about the Project Gutenberg Literary Archive Foundation
and how your efforts and donations can help, see Sections 3 and 4
and the Foundation web page at http://www.pglaf.org.
Section 3. Information about the Project Gutenberg Literary Archive
Foundation
The Project Gutenberg Literary Archive Foundation is a non profit
501(c)(3) educational corporation organized under the laws of the
state of Mississippi and granted tax exempt status by the Internal
Revenue Service. The Foundation's EIN or federal tax identification
number is 64-6221541. Its 501(c)(3) letter is posted at
http://pglaf.org/fundraising. Contributions to the Project Gutenberg
Literary Archive Foundation are tax deductible to the full extent
permitted by U.S. federal laws and your state's laws.
The Foundation's principal office is located at 4557 Melan Dr. S.
Fairbanks, AK, 99712., but its volunteers and employees are scattered
throughout numerous locations. Its business office is located at
809 North 1500 West, Salt Lake City, UT 84116, (801) 596-1887, email
business@pglaf.org. Email contact links and up to date contact
information can be found at the Foundation's web site and official
page at http://pglaf.org
For additional contact information:
Dr. Gregory B. Newby
Chief Executive and Director
gbnewby@pglaf.org
Section 4. Information about Donations to the Project Gutenberg
Literary Archive Foundation
Project Gutenberg-tm depends upon and cannot survive without wide
spread public support and donations to carry out its mission of
increasing the number of public domain and licensed works that can be
freely distributed in machine readable form accessible by the widest
array of equipment including outdated equipment. Many small donations
($1 to $5,000) are particularly important to maintaining tax exempt
status with the IRS.
The Foundation is committed to complying with the laws regulating
charities and charitable donations in all 50 states of the United
States. Compliance requirements are not uniform and it takes a
considerable effort, much paperwork and many fees to meet and keep up
with these requirements. We do not solicit donations in locations
where we have not received written confirmation of compliance. To
SEND DONATIONS or determine the status of compliance for any
particular state visit http://pglaf.org
While we cannot and do not solicit contributions from states where we
have not met the solicitation requirements, we know of no prohibition
against accepting unsolicited donations from donors in such states who
approach us with offers to donate.
International donations are gratefully accepted, but we cannot make
any statements concerning tax treatment of donations received from
outside the United States. U.S. laws alone swamp our small staff.
Please check the Project Gutenberg Web pages for current donation
methods and addresses. Donations are accepted in a number of other
ways including checks, online payments and credit card donations.
To donate, please visit: http://pglaf.org/donate
Section 5. General Information About Project Gutenberg-tm electronic
works.
Professor Michael S. Hart is the originator of the Project Gutenberg-tm
concept of a library of electronic works that could be freely shared
with anyone. For thirty years, he produced and distributed Project
Gutenberg-tm eBooks with only a loose network of volunteer support.
Project Gutenberg-tm eBooks are often created from several printed
editions, all of which are confirmed as Public Domain in the U.S.
unless a copyright notice is included. Thus, we do not necessarily
keep eBooks in compliance with any particular paper edition.
Most people start at our Web site which has the main PG search facility:
http://www.gutenberg.org
This Web site includes information about Project Gutenberg-tm,
including how to make donations to the Project Gutenberg Literary
Archive Foundation, how to help produce our new eBooks, and how to
subscribe to our email newsletter to hear about new eBooks.

View File

@@ -1,9 +0,0 @@
title: Demo Tiddler List with Custom Story Tiddler Template
tags: $:/tags/TiddlerList
filter: HelloThere Community GettingStarted Features Reference Plugins Learning
This is a demo tiddler with a custom story tiddler template. See [[Story Tiddler Templates]] for details.
Click to close this tiddler: {{||$:/core/ui/Buttons/close}}
Click to edit this tiddler: {{||$:/core/ui/Buttons/edit}}

View File

@@ -1,31 +0,0 @@
title: $:/_tw5.com/CustomStoryTiddlerTemplateDemo/Styles
tags: $:/tags/Stylesheet
.tc-custom-tiddler-template {
border: 3px solid <<colour muted-foreground>>;
border-radius: 1em;
margin-bottom: 1em;
}
.tc-custom-tiddler-template-inner {
background: <<colour muted-foreground>>;
padding: 1em;
}
.tc-custom-tiddler-template-list {
position: relative;
height: 33vh;
}
.tc-custom-tiddler-template-list .tc-custom-tiddler-template-list-item {
position: absolute;
width: 100%;
transform-origin: 50% 0;
top: 50px;
left: 0;
<<box-shadow "0px 0px 15px rgba(0, 0, 0, 0.3)">>
}
.tc-custom-tiddler-template-list .tc-custom-tiddler-template-list-item .tc-tiddler-frame {
margin: 0;
}

View File

@@ -1,22 +0,0 @@
title: $:/_tw5.com/CustomStoryTiddlerTemplateDemo/Template
\define list-item-styles()
transform: translate($(left)$%,$(top)$%) scale(0.3) rotate($(angle)$deg);
\end
<div class="tc-custom-tiddler-template">
<div class="tc-custom-tiddler-template-inner">
<$transclude mode="block"/>
</div>
<div class="tc-custom-tiddler-template-list">
<$let numItems={{{ [subfilter{!!filter}count[]] }}} angleIncrement={{{ [[45]divide<numItems>] }}} posIncrement={{{ [[90]divide<numItems>] }}}>
<$list filter={{!!filter}} counter="counter">
<$let angle={{{ [<counter>subtract[1]multiply<angleIncrement>subtract[22.5]] }}} left={{{ [<counter>subtract[1]multiply<posIncrement>subtract[45]] }}} top={{{ 0 }}}>
<div class="tc-custom-tiddler-template-list-item" style=<<list-item-styles>>>
{{||$:/core/ui/ViewTemplate}}
</div>
</$let>
</$list>
</$let>
</div>
</div>

View File

@@ -1,5 +0,0 @@
title: $:/_tw5.com/CustomStoryTiddlerTemplateDemo/Filter
tags: $:/tags/StoryTiddlerTemplateFilter
list-before: $:/config/StoryTiddlerTemplateFilters/default
[tag[$:/tags/TiddlerList]then[$:/_tw5.com/CustomStoryTiddlerTemplateDemo/Template]]

View File

@@ -1,5 +0,0 @@
title: $:/_tw5.com/CustomTiddlerIconCascadeDemo
tags: $:/tags/TiddlerIconFilter
[tag[TableOfContents]then[$:/core/images/globe]]
[tag[Working with TiddlyWiki]then[$:/core/images/help]]

View File

@@ -1,11 +1,22 @@
created: 20140809113603449
modified: 20211022195248529
modified: 20150520161451179
tags: Learning
title: ImageGallery Example
type: text/vnd.tiddlywiki
<<.warning "The [[ImageGallery Example]] has been replaced with the new [[Icon Gallery]] which is transcluded below">>
Here is an example of using the ListWidget and the TranscludeWidget to show a grid of all system images (ie, tiddlers tagged [[$:/tags/Image]]).
---
{{Icon Gallery}}
<style>
.my-gallery svg {
width: 6em;
height: 6em;
margin: 1em;
}
</style>
<div class="my-gallery">
<$list filter="[all[tiddlers+shadows]tag[$:/tags/Image]]">
<span title=<<currentTiddler>>>
<$transclude/>
</span>
</$list>
</div>

View File

@@ -1,5 +1,5 @@
created: 20140206214608586
modified: 20211009145417525
modified: 20180701185417525
tags: Features
title: LazyLoading
type: text/vnd.tiddlywiki
@@ -11,7 +11,7 @@ Lazy loading can be used in two configurations:
* When running [[TiddlyWiki on Node.js]], just image tiddlers or all non-system tiddlers can be subject to lazy loading
* When running [[TiddlyWiki in the Sky for TiddlyWeb]], all tiddlers are subject to lazy loading
See the [[Lazy Loading Mechanism|https://tiddlywiki.com/dev/#LazyLoadingMechanism]] for details of how lazy loading is implemented.
See the LazyLoadingMechanism for details of how lazy loading is implemented.
! Lazy loading under Node.js
@@ -28,3 +28,4 @@ To apply lazy loading to all non-system tiddlers use this command:
```
tiddlywiki --listen root-tiddler=$:/core/save/lazy-all
```

View File

@@ -5,7 +5,7 @@ modified: 20210506130322593
op-input: ignored
op-output: the title of each available deserializer
op-parameter: none
tags: [[Filter Operators]] [[Special Operators]] [[Selection Constructors]]
tags: [[Filter Operators]] [[Special Operators]]
title: deserializers Operator
type: text/vnd.tiddlywiki

View File

@@ -6,7 +6,7 @@ op-output: the titles stored as a [[title list|Title List]] in each input title
op-purpose: select titles by interpreting each input title as a [[title list|Title List]]
op-suffix: `dedupe` (the default) to remove duplicates, `raw` to leave duplicates untouched
op-suffix-name: D
tags: [[Filter Operators]] [[String Operators]]
tags: [[Filter Operators]] [[String Operators]] [[Selection Constructors]]
title: enlist-input Operator
type: text/vnd.tiddlywiki

View File

@@ -18,4 +18,4 @@ Cycle through a list of values to add as a tag, in reverse order:
<<.using-days-of-week>>
<<.operator-example 1 """[list[Days of the Week]first[]] +[cycle{Days of the Week!!list}]""">>
<<.operator-example 2 """[list[Days of the Week]first[]] +[cycle{Days of the Week!!list},[2]]""">>
<<.operator-example 1 """[list[Days of the Week]first[]] +[cycle{Days of the Week!!list},[2]]""">>

View File

@@ -11,4 +11,4 @@ Logarithm of `100` with base `10`:
<<.operator-example 2 "[[100]log[10]]">>
Natural logarithm of 10 (base `e`), equivalent to `ln(10)` in mathematics:
<<.operator-example 3 "[[10]log[]]">>
<<.operator-example 2 "[[10]log[]]">>

View File

@@ -5,4 +5,4 @@ title: match Operator (Examples)
type: text/vnd.tiddlywiki
<<.operator-example 1 "a b c +[match[b]]">>
<<.operator-example 2 "[match[HelloThere]]">>
<<.operator-example 1 "[match[HelloThere]]">>

View File

@@ -1,5 +1,5 @@
created: 20201107112846692
modified: 20211101125225197
modified: 20201118103305351
tags: [[Operator Examples]] [[search-replace Operator]]
title: search-replace Operator (Examples)
type: text/vnd.tiddlywiki
@@ -25,14 +25,8 @@ You can also use regular expression capture groups in the replacement string:
`\define names() (\w+)\s(\w+)`
<<.operator-example 4 """[[John Smith]search-replace::regexp<names>,[$2,$1]]""" >>
You can reference the portion of the input that matches the regular expression with `$&`:
<<.operator-example 5 """[[John Smith]search-replace::regexp[John .*],[His name is $&]]""">>
<<.operator-example 6 """[[This is an exciting feature]search-replace::regexp[exciting],[amazing and $&]]""">>
To replace everything but a match using a regular expression and the ''multiline'' (m) flag:
`\define myregexp2() ^(?!Unlike).*$`
<<.operator-example 7 """[[HelloThere]get[text]search-replace:gm:regexp<myregexp2>,[]]""">>
<<.operator-example 5 """[[HelloThere]get[text]search-replace:gm:regexp<myregexp2>,[]]""">>
{{How to remove stop words}}

View File

@@ -1,30 +1,8 @@
created: 20181031175129475
modified: 20211030223407188
modified: 20181031175129475
tags: [[subfilter Operator]] [[Operator Examples]]
title: subfilter Operator (Examples)
type: text/vnd.tiddlywiki
\define recent-mods() [has[modified]!sort[modified]limit[5]]
\define display-variable(name)
''<$text text=<<__name__>>/>'': <code><$text text={{{ [<__name__>getvariable[]] }}}/></code>
\end
Literal filter operands can be used, but such cases are better rewritten without using <<.op subfilter>>:
<<.operator-example 1 "[subfilter[one two three]addsuffix[!]]" "same as `one two three +[addsuffix[!]]`">>
The <<.op subfilter>> operator can be used to dynamically define parts of a [[filter run|Filter Run]]. This is useful for sharing a common pieces of a filter across multiple filters.
For example, this variable:
* <<display-variable recent-mods>>
can be used in one filter like this:
<<.operator-example 2 "[tag[Filter Operators]subfilter<recent-mods>addsuffix[!]]" "same as `[tag[Filter Operators]has[modified]!sort[modified]limit[5]addsuffix[!]]`">>
and in another similar filter like this:
<<.operator-example 3 "[tag[ActionWidgets]subfilter<recent-mods>addprefix[!]]">>
Variables are not the only way to define dynamic subfilters. [[Text references|TextReference]] can be used to load a subfilter from a tiddler field (see also [[Filter Parameter]]):
<<.operator-example 4 "[subfilter{$:/StoryList!!list}limit[5]]">>
<<.operator-example 5 "[subfilter{$:/StoryList!!list}subfilter<recent-mods>]">>
<<.operator-example 1 "[subfilter[one two three]addsuffix[!]]">>
<<.operator-example 2 "[subfilter{$:/StoryList!!list}]">>

View File

@@ -1,10 +0,0 @@
created: 20211029023246203
modified: 20211029023559616
tags: [[Operator Examples]] [[zth Operator]]
title: zth Operator (Examples)
type: text/vnd.tiddlywiki
<<.using-days-of-week>>
<<.operator-example 1 "[list[Days of the Week]zth[]]">>
<<.operator-example 2 "[list[Days of the Week]zth[5]]">>

View File

@@ -1,17 +1,15 @@
caption: nth
created: 20150122204111000
modified: 20211029023739450
op-input: a [[selection of titles|Title Selection]]
op-output: the <<.place N>>th input title
op-parameter: an integer, defaulting to 1
op-parameter-name: N
op-purpose: select the <<.place N>>th input title
modified: 20150203192048000
tags: [[Filter Operators]] [[Order Operators]]
title: nth Operator
type: text/vnd.tiddlywiki
caption: nth
op-purpose: select the <<.place N>>th input title
op-input: a [[selection of titles|Title Selection]]
op-parameter: an integer, defaulting to 1
op-parameter-name: N
op-output: the <<.place N>>th input title
<<.place N>> is one-based. In other words, `nth[1]` has the same effect as the <<.olink first>> operator.
<<.tip "See <<.olink zth>> for an equivalent operator with a 0 based parameter">>
<<.operator-examples "nth">>

View File

@@ -1,6 +1,6 @@
created: 20171221184734665
modified: 20210907170339891
tags: [[Filter Operators]] [[Negatable Operators]] [[Selection Constructors]]
tags: [[Filter Operators]] [[Negatable Operators]]
title: range Operator
type: text/vnd.tiddlywiki
caption: range

View File

@@ -1,6 +1,6 @@
created: 20150126141718000
modified: 20150203191420000
tags: [[Filter Operators]] [[Special Operators]] [[Selection Constructors]]
tags: [[Filter Operators]] [[Special Operators]]
title: storyviews Operator
type: text/vnd.tiddlywiki
caption: storyviews

View File

@@ -1,6 +1,6 @@
caption: subfilter
created: 20181031175129475
modified: 20211030223407188
modified: 20181031175129475
op-input: a [[selection of titles|Title Selection]] passed as input to the subfilter
op-neg-input: a [[selection of titles|Title Selection]] passed as input to the subfilter
op-neg-output: those input titles that are <<.em not>> returned from the subfilter <<.place S>>
@@ -12,7 +12,7 @@ tags: [[Filter Operators]] [[Field Operators]] [[Selection Constructors]] [[Nega
title: subfilter Operator
type: text/vnd.tiddlywiki
<<.from-version "5.1.18">> Note that the <<.op subfilter>> operator was introduced in version 5.1.18 and is not available in earlier versions.
<<.from-version "5.1.18">> Note that the subfilter operator was introduced in version 5.1.18 and is not available in earlier versions.
<<.tip " Literal filter operands cannot contain square brackets but you can work around the issue by using a variable:">>
@@ -24,6 +24,4 @@ type: text/vnd.tiddlywiki
<<.tip "Compare with the similar [[filter|filter Operator]] operator which runs a subfilter against each title, returning those titles that return a non-empty list (and discards the results of the subfilter)">>
The <<.op subfilter>> operator will act as a [[constructor|Selection Constructors]] whenever the filter defined by its operand is a [[constructor|Selection Constructors]]. Otherwise, it will act as a [[modifier|Selection Constructors]].
<<.operator-examples "subfilter">>

Some files were not shown because too many files have changed in this diff Show More