mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2025-02-21 05:20:01 +00:00
Merge branch 'Jermolene:master' into offline-extjs
This commit is contained in:
commit
31583a8d85
@ -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:
|
||||
|
||||
npm install svgo
|
||||
npm install svgo@2.3.0
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var fs = require("fs"),
|
||||
path = require("path"),
|
||||
SVGO = require("svgo"),
|
||||
svgo = new SVGO({
|
||||
{ optimize } = require("svgo"),
|
||||
config = {
|
||||
plugins: [
|
||||
{cleanupAttrs: true},
|
||||
{removeDoctype: true},
|
||||
{removeXMLProcInst: true},
|
||||
{removeComments: true},
|
||||
{removeMetadata: true},
|
||||
{removeTitle: true},
|
||||
{removeDesc: true},
|
||||
{removeUselessDefs: true},
|
||||
{removeEditorsNSData: true},
|
||||
{removeEmptyAttrs: true},
|
||||
{removeHiddenElems: true},
|
||||
{removeEmptyText: true},
|
||||
{removeEmptyContainers: true},
|
||||
{removeViewBox: false},
|
||||
{cleanupEnableBackground: true},
|
||||
{convertStyleToAttrs: true},
|
||||
{convertColors: true},
|
||||
{convertPathData: true},
|
||||
{convertTransform: true},
|
||||
{removeUnknownsAndDefaults: true},
|
||||
{removeNonInheritableGroupAttrs: true},
|
||||
{removeUselessStrokeAndFill: true},
|
||||
{removeUnusedNS: true},
|
||||
{cleanupIDs: true},
|
||||
{cleanupNumericValues: true},
|
||||
{moveElemsAttrsToGroup: true},
|
||||
{moveGroupAttrsToElems: true},
|
||||
{collapseGroups: true},
|
||||
{removeRasterImages: false},
|
||||
{mergePaths: true},
|
||||
{convertShapeToPath: true},
|
||||
{sortAttrs: true},
|
||||
{removeDimensions: false},
|
||||
{removeAttrs: {attrs: "(stroke|fill)"}}
|
||||
'cleanupAttrs',
|
||||
'removeDoctype',
|
||||
'removeXMLProcInst',
|
||||
'removeComments',
|
||||
'removeMetadata',
|
||||
'removeTitle',
|
||||
'removeDesc',
|
||||
'removeUselessDefs',
|
||||
'removeEditorsNSData',
|
||||
'removeEmptyAttrs',
|
||||
'removeHiddenElems',
|
||||
'removeEmptyText',
|
||||
'removeEmptyContainers',
|
||||
// 'removeViewBox',
|
||||
'cleanupEnableBackground',
|
||||
'convertStyleToAttrs',
|
||||
'convertColors',
|
||||
'convertPathData',
|
||||
'convertTransform',
|
||||
'removeUnknownsAndDefaults',
|
||||
'removeNonInheritableGroupAttrs',
|
||||
'removeUselessStrokeAndFill',
|
||||
'removeUnusedNS',
|
||||
'cleanupIDs',
|
||||
'cleanupNumericValues',
|
||||
'moveElemsAttrsToGroup',
|
||||
'moveGroupAttrsToElems',
|
||||
'collapseGroups',
|
||||
// 'removeRasterImages',
|
||||
'mergePaths',
|
||||
'convertShapeToPath',
|
||||
'sortAttrs',
|
||||
//'removeDimensions',
|
||||
{name: 'removeAttrs', params: { attrs: '(stroke|fill)' } }
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
var basepath = "./core/images/",
|
||||
files = fs.readdirSync(basepath).sort();
|
||||
@ -66,12 +66,14 @@ files.forEach(function(filename) {
|
||||
fakeSVG = body.join("\n");
|
||||
// A hack to make the new-journal-button work
|
||||
fakeSVG = fakeSVG.replace("<<now \"DD\">>","<<now "DD">>");
|
||||
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("<<now "DD">>","<<now \"DD\">>");
|
||||
fs.writeFileSync(filepath,newSVG);
|
||||
},function(err) {
|
||||
} else {
|
||||
console.log("Error " + err + " with " + filename)
|
||||
process.exit();
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
|
20
boot/boot.js
20
boot/boot.js
@ -2133,6 +2133,7 @@ $tw.loadWikiTiddlers = function(wikiPath,options) {
|
||||
fileInfo = $tw.boot.files[title];
|
||||
if(fileInfo.isEditableFile) {
|
||||
relativePath = path.relative($tw.boot.wikiTiddlersPath,fileInfo.filepath);
|
||||
fileInfo.originalpath = relativePath;
|
||||
output[title] =
|
||||
path.sep === "/" ?
|
||||
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) {
|
||||
var platforms = taskModule.platforms;
|
||||
if(platforms) {
|
||||
for(var t=0; t<platforms.length; t++) {
|
||||
if((platforms[t] === "browser" && !$tw.browser) || (platforms[t] === "node" && !$tw.node)) {
|
||||
return false;
|
||||
switch (platforms[t]) {
|
||||
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;
|
||||
};
|
||||
|
File diff suppressed because one or more lines are too long
4
core/images/minus-button.tid
Normal file
4
core/images/minus-button.tid
Normal 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>
|
4
core/images/plus-button.tid
Normal file
4
core/images/plus-button.tid
Normal 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>
|
@ -123,12 +123,12 @@ Saving/TiddlySpot/BackupDir: Backup Directory
|
||||
Saving/TiddlySpot/ControlPanel: ~TiddlySpot Control Panel
|
||||
Saving/TiddlySpot/Backups: Backups
|
||||
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/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/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/UploadDir: Upload Directory
|
||||
Saving/TiddlySpot/UserName: Wiki Name
|
||||
|
@ -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)
|
||||
* ''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")
|
||||
* ''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.
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
title: $:/language/Import/
|
||||
|
||||
Editor/Import/Heading: Import images and insert into the editor.
|
||||
Imported/Hint: The following tiddlers were imported:
|
||||
Listing/Cancel/Caption: Cancel
|
||||
Listing/Hint: These tiddlers are ready to import:
|
||||
|
@ -24,7 +24,6 @@ Encryption/RepeatPassword: Repeat password
|
||||
Encryption/PasswordNoMatch: Passwords do not match
|
||||
Encryption/SetPassword: Set password
|
||||
Error/Caption: Error
|
||||
Error/EditConflict: File changed on server
|
||||
Error/Filter: Filter error
|
||||
Error/FilterSyntax: Syntax error in filter expression
|
||||
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/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/PutEditConflict: File changed on server
|
||||
Error/PutForbidden: Permission denied
|
||||
Error/PutUnauthorized: Authentication required
|
||||
Error/RecursiveTransclusion: Recursive transclusion error in transclude widget
|
||||
Error/RetrievingSkinny: Error retrieving skinny tiddler list
|
||||
Error/SavingToTWEdit: Error saving to TWEdit
|
||||
|
@ -167,10 +167,10 @@ WikiFolderMaker.prototype.saveTiddler = function(directory,tiddler) {
|
||||
}
|
||||
var fileInfo = $tw.utils.generateTiddlerFileInfo(tiddler,{
|
||||
directory: path.resolve(this.wikiFolderPath,directory),
|
||||
wiki: this.wiki,
|
||||
pathFilters: pathFilters,
|
||||
extFilters: extFilters,
|
||||
originalpath: this.wiki.extractTiddlerDataItem("$:/config/OriginalTiddlerPaths",title, "")
|
||||
wiki: this.wiki,
|
||||
fileInfo: {}
|
||||
});
|
||||
try {
|
||||
$tw.utils.saveTiddlerToFileSync(tiddler,fileInfo);
|
||||
|
@ -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.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(",");
|
||||
|
||||
|
@ -87,7 +87,20 @@ function FramedEngine(options) {
|
||||
{name: "input",handlerObject: this,handlerMethod: "handleInputEvent"},
|
||||
{name: "keydown",handlerObject: this.widget,handlerMethod: "handleKeydownEvent"},
|
||||
{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
|
||||
this.iframeDoc.body.appendChild(this.domNode);
|
||||
}
|
||||
|
@ -103,7 +103,11 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
|
||||
var tiddler = this.wiki.getTiddler(this.editTitle);
|
||||
if(tiddler) {
|
||||
// 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") {
|
||||
type = tiddler.fields.type || "text/vnd.tiddlywiki";
|
||||
}
|
||||
@ -182,6 +186,7 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
|
||||
this.editRefreshTitle = this.getAttribute("refreshTitle");
|
||||
this.editAutoComplete = this.getAttribute("autocomplete");
|
||||
this.isDisabled = this.getAttribute("disabled","no");
|
||||
this.isFileDropEnabled = this.getAttribute("fileDrop","no") === "yes";
|
||||
// Get the default editor element tag and type
|
||||
var tag,type;
|
||||
if(this.editField === "text") {
|
||||
@ -213,7 +218,7 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
|
||||
EditTextWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
// 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();
|
||||
return true;
|
||||
} else if (changedTiddlers[this.editRefreshTitle]) {
|
||||
@ -293,19 +298,88 @@ function editTextWidgetFactory(toolbarEngine,nonToolbarEngine) {
|
||||
Propogate keydown events to our container for the keyboard widgets benefit
|
||||
*/
|
||||
EditTextWidget.prototype.propogateKeydownEvent = function(event) {
|
||||
var newEvent = this.document.createEventObject ? this.document.createEventObject() : this.document.createEvent("Events");
|
||||
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;
|
||||
var newEvent = this.cloneEvent(event,["keyCode","which","metaKey","ctrlKey","altKey","shiftKey"]);
|
||||
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;
|
||||
|
||||
}
|
||||
|
23
core/modules/editor/operations/text/insert-text.js
Normal file
23
core/modules/editor/operations/text/insert-text.js
Normal 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;
|
||||
};
|
||||
|
||||
})();
|
@ -17,10 +17,21 @@ exports.filter = function(operationSubFunction,options) {
|
||||
return function(results,source,widget) {
|
||||
if(results.length > 0) {
|
||||
var resultsToRemove = [];
|
||||
results.each(function(result) {
|
||||
var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([result]),widget);
|
||||
results.each(function(title) {
|
||||
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) {
|
||||
resultsToRemove.push(result);
|
||||
resultsToRemove.push(title);
|
||||
}
|
||||
});
|
||||
results.remove(resultsToRemove);
|
||||
|
@ -19,23 +19,25 @@ exports.reduce = function(operationSubFunction,options) {
|
||||
var index = 0;
|
||||
results.each(function(title) {
|
||||
var list = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
|
||||
getVariable: function(name) {
|
||||
switch(name) {
|
||||
case "currentTiddler":
|
||||
return "" + title;
|
||||
case "accumulator":
|
||||
return "" + accumulator;
|
||||
case "index":
|
||||
return "" + index;
|
||||
case "revIndex":
|
||||
return "" + (results.length - 1 - index);
|
||||
case "length":
|
||||
return "" + results.length;
|
||||
default:
|
||||
return widget.getVariable(name);
|
||||
}
|
||||
getVariable: function(name) {
|
||||
switch(name) {
|
||||
case "currentTiddler":
|
||||
return "" + title;
|
||||
case "..currentTiddler":
|
||||
return widget.getVariable("currentTiddler");
|
||||
case "accumulator":
|
||||
return "" + accumulator;
|
||||
case "index":
|
||||
return "" + index;
|
||||
case "revIndex":
|
||||
return "" + (results.length - 1 - index);
|
||||
case "length":
|
||||
return "" + results.length;
|
||||
default:
|
||||
return widget.getVariable(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if(list.length > 0) {
|
||||
accumulator = "" + list[0];
|
||||
}
|
||||
|
60
core/modules/filterrunprefixes/sort.js
Normal file
60
core/modules/filterrunprefixes/sort.js
Normal 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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
@ -137,7 +137,7 @@ exports.parseFilter = function(filterString) {
|
||||
p = 0, // Current position in the filter string
|
||||
match;
|
||||
var whitespaceRegExp = /(\s+)/mg,
|
||||
operandRegExp = /((?:\+|\-|~|=|\:(\w+))?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg;
|
||||
operandRegExp = /((?:\+|\-|~|=|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg;
|
||||
while(p < filterString.length) {
|
||||
// Skip any whitespace
|
||||
whitespaceRegExp.lastIndex = p;
|
||||
@ -162,15 +162,27 @@ exports.parseFilter = function(filterString) {
|
||||
if(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);
|
||||
} else {
|
||||
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(
|
||||
{operator: "title", operands: [{text: match[4] || match[5] || match[6]}]}
|
||||
{operator: "title", operands: [{text: match[5] || match[6] || match[7]}]}
|
||||
);
|
||||
}
|
||||
results.push(operation);
|
||||
@ -280,7 +292,7 @@ exports.compileFilter = function(filterString) {
|
||||
var filterRunPrefixes = self.getFilterRunPrefixes();
|
||||
// Wrap the operator functions in a wrapper function that depends on the prefix
|
||||
operationFunctions.push((function() {
|
||||
var options = {wiki: self};
|
||||
var options = {wiki: self, suffixes: operation.suffixes || []};
|
||||
switch(operation.prefix || "") {
|
||||
case "": // No prefix means that the operation is unioned into the result
|
||||
return filterRunPrefixes["or"](operationSubFunction, options);
|
||||
@ -311,6 +323,9 @@ exports.compileFilter = function(filterString) {
|
||||
} else if(typeof source === "object") { // Array or hashmap
|
||||
source = self.makeTiddlerIterator(source);
|
||||
}
|
||||
if(!widget) {
|
||||
widget = $tw.rootWidget;
|
||||
}
|
||||
var results = new $tw.utils.LinkedList();
|
||||
$tw.utils.each(operationFunctions,function(operationFunction) {
|
||||
operationFunction(results,source,widget);
|
||||
|
27
core/modules/filters/deserializers.js
Normal file
27
core/modules/filters/deserializers.js
Normal 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;
|
||||
};
|
||||
|
||||
})();
|
@ -102,7 +102,7 @@ exports.escapecss = function(source,operator,options) {
|
||||
var results = [];
|
||||
source(function(tiddler,title) {
|
||||
// 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;
|
||||
};
|
||||
|
@ -20,7 +20,18 @@ exports.filter = function(source,operator,options) {
|
||||
results = [],
|
||||
target = operator.prefix !== "!";
|
||||
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) {
|
||||
results.push(title);
|
||||
}
|
||||
|
@ -5,9 +5,11 @@ module-type: filteroperator
|
||||
|
||||
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(){
|
||||
@ -20,10 +22,35 @@ Prepends the prefix to the selected items and returns the specified field value
|
||||
Export our filter function
|
||||
*/
|
||||
exports.lookup = function(source,operator,options) {
|
||||
var results = [];
|
||||
source(function(tiddler,title) {
|
||||
results.push(options.wiki.getTiddlerText(operator.operand + title) || operator.suffix || '');
|
||||
});
|
||||
var results = [],
|
||||
suffixes = operator.suffixes || [],
|
||||
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;
|
||||
};
|
||||
|
||||
|
@ -125,6 +125,54 @@ exports.minall = makeNumericReducingOperator(
|
||||
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) {
|
||||
return function(source,operator,options) {
|
||||
var result = [],
|
||||
@ -134,19 +182,37 @@ function makeNumericBinaryOperator(fnCalc) {
|
||||
});
|
||||
return result;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
function makeNumericReducingOperator(fnCalc,initialValue) {
|
||||
function makeNumericReducingOperator(fnCalc,initialValue,fnFinal) {
|
||||
initialValue = initialValue || 0;
|
||||
return function(source,operator,options) {
|
||||
var result = [];
|
||||
source(function(tiddler,title) {
|
||||
result.push(title);
|
||||
result.push($tw.utils.parseNumber(title));
|
||||
});
|
||||
return [$tw.utils.stringifyNumber(result.reduce(function(accumulator,currentValue) {
|
||||
return fnCalc(accumulator,$tw.utils.parseNumber(currentValue));
|
||||
},initialValue))];
|
||||
var value = result.reduce(function(accumulator,currentValue) {
|
||||
return fnCalc(accumulator,currentValue);
|
||||
},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;
|
||||
};
|
||||
};
|
||||
|
||||
})();
|
||||
|
@ -31,6 +31,8 @@ exports.reduce = function(source,operator,options) {
|
||||
switch(name) {
|
||||
case "currentTiddler":
|
||||
return "" + title;
|
||||
case "..currentTiddler":
|
||||
return options.widget.getVariable("currentTiddler");
|
||||
case "accumulator":
|
||||
return "" + accumulator;
|
||||
case "index":
|
||||
|
@ -27,10 +27,13 @@ exports.sortsub = function(source,operator,options) {
|
||||
iterator(options.wiki.getTiddler(title),title);
|
||||
},{
|
||||
getVariable: function(name) {
|
||||
if(name === "currentTiddler") {
|
||||
return title;
|
||||
} else {
|
||||
return options.widget.getVariable(name);
|
||||
switch(name) {
|
||||
case "currentTiddler":
|
||||
return "" + title;
|
||||
case "..currentTiddler":
|
||||
return options.widget.getVariable("currentTiddler");
|
||||
default:
|
||||
return options.widget.getVariable(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -2,6 +2,7 @@
|
||||
title: $:/core/modules/macros/unusedtitle.js
|
||||
type: application/javascript
|
||||
module-type: macro
|
||||
|
||||
Macro to return a new title that is unused in the wiki. It can be given a name as a base.
|
||||
\*/
|
||||
(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 */
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
Information about this macro
|
||||
*/
|
||||
|
||||
exports.name = "unusedtitle";
|
||||
|
||||
exports.params = [
|
||||
{name: "baseName"},
|
||||
{name: "options"}
|
||||
{name: "separator"},
|
||||
{name: "template"}
|
||||
];
|
||||
|
||||
/*
|
||||
Run the macro
|
||||
*/
|
||||
exports.run = function(baseName, options) {
|
||||
exports.run = function(baseName,separator,template) {
|
||||
separator = separator || " ";
|
||||
if(!baseName) {
|
||||
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});
|
||||
};
|
||||
|
||||
})();
|
@ -23,10 +23,12 @@ var HtmlParser = function(type,text,options) {
|
||||
type: "element",
|
||||
tag: "iframe",
|
||||
attributes: {
|
||||
src: {type: "string", value: src},
|
||||
sandbox: {type: "string", value: ""}
|
||||
src: {type: "string", value: src}
|
||||
}
|
||||
}];
|
||||
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;
|
||||
|
@ -36,7 +36,7 @@ exports.parse = function() {
|
||||
// Move past the pragma invocation
|
||||
this.parser.pos = this.matchRegExp.lastIndex;
|
||||
// Parse the filter terminated by a line break
|
||||
var reMatch = /(.*)(\r?\n)|$/mg;
|
||||
var reMatch = /(.*)(?:$|\r?\n)/mg;
|
||||
reMatch.lastIndex = this.parser.pos;
|
||||
var match = reMatch.exec(this.parser.source);
|
||||
this.parser.pos = reMatch.lastIndex;
|
||||
|
@ -89,9 +89,12 @@ PutSaver.prototype.save = function(text,method,callback) {
|
||||
if(err) {
|
||||
// response is textual: "XMLHttpRequest error code: 412"
|
||||
var status = Number(err.substring(err.indexOf(':') + 2, err.length))
|
||||
if(status === 412) { // edit conflict
|
||||
var message = $tw.language.getString("Error/EditConflict");
|
||||
callback(message);
|
||||
if(status === 412) { // file changed on server
|
||||
callback($tw.language.getString("Error/PutEditConflict"));
|
||||
} 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 {
|
||||
callback(err); // fail
|
||||
}
|
||||
|
@ -17,9 +17,8 @@ exports.method = "GET";
|
||||
exports.path = /^\/favicon.ico$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": "image/x-icon"});
|
||||
var buffer = state.wiki.getTiddlerText("$:/favicon.ico","");
|
||||
response.end(buffer,"base64");
|
||||
state.sendResponse(200,{"Content-Type": "image/x-icon"},buffer,"base64");
|
||||
};
|
||||
|
||||
}());
|
||||
|
@ -34,10 +34,7 @@ exports.handler = function(request,response,state) {
|
||||
content = content;
|
||||
type = ($tw.config.fileExtensionInfo[extension] ? $tw.config.fileExtensionInfo[extension].type : "application/octet-stream");
|
||||
}
|
||||
response.writeHead(status,{
|
||||
"Content-Type": type
|
||||
});
|
||||
response.end(content);
|
||||
state.sendResponse(status,{"Content-Type": type},content);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -12,38 +12,16 @@ GET /
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var zlib = require("zlib");
|
||||
|
||||
exports.method = "GET";
|
||||
|
||||
exports.path = /^\/$/;
|
||||
|
||||
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")),
|
||||
responseHeaders = {
|
||||
"Content-Type": state.server.get("root-serve-type")
|
||||
};
|
||||
/*
|
||||
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);
|
||||
state.sendResponse(200,responseHeaders,text);
|
||||
};
|
||||
|
||||
}());
|
||||
|
@ -25,8 +25,9 @@ exports.handler = function(request,response,state) {
|
||||
response.end();
|
||||
} else {
|
||||
// Redirect to the root wiki if login worked
|
||||
var location = ($tw.syncadaptor && $tw.syncadaptor.host)? $tw.syncadaptor.host: "/";
|
||||
response.writeHead(302,{
|
||||
Location: "/"
|
||||
Location: location
|
||||
});
|
||||
response.end();
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ exports.method = "GET";
|
||||
exports.path = /^\/status$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
var text = JSON.stringify({
|
||||
username: state.authenticatedUsername || state.server.get("anon-username") || "",
|
||||
anonymous: !state.authenticatedUsername,
|
||||
@ -28,7 +27,7 @@ exports.handler = function(request,response,state) {
|
||||
},
|
||||
tiddlywiki_version: $tw.version
|
||||
});
|
||||
response.end(text,"utf8");
|
||||
state.sendResponse(200,{"Content-Type": "application/json"},text,"utf8");
|
||||
};
|
||||
|
||||
}());
|
||||
|
@ -32,9 +32,9 @@ exports.handler = function(request,response,state) {
|
||||
renderTemplate = renderTemplate || state.server.get("tiddler-render-template");
|
||||
}
|
||||
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
|
||||
response.writeHead(200);
|
||||
response.end(text,"utf8");
|
||||
state.sendResponse(200,{},text,"utf8");
|
||||
} else {
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
|
@ -36,8 +36,7 @@ exports.handler = function(request,response,state) {
|
||||
tiddlerFields.revision = state.wiki.getChangeCount(title);
|
||||
tiddlerFields.bag = "default";
|
||||
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
response.end(JSON.stringify(tiddlerFields),"utf8");
|
||||
state.sendResponse(200,{"Content-Type": "application/json"},JSON.stringify(tiddlerFields),"utf8");
|
||||
} else {
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
|
@ -33,7 +33,6 @@ exports.handler = function(request,response,state) {
|
||||
}
|
||||
var excludeFields = (state.queryParameters.exclude || "text").split(","),
|
||||
titles = state.wiki.filterTiddlers(filter);
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
var tiddlers = [];
|
||||
$tw.utils.each(titles,function(title) {
|
||||
var tiddler = state.wiki.getTiddler(title);
|
||||
@ -45,7 +44,7 @@ exports.handler = function(request,response,state) {
|
||||
}
|
||||
});
|
||||
var text = JSON.stringify(tiddlers);
|
||||
response.end(text,"utf8");
|
||||
state.sendResponse(200,{"Content-Type": "application/json"},text,"utf8");
|
||||
};
|
||||
|
||||
}());
|
||||
|
@ -17,7 +17,9 @@ if($tw.node) {
|
||||
fs = require("fs"),
|
||||
url = require("url"),
|
||||
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";
|
||||
// Initialize Gzip compression
|
||||
this.enableGzip = this.get("gzip") === "yes";
|
||||
// Initialize browser-caching
|
||||
this.enableBrowserCache = this.get("use-browser-cache") === "yes";
|
||||
// Initialise authorization
|
||||
var authorizedUserName = (this.get("username") && this.get("password")) ? this.get("username") : "(anon)";
|
||||
this.authorizationPrincipals = {
|
||||
@ -78,6 +82,71 @@ function Server(options) {
|
||||
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 = {
|
||||
port: "8080",
|
||||
host: "127.0.0.1",
|
||||
@ -89,7 +158,8 @@ Server.prototype.defaultVariables = {
|
||||
"system-tiddler-render-type": "text/plain",
|
||||
"system-tiddler-render-template": "$:/core/templates/wikified-tiddler",
|
||||
"debug-level": "none",
|
||||
"gzip": "no"
|
||||
"gzip": "no",
|
||||
"use-browser-cache": "no"
|
||||
};
|
||||
|
||||
Server.prototype.get = function(name) {
|
||||
@ -167,6 +237,7 @@ Server.prototype.requestHandler = function(request,response,options) {
|
||||
state.urlInfo = url.parse(request.url);
|
||||
state.queryParameters = querystring.parse(state.urlInfo.query);
|
||||
state.pathPrefix = options.pathPrefix || this.get("path-prefix") || "";
|
||||
state.sendResponse = sendResponse.bind(self,request,response);
|
||||
// Get the principals authorized to access this resource
|
||||
var authorizationType = this.methodMappings[request.method] || "readers";
|
||||
// Check for the CSRF header if this is a write
|
||||
|
@ -82,7 +82,7 @@ exports.startup = function() {
|
||||
var onlyThrottledTiddlersHaveChanged = true;
|
||||
for(var title in changes) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,10 @@ exports.domContains = function(a,b) {
|
||||
!!(a.compareDocumentPosition(b) & 16);
|
||||
};
|
||||
|
||||
exports.domMatchesSelector = function(node,selector) {
|
||||
return node.matches ? node.matches(selector) : node.msMatchesSelector(selector);
|
||||
};
|
||||
|
||||
exports.removeChildren = function(node) {
|
||||
while(node.hasChildNodes()) {
|
||||
node.removeChild(node.firstChild);
|
||||
|
@ -205,4 +205,16 @@ function parseJSONTiddlers(json,fallbackTitle) {
|
||||
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;
|
||||
};
|
||||
|
||||
})();
|
||||
|
@ -1,33 +1,28 @@
|
||||
/*\
|
||||
title: $:/core/modules/startup/CSSescape.js
|
||||
title: $:/core/modules/utils/escapecss.js
|
||||
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 */
|
||||
/*global $tw: false */
|
||||
/*global $tw: false, window: false */
|
||||
"use strict";
|
||||
|
||||
// Export name and synchronous status
|
||||
exports.name = "css-escape";
|
||||
exports.platforms = ["browser"];
|
||||
exports.after = ["startup"];
|
||||
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;
|
||||
exports.escapeCSS = (function() {
|
||||
// use browser's native CSS.escape() function if available
|
||||
if ($tw.browser && window.CSS && window.CSS.escape) {
|
||||
return window.CSS.escape;
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/cssom/#serialize-an-identifier
|
||||
var cssEscape = function(value) {
|
||||
// otherwise, a utility method is provided
|
||||
// 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) {
|
||||
throw new TypeError('`CSS.escape` requires an argument.');
|
||||
}
|
||||
@ -104,11 +99,6 @@ exports.startup = function() {factory(root);};
|
||||
}
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
|
||||
if (!root.CSS) {
|
||||
root.CSS = {};
|
||||
}
|
||||
|
||||
Object.getPrototypeOf(root.CSS).escape = cssEscape;
|
||||
|
||||
}));
|
||||
})();
|
@ -213,13 +213,13 @@ Options include:
|
||||
extFilters: optional array of filters to be used to generate the base path
|
||||
wiki: optional wiki for evaluating the pathFilters,
|
||||
fileInfo: an existing fileInfo to check against
|
||||
originalpath: a preferred filepath if no pathFilters match
|
||||
*/
|
||||
exports.generateTiddlerFileInfo = function(tiddler,options) {
|
||||
var fileInfo = {}, metaExt;
|
||||
// Propagate the isEditableFile flag
|
||||
if(options.fileInfo) {
|
||||
fileInfo.isEditableFile = options.fileInfo.isEditableFile || false;
|
||||
if(options.fileInfo && !!options.fileInfo.isEditableFile) {
|
||||
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
|
||||
var hasUnsafeFields = false;
|
||||
@ -247,7 +247,7 @@ exports.generateTiddlerFileInfo = function(tiddler,options) {
|
||||
fileInfo.hasMetaFile = true;
|
||||
}
|
||||
if(options.extFilters) {
|
||||
// Check for extension override
|
||||
// Check for extension overrides
|
||||
metaExt = $tw.utils.generateTiddlerExtension(tiddler.fields.title,{
|
||||
extFilters: options.extFilters,
|
||||
wiki: options.wiki
|
||||
@ -279,8 +279,7 @@ exports.generateTiddlerFileInfo = function(tiddler,options) {
|
||||
directory: options.directory,
|
||||
pathFilters: options.pathFilters,
|
||||
wiki: options.wiki,
|
||||
fileInfo: options.fileInfo,
|
||||
originalpath: options.originalpath
|
||||
fileInfo: options.fileInfo
|
||||
});
|
||||
return fileInfo;
|
||||
};
|
||||
@ -292,8 +291,7 @@ Options include:
|
||||
wiki: optional wiki for evaluating the extFilters
|
||||
*/
|
||||
exports.generateTiddlerExtension = function(title,options) {
|
||||
var self = this,
|
||||
extension;
|
||||
var extension;
|
||||
// Check if any of the extFilters applies
|
||||
if(options.extFilters && options.wiki) {
|
||||
$tw.utils.each(options.extFilters,function(filter) {
|
||||
@ -319,11 +317,10 @@ Options include:
|
||||
fileInfo: an existing fileInfo object to check against
|
||||
*/
|
||||
exports.generateTiddlerFilepath = function(title,options) {
|
||||
var self = this,
|
||||
directory = options.directory || "",
|
||||
var directory = options.directory || "",
|
||||
extension = options.extension || "",
|
||||
originalpath = options.originalpath || "",
|
||||
filepath;
|
||||
originalpath = (options.fileInfo && options.fileInfo.originalpath) ? options.fileInfo.originalpath : "",
|
||||
filepath;
|
||||
// Check if any of the pathFilters applies
|
||||
if(options.pathFilters && options.wiki) {
|
||||
$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
|
||||
var ext = path.extname(originalpath);
|
||||
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
|
||||
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)) {
|
||||
// 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(filepath.substring(filepath.length - extension.length) === extension) {
|
||||
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
|
||||
if(filepath.length > 200) {
|
||||
filepath = filepath.substr(0,200);
|
||||
}
|
||||
// Truncate the extension if it is too long
|
||||
if(extension.length > 32) {
|
||||
extension = extension.substr(0,32);
|
||||
}
|
||||
// If the resulting filename is blank (eg because the title is just punctuation characters)
|
||||
if(!filepath) {
|
||||
// If the resulting filename is blank (eg because the title is just punctuation)
|
||||
if(!filepath || /^_+$/g.test(filepath)) {
|
||||
// ...then just use the character codes of the title
|
||||
filepath = "";
|
||||
$tw.utils.each(title.split(""),function(char) {
|
||||
@ -386,14 +391,15 @@ exports.generateTiddlerFilepath = function(title,options) {
|
||||
count++;
|
||||
} while(fs.existsSync(fullPath));
|
||||
// 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,
|
||||
// then encodeURIComponent() and resolve to tiddler directory
|
||||
var writePath = $tw.hooks.invokeHook("th-make-tiddler-path",fullPath),
|
||||
// the resolved options.directory, the resolved wikiPath directory, the wikiTiddlersPath directory,
|
||||
// or the 'originalpath' directory, then encodeURIComponent() and resolve to tiddler directory.
|
||||
var writePath = $tw.hooks.invokeHook("th-make-tiddler-path",fullPath,fullPath),
|
||||
encode = (options.fileInfo || {writeError: false}).writeError == true;
|
||||
if(!encode) {
|
||||
encode = !(fullPath.indexOf(path.resolve(directory)) == 0 ||
|
||||
fullPath.indexOf(path.resolve($tw.boot.wikiPath)) == 0 ||
|
||||
fullPath.indexOf($tw.boot.wikiTiddlersPath) == 0);
|
||||
encode = !(writePath.indexOf($tw.boot.wikiTiddlersPath) == 0 ||
|
||||
writePath.indexOf(path.resolve(directory)) == 0 ||
|
||||
writePath.indexOf(path.resolve($tw.boot.wikiPath)) == 0 ||
|
||||
writePath.indexOf(path.resolve($tw.boot.wikiTiddlersPath,originalpath)) == 0 );
|
||||
}
|
||||
if(encode) {
|
||||
writePath = path.resolve(directory,encodeURIComponent(fullPath));
|
||||
@ -413,7 +419,7 @@ exports.saveTiddlerToFile = function(tiddler,fileInfo,callback) {
|
||||
if(fileInfo.hasMetaFile) {
|
||||
// Save the tiddler as a separate body and meta file
|
||||
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) {
|
||||
return callback(err);
|
||||
}
|
||||
@ -455,7 +461,7 @@ exports.saveTiddlerToFileSync = function(tiddler,fileInfo) {
|
||||
if(fileInfo.hasMetaFile) {
|
||||
// Save the tiddler as a separate body and meta file
|
||||
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");
|
||||
} else {
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
return fileInfo;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -294,6 +294,47 @@ exports.slowInSlowOut = function(t) {
|
||||
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) {
|
||||
var result = "",
|
||||
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,"&").replace(/</mg,"<").replace(/>/mg,">");
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
// Converts all HTML entities to their character equivalents
|
||||
exports.entityDecode = function(s) {
|
||||
var converter = String.fromCodePoint || String.fromCharCode,
|
||||
@ -867,7 +917,9 @@ exports.stringifyNumber = function(num) {
|
||||
|
||||
exports.makeCompareFunction = function(type,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,
|
||||
compare = function(a,b) {
|
||||
if(a > b) {
|
||||
@ -886,7 +938,11 @@ exports.makeCompareFunction = function(type,options) {
|
||||
return compare($tw.utils.parseInt(a),$tw.utils.parseInt(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) {
|
||||
var dateA = $tw.utils.parseDate(a),
|
||||
@ -901,6 +957,13 @@ exports.makeCompareFunction = function(type,options) {
|
||||
},
|
||||
"version": function(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);
|
||||
|
@ -44,9 +44,7 @@ ActionListopsWidget.prototype.execute = function() {
|
||||
*/
|
||||
ActionListopsWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(changedAttributes.$tiddler || changedAttributes.$filter ||
|
||||
changedAttributes.$subfilter || changedAttributes.$field ||
|
||||
changedAttributes.$index || changedAttributes.$tags) {
|
||||
if($tw.utils.count(changedAttributes) > 0) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
@ -60,12 +58,10 @@ ActionListopsWidget.prototype.invokeAction = function(triggeringWidget,
|
||||
//Apply the specified filters to the lists
|
||||
var field = this.listField,
|
||||
index,
|
||||
type = "!!",
|
||||
list = this.listField;
|
||||
if(this.listIndex) {
|
||||
field = undefined;
|
||||
index = this.listIndex;
|
||||
type = "##";
|
||||
list = this.listIndex;
|
||||
}
|
||||
if(this.filter) {
|
||||
@ -74,15 +70,14 @@ ActionListopsWidget.prototype.invokeAction = function(triggeringWidget,
|
||||
.filterTiddlers(this.filter, this)));
|
||||
}
|
||||
if(this.subfilter) {
|
||||
var subfilter = "[list[" + this.target + type + list + "]] " + this.subfilter;
|
||||
this.wiki.setText(this.target, field, index, $tw.utils.stringifyList(
|
||||
this.wiki
|
||||
.filterTiddlers(subfilter, this)));
|
||||
var inputList = this.wiki.getTiddlerList(this.target,field,index),
|
||||
subfilter = $tw.utils.stringifyList(inputList) + " " + this.subfilter;
|
||||
this.wiki.setText(this.target, field, index, $tw.utils.stringifyList(this.wiki.filterTiddlers(subfilter,this)));
|
||||
}
|
||||
if(this.filtertags) {
|
||||
var tiddler = this.wiki.getTiddler(this.target),
|
||||
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);
|
||||
if($tw.utils.stringifyList(oldtags.sort()) !== $tw.utils.stringifyList(newtags.sort())) {
|
||||
this.wiki.setText(this.target,"tags",undefined,$tw.utils.stringifyList(newtags));
|
||||
|
@ -37,6 +37,7 @@ Compute the internal state of the widget
|
||||
ActionPopupWidget.prototype.execute = function() {
|
||||
this.actionState = this.getAttribute("$state");
|
||||
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])
|
||||
},
|
||||
title: this.actionState,
|
||||
wiki: this.wiki
|
||||
wiki: this.wiki,
|
||||
floating: this.floating
|
||||
});
|
||||
} else {
|
||||
$tw.popup.cancel(0);
|
||||
|
@ -12,6 +12,8 @@ Dropzone widget
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var IMPORT_TITLE = "$:/Import";
|
||||
|
||||
var Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
|
||||
var DropZoneWidget = function(parseTreeNode,options) {
|
||||
@ -35,6 +37,7 @@ DropZoneWidget.prototype.render = function(parent,nextSibling) {
|
||||
this.execute();
|
||||
// Create element
|
||||
var domNode = this.document.createElement("div");
|
||||
this.domNode = domNode;
|
||||
domNode.className = this.dropzoneClass || "tc-dropzone";
|
||||
// Add event handlers
|
||||
if(this.dropzoneEnable) {
|
||||
@ -45,10 +48,8 @@ DropZoneWidget.prototype.render = function(parent,nextSibling) {
|
||||
{name: "drop", handlerObject: this, handlerMethod: "handleDropEvent"},
|
||||
{name: "paste", handlerObject: this, handlerMethod: "handlePasteEvent"},
|
||||
{name: "dragend", handlerObject: this, handlerMethod: "handleDragEndEvent"}
|
||||
]);
|
||||
]);
|
||||
}
|
||||
domNode.addEventListener("click",function (event) {
|
||||
},false);
|
||||
// Insert element
|
||||
parent.insertBefore(domNode,nextSibling);
|
||||
this.renderChildren(domNode,null);
|
||||
@ -57,12 +58,46 @@ DropZoneWidget.prototype.render = function(parent,nextSibling) {
|
||||
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) {
|
||||
if(this.currentlyEntered.indexOf(event.target) === -1) {
|
||||
this.currentlyEntered.push(event.target);
|
||||
}
|
||||
// If we're entering for the first time we need to apply highlighting
|
||||
$tw.utils.addClass(this.domNodes[0],"tc-dragover");
|
||||
if(!this.dragInProgress) {
|
||||
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) {
|
||||
@ -72,15 +107,17 @@ DropZoneWidget.prototype.leaveDrag = function(event) {
|
||||
}
|
||||
// Remove highlighting if we're leaving externally
|
||||
if(this.currentlyEntered.length === 0) {
|
||||
$tw.utils.removeClass(this.domNodes[0],"tc-dragover");
|
||||
this.resetState();
|
||||
}
|
||||
};
|
||||
|
||||
DropZoneWidget.prototype.handleDragEnterEvent = function(event) {
|
||||
// Check for this window being the source of the drag
|
||||
if($tw.dragInProgress) {
|
||||
return false;
|
||||
}
|
||||
if(this.filesOnly && !$tw.utils.dragEventContainsFiles(event)) {
|
||||
return false;
|
||||
}
|
||||
this.enterDrag(event);
|
||||
// Tell the browser that we're ready to handle the drop
|
||||
event.preventDefault();
|
||||
@ -99,7 +136,10 @@ DropZoneWidget.prototype.handleDragOverEvent = function(event) {
|
||||
}
|
||||
// Tell the browser that we're still interested in the drop
|
||||
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) {
|
||||
@ -107,13 +147,41 @@ DropZoneWidget.prototype.handleDragLeaveEvent = 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) {
|
||||
var self = this,
|
||||
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);
|
||||
// Check for being over a TEXTAREA or INPUT
|
||||
@ -127,7 +195,7 @@ DropZoneWidget.prototype.handleDropEvent = function(event) {
|
||||
var self = this,
|
||||
dataTransfer = event.dataTransfer;
|
||||
// Remove highlighting
|
||||
$tw.utils.removeClass(this.domNodes[0],"tc-dragover");
|
||||
this.resetState();
|
||||
// Import any files in the drop
|
||||
var numFiles = 0;
|
||||
if(dataTransfer.files) {
|
||||
@ -138,7 +206,23 @@ DropZoneWidget.prototype.handleDropEvent = function(event) {
|
||||
}
|
||||
// Try to import the various data types we understand
|
||||
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
|
||||
event.preventDefault();
|
||||
@ -149,7 +233,7 @@ DropZoneWidget.prototype.handleDropEvent = function(event) {
|
||||
DropZoneWidget.prototype.handlePasteEvent = function(event) {
|
||||
var self = this,
|
||||
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
|
||||
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") {
|
||||
// Create tiddlers from string items
|
||||
var type = item.type;
|
||||
var tiddlerFields,
|
||||
type = item.type;
|
||||
item.getAsString(function(str) {
|
||||
var tiddlerFields = {
|
||||
title: self.wiki.generateNewTitle("Untitled"),
|
||||
text: str,
|
||||
type: type
|
||||
};
|
||||
if($tw.log.IMPORT) {
|
||||
console.log("Importing string '" + str + "', type: '" + type + "'");
|
||||
// Use the deserializer specified if any
|
||||
if(self.dropzoneDeserializer) {
|
||||
tiddlerFields = self.wiki.deserializeTiddlers(null,str,{title: self.wiki.generateNewTitle("Untitled")},{deserializer:self.dropzoneDeserializer});
|
||||
if(tiddlerFields && tiddlerFields.length) {
|
||||
readFileCallback(tiddlerFields);
|
||||
}
|
||||
} 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.dropzoneEnable = (this.getAttribute("enable") || "yes") === "yes";
|
||||
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
|
||||
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) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(changedAttributes.enable || changedAttributes.autoOpenOnImport || changedAttributes.importTitle || changedAttributes.deserializer || changedAttributes.class) {
|
||||
if($tw.utils.count(changedAttributes) > 0) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
|
@ -47,38 +47,46 @@ EventWidget.prototype.render = function(parent,nextSibling) {
|
||||
domNode.addEventListener(type,function(event) {
|
||||
var selector = self.getAttribute("selector"),
|
||||
actions = self.getAttribute("actions-"+type),
|
||||
stopPropagation = self.getAttribute("stopPropagation","onaction"),
|
||||
selectedNode = event.target,
|
||||
selectedNodeRect,
|
||||
catcherNodeRect,
|
||||
variables = {};
|
||||
// Firefox can fire dragover and dragenter events on text nodes instead of their parents
|
||||
if(selectedNode.nodeType === 3) {
|
||||
selectedNode = selectedNode.parentNode;
|
||||
}
|
||||
if(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;
|
||||
}
|
||||
// If we found one, copy the attributes as variables, otherwise exit
|
||||
if(selectedNode.matches(selector)) {
|
||||
$tw.utils.each(selectedNode.attributes,function(attribute) {
|
||||
variables["dom-" + attribute.name] = attribute.value.toString();
|
||||
});
|
||||
//Add a variable with a popup coordinate string for the selected node
|
||||
variables["tv-popup-coords"] = "(" + selectedNode.offsetLeft + "," + selectedNode.offsetTop +"," + selectedNode.offsetWidth + "," + selectedNode.offsetHeight + ")";
|
||||
|
||||
//Add variables for offset of selected node
|
||||
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();
|
||||
if($tw.utils.domMatchesSelector(selectedNode,selector)) {
|
||||
// Only set up variables if we have actions to invoke
|
||||
if(actions) {
|
||||
$tw.utils.each(selectedNode.attributes,function(attribute) {
|
||||
variables["dom-" + attribute.name] = attribute.value.toString();
|
||||
});
|
||||
//Add a variable with a popup coordinate string for the selected node
|
||||
variables["tv-popup-coords"] = "(" + selectedNode.offsetLeft + "," + selectedNode.offsetTop +"," + selectedNode.offsetWidth + "," + selectedNode.offsetHeight + ")";
|
||||
|
||||
//Add variables for event X and Y position relative to selected node
|
||||
selectedNodeRect = selectedNode.getBoundingClientRect();
|
||||
variables["event-fromselected-posx"] = (event.clientX - selectedNodeRect.left).toString();
|
||||
variables["event-fromselected-posy"] = (event.clientY - selectedNodeRect.top).toString();
|
||||
//Add variables for offset of selected node
|
||||
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 event catcher node
|
||||
catcherNodeRect = self.domNode.getBoundingClientRect();
|
||||
variables["event-fromcatcher-posx"] = (event.clientX - catcherNodeRect.left).toString();
|
||||
variables["event-fromcatcher-posy"] = (event.clientY - catcherNodeRect.top).toString();
|
||||
//Add variables for event X and Y position relative to selected node
|
||||
selectedNodeRect = selectedNode.getBoundingClientRect();
|
||||
variables["event-fromselected-posx"] = (event.clientX - selectedNodeRect.left).toString();
|
||||
variables["event-fromselected-posy"] = (event.clientY - selectedNodeRect.top).toString();
|
||||
|
||||
//Add variables for event X and Y position relative to event catcher node
|
||||
catcherNodeRect = self.domNode.getBoundingClientRect();
|
||||
variables["event-fromcatcher-posx"] = (event.clientX - catcherNodeRect.left).toString();
|
||||
variables["event-fromcatcher-posy"] = (event.clientY - catcherNodeRect.top).toString();
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -106,6 +114,8 @@ EventWidget.prototype.render = function(parent,nextSibling) {
|
||||
variables["event-detail"] = event.detail.toString();
|
||||
}
|
||||
self.invokeActionString(actions,self,event,variables);
|
||||
}
|
||||
if((actions && stopPropagation === "onaction") || stopPropagation === "always") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return true;
|
||||
|
@ -67,7 +67,7 @@ FieldManglerWidget.prototype.handleRemoveFieldEvent = function(event) {
|
||||
deletion = {};
|
||||
deletion[event.param] = undefined;
|
||||
this.wiki.addTiddler(new $tw.Tiddler(tiddler,deletion));
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
FieldManglerWidget.prototype.handleAddFieldEvent = function(event) {
|
||||
@ -105,7 +105,7 @@ FieldManglerWidget.prototype.handleAddFieldEvent = function(event) {
|
||||
}
|
||||
}
|
||||
this.wiki.addTiddler(new $tw.Tiddler(tiddler,addition));
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
FieldManglerWidget.prototype.handleRemoveTagEvent = function(event) {
|
||||
@ -122,7 +122,7 @@ FieldManglerWidget.prototype.handleRemoveTagEvent = function(event) {
|
||||
this.wiki.addTiddler(new $tw.Tiddler(tiddler,modification));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
FieldManglerWidget.prototype.handleAddTagEvent = function(event) {
|
||||
@ -140,7 +140,7 @@ FieldManglerWidget.prototype.handleAddTagEvent = function(event) {
|
||||
tag.push(event.param.trim());
|
||||
this.wiki.addTiddler(new $tw.Tiddler({title: this.mangleTitle, tags: tag},modification));
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
exports.fieldmangler = FieldManglerWidget;
|
||||
|
@ -61,6 +61,7 @@ ListWidget.prototype.execute = function() {
|
||||
this.template = this.getAttribute("template");
|
||||
this.editTemplate = this.getAttribute("editTemplate");
|
||||
this.variableName = this.getAttribute("variable","currentTiddler");
|
||||
this.counterName = this.getAttribute("counter");
|
||||
this.storyViewName = this.getAttribute("storyview");
|
||||
this.historyTitle = this.getAttribute("history");
|
||||
// Compose the list elements
|
||||
@ -72,7 +73,7 @@ ListWidget.prototype.execute = function() {
|
||||
members = this.getEmptyMessage();
|
||||
} else {
|
||||
$tw.utils.each(this.list,function(title,index) {
|
||||
members.push(self.makeItemTemplate(title));
|
||||
members.push(self.makeItemTemplate(title,index));
|
||||
});
|
||||
}
|
||||
// Construct the child widgets
|
||||
@ -105,7 +106,7 @@ ListWidget.prototype.getEmptyMessage = function() {
|
||||
/*
|
||||
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
|
||||
var tiddler = this.wiki.getTiddler(title),
|
||||
isDraft = tiddler && tiddler.hasField("draft.of"),
|
||||
@ -128,7 +129,14 @@ ListWidget.prototype.makeItemTemplate = function(title) {
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
// 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();
|
||||
result = true;
|
||||
} else {
|
||||
@ -211,23 +219,41 @@ ListWidget.prototype.handleListChanges = function(changedTiddlers) {
|
||||
this.removeChildDomNodes();
|
||||
this.children = [];
|
||||
}
|
||||
// Cycle through the list, inserting and removing list items as needed
|
||||
var hasRefreshed = false;
|
||||
for(var 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);
|
||||
// If we are providing an counter variable then we must refresh the items, otherwise we can rearrange them
|
||||
var hasRefreshed = false,t;
|
||||
if(this.counterName) {
|
||||
// Cycle through the list and remove and re-insert the first item that has changed, and all the remaining items
|
||||
for(t=0; t<this.list.length; t++) {
|
||||
if(hasRefreshed || !this.children[t] || this.children[t].parseTreeNode.itemTitle !== this.list[t]) {
|
||||
if(this.children[t]) {
|
||||
this.removeListItem(t);
|
||||
}
|
||||
this.insertListItem(t,this.list[t]);
|
||||
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
|
||||
@ -257,7 +283,7 @@ Insert a new list item at the specified index
|
||||
*/
|
||||
ListWidget.prototype.insertListItem = function(index,title) {
|
||||
// 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
|
||||
this.children.splice(index,0,widget);
|
||||
var nextSibling = widget.findNextSiblingDomNode();
|
||||
@ -311,6 +337,11 @@ Compute the internal state of the widget
|
||||
ListItemWidget.prototype.execute = function() {
|
||||
// Set the current list item title
|
||||
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
|
||||
this.makeChildWidgets();
|
||||
};
|
||||
|
@ -170,7 +170,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
|
||||
SelectWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
// 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();
|
||||
return true;
|
||||
// If the target tiddler value has changed, just update setting and refresh the children
|
||||
|
@ -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
|
||||
this.makeChildWidgets(parseTreeNodes,{
|
||||
variables: variables
|
||||
});
|
||||
this.makeChildWidgets(parseTreeNodes);
|
||||
};
|
||||
|
||||
/*
|
||||
@ -112,7 +103,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
|
||||
*/
|
||||
TranscludeWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if($tw.utils.count(changedAttributes) || changedTiddlers[this.transcludeTitle]) {
|
||||
if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedTiddlers[this.transcludeTitle]) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
} else {
|
||||
|
@ -65,6 +65,9 @@ ViewWidget.prototype.execute = function() {
|
||||
case "htmlencoded":
|
||||
this.text = this.getValueAsHtmlEncoded();
|
||||
break;
|
||||
case "htmltextencoded":
|
||||
this.text = this.getValueAsHtmlTextEncoded();
|
||||
break;
|
||||
case "urlencoded":
|
||||
this.text = this.getValueAsUrlEncoded();
|
||||
break;
|
||||
@ -160,6 +163,10 @@ ViewWidget.prototype.getValueAsHtmlEncoded = function() {
|
||||
return $tw.utils.htmlEncode(this.getValueAsText());
|
||||
};
|
||||
|
||||
ViewWidget.prototype.getValueAsHtmlTextEncoded = function() {
|
||||
return $tw.utils.htmlTextEncode(this.getValueAsText());
|
||||
};
|
||||
|
||||
ViewWidget.prototype.getValueAsUrlEncoded = function() {
|
||||
return encodeURIComponent(this.getValueAsText());
|
||||
};
|
||||
|
@ -190,15 +190,25 @@ exports.getChangeCount = function(title) {
|
||||
|
||||
/*
|
||||
Generate an unused title from the specified base
|
||||
options.prefix must be a string
|
||||
*/
|
||||
exports.generateNewTitle = function(baseTitle,options) {
|
||||
options = options || {};
|
||||
var c = 0,
|
||||
title = baseTitle;
|
||||
while(this.tiddlerExists(title) || this.isShadowTiddler(title) || this.findDraft(title)) {
|
||||
title = baseTitle +
|
||||
(options.prefix || " ") +
|
||||
(++c);
|
||||
title = baseTitle,
|
||||
template = options.template,
|
||||
prefix = (typeof(options.prefix) === "string") ? options.prefix : " ";
|
||||
if (template) {
|
||||
// "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;
|
||||
};
|
||||
@ -211,6 +221,10 @@ exports.isTemporaryTiddler = function(title) {
|
||||
return title && title.indexOf("$:/temp/") === 0;
|
||||
};
|
||||
|
||||
exports.isVolatileTiddler = function(title) {
|
||||
return title && title.indexOf("$:/temp/volatile/") === 0;
|
||||
};
|
||||
|
||||
exports.isImageTiddler = function(title) {
|
||||
var tiddler = this.getTiddler(title);
|
||||
if(tiddler) {
|
||||
@ -364,12 +378,12 @@ exports.sortTiddlers = function(titles,sortField,isDescending,isCaseSensitive,is
|
||||
var tiddlerA = self.getTiddler(a),
|
||||
tiddlerB = self.getTiddler(b);
|
||||
if(tiddlerA) {
|
||||
a = tiddlerA.fields[sortField] || "";
|
||||
a = tiddlerA.getFieldString(sortField) || "";
|
||||
} else {
|
||||
a = "";
|
||||
}
|
||||
if(tiddlerB) {
|
||||
b = tiddlerB.fields[sortField] || "";
|
||||
b = tiddlerB.getFieldString(sortField) || "";
|
||||
} else {
|
||||
b = "";
|
||||
}
|
||||
@ -1495,6 +1509,13 @@ exports.invokeUpgraders = function(titles,tiddlers) {
|
||||
|
||||
// Determine whether a plugin by title is dynamically loadable
|
||||
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));
|
||||
};
|
||||
|
||||
@ -1539,4 +1560,3 @@ exports.slugify = function(title,options) {
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
|
@ -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.
|
||||
|
||||
-->`<div`<$fields template=' $name$="$encoded_value$"'></$fields>`>
|
||||
<pre>`<$view field="text" format="htmlencoded" />`</pre>
|
||||
<pre>`<$view field="text" format="htmltextencoded" />`</pre>
|
||||
</div>`
|
||||
|
@ -30,8 +30,6 @@ http://$(userName)$.tiddlyspot.com/$path$/
|
||||
|
||||
|<<lingo UserName>> |<$edit-text tiddler="$:/UploadName" default="" tag="input"/> |
|
||||
|<<lingo Password>> |<$password name="upload"/> |
|
||||
|<<lingo Backups>> |<<siteLink backup>> |
|
||||
|<<lingo ControlPanel>> |<<siteLink controlpanel>> |
|
||||
|
||||
''<<lingo Advanced/Heading>>''
|
||||
|
||||
|
@ -8,6 +8,7 @@ title: $:/core/ui/EditTemplate/body/editor
|
||||
tabindex={{$:/config/EditTabIndex}}
|
||||
focus={{{ [{$:/config/AutoFocus}match[text]then[true]] ~[[false]] }}}
|
||||
cancelPopups="yes"
|
||||
fileDrop={{{ [{$:/config/DragAndDrop/Enable}match[no]] :else[subfilter{$:/config/Editor/EnableImportFilter}then[yes]else[no]] }}}
|
||||
|
||||
><$set
|
||||
|
||||
@ -30,4 +31,12 @@ title: $:/core/ui/EditTemplate/body/editor
|
||||
tiddler="$:/core/ui/EditTemplate/body/toolbar/button"
|
||||
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>
|
||||
|
@ -35,22 +35,23 @@ title: $:/core/ui/EditTemplate/body/toolbar/button
|
||||
filter="[all[current]!has[dropdown]]"
|
||||
variable="no-dropdown"
|
||||
|
||||
><$button
|
||||
><$set name=disabled filter={{!!condition-disabled}}><$button
|
||||
|
||||
class="tc-btn-invisible $(buttonClasses)$"
|
||||
tooltip=<<tooltip-text>>
|
||||
actions={{!!actions}}
|
||||
disabled=<<disabled>>
|
||||
|
||||
><span
|
||||
|
||||
data-tw-keyboard-shortcut={{!!shortcuts}}
|
||||
data-tw-keyboard-shortcut={{{ [<disabled>match[yes]then[]else{!!shortcuts}] }}}
|
||||
|
||||
/><<toolbar-button-icon>><$transclude
|
||||
|
||||
tiddler=<<currentTiddler>>
|
||||
field="text"
|
||||
|
||||
/></$button></$list><$list
|
||||
/></$button></$set></$list><$list
|
||||
|
||||
filter="[all[current]has[dropdown]]"
|
||||
variable="dropdown"
|
||||
@ -60,24 +61,25 @@ title: $:/core/ui/EditTemplate/body/toolbar/button
|
||||
name="dropdown-state"
|
||||
value=<<qualify "$:/state/EditorToolbarDropdown">>
|
||||
|
||||
><$button
|
||||
><$set name=disabled filter={{!!condition-disabled}}><$button
|
||||
|
||||
popup=<<dropdown-state>>
|
||||
class="tc-popup-keep tc-btn-invisible $(buttonClasses)$"
|
||||
selectedClass="tc-selected"
|
||||
tooltip=<<tooltip-text>>
|
||||
actions={{!!actions}}
|
||||
disabled=<<disabled>>
|
||||
|
||||
><span
|
||||
|
||||
data-tw-keyboard-shortcut={{!!shortcuts}}
|
||||
data-tw-keyboard-shortcut={{{ [<disabled>match[yes]then[]else{!!shortcuts}] }}}
|
||||
|
||||
/><<toolbar-button-icon>><$transclude
|
||||
|
||||
tiddler=<<currentTiddler>>
|
||||
field="text"
|
||||
|
||||
/></$button><$reveal
|
||||
/></$button></$set><$reveal
|
||||
|
||||
state=<<dropdown-state>>
|
||||
type="popup"
|
||||
|
@ -5,6 +5,11 @@ tags: $:/tags/EditTemplate
|
||||
\define config-visibility-title()
|
||||
$:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
|
||||
\end
|
||||
|
||||
\define importFileActions()
|
||||
<$action-popup $state=<<importState>> $coords="(0,0,0,0)" $floating="yes"/>
|
||||
\end
|
||||
|
||||
<$list filter="[all[current]has[_canonical_uri]]">
|
||||
|
||||
<div class="tc-message-box">
|
||||
@ -20,9 +25,8 @@ $:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
|
||||
</$list>
|
||||
|
||||
<$list filter="[all[current]!has[_canonical_uri]]">
|
||||
|
||||
<$reveal state="$:/state/showeditpreview" type="match" text="yes">
|
||||
|
||||
<$vars importTitle=<<qualify $:/ImportImage>> importState=<<qualify $:/state/ImportImage>> >
|
||||
<$dropzone importTitle=<<importTitle>> autoOpenOnImport="no" contentTypesFilter={{$:/config/Editor/ImportContentTypesFilter}} class="tc-dropzone-editor" enable={{{ [{$:/config/DragAndDrop/Enable}match[no]] :else[subfilter{$:/config/Editor/EnableImportFilter}then[yes]else[no]] }}} filesOnly="yes" actions=<<importFileActions>> ><$reveal state="$:/state/showeditpreview" type="match" text="yes">
|
||||
<div class="tc-tiddler-preview">
|
||||
|
||||
<$transclude tiddler="$:/core/ui/EditTemplate/body/editor" mode="inline"/>
|
||||
@ -38,7 +42,6 @@ $:/config/EditorToolbarButtons/Visibility/$(currentTiddler)$
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</$reveal>
|
||||
|
||||
<$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"/>
|
||||
|
||||
</$reveal>
|
||||
|
||||
</$list>
|
||||
</$dropzone>
|
||||
</$vars>
|
||||
</$list>
|
@ -84,7 +84,7 @@ $value={{{ [<newFieldValueTiddler>get[text]] }}}/>
|
||||
|
||||
<$fieldmangler>
|
||||
<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>>
|
||||
</em>
|
||||
<$vars refreshTitle=<<qualify "$:/temp/fieldname/refresh">> storeTitle=<<newFieldNameInputTiddler>> searchListState=<<newFieldNameSelectionTiddler>>>
|
||||
|
@ -23,7 +23,7 @@ tags: $:/tags/EditTemplate
|
||||
|
||||
<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>
|
||||
|
||||
|
@ -7,7 +7,7 @@ first-search-filter: [all[shadows+tiddlers]prefix[$:/language/Docs/Types/]sort[d
|
||||
\whitespace trim
|
||||
<$set name="refreshTitle" value=<<qualify "$:/temp/type-search/refresh">>>
|
||||
<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"><$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>
|
||||
|
41
core/ui/EditorToolbar/file-import.tid
Normal file
41
core/ui/EditorToolbar/file-import.tid
Normal 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>
|
4
core/wiki/config/EditorEnableImportFilter.tid
Normal file
4
core/wiki/config/EditorEnableImportFilter.tid
Normal file
@ -0,0 +1,4 @@
|
||||
title: $:/config/Editor/EnableImportFilter
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
[all[current]type[text/vnd.tiddlywiki]] [all[current]!has[type]]
|
4
core/wiki/config/EditorImportContentTypesFilter.tid
Normal file
4
core/wiki/config/EditorImportContentTypesFilter.tid
Normal file
@ -0,0 +1,4 @@
|
||||
title: $:/config/Editor/ImportContentTypesFilter
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
[prefix[image/]]
|
@ -7,9 +7,9 @@ $:/core/images/storyview-$(storyview)$
|
||||
<div class="tc-chooser tc-viewswitcher">
|
||||
<$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>>>
|
||||
<$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>
|
||||
</$set>
|
||||
</$list>
|
||||
</div>
|
||||
</$linkcatcher>
|
||||
</$linkcatcher>
|
||||
|
@ -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]]//
|
||||
|
||||
<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:
|
||||
|
||||
* <<contributor Jermolene>>
|
||||
|
||||
</div>
|
||||
* <<contributor Arlen22
|
||||
* <<contributor BlueGreenMagick>>
|
||||
* <<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>>
|
||||
|
@ -750,6 +750,7 @@ function runTests(wiki) {
|
||||
rootWidget.setVariable("sort1","[length[]]");
|
||||
rootWidget.setVariable("sort2","[get[text]else[]length[]]");
|
||||
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("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");
|
||||
@ -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("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("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() {
|
||||
@ -796,6 +798,21 @@ function runTests(wiki) {
|
||||
expect(wiki.filterTiddlers("[[12]pad[9],[abc]]").join(",")).toBe("abcabca12");
|
||||
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");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -18,6 +18,199 @@ describe("general filter prefix tests", function() {
|
||||
var results = wiki.filterTiddlers("[tag[A]] :nonexistent[tag[B]]");
|
||||
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() {
|
||||
@ -60,7 +253,7 @@ describe("'reduce' and 'intersection' filter prefix tests", function() {
|
||||
wiki.addTiddler({
|
||||
title: "Red wine",
|
||||
tags: ["drinks", "wine", "textexample"],
|
||||
text: "This is some more text"
|
||||
text: "This is some more text!"
|
||||
});
|
||||
wiki.addTiddler({
|
||||
title: "Cheesecake",
|
||||
@ -72,6 +265,26 @@ describe("'reduce' and 'intersection' filter prefix tests", function() {
|
||||
tags: ["cakes", "food", "textexample"],
|
||||
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() {
|
||||
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
|
||||
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: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() {
|
||||
@ -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");
|
||||
});
|
||||
|
||||
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() {
|
||||
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");
|
||||
@ -116,11 +355,25 @@ describe("'reduce' and 'intersection' filter prefix tests", function() {
|
||||
rootWidget.makeChildWidgets();
|
||||
var anchorWidget = rootWidget.children[0];
|
||||
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]]",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-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");
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
@ -350,6 +350,123 @@ describe("Widget module", function() {
|
||||
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() {
|
||||
var wiki = new $tw.Wiki();
|
||||
// Add some tiddlers
|
||||
@ -585,6 +702,19 @@ describe("Widget module", function() {
|
||||
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.
|
||||
*
|
||||
* The importvariable widget was creating redundant copies into
|
||||
|
@ -6,7 +6,7 @@ type: text/vnd.tiddlywiki
|
||||
|
||||
//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
|
||||
|
||||
|
@ -1,17 +1,59 @@
|
||||
created: 20131101111400000
|
||||
modified: 20190115165616599
|
||||
modified: 20210402095728684
|
||||
tags: Community
|
||||
title: Contributing
|
||||
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
|
||||
* 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
|
||||
! Rules for Pull Requests
|
||||
|
||||
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
|
||||
|
||||
@ -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".//
|
||||
|
||||
!! 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.
|
||||
|
@ -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)
|
||||
<<<
|
@ -1,5 +1,5 @@
|
||||
created: 20140502213500000
|
||||
modified: 20160622111355787
|
||||
modified: 20210406131243532
|
||||
tags: Features Concepts
|
||||
title: PermaLinks
|
||||
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:
|
||||
|
||||
<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
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
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]]
|
||||
modified: 20201123192434277
|
||||
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: 20210519160636964
|
||||
tags: Reference Concepts
|
||||
title: SystemTags
|
||||
type: text/vnd.tiddlywiki
|
||||
|
@ -1,5 +1,5 @@
|
||||
created: 20190206140446821
|
||||
modified: 20190611155838557
|
||||
modified: 20210417090408263
|
||||
tags: Filters
|
||||
title: Mathematics Operators
|
||||
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)
|
||||
** <<.inline-operator-example "=1 =2 =3 =4 +[sum[]]">>
|
||||
** <<.inline-operator-example "=1 =2 =3 =4 +[product[]]">>
|
||||
** <<.inline-operator-example "=1 =2 =3 =4 +[average[]]">>
|
||||
|
||||
Operators can be combined:
|
||||
|
||||
|
13
editions/tw5.com/tiddlers/filters/average.tid
Normal file
13
editions/tw5.com/tiddlers/filters/average.tid
Normal 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">>
|
14
editions/tw5.com/tiddlers/filters/deserializers Operator.tid
Normal file
14
editions/tw5.com/tiddlers/filters/deserializers Operator.tid
Normal 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">>
|
@ -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[]]">>
|
@ -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[]]">>
|
@ -1,7 +1,10 @@
|
||||
created: 20170907144257037
|
||||
modified: 20170907144559822
|
||||
modified: 20201224034837935
|
||||
title: lookup Operator (Examples)
|
||||
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 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.">>
|
||||
|
@ -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[]]">>
|
@ -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[]]">>
|
@ -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[]]">>
|
@ -1,6 +1,6 @@
|
||||
caption: filter
|
||||
created: 20200929174420821
|
||||
modified: 20201027185144953
|
||||
modified: 20210522162551921
|
||||
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-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>
|
||||
```
|
||||
|
||||
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 analagous named filter run prefix `:filter`">>
|
||||
|
@ -1,24 +1,40 @@
|
||||
caption: lookup
|
||||
created: 20170907103639431
|
||||
modified: 20170907144703051
|
||||
modified: 20210116081305739
|
||||
op-input: a [[selection of titles|Title Selection]]
|
||||
op-output: the lookup values corresponding to each input title
|
||||
op-parameter: prefix applied to input titles to yield title of lookup tiddler from which value is retrieved
|
||||
op-parameter-name: P
|
||||
op-purpose: applies a prefix to each input title to yield the title of a tiddler from which the final value is retrieved
|
||||
op-suffix: the default value to be used for missing lookups
|
||||
op-suffix-name: D
|
||||
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. Now accepts 1 or 2 parameters, see below for details
|
||||
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. 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. This operator can now accept a second suffix, see below for details
|
||||
op-suffix-name: D, I
|
||||
tags: [[Filter Operators]]
|
||||
title: lookup Operator
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
<<.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
|
||||
* Transclude the value of each of those tiddlers
|
||||
** Substitute the default value for missing or empty tiddlers
|
||||
* Transclude the value of the `text` field each of those 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
|
||||
|
||||
<<.operator-examples "lookup">>
|
||||
|
13
editions/tw5.com/tiddlers/filters/median.tid
Normal file
13
editions/tw5.com/tiddlers/filters/median.tid
Normal 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">>
|
@ -1,6 +1,6 @@
|
||||
caption: reduce
|
||||
created: 20201004154131193
|
||||
modified: 20201208185109549
|
||||
modified: 20210522162536854
|
||||
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-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
|
||||
* ''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)
|
||||
* ''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
|
||||
|
@ -1,21 +1,23 @@
|
||||
caption: sortsub
|
||||
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]]
|
||||
title: sortsub Operator
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
@ -24,6 +26,7 @@ The suffix <<.place T>> determines how the items are compared and can be:
|
||||
* "integer" - invalid integers are interpreted as zero
|
||||
* "date" - invalid dates are interpreted as 1st January 1970
|
||||
* "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]...`.
|
||||
|
||||
|
@ -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">>
|
@ -1,5 +1,5 @@
|
||||
created: 20150124182421000
|
||||
modified: 20201214053032397
|
||||
modified: 20210522162642994
|
||||
tags: [[Filter Syntax]]
|
||||
title: Filter Expression
|
||||
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 `: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)]]
|
||||
* 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 `: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:
|
||||
|
||||
|!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
|
||||
|`-`, `~`, `=`, `: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!
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
created: 20201117073343969
|
||||
modified: 20201208185546667
|
||||
tags: [[Filter Syntax]]
|
||||
modified: 20210428084013109
|
||||
tags: [[Filter Syntax]] [[Filter Run Prefix Examples]]
|
||||
title: Filter Run Prefix (Examples)
|
||||
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]]`
|
||||
|
||||
<$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)]]
|
@ -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}]">>
|
@ -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)]]
|
15
editions/tw5.com/tiddlers/filters/variance Operator.tid
Normal file
15
editions/tw5.com/tiddlers/filters/variance Operator.tid
Normal 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">>
|
@ -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.
|
@ -1,10 +1,10 @@
|
||||
created: 20200315143638556
|
||||
modified: 20200315143638556
|
||||
modified: 20210519155433742
|
||||
tags: [[Hidden Settings]]
|
||||
title: Hidden Setting: Disable Drag and Drop
|
||||
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
|
||||
|
||||
|
@ -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.
|
@ -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.
|
@ -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
Loading…
x
Reference in New Issue
Block a user