mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2026-01-29 22:30:23 +00:00
Compare commits
182 Commits
publishing
...
external-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75c1e4b4d8 | ||
|
|
979f079c7a | ||
|
|
d695fca301 | ||
|
|
a0d2392f01 | ||
|
|
23b4e03cd5 | ||
|
|
d5f72cb282 | ||
|
|
5f57bf81cd | ||
|
|
e32c9e4658 | ||
|
|
2a73505508 | ||
|
|
4d87ef4231 | ||
|
|
c543036a0f | ||
|
|
8ae4428332 | ||
|
|
2f133a08aa | ||
|
|
6b17e688da | ||
|
|
8654066f03 | ||
|
|
56dae90425 | ||
|
|
1f6ef07860 | ||
|
|
f30a455ee3 | ||
|
|
03bd99cd11 | ||
|
|
cc389ec82b | ||
|
|
f85678b6dc | ||
|
|
8d7c869072 | ||
|
|
8f592d3f0a | ||
|
|
1f2e0ed189 | ||
|
|
4d6c428ba9 | ||
|
|
d095aa0182 | ||
|
|
15bf850280 | ||
|
|
62b273266e | ||
|
|
1d058177be | ||
|
|
14676b345a | ||
|
|
a50e6eed38 | ||
|
|
073a064ae8 | ||
|
|
e00a3d3cb4 | ||
|
|
68d9200a6b | ||
|
|
2551bb3e3f | ||
|
|
80b18e6315 | ||
|
|
e0561397f1 | ||
|
|
b9c3c38edc | ||
|
|
077ced0d1a | ||
|
|
e5ff84035c | ||
|
|
e18a983209 | ||
|
|
6f61fa07fb | ||
|
|
d5d73e02e9 | ||
|
|
4ba7454d8d | ||
|
|
5192a39830 | ||
|
|
aa5413b942 | ||
|
|
ef284e9bde | ||
|
|
9c41fe1d18 | ||
|
|
e577cf7302 | ||
|
|
ebf563ac70 | ||
|
|
909340c6fe | ||
|
|
df6d38df65 | ||
|
|
59a53e695b | ||
|
|
6d17505f7b | ||
|
|
36dd8ea1d2 | ||
|
|
b965ae926b | ||
|
|
f6eadbd1c9 | ||
|
|
bdbb884be0 | ||
|
|
0be39cfbc2 | ||
|
|
ef2aeac7de | ||
|
|
157afda2fc | ||
|
|
fb4d77ef46 | ||
|
|
575c233597 | ||
|
|
2e59d770f7 | ||
|
|
baf0ee9cde | ||
|
|
af89bb591d | ||
|
|
d0dec741ad | ||
|
|
33a82e395e | ||
|
|
f223896c26 | ||
|
|
de33b365ae | ||
|
|
e9d8547a81 | ||
|
|
dcff318a98 | ||
|
|
f725123690 | ||
|
|
e9e5d37ff0 | ||
|
|
f70cee6907 | ||
|
|
1491339f50 | ||
|
|
8a5ed59ff4 | ||
|
|
a2ca5e4d1e | ||
|
|
fba993502d | ||
|
|
9fae3a932b | ||
|
|
62610f0666 | ||
|
|
7282ec5286 | ||
|
|
9830e0104f | ||
|
|
e6fd0caf6b | ||
|
|
137df37bc7 | ||
|
|
d895706199 | ||
|
|
6ed7d418b5 | ||
|
|
62b8a83741 | ||
|
|
e795b501ac | ||
|
|
03228d8d20 | ||
|
|
c9c5d4cf79 | ||
|
|
f5cc5bc6a1 | ||
|
|
447494ad4d | ||
|
|
c9f178ec87 | ||
|
|
33be326ef6 | ||
|
|
c13f04d838 | ||
|
|
737685149c | ||
|
|
33eef0202d | ||
|
|
a67b1b8bb5 | ||
|
|
124b49456a | ||
|
|
24956087cc | ||
|
|
199ca57f1c | ||
|
|
64d53ac533 | ||
|
|
b3accbf9e0 | ||
|
|
99249a3160 | ||
|
|
f7cd8bad3a | ||
|
|
e9613d7f12 | ||
|
|
97dff042f7 | ||
|
|
32b36fb2af | ||
|
|
41535150dd | ||
|
|
8e69284e8c | ||
|
|
61cfac4eeb | ||
|
|
2720072b23 | ||
|
|
0413c3a38e | ||
|
|
f2b30c1fe0 | ||
|
|
3c86cf7d2e | ||
|
|
451074f7ed | ||
|
|
1e5601ca31 | ||
|
|
75c99cd235 | ||
|
|
bb2c2be9b6 | ||
|
|
53c247b9a1 | ||
|
|
82667d9d16 | ||
|
|
97ed2757f0 | ||
|
|
19fd5ca5f2 | ||
|
|
6ae78a770f | ||
|
|
04962b4cd6 | ||
|
|
f97850dd05 | ||
|
|
2cb3ed3ab9 | ||
|
|
a4421f50c6 | ||
|
|
cb726f40fa | ||
|
|
be026aa308 | ||
|
|
b95f6ca084 | ||
|
|
4a7f078abd | ||
|
|
54d8b8a373 | ||
|
|
dd6bd58140 | ||
|
|
4c56bd771a | ||
|
|
a70b26cd55 | ||
|
|
c4a7ae3164 | ||
|
|
8b8f654c9c | ||
|
|
9b247f6d63 | ||
|
|
f342fdc41d | ||
|
|
ca96f7f62b | ||
|
|
2f31eab8f4 | ||
|
|
c8528fd1f7 | ||
|
|
07ac85d9fa | ||
|
|
e84f214280 | ||
|
|
9cd65efad9 | ||
|
|
41200ab6d7 | ||
|
|
62fdaa633a | ||
|
|
79f5e6b498 | ||
|
|
23bd7e7817 | ||
|
|
792171c8fc | ||
|
|
051a468c63 | ||
|
|
894fb1ad35 | ||
|
|
9a3c60173a | ||
|
|
1b226c7556 | ||
|
|
2bd9cc45fa | ||
|
|
2b4619dcbe | ||
|
|
bb20d43cbf | ||
|
|
9c29b15fcd | ||
|
|
994a91001d | ||
|
|
c85c172fe4 | ||
|
|
eeb06a75a6 | ||
|
|
ff9539059a | ||
|
|
5831df9ece | ||
|
|
b7f758bbbe | ||
|
|
36509509bc | ||
|
|
e83d0fb6f5 | ||
|
|
a65e9a7b42 | ||
|
|
9b912f6d65 | ||
|
|
4b45afc11f | ||
|
|
e714693cfe | ||
|
|
dffd4e56b5 | ||
|
|
a0b3e1a564 | ||
|
|
4efd5288d0 | ||
|
|
a2d3778465 | ||
|
|
f2917c3355 | ||
|
|
4fcbaa2b12 | ||
|
|
d29b9c2726 | ||
|
|
97bb1fa8c9 | ||
|
|
2e5035ec2a | ||
|
|
b292985df3 |
66
boot/boot.js
66
boot/boot.js
@@ -256,6 +256,28 @@ $tw.utils.deepDefaults = function(object /*, sourceObjectList */) {
|
||||
return object;
|
||||
};
|
||||
|
||||
/*
|
||||
Convert a URIComponent encoded string to a string safely
|
||||
*/
|
||||
$tw.utils.decodeURIComponentSafe = function(s) {
|
||||
var v = s;
|
||||
try {
|
||||
v = decodeURIComponent(s);
|
||||
} catch(e) {}
|
||||
return v;
|
||||
};
|
||||
|
||||
/*
|
||||
Convert a URI encoded string to a string safely
|
||||
*/
|
||||
$tw.utils.decodeURISafe = function(s) {
|
||||
var v = s;
|
||||
try {
|
||||
v = decodeURI(s);
|
||||
} catch(e) {}
|
||||
return v;
|
||||
};
|
||||
|
||||
/*
|
||||
Convert "&" to &, " " to nbsp, "<" to <, ">" to > and """ to "
|
||||
*/
|
||||
@@ -1212,8 +1234,12 @@ $tw.Wiki = function(options) {
|
||||
index,titlesLength,title;
|
||||
for(index = 0, titlesLength = titles.length; index < titlesLength; index++) {
|
||||
title = titles[index];
|
||||
var shadowInfo = shadowTiddlers[title];
|
||||
callback(shadowInfo.tiddler,title);
|
||||
if(tiddlers[title]) {
|
||||
callback(tiddlers[title],title);
|
||||
} else {
|
||||
var shadowInfo = shadowTiddlers[title];
|
||||
callback(shadowInfo.tiddler,title);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1602,8 +1628,8 @@ $tw.modules.define("$:/boot/tiddlerdeserializer/json","tiddlerdeserializer",{
|
||||
}
|
||||
for(var f in data) {
|
||||
if($tw.utils.hop(data,f)) {
|
||||
// Check field name doesn't contain whitespace or control characters
|
||||
if(typeof(data[f]) !== "string" || /[\x00-\x1F\s]/.test(f)) {
|
||||
// Check field name doesn't contain control characters
|
||||
if(typeof(data[f]) !== "string" || /[\x00-\x1F]/.test(f)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1618,7 +1644,12 @@ $tw.modules.define("$:/boot/tiddlerdeserializer/json","tiddlerdeserializer",{
|
||||
}
|
||||
return true;
|
||||
},
|
||||
data = JSON.parse(text);
|
||||
data = {};
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch(e) {
|
||||
// Ignore JSON parse errors
|
||||
}
|
||||
if($tw.utils.isArray(data) && isTiddlerArrayValid(data)) {
|
||||
return data;
|
||||
} else if(isTiddlerValid(data)) {
|
||||
@@ -1885,13 +1916,13 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
|
||||
value = path.basename(filename);
|
||||
break;
|
||||
case "filename-uri-decoded":
|
||||
value = decodeURIComponent(path.basename(filename));
|
||||
value = $tw.utils.decodeURIComponentSafe(path.basename(filename));
|
||||
break;
|
||||
case "basename":
|
||||
value = path.basename(filename,path.extname(filename));
|
||||
break;
|
||||
case "basename-uri-decoded":
|
||||
value = decodeURIComponent(path.basename(filename,path.extname(filename)));
|
||||
value = $tw.utils.decodeURIComponentSafe(path.basename(filename,path.extname(filename)));
|
||||
break;
|
||||
case "extname":
|
||||
value = path.extname(filename);
|
||||
@@ -1919,6 +1950,20 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
|
||||
tiddlers.push({tiddlers: fileTiddlers});
|
||||
}
|
||||
};
|
||||
// Helper to recursively search subdirectories
|
||||
var getAllFiles = function(dirPath, recurse, arrayOfFiles) {
|
||||
recurse = recurse || false;
|
||||
arrayOfFiles = arrayOfFiles || [];
|
||||
var files = fs.readdirSync(dirPath);
|
||||
files.forEach(function(file) {
|
||||
if (recurse && fs.statSync(dirPath + path.sep + file).isDirectory()) {
|
||||
arrayOfFiles = getAllFiles(dirPath + path.sep + file, recurse, arrayOfFiles);
|
||||
} else if(fs.statSync(dirPath + path.sep + file).isFile()){
|
||||
arrayOfFiles.push(path.join(dirPath, path.sep, file));
|
||||
}
|
||||
});
|
||||
return arrayOfFiles;
|
||||
}
|
||||
// Process the listed tiddlers
|
||||
$tw.utils.each(filesInfo.tiddlers,function(tidInfo) {
|
||||
if(tidInfo.prefix && tidInfo.suffix) {
|
||||
@@ -1942,13 +1987,14 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
|
||||
// Process directory specifier
|
||||
var dirPath = path.resolve(filepath,dirSpec.path);
|
||||
if(fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
|
||||
var files = fs.readdirSync(dirPath),
|
||||
var files = getAllFiles(dirPath, dirSpec.searchSubdirectories),
|
||||
fileRegExp = new RegExp(dirSpec.filesRegExp || "^.*$"),
|
||||
metaRegExp = /^.*\.meta$/;
|
||||
for(var t=0; t<files.length; t++) {
|
||||
var filename = files[t];
|
||||
var thisPath = path.relative(filepath, files[t]),
|
||||
filename = path.basename(thisPath);
|
||||
if(filename !== "tiddlywiki.files" && !metaRegExp.test(filename) && fileRegExp.test(filename)) {
|
||||
processFile(dirPath + path.sep + filename,dirSpec.isTiddlerFile,dirSpec.fields,dirSpec.isEditableFile);
|
||||
processFile(thisPath,dirSpec.isTiddlerFile,dirSpec.fields,dirSpec.isEditableFile);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -32,8 +32,6 @@ ExportTiddler/Caption: export tiddler
|
||||
ExportTiddler/Hint: Export tiddler
|
||||
ExportTiddlers/Caption: export tiddlers
|
||||
ExportTiddlers/Hint: Export tiddlers
|
||||
ExportTiddlyWikiCore/Caption: export TiddlyWiki core
|
||||
ExportTiddlyWikiCore/Hint: Export the ~TiddlyWiki core code for running with external ~JavaScript
|
||||
SidebarSearch/Hint: Select the sidebar search field
|
||||
Fold/Caption: fold tiddler
|
||||
Fold/Hint: Fold the body of this tiddler
|
||||
|
||||
@@ -22,7 +22,6 @@ All parameters are optional with safe defaults, and can be specified in any orde
|
||||
* ''readers'' - comma-separated list of principals allowed to read from this wiki
|
||||
* ''writers'' - comma-separated list of principals allowed to write to this wiki
|
||||
* ''csrf-disable'' - set to "yes" to disable CSRF checks (defaults to "no")
|
||||
* ''sse-enabled'' - set to "yes" to enable Server-sent events (defaults to "no")
|
||||
* ''root-tiddler'' - the tiddler to serve at the root (defaults to "$:/core/save/all")
|
||||
* ''root-render-type'' - the content type to which the root tiddler should be rendered (defaults to "text/plain")
|
||||
* ''root-serve-type'' - the content type with which the root tiddler should be served (defaults to "text/html")
|
||||
|
||||
13
core/language/en-GB/Help/pipe.tid
Normal file
13
core/language/en-GB/Help/pipe.tid
Normal 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
|
||||
@@ -30,5 +30,5 @@ Upgrader/System/Warning: Core module tiddler.
|
||||
Upgrader/System/Alert: You are about to import a tiddler that will overwrite a core module tiddler. This is not recommended as it may make the system unstable.
|
||||
Upgrader/ThemeTweaks/Created: Migrated theme tweak from <$text text=<<from>>/>.
|
||||
Upgrader/Tiddler/Disabled: Disabled tiddler.
|
||||
Upgrader/Tiddler/Selected: User selected.
|
||||
Upgrader/Tiddler/Selected: Selected tiddler.
|
||||
Upgrader/Tiddler/Unselected: Unselected tiddler.
|
||||
|
||||
278
core/modules/commands/pipe.js
Normal file
278
core/modules/commands/pipe.js
Normal 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;
|
||||
|
||||
})();
|
||||
@@ -8,58 +8,59 @@ Render individual tiddlers and save the results to the specified files
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var widget = require("$:/core/modules/widgets/widget.js");
|
||||
|
||||
exports.info = {
|
||||
name: "render",
|
||||
synchronous: true
|
||||
};
|
||||
|
||||
var Command = function(params,commander,callback) {
|
||||
this.params = params;
|
||||
this.commander = commander;
|
||||
this.callback = callback;
|
||||
};
|
||||
|
||||
Command.prototype.execute = function() {
|
||||
if(this.params.length < 1) {
|
||||
return "Missing tiddler filter";
|
||||
}
|
||||
var self = this,
|
||||
fs = require("fs"),
|
||||
path = require("path"),
|
||||
wiki = this.commander.wiki,
|
||||
tiddlerFilter = this.params[0],
|
||||
filenameFilter = this.params[1] || "[is[tiddler]addsuffix[.html]]",
|
||||
type = this.params[2] || "text/html",
|
||||
template = this.params[3],
|
||||
variableList = this.params.slice(4),
|
||||
tiddlers = wiki.filterTiddlers(tiddlerFilter),
|
||||
variables = Object.create(null);
|
||||
while(variableList.length >= 2) {
|
||||
variables[variableList[0]] = variableList[1];
|
||||
variableList = variableList.slice(2);
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var widget = require("$:/core/modules/widgets/widget.js");
|
||||
|
||||
exports.info = {
|
||||
name: "render",
|
||||
synchronous: true
|
||||
};
|
||||
|
||||
var Command = function(params,commander,callback) {
|
||||
this.params = params;
|
||||
this.commander = commander;
|
||||
this.callback = callback;
|
||||
};
|
||||
|
||||
Command.prototype.execute = function() {
|
||||
if(this.params.length < 1) {
|
||||
return "Missing tiddler filter";
|
||||
}
|
||||
$tw.utils.each(tiddlers,function(title) {
|
||||
var parser = wiki.parseTiddler(template || title);
|
||||
var widgetNode = wiki.makeWidget(parser,{variables: $tw.utils.extend({},variables,{currentTiddler: title})}),
|
||||
container = $tw.fakeDocument.createElement("div");
|
||||
widgetNode.render(container,null);
|
||||
var text = type === "text/html" ? container.innerHTML : container.textContent,
|
||||
filepath = path.resolve(self.commander.outputPath,wiki.filterTiddlers(filenameFilter,$tw.rootWidget,wiki.makeTiddlerIterator([title]))[0]);
|
||||
if(self.commander.verbose) {
|
||||
console.log("Rendering \"" + title + "\" to \"" + filepath + "\"");
|
||||
}
|
||||
$tw.utils.createFileDirectories(filepath);
|
||||
fs.writeFileSync(filepath,text,"utf8");
|
||||
});
|
||||
return null;
|
||||
};
|
||||
|
||||
exports.Command = Command;
|
||||
|
||||
})();
|
||||
var self = this,
|
||||
fs = require("fs"),
|
||||
path = require("path"),
|
||||
wiki = this.commander.wiki,
|
||||
tiddlerFilter = this.params[0],
|
||||
filenameFilter = this.params[1] || "[is[tiddler]addsuffix[.html]]",
|
||||
type = this.params[2] || "text/html",
|
||||
template = this.params[3],
|
||||
variableList = this.params.slice(4),
|
||||
tiddlers = wiki.filterTiddlers(tiddlerFilter),
|
||||
variables = Object.create(null);
|
||||
while(variableList.length >= 2) {
|
||||
variables[variableList[0]] = variableList[1];
|
||||
variableList = variableList.slice(2);
|
||||
}
|
||||
$tw.utils.each(tiddlers,function(title) {
|
||||
var filepath = path.resolve(self.commander.outputPath,wiki.filterTiddlers(filenameFilter,$tw.rootWidget,wiki.makeTiddlerIterator([title]))[0]);
|
||||
if(self.commander.verbose) {
|
||||
console.log("Rendering \"" + title + "\" to \"" + filepath + "\"");
|
||||
}
|
||||
var parser = wiki.parseTiddler(template || title),
|
||||
widgetNode = wiki.makeWidget(parser,{variables: $tw.utils.extend({},variables,{currentTiddler: title})}),
|
||||
container = $tw.fakeDocument.createElement("div");
|
||||
widgetNode.render(container,null);
|
||||
var text = type === "text/html" ? container.innerHTML : container.textContent;
|
||||
$tw.utils.createFileDirectories(filepath);
|
||||
fs.writeFileSync(filepath,text,"utf8");
|
||||
});
|
||||
return null;
|
||||
};
|
||||
|
||||
exports.Command = Command;
|
||||
|
||||
})();
|
||||
|
||||
@@ -135,7 +135,11 @@ FramedEngine.prototype.setText = function(text,type) {
|
||||
Update the DomNode with the new text
|
||||
*/
|
||||
FramedEngine.prototype.updateDomNodeText = function(text) {
|
||||
this.domNode.value = text;
|
||||
try {
|
||||
this.domNode.value = text;
|
||||
} catch(e) {
|
||||
// Ignore
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -85,7 +85,11 @@ SimpleEngine.prototype.setText = function(text,type) {
|
||||
Update the DomNode with the new text
|
||||
*/
|
||||
SimpleEngine.prototype.updateDomNodeText = function(text) {
|
||||
this.domNode.value = text;
|
||||
try {
|
||||
this.domNode.value = text;
|
||||
} catch(e) {
|
||||
// Ignore
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -332,7 +332,7 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
|
||||
};
|
||||
|
||||
EditTextWidget.prototype.handlePasteEvent = function(event) {
|
||||
if(event.clipboardData.files.length) {
|
||||
if(event.clipboardData && event.clipboardData.files && event.clipboardData.files.length) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.dispatchDOMEvent(this.cloneEvent(event,["clipboardData"]));
|
||||
|
||||
17
core/modules/editor/operations/text/focus-editor.js
Normal file
17
core/modules/editor/operations/text/focus-editor.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/*\
|
||||
title: $:/core/modules/editor/operations/text/focus-editor.js
|
||||
type: application/javascript
|
||||
module-type: texteditoroperation
|
||||
Simply focus the Text editor
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports["focus-editor"] = function(event,operation) {
|
||||
operation = null;
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -52,7 +52,7 @@ exports.all = function(source,operator,options) {
|
||||
results.pushTop(subop(source,operator.prefix,options));
|
||||
}
|
||||
}
|
||||
return results.toArray();
|
||||
return results.makeTiddlerIterator(options.wiki);
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
@@ -16,11 +16,11 @@ Filter operator for returning all the backlinks from a tiddler
|
||||
Export our filter function
|
||||
*/
|
||||
exports.backlinks = function(source,operator,options) {
|
||||
var results = [];
|
||||
var results = new $tw.utils.LinkedList();
|
||||
source(function(tiddler,title) {
|
||||
$tw.utils.pushTop(results,options.wiki.getTiddlerBacklinks(title));
|
||||
results.pushTop(options.wiki.getTiddlerBacklinks(title));
|
||||
});
|
||||
return results;
|
||||
return results.makeTiddlerIterator(options.wiki);
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
@@ -19,12 +19,7 @@ Export our filter functions
|
||||
exports.decodeuricomponent = function(source,operator,options) {
|
||||
var results = [];
|
||||
source(function(tiddler,title) {
|
||||
var value = title;
|
||||
try {
|
||||
value = decodeURIComponent(title);
|
||||
} catch(e) {
|
||||
}
|
||||
results.push(value);
|
||||
results.push($tw.utils.decodeURIComponentSafe(title));
|
||||
});
|
||||
return results;
|
||||
};
|
||||
@@ -40,12 +35,7 @@ exports.encodeuricomponent = function(source,operator,options) {
|
||||
exports.decodeuri = function(source,operator,options) {
|
||||
var results = [];
|
||||
source(function(tiddler,title) {
|
||||
var value = title;
|
||||
try {
|
||||
value = decodeURI(title);
|
||||
} catch(e) {
|
||||
}
|
||||
results.push(value);
|
||||
results.push($tw.utils.decodeURISafe(title));
|
||||
});
|
||||
return results;
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ exports.links = function(source,operator,options) {
|
||||
source(function(tiddler,title) {
|
||||
results.pushTop(options.wiki.getTiddlerLinks(title));
|
||||
});
|
||||
return results.toArray();
|
||||
return results.makeTiddlerIterator(options.wiki);
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
@@ -165,6 +165,35 @@ exports["standard-deviation"] = makeNumericReducingOperator(
|
||||
}
|
||||
);
|
||||
|
||||
//trigonometry
|
||||
exports.cos = makeNumericBinaryOperator(
|
||||
function(a) {return Math.cos(a)}
|
||||
);
|
||||
|
||||
exports.sin = makeNumericBinaryOperator(
|
||||
function(a) {return Math.sin(a)}
|
||||
);
|
||||
|
||||
exports.tan = makeNumericBinaryOperator(
|
||||
function(a) {return Math.tan(a)}
|
||||
);
|
||||
|
||||
exports.acos = makeNumericBinaryOperator(
|
||||
function(a) {return Math.acos(a)}
|
||||
);
|
||||
|
||||
exports.asin = makeNumericBinaryOperator(
|
||||
function(a) {return Math.asin(a)}
|
||||
);
|
||||
|
||||
exports.atan = makeNumericBinaryOperator(
|
||||
function(a) {return Math.atan(a)}
|
||||
);
|
||||
|
||||
exports.atan2 = makeNumericBinaryOperator(
|
||||
function(a,b) {return Math.atan2(a,b)}
|
||||
);
|
||||
|
||||
//Calculate the variance of a population of numbers in an array given its mean
|
||||
function getVarianceFromArray(values,mean) {
|
||||
var deviationTotal = values.reduce(function(accumulator,value) {
|
||||
|
||||
30
core/modules/filters/moduleproperty.js
Normal file
30
core/modules/filters/moduleproperty.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/*\
|
||||
title: $:/core/modules/filters/moduleproperty.js
|
||||
type: application/javascript
|
||||
module-type: filteroperator
|
||||
|
||||
Filter [[module-name]moduleproperty[name]] retrieve a module property
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
Export our filter function
|
||||
*/
|
||||
exports.moduleproperty = function(source,operator,options) {
|
||||
var results = [];
|
||||
source(function(tiddler,title) {
|
||||
var value = require(title)[operator.operand || ""];
|
||||
if(value !== undefined) {
|
||||
results.push(value);
|
||||
}
|
||||
});
|
||||
results.sort();
|
||||
return results;
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -17,11 +17,23 @@ Export our filter function
|
||||
*/
|
||||
exports.modules = function(source,operator,options) {
|
||||
var results = [];
|
||||
source(function(tiddler,title) {
|
||||
$tw.utils.each($tw.modules.types[title],function(moduleInfo,moduleName) {
|
||||
results.push(moduleName);
|
||||
if(operator.operands.length >= 2) {
|
||||
// Return the modules that have the module property specified in the first operand with the value in the second operand
|
||||
source(function(tiddler,title) {
|
||||
$tw.utils.each($tw.modules.types[title],function(moduleInfo,moduleName) {
|
||||
if(require(moduleName)[operator.operands[0]] === operator.operands[1]) {
|
||||
results.push(moduleName);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Return all the module names without filtering
|
||||
source(function(tiddler,title) {
|
||||
$tw.utils.each($tw.modules.types[title],function(moduleInfo,moduleName) {
|
||||
results.push(moduleName);
|
||||
});
|
||||
});
|
||||
}
|
||||
results.sort();
|
||||
return results;
|
||||
};
|
||||
|
||||
@@ -17,9 +17,13 @@ Export our filter function
|
||||
*/
|
||||
exports.range = function(source,operator,options) {
|
||||
var results = [];
|
||||
// Split the operand into numbers delimited by these symbols
|
||||
var parts = operator.operand.split(/[,:;]/g),
|
||||
beg, end, inc, i, fixed = 0;
|
||||
// For backwards compatibility, if there is only one operand, try to split it using one of the delimiters
|
||||
var parts = operator.operands || [];
|
||||
if(parts.length === 1) {
|
||||
parts = operator.operand.split(/[,:;]/g);
|
||||
}
|
||||
// Process the parts
|
||||
var beg, end, inc, i, fixed = 0;
|
||||
for (i=0; i<parts.length; i++) {
|
||||
// Validate real number
|
||||
if(!/^\s*[+-]?((\d+(\.\d*)?)|(\.\d+))\s*$/.test(parts[i])) {
|
||||
|
||||
@@ -119,7 +119,7 @@ exports["search-replace"] = function(source,operator,options) {
|
||||
var results = [],
|
||||
suffixes = operator.suffixes || [],
|
||||
flagSuffix = (suffixes[0] ? (suffixes[0][0] || "") : ""),
|
||||
flags = (flagSuffix.indexOf("g") !== -1 ? "g" : "") + (flagSuffix.indexOf("i") !== -1 ? "i" : ""),
|
||||
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,
|
||||
searchTerm,
|
||||
regExp;
|
||||
|
||||
@@ -16,20 +16,13 @@ Filter operator returning all the selected tiddlers that are untagged
|
||||
Export our filter function
|
||||
*/
|
||||
exports.untagged = function(source,operator,options) {
|
||||
var results = [];
|
||||
if(operator.prefix === "!") {
|
||||
source(function(tiddler,title) {
|
||||
if(tiddler && $tw.utils.isArray(tiddler.fields.tags) && tiddler.fields.tags.length > 0) {
|
||||
$tw.utils.pushTop(results,title);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
source(function(tiddler,title) {
|
||||
if(!tiddler || !tiddler.hasField("tags") || ($tw.utils.isArray(tiddler.fields.tags) && tiddler.fields.tags.length === 0)) {
|
||||
$tw.utils.pushTop(results,title);
|
||||
}
|
||||
});
|
||||
}
|
||||
var results = [],
|
||||
expected = (operator.prefix === "!");
|
||||
source(function(tiddler,title) {
|
||||
if((tiddler && $tw.utils.isArray(tiddler.fields.tags) && tiddler.fields.tags.length > 0) === expected) {
|
||||
results.push(title);
|
||||
}
|
||||
});
|
||||
return results;
|
||||
};
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ exports.parse = function() {
|
||||
};
|
||||
|
||||
/*
|
||||
Look for an HTML tag. Returns null if not found, otherwise returns {type: "element", name:, attributes: [], isSelfClosing:, start:, end:,}
|
||||
Look for an HTML tag. Returns null if not found, otherwise returns {type: "element", name:, attributes: {}, orderedAttributes: [], isSelfClosing:, start:, end:,}
|
||||
*/
|
||||
exports.parseTag = function(source,pos,options) {
|
||||
options = options || {};
|
||||
@@ -74,7 +74,8 @@ exports.parseTag = function(source,pos,options) {
|
||||
node = {
|
||||
type: "element",
|
||||
start: pos,
|
||||
attributes: {}
|
||||
attributes: {},
|
||||
orderedAttributes: []
|
||||
};
|
||||
// Define our regexps
|
||||
var reTagName = /([a-zA-Z0-9\-\$]+)/g;
|
||||
@@ -106,6 +107,7 @@ exports.parseTag = function(source,pos,options) {
|
||||
// Process attributes
|
||||
var attribute = $tw.utils.parseAttribute(source,pos);
|
||||
while(attribute) {
|
||||
node.orderedAttributes.push(attribute);
|
||||
node.attributes[attribute.name] = attribute;
|
||||
pos = attribute.end;
|
||||
// Get the next attribute
|
||||
|
||||
@@ -231,7 +231,10 @@ WikiParser.prototype.parseBlock = function(terminatorRegExpString) {
|
||||
return nextMatch.rule.parse();
|
||||
}
|
||||
// Treat it as a paragraph if we didn't find a block rule
|
||||
return [{type: "element", tag: "p", children: this.parseInlineRun(terminatorRegExp)}];
|
||||
var start = this.pos;
|
||||
var children = this.parseInlineRun(terminatorRegExp);
|
||||
var end = this.pos;
|
||||
return [{type: "element", tag: "p", children: children, start: start, end: end }];
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -307,7 +310,7 @@ WikiParser.prototype.parseInlineRunUnterminated = function(options) {
|
||||
while(this.pos < this.sourceLength && nextMatch) {
|
||||
// Process the text preceding the run rule
|
||||
if(nextMatch.matchIndex > this.pos) {
|
||||
this.pushTextWidget(tree,this.source.substring(this.pos,nextMatch.matchIndex));
|
||||
this.pushTextWidget(tree,this.source.substring(this.pos,nextMatch.matchIndex),this.pos,nextMatch.matchIndex);
|
||||
this.pos = nextMatch.matchIndex;
|
||||
}
|
||||
// Process the run rule
|
||||
@@ -317,7 +320,7 @@ WikiParser.prototype.parseInlineRunUnterminated = function(options) {
|
||||
}
|
||||
// Process the remaining text
|
||||
if(this.pos < this.sourceLength) {
|
||||
this.pushTextWidget(tree,this.source.substr(this.pos));
|
||||
this.pushTextWidget(tree,this.source.substr(this.pos),this.pos,this.sourceLength);
|
||||
}
|
||||
this.pos = this.sourceLength;
|
||||
return tree;
|
||||
@@ -337,7 +340,7 @@ WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,option
|
||||
if(terminatorMatch) {
|
||||
if(!inlineRuleMatch || inlineRuleMatch.matchIndex >= terminatorMatch.index) {
|
||||
if(terminatorMatch.index > this.pos) {
|
||||
this.pushTextWidget(tree,this.source.substring(this.pos,terminatorMatch.index));
|
||||
this.pushTextWidget(tree,this.source.substring(this.pos,terminatorMatch.index),this.pos,terminatorMatch.index);
|
||||
}
|
||||
this.pos = terminatorMatch.index;
|
||||
if(options.eatTerminator) {
|
||||
@@ -350,7 +353,7 @@ WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,option
|
||||
if(inlineRuleMatch) {
|
||||
// Preceding text
|
||||
if(inlineRuleMatch.matchIndex > this.pos) {
|
||||
this.pushTextWidget(tree,this.source.substring(this.pos,inlineRuleMatch.matchIndex));
|
||||
this.pushTextWidget(tree,this.source.substring(this.pos,inlineRuleMatch.matchIndex),this.pos,inlineRuleMatch.matchIndex);
|
||||
this.pos = inlineRuleMatch.matchIndex;
|
||||
}
|
||||
// Process the inline rule
|
||||
@@ -364,7 +367,7 @@ WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,option
|
||||
}
|
||||
// Process the remaining text
|
||||
if(this.pos < this.sourceLength) {
|
||||
this.pushTextWidget(tree,this.source.substr(this.pos));
|
||||
this.pushTextWidget(tree,this.source.substr(this.pos),this.pos,this.sourceLength);
|
||||
}
|
||||
this.pos = this.sourceLength;
|
||||
return tree;
|
||||
@@ -373,12 +376,12 @@ WikiParser.prototype.parseInlineRunTerminated = function(terminatorRegExp,option
|
||||
/*
|
||||
Push a text widget onto an array, respecting the configTrimWhiteSpace setting
|
||||
*/
|
||||
WikiParser.prototype.pushTextWidget = function(array,text) {
|
||||
WikiParser.prototype.pushTextWidget = function(array,text,start,end) {
|
||||
if(this.configTrimWhiteSpace) {
|
||||
text = $tw.utils.trim(text);
|
||||
}
|
||||
if(text) {
|
||||
array.push({type: "text", text: text});
|
||||
array.push({type: "text", text: text, start: start, end: end});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ AndTidWiki.prototype.save = function(text,method,callback,options) {
|
||||
window.twi.saveWiki(text);
|
||||
} else {
|
||||
// Get the pathname of this document
|
||||
var pathname = decodeURIComponent(document.location.toString().split("#")[0]);
|
||||
var pathname = $tw.utils.decodeURIComponentSafe(document.location.toString().split("#")[0]);
|
||||
// Strip the file://
|
||||
if(pathname.indexOf("file://") === 0) {
|
||||
pathname = pathname.substr(7);
|
||||
|
||||
@@ -26,7 +26,7 @@ DownloadSaver.prototype.save = function(text,method,callback,options) {
|
||||
var p = document.location.pathname.lastIndexOf("/");
|
||||
if(p !== -1) {
|
||||
// We decode the pathname because document.location is URL encoded by the browser
|
||||
filename = decodeURIComponent(document.location.pathname.substr(p+1));
|
||||
filename = $tw.utils.decodeURIComponentSafe(document.location.pathname.substr(p+1));
|
||||
}
|
||||
}
|
||||
if(!filename) {
|
||||
|
||||
@@ -72,7 +72,7 @@ GiteaSaver.prototype.save = function(text,method,callback) {
|
||||
}
|
||||
}
|
||||
var data = {
|
||||
message: $tw.language.getRawString("ControlPanel/Saving/GitService/CommitMessage"),
|
||||
message: $tw.language.getString("ControlPanel/Saving/GitService/CommitMessage"),
|
||||
content: $tw.utils.base64Encode(text),
|
||||
sha: sha
|
||||
};
|
||||
|
||||
@@ -69,7 +69,7 @@ GitHubSaver.prototype.save = function(text,method,callback) {
|
||||
});
|
||||
}
|
||||
var data = {
|
||||
message: $tw.language.getRawString("ControlPanel/Saving/GitService/CommitMessage"),
|
||||
message: $tw.language.getString("ControlPanel/Saving/GitService/CommitMessage"),
|
||||
content: $tw.utils.base64Encode(text),
|
||||
branch: branch,
|
||||
sha: sha
|
||||
|
||||
@@ -67,7 +67,7 @@ GitLabSaver.prototype.save = function(text,method,callback) {
|
||||
});
|
||||
}
|
||||
var data = {
|
||||
commit_message: $tw.language.getRawString("ControlPanel/Saving/GitService/CommitMessage"),
|
||||
commit_message: $tw.language.getString("ControlPanel/Saving/GitService/CommitMessage"),
|
||||
content: text,
|
||||
branch: branch,
|
||||
sha: sha
|
||||
|
||||
@@ -43,7 +43,7 @@ TiddlyFoxSaver.prototype.save = function(text,method,callback) {
|
||||
}
|
||||
// Create the message element and put it in the message box
|
||||
var message = document.createElement("div");
|
||||
message.setAttribute("data-tiddlyfox-path",decodeURIComponent(pathname));
|
||||
message.setAttribute("data-tiddlyfox-path",$tw.utils.decodeURIComponentSafe(pathname));
|
||||
message.setAttribute("data-tiddlyfox-content",text);
|
||||
messageBox.appendChild(message);
|
||||
// Add an event handler for when the file has been saved
|
||||
|
||||
@@ -21,7 +21,7 @@ TWEditSaver.prototype.save = function(text,method,callback) {
|
||||
return false;
|
||||
}
|
||||
// Get the pathname of this document
|
||||
var pathname = decodeURIComponent(document.location.pathname);
|
||||
var pathname = $tw.utils.decodeURIComponentSafe(document.location.pathname);
|
||||
// Strip any query or location part
|
||||
var p = pathname.indexOf("?");
|
||||
if(p !== -1) {
|
||||
|
||||
@@ -17,7 +17,7 @@ exports.method = "DELETE";
|
||||
exports.path = /^\/bags\/default\/tiddlers\/(.+)$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]);
|
||||
var title = $tw.utils.decodeURIComponentSafe(state.params[0]);
|
||||
state.wiki.deleteTiddler(title);
|
||||
response.writeHead(204, "OK", {
|
||||
"Content-Type": "text/plain"
|
||||
|
||||
@@ -20,22 +20,29 @@ exports.handler = function(request,response,state) {
|
||||
var path = require("path"),
|
||||
fs = require("fs"),
|
||||
util = require("util"),
|
||||
suppliedFilename = decodeURIComponent(state.params[0]),
|
||||
filename = path.resolve(state.boot.wikiPath,"files",suppliedFilename),
|
||||
suppliedFilename = $tw.utils.decodeURIComponentSafe(state.params[0]),
|
||||
baseFilename = path.resolve(state.boot.wikiPath,"files"),
|
||||
filename = path.resolve(baseFilename,suppliedFilename),
|
||||
extension = path.extname(filename);
|
||||
fs.readFile(filename,function(err,content) {
|
||||
var status,content,type = "text/plain";
|
||||
if(err) {
|
||||
console.log("Error accessing file " + filename + ": " + err.toString());
|
||||
status = 404;
|
||||
content = "File '" + suppliedFilename + "' not found";
|
||||
} else {
|
||||
status = 200;
|
||||
content = content;
|
||||
type = ($tw.config.fileExtensionInfo[extension] ? $tw.config.fileExtensionInfo[extension].type : "application/octet-stream");
|
||||
}
|
||||
state.sendResponse(status,{"Content-Type": type},content);
|
||||
});
|
||||
// Check that the filename is inside the wiki files folder
|
||||
if(path.relative(baseFilename,filename).indexOf("..") !== 0) {
|
||||
// Send the file
|
||||
fs.readFile(filename,function(err,content) {
|
||||
var status,content,type = "text/plain";
|
||||
if(err) {
|
||||
console.log("Error accessing file " + filename + ": " + err.toString());
|
||||
status = 404;
|
||||
content = "File '" + suppliedFilename + "' not found";
|
||||
} else {
|
||||
status = 200;
|
||||
content = content;
|
||||
type = ($tw.config.fileExtensionInfo[extension] ? $tw.config.fileExtensionInfo[extension].type : "application/octet-stream");
|
||||
}
|
||||
state.sendResponse(status,{"Content-Type": type},content);
|
||||
});
|
||||
} else {
|
||||
state.sendResponse(404,{"Content-Type": "text/plain"},"File '" + suppliedFilename + "' not found");
|
||||
}
|
||||
};
|
||||
|
||||
}());
|
||||
|
||||
@@ -21,7 +21,6 @@ exports.handler = function(request,response,state) {
|
||||
username: state.authenticatedUsername || state.server.get("anon-username") || "",
|
||||
anonymous: !state.authenticatedUsername,
|
||||
read_only: !state.server.isAuthorized("writers",state.authenticatedUsername),
|
||||
sse_enabled: state.server.get("sse-enabled") === "yes",
|
||||
space: {
|
||||
recipe: "default"
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ exports.method = "GET";
|
||||
exports.path = /^\/([^\/]+)$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]),
|
||||
var title = $tw.utils.decodeURIComponentSafe(state.params[0]),
|
||||
tiddler = state.wiki.getTiddler(title);
|
||||
if(tiddler) {
|
||||
var renderType = tiddler.getFieldString("_render_type"),
|
||||
|
||||
@@ -17,7 +17,7 @@ exports.method = "GET";
|
||||
exports.path = /^\/recipes\/default\/tiddlers\/(.+)$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]),
|
||||
var title = $tw.utils.decodeURIComponentSafe(state.params[0]),
|
||||
tiddler = state.wiki.getTiddler(title),
|
||||
tiddlerFields = {},
|
||||
knownFields = [
|
||||
|
||||
55
core/modules/server/routes/post-commands.js
Normal file
55
core/modules/server/routes/post-commands.js
Normal 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
|
||||
};
|
||||
|
||||
}());
|
||||
@@ -17,7 +17,7 @@ exports.method = "PUT";
|
||||
exports.path = /^\/recipes\/default\/tiddlers\/(.+)$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]),
|
||||
var title = $tw.utils.decodeURIComponentSafe(state.params[0]),
|
||||
fields = JSON.parse(state.data);
|
||||
// Pull up any subfields in the `fields` object
|
||||
if(fields.fields) {
|
||||
@@ -30,7 +30,7 @@ exports.handler = function(request,response,state) {
|
||||
if(fields.revision) {
|
||||
delete fields.revision;
|
||||
}
|
||||
state.wiki.addTiddler(new $tw.Tiddler(state.wiki.getCreationFields(),fields,{title: title},state.wiki.getModificationFields()));
|
||||
state.wiki.addTiddler(new $tw.Tiddler(fields,{title: title}));
|
||||
var changeCount = state.wiki.getChangeCount(title).toString();
|
||||
response.writeHead(204, "OK",{
|
||||
Etag: "\"default/" + encodeURIComponent(title) + "/" + changeCount + ":\"",
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/*\
|
||||
title: $:/core/modules/server/server-sent-events.js
|
||||
type: application/javascript
|
||||
module-type: library
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
parameters:
|
||||
prefix - usually the plugin path, such as `plugins/tiddlywiki/tiddlyweb`. The
|
||||
route will match `/events/${prefix}` exactly.
|
||||
|
||||
handler - a function that will be called each time a request comes in with the
|
||||
request and state from the route and an emit function to call.
|
||||
*/
|
||||
|
||||
var ServerSentEvents = function ServerSentEvents(prefix, handler) {
|
||||
this.handler = handler;
|
||||
this.prefix = prefix;
|
||||
};
|
||||
|
||||
ServerSentEvents.prototype.getExports = function() {
|
||||
return {
|
||||
bodyFormat: "stream",
|
||||
method: "GET",
|
||||
path: new RegExp("^/events/" + this.prefix + "$"),
|
||||
handler: this.handleEventRequest.bind(this)
|
||||
};
|
||||
};
|
||||
|
||||
ServerSentEvents.prototype.handleEventRequest = function(request,response,state) {
|
||||
if(ServerSentEvents.prototype.isEventStreamRequest(request)) {
|
||||
response.writeHead(200, {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive"
|
||||
});
|
||||
this.handler(request,state,this.emit.bind(this,response),this.end.bind(this,response));
|
||||
} else {
|
||||
response.writeHead(406,"Not Acceptable",{});
|
||||
response.end();
|
||||
}
|
||||
};
|
||||
|
||||
ServerSentEvents.prototype.isEventStreamRequest = function(request) {
|
||||
return request.headers.accept &&
|
||||
request.headers.accept.match(/^text\/event-stream/);
|
||||
};
|
||||
|
||||
ServerSentEvents.prototype.emit = function(response,event,data) {
|
||||
if(typeof event !== "string" || event.indexOf("\n") !== -1) {
|
||||
throw new Error("Type must be a single-line string");
|
||||
}
|
||||
if(typeof data !== "string" || data.indexOf("\n") !== -1) {
|
||||
throw new Error("Data must be a single-line string");
|
||||
}
|
||||
response.write("event: " + event + "\ndata: " + data + "\n\n", "utf8");
|
||||
};
|
||||
|
||||
ServerSentEvents.prototype.end = function(response) {
|
||||
response.end();
|
||||
};
|
||||
|
||||
exports.ServerSentEvents = ServerSentEvents;
|
||||
|
||||
})();
|
||||
@@ -120,10 +120,10 @@ function openStartupTiddlers(options) {
|
||||
var hash = $tw.locationHash.substr(1),
|
||||
split = hash.indexOf(":");
|
||||
if(split === -1) {
|
||||
target = decodeURIComponent(hash.trim());
|
||||
target = $tw.utils.decodeURIComponentSafe(hash.trim());
|
||||
} else {
|
||||
target = decodeURIComponent(hash.substr(0,split).trim());
|
||||
storyFilter = decodeURIComponent(hash.substr(split + 1).trim());
|
||||
target = $tw.utils.decodeURIComponentSafe(hash.substr(0,split).trim());
|
||||
storyFilter = $tw.utils.decodeURIComponentSafe(hash.substr(split + 1).trim());
|
||||
}
|
||||
}
|
||||
// If the story wasn't specified use the current tiddlers or a blank story
|
||||
|
||||
@@ -20,7 +20,6 @@ Syncer.prototype.titleIsAnonymous = "$:/status/IsAnonymous";
|
||||
Syncer.prototype.titleIsReadOnly = "$:/status/IsReadOnly";
|
||||
Syncer.prototype.titleUserName = "$:/status/UserName";
|
||||
Syncer.prototype.titleSyncFilter = "$:/config/SyncFilter";
|
||||
Syncer.prototype.titleSyncDisablePolling = "$:/config/SyncDisablePolling";
|
||||
Syncer.prototype.titleSyncPollingInterval = "$:/config/SyncPollingInterval";
|
||||
Syncer.prototype.titleSyncDisableLazyLoading = "$:/config/SyncDisableLazyLoading";
|
||||
Syncer.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done";
|
||||
@@ -90,7 +89,7 @@ function Syncer(options) {
|
||||
if(filteredChanges.length > 0) {
|
||||
self.processTaskQueue();
|
||||
} else {
|
||||
// Look for deletions of tiddlers we're already syncing
|
||||
// Look for deletions of tiddlers we're already syncing
|
||||
var outstandingDeletion = false
|
||||
$tw.utils.each(changes,function(change,title,object) {
|
||||
if(change.deleted && $tw.utils.hop(self.tiddlerInfo,title)) {
|
||||
@@ -122,7 +121,7 @@ function Syncer(options) {
|
||||
self.login(username,password,function() {});
|
||||
} else {
|
||||
// No username and password, so we display a prompt
|
||||
self.handleLoginEvent();
|
||||
self.handleLoginEvent();
|
||||
}
|
||||
});
|
||||
$tw.rootWidget.addEventListener("tm-logout",function() {
|
||||
@@ -131,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());
|
||||
});
|
||||
@@ -139,7 +143,7 @@ function Syncer(options) {
|
||||
if(!this.disableUI && this.wiki.getTiddlerText(this.titleSyncDisableLazyLoading) !== "yes") {
|
||||
this.wiki.addEventListener("lazyLoad",function(title) {
|
||||
self.handleLazyLoadEvent(title);
|
||||
});
|
||||
});
|
||||
}
|
||||
// Get the login status
|
||||
this.getStatus(function(err,isLoggedIn) {
|
||||
@@ -174,8 +178,8 @@ Syncer.prototype.getTiddlerRevision = function(title) {
|
||||
if(this.syncadaptor && this.syncadaptor.getTiddlerRevision) {
|
||||
return this.syncadaptor.getTiddlerRevision(title);
|
||||
} else {
|
||||
return this.wiki.getTiddler(title).fields.revision;
|
||||
}
|
||||
return this.wiki.getTiddler(title).fields.revision;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -268,9 +272,9 @@ Syncer.prototype.getStatus = function(callback) {
|
||||
// Mark us as not logged in
|
||||
this.wiki.addTiddler({title: this.titleIsLoggedIn,text: "no"});
|
||||
// Get login status
|
||||
this.syncadaptor.getStatus(function(err,isLoggedIn,username,isReadOnly,isAnonymous,isPollingDisabled) {
|
||||
this.syncadaptor.getStatus(function(err,isLoggedIn,username,isReadOnly,isAnonymous) {
|
||||
if(err) {
|
||||
self.logger.alert(err);
|
||||
self.displayError("Get Status Error",err);
|
||||
} else {
|
||||
// Set the various status tiddlers
|
||||
self.wiki.addTiddler({title: self.titleIsReadOnly,text: isReadOnly ? "yes" : "no"});
|
||||
@@ -279,9 +283,6 @@ Syncer.prototype.getStatus = function(callback) {
|
||||
if(isLoggedIn) {
|
||||
self.wiki.addTiddler({title: self.titleUserName,text: username || ""});
|
||||
}
|
||||
if(isPollingDisabled) {
|
||||
self.wiki.addTiddler({title: self.titleSyncDisablePolling, text: "yes"});
|
||||
}
|
||||
}
|
||||
// Invoke the callback
|
||||
if(callback) {
|
||||
@@ -305,15 +306,12 @@ Syncer.prototype.syncFromServer = function() {
|
||||
}
|
||||
},
|
||||
triggerNextSync = function() {
|
||||
if(pollingEnabled) {
|
||||
self.pollTimerId = setTimeout(function() {
|
||||
self.pollTimerId = null;
|
||||
self.syncFromServer.call(self);
|
||||
},self.pollTimerInterval);
|
||||
}
|
||||
self.pollTimerId = setTimeout(function() {
|
||||
self.pollTimerId = null;
|
||||
self.syncFromServer.call(self);
|
||||
},self.pollTimerInterval);
|
||||
},
|
||||
syncSystemFromServer = (self.wiki.getTiddlerText("$:/config/SyncSystemTiddlersFromServer") === "yes"),
|
||||
pollingEnabled = (self.wiki.getTiddlerText(self.titleSyncDisablePolling) !== "yes");
|
||||
syncSystemFromServer = (self.wiki.getTiddlerText("$:/config/SyncSystemTiddlersFromServer") === "yes" ? true : false);
|
||||
if(this.syncadaptor && this.syncadaptor.getUpdatedTiddlers) {
|
||||
this.logger.log("Retrieving updated tiddler list");
|
||||
cancelNextSync();
|
||||
@@ -336,7 +334,7 @@ Syncer.prototype.syncFromServer = function() {
|
||||
});
|
||||
if(updates.modifications.length > 0 || updates.deletions.length > 0) {
|
||||
self.processTaskQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if(this.syncadaptor && this.syncadaptor.getSkinnyTiddlers) {
|
||||
@@ -479,7 +477,7 @@ Syncer.prototype.handleLogoutEvent = function() {
|
||||
if(this.syncadaptor.logout) {
|
||||
this.syncadaptor.logout(function(err) {
|
||||
if(err) {
|
||||
self.logger.alert(err);
|
||||
self.displayError("Logout Error",err);
|
||||
} else {
|
||||
self.getStatus();
|
||||
}
|
||||
@@ -516,7 +514,7 @@ Syncer.prototype.processTaskQueue = function() {
|
||||
} else {
|
||||
self.updateDirtyStatus();
|
||||
// Process the next task
|
||||
self.processTaskQueue.call(self);
|
||||
self.processTaskQueue.call(self);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -524,11 +522,11 @@ Syncer.prototype.processTaskQueue = function() {
|
||||
this.updateDirtyStatus();
|
||||
// And trigger a timeout if there is a pending task
|
||||
if(task === true) {
|
||||
this.triggerTimeout();
|
||||
this.triggerTimeout();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.updateDirtyStatus();
|
||||
this.updateDirtyStatus();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -562,7 +560,7 @@ Syncer.prototype.chooseNextTask = function() {
|
||||
isReadyToSave = !tiddlerInfo || !tiddlerInfo.timestampLastSaved || tiddlerInfo.timestampLastSaved < thresholdLastSaved;
|
||||
if(hasChanged) {
|
||||
if(isReadyToSave) {
|
||||
return new SaveTiddlerTask(this,title);
|
||||
return new SaveTiddlerTask(this,title);
|
||||
} else {
|
||||
havePending = true;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ Browser data transfer utilities, used with the clipboard and drag and drop
|
||||
Options:
|
||||
|
||||
domNode: dom node to make draggable
|
||||
dragImageType: "pill" or "dom"
|
||||
dragImageType: "pill", "blank" or "dom" (the default)
|
||||
dragTiddlerFn: optional function to retrieve the title of tiddler to drag
|
||||
dragFilterFn: optional function to retreive the filter defining a list of tiddlers to drag
|
||||
widget: widget to use as the contect for the filter
|
||||
@@ -73,6 +73,9 @@ exports.makeDraggable = function(options) {
|
||||
if(dataTransfer.setDragImage) {
|
||||
if(dragImageType === "pill") {
|
||||
dataTransfer.setDragImage(dragImage.firstChild,-16,-16);
|
||||
} else if (dragImageType === "blank") {
|
||||
dragImage.removeChild(dragImage.firstChild);
|
||||
dataTransfer.setDragImage(dragImage,0,0);
|
||||
} else {
|
||||
var r = domNode.getBoundingClientRect();
|
||||
dataTransfer.setDragImage(domNode,event.clientX-r.left,event.clientY-r.top);
|
||||
@@ -164,7 +167,7 @@ var importDataTypes = [
|
||||
}},
|
||||
{type: "URL", IECompatible: true, toTiddlerFieldsArray: function(data,fallbackTitle) {
|
||||
// Check for tiddler data URI
|
||||
var match = decodeURIComponent(data).match(/^data\:text\/vnd\.tiddler,(.*)/i);
|
||||
var match = $tw.utils.decodeURIComponentSafe(data).match(/^data\:text\/vnd\.tiddler,(.*)/i);
|
||||
if(match) {
|
||||
return parseJSONTiddlers(match[1],fallbackTitle);
|
||||
} else {
|
||||
@@ -173,7 +176,7 @@ var importDataTypes = [
|
||||
}},
|
||||
{type: "text/x-moz-url", IECompatible: false, toTiddlerFieldsArray: function(data,fallbackTitle) {
|
||||
// Check for tiddler data URI
|
||||
var match = decodeURIComponent(data).match(/^data\:text\/vnd\.tiddler,(.*)/i);
|
||||
var match = $tw.utils.decodeURIComponentSafe(data).match(/^data\:text\/vnd\.tiddler,(.*)/i);
|
||||
if(match) {
|
||||
return parseJSONTiddlers(match[1],fallbackTitle);
|
||||
} else {
|
||||
|
||||
@@ -34,6 +34,23 @@ exports.httpRequest = function(options) {
|
||||
});
|
||||
return result;
|
||||
},
|
||||
getHeader = function(targetHeader) {
|
||||
return headers[targetHeader] || headers[targetHeader.toLowerCase()];
|
||||
},
|
||||
isSimpleRequest = function(type,headers) {
|
||||
if(["GET","HEAD","POST"].indexOf(type) === -1) {
|
||||
return false;
|
||||
}
|
||||
for(var header in headers) {
|
||||
if(["accept","accept-language","content-language","content-type"].indexOf(header.toLowerCase()) === -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if(hasHeader("Content-Type") && ["application/x-www-form-urlencoded","multipart/form-data","text/plain"].indexOf(getHeader["Content-Type"]) === -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
returnProp = options.returnProp || "responseText",
|
||||
request = new XMLHttpRequest(),
|
||||
data = "",
|
||||
@@ -76,7 +93,7 @@ exports.httpRequest = function(options) {
|
||||
if(data && !hasHeader("Content-Type")) {
|
||||
request.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
|
||||
}
|
||||
if(!hasHeader("X-Requested-With")) {
|
||||
if(!hasHeader("X-Requested-With") && !isSimpleRequest(type,headers)) {
|
||||
request.setRequestHeader("X-Requested-With","TiddlyWiki");
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -235,7 +235,7 @@ Object.defineProperty(TW_Element.prototype, "innerHTML", {
|
||||
if(node instanceof TW_Element) {
|
||||
b.push(node.outerHTML);
|
||||
} else if(node instanceof TW_TextNode) {
|
||||
b.push($tw.utils.htmlEncode(node.textContent));
|
||||
b.push($tw.utils.htmlTextEncode(node.textContent));
|
||||
}
|
||||
});
|
||||
return b.join("");
|
||||
|
||||
@@ -95,6 +95,15 @@ LinkedList.prototype.toArray = function() {
|
||||
return output;
|
||||
};
|
||||
|
||||
LinkedList.prototype.makeTiddlerIterator = function(wiki) {
|
||||
var self = this;
|
||||
return function(callback) {
|
||||
self.each(function(title) {
|
||||
callback(wiki.getTiddler(title),title);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
function _removeOne(list,value) {
|
||||
var prevEntry = list.prev[value],
|
||||
nextEntry = list.next[value],
|
||||
|
||||
@@ -199,6 +199,8 @@ exports.transliterationPairs = {
|
||||
"Nj":"N",
|
||||
"Ñ":"N",
|
||||
"NJ":"NJ",
|
||||
"ð":"d",
|
||||
"Ð":"D",
|
||||
"Ó":"O",
|
||||
"Ŏ":"O",
|
||||
"Ǒ":"O",
|
||||
@@ -265,6 +267,8 @@ exports.transliterationPairs = {
|
||||
"Ɽ":"R",
|
||||
"Ꜿ":"C",
|
||||
"Ǝ":"E",
|
||||
"ß":"ss",
|
||||
"ẞ":"SS",
|
||||
"Ś":"S",
|
||||
"Ṥ":"S",
|
||||
"Š":"S",
|
||||
@@ -275,6 +279,8 @@ exports.transliterationPairs = {
|
||||
"Ṡ":"S",
|
||||
"Ṣ":"S",
|
||||
"Ṩ":"S",
|
||||
"þ": "th",
|
||||
"Þ": "TH",
|
||||
"Ť":"T",
|
||||
"Ţ":"T",
|
||||
"Ṱ":"T",
|
||||
@@ -907,7 +913,8 @@ exports.transliterationPairs = {
|
||||
"т":"t",
|
||||
"ь":"'",
|
||||
"б":"b",
|
||||
"ю":"yu"
|
||||
"ю":"yu",
|
||||
"…":"..."
|
||||
};
|
||||
|
||||
exports.transliterate = function(str) {
|
||||
|
||||
@@ -383,6 +383,15 @@ exports.formatDateString = function(date,template) {
|
||||
[/^0WW/, function() {
|
||||
return $tw.utils.pad($tw.utils.getWeek(date));
|
||||
}],
|
||||
[/^0ddddd/, function() {
|
||||
return $tw.utils.pad(Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24),3);
|
||||
}],
|
||||
[/^ddddd/, function() {
|
||||
return Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 1000 / 60 / 60 / 24);
|
||||
}],
|
||||
[/^dddd/, function() {
|
||||
return [7,1,2,3,4,5,6][date.getDay()];
|
||||
}],
|
||||
[/^ddd/, function() {
|
||||
return $tw.language.getString("Date/Short/Day/" + date.getDay());
|
||||
}],
|
||||
|
||||
@@ -59,7 +59,7 @@ Invoke the action associated with this widget
|
||||
ConfirmWidget.prototype.invokeAction = function(triggeringWidget,event) {
|
||||
var invokeActions = true,
|
||||
handled = true,
|
||||
win = event.event && event.event.view ? event.event.view : window;
|
||||
win = event && event.event && event.event.view ? event.event.view : window;
|
||||
if(this.prompt) {
|
||||
invokeActions = win.confirm(this.message);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ SendMessageWidget.prototype.execute = function() {
|
||||
this.actionParam = this.getAttribute("$param");
|
||||
this.actionName = this.getAttribute("$name");
|
||||
this.actionValue = this.getAttribute("$value","");
|
||||
this.actionNames = this.getAttribute("$names");
|
||||
this.actionValues = this.getAttribute("$values");
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -59,13 +61,20 @@ Invoke the action associated with this widget
|
||||
SendMessageWidget.prototype.invokeAction = function(triggeringWidget,event) {
|
||||
// Get the string parameter
|
||||
var param = this.actionParam;
|
||||
// Assemble the attributes as a hashmap
|
||||
// Assemble the parameters as a hashmap
|
||||
var paramObject = Object.create(null);
|
||||
var count = 0;
|
||||
// Add names/values pairs if present
|
||||
if(this.actionNames && this.actionValues) {
|
||||
var names = this.wiki.filterTiddlers(this.actionNames,this),
|
||||
values = this.wiki.filterTiddlers(this.actionValues,this);
|
||||
$tw.utils.each(names,function(name,index) {
|
||||
paramObject[name] = values[index] || "";
|
||||
});
|
||||
}
|
||||
// Add raw parameters
|
||||
$tw.utils.each(this.attributes,function(attribute,name) {
|
||||
if(name.charAt(0) !== "$") {
|
||||
paramObject[name] = attribute;
|
||||
count++;
|
||||
}
|
||||
});
|
||||
// Add name/value pair if present
|
||||
@@ -73,14 +82,15 @@ SendMessageWidget.prototype.invokeAction = function(triggeringWidget,event) {
|
||||
paramObject[this.actionName] = this.actionValue;
|
||||
}
|
||||
// Dispatch the message
|
||||
this.dispatchEvent({
|
||||
var params = {
|
||||
type: this.actionMessage,
|
||||
param: param,
|
||||
paramObject: paramObject,
|
||||
tiddlerTitle: this.getVariable("currentTiddler"),
|
||||
navigateFromTitle: this.getVariable("storyTiddler"),
|
||||
event: event
|
||||
});
|
||||
event: event,
|
||||
currentTiddler: this.getVariable("currentTiddler"),
|
||||
navigateFromTitle: this.getVariable("storyTiddler")
|
||||
};
|
||||
this.dispatchEvent(params);
|
||||
return true; // Action was invoked
|
||||
};
|
||||
|
||||
|
||||
86
core/modules/widgets/action-setmultiplefields.js
Normal file
86
core/modules/widgets/action-setmultiplefields.js
Normal file
@@ -0,0 +1,86 @@
|
||||
/*\
|
||||
title: $:/core/modules/widgets/action-setmultiplefields.js
|
||||
type: application/javascript
|
||||
module-type: widget
|
||||
|
||||
Action widget to set multiple fields or indexes on a tiddler
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
|
||||
var SetMultipleFieldsWidget = function(parseTreeNode,options) {
|
||||
this.initialise(parseTreeNode,options);
|
||||
};
|
||||
|
||||
/*
|
||||
Inherit from the base widget class
|
||||
*/
|
||||
SetMultipleFieldsWidget.prototype = new Widget();
|
||||
|
||||
/*
|
||||
Render this widget into the DOM
|
||||
*/
|
||||
SetMultipleFieldsWidget.prototype.render = function(parent,nextSibling) {
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
};
|
||||
|
||||
/*
|
||||
Compute the internal state of the widget
|
||||
*/
|
||||
SetMultipleFieldsWidget.prototype.execute = function() {
|
||||
this.actionTiddler = this.getAttribute("$tiddler",this.getVariable("currentTiddler"));
|
||||
this.actionFields = this.getAttribute("$fields");
|
||||
this.actionIndexes = this.getAttribute("$indexes");
|
||||
this.actionValues = this.getAttribute("$values");
|
||||
this.actionTimestamp = this.getAttribute("$timestamp","yes") === "yes";
|
||||
};
|
||||
|
||||
/*
|
||||
Refresh the widget by ensuring our attributes are up to date
|
||||
*/
|
||||
SetMultipleFieldsWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(changedAttributes["$tiddler"] || changedAttributes["$fields"] || changedAttributes["$indexes"] || changedAttributes["$values"] || changedAttributes["$timestamp"]) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
};
|
||||
|
||||
/*
|
||||
Invoke the action associated with this widget
|
||||
*/
|
||||
SetMultipleFieldsWidget.prototype.invokeAction = function(triggeringWidget,event) {
|
||||
var tiddler = this.wiki.getTiddler(this.actionTiddler),
|
||||
names, values = this.wiki.filterTiddlers(this.actionValues,this);
|
||||
if(this.actionFields) {
|
||||
var additions = {};
|
||||
names = this.wiki.filterTiddlers(this.actionFields,this);
|
||||
$tw.utils.each(names,function(fieldname,index) {
|
||||
additions[fieldname] = values[index] || "";
|
||||
});
|
||||
var creationFields = this.actionTimestamp ? this.wiki.getCreationFields() : undefined,
|
||||
modificationFields = this.actionTimestamp ? this.wiki.getModificationFields() : undefined;
|
||||
this.wiki.addTiddler(new $tw.Tiddler(creationFields,tiddler,{title: this.actionTiddler},modificationFields,additions));
|
||||
} else if(this.actionIndexes) {
|
||||
var data = this.wiki.getTiddlerData(this.actionTiddler,Object.create(null));
|
||||
names = this.wiki.filterTiddlers(this.actionIndexes,this);
|
||||
$tw.utils.each(names,function(name,index) {
|
||||
data[name] = values[index] || "";
|
||||
});
|
||||
this.wiki.setTiddlerData(this.actionTiddler,data,{},{suppressTimestamp: !this.actionTimestamp});
|
||||
}
|
||||
return true; // Action was invoked
|
||||
};
|
||||
|
||||
exports["action-setmultiplefields"] = SetMultipleFieldsWidget;
|
||||
|
||||
})();
|
||||
|
||||
@@ -54,6 +54,7 @@ DraggableWidget.prototype.render = function(parent,nextSibling) {
|
||||
dragFilterFn: function() {return self.getAttribute("filter");},
|
||||
startActions: self.startActions,
|
||||
endActions: self.endActions,
|
||||
dragImageType: self.dragImageType,
|
||||
widget: this
|
||||
});
|
||||
// Insert the link into the DOM and render any children
|
||||
@@ -71,6 +72,7 @@ DraggableWidget.prototype.execute = function() {
|
||||
this.draggableClasses = this.getAttribute("class");
|
||||
this.startActions = this.getAttribute("startactions");
|
||||
this.endActions = this.getAttribute("endactions");
|
||||
this.dragImageType = this.getAttribute("dragimagetype");
|
||||
// Make the child widgets
|
||||
this.makeChildWidgets();
|
||||
};
|
||||
|
||||
@@ -76,16 +76,22 @@ EventWidget.prototype.render = function(parent,nextSibling) {
|
||||
variables["tv-selectednode-posy"] = selectedNode.offsetTop.toString();
|
||||
variables["tv-selectednode-width"] = selectedNode.offsetWidth.toString();
|
||||
variables["tv-selectednode-height"] = selectedNode.offsetHeight.toString();
|
||||
|
||||
if(event.clientX && event.clientY) {
|
||||
//Add variables for event X and Y position relative to selected node
|
||||
selectedNodeRect = selectedNode.getBoundingClientRect();
|
||||
variables["event-fromselected-posx"] = (event.clientX - selectedNodeRect.left).toString();
|
||||
variables["event-fromselected-posy"] = (event.clientY - selectedNodeRect.top).toString();
|
||||
|
||||
//Add variables for event X and Y position relative to selected node
|
||||
selectedNodeRect = selectedNode.getBoundingClientRect();
|
||||
variables["event-fromselected-posx"] = (event.clientX - selectedNodeRect.left).toString();
|
||||
variables["event-fromselected-posy"] = (event.clientY - selectedNodeRect.top).toString();
|
||||
//Add variables for event X and Y position relative to event catcher node
|
||||
catcherNodeRect = self.domNode.getBoundingClientRect();
|
||||
variables["event-fromcatcher-posx"] = (event.clientX - catcherNodeRect.left).toString();
|
||||
variables["event-fromcatcher-posy"] = (event.clientY - catcherNodeRect.top).toString();
|
||||
|
||||
//Add variables for event X and Y position relative to event catcher node
|
||||
catcherNodeRect = self.domNode.getBoundingClientRect();
|
||||
variables["event-fromcatcher-posx"] = (event.clientX - catcherNodeRect.left).toString();
|
||||
variables["event-fromcatcher-posy"] = (event.clientY - catcherNodeRect.top).toString();
|
||||
//Add variables for event X and Y position relative to the viewport
|
||||
variables["event-fromviewport-posx"] = event.clientX.toString();
|
||||
variables["event-fromviewport-posy"] = event.clientY.toString();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -32,13 +32,7 @@ JSONTiddlerWidget.prototype.render = function(parent,nextSibling) {
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
// Collect the fields from the optional base tiddler
|
||||
var fields = {};
|
||||
if(this.attTiddler) {
|
||||
var tiddler = this.wiki.getTiddler(this.attTiddler);
|
||||
if(tiddler) {
|
||||
fields = tiddler.getFieldStrings({exclude: this.attExclude.split(" ")});
|
||||
}
|
||||
}
|
||||
var fields = this.getTiddlerFields();
|
||||
// Add custom fields specified in attributes starting with $
|
||||
$tw.utils.each(this.attributes,function(attribute,name) {
|
||||
if(name.charAt(0) === "$") {
|
||||
@@ -79,6 +73,19 @@ JSONTiddlerWidget.prototype.refresh = function(changedTiddlers) {
|
||||
}
|
||||
};
|
||||
|
||||
JSONTiddlerWidget.prototype.getTiddlerFields = function() {
|
||||
var fields = {};
|
||||
if(this.attTiddler) {
|
||||
var tiddler = this.wiki.getTiddler(this.attTiddler);
|
||||
if(tiddler) {
|
||||
fields = tiddler.getFieldStrings({exclude: this.attExclude.split(" ")});
|
||||
} else {
|
||||
fields = {title: this.attTiddler};
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
};
|
||||
|
||||
exports.jsontiddler = JSONTiddlerWidget;
|
||||
|
||||
})();
|
||||
@@ -207,7 +207,8 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
|
||||
*/
|
||||
LinkWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(changedAttributes.to || changedTiddlers[this.to] || changedAttributes["aria-label"] || changedAttributes.tooltip) {
|
||||
if(changedAttributes.to || changedTiddlers[this.to] || changedAttributes["aria-label"] || changedAttributes.tooltip ||
|
||||
changedAttributes["class"] || changedAttributes.tabindex || changedAttributes.draggable || changedAttributes.tag) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -236,6 +236,11 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) {
|
||||
hasRefreshed = hasRefreshed || refreshed;
|
||||
}
|
||||
}
|
||||
// If there are items to remove and we have not refreshed then recreate the item that will now be at the last position
|
||||
if(!hasRefreshed && this.children.length > this.list.length) {
|
||||
this.removeListItem(this.list.length-1);
|
||||
this.insertListItem(this.list.length-1,this.list[this.list.length-1]);
|
||||
}
|
||||
} else {
|
||||
// Cycle through the list, inserting and removing list items as needed
|
||||
for(t=0; t<this.list.length; t++) {
|
||||
|
||||
@@ -36,28 +36,38 @@ MessageCatcherWidget.prototype.render = function(parent,nextSibling) {
|
||||
// Helper to add an event handler
|
||||
var addEventHandler = function(type,actions) {
|
||||
if(type && actions) {
|
||||
var isActionStringExecuting = false;
|
||||
self.addEventListener(
|
||||
type,
|
||||
function(event) {
|
||||
// Don't trap the event if it came from one of our action handlers
|
||||
if(isActionStringExecuting) {
|
||||
return true;
|
||||
}
|
||||
// Collect all the event properties into variables
|
||||
var collectProps = function(obj,prefix) {
|
||||
prefix = prefix || "";
|
||||
var props = {};
|
||||
var props = {},
|
||||
names = [];
|
||||
$tw.utils.each(obj,function(value,name) {
|
||||
if(["string","boolean","number"].indexOf(typeof value) !== -1) {
|
||||
props[prefix + name] = value.toString();
|
||||
names.push(name);
|
||||
props[prefix + "-" + name] = value.toString();
|
||||
}
|
||||
});
|
||||
props["list-" + prefix] = $tw.utils.stringifyList(names);
|
||||
return props;
|
||||
};
|
||||
var variables = $tw.utils.extend(
|
||||
{},
|
||||
collectProps(event.paramObject,"event-paramObject-"),
|
||||
collectProps(event,"event-"),
|
||||
collectProps(event.paramObject,"event-paramObject"),
|
||||
collectProps(event,"event"),
|
||||
{
|
||||
modifier: $tw.keyboardManager.getEventModifierKeyDescriptor(event)
|
||||
});
|
||||
isActionStringExecuting = true;
|
||||
self.invokeActionString(actions,self,event,variables);
|
||||
isActionStringExecuting = false;
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -122,6 +122,7 @@ RadioWidget.prototype.refresh = function(changedTiddlers) {
|
||||
return true;
|
||||
} else if(changedTiddlers[this.radioTitle]) {
|
||||
this.inputDomNode.checked = this.getValue() === this.radioValue;
|
||||
$tw.utils.toggleClass(this.labelDomNode,"tc-radio-selected",this.inputDomNode.checked);
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
} else {
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
|
||||
@@ -226,7 +226,7 @@ RevealWidget.prototype.refresh = function(changedTiddlers) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
} else if(this.type === "popup" && this.updatePopupPosition && (changedTiddlers[this.state] || changedTiddlers[this.stateTitle])) {
|
||||
} else if(this.type === "popup" && this.isOpen && this.updatePopupPosition && (changedTiddlers[this.state] || changedTiddlers[this.stateTitle])) {
|
||||
this.positionPopup(this.domNode);
|
||||
}
|
||||
if(changedAttributes.style) {
|
||||
|
||||
81
core/modules/widgets/setmultiplevariables.js
Normal file
81
core/modules/widgets/setmultiplevariables.js
Normal file
@@ -0,0 +1,81 @@
|
||||
/*\
|
||||
title: $:/core/modules/widgets/setmultiplevariables.js
|
||||
type: application/javascript
|
||||
module-type: widget
|
||||
|
||||
Widget to set multiple variables at once from a list of names and a list of values
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
|
||||
var SetMultipleVariablesWidget = function(parseTreeNode,options) {
|
||||
this.initialise(parseTreeNode,options);
|
||||
};
|
||||
|
||||
/*
|
||||
Inherit from the base widget class
|
||||
*/
|
||||
SetMultipleVariablesWidget.prototype = new Widget();
|
||||
|
||||
/*
|
||||
Render this widget into the DOM
|
||||
*/
|
||||
SetMultipleVariablesWidget.prototype.render = function(parent,nextSibling) {
|
||||
this.parentDomNode = parent;
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
this.renderChildren(parent,nextSibling);
|
||||
};
|
||||
|
||||
/*
|
||||
Compute the internal state of the widget
|
||||
*/
|
||||
SetMultipleVariablesWidget.prototype.execute = function() {
|
||||
// Setup our variables
|
||||
this.setVariables();
|
||||
// Construct the child widgets
|
||||
this.makeChildWidgets();
|
||||
};
|
||||
|
||||
|
||||
SetMultipleVariablesWidget.prototype.setVariables = function() {
|
||||
// Set the variables
|
||||
var self = this,
|
||||
filterNames = this.getAttribute("$names",""),
|
||||
filterValues = this.getAttribute("$values","");
|
||||
this.variableNames = [];
|
||||
this.variableValues = [];
|
||||
if(filterNames && filterValues) {
|
||||
this.variableNames = this.wiki.filterTiddlers(filterNames,this);
|
||||
this.variableValues = this.wiki.filterTiddlers(filterValues,this);
|
||||
$tw.utils.each(this.variableNames,function(varname,index) {
|
||||
self.setVariable(varname,self.variableValues[index]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Refresh the widget by ensuring our attributes are up to date
|
||||
*/
|
||||
SetMultipleVariablesWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var filterNames = this.getAttribute("$names",""),
|
||||
filterValues = this.getAttribute("$values",""),
|
||||
variableNames = this.wiki.filterTiddlers(filterNames,this),
|
||||
variableValues = this.wiki.filterTiddlers(filterValues,this);
|
||||
if(!$tw.utils.isArrayEqual(this.variableNames,variableNames) || !$tw.utils.isArrayEqual(this.variableValues,variableValues)) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
return this.refreshChildren(changedTiddlers);
|
||||
};
|
||||
|
||||
exports["setmultiplevariables"] = SetMultipleVariablesWidget;
|
||||
|
||||
})();
|
||||
|
||||
@@ -570,7 +570,7 @@ Widget.prototype.invokeActions = function(triggeringWidget,event) {
|
||||
for(var t=0; t<this.children.length; t++) {
|
||||
var child = this.children[t],
|
||||
childIsActionWidget = !!child.invokeAction,
|
||||
actionRefreshPolicy = child.getVariable("tv-action-refresh-policy");
|
||||
actionRefreshPolicy = child.getVariable("tv-action-refresh-policy"); // Default is "once"
|
||||
// Refresh the child if required
|
||||
if(childIsActionWidget || actionRefreshPolicy === "always") {
|
||||
child.refreshSelf();
|
||||
|
||||
@@ -81,7 +81,7 @@ exports.setText = function(title,field,index,value,options) {
|
||||
} else {
|
||||
delete data[index];
|
||||
}
|
||||
this.setTiddlerData(title,data,modificationFields);
|
||||
this.setTiddlerData(title,data,{},{suppressTimestamp: options.suppressTimestamp});
|
||||
} else {
|
||||
var tiddler = this.getTiddler(title),
|
||||
fields = {title: title};
|
||||
@@ -365,47 +365,108 @@ Sort an array of tiddler titles by a specified field
|
||||
*/
|
||||
exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,isNumeric,isAlphaNumeric) {
|
||||
var self = this;
|
||||
titles.sort(function(a,b) {
|
||||
var x,y,
|
||||
compareNumbers = function(x,y) {
|
||||
var result =
|
||||
isNaN(x) && !isNaN(y) ? (isDescending ? -1 : 1) :
|
||||
!isNaN(x) && isNaN(y) ? (isDescending ? 1 : -1) :
|
||||
(isDescending ? y - x : x - y);
|
||||
return result;
|
||||
};
|
||||
if(sortField !== "title") {
|
||||
var tiddlerA = self.getTiddler(a),
|
||||
tiddlerB = self.getTiddler(b);
|
||||
if(tiddlerA) {
|
||||
a = tiddlerA.getFieldString(sortField) || "";
|
||||
if(sortField === "title") {
|
||||
if(!isNumeric && !isAlphaNumeric) {
|
||||
if(isCaseSensitive) {
|
||||
if(isDescending) {
|
||||
titles.sort(function(a,b) {
|
||||
return b.localeCompare(a);
|
||||
});
|
||||
} else {
|
||||
titles.sort(function(a,b) {
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
a = "";
|
||||
if(isDescending) {
|
||||
titles.sort(function(a,b) {
|
||||
return b.toLowerCase().localeCompare(a.toLowerCase());
|
||||
});
|
||||
} else {
|
||||
titles.sort(function(a,b) {
|
||||
return a.toLowerCase().localeCompare(b.toLowerCase());
|
||||
});
|
||||
}
|
||||
}
|
||||
if(tiddlerB) {
|
||||
b = tiddlerB.getFieldString(sortField) || "";
|
||||
} else {
|
||||
b = "";
|
||||
}
|
||||
}
|
||||
x = Number(a);
|
||||
y = Number(b);
|
||||
if(isNumeric && (!isNaN(x) || !isNaN(y))) {
|
||||
return compareNumbers(x,y);
|
||||
} else if($tw.utils.isDate(a) && $tw.utils.isDate(b)) {
|
||||
return isDescending ? b - a : a - b;
|
||||
} else if(isAlphaNumeric) {
|
||||
return isDescending ? b.localeCompare(a,undefined,{numeric: true,sensitivity: "base"}) : a.localeCompare(b,undefined,{numeric: true,sensitivity: "base"});
|
||||
} else {
|
||||
titles.sort(function(a,b) {
|
||||
var x,y;
|
||||
if(isNumeric) {
|
||||
x = Number(a);
|
||||
y = Number(b);
|
||||
if(isNaN(x)) {
|
||||
if(isNaN(y)) {
|
||||
// If neither value is a number then fall through to a textual comparison
|
||||
} else {
|
||||
return isDescending ? -1 : 1;
|
||||
}
|
||||
} else {
|
||||
if(isNaN(y)) {
|
||||
return isDescending ? 1 : -1;
|
||||
} else {
|
||||
return isDescending ? y - x : x - y;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(isAlphaNumeric) {
|
||||
return isDescending ? b.localeCompare(a,undefined,{numeric: true,sensitivity: "base"}) : a.localeCompare(b,undefined,{numeric: true,sensitivity: "base"});
|
||||
}
|
||||
if(!isCaseSensitive) {
|
||||
a = a.toLowerCase();
|
||||
b = b.toLowerCase();
|
||||
}
|
||||
return isDescending ? b.localeCompare(a) : a.localeCompare(b);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
titles.sort(function(a,b) {
|
||||
var x,y;
|
||||
if(sortField !== "title") {
|
||||
var tiddlerA = self.getTiddler(a),
|
||||
tiddlerB = self.getTiddler(b);
|
||||
if(tiddlerA) {
|
||||
a = tiddlerA.fields[sortField] || "";
|
||||
} else {
|
||||
a = "";
|
||||
}
|
||||
if(tiddlerB) {
|
||||
b = tiddlerB.fields[sortField] || "";
|
||||
} else {
|
||||
b = "";
|
||||
}
|
||||
}
|
||||
if(isNumeric) {
|
||||
x = Number(a);
|
||||
y = Number(b);
|
||||
if(isNaN(x)) {
|
||||
if(isNaN(y)) {
|
||||
// If neither value is a number then fall through to a textual comparison
|
||||
} else {
|
||||
return isDescending ? -1 : 1;
|
||||
}
|
||||
} else {
|
||||
if(isNaN(y)) {
|
||||
return isDescending ? 1 : -1;
|
||||
} else {
|
||||
return isDescending ? y - x : x - y;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(Object.prototype.toString.call(a) === "[object Date]" && Object.prototype.toString.call(b) === "[object Date]") {
|
||||
return isDescending ? b - a : a - b;
|
||||
}
|
||||
a = String(a);
|
||||
b = String(b);
|
||||
if(isAlphaNumeric) {
|
||||
return isDescending ? b.localeCompare(a,undefined,{numeric: true,sensitivity: "base"}) : a.localeCompare(b,undefined,{numeric: true,sensitivity: "base"});
|
||||
}
|
||||
if(!isCaseSensitive) {
|
||||
a = a.toLowerCase();
|
||||
b = b.toLowerCase();
|
||||
}
|
||||
return isDescending ? b.localeCompare(a) : a.localeCompare(b);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -795,19 +856,24 @@ Set a tiddlers content to a JavaScript object. Currently this is done by setting
|
||||
title: title of tiddler
|
||||
data: object that can be serialised to JSON
|
||||
fields: optional hashmap of additional tiddler fields to be set
|
||||
options: optional hashmap of options including:
|
||||
suppressTimestamp: if true, don't set the creation/modification timestamps
|
||||
*/
|
||||
exports.setTiddlerData = function(title,data,fields) {
|
||||
exports.setTiddlerData = function(title,data,fields,options) {
|
||||
options = options || {};
|
||||
var existingTiddler = this.getTiddler(title),
|
||||
creationFields = options.suppressTimestamp ? {} : this.getCreationFields(),
|
||||
modificationFields = options.suppressTimestamp ? {} : this.getModificationFields(),
|
||||
newFields = {
|
||||
title: title
|
||||
};
|
||||
};
|
||||
if(existingTiddler && existingTiddler.fields.type === "application/x-tiddler-dictionary") {
|
||||
newFields.text = $tw.utils.makeTiddlerDictionary(data);
|
||||
} else {
|
||||
newFields.type = "application/json";
|
||||
newFields.text = JSON.stringify(data,null,$tw.config.preferences.jsonSpaces);
|
||||
}
|
||||
this.addTiddler(new $tw.Tiddler(this.getCreationFields(),existingTiddler,fields,newFields,this.getModificationFields()));
|
||||
this.addTiddler(new $tw.Tiddler(creationFields,existingTiddler,fields,newFields,modificationFields));
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -844,7 +910,7 @@ exports.clearGlobalCache = function() {
|
||||
exports.getCacheForTiddler = function(title,cacheName,initializer) {
|
||||
this.caches = this.caches || Object.create(null);
|
||||
var caches = this.caches[title];
|
||||
if(caches && caches[cacheName]) {
|
||||
if(caches && caches[cacheName] !== undefined) {
|
||||
return caches[cacheName];
|
||||
} else {
|
||||
if(!caches) {
|
||||
|
||||
@@ -19,71 +19,73 @@ $:/config/Plugins/Disabled/$(currentTiddler)$
|
||||
\end
|
||||
|
||||
\define plugin-table-body(type,disabledMessage,default-popup-state)
|
||||
\whitespace trim
|
||||
<div class="tc-plugin-info-chunk tc-plugin-info-toggle">
|
||||
<$reveal type="nomatch" state=<<popup-state>> text="yes" default="""$default-popup-state$""">
|
||||
<$button class="tc-btn-invisible tc-btn-dropdown" set=<<popup-state>> setTo="yes">
|
||||
{{$:/core/images/chevron-right}}
|
||||
</$button>
|
||||
</$reveal>
|
||||
<$reveal type="match" state=<<popup-state>> text="yes" default="""$default-popup-state$""">
|
||||
<$button class="tc-btn-invisible tc-btn-dropdown" set=<<popup-state>> setTo="no">
|
||||
{{$:/core/images/chevron-down}}
|
||||
</$button>
|
||||
</$reveal>
|
||||
<$reveal type="nomatch" state=<<popup-state>> text="yes" default="""$default-popup-state$""">
|
||||
<$button class="tc-btn-invisible tc-btn-dropdown" set=<<popup-state>> setTo="yes">
|
||||
{{$:/core/images/chevron-right}}
|
||||
</$button>
|
||||
</$reveal>
|
||||
<$reveal type="match" state=<<popup-state>> text="yes" default="""$default-popup-state$""">
|
||||
<$button class="tc-btn-invisible tc-btn-dropdown" set=<<popup-state>> setTo="no">
|
||||
{{$:/core/images/chevron-down}}
|
||||
</$button>
|
||||
</$reveal>
|
||||
</div>
|
||||
<div class="tc-plugin-info-chunk tc-plugin-info-icon">
|
||||
<$transclude tiddler=<<currentTiddler>> subtiddler=<<plugin-icon-title>>>
|
||||
<$transclude tiddler="$:/core/images/plugin-generic-$type$"/>
|
||||
</$transclude>
|
||||
<$transclude tiddler=<<currentTiddler>> subtiddler=<<plugin-icon-title>>>
|
||||
<$transclude tiddler="$:/core/images/plugin-generic-$type$"/>
|
||||
</$transclude>
|
||||
</div>
|
||||
<div class="tc-plugin-info-chunk tc-plugin-info-description">
|
||||
<h1>
|
||||
''<$text text={{{ [<currentTiddler>get[name]] ~[<currentTiddler>split[/]last[1]] }}}/>'': <$view field="description"><$view field="title"/></$view> $disabledMessage$
|
||||
</h1>
|
||||
<h2>
|
||||
<$view field="title"/>
|
||||
</h2>
|
||||
<h2>
|
||||
<div><em><$view field="version"/></em></div>
|
||||
</h2>
|
||||
<h1>
|
||||
''<$text text={{{ [<currentTiddler>get[name]] ~[<currentTiddler>split[/]last[1]] }}}/>'': <$view field="description"><$view field="title"/></$view> $disabledMessage$
|
||||
</h1>
|
||||
<h2>
|
||||
<$view field="title"/>
|
||||
</h2>
|
||||
<h2>
|
||||
<div><em><$view field="version"/></em></div>
|
||||
</h2>
|
||||
</div>
|
||||
\end
|
||||
|
||||
\define plugin-info(type,default-popup-state)
|
||||
\whitespace trim
|
||||
<$set name="popup-state" value=<<popup-state-macro>>>
|
||||
<$reveal type="nomatch" state=<<plugin-disable-title>> text="yes">
|
||||
<$link to={{!!title}} class="tc-plugin-info">
|
||||
<<plugin-table-body type:"$type$" default-popup-state:"""$default-popup-state$""">>
|
||||
</$link>
|
||||
</$reveal>
|
||||
<$reveal type="match" state=<<plugin-disable-title>> text="yes">
|
||||
<$link to={{!!title}} class="tc-plugin-info tc-plugin-info-disabled">
|
||||
<<plugin-table-body type:"$type$" default-popup-state:"""$default-popup-state$""" disabledMessage:"<$macrocall $name='lingo' title='Disabled/Status'/>">>
|
||||
</$link>
|
||||
</$reveal>
|
||||
<$reveal type="match" text="yes" state=<<popup-state>> default="""$default-popup-state$""">
|
||||
<div class="tc-plugin-info-dropdown">
|
||||
<div class="tc-plugin-info-dropdown-body">
|
||||
<$list filter="[all[current]] -[[$:/core]]">
|
||||
<div style="float:right;">
|
||||
<$reveal type="nomatch" state=<<plugin-disable-title>> text="yes">
|
||||
<$button set=<<plugin-disable-title>> setTo="yes" tooltip={{$:/language/ControlPanel/Plugins/Disable/Hint}} aria-label={{$:/language/ControlPanel/Plugins/Disable/Caption}}>
|
||||
<<lingo Disable/Caption>>
|
||||
</$button>
|
||||
</$reveal>
|
||||
<$reveal type="match" state=<<plugin-disable-title>> text="yes">
|
||||
<$button set=<<plugin-disable-title>> setTo="no" tooltip={{$:/language/ControlPanel/Plugins/Enable/Hint}} aria-label={{$:/language/ControlPanel/Plugins/Enable/Caption}}>
|
||||
<<lingo Enable/Caption>>
|
||||
</$button>
|
||||
</$reveal>
|
||||
</div>
|
||||
</$list>
|
||||
<$set name="tabsList" filter="[<currentTiddler>list[]] contents">
|
||||
<$macrocall $name="tabs" state=<<tabs-state-macro>> tabsList=<<tabsList>> default={{{ [enlist<tabsList>] }}} template="$:/core/ui/PluginInfo"/>
|
||||
</$set>
|
||||
</div>
|
||||
</div>
|
||||
</$reveal>
|
||||
<$reveal type="nomatch" state=<<plugin-disable-title>> text="yes">
|
||||
<$link to={{!!title}} class="tc-plugin-info">
|
||||
<<plugin-table-body type:"$type$" default-popup-state:"""$default-popup-state$""">>
|
||||
</$link>
|
||||
</$reveal>
|
||||
<$reveal type="match" state=<<plugin-disable-title>> text="yes">
|
||||
<$link to={{!!title}} class="tc-plugin-info tc-plugin-info-disabled">
|
||||
<<plugin-table-body type:"$type$" default-popup-state:"""$default-popup-state$""" disabledMessage:"<$macrocall $name='lingo' title='Disabled/Status'/>">>
|
||||
</$link>
|
||||
</$reveal>
|
||||
<$reveal type="match" text="yes" state=<<popup-state>> default="""$default-popup-state$""">
|
||||
<div class="tc-plugin-info-dropdown">
|
||||
<div class="tc-plugin-info-dropdown-body">
|
||||
<$list filter="[all[current]] -[[$:/core]]">
|
||||
<div style="float:right;">
|
||||
<$reveal type="nomatch" state=<<plugin-disable-title>> text="yes">
|
||||
<$button set=<<plugin-disable-title>> setTo="yes" tooltip={{$:/language/ControlPanel/Plugins/Disable/Hint}} aria-label={{$:/language/ControlPanel/Plugins/Disable/Caption}}>
|
||||
<<lingo Disable/Caption>>
|
||||
</$button>
|
||||
</$reveal>
|
||||
<$reveal type="match" state=<<plugin-disable-title>> text="yes">
|
||||
<$button set=<<plugin-disable-title>> setTo="no" tooltip={{$:/language/ControlPanel/Plugins/Enable Hint}} aria-label={{$:/language/ControlPanel/Plugins/Enable/Caption}}>
|
||||
<<lingo Enable/Caption>>
|
||||
</$button>
|
||||
</$reveal>
|
||||
</div>
|
||||
</$list>
|
||||
<$set name="tabsList" filter="[<currentTiddler>list[]] contents">
|
||||
<$macrocall $name="tabs" state=<<tabs-state-macro>> tabsList=<<tabsList>> default={{{ [enlist<tabsList>] }}} template="$:/core/ui/PluginInfo"/>
|
||||
</$set>
|
||||
</div>
|
||||
</div>
|
||||
</$reveal>
|
||||
</$set>
|
||||
\end
|
||||
|
||||
|
||||
@@ -16,4 +16,4 @@ caption: {{$:/language/ControlPanel/Plugins/Caption}}
|
||||
|
||||
<<lingo Installed/Hint>>
|
||||
|
||||
<$macrocall $name="tabs" tabsList="[[$:/core/ui/ControlPanel/Plugins/Installed/Plugins]] [[$:/core/ui/ControlPanel/Plugins/Installed/Themes]] [[$:/core/ui/ControlPanel/Plugins/Installed/Languages]]" default="$:/core/ui/ControlPanel/Plugins/Installed/Plugins" explicitState="$:/state/tab--86143343"/>
|
||||
<$macrocall $name="tabs" tabsList="[all[tiddlers+shadows]tag[$:/tags/ControlPanel/Plugins]!has[draft.of]]" default="$:/core/ui/ControlPanel/Plugins/Installed/Plugins" explicitState="$:/state/tab--86143343"/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
title: $:/core/ui/ControlPanel/Plugins/Installed/Languages
|
||||
tags: $:/tags/ControlPanel/Plugins
|
||||
caption: {{$:/language/ControlPanel/Plugins/Languages/Caption}} (<$count filter="[!has[draft.of]plugin-type[language]]"/>)
|
||||
|
||||
<<plugin-table language>>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
title: $:/core/ui/ControlPanel/Plugins/Installed/Plugins
|
||||
tags: $:/tags/ControlPanel/Plugins
|
||||
caption: {{$:/language/ControlPanel/Plugins/Plugins/Caption}} (<$count filter="[!has[draft.of]plugin-type[plugin]]"/>)
|
||||
|
||||
<<plugin-table plugin>>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
title: $:/core/ui/ControlPanel/Plugins/Installed/Themes
|
||||
tags: $:/tags/ControlPanel/Plugins
|
||||
caption: {{$:/language/ControlPanel/Plugins/Themes/Caption}} (<$count filter="[!has[draft.of]plugin-type[theme]]"/>)
|
||||
|
||||
<<plugin-table theme>>
|
||||
|
||||
@@ -13,7 +13,7 @@ title: $:/core/ui/EditorToolbar/link-dropdown
|
||||
<$set name="userInput" value={{{ [<storeTitle>get[text]] }}}><$list filter="[<searchTiddler>get[text]!match<userInput>]" emptyMessage="""<$action-deletetiddler $filter="[<searchTiddler>] [<linkTiddler>] [<storeTitle>] [<searchListState>]"/>"""><$action-setfield $tiddler=<<searchTiddler>> text=<<userInput>>/><$action-setfield $tiddler=<<refreshTitle>> text="yes"/></$list></$set>
|
||||
\end
|
||||
|
||||
\define cancel-search-actions() <$list filter="[<storeTitle>!has[text]] +[<searchTiddler>!has[text]]" emptyMessage="""<<cancel-search-actions-inner>>"""><$action-sendmessage $message="tm-edit-text-operation" $param="wrap-selection" prefix="" suffix=""/></$list>
|
||||
\define cancel-search-actions() <$list filter="[<storeTitle>!has[text]] +[<searchTiddler>!has[text]]" emptyMessage="""<<cancel-search-actions-inner>>"""><$action-sendmessage $message="tm-edit-text-operation" $param="focus-editor"/></$list>
|
||||
|
||||
\define external-link()
|
||||
<$button class="tc-btn-invisible" style="width: auto; display: inline-block; background-colour: inherit;" actions=<<add-link-actions>>>
|
||||
|
||||
53
core/ui/EditorToolbar/stamp-dropdown-item-template.tid
Normal file
53
core/ui/EditorToolbar/stamp-dropdown-item-template.tid
Normal file
@@ -0,0 +1,53 @@
|
||||
title: $:/core/ui/EditorToolbar/StampDropdown/ItemTemplate
|
||||
|
||||
<$linkcatcher actions="""
|
||||
|
||||
<$list filter="[<modifier>!match[ctrl]]" variable="ignore">
|
||||
|
||||
<$list filter="[<currentTiddler>addsuffix[/prefix]!is[tiddler]!is[shadow]removesuffix[/prefix]addsuffix[/suffix]!is[tiddler]!is[shadow]]" variable="ignore">
|
||||
|
||||
<$action-sendmessage
|
||||
$message="tm-edit-text-operation"
|
||||
$param="replace-selection"
|
||||
text={{{ [<currentTiddler>get[text]] }}}
|
||||
/>
|
||||
|
||||
</$list>
|
||||
|
||||
|
||||
<$list filter="[<currentTiddler>addsuffix[/prefix]] [<currentTiddler>addsuffix[/suffix]] +[is[shadow]] :else[is[tiddler]] +[limit[1]]" variable="ignore">
|
||||
|
||||
<$action-sendmessage
|
||||
$message="tm-edit-text-operation"
|
||||
$param="wrap-selection"
|
||||
prefix={{{ [<currentTiddler>addsuffix[/prefix]get[text]] }}}
|
||||
suffix={{{ [<currentTiddler>addsuffix[/suffix]get[text]] }}}
|
||||
/>
|
||||
|
||||
</$list>
|
||||
|
||||
</$list>
|
||||
|
||||
<$list filter="[<modifier>match[ctrl]]" variable="ignore">
|
||||
|
||||
<$action-sendmessage $message="tm-edit-tiddler"/>
|
||||
|
||||
</$list>
|
||||
|
||||
<$action-deletetiddler
|
||||
$tiddler=<<dropdown-state>>
|
||||
/>
|
||||
|
||||
""">
|
||||
|
||||
<$link tooltip={{{ [<currentTiddler>get[description]] }}}>
|
||||
|
||||
<$transclude tiddler=<<currentTiddler>> field="caption" mode="inline">
|
||||
|
||||
<$view tiddler=<<currentTiddler>> field="title" />
|
||||
|
||||
</$transclude>
|
||||
|
||||
</$link>
|
||||
|
||||
</$linkcatcher>
|
||||
@@ -1,48 +1,6 @@
|
||||
title: $:/core/ui/EditorToolbar/stamp-dropdown
|
||||
|
||||
\define toolbar-button-stamp-inner()
|
||||
<$button tag="a">
|
||||
|
||||
<$list filter="[[$(snippetTitle)$]addsuffix[/prefix]is[missing]removesuffix[/prefix]addsuffix[/suffix]is[missing]]">
|
||||
|
||||
<$action-sendmessage
|
||||
$message="tm-edit-text-operation"
|
||||
$param="replace-selection"
|
||||
text={{$(snippetTitle)$}}
|
||||
/>
|
||||
|
||||
</$list>
|
||||
|
||||
|
||||
<$list filter="[[$(snippetTitle)$]addsuffix[/prefix]is[missing]removesuffix[/prefix]addsuffix[/suffix]!is[missing]] [[$(snippetTitle)$]addsuffix[/prefix]!is[missing]removesuffix[/prefix]addsuffix[/suffix]is[missing]] [[$(snippetTitle)$]addsuffix[/prefix]!is[missing]removesuffix[/prefix]addsuffix[/suffix]!is[missing]]">
|
||||
|
||||
<$action-sendmessage
|
||||
$message="tm-edit-text-operation"
|
||||
$param="wrap-selection"
|
||||
prefix={{{ [[$(snippetTitle)$]addsuffix[/prefix]get[text]] }}}
|
||||
suffix={{{ [[$(snippetTitle)$]addsuffix[/suffix]get[text]] }}}
|
||||
/>
|
||||
|
||||
</$list>
|
||||
|
||||
<$action-deletetiddler
|
||||
$tiddler=<<dropdown-state>>
|
||||
/>
|
||||
|
||||
<$transclude tiddler=<<snippetTitle>> field="caption" mode="inline">
|
||||
|
||||
<$view tiddler=<<snippetTitle>> field="title" />
|
||||
|
||||
</$transclude>
|
||||
|
||||
</$button>
|
||||
\end
|
||||
|
||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/TextEditor/Snippet]!has[draft.of]sort[caption]]" variable="snippetTitle">
|
||||
|
||||
<<toolbar-button-stamp-inner>>
|
||||
|
||||
</$list>
|
||||
<$macrocall $name="list-tagged-draggable" tag="$:/tags/TextEditor/Snippet" subFilter="!is[draft]" itemTemplate="$:/core/ui/EditorToolbar/StampDropdown/ItemTemplate"/>
|
||||
|
||||
----
|
||||
|
||||
|
||||
@@ -6,4 +6,5 @@ description: {{$:/language/Buttons/Stamp/Hint}}
|
||||
condition: [<targetTiddler>type[]] [<targetTiddler>get[type]prefix[text/]] [<targetTiddler>get[type]match[application/javascript]] [<targetTiddler>get[type]match[application/json]] [<targetTiddler>get[type]match[application/x-tiddler-dictionary]] [<targetTiddler>get[type]match[image/svg+xml]] +[first[]]
|
||||
shortcuts: ((stamp))
|
||||
dropdown: $:/core/ui/EditorToolbar/stamp-dropdown
|
||||
button-classes: tc-editortoolbar-stamp-button
|
||||
text:
|
||||
|
||||
22
core/ui/ExportTiddlyWikiCore.tid
Normal file
22
core/ui/ExportTiddlyWikiCore.tid
Normal file
@@ -0,0 +1,22 @@
|
||||
title: $:/core/ui/ExportTiddlyWikiCore
|
||||
|
||||
\define jsFileName() tiddlywikicore-$(version)$.js
|
||||
\define noExportMsg()
|
||||
It appears that you have a wiki with an external ~TiddlyWiki core. The export action cannot be performed.
|
||||
<p>You will need to view the page source in your browser. Then go to the very bottom the the source, find the last `<script>`
|
||||
element, and right-click its `src` URI. Save the link as ''$(jsFileName)$''</p>
|
||||
\end
|
||||
|
||||
''For advanced users''
|
||||
|
||||
Export the ~TiddlyWiki core ~JavaScript code for running with external ~JavaScript:
|
||||
|
||||
<$button tooltip="Export the ~TiddlyWiki core code for running with external ~JavaScript" aria-label="export TiddlyWiki core" class="tc-btn-big-green">
|
||||
<$list filter="[[$:/boot/boot.js]is[missing]]" variable="ignore" emptyMessage="""<$action-sendmessage $message="tm-download-file" $param="$:/core/templates/tiddlywiki5.js" filename=<<jsFileName>>/>""" >
|
||||
<$action-setfield $tiddler=<<qualify "$:/temp/alert">> text=<<noExportMsg>> subtitle="Export ~TiddllyWiki Core"/>
|
||||
<$action-sendmessage $message="tm-modal" $param=<<qualify "$:/temp/alert">>/>
|
||||
</$list>
|
||||
{{$:/core/images/download-button}} Download ~TiddlyWiki core
|
||||
</$button>
|
||||
|
||||
[[Further information|https://tiddlywiki.com/#Using%20the%20external%20JavaScript%20template]]
|
||||
@@ -1,9 +1,5 @@
|
||||
title: $:/core/ui/Buttons/export-tiddlywikicore
|
||||
tags: $:/tags/PageControls
|
||||
caption: {{$:/core/images/star-filled}} {{$:/language/Buttons/ExportTiddlyWikiCore/Caption}}
|
||||
description: {{$:/language/Buttons/ExportTiddlyWikiCore/Hint}}
|
||||
title: $:/core/ui/ExportTiddlyWikiCore
|
||||
|
||||
\whitespace trim
|
||||
\define jsFileName() tiddlywikicore-$(version)$.js
|
||||
\define noExportMsg()
|
||||
It appears that you have a wiki with an external ~TiddlyWiki core. The export action cannot be performed.
|
||||
@@ -11,15 +7,16 @@ It appears that you have a wiki with an external ~TiddlyWiki core. The export ac
|
||||
element, and right-click its `src` URI. Save the link as ''$(jsFileName)$''</p>
|
||||
\end
|
||||
|
||||
<$button tooltip={{$:/language/Buttons/ExportTiddlyWikiCore/Hint}} aria-label={{$:/language/Buttons/ExportTiddlyWikiCore/Caption}} class=<<tv-config-toolbar-class>>>
|
||||
''For advanced users''
|
||||
|
||||
Export the ~TiddlyWiki core ~JavaScript code for running with external ~JavaScript:
|
||||
|
||||
<$button tooltip="Export the ~TiddlyWiki core code for running with external ~JavaScript" aria-label="export TiddlyWiki core" class="tc-btn-big-green">
|
||||
<$list filter="[[$:/boot/boot.js]is[missing]]" variable="ignore" emptyMessage="""<$action-sendmessage $message="tm-download-file" $param="$:/core/templates/tiddlywiki5.js" filename=<<jsFileName>>/>""" >
|
||||
<$action-setfield $tiddler=<<qualify "$:/temp/alert">> text=<<noExportMsg>> subtitle="Export ~TiddllyWiki Core"/>
|
||||
<$action-sendmessage $message="tm-modal" $param=<<qualify "$:/temp/alert">>/>
|
||||
</$list>
|
||||
<$list filter="[<tv-config-toolbar-icons>match[yes]]" variable="listItem">
|
||||
{{$:/core/images/star-filled}}
|
||||
</$list>
|
||||
<$list filter="[<tv-config-toolbar-text>match[yes]]">
|
||||
<span class="tc-btn-text"><$text text={{$:/language/Buttons/ExportTiddlyWikiCore/Caption}}/></span>
|
||||
</$list>
|
||||
{{$:/core/images/download-button}} Download ~TiddlyWiki core
|
||||
</$button>
|
||||
|
||||
[[Further information|https://tiddlywiki.com/#Using%20the%20external%20JavaScript%20template]]
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
title: $:/core/ui/ViewTemplate
|
||||
|
||||
\whitespace trim
|
||||
\define folded-state()
|
||||
$:/state/folded/$(currentTiddler)$
|
||||
\end
|
||||
\define cancel-delete-tiddler-actions(message) <$action-sendmessage $message="tm-$message$-tiddler"/>
|
||||
\import [all[shadows+tiddlers]tag[$:/tags/Macro/View]!has[draft.of]]
|
||||
<$vars storyTiddler=<<currentTiddler>> tiddlerInfoState=<<qualify "$:/state/popup/tiddler-info">>><div data-tiddler-title=<<currentTiddler>> data-tags={{!!tags}} class={{{ tc-tiddler-frame tc-tiddler-view-frame [<currentTiddler>is[tiddler]then[tc-tiddler-exists]] [<currentTiddler>is[missing]!is[shadow]then[tc-tiddler-missing]] [<currentTiddler>is[shadow]then[tc-tiddler-exists tc-tiddler-shadow]] [<currentTiddler>is[shadow]is[tiddler]then[tc-tiddler-overridden-shadow]] [<currentTiddler>is[system]then[tc-tiddler-system]] [{!!class}] [<currentTiddler>tags[]encodeuricomponent[]addprefix[tc-tagged-]] +[join[ ]] }}}><$list filter="[all[shadows+tiddlers]tag[$:/tags/ViewTemplate]!has[draft.of]]" variable="listItem"><$transclude tiddler=<<listItem>>/></$list>
|
||||
<$vars storyTiddler=<<currentTiddler>> tiddlerInfoState=<<qualify "$:/state/popup/tiddler-info">>>
|
||||
<div data-tiddler-title=<<currentTiddler>> data-tags={{!!tags}} class={{{ tc-tiddler-frame tc-tiddler-view-frame [<currentTiddler>is[tiddler]then[tc-tiddler-exists]] [<currentTiddler>is[missing]!is[shadow]then[tc-tiddler-missing]] [<currentTiddler>is[shadow]then[tc-tiddler-exists tc-tiddler-shadow]] [<currentTiddler>is[shadow]is[tiddler]then[tc-tiddler-overridden-shadow]] [<currentTiddler>is[system]then[tc-tiddler-system]] [{!!class}] [<currentTiddler>tags[]encodeuricomponent[]addprefix[tc-tagged-]] +[join[ ]] }}}>
|
||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/ViewTemplate]!has[draft.of]]" variable="listItem">
|
||||
<$transclude tiddler=<<listItem>>/>
|
||||
</$list>
|
||||
</div>
|
||||
</$vars>
|
||||
|
||||
@@ -5,13 +5,10 @@ tags: $:/tags/ViewTemplate
|
||||
\define title-styles()
|
||||
fill:$(foregroundColor)$;
|
||||
\end
|
||||
\define config-title()
|
||||
$:/config/ViewToolbarButtons/Visibility/$(listItem)$
|
||||
\end
|
||||
<div class="tc-tiddler-title">
|
||||
<div class="tc-titlebar">
|
||||
<span class="tc-tiddler-controls">
|
||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/ViewToolbar]!has[draft.of]]" variable="listItem"><$reveal type="nomatch" state=<<config-title>> text="hide"><$set name="tv-config-toolbar-class" filter="[<tv-config-toolbar-class>] [<listItem>encodeuricomponent[]addprefix[tc-btn-]]"><$transclude tiddler=<<listItem>>/></$set></$reveal></$list>
|
||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/ViewToolbar]!has[draft.of]] :filter[lookup[$:/config/ViewToolbarButtons/Visibility/]!match[hide]]" storyview="pop" variable="listItem"><$set name="tv-config-toolbar-class" filter="[<tv-config-toolbar-class>] [<listItem>encodeuricomponent[]addprefix[tc-btn-]]"><$transclude tiddler=<<listItem>>/></$set></$list>
|
||||
</span>
|
||||
<$set name="tv-wikilinks" value={{$:/config/Tiddlers/TitleLinks}}>
|
||||
<$link>
|
||||
@@ -43,4 +40,4 @@ $:/config/ViewToolbarButtons/Visibility/$(listItem)$
|
||||
<$list filter="[all[shadows+tiddlers]tag[$:/tags/TiddlerInfoSegment]!has[draft.of]] [[$:/core/ui/TiddlerInfo]]" variable="listItem"><$transclude tiddler=<<listItem>> mode="block"/></$list>
|
||||
|
||||
</$reveal>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
title: $:/config/EditTabIndex
|
||||
|
||||
1
|
||||
text: 1
|
||||
@@ -4,7 +4,6 @@ core/ui/Buttons/advanced-search: hide
|
||||
core/ui/Buttons/close-all: hide
|
||||
core/ui/Buttons/encryption: hide
|
||||
core/ui/Buttons/export-page: hide
|
||||
core/ui/Buttons/export-tiddlywikicore: hide
|
||||
core/ui/Buttons/fold-all: hide
|
||||
core/ui/Buttons/full-screen: hide
|
||||
core/ui/Buttons/home: hide
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
title: $:/config/shortcuts-mac/
|
||||
|
||||
bold: meta-B
|
||||
input-tab-left: ctrl-Left
|
||||
input-tab-right: ctrl-Right
|
||||
italic: meta-I
|
||||
underline: meta-U
|
||||
new-image: ctrl-I
|
||||
|
||||
@@ -18,8 +18,8 @@ input-accept: Enter
|
||||
input-accept-variant: ctrl-Enter
|
||||
input-cancel: Escape
|
||||
input-down: Down
|
||||
input-tab-left: alt-Left
|
||||
input-tab-right: alt-Right
|
||||
input-tab-left: alt-ctrl-Left
|
||||
input-tab-right: alt-ctrl-Right
|
||||
input-up: Up
|
||||
layout-switcher: ctrl-shift-L
|
||||
link: ctrl-L
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
title: $:/tags/ControlPanel/Plugins
|
||||
list: [[$:/core/ui/ControlPanel/Plugins/Installed]] [[$:/core/ui/ControlPanel/Plugins/Add]]
|
||||
list: $:/core/ui/ControlPanel/Plugins/Installed/Plugins $:/core/ui/ControlPanel/Plugins/Installed/Themes $:/core/ui/ControlPanel/Plugins/Installed/Languages
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
title: $:/tags/PageControls
|
||||
list: [[$:/core/ui/Buttons/home]] [[$:/core/ui/Buttons/close-all]] [[$:/core/ui/Buttons/fold-all]] [[$:/core/ui/Buttons/unfold-all]] [[$:/core/ui/Buttons/permaview]] [[$:/core/ui/Buttons/new-tiddler]] [[$:/core/ui/Buttons/new-journal]] [[$:/core/ui/Buttons/new-image]] [[$:/core/ui/Buttons/import]] [[$:/core/ui/Buttons/export-page]] [[$:/core/ui/Buttons/control-panel]] [[$:/core/ui/Buttons/advanced-search]] [[$:/core/ui/Buttons/manager]] [[$:/core/ui/Buttons/tag-manager]] [[$:/core/ui/Buttons/language]] [[$:/core/ui/Buttons/palette]] [[$:/core/ui/Buttons/theme]] [[$:/core/ui/Buttons/storyview]] [[$:/core/ui/Buttons/encryption]] [[$:/core/ui/Buttons/timestamp]] [[$:/core/ui/Buttons/full-screen]] [[$:/core/ui/Buttons/print]] [[$:/core/ui/Buttons/save-wiki]] [[$:/core/ui/Buttons/refresh]] [[$:/core/ui/Buttons/export-tiddlywikicore]] [[$:/core/ui/Buttons/more-page-actions]]
|
||||
list: [[$:/core/ui/Buttons/home]] [[$:/core/ui/Buttons/close-all]] [[$:/core/ui/Buttons/fold-all]] [[$:/core/ui/Buttons/unfold-all]] [[$:/core/ui/Buttons/permaview]] [[$:/core/ui/Buttons/new-tiddler]] [[$:/core/ui/Buttons/new-journal]] [[$:/core/ui/Buttons/new-image]] [[$:/core/ui/Buttons/import]] [[$:/core/ui/Buttons/export-page]] [[$:/core/ui/Buttons/control-panel]] [[$:/core/ui/Buttons/advanced-search]] [[$:/core/ui/Buttons/manager]] [[$:/core/ui/Buttons/tag-manager]] [[$:/core/ui/Buttons/language]] [[$:/core/ui/Buttons/palette]] [[$:/core/ui/Buttons/theme]] [[$:/core/ui/Buttons/storyview]] [[$:/core/ui/Buttons/encryption]] [[$:/core/ui/Buttons/timestamp]] [[$:/core/ui/Buttons/full-screen]] [[$:/core/ui/Buttons/print]] [[$:/core/ui/Buttons/save-wiki]] [[$:/core/ui/Buttons/refresh]] [[$:/core/ui/Buttons/more-page-actions]]
|
||||
|
||||
@@ -14,6 +14,7 @@ Welcome to the developer documentation for TiddlyWiki (https://tiddlywiki.com/).
|
||||
** HookMechanism
|
||||
** [[Using ES2016 for Writing Plugins]]
|
||||
** [[Adding Babel Polyfill to TiddlyWiki]]
|
||||
** [[TiddlyWiki Drag and Drop Interoperability]]
|
||||
* The original developer documentation from https://tiddlywiki.com:
|
||||
** [[TiddlyWiki for Developers]]
|
||||
** [[TiddlyWiki Coding Style Guidelines]]
|
||||
|
||||
@@ -37,8 +37,6 @@ Example:
|
||||
</div>
|
||||
```
|
||||
|
||||
|
||||
|
||||
!!! With encryption
|
||||
|
||||
If the ~TiddlyWiki is configured to encrypt its content, the tiddlers are stored as as an encrypted JSON string in a `<pre>` tag with the `id` attribute "encryptedStoreArea".
|
||||
@@ -83,15 +81,20 @@ Any tiddlers in the older format `<div>` store area are also loaded before tiddl
|
||||
|
||||
Tiddlers from the new `<script>` tag store areas are loaded in the order of their store areas in the HTML file. Therefore if the same tiddler exists in two different `<script>` tag store areas, the tiddler from the later store area takes precedence. Note however that tiddlers from `<script>` tag store areas always take precedence over tiddlers from the older format `<div>` store area. Therefore a tiddler from the older `<div>` store area can never overwrite a tiddler from a `<script>` tag store area.
|
||||
|
||||
Note that all `<script>` tags with the `class` attribute "tiddlywiki-tiddler-store" have their content parsed as JSON and loaded as tiddlers. This allows external tools to easily insert tiddlers into an HTML file by appending additional `<script>` tag(s) at the very end of the HTML file:
|
||||
Note that all `<script>` tags with the `class` attribute "tiddlywiki-tiddler-store" that precede the `$:/boot.js` module in the HTML file will have their content parsed as JSON and loaded as tiddlers.
|
||||
|
||||
This allows external tools to easily insert tiddlers into an HTML file by prepending an additional `<script>` tag(s) at the very start of the HTML file:
|
||||
|
||||
```html
|
||||
</html>
|
||||
<script class="tiddlywiki-tiddler-store" type="application/json">[
|
||||
{"created":"20210525212411223","text":"This is some test text","tags":"[[test tag]] [[another tag]]","title":"My new tiddler to insert","modified":"20210525212430577"}
|
||||
]</script>
|
||||
<!doctype html>
|
||||
...
|
||||
```
|
||||
|
||||
Although invalid HTML, all known browsers will silently move the script tag to a valid position within the
|
||||
|
||||
Additional topics:
|
||||
|
||||
* [[Extracting tiddlers from a single file TiddlyWiki]]
|
||||
@@ -0,0 +1,15 @@
|
||||
title: TiddlyWiki Drag and Drop Interoperability
|
||||
|
||||
It is straightforward to allow any HTML file to interoperate with TiddlyWiki's drag and drop implementation.
|
||||
|
||||
This example shows how to attach draggable data to a DOM element. The data is provided in two different forms:
|
||||
|
||||
* the string data is used if the element is dragged onto a text editing area
|
||||
* the tiddler data is used if the element is dragged into TiddlyWiki's import area
|
||||
|
||||
<$button>
|
||||
<$action-sendmessage $message="tm-download-file" $param="$:/dev/save/dragndropinterop" filename="index.html"/>
|
||||
Download this sample code
|
||||
</$button>
|
||||
|
||||
<$codeblock code={{DragAndDropInterop}} language="text/html"/>
|
||||
35
editions/dev/tiddlers/new/dragndropinterop.html
Normal file
35
editions/dev/tiddlers/new/dragndropinterop.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Drag and Drop Interoperability with TiddlyWiki Demo</title>
|
||||
<style>
|
||||
#draggable {
|
||||
padding: 1em;
|
||||
margin: 1em;
|
||||
background: #ecc;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="draggable" draggable="true">
|
||||
Drag me to a TiddlyWiki window
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
|
||||
var titleString = "This is the string that appears when the block is dragged to a text input";
|
||||
var tiddlerData = [
|
||||
{title: "Tiddler One", text: "This is one of the payload tiddlers"},
|
||||
{title: "Tiddler Two", text: "This is another of the payload tiddlers", "custom-field": "A custom field value"}
|
||||
];
|
||||
|
||||
document.getElementById("draggable").addEventListener("dragstart",function(event) {
|
||||
event.dataTransfer.setData("URL","data:text/vnd.tiddler," + encodeURIComponent(JSON.stringify(tiddlerData)));
|
||||
event.dataTransfer.setData("Text",titleString);
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
});
|
||||
|
||||
</script>
|
||||
</html>
|
||||
3
editions/dev/tiddlers/new/dragndropinterop.html.meta
Normal file
3
editions/dev/tiddlers/new/dragndropinterop.html.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
title: DragAndDropInterop
|
||||
type: text/html
|
||||
|
||||
123
editions/dev/tiddlers/new/pipes/External Pipes.tid
Normal file
123
editions/dev/tiddlers/new/pipes/External Pipes.tid
Normal 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".
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
3
editions/dev/tiddlers/new/save-dragndropinterop.tid
Normal file
3
editions/dev/tiddlers/new/save-dragndropinterop.tid
Normal file
@@ -0,0 +1,3 @@
|
||||
title: $:/dev/save/dragndropinterop
|
||||
|
||||
<$view tiddler="DragAndDropInterop" field="text"/>
|
||||
@@ -1,15 +1,13 @@
|
||||
title: $:/core/ui/ViewTemplate
|
||||
|
||||
\whitespace trim
|
||||
\define frame-classes()
|
||||
tc-tiddler-frame tc-tiddler-view-frame $(missingTiddlerClass)$ $(shadowTiddlerClass)$ $(systemTiddlerClass)$ $(tiddlerTagClasses)$ $(userClass)$
|
||||
\end
|
||||
\define folded-state()
|
||||
$:/state/folded/$(currentTiddler)$
|
||||
\end
|
||||
<$vars storyTiddler=<<currentTiddler>> tiddlerInfoState=<<qualify "$:/state/popup/tiddler-info">> userClass={{!!class}}>
|
||||
<$tiddler tiddler=<<currentTiddler>>>
|
||||
<div data-tiddler-title=<<currentTiddler>> data-tags={{!!tags}} class=<<frame-classes>>>
|
||||
\define cancel-delete-tiddler-actions(message) <$action-sendmessage $message="tm-$message$-tiddler"/>
|
||||
\import [all[shadows+tiddlers]tag[$:/tags/Macro/View]!has[draft.of]]
|
||||
<$vars storyTiddler=<<currentTiddler>> tiddlerInfoState=<<qualify "$:/state/popup/tiddler-info">>>
|
||||
<div data-tiddler-title=<<currentTiddler>> data-tags={{!!tags}} class={{{ tc-tiddler-frame tc-tiddler-view-frame [<currentTiddler>is[tiddler]then[tc-tiddler-exists]] [<currentTiddler>is[missing]!is[shadow]then[tc-tiddler-missing]] [<currentTiddler>is[shadow]then[tc-tiddler-exists tc-tiddler-shadow]] [<currentTiddler>is[shadow]is[tiddler]then[tc-tiddler-overridden-shadow]] [<currentTiddler>is[system]then[tc-tiddler-system]] [{!!class}] [<currentTiddler>tags[]encodeuricomponent[]addprefix[tc-tagged-]] +[join[ ]] }}}>
|
||||
<$set name="state" value={{{ [[$:/state/viewtemplate/visibility/]addsuffix<currentTiddler>] }}}>
|
||||
<$reveal stateTitle=<<state>> type="nomatch" text="" default="" tag="div">
|
||||
<div class="tc-dynaview-track-tiddler-when-visible" data-dynaview-track-tiddler=<<state>>>
|
||||
@@ -24,5 +22,4 @@ $:/state/folded/$(currentTiddler)$
|
||||
</$reveal>
|
||||
</$set>
|
||||
</div>
|
||||
</$tiddler>
|
||||
</$vars>
|
||||
|
||||
@@ -54,7 +54,7 @@ title="New tiddler"
|
||||
|
||||
#* Asígnale la etiqueta <<.tag ~$:/tags/ViewToolbar]]>>
|
||||
|
||||
# Hay que crear un tiddler que le diga a ~TiddlyWIki si el botón será o no visible en la barra. Llamémosle por ejemplo <<.tid "~$:/config/ViewToolbarButtons/Visibility/Receta"">>. Escribe `reveal` en el cuerpo y guárdalo.
|
||||
# Hay que crear un tiddler que le diga a ~TiddlyWIki si el botón será o no visible en la barra. Llamémosle por ejemplo <<.tid "~$:/config/ViewToolbarButtons/Visibility/Receta"">>. Escribe `show` en el cuerpo y guárdalo.
|
||||
|
||||
# Habrá que posicionar el botón adecuadamente. Abre el tiddler <<.tid ~$:/tags/ViewToolbar>> e inserta el nombre de tu botón en el lugar adecuado del campo <<.field list>>.
|
||||
|
||||
|
||||
80
editions/externalpipesdemo/demo-tasks/badtagger.js
Executable file
80
editions/externalpipesdemo/demo-tasks/badtagger.js
Executable 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");
|
||||
}
|
||||
109
editions/externalpipesdemo/demo-tasks/mimic.js
Executable file
109
editions/externalpipesdemo/demo-tasks/mimic.js
Executable 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("");
|
||||
}
|
||||
3
editions/externalpipesdemo/demo-tasks/readme.md
Normal file
3
editions/externalpipesdemo/demo-tasks/readme.md
Normal 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.
|
||||
39
editions/externalpipesdemo/demo-tasks/recase_erl.sh
Executable file
39
editions/externalpipesdemo/demo-tasks/recase_erl.sh
Executable 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.
|
||||
77
editions/externalpipesdemo/demo-tasks/recaser.js
Executable file
77
editions/externalpipesdemo/demo-tasks/recaser.js
Executable 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("");
|
||||
}
|
||||
|
||||
49
editions/externalpipesdemo/demo-tasks/reverser.js
Executable file
49
editions/externalpipesdemo/demo-tasks/reverser.js
Executable 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("");
|
||||
}
|
||||
|
||||
82
editions/externalpipesdemo/demo-tasks/stats.js
Executable file
82
editions/externalpipesdemo/demo-tasks/stats.js
Executable 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"
|
||||
}
|
||||
];
|
||||
};
|
||||
@@ -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.
|
||||
3341
editions/externalpipesdemo/tiddlers/Alice in Wonderland.tid
Normal file
3341
editions/externalpipesdemo/tiddlers/Alice in Wonderland.tid
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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>>/>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user