1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2025-08-07 22:33:50 +00:00

Merge branch 'Jermolene:master' into offline-extjs

This commit is contained in:
cdruan 2021-05-23 16:50:41 -07:00
commit 31583a8d85
153 changed files with 2499 additions and 574 deletions

View File

@ -5,52 +5,52 @@ Optimise the SVGs in ./core/images using SVGO from https://github.com/svg/svgo
Install SVGO with the following command in the root of the repo: Install SVGO with the following command in the root of the repo:
npm install svgo npm install svgo@2.3.0
*/ */
"use strict"; "use strict";
var fs = require("fs"), var fs = require("fs"),
path = require("path"), path = require("path"),
SVGO = require("svgo"), { optimize } = require("svgo"),
svgo = new SVGO({ config = {
plugins: [ plugins: [
{cleanupAttrs: true}, 'cleanupAttrs',
{removeDoctype: true}, 'removeDoctype',
{removeXMLProcInst: true}, 'removeXMLProcInst',
{removeComments: true}, 'removeComments',
{removeMetadata: true}, 'removeMetadata',
{removeTitle: true}, 'removeTitle',
{removeDesc: true}, 'removeDesc',
{removeUselessDefs: true}, 'removeUselessDefs',
{removeEditorsNSData: true}, 'removeEditorsNSData',
{removeEmptyAttrs: true}, 'removeEmptyAttrs',
{removeHiddenElems: true}, 'removeHiddenElems',
{removeEmptyText: true}, 'removeEmptyText',
{removeEmptyContainers: true}, 'removeEmptyContainers',
{removeViewBox: false}, // 'removeViewBox',
{cleanupEnableBackground: true}, 'cleanupEnableBackground',
{convertStyleToAttrs: true}, 'convertStyleToAttrs',
{convertColors: true}, 'convertColors',
{convertPathData: true}, 'convertPathData',
{convertTransform: true}, 'convertTransform',
{removeUnknownsAndDefaults: true}, 'removeUnknownsAndDefaults',
{removeNonInheritableGroupAttrs: true}, 'removeNonInheritableGroupAttrs',
{removeUselessStrokeAndFill: true}, 'removeUselessStrokeAndFill',
{removeUnusedNS: true}, 'removeUnusedNS',
{cleanupIDs: true}, 'cleanupIDs',
{cleanupNumericValues: true}, 'cleanupNumericValues',
{moveElemsAttrsToGroup: true}, 'moveElemsAttrsToGroup',
{moveGroupAttrsToElems: true}, 'moveGroupAttrsToElems',
{collapseGroups: true}, 'collapseGroups',
{removeRasterImages: false}, // 'removeRasterImages',
{mergePaths: true}, 'mergePaths',
{convertShapeToPath: true}, 'convertShapeToPath',
{sortAttrs: true}, 'sortAttrs',
{removeDimensions: false}, //'removeDimensions',
{removeAttrs: {attrs: "(stroke|fill)"}} {name: 'removeAttrs', params: { attrs: '(stroke|fill)' } }
] ]
}); };
var basepath = "./core/images/", var basepath = "./core/images/",
files = fs.readdirSync(basepath).sort(); files = fs.readdirSync(basepath).sort();
@ -66,12 +66,14 @@ files.forEach(function(filename) {
fakeSVG = body.join("\n"); fakeSVG = body.join("\n");
// A hack to make the new-journal-button work // A hack to make the new-journal-button work
fakeSVG = fakeSVG.replace("<<now \"DD\">>","&lt;&lt;now &quot;DD&quot;&gt;&gt;"); fakeSVG = fakeSVG.replace("<<now \"DD\">>","&lt;&lt;now &quot;DD&quot;&gt;&gt;");
svgo.optimize(fakeSVG, {path: filepath}).then(function(result) { config.path = filepath;
var result = optimize(fakeSVG,config);
if(result) {
var newSVG = header.join("\n") + "\n\n" + result.data.replace("&lt;&lt;now &quot;DD&quot;&gt;&gt;","<<now \"DD\">>"); var newSVG = header.join("\n") + "\n\n" + result.data.replace("&lt;&lt;now &quot;DD&quot;&gt;&gt;","<<now \"DD\">>");
fs.writeFileSync(filepath,newSVG); fs.writeFileSync(filepath,newSVG);
},function(err) { } else {
console.log("Error " + err + " with " + filename) console.log("Error " + err + " with " + filename)
process.exit(); process.exit();
}); };
} }
}); });

View File

@ -2133,6 +2133,7 @@ $tw.loadWikiTiddlers = function(wikiPath,options) {
fileInfo = $tw.boot.files[title]; fileInfo = $tw.boot.files[title];
if(fileInfo.isEditableFile) { if(fileInfo.isEditableFile) {
relativePath = path.relative($tw.boot.wikiTiddlersPath,fileInfo.filepath); relativePath = path.relative($tw.boot.wikiTiddlersPath,fileInfo.filepath);
fileInfo.originalpath = relativePath;
output[title] = output[title] =
path.sep === "/" ? path.sep === "/" ?
relativePath : relativePath :
@ -2466,16 +2467,29 @@ $tw.boot.executeNextStartupTask = function(callback) {
}; };
/* /*
Returns true if we are running on one platforms specified in a task modules `platforms` array Returns true if we are running on one of the platforms specified in taskModule's
`platforms` array; or if `platforms` property is not defined.
*/ */
$tw.boot.doesTaskMatchPlatform = function(taskModule) { $tw.boot.doesTaskMatchPlatform = function(taskModule) {
var platforms = taskModule.platforms; var platforms = taskModule.platforms;
if(platforms) { if(platforms) {
for(var t=0; t<platforms.length; t++) { for(var t=0; t<platforms.length; t++) {
if((platforms[t] === "browser" && !$tw.browser) || (platforms[t] === "node" && !$tw.node)) { switch (platforms[t]) {
return false; case "browser":
if ($tw.browser) {
return true;
}
break;
case "node":
if ($tw.node) {
return true;
}
break;
default:
$tw.utils.error("Module " + taskModule.name + ": '" + platforms[t] + "' in export.platforms invalid");
} }
} }
return false;
} }
return true; return true;
}; };

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,4 @@
title: $:/core/images/minus-button
tags: $:/tags/Image
<svg width="22pt" height="22pt" class="tc-image-minus-button tc-image-button" viewBox="0 0 128 128"><path d="M64 0c35.346 0 64 28.654 64 64 0 35.346-28.654 64-64 64-35.346 0-64-28.654-64-64C0 28.654 28.654 0 64 0zm.332 16c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48z"/><rect width="80" height="16" x="24" y="56" rx="8"/></svg>

View File

@ -0,0 +1,4 @@
title: $:/core/images/plus-button
tags: $:/tags/Image
<svg width="22pt" height="22pt" class="tc-image-plus-button tc-image-button" viewBox="0 0 128 128"><path d="M64-.333c35.346 0 64 28.654 64 64 0 35.346-28.654 64-64 64-35.346 0-64-28.654-64-64 0-35.346 28.654-64 64-64zM64 16c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48z"/><rect width="80" height="16" x="24" y="56" rx="8"/><rect width="16" height="80" x="56" y="24" rx="8"/></svg>

View File

@ -123,12 +123,12 @@ Saving/TiddlySpot/BackupDir: Backup Directory
Saving/TiddlySpot/ControlPanel: ~TiddlySpot Control Panel Saving/TiddlySpot/ControlPanel: ~TiddlySpot Control Panel
Saving/TiddlySpot/Backups: Backups Saving/TiddlySpot/Backups: Backups
Saving/TiddlySpot/Caption: ~TiddlySpot Saver Saving/TiddlySpot/Caption: ~TiddlySpot Saver
Saving/TiddlySpot/Description: These settings are only used when saving to http://tiddlyspot.com or a compatible remote server Saving/TiddlySpot/Description: These settings are only used when saving to [[TiddlySpot|http://tiddlyspot.com]], [[TiddlyHost|https://tiddlyhost.com]], or a compatible remote server. See [[here|https://github.com/simonbaird/tiddlyhost/wiki/TiddlySpot-Saver-configuration-for-Tiddlyhost-and-Tiddlyspot]] for information on ~TiddlySpot and ~TiddlyHost saving configuration.
Saving/TiddlySpot/Filename: Upload Filename Saving/TiddlySpot/Filename: Upload Filename
Saving/TiddlySpot/Heading: ~TiddlySpot Saving/TiddlySpot/Heading: ~TiddlySpot
Saving/TiddlySpot/Hint: //The server URL defaults to `http://<wikiname>.tiddlyspot.com/store.cgi` and can be changed to use a custom server address, e.g. `http://example.com/store.php`.// Saving/TiddlySpot/Hint: //The server URL defaults to `http://<wikiname>.tiddlyspot.com/store.cgi` and can be changed to use a custom server address, e.g. `http://example.com/store.php`.//
Saving/TiddlySpot/Password: Password Saving/TiddlySpot/Password: Password
Saving/TiddlySpot/ReadOnly: The ~TiddlySpot service is currently only available in read-only form. Please see http://tiddlyspot.com/ for the latest details. The ~TiddlySpot saver can still be used to save to compatible servers. Saving/TiddlySpot/ReadOnly: Note that [[TiddlySpot|http://tiddlyspot.com]] no longer allows the creation of new sites. For new sites you can use [[TiddlyHost|https://tiddlyhost.com]], a new hosting service which replaces ~TiddlySpot.
Saving/TiddlySpot/ServerURL: Server URL Saving/TiddlySpot/ServerURL: Server URL
Saving/TiddlySpot/UploadDir: Upload Directory Saving/TiddlySpot/UploadDir: Upload Directory
Saving/TiddlySpot/UserName: Wiki Name Saving/TiddlySpot/UserName: Wiki Name

View File

@ -30,6 +30,7 @@ All parameters are optional with safe defaults, and can be specified in any orde
* ''tls-key'' - pathname of TLS key file (relative to wiki folder) * ''tls-key'' - pathname of TLS key file (relative to wiki folder)
* ''debug-level'' - optional debug level; set to "debug" to view request details (defaults to "none") * ''debug-level'' - optional debug level; set to "debug" to view request details (defaults to "none")
* ''gzip'' - set to "yes" to enable gzip compression for some http endpoints (defaults to "no") * ''gzip'' - set to "yes" to enable gzip compression for some http endpoints (defaults to "no")
* ''use-browser-cache'' - set to "yes" to allow the browser to cache responses to save bandwith (defaults to "no")
For information on opening up your instance to the entire local network, and possible security concerns, see the WebServer tiddler at TiddlyWiki.com. For information on opening up your instance to the entire local network, and possible security concerns, see the WebServer tiddler at TiddlyWiki.com.

View File

@ -1,5 +1,6 @@
title: $:/language/Import/ title: $:/language/Import/
Editor/Import/Heading: Import images and insert into the editor.
Imported/Hint: The following tiddlers were imported: Imported/Hint: The following tiddlers were imported:
Listing/Cancel/Caption: Cancel Listing/Cancel/Caption: Cancel
Listing/Hint: These tiddlers are ready to import: Listing/Hint: These tiddlers are ready to import:

View File

@ -24,7 +24,6 @@ Encryption/RepeatPassword: Repeat password
Encryption/PasswordNoMatch: Passwords do not match Encryption/PasswordNoMatch: Passwords do not match
Encryption/SetPassword: Set password Encryption/SetPassword: Set password
Error/Caption: Error Error/Caption: Error
Error/EditConflict: File changed on server
Error/Filter: Filter error Error/Filter: Filter error
Error/FilterSyntax: Syntax error in filter expression Error/FilterSyntax: Syntax error in filter expression
Error/FilterRunPrefix: Filter Error: Unknown prefix for filter run Error/FilterRunPrefix: Filter Error: Unknown prefix for filter run
@ -32,6 +31,9 @@ Error/IsFilterOperator: Filter Error: Unknown operand for the 'is' filter operat
Error/FormatFilterOperator: Filter Error: Unknown suffix for the 'format' filter operator Error/FormatFilterOperator: Filter Error: Unknown suffix for the 'format' filter operator
Error/LoadingPluginLibrary: Error loading plugin library Error/LoadingPluginLibrary: Error loading plugin library
Error/NetworkErrorAlert: `<h2>''Network Error''</h2>It looks like the connection to the server has been lost. This may indicate a problem with your network connection. Please attempt to restore network connectivity before continuing.<br><br>''Any unsaved changes will be automatically synchronised when connectivity is restored''.` Error/NetworkErrorAlert: `<h2>''Network Error''</h2>It looks like the connection to the server has been lost. This may indicate a problem with your network connection. Please attempt to restore network connectivity before continuing.<br><br>''Any unsaved changes will be automatically synchronised when connectivity is restored''.`
Error/PutEditConflict: File changed on server
Error/PutForbidden: Permission denied
Error/PutUnauthorized: Authentication required
Error/RecursiveTransclusion: Recursive transclusion error in transclude widget Error/RecursiveTransclusion: Recursive transclusion error in transclude widget
Error/RetrievingSkinny: Error retrieving skinny tiddler list Error/RetrievingSkinny: Error retrieving skinny tiddler list
Error/SavingToTWEdit: Error saving to TWEdit Error/SavingToTWEdit: Error saving to TWEdit

View File

@ -167,10 +167,10 @@ WikiFolderMaker.prototype.saveTiddler = function(directory,tiddler) {
} }
var fileInfo = $tw.utils.generateTiddlerFileInfo(tiddler,{ var fileInfo = $tw.utils.generateTiddlerFileInfo(tiddler,{
directory: path.resolve(this.wikiFolderPath,directory), directory: path.resolve(this.wikiFolderPath,directory),
wiki: this.wiki,
pathFilters: pathFilters, pathFilters: pathFilters,
extFilters: extFilters, extFilters: extFilters,
originalpath: this.wiki.extractTiddlerDataItem("$:/config/OriginalTiddlerPaths",title, "") wiki: this.wiki,
fileInfo: {}
}); });
try { try {
$tw.utils.saveTiddlerToFileSync(tiddler,fileInfo); $tw.utils.saveTiddlerToFileSync(tiddler,fileInfo);

View File

@ -34,7 +34,7 @@ exports.htmlEntities = {quot:34, amp:38, apos:39, lt:60, gt:62, nbsp:160, iexcl:
exports.htmlVoidElements = "area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr".split(","); exports.htmlVoidElements = "area,base,br,col,command,embed,hr,img,input,keygen,link,meta,param,source,track,wbr".split(",");
exports.htmlBlockElements = "address,article,aside,audio,blockquote,canvas,dd,div,dl,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,li,noscript,ol,output,p,pre,section,table,tfoot,ul,video".split(","); exports.htmlBlockElements = "address,article,aside,audio,blockquote,canvas,dd,details,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,li,nav,ol,p,pre,section,summary,table,tfoot,ul,video".split(",");
exports.htmlUnsafeElements = "script".split(","); exports.htmlUnsafeElements = "script".split(",");

View File

@ -87,7 +87,20 @@ function FramedEngine(options) {
{name: "input",handlerObject: this,handlerMethod: "handleInputEvent"}, {name: "input",handlerObject: this,handlerMethod: "handleInputEvent"},
{name: "keydown",handlerObject: this.widget,handlerMethod: "handleKeydownEvent"}, {name: "keydown",handlerObject: this.widget,handlerMethod: "handleKeydownEvent"},
{name: "focus",handlerObject: this,handlerMethod: "handleFocusEvent"} {name: "focus",handlerObject: this,handlerMethod: "handleFocusEvent"}
]); ]);
// Add drag and drop event listeners if fileDrop is enabled
if(this.widget.isFileDropEnabled) {
$tw.utils.addEventListeners(this.domNode,[
{name: "dragenter",handlerObject: this.widget,handlerMethod: "handleDragEnterEvent"},
{name: "dragover",handlerObject: this.widget,handlerMethod: "handleDragOverEvent"},
{name: "dragleave",handlerObject: this.widget,handlerMethod: "handleDragLeaveEvent"},
{name: "dragend",handlerObject: this.widget,handlerMethod: "handleDragEndEvent"},
{name: "drop", handlerObject: this.widget,handlerMethod: "handleDropEvent"},
{name: "paste", handlerObject: this.widget,handlerMethod: "handlePasteEvent"},
{name: "click",handlerObject: this.widget,handlerMethod: "handleClickEvent"}
]);
}
// Insert the element into the DOM // Insert the element into the DOM
this.iframeDoc.body.appendChild(this.domNode); this.iframeDoc.body.appendChild(this.domNode);
} }

View File

@ -103,7 +103,11 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
var tiddler = this.wiki.getTiddler(this.editTitle); var tiddler = this.wiki.getTiddler(this.editTitle);
if(tiddler) { if(tiddler) {
// If we've got a tiddler, the value to display is the field string value // If we've got a tiddler, the value to display is the field string value
value = tiddler.getFieldString(this.editField); if(tiddler.hasField(this.editField)) {
value = tiddler.getFieldString(this.editField);
} else {
value = this.editDefault || "";
}
if(this.editField === "text") { if(this.editField === "text") {
type = tiddler.fields.type || "text/vnd.tiddlywiki"; type = tiddler.fields.type || "text/vnd.tiddlywiki";
} }
@ -182,6 +186,7 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
this.editRefreshTitle = this.getAttribute("refreshTitle"); this.editRefreshTitle = this.getAttribute("refreshTitle");
this.editAutoComplete = this.getAttribute("autocomplete"); this.editAutoComplete = this.getAttribute("autocomplete");
this.isDisabled = this.getAttribute("disabled","no"); this.isDisabled = this.getAttribute("disabled","no");
this.isFileDropEnabled = this.getAttribute("fileDrop","no") === "yes";
// Get the default editor element tag and type // Get the default editor element tag and type
var tag,type; var tag,type;
if(this.editField === "text") { if(this.editField === "text") {
@ -213,7 +218,7 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
EditTextWidget.prototype.refresh = function(changedTiddlers) { EditTextWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(); var changedAttributes = this.computeAttributes();
// Completely rerender if any of our attributes have changed // Completely rerender if any of our attributes have changed
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes["default"] || changedAttributes["class"] || changedAttributes.placeholder || changedAttributes.size || changedAttributes.autoHeight || changedAttributes.minHeight || changedAttributes.focusPopup || changedAttributes.rows || changedAttributes.tabindex || changedAttributes.cancelPopups || changedAttributes.inputActions || changedAttributes.refreshTitle || changedAttributes.autocomplete || changedTiddlers[HEIGHT_MODE_TITLE] || changedTiddlers[ENABLE_TOOLBAR_TITLE] || changedAttributes.disabled) { if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes["default"] || changedAttributes["class"] || changedAttributes.placeholder || changedAttributes.size || changedAttributes.autoHeight || changedAttributes.minHeight || changedAttributes.focusPopup || changedAttributes.rows || changedAttributes.tabindex || changedAttributes.cancelPopups || changedAttributes.inputActions || changedAttributes.refreshTitle || changedAttributes.autocomplete || changedTiddlers[HEIGHT_MODE_TITLE] || changedTiddlers[ENABLE_TOOLBAR_TITLE] || changedAttributes.disabled || changedAttributes.fileDrop) {
this.refreshSelf(); this.refreshSelf();
return true; return true;
} else if (changedTiddlers[this.editRefreshTitle]) { } else if (changedTiddlers[this.editRefreshTitle]) {
@ -293,19 +298,88 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
Propogate keydown events to our container for the keyboard widgets benefit Propogate keydown events to our container for the keyboard widgets benefit
*/ */
EditTextWidget.prototype.propogateKeydownEvent = function(event) { EditTextWidget.prototype.propogateKeydownEvent = function(event) {
var newEvent = this.document.createEventObject ? this.document.createEventObject() : this.document.createEvent("Events"); var newEvent = this.cloneEvent(event,["keyCode","which","metaKey","ctrlKey","altKey","shiftKey"]);
if(newEvent.initEvent) {
newEvent.initEvent("keydown", true, true);
}
newEvent.keyCode = event.keyCode;
newEvent.which = event.which;
newEvent.metaKey = event.metaKey;
newEvent.ctrlKey = event.ctrlKey;
newEvent.altKey = event.altKey;
newEvent.shiftKey = event.shiftKey;
return !this.parentDomNode.dispatchEvent(newEvent); return !this.parentDomNode.dispatchEvent(newEvent);
}; };
EditTextWidget.prototype.cloneEvent = function(event,propertiesToCopy) {
var propertiesToCopy = propertiesToCopy || [],
newEvent = this.document.createEventObject ? this.document.createEventObject() : this.document.createEvent("Events");
if(newEvent.initEvent) {
newEvent.initEvent(event.type, true, true);
}
$tw.utils.each(propertiesToCopy,function(prop){
newEvent[prop] = event[prop];
});
return newEvent;
};
EditTextWidget.prototype.dispatchDOMEvent = function(newEvent) {
var dispatchNode = this.engine.iframeNode || this.engine.parentNode;
return dispatchNode.dispatchEvent(newEvent);
};
/*
Propogate drag and drop events with File data to our container for the dropzone widgets benefit.
If there are no Files, let the browser handle it.
*/
EditTextWidget.prototype.handleDropEvent = function(event) {
if(event.dataTransfer.files.length) {
event.preventDefault();
event.stopPropagation();
this.dispatchDOMEvent(this.cloneEvent(event,["dataTransfer"]));
}
};
EditTextWidget.prototype.handlePasteEvent = function(event) {
if(event.clipboardData.files.length) {
event.preventDefault();
event.stopPropagation();
this.dispatchDOMEvent(this.cloneEvent(event,["clipboardData"]));
}
};
EditTextWidget.prototype.handleDragEnterEvent = function(event) {
if($tw.utils.dragEventContainsFiles(event)) {
// Ignore excessive events fired by FF when entering and leaving text nodes in a text area.
if( event.relatedTarget && (event.relatedTarget.nodeType === 3 || event.target === event.relatedTarget)) {
return true;
}
event.preventDefault();
return this.dispatchDOMEvent(this.cloneEvent(event,["dataTransfer"]));
}
return true;
};
EditTextWidget.prototype.handleDragOverEvent = function(event) {
if($tw.utils.dragEventContainsFiles(event)) {
// Call preventDefault() in browsers that default to not allowing drop events on textarea
if($tw.browser.isFirefox || $tw.browser.isIE) {
event.preventDefault();
}
event.dataTransfer.dropEffect = "copy";
return this.dispatchDOMEvent(this.cloneEvent(event,["dataTransfer"]));
}
return true;
};
EditTextWidget.prototype.handleDragLeaveEvent = function(event) {
// Ignore excessive events fired by FF when entering and leaving text nodes in a text area.
if(event.relatedTarget && ((event.relatedTarget.nodeType === 3) || (event.target === event.relatedTarget))) {
return true;
}
event.preventDefault();
this.dispatchDOMEvent(this.cloneEvent(event,["dataTransfer"]));
};
EditTextWidget.prototype.handleDragEndEvent = function(event) {
this.dispatchDOMEvent(this.cloneEvent(event));
};
EditTextWidget.prototype.handleClickEvent = function(event) {
return !this.dispatchDOMEvent(this.cloneEvent(event));
};
return EditTextWidget; return EditTextWidget;
} }

View File

@ -0,0 +1,23 @@
/*\
title: $:/core/modules/editor/operations/text/insert-text.js
type: application/javascript
module-type: texteditoroperation
Text editor operation insert text at the caret position. If there is a selection it is replaced.
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports["insert-text"] = function(event,operation) {
operation.replacement = event.paramObject.text;
operation.cutStart = operation.selStart;
operation.cutEnd = operation.selEnd;
operation.newSelStart = operation.selStart + operation.replacement.length;
operation.newSelEnd = operation.newSelStart;
};
})();

View File

@ -17,10 +17,21 @@ exports.filter = function(operationSubFunction,options) {
return function(results,source,widget) { return function(results,source,widget) {
if(results.length > 0) { if(results.length > 0) {
var resultsToRemove = []; var resultsToRemove = [];
results.each(function(result) { results.each(function(title) {
var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([result]),widget); var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
getVariable: function(name) {
switch(name) {
case "currentTiddler":
return "" + title;
case "..currentTiddler":
return widget.getVariable("currentTiddler");
default:
return widget.getVariable(name);
}
}
});
if(filtered.length === 0) { if(filtered.length === 0) {
resultsToRemove.push(result); resultsToRemove.push(title);
} }
}); });
results.remove(resultsToRemove); results.remove(resultsToRemove);

View File

@ -19,23 +19,25 @@ exports.reduce = function(operationSubFunction,options) {
var index = 0; var index = 0;
results.each(function(title) { results.each(function(title) {
var list = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{ var list = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
getVariable: function(name) { getVariable: function(name) {
switch(name) { switch(name) {
case "currentTiddler": case "currentTiddler":
return "" + title; return "" + title;
case "accumulator": case "..currentTiddler":
return "" + accumulator; return widget.getVariable("currentTiddler");
case "index": case "accumulator":
return "" + index; return "" + accumulator;
case "revIndex": case "index":
return "" + (results.length - 1 - index); return "" + index;
case "length": case "revIndex":
return "" + results.length; return "" + (results.length - 1 - index);
default: case "length":
return widget.getVariable(name); return "" + results.length;
} default:
return widget.getVariable(name);
} }
}); }
});
if(list.length > 0) { if(list.length > 0) {
accumulator = "" + list[0]; accumulator = "" + list[0];
} }

View File

@ -0,0 +1,60 @@
/*\
title: $:/core/modules/filterrunprefixes/sort.js
type: application/javascript
module-type: filterrunprefix
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter prefix function
*/
exports.sort = function(operationSubFunction,options) {
return function(results,source,widget) {
if(results.length > 0) {
var suffixes = options.suffixes,
sortType = (suffixes[0] && suffixes[0][0]) ? suffixes[0][0] : "string",
invert = suffixes[1] ? (suffixes[1].indexOf("reverse") !== -1) : false,
isCaseSensitive = suffixes[1] ? (suffixes[1].indexOf("casesensitive") !== -1) : false,
inputTitles = results.toArray(),
sortKeys = [],
indexes = new Array(inputTitles.length),
compareFn;
results.each(function(title) {
var key = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
getVariable: function(name) {
switch(name) {
case "currentTiddler":
return "" + title;
case "..currentTiddler":
return widget.getVariable("currentTiddler");
default:
return widget.getVariable(name);
}
}
});
sortKeys.push(key[0] || "");
});
results.clear();
// Prepare an array of indexes to sort
for(var t=0; t<inputTitles.length; t++) {
indexes[t] = t;
}
// Sort the indexes
compareFn = $tw.utils.makeCompareFunction(sortType,{defaultType: "string", invert:invert, isCaseSensitive:isCaseSensitive});
indexes = indexes.sort(function(a,b) {
return compareFn(sortKeys[a],sortKeys[b]);
});
// Add to results in correct order
$tw.utils.each(indexes,function(index) {
results.push(inputTitles[index]);
});
}
}
};
})();

View File

@ -137,7 +137,7 @@ exports.parseFilter = function(filterString) {
p = 0, // Current position in the filter string p = 0, // Current position in the filter string
match; match;
var whitespaceRegExp = /(\s+)/mg, var whitespaceRegExp = /(\s+)/mg,
operandRegExp = /((?:\+|\-|~|=|\:(\w+))?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg; operandRegExp = /((?:\+|\-|~|=|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg;
while(p < filterString.length) { while(p < filterString.length) {
// Skip any whitespace // Skip any whitespace
whitespaceRegExp.lastIndex = p; whitespaceRegExp.lastIndex = p;
@ -162,15 +162,27 @@ exports.parseFilter = function(filterString) {
if(match[2]) { if(match[2]) {
operation.namedPrefix = match[2]; operation.namedPrefix = match[2];
} }
if(match[3]) {
operation.suffixes = [];
$tw.utils.each(match[3].split(":"),function(subsuffix) {
operation.suffixes.push([]);
$tw.utils.each(subsuffix.split(","),function(entry) {
entry = $tw.utils.trim(entry);
if(entry) {
operation.suffixes[operation.suffixes.length -1].push(entry);
}
});
});
}
} }
if(match[3]) { // Opening square bracket if(match[4]) { // Opening square bracket
p = parseFilterOperation(operation.operators,filterString,p); p = parseFilterOperation(operation.operators,filterString,p);
} else { } else {
p = match.index + match[0].length; p = match.index + match[0].length;
} }
if(match[4] || match[5] || match[6]) { // Double quoted string, single quoted string or unquoted title if(match[5] || match[6] || match[7]) { // Double quoted string, single quoted string or unquoted title
operation.operators.push( operation.operators.push(
{operator: "title", operands: [{text: match[4] || match[5] || match[6]}]} {operator: "title", operands: [{text: match[5] || match[6] || match[7]}]}
); );
} }
results.push(operation); results.push(operation);
@ -280,7 +292,7 @@ exports.compileFilter = function(filterString) {
var filterRunPrefixes = self.getFilterRunPrefixes(); var filterRunPrefixes = self.getFilterRunPrefixes();
// Wrap the operator functions in a wrapper function that depends on the prefix // Wrap the operator functions in a wrapper function that depends on the prefix
operationFunctions.push((function() { operationFunctions.push((function() {
var options = {wiki: self}; var options = {wiki: self, suffixes: operation.suffixes || []};
switch(operation.prefix || "") { switch(operation.prefix || "") {
case "": // No prefix means that the operation is unioned into the result case "": // No prefix means that the operation is unioned into the result
return filterRunPrefixes["or"](operationSubFunction, options); return filterRunPrefixes["or"](operationSubFunction, options);
@ -311,6 +323,9 @@ exports.compileFilter = function(filterString) {
} else if(typeof source === "object") { // Array or hashmap } else if(typeof source === "object") { // Array or hashmap
source = self.makeTiddlerIterator(source); source = self.makeTiddlerIterator(source);
} }
if(!widget) {
widget = $tw.rootWidget;
}
var results = new $tw.utils.LinkedList(); var results = new $tw.utils.LinkedList();
$tw.utils.each(operationFunctions,function(operationFunction) { $tw.utils.each(operationFunctions,function(operationFunction) {
operationFunction(results,source,widget); operationFunction(results,source,widget);

View File

@ -0,0 +1,27 @@
/*\
title: $:/core/modules/filters/deserializers.js
type: application/javascript
module-type: filteroperator
Filter operator for returning the names of the deserializers in this wiki
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
/*
Export our filter function
*/
exports.deserializers = function(source,operator,options) {
var results = [];
$tw.utils.each($tw.Wiki.tiddlerDeserializerModules,function(deserializer,type) {
results.push(type);
});
results.sort();
return results;
};
})();

View File

@ -102,7 +102,7 @@ exports.escapecss = function(source,operator,options) {
var results = []; var results = [];
source(function(tiddler,title) { source(function(tiddler,title) {
// escape any character with a special meaning in CSS using CSS.escape() // escape any character with a special meaning in CSS using CSS.escape()
results.push(CSS.escape(title)); results.push($tw.utils.escapeCSS(title));
}); });
return results; return results;
}; };

View File

@ -20,7 +20,18 @@ exports.filter = function(source,operator,options) {
results = [], results = [],
target = operator.prefix !== "!"; target = operator.prefix !== "!";
source(function(tiddler,title) { source(function(tiddler,title) {
var list = filterFn.call(options.wiki,options.wiki.makeTiddlerIterator([title])); var list = filterFn.call(options.wiki,options.wiki.makeTiddlerIterator([title]),{
getVariable: function(name) {
switch(name) {
case "currentTiddler":
return "" + title;
case "..currentTiddler":
return options.widget.getVariable("currentTiddler");
default:
return options.widget.getVariable(name);
}
}
});
if((list.length > 0) === target) { if((list.length > 0) === target) {
results.push(title); results.push(title);
} }

View File

@ -5,9 +5,11 @@ module-type: filteroperator
Filter operator that looks up values via a title prefix Filter operator that looks up values via a title prefix
[lookup:<field>[<prefix>]] [lookup:<defaultvalue>:<field OR index>[<prefix>],[<field-name OR index-name>]]
Prepends the prefix to the selected items and returns the specified field value Prepends the prefix to the selected items and returns the specified
field or index value. If the 2nd suffix does not exist, it defaults to field.
If the second operand is missing it defaults to "text" for fields, and "0" for indexes
\*/ \*/
(function(){ (function(){
@ -20,10 +22,35 @@ Prepends the prefix to the selected items and returns the specified field value
Export our filter function Export our filter function
*/ */
exports.lookup = function(source,operator,options) { exports.lookup = function(source,operator,options) {
var results = []; var results = [],
source(function(tiddler,title) { suffixes = operator.suffixes || [],
results.push(options.wiki.getTiddlerText(operator.operand + title) || operator.suffix || ''); defaultSuffix = suffixes[0] ? (suffixes[0][0] || "") : "",
}); indexSuffix = (suffixes[1] && suffixes[1][0] === "index") ? true : false,
target;
if(operator.operands.length == 2) {
target = operator.operands[1]
} else {
target = indexSuffix ? "0": "text";
}
if(indexSuffix) {
source(function(tiddler,title) {
var targetTitle = operator.operands[0] + title;
var data = options.wiki.extractTiddlerDataItem(targetTitle,target,defaultSuffix);
results.push(data);
});
} else {
source(function(tiddler,title) {
var targetTitle = operator.operands[0] + title;
var targetTiddler = options.wiki.getTiddler(targetTitle);
if(targetTiddler) {
var value = targetTiddler.getFieldString(target);
if(value == "" && defaultSuffix !== "") {
value = defaultSuffix;
}
results.push(value);
}
});
}
return results; return results;
}; };

View File

@ -125,6 +125,54 @@ exports.minall = makeNumericReducingOperator(
Infinity // Initial value Infinity // Initial value
); );
exports.median = makeNumericArrayOperator(
function(values) {
var len = values.length, median;
values.sort();
if(len % 2) {
// Odd, return the middle number
median = values[(len - 1) / 2];
} else {
// Even, return average of two middle numbers
median = (values[len / 2 - 1] + values[len / 2]) / 2;
}
return [median];
}
);
exports.average = makeNumericReducingOperator(
function(accumulator,value) {return accumulator + value},
0, // Initial value
function(finalValue,numberOfValues) {
return finalValue/numberOfValues;
}
);
exports.variance = makeNumericReducingOperator(
function(accumulator,value) {return accumulator + value},
0,
function(finalValue,numberOfValues,originalValues) {
return getVarianceFromArray(originalValues,finalValue/numberOfValues);
}
);
exports["standard-deviation"] = makeNumericReducingOperator(
function(accumulator,value) {return accumulator + value},
0,
function(finalValue,numberOfValues,originalValues) {
var variance = getVarianceFromArray(originalValues,finalValue/numberOfValues);
return Math.sqrt(variance);
}
);
//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) {
return accumulator + Math.pow(value - mean, 2);
},0);
return deviationTotal/values.length;
};
function makeNumericBinaryOperator(fnCalc) { function makeNumericBinaryOperator(fnCalc) {
return function(source,operator,options) { return function(source,operator,options) {
var result = [], var result = [],
@ -134,19 +182,37 @@ function makeNumericBinaryOperator(fnCalc) {
}); });
return result; return result;
}; };
} };
function makeNumericReducingOperator(fnCalc,initialValue) { function makeNumericReducingOperator(fnCalc,initialValue,fnFinal) {
initialValue = initialValue || 0; initialValue = initialValue || 0;
return function(source,operator,options) { return function(source,operator,options) {
var result = []; var result = [];
source(function(tiddler,title) { source(function(tiddler,title) {
result.push(title); result.push($tw.utils.parseNumber(title));
}); });
return [$tw.utils.stringifyNumber(result.reduce(function(accumulator,currentValue) { var value = result.reduce(function(accumulator,currentValue) {
return fnCalc(accumulator,$tw.utils.parseNumber(currentValue)); return fnCalc(accumulator,currentValue);
},initialValue))]; },initialValue);
if(fnFinal) {
value = fnFinal(value,result.length,result);
}
return [$tw.utils.stringifyNumber(value)];
}; };
} };
function makeNumericArrayOperator(fnCalc) {
return function(source,operator,options) {
var results = [];
source(function(tiddler,title) {
results.push($tw.utils.parseNumber(title));
});
results = fnCalc(results);
$tw.utils.each(results,function(value,index) {
results[index] = $tw.utils.stringifyNumber(value);
});
return results;
};
};
})(); })();

View File

@ -31,6 +31,8 @@ exports.reduce = function(source,operator,options) {
switch(name) { switch(name) {
case "currentTiddler": case "currentTiddler":
return "" + title; return "" + title;
case "..currentTiddler":
return options.widget.getVariable("currentTiddler");
case "accumulator": case "accumulator":
return "" + accumulator; return "" + accumulator;
case "index": case "index":

View File

@ -27,10 +27,13 @@ exports.sortsub = function(source,operator,options) {
iterator(options.wiki.getTiddler(title),title); iterator(options.wiki.getTiddler(title),title);
},{ },{
getVariable: function(name) { getVariable: function(name) {
if(name === "currentTiddler") { switch(name) {
return title; case "currentTiddler":
} else { return "" + title;
return options.widget.getVariable(name); case "..currentTiddler":
return options.widget.getVariable("currentTiddler");
default:
return options.widget.getVariable(name);
} }
} }
}); });

View File

@ -2,6 +2,7 @@
title: $:/core/modules/macros/unusedtitle.js title: $:/core/modules/macros/unusedtitle.js
type: application/javascript type: application/javascript
module-type: macro module-type: macro
Macro to return a new title that is unused in the wiki. It can be given a name as a base. Macro to return a new title that is unused in the wiki. It can be given a name as a base.
\*/ \*/
(function(){ (function(){
@ -10,25 +11,25 @@ Macro to return a new title that is unused in the wiki. It can be given a name a
/*global $tw: false */ /*global $tw: false */
"use strict"; "use strict";
/*
Information about this macro
*/
exports.name = "unusedtitle"; exports.name = "unusedtitle";
exports.params = [ exports.params = [
{name: "baseName"}, {name: "baseName"},
{name: "options"} {name: "separator"},
{name: "template"}
]; ];
/* /*
Run the macro Run the macro
*/ */
exports.run = function(baseName, options) { exports.run = function(baseName,separator,template) {
separator = separator || " ";
if(!baseName) { if(!baseName) {
baseName = $tw.language.getString("DefaultNewTiddlerTitle"); baseName = $tw.language.getString("DefaultNewTiddlerTitle");
} }
return this.wiki.generateNewTitle(baseName, options); // $tw.wiki.generateNewTitle = function(baseTitle,options)
// options.prefix must be a string!
return this.wiki.generateNewTitle(baseName, {"prefix": separator, "template": template});
}; };
})(); })();

View File

@ -23,10 +23,12 @@ var HtmlParser = function(type,text,options) {
type: "element", type: "element",
tag: "iframe", tag: "iframe",
attributes: { attributes: {
src: {type: "string", value: src}, src: {type: "string", value: src}
sandbox: {type: "string", value: ""}
} }
}]; }];
if($tw.wiki.getTiddlerText("$:/config/HtmlParser/DisableSandbox","no") !== "yes") {
this.tree[0].attributes.sandbox = {type: "string", value: $tw.wiki.getTiddlerText("$:/config/HtmlParser/SandboxTokens","")};
}
}; };
exports["text/html"] = HtmlParser; exports["text/html"] = HtmlParser;

View File

@ -36,7 +36,7 @@ exports.parse = function() {
// Move past the pragma invocation // Move past the pragma invocation
this.parser.pos = this.matchRegExp.lastIndex; this.parser.pos = this.matchRegExp.lastIndex;
// Parse the filter terminated by a line break // Parse the filter terminated by a line break
var reMatch = /(.*)(\r?\n)|$/mg; var reMatch = /(.*)(?:$|\r?\n)/mg;
reMatch.lastIndex = this.parser.pos; reMatch.lastIndex = this.parser.pos;
var match = reMatch.exec(this.parser.source); var match = reMatch.exec(this.parser.source);
this.parser.pos = reMatch.lastIndex; this.parser.pos = reMatch.lastIndex;

View File

@ -89,9 +89,12 @@ PutSaver.prototype.save = function(text,method,callback) {
if(err) { if(err) {
// response is textual: "XMLHttpRequest error code: 412" // response is textual: "XMLHttpRequest error code: 412"
var status = Number(err.substring(err.indexOf(':') + 2, err.length)) var status = Number(err.substring(err.indexOf(':') + 2, err.length))
if(status === 412) { // edit conflict if(status === 412) { // file changed on server
var message = $tw.language.getString("Error/EditConflict"); callback($tw.language.getString("Error/PutEditConflict"));
callback(message); } else if(status === 401) { // authentication required
callback($tw.language.getString("Error/PutUnauthorized"));
} else if(status === 403) { // permission denied
callback($tw.language.getString("Error/PutForbidden"));
} else { } else {
callback(err); // fail callback(err); // fail
} }

View File

@ -17,9 +17,8 @@ exports.method = "GET";
exports.path = /^\/favicon.ico$/; exports.path = /^\/favicon.ico$/;
exports.handler = function(request,response,state) { exports.handler = function(request,response,state) {
response.writeHead(200, {"Content-Type": "image/x-icon"});
var buffer = state.wiki.getTiddlerText("$:/favicon.ico",""); var buffer = state.wiki.getTiddlerText("$:/favicon.ico","");
response.end(buffer,"base64"); state.sendResponse(200,{"Content-Type": "image/x-icon"},buffer,"base64");
}; };
}()); }());

View File

@ -34,10 +34,7 @@ exports.handler = function(request,response,state) {
content = content; content = content;
type = ($tw.config.fileExtensionInfo[extension] ? $tw.config.fileExtensionInfo[extension].type : "application/octet-stream"); type = ($tw.config.fileExtensionInfo[extension] ? $tw.config.fileExtensionInfo[extension].type : "application/octet-stream");
} }
response.writeHead(status,{ state.sendResponse(status,{"Content-Type": type},content);
"Content-Type": type
});
response.end(content);
}); });
}; };

View File

@ -12,38 +12,16 @@ GET /
/*global $tw: false */ /*global $tw: false */
"use strict"; "use strict";
var zlib = require("zlib");
exports.method = "GET"; exports.method = "GET";
exports.path = /^\/$/; exports.path = /^\/$/;
exports.handler = function(request,response,state) { exports.handler = function(request,response,state) {
var acceptEncoding = request.headers["accept-encoding"];
if(!acceptEncoding) {
acceptEncoding = "";
}
var text = state.wiki.renderTiddler(state.server.get("root-render-type"),state.server.get("root-tiddler")), var text = state.wiki.renderTiddler(state.server.get("root-render-type"),state.server.get("root-tiddler")),
responseHeaders = { responseHeaders = {
"Content-Type": state.server.get("root-serve-type") "Content-Type": state.server.get("root-serve-type")
}; };
/* state.sendResponse(200,responseHeaders,text);
If the gzip=yes flag for `listen` is set, check if the user agent permits
compression. If so, compress our response. Note that we use the synchronous
functions from zlib to stay in the imperative style. The current `Server`
doesn't depend on this, and we may just as well use the async versions.
*/
if(state.server.enableGzip) {
if (/\bdeflate\b/.test(acceptEncoding)) {
responseHeaders["Content-Encoding"] = "deflate";
text = zlib.deflateSync(text);
} else if (/\bgzip\b/.test(acceptEncoding)) {
responseHeaders["Content-Encoding"] = "gzip";
text = zlib.gzipSync(text);
}
}
response.writeHead(200,responseHeaders);
response.end(text);
}; };
}()); }());

View File

@ -25,8 +25,9 @@ exports.handler = function(request,response,state) {
response.end(); response.end();
} else { } else {
// Redirect to the root wiki if login worked // Redirect to the root wiki if login worked
var location = ($tw.syncadaptor && $tw.syncadaptor.host)? $tw.syncadaptor.host: "/";
response.writeHead(302,{ response.writeHead(302,{
Location: "/" Location: location
}); });
response.end(); response.end();
} }

View File

@ -17,7 +17,6 @@ exports.method = "GET";
exports.path = /^\/status$/; exports.path = /^\/status$/;
exports.handler = function(request,response,state) { exports.handler = function(request,response,state) {
response.writeHead(200, {"Content-Type": "application/json"});
var text = JSON.stringify({ var text = JSON.stringify({
username: state.authenticatedUsername || state.server.get("anon-username") || "", username: state.authenticatedUsername || state.server.get("anon-username") || "",
anonymous: !state.authenticatedUsername, anonymous: !state.authenticatedUsername,
@ -28,7 +27,7 @@ exports.handler = function(request,response,state) {
}, },
tiddlywiki_version: $tw.version tiddlywiki_version: $tw.version
}); });
response.end(text,"utf8"); state.sendResponse(200,{"Content-Type": "application/json"},text,"utf8");
}; };
}()); }());

View File

@ -32,9 +32,9 @@ exports.handler = function(request,response,state) {
renderTemplate = renderTemplate || state.server.get("tiddler-render-template"); renderTemplate = renderTemplate || state.server.get("tiddler-render-template");
} }
var text = state.wiki.renderTiddler(renderType,renderTemplate,{parseAsInline: true, variables: {currentTiddler: title}}); var text = state.wiki.renderTiddler(renderType,renderTemplate,{parseAsInline: true, variables: {currentTiddler: title}});
// Naughty not to set a content-type, but it's the easiest way to ensure the browser will see HTML pages as HTML, and accept plain text tiddlers as CSS or JS // Naughty not to set a content-type, but it's the easiest way to ensure the browser will see HTML pages as HTML, and accept plain text tiddlers as CSS or JS
response.writeHead(200); state.sendResponse(200,{},text,"utf8");
response.end(text,"utf8");
} else { } else {
response.writeHead(404); response.writeHead(404);
response.end(); response.end();

View File

@ -36,8 +36,7 @@ exports.handler = function(request,response,state) {
tiddlerFields.revision = state.wiki.getChangeCount(title); tiddlerFields.revision = state.wiki.getChangeCount(title);
tiddlerFields.bag = "default"; tiddlerFields.bag = "default";
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki"; tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
response.writeHead(200, {"Content-Type": "application/json"}); state.sendResponse(200,{"Content-Type": "application/json"},JSON.stringify(tiddlerFields),"utf8");
response.end(JSON.stringify(tiddlerFields),"utf8");
} else { } else {
response.writeHead(404); response.writeHead(404);
response.end(); response.end();

View File

@ -33,7 +33,6 @@ exports.handler = function(request,response,state) {
} }
var excludeFields = (state.queryParameters.exclude || "text").split(","), var excludeFields = (state.queryParameters.exclude || "text").split(","),
titles = state.wiki.filterTiddlers(filter); titles = state.wiki.filterTiddlers(filter);
response.writeHead(200, {"Content-Type": "application/json"});
var tiddlers = []; var tiddlers = [];
$tw.utils.each(titles,function(title) { $tw.utils.each(titles,function(title) {
var tiddler = state.wiki.getTiddler(title); var tiddler = state.wiki.getTiddler(title);
@ -45,7 +44,7 @@ exports.handler = function(request,response,state) {
} }
}); });
var text = JSON.stringify(tiddlers); var text = JSON.stringify(tiddlers);
response.end(text,"utf8"); state.sendResponse(200,{"Content-Type": "application/json"},text,"utf8");
}; };
}()); }());

View File

@ -17,7 +17,9 @@ if($tw.node) {
fs = require("fs"), fs = require("fs"),
url = require("url"), url = require("url"),
path = require("path"), path = require("path"),
querystring = require("querystring"); querystring = require("querystring"),
crypto = require("crypto"),
zlib = require("zlib");
} }
/* /*
@ -47,6 +49,8 @@ function Server(options) {
this.csrfDisable = this.get("csrf-disable") === "yes"; this.csrfDisable = this.get("csrf-disable") === "yes";
// Initialize Gzip compression // Initialize Gzip compression
this.enableGzip = this.get("gzip") === "yes"; this.enableGzip = this.get("gzip") === "yes";
// Initialize browser-caching
this.enableBrowserCache = this.get("use-browser-cache") === "yes";
// Initialise authorization // Initialise authorization
var authorizedUserName = (this.get("username") && this.get("password")) ? this.get("username") : "(anon)"; var authorizedUserName = (this.get("username") && this.get("password")) ? this.get("username") : "(anon)";
this.authorizationPrincipals = { this.authorizationPrincipals = {
@ -78,6 +82,71 @@ function Server(options) {
this.transport = require(this.protocol); this.transport = require(this.protocol);
} }
/*
Send a response to the client. This method checks if the response must be sent
or if the client alrady has the data cached. If that's the case only a 304
response will be transmitted and the browser will use the cached data.
Only requests with status code 200 are considdered for caching.
request: request instance passed to the handler
response: response instance passed to the handler
statusCode: stauts code to send to the browser
headers: response headers (they will be augmented with an `Etag` header)
data: the data to send (passed to the end method of the response instance)
encoding: the encoding of the data to send (passed to the end method of the response instance)
*/
function sendResponse(request,response,statusCode,headers,data,encoding) {
if(this.enableBrowserCache && (statusCode == 200)) {
var hash = crypto.createHash('md5');
// Put everything into the hash that could change and invalidate the data that
// the browser already stored. The headers the data and the encoding.
hash.update(data);
hash.update(JSON.stringify(headers));
if(encoding) {
hash.update(encoding);
}
var contentDigest = hash.digest("hex");
// RFC 7232 section 2.3 mandates for the etag to be enclosed in quotes
headers["Etag"] = '"' + contentDigest + '"';
headers["Cache-Control"] = "max-age=0, must-revalidate";
// Check if any of the hashes contained within the if-none-match header
// matches the current hash.
// If one matches, do not send the data but tell the browser to use the
// cached data.
// We do not implement "*" as it makes no sense here.
var ifNoneMatch = request.headers["if-none-match"];
if(ifNoneMatch) {
var matchParts = ifNoneMatch.split(",").map(function(etag) {
return etag.replace(/^[ "]+|[ "]+$/g, "");
});
if(matchParts.indexOf(contentDigest) != -1) {
response.writeHead(304,headers);
response.end();
return;
}
}
}
/*
If the gzip=yes is set, check if the user agent permits compression. If so,
compress our response if the raw data is bigger than 2k. Compressing less
data is inefficient. Note that we use the synchronous functions from zlib
to stay in the imperative style. The current `Server` doesn't depend on
this, and we may just as well use the async versions.
*/
if(this.enableGzip && (data.length > 2048)) {
var acceptEncoding = request.headers["accept-encoding"] || "";
if(/\bdeflate\b/.test(acceptEncoding)) {
headers["Content-Encoding"] = "deflate";
data = zlib.deflateSync(data);
} else if(/\bgzip\b/.test(acceptEncoding)) {
headers["Content-Encoding"] = "gzip";
data = zlib.gzipSync(data);
}
}
response.writeHead(statusCode,headers);
response.end(data,encoding);
}
Server.prototype.defaultVariables = { Server.prototype.defaultVariables = {
port: "8080", port: "8080",
host: "127.0.0.1", host: "127.0.0.1",
@ -89,7 +158,8 @@ Server.prototype.defaultVariables = {
"system-tiddler-render-type": "text/plain", "system-tiddler-render-type": "text/plain",
"system-tiddler-render-template": "$:/core/templates/wikified-tiddler", "system-tiddler-render-template": "$:/core/templates/wikified-tiddler",
"debug-level": "none", "debug-level": "none",
"gzip": "no" "gzip": "no",
"use-browser-cache": "no"
}; };
Server.prototype.get = function(name) { Server.prototype.get = function(name) {
@ -167,6 +237,7 @@ Server.prototype.requestHandler = function(request,response,options) {
state.urlInfo = url.parse(request.url); state.urlInfo = url.parse(request.url);
state.queryParameters = querystring.parse(state.urlInfo.query); state.queryParameters = querystring.parse(state.urlInfo.query);
state.pathPrefix = options.pathPrefix || this.get("path-prefix") || ""; state.pathPrefix = options.pathPrefix || this.get("path-prefix") || "";
state.sendResponse = sendResponse.bind(self,request,response);
// Get the principals authorized to access this resource // Get the principals authorized to access this resource
var authorizationType = this.methodMappings[request.method] || "readers"; var authorizationType = this.methodMappings[request.method] || "readers";
// Check for the CSRF header if this is a write // Check for the CSRF header if this is a write

View File

@ -82,7 +82,7 @@ exports.startup = function() {
var onlyThrottledTiddlersHaveChanged = true; var onlyThrottledTiddlersHaveChanged = true;
for(var title in changes) { for(var title in changes) {
var tiddler = $tw.wiki.getTiddler(title); var tiddler = $tw.wiki.getTiddler(title);
if(!tiddler || !(tiddler.hasField("draft.of") || tiddler.hasField("throttle.refresh"))) { if(!$tw.wiki.isVolatileTiddler(title) && (!tiddler || !(tiddler.hasField("draft.of") || tiddler.hasField("throttle.refresh")))) {
onlyThrottledTiddlersHaveChanged = false; onlyThrottledTiddlersHaveChanged = false;
} }
} }

View File

@ -22,6 +22,10 @@ exports.domContains = function(a,b) {
!!(a.compareDocumentPosition(b) & 16); !!(a.compareDocumentPosition(b) & 16);
}; };
exports.domMatchesSelector = function(node,selector) {
return node.matches ? node.matches(selector) : node.msMatchesSelector(selector);
};
exports.removeChildren = function(node) { exports.removeChildren = function(node) {
while(node.hasChildNodes()) { while(node.hasChildNodes()) {
node.removeChild(node.firstChild); node.removeChild(node.firstChild);

View File

@ -205,4 +205,16 @@ function parseJSONTiddlers(json,fallbackTitle) {
return data; return data;
}; };
exports.dragEventContainsFiles = function(event) {
if(event.dataTransfer.types) {
for(var i=0; i<event.dataTransfer.types.length; i++) {
if(event.dataTransfer.types[i] === "Files") {
return true;
break;
}
}
}
return false;
};
})(); })();

View File

@ -1,33 +1,28 @@
/*\ /*\
title: $:/core/modules/startup/CSSescape.js title: $:/core/modules/utils/escapecss.js
type: application/javascript type: application/javascript
module-type: startup module-type: utils
Polyfill for CSS.escape() Provides CSS.escape() functionality.
\*/ \*/
(function(root,factory){ (function(){
/*jslint node: true, browser: true */ /*jslint node: true, browser: true */
/*global $tw: false */ /*global $tw: false, window: false */
"use strict"; "use strict";
// Export name and synchronous status exports.escapeCSS = (function() {
exports.name = "css-escape"; // use browser's native CSS.escape() function if available
exports.platforms = ["browser"]; if ($tw.browser && window.CSS && window.CSS.escape) {
exports.after = ["startup"]; return window.CSS.escape;
exports.synchronous = true;
/*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */
// https://github.com/umdjs/umd/blob/master/returnExports.js
exports.startup = function() {factory(root);};
}(typeof global != 'undefined' ? global : this, function(root) {
if (root.CSS && root.CSS.escape) {
return;
} }
// https://drafts.csswg.org/cssom/#serialize-an-identifier // otherwise, a utility method is provided
var cssEscape = function(value) { // see also https://drafts.csswg.org/cssom/#serialize-an-identifier
/*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */
return function(value) {
if (arguments.length == 0) { if (arguments.length == 0) {
throw new TypeError('`CSS.escape` requires an argument.'); throw new TypeError('`CSS.escape` requires an argument.');
} }
@ -104,11 +99,6 @@ exports.startup = function() {factory(root);};
} }
return result; return result;
}; };
})();
if (!root.CSS) { })();
root.CSS = {};
}
Object.getPrototypeOf(root.CSS).escape = cssEscape;
}));

View File

@ -213,13 +213,13 @@ Options include:
extFilters: optional array of filters to be used to generate the base path extFilters: optional array of filters to be used to generate the base path
wiki: optional wiki for evaluating the pathFilters, wiki: optional wiki for evaluating the pathFilters,
fileInfo: an existing fileInfo to check against fileInfo: an existing fileInfo to check against
originalpath: a preferred filepath if no pathFilters match
*/ */
exports.generateTiddlerFileInfo = function(tiddler,options) { exports.generateTiddlerFileInfo = function(tiddler,options) {
var fileInfo = {}, metaExt; var fileInfo = {}, metaExt;
// Propagate the isEditableFile flag // Propagate the isEditableFile flag
if(options.fileInfo) { if(options.fileInfo && !!options.fileInfo.isEditableFile) {
fileInfo.isEditableFile = options.fileInfo.isEditableFile || false; fileInfo.isEditableFile = true;
fileInfo.originalpath = options.fileInfo.originalpath;
} }
// Check if the tiddler has any unsafe fields that can't be expressed in a .tid or .meta file: containing control characters, or leading/trailing whitespace // Check if the tiddler has any unsafe fields that can't be expressed in a .tid or .meta file: containing control characters, or leading/trailing whitespace
var hasUnsafeFields = false; var hasUnsafeFields = false;
@ -247,7 +247,7 @@ exports.generateTiddlerFileInfo = function(tiddler,options) {
fileInfo.hasMetaFile = true; fileInfo.hasMetaFile = true;
} }
if(options.extFilters) { if(options.extFilters) {
// Check for extension override // Check for extension overrides
metaExt = $tw.utils.generateTiddlerExtension(tiddler.fields.title,{ metaExt = $tw.utils.generateTiddlerExtension(tiddler.fields.title,{
extFilters: options.extFilters, extFilters: options.extFilters,
wiki: options.wiki wiki: options.wiki
@ -279,8 +279,7 @@ exports.generateTiddlerFileInfo = function(tiddler,options) {
directory: options.directory, directory: options.directory,
pathFilters: options.pathFilters, pathFilters: options.pathFilters,
wiki: options.wiki, wiki: options.wiki,
fileInfo: options.fileInfo, fileInfo: options.fileInfo
originalpath: options.originalpath
}); });
return fileInfo; return fileInfo;
}; };
@ -292,8 +291,7 @@ Options include:
wiki: optional wiki for evaluating the extFilters wiki: optional wiki for evaluating the extFilters
*/ */
exports.generateTiddlerExtension = function(title,options) { exports.generateTiddlerExtension = function(title,options) {
var self = this, var extension;
extension;
// Check if any of the extFilters applies // Check if any of the extFilters applies
if(options.extFilters && options.wiki) { if(options.extFilters && options.wiki) {
$tw.utils.each(options.extFilters,function(filter) { $tw.utils.each(options.extFilters,function(filter) {
@ -319,11 +317,10 @@ Options include:
fileInfo: an existing fileInfo object to check against fileInfo: an existing fileInfo object to check against
*/ */
exports.generateTiddlerFilepath = function(title,options) { exports.generateTiddlerFilepath = function(title,options) {
var self = this, var directory = options.directory || "",
directory = options.directory || "",
extension = options.extension || "", extension = options.extension || "",
originalpath = options.originalpath || "", originalpath = (options.fileInfo && options.fileInfo.originalpath) ? options.fileInfo.originalpath : "",
filepath; filepath;
// Check if any of the pathFilters applies // Check if any of the pathFilters applies
if(options.pathFilters && options.wiki) { if(options.pathFilters && options.wiki) {
$tw.utils.each(options.pathFilters,function(filter) { $tw.utils.each(options.pathFilters,function(filter) {
@ -336,7 +333,7 @@ exports.generateTiddlerFilepath = function(title,options) {
} }
}); });
} }
if(!filepath && originalpath !== "") { if(!filepath && !!originalpath) {
//Use the originalpath without the extension //Use the originalpath without the extension
var ext = path.extname(originalpath); var ext = path.extname(originalpath);
filepath = originalpath.substring(0,originalpath.length - ext.length); filepath = originalpath.substring(0,originalpath.length - ext.length);
@ -345,27 +342,35 @@ exports.generateTiddlerFilepath = function(title,options) {
// Remove any forward or backward slashes so we don't create directories // Remove any forward or backward slashes so we don't create directories
filepath = filepath.replace(/\/|\\/g,"_"); filepath = filepath.replace(/\/|\\/g,"_");
} }
//If the path does not start with "." or ".." and a path seperator, then // Replace any Windows control codes
filepath = filepath.replace(/^(con|prn|aux|nul|com[0-9]|lpt[0-9])$/i,"_$1_");
// Replace any leading spaces with the same number of underscores
filepath = filepath.replace(/^ +/,function (u) { return u.replace(/ /g, "_")});
//If the path does not start with "." or ".." && a path seperator, then
if(!/^\.{1,2}[/\\]/g.test(filepath)) { if(!/^\.{1,2}[/\\]/g.test(filepath)) {
// Don't let the filename start with any dots because such files are invisible on *nix // Don't let the filename start with any dots because such files are invisible on *nix
filepath = filepath.replace(/^\.+/g,"_"); filepath = filepath.replace(/^\.+/g,function (u) { return u.replace(/\./g, "_")});
}
// Replace any Unicode control codes
filepath = filepath.replace(/[\x00-\x1f\x80-\x9f]/g,"_");
// Replace any characters that can't be used in cross-platform filenames
filepath = $tw.utils.transliterate(filepath.replace(/<|>|~|\:|\"|\||\?|\*|\^/g,"_"));
// Replace any dots or spaces at the end of the extension with the same number of underscores
extension = extension.replace(/[\. ]+$/, function (u) { return u.replace(/[\. ]/g, "_")});
// Truncate the extension if it is too long
if(extension.length > 32) {
extension = extension.substr(0,32);
} }
// If the filepath already ends in the extension then remove it // If the filepath already ends in the extension then remove it
if(filepath.substring(filepath.length - extension.length) === extension) { if(filepath.substring(filepath.length - extension.length) === extension) {
filepath = filepath.substring(0,filepath.length - extension.length); filepath = filepath.substring(0,filepath.length - extension.length);
} }
// Remove any characters that can't be used in cross-platform filenames
filepath = $tw.utils.transliterate(filepath.replace(/<|>|~|\:|\"|\||\?|\*|\^/g,"_"));
// Truncate the filename if it is too long // Truncate the filename if it is too long
if(filepath.length > 200) { if(filepath.length > 200) {
filepath = filepath.substr(0,200); filepath = filepath.substr(0,200);
} }
// Truncate the extension if it is too long // If the resulting filename is blank (eg because the title is just punctuation)
if(extension.length > 32) { if(!filepath || /^_+$/g.test(filepath)) {
extension = extension.substr(0,32);
}
// If the resulting filename is blank (eg because the title is just punctuation characters)
if(!filepath) {
// ...then just use the character codes of the title // ...then just use the character codes of the title
filepath = ""; filepath = "";
$tw.utils.each(title.split(""),function(char) { $tw.utils.each(title.split(""),function(char) {
@ -386,14 +391,15 @@ exports.generateTiddlerFilepath = function(title,options) {
count++; count++;
} while(fs.existsSync(fullPath)); } while(fs.existsSync(fullPath));
// If the last write failed with an error, or if path does not start with: // If the last write failed with an error, or if path does not start with:
// the resolved options.directory, the resolved wikiPath directory, or the wikiTiddlersPath directory, // the resolved options.directory, the resolved wikiPath directory, the wikiTiddlersPath directory,
// then encodeURIComponent() and resolve to tiddler directory // or the 'originalpath' directory, then encodeURIComponent() and resolve to tiddler directory.
var writePath = $tw.hooks.invokeHook("th-make-tiddler-path",fullPath), var writePath = $tw.hooks.invokeHook("th-make-tiddler-path",fullPath,fullPath),
encode = (options.fileInfo || {writeError: false}).writeError == true; encode = (options.fileInfo || {writeError: false}).writeError == true;
if(!encode) { if(!encode) {
encode = !(fullPath.indexOf(path.resolve(directory)) == 0 || encode = !(writePath.indexOf($tw.boot.wikiTiddlersPath) == 0 ||
fullPath.indexOf(path.resolve($tw.boot.wikiPath)) == 0 || writePath.indexOf(path.resolve(directory)) == 0 ||
fullPath.indexOf($tw.boot.wikiTiddlersPath) == 0); writePath.indexOf(path.resolve($tw.boot.wikiPath)) == 0 ||
writePath.indexOf(path.resolve($tw.boot.wikiTiddlersPath,originalpath)) == 0 );
} }
if(encode) { if(encode) {
writePath = path.resolve(directory,encodeURIComponent(fullPath)); writePath = path.resolve(directory,encodeURIComponent(fullPath));
@ -413,7 +419,7 @@ exports.saveTiddlerToFile = function(tiddler,fileInfo,callback) {
if(fileInfo.hasMetaFile) { if(fileInfo.hasMetaFile) {
// Save the tiddler as a separate body and meta file // Save the tiddler as a separate body and meta file
var typeInfo = $tw.config.contentTypeInfo[tiddler.fields.type || "text/plain"] || {encoding: "utf8"}; var typeInfo = $tw.config.contentTypeInfo[tiddler.fields.type || "text/plain"] || {encoding: "utf8"};
fs.writeFile(fileInfo.filepath,tiddler.fields.text,typeInfo.encoding,function(err) { fs.writeFile(fileInfo.filepath,tiddler.fields.text || "",typeInfo.encoding,function(err) {
if(err) { if(err) {
return callback(err); return callback(err);
} }
@ -455,7 +461,7 @@ exports.saveTiddlerToFileSync = function(tiddler,fileInfo) {
if(fileInfo.hasMetaFile) { if(fileInfo.hasMetaFile) {
// Save the tiddler as a separate body and meta file // Save the tiddler as a separate body and meta file
var typeInfo = $tw.config.contentTypeInfo[tiddler.fields.type || "text/plain"] || {encoding: "utf8"}; var typeInfo = $tw.config.contentTypeInfo[tiddler.fields.type || "text/plain"] || {encoding: "utf8"};
fs.writeFileSync(fileInfo.filepath,tiddler.fields.text,typeInfo.encoding); fs.writeFileSync(fileInfo.filepath,tiddler.fields.text || "",typeInfo.encoding);
fs.writeFileSync(fileInfo.filepath + ".meta",tiddler.getFieldStringBlock({exclude: ["text","bag"]}),"utf8"); fs.writeFileSync(fileInfo.filepath + ".meta",tiddler.getFieldStringBlock({exclude: ["text","bag"]}),"utf8");
} else { } else {
// Save the tiddler as a self contained templated file // Save the tiddler as a self contained templated file
@ -465,6 +471,7 @@ exports.saveTiddlerToFileSync = function(tiddler,fileInfo) {
fs.writeFileSync(fileInfo.filepath,JSON.stringify([tiddler.getFieldStrings({exclude: ["bag"]})],null,$tw.config.preferences.jsonSpaces),"utf8"); fs.writeFileSync(fileInfo.filepath,JSON.stringify([tiddler.getFieldStrings({exclude: ["bag"]})],null,$tw.config.preferences.jsonSpaces),"utf8");
} }
} }
return fileInfo;
}; };
/* /*

View File

@ -294,6 +294,47 @@ exports.slowInSlowOut = function(t) {
return (1 - ((Math.cos(t * Math.PI) + 1) / 2)); return (1 - ((Math.cos(t * Math.PI) + 1) / 2));
}; };
exports.formatTitleString = function(template,options) {
var base = options.base || "",
separator = options.separator || "",
counter = options.counter || "";
var result = "",
t = template,
matches = [
[/^\$basename\$/i, function() {
return base;
}],
[/^\$count:(\d+)\$/i, function(match) {
return $tw.utils.pad(counter,match[1]);
}],
[/^\$separator\$/i, function() {
return separator;
}],
[/^\$count\$/i, function() {
return counter + "";
}]
];
while(t.length){
var matchString = "";
$tw.utils.each(matches, function(m) {
var match = m[0].exec(t);
if(match) {
matchString = m[1].call(null,match);
t = t.substr(match[0].length);
return false;
}
});
if(matchString) {
result += matchString;
} else {
result += t.charAt(0);
t = t.substr(1);
}
}
result = result.replace(/\\(.)/g,"$1");
return result;
};
exports.formatDateString = function(date,template) { exports.formatDateString = function(date,template) {
var result = "", var result = "",
t = template, t = template,
@ -515,6 +556,15 @@ exports.htmlEncode = function(s) {
} }
}; };
// Converts like htmlEncode, but forgets the double quote for brevity
exports.htmlTextEncode = function(s) {
if(s) {
return s.toString().replace(/&/mg,"&amp;").replace(/</mg,"&lt;").replace(/>/mg,"&gt;");
} else {
return "";
}
};
// Converts all HTML entities to their character equivalents // Converts all HTML entities to their character equivalents
exports.entityDecode = function(s) { exports.entityDecode = function(s) {
var converter = String.fromCodePoint || String.fromCharCode, var converter = String.fromCodePoint || String.fromCharCode,
@ -867,7 +917,9 @@ exports.stringifyNumber = function(num) {
exports.makeCompareFunction = function(type,options) { exports.makeCompareFunction = function(type,options) {
options = options || {}; options = options || {};
var gt = options.invert ? -1 : +1, // set isCaseSensitive to true if not defined in options
var isCaseSensitive = (options.isCaseSensitive === false) ? false : true,
gt = options.invert ? -1 : +1,
lt = options.invert ? +1 : -1, lt = options.invert ? +1 : -1,
compare = function(a,b) { compare = function(a,b) {
if(a > b) { if(a > b) {
@ -886,7 +938,11 @@ exports.makeCompareFunction = function(type,options) {
return compare($tw.utils.parseInt(a),$tw.utils.parseInt(b)); return compare($tw.utils.parseInt(a),$tw.utils.parseInt(b));
}, },
"string": function(a,b) { "string": function(a,b) {
return compare("" + a,"" +b); if(!isCaseSensitive) {
a = a.toLowerCase();
b = b.toLowerCase();
}
return compare("" + a,"" + b);
}, },
"date": function(a,b) { "date": function(a,b) {
var dateA = $tw.utils.parseDate(a), var dateA = $tw.utils.parseDate(a),
@ -901,6 +957,13 @@ exports.makeCompareFunction = function(type,options) {
}, },
"version": function(a,b) { "version": function(a,b) {
return $tw.utils.compareVersions(a,b); return $tw.utils.compareVersions(a,b);
},
"alphanumeric": function(a,b) {
if(!isCaseSensitive) {
a = a.toLowerCase();
b = b.toLowerCase();
}
return options.invert ? b.localeCompare(a,undefined,{numeric: true,sensitivity: "base"}) : a.localeCompare(b,undefined,{numeric: true,sensitivity: "base"});
} }
}; };
return (types[type] || types[options.defaultType] || types.number); return (types[type] || types[options.defaultType] || types.number);

View File

@ -44,9 +44,7 @@ ActionListopsWidget.prototype.execute = function() {
*/ */
ActionListopsWidget.prototype.refresh = function(changedTiddlers) { ActionListopsWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(); var changedAttributes = this.computeAttributes();
if(changedAttributes.$tiddler || changedAttributes.$filter || if($tw.utils.count(changedAttributes) > 0) {
changedAttributes.$subfilter || changedAttributes.$field ||
changedAttributes.$index || changedAttributes.$tags) {
this.refreshSelf(); this.refreshSelf();
return true; return true;
} }
@ -60,12 +58,10 @@ ActionListopsWidget.prototype.invokeAction = function(triggeringWidget,
//Apply the specified filters to the lists //Apply the specified filters to the lists
var field = this.listField, var field = this.listField,
index, index,
type = "!!",
list = this.listField; list = this.listField;
if(this.listIndex) { if(this.listIndex) {
field = undefined; field = undefined;
index = this.listIndex; index = this.listIndex;
type = "##";
list = this.listIndex; list = this.listIndex;
} }
if(this.filter) { if(this.filter) {
@ -74,15 +70,14 @@ ActionListopsWidget.prototype.invokeAction = function(triggeringWidget,
.filterTiddlers(this.filter, this))); .filterTiddlers(this.filter, this)));
} }
if(this.subfilter) { if(this.subfilter) {
var subfilter = "[list[" + this.target + type + list + "]] " + this.subfilter; var inputList = this.wiki.getTiddlerList(this.target,field,index),
this.wiki.setText(this.target, field, index, $tw.utils.stringifyList( subfilter = $tw.utils.stringifyList(inputList) + " " + this.subfilter;
this.wiki this.wiki.setText(this.target, field, index, $tw.utils.stringifyList(this.wiki.filterTiddlers(subfilter,this)));
.filterTiddlers(subfilter, this)));
} }
if(this.filtertags) { if(this.filtertags) {
var tiddler = this.wiki.getTiddler(this.target), var tiddler = this.wiki.getTiddler(this.target),
oldtags = tiddler ? (tiddler.fields.tags || []).slice(0) : [], oldtags = tiddler ? (tiddler.fields.tags || []).slice(0) : [],
tagfilter = "[list[" + this.target + "!!tags]] " + this.filtertags, tagfilter = $tw.utils.stringifyList(oldtags) + " " + this.filtertags,
newtags = this.wiki.filterTiddlers(tagfilter,this); newtags = this.wiki.filterTiddlers(tagfilter,this);
if($tw.utils.stringifyList(oldtags.sort()) !== $tw.utils.stringifyList(newtags.sort())) { if($tw.utils.stringifyList(oldtags.sort()) !== $tw.utils.stringifyList(newtags.sort())) {
this.wiki.setText(this.target,"tags",undefined,$tw.utils.stringifyList(newtags)); this.wiki.setText(this.target,"tags",undefined,$tw.utils.stringifyList(newtags));

View File

@ -37,6 +37,7 @@ Compute the internal state of the widget
ActionPopupWidget.prototype.execute = function() { ActionPopupWidget.prototype.execute = function() {
this.actionState = this.getAttribute("$state"); this.actionState = this.getAttribute("$state");
this.actionCoords = this.getAttribute("$coords"); this.actionCoords = this.getAttribute("$coords");
this.floating = this.getAttribute("$floating","no") === "yes";
}; };
/* /*
@ -68,7 +69,8 @@ ActionPopupWidget.prototype.invokeAction = function(triggeringWidget,event) {
height: parseFloat(match[4]) height: parseFloat(match[4])
}, },
title: this.actionState, title: this.actionState,
wiki: this.wiki wiki: this.wiki,
floating: this.floating
}); });
} else { } else {
$tw.popup.cancel(0); $tw.popup.cancel(0);

View File

@ -12,6 +12,8 @@ Dropzone widget
/*global $tw: false */ /*global $tw: false */
"use strict"; "use strict";
var IMPORT_TITLE = "$:/Import";
var Widget = require("$:/core/modules/widgets/widget.js").widget; var Widget = require("$:/core/modules/widgets/widget.js").widget;
var DropZoneWidget = function(parseTreeNode,options) { var DropZoneWidget = function(parseTreeNode,options) {
@ -35,6 +37,7 @@ DropZoneWidget.prototype.render = function(parent,nextSibling) {
this.execute(); this.execute();
// Create element // Create element
var domNode = this.document.createElement("div"); var domNode = this.document.createElement("div");
this.domNode = domNode;
domNode.className = this.dropzoneClass || "tc-dropzone"; domNode.className = this.dropzoneClass || "tc-dropzone";
// Add event handlers // Add event handlers
if(this.dropzoneEnable) { if(this.dropzoneEnable) {
@ -45,10 +48,8 @@ DropZoneWidget.prototype.render = function(parent,nextSibling) {
{name: "drop", handlerObject: this, handlerMethod: "handleDropEvent"}, {name: "drop", handlerObject: this, handlerMethod: "handleDropEvent"},
{name: "paste", handlerObject: this, handlerMethod: "handlePasteEvent"}, {name: "paste", handlerObject: this, handlerMethod: "handlePasteEvent"},
{name: "dragend", handlerObject: this, handlerMethod: "handleDragEndEvent"} {name: "dragend", handlerObject: this, handlerMethod: "handleDragEndEvent"}
]); ]);
} }
domNode.addEventListener("click",function (event) {
},false);
// Insert element // Insert element
parent.insertBefore(domNode,nextSibling); parent.insertBefore(domNode,nextSibling);
this.renderChildren(domNode,null); this.renderChildren(domNode,null);
@ -57,12 +58,46 @@ DropZoneWidget.prototype.render = function(parent,nextSibling) {
this.currentlyEntered = []; this.currentlyEntered = [];
}; };
// Handler for transient event listeners added when the dropzone has an active drag in progress
DropZoneWidget.prototype.handleEvent = function(event) {
if(event.type === "click") {
if(this.currentlyEntered.length) {
this.resetState();
}
} else if(event.type === "dragenter") {
if(event.target && event.target !== this.domNode && !$tw.utils.domContains(this.domNode,event.target)) {
this.resetState();
}
} else if(event.type === "dragleave") {
// Check if drag left the window
if(event.relatedTarget === null || (event.relatedTarget && event.relatedTarget.nodeName === "HTML")) {
this.resetState();
}
}
};
// Reset the state of the dropzone after a drag has ended
DropZoneWidget.prototype.resetState = function() {
$tw.utils.removeClass(this.domNode,"tc-dragover");
this.currentlyEntered = [];
this.document.body.removeEventListener("click",this,true);
this.document.body.removeEventListener("dragenter",this,true);
this.document.body.removeEventListener("dragleave",this,true);
this.dragInProgress = false;
};
DropZoneWidget.prototype.enterDrag = function(event) { DropZoneWidget.prototype.enterDrag = function(event) {
if(this.currentlyEntered.indexOf(event.target) === -1) { if(this.currentlyEntered.indexOf(event.target) === -1) {
this.currentlyEntered.push(event.target); this.currentlyEntered.push(event.target);
} }
// If we're entering for the first time we need to apply highlighting if(!this.dragInProgress) {
$tw.utils.addClass(this.domNodes[0],"tc-dragover"); this.dragInProgress = true;
// If we're entering for the first time we need to apply highlighting
$tw.utils.addClass(this.domNodes[0],"tc-dragover");
this.document.body.addEventListener("click",this,true);
this.document.body.addEventListener("dragenter",this,true);
this.document.body.addEventListener("dragleave",this,true);
}
}; };
DropZoneWidget.prototype.leaveDrag = function(event) { DropZoneWidget.prototype.leaveDrag = function(event) {
@ -72,15 +107,17 @@ DropZoneWidget.prototype.leaveDrag = function(event) {
} }
// Remove highlighting if we're leaving externally // Remove highlighting if we're leaving externally
if(this.currentlyEntered.length === 0) { if(this.currentlyEntered.length === 0) {
$tw.utils.removeClass(this.domNodes[0],"tc-dragover"); this.resetState();
} }
}; };
DropZoneWidget.prototype.handleDragEnterEvent = function(event) { DropZoneWidget.prototype.handleDragEnterEvent = function(event) {
// Check for this window being the source of the drag
if($tw.dragInProgress) { if($tw.dragInProgress) {
return false; return false;
} }
if(this.filesOnly && !$tw.utils.dragEventContainsFiles(event)) {
return false;
}
this.enterDrag(event); this.enterDrag(event);
// Tell the browser that we're ready to handle the drop // Tell the browser that we're ready to handle the drop
event.preventDefault(); event.preventDefault();
@ -99,7 +136,10 @@ DropZoneWidget.prototype.handleDragOverEvent = function(event) {
} }
// Tell the browser that we're still interested in the drop // Tell the browser that we're still interested in the drop
event.preventDefault(); event.preventDefault();
event.dataTransfer.dropEffect = "copy"; // Explicitly show this is a copy // Check if this is a synthetic event, IE does not allow accessing dropEffect outside of original event handler
if(event.isTrusted) {
event.dataTransfer.dropEffect = "copy"; // Explicitly show this is a copy
}
}; };
DropZoneWidget.prototype.handleDragLeaveEvent = function(event) { DropZoneWidget.prototype.handleDragLeaveEvent = function(event) {
@ -107,13 +147,41 @@ DropZoneWidget.prototype.handleDragLeaveEvent = function(event) {
}; };
DropZoneWidget.prototype.handleDragEndEvent = function(event) { DropZoneWidget.prototype.handleDragEndEvent = function(event) {
$tw.utils.removeClass(this.domNodes[0],"tc-dragover"); this.resetState();
};
DropZoneWidget.prototype.filterByContentTypes = function(tiddlerFieldsArray) {
var filteredTypes,
filtered = [],
types = [];
$tw.utils.each(tiddlerFieldsArray,function(tiddlerFields) {
types.push(tiddlerFields.type || "");
});
filteredTypes = this.wiki.filterTiddlers(this.contentTypesFilter,this,this.wiki.makeTiddlerIterator(types));
$tw.utils.each(tiddlerFieldsArray,function(tiddlerFields) {
if(filteredTypes.indexOf(tiddlerFields.type) !== -1) {
filtered.push(tiddlerFields);
}
});
return filtered;
};
DropZoneWidget.prototype.readFileCallback = function(tiddlerFieldsArray) {
if(this.contentTypesFilter) {
tiddlerFieldsArray = this.filterByContentTypes(tiddlerFieldsArray);
}
if(tiddlerFieldsArray.length) {
this.dispatchEvent({type: "tm-import-tiddlers", param: JSON.stringify(tiddlerFieldsArray), autoOpenOnImport: this.autoOpenOnImport, importTitle: this.importTitle});
if(this.actions) {
this.invokeActionString(this.actions,this,event,{importTitle: this.importTitle});
}
}
}; };
DropZoneWidget.prototype.handleDropEvent = function(event) { DropZoneWidget.prototype.handleDropEvent = function(event) {
var self = this, var self = this,
readFileCallback = function(tiddlerFieldsArray) { readFileCallback = function(tiddlerFieldsArray) {
self.dispatchEvent({type: "tm-import-tiddlers", param: JSON.stringify(tiddlerFieldsArray), autoOpenOnImport: self.autoOpenOnImport, importTitle: self.importTitle}); self.readFileCallback(tiddlerFieldsArray);
}; };
this.leaveDrag(event); this.leaveDrag(event);
// Check for being over a TEXTAREA or INPUT // Check for being over a TEXTAREA or INPUT
@ -127,7 +195,7 @@ DropZoneWidget.prototype.handleDropEvent = function(event) {
var self = this, var self = this,
dataTransfer = event.dataTransfer; dataTransfer = event.dataTransfer;
// Remove highlighting // Remove highlighting
$tw.utils.removeClass(this.domNodes[0],"tc-dragover"); this.resetState();
// Import any files in the drop // Import any files in the drop
var numFiles = 0; var numFiles = 0;
if(dataTransfer.files) { if(dataTransfer.files) {
@ -138,7 +206,23 @@ DropZoneWidget.prototype.handleDropEvent = function(event) {
} }
// Try to import the various data types we understand // Try to import the various data types we understand
if(numFiles === 0) { if(numFiles === 0) {
$tw.utils.importDataTransfer(dataTransfer,this.wiki.generateNewTitle("Untitled"),readFileCallback); var fallbackTitle = self.wiki.generateNewTitle("Untitled");
//Use the deserializer specified if any
if(this.dropzoneDeserializer) {
for(var t= 0; t<dataTransfer.items.length; t++) {
var item = dataTransfer.items[t];
if(item.kind === "string") {
item.getAsString(function(str){
var tiddlerFields = self.wiki.deserializeTiddlers(null,str,{title: fallbackTitle},{deserializer:self.dropzoneDeserializer});
if(tiddlerFields && tiddlerFields.length) {
readFileCallback(tiddlerFields);
}
})
}
}
} else {
$tw.utils.importDataTransfer(dataTransfer,fallbackTitle,readFileCallback);
}
} }
// Tell the browser that we handled the drop // Tell the browser that we handled the drop
event.preventDefault(); event.preventDefault();
@ -149,7 +233,7 @@ DropZoneWidget.prototype.handleDropEvent = function(event) {
DropZoneWidget.prototype.handlePasteEvent = function(event) { DropZoneWidget.prototype.handlePasteEvent = function(event) {
var self = this, var self = this,
readFileCallback = function(tiddlerFieldsArray) { readFileCallback = function(tiddlerFieldsArray) {
self.dispatchEvent({type: "tm-import-tiddlers", param: JSON.stringify(tiddlerFieldsArray), autoOpenOnImport: self.autoOpenOnImport, importTitle: self.importTitle}); self.readFileCallback(tiddlerFieldsArray);
}; };
// Let the browser handle it if we're in a textarea or input box // Let the browser handle it if we're in a textarea or input box
if(["TEXTAREA","INPUT"].indexOf(event.target.tagName) == -1 && !event.target.isContentEditable) { if(["TEXTAREA","INPUT"].indexOf(event.target.tagName) == -1 && !event.target.isContentEditable) {
@ -166,17 +250,26 @@ DropZoneWidget.prototype.handlePasteEvent = function(event) {
}); });
} else if(item.kind === "string") { } else if(item.kind === "string") {
// Create tiddlers from string items // Create tiddlers from string items
var type = item.type; var tiddlerFields,
type = item.type;
item.getAsString(function(str) { item.getAsString(function(str) {
var tiddlerFields = { // Use the deserializer specified if any
title: self.wiki.generateNewTitle("Untitled"), if(self.dropzoneDeserializer) {
text: str, tiddlerFields = self.wiki.deserializeTiddlers(null,str,{title: self.wiki.generateNewTitle("Untitled")},{deserializer:self.dropzoneDeserializer});
type: type if(tiddlerFields && tiddlerFields.length) {
}; readFileCallback(tiddlerFields);
if($tw.log.IMPORT) { }
console.log("Importing string '" + str + "', type: '" + type + "'"); } else {
tiddlerFields = {
title: self.wiki.generateNewTitle("Untitled"),
text: str,
type: type
};
if($tw.log.IMPORT) {
console.log("Importing string '" + str + "', type: '" + type + "'");
}
readFileCallback([tiddlerFields]);
} }
self.dispatchEvent({type: "tm-import-tiddlers", param: JSON.stringify([tiddlerFields]), autoOpenOnImport: self.autoOpenOnImport, importTitle: self.importTitle});
}); });
} }
} }
@ -194,7 +287,10 @@ DropZoneWidget.prototype.execute = function() {
this.dropzoneDeserializer = this.getAttribute("deserializer"); this.dropzoneDeserializer = this.getAttribute("deserializer");
this.dropzoneEnable = (this.getAttribute("enable") || "yes") === "yes"; this.dropzoneEnable = (this.getAttribute("enable") || "yes") === "yes";
this.autoOpenOnImport = this.getAttribute("autoOpenOnImport"); this.autoOpenOnImport = this.getAttribute("autoOpenOnImport");
this.importTitle = this.getAttribute("importTitle"); this.importTitle = this.getAttribute("importTitle",IMPORT_TITLE);
this.actions = this.getAttribute("actions");
this.contentTypesFilter = this.getAttribute("contentTypesFilter");
this.filesOnly = this.getAttribute("filesOnly","no") === "yes";
// Make child widgets // Make child widgets
this.makeChildWidgets(); this.makeChildWidgets();
}; };
@ -204,7 +300,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
*/ */
DropZoneWidget.prototype.refresh = function(changedTiddlers) { DropZoneWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(); var changedAttributes = this.computeAttributes();
if(changedAttributes.enable || changedAttributes.autoOpenOnImport || changedAttributes.importTitle || changedAttributes.deserializer || changedAttributes.class) { if($tw.utils.count(changedAttributes) > 0) {
this.refreshSelf(); this.refreshSelf();
return true; return true;
} }

View File

@ -47,38 +47,46 @@ EventWidget.prototype.render = function(parent,nextSibling) {
domNode.addEventListener(type,function(event) { domNode.addEventListener(type,function(event) {
var selector = self.getAttribute("selector"), var selector = self.getAttribute("selector"),
actions = self.getAttribute("actions-"+type), actions = self.getAttribute("actions-"+type),
stopPropagation = self.getAttribute("stopPropagation","onaction"),
selectedNode = event.target, selectedNode = event.target,
selectedNodeRect, selectedNodeRect,
catcherNodeRect, catcherNodeRect,
variables = {}; variables = {};
// Firefox can fire dragover and dragenter events on text nodes instead of their parents
if(selectedNode.nodeType === 3) {
selectedNode = selectedNode.parentNode;
}
if(selector) { if(selector) {
// Search ancestors for a node that matches the selector // Search ancestors for a node that matches the selector
while(!selectedNode.matches(selector) && selectedNode !== domNode) { while(!$tw.utils.domMatchesSelector(selectedNode,selector) && selectedNode !== domNode) {
selectedNode = selectedNode.parentNode; selectedNode = selectedNode.parentNode;
} }
// If we found one, copy the attributes as variables, otherwise exit // If we found one, copy the attributes as variables, otherwise exit
if(selectedNode.matches(selector)) { if($tw.utils.domMatchesSelector(selectedNode,selector)) {
$tw.utils.each(selectedNode.attributes,function(attribute) { // Only set up variables if we have actions to invoke
variables["dom-" + attribute.name] = attribute.value.toString(); if(actions) {
}); $tw.utils.each(selectedNode.attributes,function(attribute) {
//Add a variable with a popup coordinate string for the selected node variables["dom-" + attribute.name] = attribute.value.toString();
variables["tv-popup-coords"] = "(" + selectedNode.offsetLeft + "," + selectedNode.offsetTop +"," + selectedNode.offsetWidth + "," + selectedNode.offsetHeight + ")"; });
//Add a variable with a popup coordinate string for the selected node
//Add variables for offset of selected node variables["tv-popup-coords"] = "(" + selectedNode.offsetLeft + "," + selectedNode.offsetTop +"," + selectedNode.offsetWidth + "," + selectedNode.offsetHeight + ")";
variables["tv-selectednode-posx"] = selectedNode.offsetLeft.toString();
variables["tv-selectednode-posy"] = selectedNode.offsetTop.toString();
variables["tv-selectednode-width"] = selectedNode.offsetWidth.toString();
variables["tv-selectednode-height"] = selectedNode.offsetHeight.toString();
//Add variables for event X and Y position relative to selected node //Add variables for offset of selected node
selectedNodeRect = selectedNode.getBoundingClientRect(); variables["tv-selectednode-posx"] = selectedNode.offsetLeft.toString();
variables["event-fromselected-posx"] = (event.clientX - selectedNodeRect.left).toString(); variables["tv-selectednode-posy"] = selectedNode.offsetTop.toString();
variables["event-fromselected-posy"] = (event.clientY - selectedNodeRect.top).toString(); variables["tv-selectednode-width"] = selectedNode.offsetWidth.toString();
variables["tv-selectednode-height"] = selectedNode.offsetHeight.toString();
//Add variables for event X and Y position relative to event catcher node //Add variables for event X and Y position relative to selected node
catcherNodeRect = self.domNode.getBoundingClientRect(); selectedNodeRect = selectedNode.getBoundingClientRect();
variables["event-fromcatcher-posx"] = (event.clientX - catcherNodeRect.left).toString(); variables["event-fromselected-posx"] = (event.clientX - selectedNodeRect.left).toString();
variables["event-fromcatcher-posy"] = (event.clientY - catcherNodeRect.top).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();
}
} else { } else {
return false; return false;
} }
@ -106,6 +114,8 @@ EventWidget.prototype.render = function(parent,nextSibling) {
variables["event-detail"] = event.detail.toString(); variables["event-detail"] = event.detail.toString();
} }
self.invokeActionString(actions,self,event,variables); self.invokeActionString(actions,self,event,variables);
}
if((actions && stopPropagation === "onaction") || stopPropagation === "always") {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
return true; return true;

View File

@ -67,7 +67,7 @@ FieldManglerWidget.prototype.handleRemoveFieldEvent = function(event) {
deletion = {}; deletion = {};
deletion[event.param] = undefined; deletion[event.param] = undefined;
this.wiki.addTiddler(new $tw.Tiddler(tiddler,deletion)); this.wiki.addTiddler(new $tw.Tiddler(tiddler,deletion));
return true; return false;
}; };
FieldManglerWidget.prototype.handleAddFieldEvent = function(event) { FieldManglerWidget.prototype.handleAddFieldEvent = function(event) {
@ -105,7 +105,7 @@ FieldManglerWidget.prototype.handleAddFieldEvent = function(event) {
} }
} }
this.wiki.addTiddler(new $tw.Tiddler(tiddler,addition)); this.wiki.addTiddler(new $tw.Tiddler(tiddler,addition));
return true; return false;
}; };
FieldManglerWidget.prototype.handleRemoveTagEvent = function(event) { FieldManglerWidget.prototype.handleRemoveTagEvent = function(event) {
@ -122,7 +122,7 @@ FieldManglerWidget.prototype.handleRemoveTagEvent = function(event) {
this.wiki.addTiddler(new $tw.Tiddler(tiddler,modification)); this.wiki.addTiddler(new $tw.Tiddler(tiddler,modification));
} }
} }
return true; return false;
}; };
FieldManglerWidget.prototype.handleAddTagEvent = function(event) { FieldManglerWidget.prototype.handleAddTagEvent = function(event) {
@ -140,7 +140,7 @@ FieldManglerWidget.prototype.handleAddTagEvent = function(event) {
tag.push(event.param.trim()); tag.push(event.param.trim());
this.wiki.addTiddler(new $tw.Tiddler({title: this.mangleTitle, tags: tag},modification)); this.wiki.addTiddler(new $tw.Tiddler({title: this.mangleTitle, tags: tag},modification));
} }
return true; return false;
}; };
exports.fieldmangler = FieldManglerWidget; exports.fieldmangler = FieldManglerWidget;

View File

@ -61,6 +61,7 @@ ListWidget.prototype.execute = function() {
this.template = this.getAttribute("template"); this.template = this.getAttribute("template");
this.editTemplate = this.getAttribute("editTemplate"); this.editTemplate = this.getAttribute("editTemplate");
this.variableName = this.getAttribute("variable","currentTiddler"); this.variableName = this.getAttribute("variable","currentTiddler");
this.counterName = this.getAttribute("counter");
this.storyViewName = this.getAttribute("storyview"); this.storyViewName = this.getAttribute("storyview");
this.historyTitle = this.getAttribute("history"); this.historyTitle = this.getAttribute("history");
// Compose the list elements // Compose the list elements
@ -72,7 +73,7 @@ ListWidget.prototype.execute = function() {
members = this.getEmptyMessage(); members = this.getEmptyMessage();
} else { } else {
$tw.utils.each(this.list,function(title,index) { $tw.utils.each(this.list,function(title,index) {
members.push(self.makeItemTemplate(title)); members.push(self.makeItemTemplate(title,index));
}); });
} }
// Construct the child widgets // Construct the child widgets
@ -105,7 +106,7 @@ ListWidget.prototype.getEmptyMessage = function() {
/* /*
Compose the template for a list item Compose the template for a list item
*/ */
ListWidget.prototype.makeItemTemplate = function(title) { ListWidget.prototype.makeItemTemplate = function(title,index) {
// Check if the tiddler is a draft // Check if the tiddler is a draft
var tiddler = this.wiki.getTiddler(title), var tiddler = this.wiki.getTiddler(title),
isDraft = tiddler && tiddler.hasField("draft.of"), isDraft = tiddler && tiddler.hasField("draft.of"),
@ -128,7 +129,14 @@ ListWidget.prototype.makeItemTemplate = function(title) {
} }
} }
// Return the list item // Return the list item
return {type: "listitem", itemTitle: title, variableName: this.variableName, children: templateTree}; var parseTreeNode = {type: "listitem", itemTitle: title, variableName: this.variableName, children: templateTree};
if(this.counterName) {
parseTreeNode.counter = (index + 1).toString();
parseTreeNode.counterName = this.counterName;
parseTreeNode.isFirst = index === 0;
parseTreeNode.isLast = index === this.list.length - 1;
}
return parseTreeNode;
}; };
/* /*
@ -142,7 +150,7 @@ ListWidget.prototype.refresh = function(changedTiddlers) {
this.storyview.refreshStart(changedTiddlers,changedAttributes); this.storyview.refreshStart(changedTiddlers,changedAttributes);
} }
// Completely refresh if any of our attributes have changed // Completely refresh if any of our attributes have changed
if(changedAttributes.filter || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.storyview || changedAttributes.history) { if(changedAttributes.filter || changedAttributes.variable || changedAttributes.counter || changedAttributes.template || changedAttributes.editTemplate || changedAttributes.emptyMessage || changedAttributes.storyview || changedAttributes.history) {
this.refreshSelf(); this.refreshSelf();
result = true; result = true;
} else { } else {
@ -211,23 +219,41 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) {
this.removeChildDomNodes(); this.removeChildDomNodes();
this.children = []; this.children = [];
} }
// Cycle through the list, inserting and removing list items as needed // If we are providing an counter variable then we must refresh the items, otherwise we can rearrange them
var hasRefreshed = false; var hasRefreshed = false,t;
for(var t=0; t<this.list.length; t++) { if(this.counterName) {
var index = this.findListItem(t,this.list[t]); // Cycle through the list and remove and re-insert the first item that has changed, and all the remaining items
if(index === undefined) { for(t=0; t<this.list.length; t++) {
// The list item must be inserted if(hasRefreshed || !this.children[t] || this.children[t].parseTreeNode.itemTitle !== this.list[t]) {
this.insertListItem(t,this.list[t]); if(this.children[t]) {
hasRefreshed = true; this.removeListItem(t);
} else { }
// There are intervening list items that must be removed this.insertListItem(t,this.list[t]);
for(var n=index-1; n>=t; n--) {
this.removeListItem(n);
hasRefreshed = true; hasRefreshed = true;
} else {
// Refresh the item we're reusing
var refreshed = this.children[t].refresh(changedTiddlers);
hasRefreshed = hasRefreshed || refreshed;
}
}
} else {
// Cycle through the list, inserting and removing list items as needed
for(t=0; t<this.list.length; t++) {
var index = this.findListItem(t,this.list[t]);
if(index === undefined) {
// The list item must be inserted
this.insertListItem(t,this.list[t]);
hasRefreshed = true;
} else {
// There are intervening list items that must be removed
for(var n=index-1; n>=t; n--) {
this.removeListItem(n);
hasRefreshed = true;
}
// Refresh the item we're reusing
var refreshed = this.children[t].refresh(changedTiddlers);
hasRefreshed = hasRefreshed || refreshed;
} }
// Refresh the item we're reusing
var refreshed = this.children[t].refresh(changedTiddlers);
hasRefreshed = hasRefreshed || refreshed;
} }
} }
// Remove any left over items // Remove any left over items
@ -257,7 +283,7 @@ Insert a new list item at the specified index
*/ */
ListWidget.prototype.insertListItem = function(index,title) { ListWidget.prototype.insertListItem = function(index,title) {
// Create, insert and render the new child widgets // Create, insert and render the new child widgets
var widget = this.makeChildWidget(this.makeItemTemplate(title)); var widget = this.makeChildWidget(this.makeItemTemplate(title,index));
widget.parentDomNode = this.parentDomNode; // Hack to enable findNextSiblingDomNode() to work widget.parentDomNode = this.parentDomNode; // Hack to enable findNextSiblingDomNode() to work
this.children.splice(index,0,widget); this.children.splice(index,0,widget);
var nextSibling = widget.findNextSiblingDomNode(); var nextSibling = widget.findNextSiblingDomNode();
@ -311,6 +337,11 @@ Compute the internal state of the widget
ListItemWidget.prototype.execute = function() { ListItemWidget.prototype.execute = function() {
// Set the current list item title // Set the current list item title
this.setVariable(this.parseTreeNode.variableName,this.parseTreeNode.itemTitle); this.setVariable(this.parseTreeNode.variableName,this.parseTreeNode.itemTitle);
if(this.parseTreeNode.counterName) {
this.setVariable(this.parseTreeNode.counterName,this.parseTreeNode.counter);
this.setVariable(this.parseTreeNode.counterName + "-first",this.parseTreeNode.isFirst ? "yes" : "no");
this.setVariable(this.parseTreeNode.counterName + "-last",this.parseTreeNode.isLast ? "yes" : "no");
}
// Construct the child widgets // Construct the child widgets
this.makeChildWidgets(); this.makeChildWidgets();
}; };

View File

@ -170,7 +170,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
SelectWidget.prototype.refresh = function(changedTiddlers) { SelectWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(); var changedAttributes = this.computeAttributes();
// If we're using a different tiddler/field/index then completely refresh ourselves // If we're using a different tiddler/field/index then completely refresh ourselves
if(changedAttributes.selectTitle || changedAttributes.selectField || changedAttributes.selectIndex || changedAttributes.selectTooltip) { if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedAttributes.tooltip) {
this.refreshSelf(); this.refreshSelf();
return true; return true;
// If the target tiddler value has changed, just update setting and refresh the children // If the target tiddler value has changed, just update setting and refresh the children

View File

@ -75,17 +75,8 @@ TranscludeWidget.prototype.execute = function() {
]}]; ]}];
} }
} }
// Assign any variables set via attributes starting with $
var variables = Object.create(null);
$tw.utils.each(this.attributes,function(attribute,name) {
if(name.charAt(0) === "$") {
variables[name.substr(1)] = attribute;
}
});
// Construct the child widgets // Construct the child widgets
this.makeChildWidgets(parseTreeNodes,{ this.makeChildWidgets(parseTreeNodes);
variables: variables
});
}; };
/* /*
@ -112,7 +103,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
*/ */
TranscludeWidget.prototype.refresh = function(changedTiddlers) { TranscludeWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(); var changedAttributes = this.computeAttributes();
if($tw.utils.count(changedAttributes) || changedTiddlers[this.transcludeTitle]) { if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedTiddlers[this.transcludeTitle]) {
this.refreshSelf(); this.refreshSelf();
return true; return true;
} else { } else {

View File

@ -65,6 +65,9 @@ ViewWidget.prototype.execute = function() {
case "htmlencoded": case "htmlencoded":
this.text = this.getValueAsHtmlEncoded(); this.text = this.getValueAsHtmlEncoded();
break; break;
case "htmltextencoded":
this.text = this.getValueAsHtmlTextEncoded();
break;
case "urlencoded": case "urlencoded":
this.text = this.getValueAsUrlEncoded(); this.text = this.getValueAsUrlEncoded();
break; break;
@ -160,6 +163,10 @@ ViewWidget.prototype.getValueAsHtmlEncoded = function() {
return $tw.utils.htmlEncode(this.getValueAsText()); return $tw.utils.htmlEncode(this.getValueAsText());
}; };
ViewWidget.prototype.getValueAsHtmlTextEncoded = function() {
return $tw.utils.htmlTextEncode(this.getValueAsText());
};
ViewWidget.prototype.getValueAsUrlEncoded = function() { ViewWidget.prototype.getValueAsUrlEncoded = function() {
return encodeURIComponent(this.getValueAsText()); return encodeURIComponent(this.getValueAsText());
}; };

View File

@ -190,15 +190,25 @@ exports.getChangeCount = function(title) {
/* /*
Generate an unused title from the specified base Generate an unused title from the specified base
options.prefix must be a string
*/ */
exports.generateNewTitle = function(baseTitle,options) { exports.generateNewTitle = function(baseTitle,options) {
options = options || {}; options = options || {};
var c = 0, var c = 0,
title = baseTitle; title = baseTitle,
while(this.tiddlerExists(title) || this.isShadowTiddler(title) || this.findDraft(title)) { template = options.template,
title = baseTitle + prefix = (typeof(options.prefix) === "string") ? options.prefix : " ";
(options.prefix || " ") + if (template) {
(++c); // "count" is important to avoid an endless loop in while(...)!!
template = (/\$count:?(\d+)?\$/i.test(template)) ? template : template + "$count$";
title = $tw.utils.formatTitleString(template,{"base":baseTitle,"separator":prefix,"counter":c});
while(this.tiddlerExists(title) || this.isShadowTiddler(title) || this.findDraft(title)) {
title = $tw.utils.formatTitleString(template,{"base":baseTitle,"separator":prefix,"counter":(++c)});
}
} else {
while(this.tiddlerExists(title) || this.isShadowTiddler(title) || this.findDraft(title)) {
title = baseTitle + prefix + (++c);
}
} }
return title; return title;
}; };
@ -211,6 +221,10 @@ exports.isTemporaryTiddler = function(title) {
return title && title.indexOf("$:/temp/") === 0; return title && title.indexOf("$:/temp/") === 0;
}; };
exports.isVolatileTiddler = function(title) {
return title && title.indexOf("$:/temp/volatile/") === 0;
};
exports.isImageTiddler = function(title) { exports.isImageTiddler = function(title) {
var tiddler = this.getTiddler(title); var tiddler = this.getTiddler(title);
if(tiddler) { if(tiddler) {
@ -364,12 +378,12 @@ exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,is
var tiddlerA = self.getTiddler(a), var tiddlerA = self.getTiddler(a),
tiddlerB = self.getTiddler(b); tiddlerB = self.getTiddler(b);
if(tiddlerA) { if(tiddlerA) {
a = tiddlerA.fields[sortField] || ""; a = tiddlerA.getFieldString(sortField) || "";
} else { } else {
a = ""; a = "";
} }
if(tiddlerB) { if(tiddlerB) {
b = tiddlerB.fields[sortField] || ""; b = tiddlerB.getFieldString(sortField) || "";
} else { } else {
b = ""; b = "";
} }
@ -1495,6 +1509,13 @@ exports.invokeUpgraders = function(titles,tiddlers) {
// Determine whether a plugin by title is dynamically loadable // Determine whether a plugin by title is dynamically loadable
exports.doesPluginRequireReload = function(title) { exports.doesPluginRequireReload = function(title) {
var tiddler = this.getTiddler(title);
if(tiddler && tiddler.fields.type === "application/json" && tiddler.fields["plugin-type"]) {
if(tiddler.fields["plugin-type"] === "import") {
// The import plugin never requires reloading
return false;
}
}
return this.doesPluginInfoRequireReload(this.getPluginInfo(title) || this.getTiddlerDataCached(title)); return this.doesPluginInfoRequireReload(this.getPluginInfo(title) || this.getTiddlerDataCached(title));
}; };
@ -1539,4 +1560,3 @@ exports.slugify = function(title,options) {
}; };
})(); })();

View File

@ -5,5 +5,5 @@ title: $:/core/templates/html-div-tiddler
This template is used for saving tiddlers as an HTML DIV tag with attributes representing the tiddler fields. This template is used for saving tiddlers as an HTML DIV tag with attributes representing the tiddler fields.
-->`<div`<$fields template=' $name$="$encoded_value$"'></$fields>`> -->`<div`<$fields template=' $name$="$encoded_value$"'></$fields>`>
<pre>`<$view field="text" format="htmlencoded" />`</pre> <pre>`<$view field="text" format="htmltextencoded" />`</pre>
</div>` </div>`

View File

@ -30,8 +30,6 @@ http://$(userName)$.tiddlyspot.com/$path$/
|<<lingo UserName>> |<$edit-text tiddler="$:/UploadName" default="" tag="input"/> | |<<lingo UserName>> |<$edit-text tiddler="$:/UploadName" default="" tag="input"/> |
|<<lingo Password>> |<$password name="upload"/> | |<<lingo Password>> |<$password name="upload"/> |
|<<lingo Backups>> |<<siteLink backup>> |
|<<lingo ControlPanel>> |<<siteLink controlpanel>> |
''<<lingo Advanced/Heading>>'' ''<<lingo Advanced/Heading>>''

View File

@ -8,6 +8,7 @@ title: $:/core/ui/EditTemplate/body/editor
tabindex={{$:/config/EditTabIndex}} tabindex={{$:/config/EditTabIndex}}
focus={{{ [{$:/config/AutoFocus}match[text]then[true]] ~[[false]] }}} focus={{{ [{$:/config/AutoFocus}match[text]then[true]] ~[[false]] }}}
cancelPopups="yes" cancelPopups="yes"
fileDrop={{{ [{$:/config/DragAndDrop/Enable}match[no]] :else[subfilter{$:/config/Editor/EnableImportFilter}then[yes]else[no]] }}}
><$set ><$set
@ -30,4 +31,12 @@ title: $:/core/ui/EditTemplate/body/editor
tiddler="$:/core/ui/EditTemplate/body/toolbar/button" tiddler="$:/core/ui/EditTemplate/body/toolbar/button"
mode="inline" mode="inline"
/></$reveal></$list></$set></$edit> /></$reveal></$list><$list
filter="[all[shadows+tiddlers]tag[$:/tags/EditorTools]!has[draft.of]]"
><$list
filter={{!!condition}}
variable="list-condition"
><$transclude/>
</$list></$list></$set></$edit>

View File

@ -35,22 +35,23 @@ title: $:/core/ui/EditTemplate/body/toolbar/button
filter="[all[current]!has[dropdown]]" filter="[all[current]!has[dropdown]]"
variable="no-dropdown" variable="no-dropdown"
><$button ><$set name=disabled filter={{!!condition-disabled}}><$button
class="tc-btn-invisible $(buttonClasses)$" class="tc-btn-invisible $(buttonClasses)$"
tooltip=<<tooltip-text>> tooltip=<<tooltip-text>>
actions={{!!actions}} actions={{!!actions}}
disabled=<<disabled>>
><span ><span
data-tw-keyboard-shortcut={{!!shortcuts}} data-tw-keyboard-shortcut={{{ [<disabled>match[yes]then[]else{!!shortcuts}] }}}
/><<toolbar-button-icon>><$transclude /><<toolbar-button-icon>><$transclude
tiddler=<<currentTiddler>> tiddler=<<currentTiddler>>
field="text" field="text"
/></$button></$list><$list /></$button></$set></$list><$list
filter="[all[current]has[dropdown]]" filter="[all[current]has[dropdown]]"
variable="dropdown" variable="dropdown"
@ -60,24 +61,25 @@ title: $:/core/ui/EditTemplate/body/toolbar/button
name="dropdown-state" name="dropdown-state"
value=<<qualify "$:/state/EditorToolbarDropdown">> value=<<qualify "$:/state/EditorToolbarDropdown">>
><$button ><$set name=disabled filter={{!!condition-disabled}}><$button
popup=<<dropdown-state>> popup=<<dropdown-state>>
class="tc-popup-keep tc-btn-invisible $(buttonClasses)$" class="tc-popup-keep tc-btn-invisible $(buttonClasses)$"
selectedClass="tc-selected" selectedClass="tc-selected"
tooltip=<<tooltip-text>> tooltip=<<tooltip-text>>
actions={{!!actions}} actions={{!!actions}}
disabled=<<disabled>>
><span ><span
data-tw-keyboard-shortcut={{!!shortcuts}} data-tw-keyboard-shortcut={{{ [<disabled>match[yes]then[]else{!!shortcuts}] }}}
/><<toolbar-button-icon>><$transclude /><<toolbar-button-icon>><$transclude
tiddler=<<currentTiddler>> tiddler=<<currentTiddler>>
field="text" field="text"
/></$button><$reveal /></$button></$set><$reveal
state=<<dropdown-state>> state=<<dropdown-state>>
type="popup" type="popup"

View File

@ -5,6 +5,11 @@ tags: $:/tags/EditTemplate
\define config-visibility-title() \define config-visibility-title()
$:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$ $:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
\end \end
\define importFileActions()
<$action-popup $state=<<importState>> $coords="(0,0,0,0)" $floating="yes"/>
\end
<$list filter="[all[current]has[_canonical_uri]]"> <$list filter="[all[current]has[_canonical_uri]]">
<div class="tc-message-box"> <div class="tc-message-box">
@ -20,9 +25,8 @@ $:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
</$list> </$list>
<$list filter="[all[current]!has[_canonical_uri]]"> <$list filter="[all[current]!has[_canonical_uri]]">
<$vars importTitle=<<qualify $:/ImportImage>> importState=<<qualify $:/state/ImportImage>> >
<$reveal state="$:/state/showeditpreview" type="match" text="yes"> <$dropzone importTitle=<<importTitle>> autoOpenOnImport="no" contentTypesFilter={{$:/config/Editor/ImportContentTypesFilter}} class="tc-dropzone-editor" enable={{{ [{$:/config/DragAndDrop/Enable}match[no]] :else[subfilter{$:/config/Editor/EnableImportFilter}then[yes]else[no]] }}} filesOnly="yes" actions=<<importFileActions>> ><$reveal state="$:/state/showeditpreview" type="match" text="yes">
<div class="tc-tiddler-preview"> <div class="tc-tiddler-preview">
<$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/> <$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/>
@ -38,7 +42,6 @@ $:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
</div> </div>
</div> </div>
</$reveal> </$reveal>
<$reveal state="$:/state/showeditpreview" type="nomatch" text="yes"> <$reveal state="$:/state/showeditpreview" type="nomatch" text="yes">
@ -46,5 +49,6 @@ $:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
<$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/> <$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/>
</$reveal> </$reveal>
</$dropzone>
</$list> </$vars>
</$list>

View File

@ -84,7 +84,7 @@ $value={{{ [<newFieldValueTiddler>get[text]] }}}/>
<$fieldmangler> <$fieldmangler>
<div class="tc-edit-field-add"> <div class="tc-edit-field-add">
<em class="tc-edit tc-big-gap-right"> <em class="tc-edit tc-small-gap-right">
<<lingo Fields/Add/Prompt>> <<lingo Fields/Add/Prompt>>
</em> </em>
<$vars refreshTitle=<<qualify "$:/temp/fieldname/refresh">> storeTitle=<<newFieldNameInputTiddler>> searchListState=<<newFieldNameSelectionTiddler>>> <$vars refreshTitle=<<qualify "$:/temp/fieldname/refresh">> storeTitle=<<newFieldNameInputTiddler>> searchListState=<<newFieldNameSelectionTiddler>>>

View File

@ -23,7 +23,7 @@ tags: $:/tags/EditTemplate
<div class="tc-message-box"> <div class="tc-message-box">
{{$:/core/images/warning}} {{$:/language/EditTemplate/Title/Exists/Prompt}} {{$:/core/images/warning}} {{$:/language/EditTemplate/Title/Exists/Prompt}}: <$link to={{!!draft.title}} />
</div> </div>

View File

@ -7,7 +7,7 @@ first-search-filter: [all[shadows+tiddlers]prefix[$:/language/Docs/Types/]sort[d
\whitespace trim \whitespace trim
<$set name="refreshTitle" value=<<qualify "$:/temp/type-search/refresh">>> <$set name="refreshTitle" value=<<qualify "$:/temp/type-search/refresh">>>
<div class="tc-edit-type-selector-wrapper"> <div class="tc-edit-type-selector-wrapper">
<em class="tc-edit tc-big-gap-right"><<lingo Type/Prompt>></em> <em class="tc-edit tc-small-gap-right"><<lingo Type/Prompt>></em>
<div class="tc-type-selector-dropdown-wrapper"> <div class="tc-type-selector-dropdown-wrapper">
<div class="tc-type-selector"><$fieldmangler> <div class="tc-type-selector"><$fieldmangler>
<$macrocall $name="keyboard-driven-input" tiddler=<<currentTiddler>> storeTitle=<<typeInputTiddler>> refreshTitle=<<refreshTitle>> selectionStateTitle=<<typeSelectionTiddler>> field="type" tag="input" default="" placeholder={{$:/language/EditTemplate/Type/Placeholder}} focusPopup=<<qualify "$:/state/popup/type-dropdown">> class="tc-edit-typeeditor tc-edit-texteditor tc-popup-handle" tabindex={{$:/config/EditTabIndex}} focus={{{ [{$:/config/AutoFocus}match[type]then[true]] ~[[false]] }}} cancelPopups="yes" configTiddlerFilter="[[$:/core/ui/EditTemplate/type]]" inputCancelActions=<<input-cancel-actions>>/><$button popup=<<qualify "$:/state/popup/type-dropdown">> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button><$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>{{$:/core/images/delete-button}}<$action-deletetiddler $filter="[<storeTitle>] [<refreshTitle>] [<selectionStateTitle>]"/></$button> <$macrocall $name="keyboard-driven-input" tiddler=<<currentTiddler>> storeTitle=<<typeInputTiddler>> refreshTitle=<<refreshTitle>> selectionStateTitle=<<typeSelectionTiddler>> field="type" tag="input" default="" placeholder={{$:/language/EditTemplate/Type/Placeholder}} focusPopup=<<qualify "$:/state/popup/type-dropdown">> class="tc-edit-typeeditor tc-edit-texteditor tc-popup-handle" tabindex={{$:/config/EditTabIndex}} focus={{{ [{$:/config/AutoFocus}match[type]then[true]] ~[[false]] }}} cancelPopups="yes" configTiddlerFilter="[[$:/core/ui/EditTemplate/type]]" inputCancelActions=<<input-cancel-actions>>/><$button popup=<<qualify "$:/state/popup/type-dropdown">> class="tc-btn-invisible tc-btn-dropdown tc-small-gap" tooltip={{$:/language/EditTemplate/Type/Dropdown/Hint}} aria-label={{$:/language/EditTemplate/Type/Dropdown/Caption}}>{{$:/core/images/down-arrow}}</$button><$button message="tm-remove-field" param="type" class="tc-btn-invisible tc-btn-icon" tooltip={{$:/language/EditTemplate/Type/Delete/Hint}} aria-label={{$:/language/EditTemplate/Type/Delete/Caption}}>{{$:/core/images/delete-button}}<$action-deletetiddler $filter="[<storeTitle>] [<refreshTitle>] [<selectionStateTitle>]"/></$button>

View File

@ -0,0 +1,41 @@
title: $:/core/ui/EditorToolbar/file-import
tags: $:/tags/EditorTools
condition: [<targetTiddler>!has[type]] [<targetTiddler>type[text/vnd.tiddlywiki]]
\define lingo-base() $:/language/Import/
\define closePopupActions()
<$action-deletetiddler $filter="[title<importState>] [title<importTitle>]"/>
\end
\define replacement-text-image() [img[$title$]]
\define replacement-text-file() [[$title$]]
\define postImportActions()
<$list filter="[<importTitle>links[]] :reduce[get[type]prefix[image]then<replacement-text-image>else<replacement-text-file>search-replace[$title$],<currentTiddler>addprefix<accumulator>]" variable="imageTitle">
<$action-sendmessage
$message="tm-edit-text-operation"
$param="insert-text"
text=<<imageTitle>>
/>
</$list>
<<closePopupActions>>
\end
\define buttons()
<$button class="tc-btn-invisible" actions=<<closePopupActions>> ><<lingo Listing/Cancel/Caption>></$button>
<$button class="tc-btn-invisible" message="tm-perform-import" param=<<importTitle>> actions=<<postImportActions>> ><<lingo Listing/Import/Caption>></$button>
\end
<$reveal type="popup" state=<<importState>> tag="div" class="tc-editor-importpopup">
<div class="tc-editor-import">
<$list filter="[<importTitle>field:plugin-type[import]]">
<h2><<lingo Editor/Import/Heading>></h2>
<$tiddler tiddler=<<importTitle>>>
{{||$:/core/ui/ImportListing}}
<<buttons>>
</$tiddler>
</$list>
</div>
</$reveal>

View File

@ -0,0 +1,4 @@
title: $:/config/Editor/EnableImportFilter
type: text/vnd.tiddlywiki
[all[current]type[text/vnd.tiddlywiki]] [all[current]!has[type]]

View File

@ -0,0 +1,4 @@
title: $:/config/Editor/ImportContentTypesFilter
type: text/vnd.tiddlywiki
[prefix[image/]]

View File

@ -7,9 +7,9 @@ $:/core/images/storyview-$(storyview)$
<div class="tc-chooser tc-viewswitcher"> <div class="tc-chooser tc-viewswitcher">
<$list filter="[storyviews[]]" variable="storyview"> <$list filter="[storyviews[]]" variable="storyview">
<$set name="cls" filter="[<storyview>prefix{$:/view}]" value="tc-chooser-item tc-chosen" emptyValue="tc-chooser-item"><div class=<<cls>>> <$set name="cls" filter="[<storyview>prefix{$:/view}]" value="tc-chooser-item tc-chosen" emptyValue="tc-chooser-item"><div class=<<cls>>>
<$link to=<<storyview>>><$transclude tiddler=<<icon>>/><$text text=<<storyview>>/></$link> <$button tag="a" class="tc-tiddlylink tc-btn-invisible" to=<<storyview>>><$transclude tiddler=<<icon>>/><$text text=<<storyview>>/></$button>
</div> </div>
</$set> </$set>
</$list> </$list>
</div> </div>
</$linkcatcher> </$linkcatcher>

View File

@ -11,16 +11,140 @@ type: text/vnd.tiddlywiki
//[[See GitHub for detailed change history of this release|https://github.com/Jermolene/TiddlyWiki5/compare/v5.1.23...master]]// //[[See GitHub for detailed change history of this release|https://github.com/Jermolene/TiddlyWiki5/compare/v5.1.23...master]]//
<div class="doc-plain-list"> ! Performance Improvements
! Improvements * <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/5380">> (and again [[here|https://github.com/Jermolene/TiddlyWiki5/pull/5488]]) the efficiency of the linked list implementation
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/5362">> [[all Operator]] to use new linked list implementation
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/5369">> [[links Operator]] to use new linked list implementation
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5383">> unneeded escaping of double quotes in tiddler DIVs inside single file wikis (saving about 10% from the size of empty.html)
* ! Usability Improvements
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/commit/a360adbba924d222c5b55709133c18890c04398d">> dropzone size when story river is empty
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5326">> fill colour for "remove tag" button
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5370">> page title so that the separating em-dash is only used if the site subtitle is present
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5397">> broken aria-label in $:/PaletteManager
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/5451">> macro calls to use the same parser as that used for widget attributes
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/commit/89546b3357b0696a7047e6915bd6cd137b589de6">> a hidden setting to control sandboxing of tiddlers of type `text/html`
* <<.link-badge-updated "https://github.com/Jermolene/TiddlyWiki5/commit/caec6bc3fea9155eb2b0aae64d577c565dd7b088">> SVG optimiser script
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/commit/c325380231a8c592a6e51d4498c1e6c3a241b539">> plus/minus SVG icons: <<.icon $:/core/images/plus-button>> and <<.icon $:/core/images/minus-button>>
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/5294">> support for [[dynamic toolbar buttons|How to create dynamic editor toolbar buttons]]
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/5612">> [[average Operator]], [[median Operator]], [[variance Operator]] and [[standard-deviation Operator]] for calculating the arithmetic mean of a list of numbers
* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/commit/cf56a17f28f1e44dcb62c5e161be4ac29e27c3f2">> unusedtitle macro to use the prefix parameter
! Hackability Improvements
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/commit/9eda02868f21e9dd1733ffe26352bd7ac96285b4">> new MessageCatcherWidget
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/commit/d25e540dd2f0decf61c52fdc665a28a5dfeda93f">> support for `image/vnd.microsoft.icon` content type
! Widget Improvements
* <<.link-badge-modified "https://github.com/Jermolene/TiddlyWiki5/commit/b9647b2c48152dac069a1099a0822b32375a66cf">> [[FieldManglerWidget]] to ensure it doesn't propogate events that it traps
* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/pull/5597">> [[DropzoneWidget]] to optionally invoke actions after the `tm-import-tiddlers` message has been sent, and to specify an optional `contentTypesFilter` which determines which content types are accepted by the dropzone.
* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/pull/5611">> [[ListWidget]] with `index` attribute and here (and [[here| https://github.com/Jermolene/TiddlyWiki5/commit/4a99e0cc7d4a6b9e7071c0b2a9a0f63c3c7d2492]])
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5635">> [[SelectWidget]] refreshing
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/5252">> support for suffixes to filter run prefixes
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/5653">> :sort filter run prefix
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5644">> [[ActionListopsWidget]] bug by avoiding stitching together filter expressions for the original list values
* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/commit/07caa16e8714afe9a64eb202375e4a2f95da1508">> [[DropzoneWidget]] to also use the specified deserializer for strings either dropped or pasted on to the dropzone
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/commit/44df6fe52f79bee88357afb4fc3d6f4800aa6dde">> issue with widget not being available to filter operator
* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/commit/3f986861538a3cc5c3c6da578b45d0d9138a6b2b">> [[ActionPopupWidget]] to create floating popups that must be manually cleared
! Client-server Improvements
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/commit/e96a54c7531a2d9e07745e27d2015d8d7d09588f">> crash running in client server configuration when 'etag' header is missing
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5423">> blank favicon when using lazily loaded images
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/issues/4461">> web server issue with custom path prefix and basic authentication
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/issues/5366">> crash on Node.js with ActionSetFieldWidget when type field is given a value upon new tiddler creation
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5329">> issue with tiddler titles that already end in the required extension
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5465">> several consistency issues with the filesystem plugin
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/issues/5483">> issue with encoding of $:/config/OriginalTiddlerPaths outside the wiki folder
* <<.link-badge-updated "https://github.com/Jermolene/TiddlyWiki5/pull/5628">> the TiddlySpot Saver settings form
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/5638">> 401 and 403 error messages for PUT saver
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/commit/d8ac00a10856b1b64311b8e0496344d5b0c1b987">> fixed crash if browser doesn't support Server Sent Events
! Plugin Improvements
!! [[XLSX Utilities Plugin]]
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/issues/5400">> crash with the XLSX Utils plugin
!! [[KaTeX Plugin]]
* <<.link-badge-updated "https://github.com/Jermolene/TiddlyWiki5/commit/f2aba29d94cddcff6d7c188c4aa0b668995d8002">> to KaTeX v0.12.0
!! [[Freelinks Plugin]]
* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/commit/0ed32fded996826a59832d7a7555bb16c4a57864">> the Freelinks plugin with a filter to determine which tiddlers can be the targets of freelinks
!! [[Menubar Plugin]]
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/discussions/5533">> Menu plugin to support optional ''dropdown-position'' field
!! [[BibTeX Plugin]]
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/issues/5581">> BibTeX Plugin to report errors more sensibly
* <<.link-badge-modified "https://github.com/Jermolene/TiddlyWiki5/commit/953fb9f237ad78e409c03d4b29b9854d8abf6cdf">> BibTex Plugin to force fieldnames to be lowercase
! Developer Experience Improvements
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/pull/5279">> support for [[server sent events|https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events]]
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/5382">> the widget subclassing mechanism to work with widgets that add event listeners in their constructor
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/pull/5377">> the Jasmine test suite output
* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/commit/9f9ce6595b08032a602981f82940ca113cff8211">> wikitext parser with a subclassing mechanism
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/commit/ef76349c37662e9706acfffc2c2edb51a920183d">> added support for ''utils-browser'' modules
! Translation improvements
* <<.link-badge-improved>> Chinese translations
! Other Bug Fixes
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5376">> issue with [[lookup Operator]] returning "undefined" under some circumstances
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/issues/5396">> crash with unterminated wikitext comments
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5453">> tiddler info area content bleeding on close animation
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5452">> inline/block widget parsing glitch
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5401">> runaway regexp when parsing filters
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5440">> right margin of tag pill when used outside of the tags wrapper
* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/pull/5455">> upload saver to optionally work without a username or password
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/commit/5e4430dbf9ff66d9a18fbdf3005abcd716efc07d">> RadioWidget to refresh selectively, and to use the ''checked'' attribute correctly
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5542">> "invert" option of `wiki.search()` method
* <<.link-badge-added "https://github.com/Jermolene/TiddlyWiki5/commit/e157d16b724172f752da0ff714847e0c0ca9664d">> ''data-tag-title'' attribute to tag pills
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/commit/7b1a0c6e6a8bd2d3badf8766af0cd3f5f7ac5ec8">> ES5 compatibility issue
* <<.link-badge-extended "https://github.com/Jermolene/TiddlyWiki5/commit/a38dc1730010c6a2b6a011aff4818bbc67c04055">> RenderCommand to allow multiple variables to be passed
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/commit/226df2ad7d2978d3d7400d94767a0503e495cf98">> exporting of tiddlers that begin and end with double quotes
* <<.link-badge-improved "https://github.com/Jermolene/TiddlyWiki5/commit/d56e8764a1f02a214df5da1cc95191be2da2491b">> accessibility of button widget when controlling a popup
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/commit/d6ea369f5ef9d3092a360a4286a99902df37782b">> EditTextWidget to use default text for missing fields
* <<.link-badge-fixed "https://github.com/Jermolene/TiddlyWiki5/pull/5552">> css-escape-polyfill to work under Node.js
! Contributors
[[@Jermolene|https://github.com/Jermolene]] would like to thank the contributors to this release who have generously given their time to help improve TiddlyWiki: [[@Jermolene|https://github.com/Jermolene]] would like to thank the contributors to this release who have generously given their time to help improve TiddlyWiki:
* <<contributor Jermolene>> * <<contributor Arlen22
* <<contributor BlueGreenMagick>>
</div> * <<contributor BramChen>>
* <<contributor BurningTreeC>>
* <<contributor cdruan>>
* <<contributor clutterstack>>
* <<contributor CodaCodr>>
* <<contributor dixonge>>
* <<contributor donmor>>
* <<contributor FlashSystems>>
* <<contributor flibbles>>
* <<contributor hoelzro>>
* <<contributor jeremyredhead>>
* <<contributor joebordes>>
* <<contributor joshuafontany>>
* <<contributor kookma>>
* <<contributor laomaiweng>>
* <<contributor leehawk787>>
* <<contributor morosanuae>>
* <<contributor neumark>>
* <<contributor NicolasPetton>>
* <<contributor OdinJorna>>
* <<contributor pmario>>
* <<contributor saqimtias>>
* <<contributor simonbaird>>
* <<contributor slaymaker1907>>
* <<contributor twMat>>

View File

@ -750,6 +750,7 @@ function runTests(wiki) {
rootWidget.setVariable("sort1","[length[]]"); rootWidget.setVariable("sort1","[length[]]");
rootWidget.setVariable("sort2","[get[text]else[]length[]]"); rootWidget.setVariable("sort2","[get[text]else[]length[]]");
rootWidget.setVariable("sort3","[{!!value}divide{!!cost}]"); rootWidget.setVariable("sort3","[{!!value}divide{!!cost}]");
rootWidget.setVariable("sort4","[{!!title}]");
expect(wiki.filterTiddlers("[sortsub:number<sort1>]",anchorWidget).join(",")).toBe("one,hasList,TiddlerOne,has filter,$:/TiddlerTwo,Tiddler Three,$:/ShadowPlugin,a fourth tiddler,filter regexp test"); expect(wiki.filterTiddlers("[sortsub:number<sort1>]",anchorWidget).join(",")).toBe("one,hasList,TiddlerOne,has filter,$:/TiddlerTwo,Tiddler Three,$:/ShadowPlugin,a fourth tiddler,filter regexp test");
expect(wiki.filterTiddlers("[!sortsub:number<sort1>]",anchorWidget).join(",")).toBe("filter regexp test,a fourth tiddler,$:/ShadowPlugin,$:/TiddlerTwo,Tiddler Three,TiddlerOne,has filter,hasList,one"); expect(wiki.filterTiddlers("[!sortsub:number<sort1>]",anchorWidget).join(",")).toBe("filter regexp test,a fourth tiddler,$:/ShadowPlugin,$:/TiddlerTwo,Tiddler Three,TiddlerOne,has filter,hasList,one");
expect(wiki.filterTiddlers("[sortsub:string<sort1>]",anchorWidget).join(",")).toBe("TiddlerOne,has filter,$:/TiddlerTwo,Tiddler Three,$:/ShadowPlugin,a fourth tiddler,filter regexp test,one,hasList"); expect(wiki.filterTiddlers("[sortsub:string<sort1>]",anchorWidget).join(",")).toBe("TiddlerOne,has filter,$:/TiddlerTwo,Tiddler Three,$:/ShadowPlugin,a fourth tiddler,filter regexp test,one,hasList");
@ -759,6 +760,7 @@ function runTests(wiki) {
expect(wiki.filterTiddlers("[sortsub:string<sort2>]",anchorWidget).join(",")).toBe("one,TiddlerOne,hasList,has filter,$:/ShadowPlugin,a fourth tiddler,Tiddler Three,$:/TiddlerTwo,filter regexp test"); expect(wiki.filterTiddlers("[sortsub:string<sort2>]",anchorWidget).join(",")).toBe("one,TiddlerOne,hasList,has filter,$:/ShadowPlugin,a fourth tiddler,Tiddler Three,$:/TiddlerTwo,filter regexp test");
expect(wiki.filterTiddlers("[!sortsub:string<sort2>]",anchorWidget).join(",")).toBe("filter regexp test,$:/TiddlerTwo,Tiddler Three,a fourth tiddler,$:/ShadowPlugin,has filter,hasList,TiddlerOne,one"); expect(wiki.filterTiddlers("[!sortsub:string<sort2>]",anchorWidget).join(",")).toBe("filter regexp test,$:/TiddlerTwo,Tiddler Three,a fourth tiddler,$:/ShadowPlugin,has filter,hasList,TiddlerOne,one");
expect(wiki.filterTiddlers("[[TiddlerOne]] [[$:/TiddlerTwo]] [[Tiddler Three]] [[a fourth tiddler]] +[!sortsub:number<sort3>]",anchorWidget).join(",")).toBe("$:/TiddlerTwo,Tiddler Three,TiddlerOne,a fourth tiddler"); expect(wiki.filterTiddlers("[[TiddlerOne]] [[$:/TiddlerTwo]] [[Tiddler Three]] [[a fourth tiddler]] +[!sortsub:number<sort3>]",anchorWidget).join(",")).toBe("$:/TiddlerTwo,Tiddler Three,TiddlerOne,a fourth tiddler");
expect(wiki.filterTiddlers("a1 a10 a2 a3 b10 b3 b1 c9 c11 c1 +[sortsub:alphanumeric<sort4>]",anchorWidget).join(",")).toBe("a1,a2,a3,a10,b1,b3,b10,c1,c9,c11");
}); });
it("should handle the toggle operator", function() { it("should handle the toggle operator", function() {
@ -796,6 +798,21 @@ function runTests(wiki) {
expect(wiki.filterTiddlers("[[12]pad[9],[abc]]").join(",")).toBe("abcabca12"); expect(wiki.filterTiddlers("[[12]pad[9],[abc]]").join(",")).toBe("abcabca12");
expect(wiki.filterTiddlers("[[12]pad:suffix[9],[abc]]").join(",")).toBe("12abcabca"); expect(wiki.filterTiddlers("[[12]pad:suffix[9],[abc]]").join(",")).toBe("12abcabca");
}); });
it("should handle the escapecss operator", function() {
expect(wiki.filterTiddlers("[[Hello There]escapecss[]]").join(",")).toBe("Hello\\ There");
expect(wiki.filterTiddlers('\'"Reveal.js" by Devin Weaver[1]\' +[escapecss[]]').join(",")).toBe('\\"Reveal\\.js\\"\\ by\\ Devin\\ Weaver\\[1\\]');
expect(wiki.filterTiddlers(".foo#bar (){} '--a' 0 \0 +[escapecss[]]").join(",")).toBe("\\.foo\\#bar,\\(\\)\\{\\},--a,\\30 ,\ufffd");
expect(wiki.filterTiddlers("'' +[escapecss[]]").join(",")).toBe("");
expect(wiki.filterTiddlers("1234 +[escapecss[]]").join(",")).toBe("\\31 234");
expect(wiki.filterTiddlers("'-25' +[escapecss[]]").join(",")).toBe("-\\32 5");
expect(wiki.filterTiddlers("'-' +[escapecss[]]").join(",")).toBe("\\-");
});
it("should handle the deserializers operator", function() {
expect(wiki.filterTiddlers("[deserializers[]]").join(",")).toBe("application/javascript,application/json,application/x-tiddler,application/x-tiddler-html-div,application/x-tiddlers,text/css,text/html,text/plain");
});
} }
}); });

View File

@ -18,6 +18,199 @@ describe("general filter prefix tests", function() {
var results = wiki.filterTiddlers("[tag[A]] :nonexistent[tag[B]]"); var results = wiki.filterTiddlers("[tag[A]] :nonexistent[tag[B]]");
expect(results).toEqual(["Filter Error: Unknown prefix for filter run"]); expect(results).toEqual(["Filter Error: Unknown prefix for filter run"]);
}); });
// Test filter run prefix parsing
it("should parse filter run prefix suffixes", function() {
// two runs, one with a named prefix but no suffix
expect($tw.wiki.parseFilter("[[Sparkling water]tags[]] :intersection[[Red wine]tags[]]")).toEqual(
[
{
"prefix": "",
"operators": [
{
"operator": "title",
"operands": [
{
"text": "Sparkling water"
}
]
},
{
"operator": "tags",
"operands": [
{
"text": ""
}
]
}
]
},
{
"prefix": ":intersection",
"operators": [
{
"operator": "title",
"operands": [
{
"text": "Red wine"
}
]
},
{
"operator": "tags",
"operands": [
{
"text": ""
}
]
}
],
"namedPrefix": "intersection"
}
]
);
// named prefix with no suffix
expect($tw.wiki.parseFilter(":reduce[multiply<accumulator>]")).toEqual(
[
{
"prefix": ":reduce",
"operators": [
{
"operator": "multiply",
"operands": [
{
"variable": true,
"text": "accumulator"
}
]
}
],
"namedPrefix": "reduce"
}
]
);
//named prefix with one simple suffix
expect($tw.wiki.parseFilter(":reduce:1[multiply<accumulator>]")).toEqual(
[
{
"prefix": ":reduce:1",
"operators": [
{
"operator": "multiply",
"operands": [
{
"variable": true,
"text": "accumulator"
}
]
}
],
"namedPrefix": "reduce",
"suffixes": [
[
"1"
]
]
}
]
);
//named prefix with two simple suffixes
expect($tw.wiki.parseFilter(":reduce:1:hello[multiply<accumulator>]")).toEqual(
[
{
"prefix": ":reduce:1:hello",
"operators": [
{
"operator": "multiply",
"operands": [
{
"variable": true,
"text": "accumulator"
}
]
}
],
"namedPrefix": "reduce",
"suffixes": [
[
"1"
],
[
"hello",
]
]
}
]
);
//named prefix with two rich (comma separated) suffixes
expect($tw.wiki.parseFilter(":reduce:1,one:hello,there[multiply<accumulator>]")).toEqual(
[
{
"prefix": ":reduce:1,one:hello,there",
"operators": [
{
"operator": "multiply",
"operands": [
{
"variable": true,
"text": "accumulator"
}
]
}
],
"namedPrefix": "reduce",
"suffixes": [
[
"1",
"one"
],
[
"hello",
"there"
]
]
}
]
);
// suffixes with spaces
expect($tw.wiki.parseFilter(":reduce: 1, one:hello, there [multiply<accumulator>]")).toEqual(
[
{
"prefix": ":reduce: 1, one:hello, there ",
"operators": [
{
"operator": "multiply",
"operands": [
{
"variable": true,
"text": "accumulator"
}
]
}
],
"namedPrefix": "reduce",
"suffixes": [
[
"1",
"one"
],
[
"hello",
"there"
]
]
}
]
);
});
}); });
describe("'reduce' and 'intersection' filter prefix tests", function() { describe("'reduce' and 'intersection' filter prefix tests", function() {
@ -60,7 +253,7 @@ describe("'reduce' and 'intersection' filter prefix tests", function() {
wiki.addTiddler({ wiki.addTiddler({
title: "Red wine", title: "Red wine",
tags: ["drinks", "wine", "textexample"], tags: ["drinks", "wine", "textexample"],
text: "This is some more text" text: "This is some more text!"
}); });
wiki.addTiddler({ wiki.addTiddler({
title: "Cheesecake", title: "Cheesecake",
@ -72,6 +265,26 @@ describe("'reduce' and 'intersection' filter prefix tests", function() {
tags: ["cakes", "food", "textexample"], tags: ["cakes", "food", "textexample"],
text: "This is even more text" text: "This is even more text"
}); });
wiki.addTiddler({
title: "Persian love cake",
tags: ["cakes"],
text: "An amazing cake worth the effort to make"
});
wiki.addTiddler({
title: "cheesecake",
tags: ["cakes"],
text: "Everyone likes cheescake"
});
wiki.addTiddler({
title: "chocolate cake",
tags: ["cakes"],
text: "lower case chocolate cake"
});
wiki.addTiddler({
title: "Pound cake",
tags: ["cakes","with tea"],
text: "Does anyone eat pound cake?"
});
it("should handle the :reduce filter prefix", function() { it("should handle the :reduce filter prefix", function() {
expect(wiki.filterTiddlers("[tag[shopping]] :reduce[get[quantity]add<accumulator>]").join(",")).toBe("22"); expect(wiki.filterTiddlers("[tag[shopping]] :reduce[get[quantity]add<accumulator>]").join(",")).toBe("22");
@ -80,6 +293,10 @@ describe("'reduce' and 'intersection' filter prefix tests", function() {
// Empty input should become empty output // Empty input should become empty output
expect(wiki.filterTiddlers("[tag[non-existent]] :reduce[get[price]multiply{!!quantity}add<accumulator>]").length).toBe(0); expect(wiki.filterTiddlers("[tag[non-existent]] :reduce[get[price]multiply{!!quantity}add<accumulator>]").length).toBe(0);
expect(wiki.filterTiddlers("[tag[non-existent]] :reduce[get[price]multiply{!!quantity}add<accumulator>] :else[[0]]").join(",")).toBe("0"); expect(wiki.filterTiddlers("[tag[non-existent]] :reduce[get[price]multiply{!!quantity}add<accumulator>] :else[[0]]").join(",")).toBe("0");
expect(wiki.filterTiddlers("[tag[non-existent]] :reduce:11,22[get[price]multiply{!!quantity}add<accumulator>] :else[[0]]").join(",")).toBe("0");
expect(wiki.filterTiddlers("[tag[non-existent]] :reduce:11[get[price]multiply{!!quantity}add<accumulator>] :else[[0]]").join(",")).toBe("0");
}); });
it("should handle the reduce operator", function() { it("should handle the reduce operator", function() {
@ -101,6 +318,28 @@ describe("'reduce' and 'intersection' filter prefix tests", function() {
expect(wiki.filterTiddlers("[tag[non-existent]reduce<add-price>else[0]]",anchorWidget).join(",")).toBe("0"); expect(wiki.filterTiddlers("[tag[non-existent]reduce<add-price>else[0]]",anchorWidget).join(",")).toBe("0");
}); });
it("should handle the average operator", function() {
expect(wiki.filterTiddlers("[tag[shopping]get[price]average[]]").join(",")).toBe("2.3575");
expect(parseFloat(wiki.filterTiddlers("[tag[food]get[price]average[]]").join(","))).toBeCloseTo(3.155);
});
it("should handle the median operator", function() {
expect(parseFloat(wiki.filterTiddlers("[tag[shopping]get[price]median[]]").join(","))).toBeCloseTo(1.99);
expect(parseFloat(wiki.filterTiddlers("[tag[food]get[price]median[]]").join(","))).toBeCloseTo(3.155);
});
it("should handle the variance operator", function() {
expect(parseFloat(wiki.filterTiddlers("[tag[shopping]get[price]variance[]]").join(","))).toBeCloseTo(2.92);
expect(parseFloat(wiki.filterTiddlers("[tag[food]get[price]variance[]]").join(","))).toBeCloseTo(3.367);
expect(wiki.filterTiddlers(" +[variance[]]").toString()).toBe("NaN");
});
it("should handle the standard-deviation operator", function() {
expect(parseFloat(wiki.filterTiddlers("[tag[shopping]get[price]standard-deviation[]]").join(","))).toBeCloseTo(1.71);
expect(parseFloat(wiki.filterTiddlers("[tag[food]get[price]standard-deviation[]]").join(","))).toBeCloseTo(1.835);
expect(wiki.filterTiddlers(" +[standard-deviation[]]").toString()).toBe("NaN");
});
it("should handle the :intersection prefix", function() { it("should handle the :intersection prefix", function() {
expect(wiki.filterTiddlers("[[Sparkling water]tags[]] :intersection[[Red wine]tags[]]").join(",")).toBe("drinks,textexample"); expect(wiki.filterTiddlers("[[Sparkling water]tags[]] :intersection[[Red wine]tags[]]").join(",")).toBe("drinks,textexample");
expect(wiki.filterTiddlers("[[Brownies]tags[]] :intersection[[Chocolate Cake]tags[]]").join(",")).toBe("food"); expect(wiki.filterTiddlers("[[Brownies]tags[]] :intersection[[Chocolate Cake]tags[]]").join(",")).toBe("food");
@ -116,11 +355,25 @@ describe("'reduce' and 'intersection' filter prefix tests", function() {
rootWidget.makeChildWidgets(); rootWidget.makeChildWidgets();
var anchorWidget = rootWidget.children[0]; var anchorWidget = rootWidget.children[0];
rootWidget.setVariable("larger-than-18","[get[text]length[]compare:integer:gteq[18]]"); rootWidget.setVariable("larger-than-18","[get[text]length[]compare:integer:gteq[18]]");
rootWidget.setVariable("nr","18");
rootWidget.setVariable("larger-than-18-with-var","[get[text]length[]compare:integer:gteq<nr>]");
expect(wiki.filterTiddlers("[tag[textexample]] :filter[get[text]length[]compare:integer:gteq[18]]",anchorWidget).join(",")).toBe("Red wine,Cheesecake,Chocolate Cake"); expect(wiki.filterTiddlers("[tag[textexample]] :filter[get[text]length[]compare:integer:gteq[18]]",anchorWidget).join(",")).toBe("Red wine,Cheesecake,Chocolate Cake");
expect(wiki.filterTiddlers("[tag[textexample]]",anchorWidget).join(",")).toBe("Sparkling water,Red wine,Cheesecake,Chocolate Cake"); expect(wiki.filterTiddlers("[tag[textexample]]",anchorWidget).join(",")).toBe("Sparkling water,Red wine,Cheesecake,Chocolate Cake");
expect(wiki.filterTiddlers("[tag[textexample]filter<larger-than-18>]",anchorWidget).join(",")).toBe("Red wine,Cheesecake,Chocolate Cake"); expect(wiki.filterTiddlers("[tag[textexample]filter<larger-than-18>]",anchorWidget).join(",")).toBe("Red wine,Cheesecake,Chocolate Cake");
}) expect(wiki.filterTiddlers("[tag[textexample]filter<larger-than-18-with-var>]",anchorWidget).join(",")).toBe("Red wine,Cheesecake,Chocolate Cake");
});
it("should handle the :sort prefix", function() {
expect(wiki.filterTiddlers("a1 a10 a2 a3 b10 b3 b1 c9 c11 c1 :sort:alphanumeric[{!!title}]").join(",")).toBe("a1,a2,a3,a10,b1,b3,b10,c1,c9,c11");
expect(wiki.filterTiddlers("a1 a10 a2 a3 b10 b3 b1 c9 c11 c1 :sort:alphanumeric:reverse[{!!title}]").join(",")).toBe("c11,c9,c1,b10,b3,b1,a10,a3,a2,a1");
expect(wiki.filterTiddlers("[tag[shopping]] :sort:number:[get[price]]").join(",")).toBe("Milk,Chick Peas,Rice Pudding,Brownies");
expect(wiki.filterTiddlers("[tag[textexample]] :sort:number:[get[text]length[]]").join(",")).toBe("Sparkling water,Chocolate Cake,Red wine,Cheesecake");
expect(wiki.filterTiddlers("[tag[textexample]] :sort:number:reverse[get[text]length[]]").join(",")).toBe("Cheesecake,Red wine,Chocolate Cake,Sparkling water");
expect(wiki.filterTiddlers("[tag[notatag]] :sort:number[get[price]]").join(",")).toBe("");
expect(wiki.filterTiddlers("[tag[cakes]] :sort:string[{!!title}]").join(",")).toBe("Cheesecake,cheesecake,Chocolate Cake,chocolate cake,Persian love cake,Pound cake");
expect(wiki.filterTiddlers("[tag[cakes]] :sort:string:casesensitive[{!!title}]").join(",")).toBe("Cheesecake,Chocolate Cake,Persian love cake,Pound cake,cheesecake,chocolate cake");
expect(wiki.filterTiddlers("[tag[cakes]] :sort:string:casesensitive,reverse[{!!title}]").join(",")).toBe("chocolate cake,cheesecake,Pound cake,Persian love cake,Chocolate Cake,Cheesecake");
});
}); });
})(); })();

View File

@ -350,6 +350,123 @@ describe("Widget module", function() {
expect(wrapper.children[0].children[4].sequenceNumber).toBe(5); expect(wrapper.children[0].children[4].sequenceNumber).toBe(5);
}); });
it("should deal with the list widget using a counter variable", function() {
var wiki = new $tw.Wiki();
// Add some tiddlers
wiki.addTiddlers([
{title: "TiddlerOne", text: "Jolly Old World"},
{title: "TiddlerTwo", text: "Worldly Old Jelly"},
{title: "TiddlerThree", text: "Golly Gosh"},
{title: "TiddlerFour", text: "Lemon Squash"}
]);
// Construct the widget node
var text = "<$list counter='index'><$view field='text'/><$text text=<<index>>/><$text text=<<index-first>>/><$text text=<<index-last>>/></$list>";
var widgetNode = createWidgetNode(parseText(text,wiki),wiki);
// Render the widget node to the DOM
var wrapper = renderWidgetNode(widgetNode);
// Test the rendering
expect(wrapper.innerHTML).toBe("<p>Lemon Squash1yesnoJolly Old World2nonoGolly Gosh3nonoWorldly Old Jelly4noyes</p>");
// Test the sequence numbers in the DOM
expect(wrapper.sequenceNumber).toBe(0);
expect(wrapper.children[0].sequenceNumber).toBe(1);
expect(wrapper.children[0].children[0].sequenceNumber).toBe(2);
expect(wrapper.children[0].children[1].sequenceNumber).toBe(3);
expect(wrapper.children[0].children[2].sequenceNumber).toBe(4);
expect(wrapper.children[0].children[3].sequenceNumber).toBe(5);
expect(wrapper.children[0].children[4].sequenceNumber).toBe(6);
expect(wrapper.children[0].children[5].sequenceNumber).toBe(7);
expect(wrapper.children[0].children[6].sequenceNumber).toBe(8);
expect(wrapper.children[0].children[7].sequenceNumber).toBe(9);
expect(wrapper.children[0].children[8].sequenceNumber).toBe(10);
expect(wrapper.children[0].children[9].sequenceNumber).toBe(11);
expect(wrapper.children[0].children[10].sequenceNumber).toBe(12);
expect(wrapper.children[0].children[11].sequenceNumber).toBe(13);
expect(wrapper.children[0].children[12].sequenceNumber).toBe(14);
expect(wrapper.children[0].children[13].sequenceNumber).toBe(15);
expect(wrapper.children[0].children[14].sequenceNumber).toBe(16);
expect(wrapper.children[0].children[15].sequenceNumber).toBe(17);
// Add another tiddler
wiki.addTiddler({title: "TiddlerFive", text: "Jalapeno Peppers"});
// Refresh
refreshWidgetNode(widgetNode,wrapper,["TiddlerFive"]);
// Test the refreshing
expect(wrapper.innerHTML).toBe("<p>Jalapeno Peppers1yesnoLemon Squash2nonoJolly Old World3nonoGolly Gosh4nonoWorldly Old Jelly5noyes</p>");
// Test the sequence numbers in the DOM
expect(wrapper.sequenceNumber).toBe(0);
expect(wrapper.children[0].sequenceNumber).toBe(1);
expect(wrapper.children[0].children[0].sequenceNumber).toBe(18);
expect(wrapper.children[0].children[1].sequenceNumber).toBe(19);
expect(wrapper.children[0].children[2].sequenceNumber).toBe(20);
expect(wrapper.children[0].children[3].sequenceNumber).toBe(21);
expect(wrapper.children[0].children[4].sequenceNumber).toBe(22);
expect(wrapper.children[0].children[5].sequenceNumber).toBe(23);
expect(wrapper.children[0].children[6].sequenceNumber).toBe(24);
expect(wrapper.children[0].children[7].sequenceNumber).toBe(25);
expect(wrapper.children[0].children[8].sequenceNumber).toBe(26);
expect(wrapper.children[0].children[9].sequenceNumber).toBe(27);
expect(wrapper.children[0].children[10].sequenceNumber).toBe(28);
expect(wrapper.children[0].children[11].sequenceNumber).toBe(29);
expect(wrapper.children[0].children[12].sequenceNumber).toBe(30);
expect(wrapper.children[0].children[13].sequenceNumber).toBe(31);
expect(wrapper.children[0].children[14].sequenceNumber).toBe(32);
expect(wrapper.children[0].children[15].sequenceNumber).toBe(33);
expect(wrapper.children[0].children[16].sequenceNumber).toBe(34);
expect(wrapper.children[0].children[17].sequenceNumber).toBe(35);
expect(wrapper.children[0].children[18].sequenceNumber).toBe(36);
expect(wrapper.children[0].children[19].sequenceNumber).toBe(37);
// Remove a tiddler
wiki.deleteTiddler("TiddlerThree");
// Refresh
refreshWidgetNode(widgetNode,wrapper,["TiddlerThree"]);
// Test the refreshing
expect(wrapper.innerHTML).toBe("<p>Jalapeno Peppers1yesnoLemon Squash2nonoJolly Old World3nonoWorldly Old Jelly4noyes</p>");
// Test the sequence numbers in the DOM
expect(wrapper.sequenceNumber).toBe(0);
expect(wrapper.children[0].sequenceNumber).toBe(1);
expect(wrapper.children[0].children[0].sequenceNumber).toBe(18);
expect(wrapper.children[0].children[1].sequenceNumber).toBe(19);
expect(wrapper.children[0].children[2].sequenceNumber).toBe(20);
expect(wrapper.children[0].children[3].sequenceNumber).toBe(21);
expect(wrapper.children[0].children[4].sequenceNumber).toBe(22);
expect(wrapper.children[0].children[5].sequenceNumber).toBe(23);
expect(wrapper.children[0].children[6].sequenceNumber).toBe(24);
expect(wrapper.children[0].children[7].sequenceNumber).toBe(25);
expect(wrapper.children[0].children[8].sequenceNumber).toBe(26);
expect(wrapper.children[0].children[9].sequenceNumber).toBe(27);
expect(wrapper.children[0].children[10].sequenceNumber).toBe(28);
expect(wrapper.children[0].children[11].sequenceNumber).toBe(29);
expect(wrapper.children[0].children[12].sequenceNumber).toBe(38);
expect(wrapper.children[0].children[13].sequenceNumber).toBe(39);
expect(wrapper.children[0].children[14].sequenceNumber).toBe(40);
expect(wrapper.children[0].children[15].sequenceNumber).toBe(41);
// Add it back a tiddler
wiki.addTiddler({title: "TiddlerThree", text: "Something"});
// Refresh
refreshWidgetNode(widgetNode,wrapper,["TiddlerThree"]);
// Test the refreshing
expect(wrapper.innerHTML).toBe("<p>Jalapeno Peppers1yesnoLemon Squash2nonoJolly Old World3nonoSomething4nonoWorldly Old Jelly5noyes</p>");
// Test the sequence numbers in the DOM
expect(wrapper.sequenceNumber).toBe(0);
expect(wrapper.children[0].sequenceNumber).toBe(1);
expect(wrapper.children[0].children[0].sequenceNumber).toBe(18);
expect(wrapper.children[0].children[1].sequenceNumber).toBe(19);
expect(wrapper.children[0].children[2].sequenceNumber).toBe(20);
expect(wrapper.children[0].children[3].sequenceNumber).toBe(21);
expect(wrapper.children[0].children[4].sequenceNumber).toBe(22);
expect(wrapper.children[0].children[5].sequenceNumber).toBe(23);
expect(wrapper.children[0].children[6].sequenceNumber).toBe(24);
expect(wrapper.children[0].children[7].sequenceNumber).toBe(25);
expect(wrapper.children[0].children[8].sequenceNumber).toBe(26);
expect(wrapper.children[0].children[9].sequenceNumber).toBe(27);
expect(wrapper.children[0].children[10].sequenceNumber).toBe(28);
expect(wrapper.children[0].children[11].sequenceNumber).toBe(29);
expect(wrapper.children[0].children[12].sequenceNumber).toBe(42);
expect(wrapper.children[0].children[13].sequenceNumber).toBe(43);
expect(wrapper.children[0].children[14].sequenceNumber).toBe(44);
expect(wrapper.children[0].children[15].sequenceNumber).toBe(45);
});
it("should deal with the list widget followed by other widgets", function() { it("should deal with the list widget followed by other widgets", function() {
var wiki = new $tw.Wiki(); var wiki = new $tw.Wiki();
// Add some tiddlers // Add some tiddlers
@ -585,6 +702,19 @@ describe("Widget module", function() {
expect(wrapper.innerHTML).toBe("<p>Don't forget me.</p>"); expect(wrapper.innerHTML).toBe("<p>Don't forget me.</p>");
}); });
/** Special case. \import should parse correctly, even if it's
* the only line in the tiddler. Technically doesn't cause a
* visual difference, but may affect plugins if it doesn't.
*/
it("should work when import pragma is standalone", function() {
var wiki = new $tw.Wiki();
var text = "\\import [prefix[XXX]]";
var parseTreeNode = parseText(text,wiki);
// Test the resulting parse tree node, since there is no
// rendering which may expose a problem.
expect(parseTreeNode.children[0].attributes.filter.value).toBe('[prefix[XXX]]');
});
/** This test reproduces issue #4504. /** This test reproduces issue #4504.
* *
* The importvariable widget was creating redundant copies into * The importvariable widget was creating redundant copies into

View File

@ -6,7 +6,7 @@ type: text/vnd.tiddlywiki
//These are personal reflections on the history and development of TiddlyWiki from JeremyRuston.// //These are personal reflections on the history and development of TiddlyWiki from JeremyRuston.//
//There is also a [[podcast|https://changelog.com/196/]] discussing TiddlyWiki's backstory.// //There is also a [[podcast from 2016|https://changelog.com/podcast/196]] discussing TiddlyWiki's backstory as well as a [[recording from 2021|https://twit.tv/shows/floss-weekly/episodes/620]].//
! Origins of TiddlyWiki ! Origins of TiddlyWiki

View File

@ -1,17 +1,59 @@
created: 20131101111400000 created: 20131101111400000
modified: 20190115165616599 modified: 20210402095728684
tags: Community tags: Community
title: Contributing title: Contributing
type: text/vnd.tiddlywiki type: text/vnd.tiddlywiki
We welcome contributions to the code and documentation of TiddlyWiki in several ways: Here we focus on contributions via GitHub Pull Requests but there are many other ways that anyone can help the TiddlyWiki project, such as [[reporting bugs|ReportingBugs]] or helping to [[improve our documentation|Improving TiddlyWiki Documentation]].
* ReportingBugs ! Rules for Pull Requests
* Helping to [[improve our documentation|Improving TiddlyWiki Documentation]]
* Contributing to the code via [[GitHub|https://github.com/Jermolene/TiddlyWiki5]]
** See https://tiddlywiki.com/dev for more details
There are other ways to [[help TiddlyWiki|HelpingTiddlyWiki]] too. PRs must meet these minimum requirements before they can be considered for merging:
* The material in the PR must be free of licensing restrictions. Which means that either:
** The author must hold the copyright in all of the material themselves
** The material must be licensed under a license compatible with TiddlyWiki's BSD license
* The author must sign the Contributors License Agreement (see below)
* Each PR should only make a single feature change
* The title of the PR should be 50 characters or less
* The title of the PR should be capitalised, and should not end with a period
* The title of the PR should be written in the imperative mood. See below
* Adequate explanation in the body of the PR for the motivation and implementation of the change. Focus on the //why// and //what//, rather than the //how//
* PRs must be self-contained. Although they can link to material elsewhere, everything needed to understand the intention of the PR should be included
* Documentation as appropriate for end-users or developers
* Observe the coding style
* Read the developers documentation
* Please open a consultation issue prior to investing time in making a large PR
!! Imperative Mood for PR Titles
The "imperative mood" means written as if giving a command or instruction. See [[this post|https://chris.beams.io/posts/git-commit/#imperative]] for more details, but the gist is that the title of the PR should make sense when used to complete the sentence "If applied, this commit will...". So for example, these are good PR titles:
* If applied, this commit will //update the contributing guidelines//
* If applied, this commit will //change css-escape-polyfill to a $tw.utils method//
* If applied, this commit will //make it easier to subclass the wikitext parser with a custom rule set//
These a poorly worded PR titles:
* ~~If applied, this commit will //edit text widgets should use default text for missing fields//~~
* ~~If applied, this commit will //signing the CLA//~~
* ~~If applied, this commit will //don't crash if options.event is missing//~~
PR titles may also include a short prefix to indicate the subsystem to which they apply. For example:
* //Menu plugin: Include menu text in aerial rotator//
! Commenting on Pull Requests
One of the principles of open source is that many pairs of eyes on the code can improve quality. So, we welcome comments and critiques of pending PRs. [[Conventional Comments|https://conventionalcomments.org]] has some techcniques to help make comments as constructive and actionable as possible. Notably, they recommend prefixing a comment with a label to clarify the intention:
|praise |Praises highlight something positive. Try to leave at least one of these comments per review. Do not leave false praise (which can actually be damaging). Do look for something to sincerely praise |
|nitpick |Nitpicks are small, trivial, but necessary changes. Distinguishing nitpick comments significantly helps direct the reader's attention to comments requiring more involvement |
|suggestion |Suggestions are specific requests to improve the subject under review. It is assumed that we all want to do what's best, so these comments are never dismissed as “mere suggestions”, but are taken seriously |
|issue |Issues represent user-facing problems. If possible, it's great to follow this kind of comment with a suggestion |
|question |Questions are appropriate if you have a potential concern but are not quite sure if it's relevant or not. Asking the author for clarification or investigation can lead to a quick resolution |
|thought |Thoughts represent an idea that popped up from reviewing. These comments are non-blocking by nature, but they are extremely valuable and can lead to more focused initiatives and mentoring opportunities |
|chore |Chores are simple tasks that must be done before the subject can be “officially” accepted. Usually, these comments reference some common process. Try to leave a link to the process description so that the reader knows how to resolve the chore |
! Contributor License Agreement ! Contributor License Agreement
@ -24,9 +66,3 @@ There are other ways to [[help TiddlyWiki|HelpingTiddlyWiki]] too.
--- ---
//The CLA documents used for this project were created using [[Harmony Project Templates|http://www.harmonyagreements.org]]. "HA-CLA-I-LIST Version 1.0" for "CLA-individual" and "HA-CLA-E-LIST Version 1.0" for "CLA-entity".// //The CLA documents used for this project were created using [[Harmony Project Templates|http://www.harmonyagreements.org]]. "HA-CLA-I-LIST Version 1.0" for "CLA-individual" and "HA-CLA-E-LIST Version 1.0" for "CLA-entity".//
!! Remarks
''If you do not own the copyright in the entire work of authorship'':
In this case, please clearly state so and provide links and any additional information that clarify under which license the rest of the code is distributed.

View File

@ -1,14 +0,0 @@
created: 20140315085406905
modified: 20210106151027120
tags: Tutorials
title: "Hosting TiddlyWiki5 on GoogleDrive" by Tony Ching
type: text/vnd.tiddlywiki
url: https://googledrive.com/host/0B51gSXixfJ2Qb0I4R2M4MWJVMlU
Tony Ching's quick guide for sharing TiddlyWiki with Google Drive.
{{!!url}}
<<<
Anyway your self-contained a non-linear personal web notebook can be hosted on Google Drive, a free cloud service from Google.com. Because TiddlyWiki5 now supports the Stanford Javascript Crypto Library (SJCL), you can encrypt your content from prying eyes (excluding the NSA of course)
<<<

View File

@ -1,5 +1,5 @@
created: 20140502213500000 created: 20140502213500000
modified: 20160622111355787 modified: 20210406131243532
tags: Features Concepts tags: Features Concepts
title: PermaLinks title: PermaLinks
type: text/vnd.tiddlywiki type: text/vnd.tiddlywiki
@ -32,7 +32,7 @@ If the target tiddler isn't present in the story list then it is automatically i
It is also possible to specify a story filter without specifying a target tiddler for navigation: It is also possible to specify a story filter without specifying a target tiddler for navigation:
<a href="https://tiddlywiki.com/#:[tags[task]]" rel="noopener noreferrer">~https://tiddlywiki.com/#:[tags[task]]</a> <a href="https://tiddlywiki.com/#:[tag[task]]" rel="noopener noreferrer">~https://tiddlywiki.com/#:[tag[task]]</a>
! About URL encoding ! About URL encoding

View File

@ -1,6 +1,6 @@
created: 20130822080600000 created: 20130822080600000
list: [[SystemTag: $:/tags/AboveStory]] [[SystemTag: $:/tags/AdvancedSearch]] [[SystemTag: $:/tags/AdvancedSearch/FilterButton]] [[SystemTag: $:/tags/Alert]] [[SystemTag: $:/tags/BelowStory]] [[SystemTag: $:/tags/ControlPanel]] [[SystemTag: $:/tags/ControlPanel/Advanced]] [[SystemTag: $:/tags/ControlPanel/Appearance]] [[SystemTag: $:/tags/ControlPanel/Info]] [[SystemTag: $:/tags/ControlPanel/Saving]] [[SystemTag: $:/tags/ControlPanel/Settings]] [[SystemTag: $:/tags/ControlPanel/Toolbars]] [[SystemTag: $:/tags/EditorToolbar]] [[SystemTag: $:/tags/EditPreview]] [[SystemTag: $:/tags/EditTemplate]] [[SystemTag: $:/tags/EditToolbar]] [[SystemTag: $:/tags/Exporter]] [[SystemTag: $:/tags/Filter]] [[SystemTag: $:/tags/Image]] [[SystemTag: $:/tags/ImportPreview]] [[SystemTag: $:/tags/KeyboardShortcut]] [[SystemTag: $:/tags/Layout]] [[SystemTag: $:/tags/Macro]] [[SystemTag: $:/tags/Macro/View]] [[SystemTag: $:/tags/Manager/ItemMain]] [[SystemTag: $:/tags/Manager/ItemSidebar]] [[SystemTag: $:/tags/MoreSideBar]] [[SystemTag: $:/tags/MoreSideBar/Plugins]] [[SystemTag: $:/tags/PageControls]] [[SystemTag: $:/tags/PageTemplate]] [[SystemTag: $:/tags/Palette]] [[SystemTag: $:/tags/PluginLibrary]] [[SystemTag: $:/tags/RawMarkup]] [[SystemTag: $:/tags/RawMarkupWikified]] [[SystemTag: $:/tags/RawMarkupWikified/BottomBody]] [[SystemTag: $:/tags/RawMarkupWikified/TopBody]] [[SystemTag: $:/tags/RawMarkupWikified/TopHead]] [[SystemTag: $:/tags/RawStaticContent]] [[SystemTag: $:/tags/RemoteAssetInfo]] [[SystemTag: $:/tags/SearchResults]] [[SystemTag: $:/tags/ServerConnection]] [[SystemTag: $:/tags/SideBar]] [[SystemTag: $:/tags/SideBarSegment]] [[SystemTag: $:/tags/StartupAction]] [[SystemTag: $:/tags/StartupAction/Browser]] [[SystemTag: $:/tags/StartupAction/Node]] [[SystemTag: $:/tags/Stylesheet]] [[SystemTag: $:/tags/TagDropdown]] [[SystemTag: $:/tags/TextEditor/Snippet]] [[SystemTag: $:/tags/TiddlerInfo]] [[SystemTag: $:/tags/TiddlerInfo/Advanced]] [[SystemTag: $:/tags/TiddlerInfoSegment]] [[SystemTag: $:/tags/ToolbarButtonStyle]] [[SystemTag: $:/tags/TopLeftBar]] [[SystemTag: $:/tags/TopRightBar]] [[SystemTag: $:/tags/ViewTemplate]] [[SystemTag: $:/tags/ViewToolbar]] list: [[SystemTag: $:/tags/AboveStory]] [[SystemTag: $:/tags/AdvancedSearch]] [[SystemTag: $:/tags/AdvancedSearch/FilterButton]] [[SystemTag: $:/tags/Alert]] [[SystemTag: $:/tags/BelowStory]] [[SystemTag: $:/tags/ControlPanel]] [[SystemTag: $:/tags/ControlPanel/Advanced]] [[SystemTag: $:/tags/ControlPanel/Appearance]] [[SystemTag: $:/tags/ControlPanel/Info]] [[SystemTag: $:/tags/ControlPanel/Saving]] [[SystemTag: $:/tags/ControlPanel/Settings]] [[SystemTag: $:/tags/ControlPanel/Toolbars]] [[SystemTag: $:/tags/EditorTools]] [[SystemTag: $:/tags/EditorToolbar]] [[SystemTag: $:/tags/EditPreview]] [[SystemTag: $:/tags/EditTemplate]] [[SystemTag: $:/tags/EditToolbar]] [[SystemTag: $:/tags/Exporter]] [[SystemTag: $:/tags/Filter]] [[SystemTag: $:/tags/Image]] [[SystemTag: $:/tags/ImportPreview]] [[SystemTag: $:/tags/KeyboardShortcut]] [[SystemTag: $:/tags/Layout]] [[SystemTag: $:/tags/Macro]] [[SystemTag: $:/tags/Macro/View]] [[SystemTag: $:/tags/Manager/ItemMain]] [[SystemTag: $:/tags/Manager/ItemSidebar]] [[SystemTag: $:/tags/MoreSideBar]] [[SystemTag: $:/tags/MoreSideBar/Plugins]] [[SystemTag: $:/tags/PageControls]] [[SystemTag: $:/tags/PageTemplate]] [[SystemTag: $:/tags/Palette]] [[SystemTag: $:/tags/PluginLibrary]] [[SystemTag: $:/tags/RawMarkup]] [[SystemTag: $:/tags/RawMarkupWikified]] [[SystemTag: $:/tags/RawMarkupWikified/BottomBody]] [[SystemTag: $:/tags/RawMarkupWikified/TopBody]] [[SystemTag: $:/tags/RawMarkupWikified/TopHead]] [[SystemTag: $:/tags/RawStaticContent]] [[SystemTag: $:/tags/RemoteAssetInfo]] [[SystemTag: $:/tags/SearchResults]] [[SystemTag: $:/tags/ServerConnection]] [[SystemTag: $:/tags/SideBar]] [[SystemTag: $:/tags/SideBarSegment]] [[SystemTag: $:/tags/StartupAction]] [[SystemTag: $:/tags/StartupAction/Browser]] [[SystemTag: $:/tags/StartupAction/Node]] [[SystemTag: $:/tags/Stylesheet]] [[SystemTag: $:/tags/TagDropdown]] [[SystemTag: $:/tags/TextEditor/Snippet]] [[SystemTag: $:/tags/TiddlerInfo]] [[SystemTag: $:/tags/TiddlerInfo/Advanced]] [[SystemTag: $:/tags/TiddlerInfoSegment]] [[SystemTag: $:/tags/ToolbarButtonStyle]] [[SystemTag: $:/tags/TopLeftBar]] [[SystemTag: $:/tags/TopRightBar]] [[SystemTag: $:/tags/ViewTemplate]] [[SystemTag: $:/tags/ViewToolbar]]
modified: 20201123192434277 modified: 20210519160636964
tags: Reference Concepts tags: Reference Concepts
title: SystemTags title: SystemTags
type: text/vnd.tiddlywiki type: text/vnd.tiddlywiki

View File

@ -1,5 +1,5 @@
created: 20190206140446821 created: 20190206140446821
modified: 20190611155838557 modified: 20210417090408263
tags: Filters tags: Filters
title: Mathematics Operators title: Mathematics Operators
type: text/vnd.tiddlywiki type: text/vnd.tiddlywiki
@ -26,6 +26,7 @@ The mathematics operators take three different forms:
* ''Reducing operators'' apply an operation to all of the numbers in the input list, returning a single result (e.g. sum, product) * ''Reducing operators'' apply an operation to all of the numbers in the input list, returning a single result (e.g. sum, product)
** <<.inline-operator-example "=1 =2 =3 =4 +[sum[]]">> ** <<.inline-operator-example "=1 =2 =3 =4 +[sum[]]">>
** <<.inline-operator-example "=1 =2 =3 =4 +[product[]]">> ** <<.inline-operator-example "=1 =2 =3 =4 +[product[]]">>
** <<.inline-operator-example "=1 =2 =3 =4 +[average[]]">>
Operators can be combined: Operators can be combined:

View File

@ -0,0 +1,13 @@
caption: average
created: 20210417090137714
modified: 20210426131553482
op-input: a [[selection of titles|Title Selection]]
op-output: the arithmetic mean of the input as numbers
op-purpose: treating each input title as a number, compute their arithmetic mean
tags: [[Reducing Mathematics Operators]] [[Filter Operators]] [[Mathematics Operators]]
title: average Operator
type: text/vnd.tiddlywiki
<<.from-version "5.1.24">> See [[Mathematics Operators]] for an overview.
<<.operator-examples "average">>

View File

@ -0,0 +1,14 @@
caption: deserializers
created: 20210506115203172
from-version: 5.1.24
modified: 20210506130322593
op-input: ignored
op-output: the title of each available deserializer
op-parameter: none
tags: [[Filter Operators]] [[Special Operators]]
title: deserializers Operator
type: text/vnd.tiddlywiki
<<.tip "You can specify a specific deserializer for a DropzoneWidget to use">>
<<.operator-examples "deserializers">>

View File

@ -0,0 +1,10 @@
created: 20210426130837644
modified: 20210426131553546
tags: [[Operator Examples]] [[average Operator]]
title: average Operator (Examples)
type: text/vnd.tiddlywiki
<<.operator-example 1 "=1 =3 =4 =5 +[average[]]">>
Note that if there is no input the operator returns `NaN`
<<.operator-example 2 "[tag[NotATiddler]get[price]] +[average[]]">>

View File

@ -0,0 +1,7 @@
created: 20210506115345021
modified: 20210506115402052
tags: [[Operator Examples]] [[deserializer Operator]]
title: deserializers Operator (Examples)
type: text/vnd.tiddlywiki
<<.operator-example 1 "[deserializers[]]">>

View File

@ -1,7 +1,10 @@
created: 20170907144257037 created: 20170907144257037
modified: 20170907144559822 modified: 20201224034837935
title: lookup Operator (Examples) title: lookup Operator (Examples)
type: text/vnd.tiddlywiki type: text/vnd.tiddlywiki
<<.operator-example 1 "[all[shadows+tiddlers]tag[$:/tags/PageControls]lookup[$:/config/PageControlButtons/Visibility/]]" "Retrieve the visibility status of each page control button">> <<.operator-example 1 "[all[shadows+tiddlers]tag[$:/tags/PageControls]lookup[$:/config/PageControlButtons/Visibility/]]" "Retrieve the visibility status of each page control button">>
<<.operator-example 2 "[all[shadows+tiddlers]tag[$:/tags/PageControls]lookup:show[$:/config/PageControlButtons/Visibility/]]" "Retrieve the visibility status of each page control button, this time with a default value">> <<.operator-example 2 "[all[shadows+tiddlers]tag[$:/tags/PageControls]lookup:show[$:/config/PageControlButtons/Visibility/]]" "Retrieve the visibility status of each page control button, this time with a default value">>
<<.operator-example 3 "[all[tiddlers]has[plugin-type]removeprefix[$:/plugins/tiddlywiki/]lookup:missing-description:field[$:/plugins/tiddlywiki/],[description]]" "Retrieve the description of all plugin-tiddlers that are in the `$:/plugins/tiddlywiki/` namespace.">>
<<.operator-example 4 "OriginalTiddlerPaths +[lookup:missing-index:index[$:/config/],[HelloThere]]" "Lookup the original tiddler path on disk for the [[Hello There]] tiddler.">>
<<.operator-example 5 "OriginalTiddlerPaths +[lookup:missing-index:index[$:/config/],[MissingTiddler]]" "Lookup the original tiddler path on disk for the [[MissingTiddler]] tiddler.">>

View File

@ -0,0 +1,10 @@
created: 20210426131042769
modified: 20210426131553560
tags: [[Operator Examples]] [[median Operator]]
title: median Operator (Examples)
type: text/vnd.tiddlywiki
<<.operator-example 1 "=1 =3 =4 =5 +[median[]]">>
Note that if there is no input the operator returns `NaN`
<<.operator-example 2 "[title[NotATiddler]get[price]] +[median[]]">>

View File

@ -0,0 +1,10 @@
created: 20210426130306824
modified: 20210426131553553
tags: [[Operator Examples]] [[standard-deviation Operator]]
title: standard-deviation Operator (Examples)
type: text/vnd.tiddlywiki
<<.operator-example 1 "=1 =3 =4 =5 +[standard-deviation[]]">>
Note that if there is no input the operator returns `NaN`
<<.operator-example 2 "[title[NotATiddler]get[price]] +[standard-deviation[]]">>

View File

@ -0,0 +1,10 @@
created: 20210426130620777
modified: 20210426131553522
tags: [[Operator Examples]] [[variance Operator]]
title: variance Operator (Examples)
type: text/vnd.tiddlywiki
<<.operator-example 1 "1 3 4 5 +[variance[]]">>
Note that if there is no input the operator returns `NaN`
<<.operator-example 2 "[title[NotATiddler]is[tiddler]get[price]] +[variance[]]">>

View File

@ -1,6 +1,6 @@
caption: filter caption: filter
created: 20200929174420821 created: 20200929174420821
modified: 20201027185144953 modified: 20210522162551921
op-input: a [[selection of titles|Title Selection]] passed as input to the filter op-input: a [[selection of titles|Title Selection]] passed as input to the filter
op-neg-input: a [[selection of titles|Title Selection]] passed as input to the filter op-neg-input: a [[selection of titles|Title Selection]] passed as input to the filter
op-neg-output: those input titles that <<.em "do not">> pass the filter <<.place S>> op-neg-output: those input titles that <<.em "do not">> pass the filter <<.place S>>
@ -28,6 +28,8 @@ Simple filter operations can be concatenated together directly (eg `[tag[HelloTh
</$vars> </$vars>
``` ```
Note that within the subfilter, the "currentTiddler" variable is set to the title of the tiddler being processed. The value of currentTiddler outside the subfilter is available in the variable "..currentTiddler". <<.from-version "5.1.24">>
<<.tip "Compare with the similar [[subfilter|subfilter Operator]] operator which runs a subfilter and directly returns the results">> <<.tip "Compare with the similar [[subfilter|subfilter Operator]] operator which runs a subfilter and directly returns the results">>
<<.tip "Compare with the analagous named filter run prefix `:filter`">> <<.tip "Compare with the analagous named filter run prefix `:filter`">>

View File

@ -1,24 +1,40 @@
caption: lookup caption: lookup
created: 20170907103639431 created: 20170907103639431
modified: 20170907144703051 modified: 20210116081305739
op-input: a [[selection of titles|Title Selection]] op-input: a [[selection of titles|Title Selection]]
op-output: the lookup values corresponding to each input title op-output: the lookup values corresponding to each lookup tiddler
op-parameter: prefix applied to input titles to yield title of lookup tiddler from which value is retrieved op-parameter: prefix applied to input titles to yield title of lookup tiddler from which value is retrieved. Now accepts 1 or 2 parameters, see below for details
op-parameter-name: P op-parameter-name: P, T
op-purpose: applies a prefix to each input title to yield the title of a tiddler from which the final value is retrieved op-purpose: applies a prefix to each input title to yield the title of a tiddler from which the final value is retrieved. With a single parameter, the default field is "text" and the default index is "0". If a second parameter is provided, that becomes the target field or index.
op-suffix: the default value to be used for missing lookups op-suffix: the default value to be used for missing lookups. This operator can now accept a second suffix, see below for details
op-suffix-name: D op-suffix-name: D, I
tags: [[Filter Operators]] tags: [[Filter Operators]]
title: lookup Operator title: lookup Operator
type: text/vnd.tiddlywiki type: text/vnd.tiddlywiki
<<.from-version "5.1.15">> <<.from-version "5.1.15">>
The action of this operator is as follows: The action of this operator is as follows with 1 parameter:
* Apply the specified prefix to each input tiddler title, yielding a new list of tiddler titles * Apply the specified prefix to each input tiddler title, yielding a new list of tiddler titles
* Transclude the value of each of those tiddlers * Transclude the value of the `text` field each of those tiddlers
** Substitute the default value for missing or empty tiddlers ** Substitute the default value for missing or empty values
* Return the list of values
<<.from-version "5.1.24">>
The action of this operator is as follows with 2 parameters:
If there are two parameters provided, use the second parameter as the target field or index.
<<.note """If there is only one parameter given, the filter checks for a second suffix equal to "index". If this suffix is found, the default target index is "0".
In all other cases, the default target field is "text".""">>
Then:
* Apply the specified prefix to each input tiddler title, yielding a new list of tiddler titles
* Transclude the value of the target field or index
** Substitute the default value for missing or empty values
* Return the list of values * Return the list of values
<<.operator-examples "lookup">> <<.operator-examples "lookup">>

View File

@ -0,0 +1,13 @@
caption: median
created: 20210417090137714
modified: 20210426131553507
op-input: a [[selection of titles|Title Selection]]
op-output: the median of the input numbers
op-purpose: treating each input title as a number, compute their median value
tags: [[Filter Operators]] [[Mathematics Operators]]
title: median Operator
type: text/vnd.tiddlywiki
<<.from-version "5.1.24">> See [[Mathematics Operators]] for an overview.
<<.operator-examples "median">>

View File

@ -1,6 +1,6 @@
caption: reduce caption: reduce
created: 20201004154131193 created: 20201004154131193
modified: 20201208185109549 modified: 20210522162536854
op-input: a [[selection of titles|Title Selection]] passed as input to the filter op-input: a [[selection of titles|Title Selection]] passed as input to the filter
op-output: the final result of running the subfilter <<.place S>> op-output: the final result of running the subfilter <<.place S>>
op-parameter: a [[filter expression|Filter Expression]]. Optional second parameter for initial value for accumulator op-parameter: a [[filter expression|Filter Expression]]. Optional second parameter for initial value for accumulator
@ -18,6 +18,7 @@ The following variables are available within the subfilter:
* ''accumulator'' - the result of the previous subfilter run * ''accumulator'' - the result of the previous subfilter run
* ''currentTiddler'' - the input title * ''currentTiddler'' - the input title
* ''..currentTiddler'' - the value of the variable `currentTiddler` outside the subfilter. <<.from-version "5.1.24">>
* ''index'' - the numeric index of the current list item (with zero being the first item in the list) * ''index'' - the numeric index of the current list item (with zero being the first item in the list)
* ''revIndex'' - the reverse numeric index of the current list item (with zero being the last item in the list) * ''revIndex'' - the reverse numeric index of the current list item (with zero being the last item in the list)
* ''length'' - the total length of the input list * ''length'' - the total length of the input list

View File

@ -1,21 +1,23 @@
caption: sortsub
created: 20200424160155182 created: 20200424160155182
modified: 20200424160155182 modified: 20210522162521222
op-input: a [[selection of titles|Title Selection]]
op-neg-output: the input, sorted into reverse order by the result of evaluating subfilter <<.param S>>
op-output: the input, sorted into ascending order by the result of evaluating subfilter <<.param S>>
op-parameter: a subfilter to be evaluated
op-parameter-name: S
op-purpose: sort the input by the result of evaluating a subfilter for each item
op-suffix: the type used for the comparison (string, number, integer, date, version), defaulting to string
op-suffix-name: T
tags: [[Filter Operators]] [[Field Operators]] [[Order Operators]] [[Negatable Operators]] tags: [[Filter Operators]] [[Field Operators]] [[Order Operators]] [[Negatable Operators]]
title: sortsub Operator title: sortsub Operator
type: text/vnd.tiddlywiki type: text/vnd.tiddlywiki
caption: sortsub
op-purpose: sort the input by the result of evaluating a subfilter for each item
op-input: a [[selection of titles|Title Selection]]
op-parameter: a subfilter to be evaluated
op-parameter-name: S
op-suffix: the type used for the comparison (string, number, integer, date, version), defaulting to string
op-suffix-name: T
op-output: the input, sorted into ascending order by the result of evaluating subfilter <<.param S>>
op-neg-output: the input, sorted into reverse order by the result of evaluating subfilter <<.param S>>
Each item in the list of input titles is passed to the subfilter in turn. The subfilter transforms the input titles into the form needed for sorting. For example, the subfilter `[length[]]` transforms each input title in the number representing its length, and thus sorts the input titles according to their length. Each item in the list of input titles is passed to the subfilter in turn. The subfilter transforms the input titles into the form needed for sorting. For example, the subfilter `[length[]]` transforms each input title in the number representing its length, and thus sorts the input titles according to their length.
Note that within the subfilter, the "currentTiddler" variable is set to the title of the tiddler being processed. This permits subfilters like `[{!!value}divide{!!cost}]` to be used for computation. Note that within the subfilter, the "currentTiddler" variable is set to the title of the tiddler being processed. This permits subfilters like `[{!!value}divide{!!cost}]` to be used for computation.
The value of currentTiddler outside the subfilter is available in the variable "..currentTiddler". <<.from-version "5.1.24">>
The suffix <<.place T>> determines how the items are compared and can be: The suffix <<.place T>> determines how the items are compared and can be:
@ -24,6 +26,7 @@ The suffix <<.place T>> determines how the items are compared and can be:
* "integer" - invalid integers are interpreted as zero * "integer" - invalid integers are interpreted as zero
* "date" - invalid dates are interpreted as 1st January 1970 * "date" - invalid dates are interpreted as 1st January 1970
* "version" - invalid versions are interpreted as "v0.0.0" * "version" - invalid versions are interpreted as "v0.0.0"
* "alphanumeric" - treat items as alphanumerics <<.from-version "5.1.24">>
Note that subfilters should return the same number of items that they are passed. Any missing entries will be treated as zero or the empty string. In particular, when retrieving the value of a field with the [[get Operator]] it is helpful to guard against a missing field value using the [[else Operator]]. For example `[get[myfield]else[default-value]...`. Note that subfilters should return the same number of items that they are passed. Any missing entries will be treated as zero or the empty string. In particular, when retrieving the value of a field with the [[get Operator]] it is helpful to guard against a missing field value using the [[else Operator]]. For example `[get[myfield]else[default-value]...`.

View File

@ -0,0 +1,15 @@
caption: standard-deviation
created: 20210426130150358
modified: 20210426131553530
op-input: a [[selection of titles|Title Selection]]
op-output: the standard-deviation of the input as numbers
op-purpose: treating each input title as a number, compute their standard-deviation
tags: [[Reducing Mathematics Operators]] [[Filter Operators]] [[Mathematics Operators]]
title: standard-deviation Operator
type: text/vnd.tiddlywiki
<<.from-version "5.1.24">> See [[Mathematics Operators]] for an overview.
<<.tip """ The `standard-deviation` operator treats the input as a complete population and not a sample""">>
<<.operator-examples "standard-deviation">>

View File

@ -1,5 +1,5 @@
created: 20150124182421000 created: 20150124182421000
modified: 20201214053032397 modified: 20210522162642994
tags: [[Filter Syntax]] tags: [[Filter Syntax]]
title: Filter Expression title: Filter Expression
type: text/vnd.tiddlywiki type: text/vnd.tiddlywiki
@ -26,11 +26,15 @@ If a run has:
* named prefix `:intersection` replaces all filter output so far with titles that are present in the output of this run, as well as the output from previous runs. Forms the input for the next run. <<.from-version "5.1.23">> * named prefix `:intersection` replaces all filter output so far with titles that are present in the output of this run, as well as the output from previous runs. Forms the input for the next run. <<.from-version "5.1.23">>
* named prefix `:reduce` replaces all filter output so far with a single item by repeatedly applying a formula to each input title. A typical use is to add up the values in a given field of each input title. <<.from-version "5.1.23">> * named prefix `:reduce` replaces all filter output so far with a single item by repeatedly applying a formula to each input title. A typical use is to add up the values in a given field of each input title. <<.from-version "5.1.23">>
** [[Examples|Filter Run Prefix (Examples)]] ** [[Examples|Filter Run Prefix (Examples)]]
* named prefix `:sort` sorts all filter output so far by applying this run to each input title and sorting according to that output. <<.from-version "5.1.24">>
** See [[Sort Filter Run Prefix]].
<<.tip "Compare named filter run prefix `:filter` with [[filter Operator]] which applies a subfilter to every input title, removing the titles that return an empty result from the subfilter">> <<.tip "Compare named filter run prefix `:filter` with [[filter Operator]] which applies a subfilter to every input title, removing the titles that return an empty result from the subfilter">>
<<.tip "Compare named filter run prefix `:reduce` with [[reduce Operator]] which is used to used to flatten a list of items down to a single item by repeatedly applying a subfilter.">> <<.tip "Compare named filter run prefix `:reduce` with [[reduce Operator]] which is used to used to flatten a list of items down to a single item by repeatedly applying a subfilter.">>
<<.tip """Within the filter runs prefixed with `:reduce`, `:sort` and `:filter`, the "currentTiddler" variable is set to the title of the tiddler being processed. The value of currentTiddler outside the subfilter is available in the variable "..currentTiddler".<<.from-version "5.1.24">>""" >>
In technical / logical terms: In technical / logical terms:
|!Run |!Equivalent named prefix |!Interpretation |!Output | |!Run |!Equivalent named prefix |!Interpretation |!Output |
@ -47,7 +51,7 @@ The input of a run is normally a list of all the non-[[shadow|ShadowTiddlers]] t
|Prefix|Input|h |Prefix|Input|h
|`-`, `~`, `=`, `:intersection` or none| <$link to="all Operator">`[all[]]`</$link> tiddler titles, unless otherwise determined by the first [[filter operator|Filter Operators]]| |`-`, `~`, `=`, `:intersection` or none| <$link to="all Operator">`[all[]]`</$link> tiddler titles, unless otherwise determined by the first [[filter operator|Filter Operators]]|
|`+`, `:filter`, `:reduce`|the filter output of all previous runs so far| |`+`, `:filter`, `:reduce`,`:sort`|the filter output of all previous runs so far|
Precisely because of varying inputs, be aware that both prefixes `-` and `+` do not behave inverse to one another! Precisely because of varying inputs, be aware that both prefixes `-` and `+` do not behave inverse to one another!

View File

@ -1,6 +1,6 @@
created: 20201117073343969 created: 20201117073343969
modified: 20201208185546667 modified: 20210428084013109
tags: [[Filter Syntax]] tags: [[Filter Syntax]] [[Filter Run Prefix Examples]]
title: Filter Run Prefix (Examples) title: Filter Run Prefix (Examples)
type: text/vnd.tiddlywiki type: text/vnd.tiddlywiki
@ -44,3 +44,7 @@ Specifying a default value when input is empty:
`[tag[non-existent]] :reduce[get[price]multiply{!!quantity}add<accumulator>] :else[[0]]` `[tag[non-existent]] :reduce[get[price]multiply{!!quantity}add<accumulator>] :else[[0]]`
<$macrocall $name=".tip" _="""Unlike the [[reduce Operator]], the `:reduce` prefix cannot specify an initial value for the accumulator, so its initial value will always be empty (which is treated as 0 by mathematical operators). So `=1 =2 =3 :reduce[multiply<accumulator>]` will produce 0, not 6. If you need to specify an initial accumulator value, use the [[reduce Operator]]."""/> <$macrocall $name=".tip" _="""Unlike the [[reduce Operator]], the `:reduce` prefix cannot specify an initial value for the accumulator, so its initial value will always be empty (which is treated as 0 by mathematical operators). So `=1 =2 =3 :reduce[multiply<accumulator>]` will produce 0, not 6. If you need to specify an initial accumulator value, use the [[reduce Operator]]."""/>
!! `:sort` examples
See [[Sort Filter Run Prefix (Examples)]]

View File

@ -0,0 +1,33 @@
created: 20210428074912172
modified: 20210428085746041
tags: [[Filter Syntax]] [[Sort Filter Run Prefix]] [[Filter Run Prefix Examples]]
title: Sort Filter Run Prefix (Examples)
type: text/vnd.tiddlywiki
Sort by title length:
<<.operator-example 1 "[all[tiddlers]] :sort:number[length[]] +[limit[10]]">>
Sort by title length reversed:
<<.operator-example 2 "[all[tiddlers]] :sort:number:reverse[length[]] +[limit[10]]">>
Sort by text length:
<<.operator-example 3 "[all[tiddlers]] :sort:number[get[text]length[]] +[limit[10]]">>
Sort by newest of modified dates:
<<.operator-example 4 "[tag[Field Operators]] :sort:date[get[modified]else[19700101]] +[limit[10]]">>
Sort by title:
<<.operator-example 5 "[tag[Field Operators]] :sort:string:casesensitive[get[caption]] +[limit[10]]">>
Sort by title in reverse order:
<<.operator-example 6 "[tag[Field Operators]] :sort:string:casesensitive,reverse[get[caption]] +[limit[10]]">>
Sort as text with case sensitivity:
<<.operator-example 7 "Apple Banana Orange Grapefruit guava DragonFruit Kiwi apple orange :sort:string:casesensitive[{!!title}]">>
Sort as text ignoring case:
<<.operator-example 8 "Apple Banana Orange Grapefruit guava DragonFruit Kiwi apple orange :sort:string:caseinsensitive[{!!title}]">>

View File

@ -0,0 +1,32 @@
created: 20210428083929749
modified: 20210522162628946
tags: [[Filter Syntax]] [[Filter Run Prefix]]
title: Sort Filter Run Prefix
type: text/vnd.tiddlywiki
<<.from-version "5.1.24">>
|''purpose'' |sort the input titles by the result of evaluating this filter run for each item |
|''input'' |all titles from previous filter runs |
|''suffix'' |the `:sort` filter run prefix uses a rich suffix, see below for details |
|''output''|the sorted result of previous filter runs |
Each input title from previous runs is passed to this run in turn. The filter run transforms the input titles into the form needed for sorting. For example, the filter run `[length[]]` transforms each input title in to the number representing its length, and thus sorts the input titles according to their length.
Note that within the filter run, the "currentTiddler" variable is set to the title of the tiddler being processed. This permits filter runs like `:sort:number[{!!value}divide{!!cost}]` to be used for computation. The value of currentTiddler outside the run is available in the variable "..currentTiddler".
The `:sort` filter run prefix uses an extended syntax that allows for multiple suffixes, some of which are required:
```
:sort:<type>:<flaglist>[...filter run...]
```
* ''type'': Required. Determines how the items are compared and can be any of: ''string'', ''alphanumeric'', ''number'', ''integer'', ''version'' or ''date''.
* ''flaglist'': comma separated list of the following flags:
** ''casesensitive'' or ''caseinsensitive'' (required for types `string` and `alphanumeric`).
** ''reverse'' to invert the order of the filter run (optional).
Note that filter runs used with the `:sort` prefix should return the same number of items that they are passed. Any missing entries will be treated as zero or the empty string. In particular, when retrieving the value of a field with the [[get Operator]] it is helpful to guard against a missing field value using the [[else Operator]]. For example `[get[myfield]else[default-value]...`.
[[Examples|Sort Filter Run Prefix (Examples)]]

View File

@ -0,0 +1,15 @@
caption: variance
created: 20210426130029500
modified: 20210426131553539
op-input: a [[selection of titles|Title Selection]]
op-output: the variance of the input as numbers
op-purpose: treating each input title as a number, compute their variance
tags: [[Reducing Mathematics Operators]] [[Filter Operators]] [[Mathematics Operators]]
title: variance Operator
type: text/vnd.tiddlywiki
<<.from-version "5.1.24">> See [[Mathematics Operators]] for an overview.
<<.tip """ The `standard-deviation` operator treats the input as a complete population and not a sample""">>
<<.operator-examples "variance">>

View File

@ -0,0 +1,13 @@
created: 20210411100148461
modified: 20210411100148461
tags: [[Hidden Settings]]
title: Hidden Setting: HTML Parser Sandbox
type: text/vnd.tiddlywiki
<<.from-version "5.1.24">> By default, tiddlers with the type `text/html` are displayed in an iframe with the [[sandbox attribute|https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox]] set to the empty string. This causes all security restrictions to be applied, disabling many features such as JavaScript, downloads and external file references. This is the safest setting.
To globally disable the sandbox, set the tiddler $:/config/HtmlParser/DisableSandbox to `yes`. This will mean that the code in the iframe has full access to TiddlyWiki's internals, which means that a malicious HTML page could exfiltrate data from a private wiki.
To keep the sandbox but control which restrictions are applied, ensure that $:/config/HtmlParser/DisableSandbox is not set to `yes`, and then set $:/config/HtmlParser/SandboxTokens to the desired list of tokens [[from the MDN documentation|https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox]].
Note that these are global settings. To control the sandboxing on an individual tiddler basis will require a custom `<iframe>` to be used.

View File

@ -1,10 +1,10 @@
created: 20200315143638556 created: 20200315143638556
modified: 20200315143638556 modified: 20210519155433742
tags: [[Hidden Settings]] tags: [[Hidden Settings]]
title: Hidden Setting: Disable Drag and Drop title: Hidden Setting: Disable Drag and Drop
type: text/vnd.tiddlywiki type: text/vnd.tiddlywiki
<.from-version "5.1.22">> To disable all the drag and drop operations that are built into the core, set the following tiddler to "no": <<.from-version "5.1.22">> To disable all the drag and drop operations that are built into the core, set the following tiddler to "no":
$:/config/DragAndDrop/Enable $:/config/DragAndDrop/Enable

View File

@ -0,0 +1,12 @@
created: 20210519155447339
modified: 20210519160010708
tags: [[Hidden Settings]]
title: Hidden Setting: Enable File Import in Editor
type: text/vnd.tiddlywiki
<<.from-version "5.1.24">>
$:/config/Editor/EnableImportFilter
This filter determines whether dragging and dropping files in the editor works for a given tiddler or not. A non-empty result enables drag and drop in the editor for that tiddler.
This filter is used in such a manner that it respects the global drag and drop setting.

View File

@ -0,0 +1,11 @@
created: 20210519155910219
modified: 20210519160221219
tags: [[Hidden Settings]]
title: Hidden Settings: Import Content Types for Editor
type: text/vnd.tiddlywiki
<<.from-version "5.1.24">>
$:/config/Editor/ImportContentTypesFilter
This filter determines which `contentTypes` can be imported by dragging and dropping into the editor. It used by a DropzoneWidget wrapped around the editor, for the `contentTypesFilter` attribute.

View File

@ -0,0 +1,126 @@
created: 20201216182347597
modified: 20201217193318816
tags:
title: How to create dynamic editor toolbar buttons
type: text/vnd.tiddlywiki
\define disabledFilterExample()`[[$(tempBoldDisabled)$]get[state-disabled]else[no]]`
\define remove-shortcuts()
<$action-deletetiddler $tiddler=<<shortcutInfo>>/>
<$action-deletetiddler $tiddler=<<shortcutConfigMac>>/>
<$action-deletetiddler $tiddler=<<shortcutConfigNotMac>>/>
<$action-deletetiddler $tiddler=<<tempBold>>/>
<$action-deletetiddler $tiddler=<<tempBoldDisabled>>/>
\end
\define create-shortcut-tiddlers()
<$action-createtiddler $basetitle=<<shortcutInfo>>
$template="$:/config/ShortcutInfo/bold"
$overwrite="yes"
/>
<$action-navigate $to=<<shortcutInfo>>/>
<$action-createtiddler $basetitle=<<shortcutConfigMac>>
$template="$:/config/shortcuts-mac/bold"
$overwrite="yes"
text="meta-shift-X"
/>
<$action-navigate $to=<<shortcutConfigMac>>/>
<$action-createtiddler $basetitle=<<shortcutConfigNotMac>>
$template="$:/config/shortcuts-not-mac/bold"
$overwrite="yes"
text="ctrl-shift-X"
/>
<$action-navigate $to=<<shortcutConfigNotMac>>/>
\end
\define clone-bold()
<$action-createtiddler $basetitle=<<tempBold>>
$template="$:/core/ui/EditorToolbar/bold"
$overwrite="yes"
icon="$:/core/images/spiral"
shortcuts="((temp-bold))"
condition-disabled="[[$(tempBoldDisabled)$]get[state-disabled]else[no]]"
/>
<$action-sendmessage $message="tm-edit-tiddler" $param=<<tempBold>>/>
\end
\define clone-button-bold()
<$button actions=<<clone-bold>> >Create a Temporary Bold Button</$button>
\end
\define toggle-bold()
<$action-listops $tiddler=<<tempBoldDisabled>> $field="state-disabled" $subfilter="+[toggle[yes],[no]]" />
\end
<!-- $vars is needed don't remove it! -->
<$vars tempBold="$:/temp/bold" tempBoldDisabled="$:/temp/bold/disabled" shortcutInfo="$:/config/ShortcutInfo/temp-bold" shortcutConfigMac="$:/config/shortcuts-mac/temp-bold" shortcutConfigNotMac="$:/config/shortcuts-not-mac/temp-bold">
!! Create a New Toolbar Button
The easiest way to create new editor toolbar button is to clone and open one.
><<clone-button-bold>>
This tiddler contains all the necessary elements that are important for toolbar buttons.
<<<
; text
: We don't discuss the text field details in this howto
; caption
: The caption field is used to display the shortcut name in the $:/ControlPanel : Keyboard Shortcuts tab
; condition
: A filter, that defines the button visibility state
; condition-disabled <<.from-version "5.1.23">>
: A ''filter'', that allows us to define the "disabled" attribute for buttons. eg: <<disabledFilterExample>>
: This condition ''must'' return "no", if the "state tiddler" or "state field" doesn't exist! So there has to be a `else[no]` element in the filter.
; description
: Is used as the button tooltip
; icon
: Assigns the button icon. We use `$:/core/images/spiral` {{$:/core/images/spiral}} here.
; shortcuts
: This is the [[Keyboard Shortcut Descriptor]] eg: `((temp-bold))`
<<<
!! Disabled State
You can use any "state tiddler" to define the button disabled state. It's important, that the ''condition-disabled'' field is a ''filter''. For our example we use the `state-disabled` field from tiddler: <<tempBoldDisabled>>
The easiest way to test filters is with the $:/AdvancedSearch : Filter tab
> The temporary "bold button" is disabled: ''"{{{ [<tempBoldDisabled>get[state-disabled]else[no]] }}}"'' ... You can see the button in the editor toolbar if you edit any tiddler!
> <$button actions=<<toggle-bold>>>Toggle Temporary Bold Button Visisbility</$button>
!! Create Keyboard Shortcuts for New Button
For our ~HowTo we use the ''Keyboard Shortcut Descriptor'': `((temp-bold))`
To create a valid shortcut configuration we need 2 tiddlers:
>[[$:/config/ShortcutInfo/|$:/config/ShortcutInfo/temp-bold]]''temp-bold'' .. and
>[[$:/config/shortcuts/|$:/config/shortcuts/temp-bold]]''temp-bold''
> <$button actions=<<create-shortcut-tiddlers>>>Create Shortcut Tiddlers</$button>
!!
!! Clean up the Configuration Tiddlers
If you don't need the config tiddlers anymore you can
><$button actions=<<remove-shortcuts>>>Remove the Shortcut Configuration</$button>
</$vars><!-- needed don't remove! -->

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