1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-06-26 07:13:15 +00:00

Merge branch 'master' into publishing-framework

This commit is contained in:
jeremy@jermolene.com 2021-05-20 13:53:29 +01:00
commit 160c154ef1
101 changed files with 1865 additions and 369 deletions

View File

@ -5,52 +5,52 @@ Optimise the SVGs in ./core/images using SVGO from https://github.com/svg/svgo
Install SVGO with the following command in the root of the repo:
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\">>","&lt;&lt;now &quot;DD&quot;&gt;&gt;");
svgo.optimize(fakeSVG, {path: filepath}).then(function(result) {
config.path = filepath;
var result = optimize(fakeSVG,config);
if(result) {
var newSVG = header.join("\n") + "\n\n" + result.data.replace("&lt;&lt;now &quot;DD&quot;&gt;&gt;","<<now \"DD\">>");
fs.writeFileSync(filepath,newSVG);
},function(err) {
} else {
console.log("Error " + err + " with " + filename)
process.exit();
});
};
}
});

View File

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

View File

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

View File

@ -125,12 +125,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

View File

@ -31,6 +31,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.

View File

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

View File

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

View File

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

View File

@ -186,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") {
@ -217,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]) {
@ -297,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;
}

View File

@ -0,0 +1,58 @@
/*\
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;
default:
return widget.getVariable(name);
}
}
});
sortKeys.push(key[0] || "");
});
results.clear();
// Prepare an array of indexes to sort
for(var t=0; t<inputTitles.length; t++) {
indexes[t] = t;
}
// Sort the indexes
compareFn = $tw.utils.makeCompareFunction(sortType,{defaultType: "string", invert:invert, isCaseSensitive:isCaseSensitive});
indexes = indexes.sort(function(a,b) {
return compareFn(sortKeys[a],sortKeys[b]);
});
// Add to results in correct order
$tw.utils.each(indexes,function(index) {
results.push(inputTitles[index]);
});
}
}
};
})();

View File

@ -137,7 +137,7 @@ exports.parseFilter = function(filterString) {
p = 0, // Current position in the filter string
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);

View File

@ -20,7 +20,7 @@ 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]),options.widget);
if((list.length > 0) === target) {
results.push(title);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,38 +12,16 @@ GET /
/*global $tw: false */
"use strict";
var zlib = require("zlib");
exports.method = "GET";
exports.path = /^\/index.html$/;
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);
};
}());

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = {
@ -75,6 +79,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",
@ -86,7 +155,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) {
@ -196,6 +266,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

View File

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

View File

@ -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,
@ -876,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) {
@ -895,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),
@ -910,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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -170,7 +170,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
SelectWidget.prototype.refresh = function(changedTiddlers) {
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

View File

@ -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;
};
@ -364,12 +374,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 = "";
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,12 +25,31 @@ type: text/vnd.tiddlywiki
* <<.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
@ -40,6 +59,9 @@ type: text/vnd.tiddlywiki
* <<.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
@ -62,6 +84,7 @@ type: text/vnd.tiddlywiki
!! [[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
@ -71,6 +94,11 @@ type: text/vnd.tiddlywiki
* <<.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
@ -90,6 +118,7 @@ type: text/vnd.tiddlywiki
* <<.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
[[@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 Arlen22

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
created: 20140502213500000
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,17 @@
caption: sortsub
created: 20200424160155182
modified: 20200424160155182
modified: 20210428152533501
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.
@ -24,6 +24,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]...`.

View File

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

View File

@ -1,5 +1,5 @@
created: 20150124182421000
modified: 20201214053032397
modified: 20210428084144231
tags: [[Filter Syntax]]
title: Filter Expression
type: text/vnd.tiddlywiki
@ -26,6 +26,8 @@ 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">>
@ -47,7 +49,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!

View File

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

View File

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

View File

@ -0,0 +1,32 @@
created: 20210428083929749
modified: 20210428140713422
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 `:sort` filter run prefix uses an extended syntax that allows for multiple suffixes, some of which are required:
```
:sort:<type>:<flaglist>[...filter run...]
```
* ''type'': Required. Determines how the items are compared and can be any of: ''string'', ''alphanumeric'', ''number'', ''integer'', ''version'' or ''date''.
* ''flaglist'': comma separated list of the following flags:
** ''casesensitive'' or ''caseinsensitive'' (required for types `string` and `alphanumeric`).
** ''reverse'' to invert the order of the filter run (optional).
Note that filter runs used with the `:sort` prefix should return the same number of items that they are passed. Any missing entries will be treated as zero or the empty string. In particular, when retrieving the value of a field with the [[get Operator]] it is helpful to guard against a missing field value using the [[else Operator]]. For example `[get[myfield]else[default-value]...`.
[[Examples|Sort Filter Run Prefix (Examples)]]

View File

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

View File

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

View File

@ -1,10 +1,10 @@
created: 20200315143638556
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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
caption: unusedtitle
created: 20210104143546885
modified: 20210427184035684
tags: Macros [[Core Macros]]
title: unusedtitle Macro
type: text/vnd.tiddlywiki
@ -10,7 +11,30 @@ It uses the same method as the create new tiddler button, a number is appended t
!! Parameters
;baseName
: A string specifying the desired base name, defaulting to `New Tiddler`
; baseName
: A string specifying the desired base name, defaulting to `New Tiddler`. <br>The default setting can be adjusted in the $:/ControlPanel '': Info : Basics - tab.''
<<.macro-examples "unusedtitle">>
; separator
: <<.from-version "5.1.24">> An ''optional'' string specifying the separator between baseName and the unique number. eg: `separator:"-"`. Defaults to a space: `" "`. If you need an empty separator use the ''template''!
; template
: <<.from-version "5.1.24">> A ''optional'' template string can be used to allow you maximum flexibility. If the template string is used, there will always be a counter value.
!! Template String
; `$basename$`
: This variable will be replaced by the content of the ''baseName'' parameter
; `$separator$`
: This variable will be replaced by the ''separator'' parameter
;`$count$`
: This variable will be createad automatically and is a counter starting with 0
;`$count:4$`
: This variable will be createad automatically and starts at 0000
: `:4` represents the number of digits
!! Examples
<<list-links "[prefix[unusedtitle Macro (E]!sort[]]">>

View File

@ -0,0 +1,51 @@
created: 20210227212730299
modified: 20210427184057456
tags:
title: unusedtitle Macro (Examples 1)
type: text/vnd.tiddlywiki
\define testCreate()
<$action-createtiddler $basetitle=<<unusedtitle template:"$count:2$-new">>/>
\end
\define testCreate1()
<$action-createtiddler $basetitle=<<unusedtitle baseName:"new" separator:"-" template:"$count:2$$separator$$basename$">>/>
\end
\define testNew()
<$action-sendmessage $message="tm-new-tiddler" title=<<unusedtitle baseName:"new" template:"$count:2$-$basename$">> />
\end
```
<<unusedtitle template:"$count:2$-new">>
```
<$button actions=<<testCreate>> >
<$action-setfield $tiddler="$:/state/tab/sidebar--595412856" text="$:/core/ui/SideBar/Recent"/>
Create Tiddler
</$button>
```
<<unusedtitle baseName:"new" template:"$count:2$-$basename$">>
```
<$button actions=<<testNew>>>
<$action-setfield $tiddler="$:/state/tab/sidebar--595412856" text="$:/core/ui/SideBar/Recent"/>
New Tiddler
</$button>
```
<<unusedtitle baseName:"new" separator:"-" template:"$count:2$$separator$$basename$">>
```
<$button actions=<<testCreate1>>>
<$action-setfield $tiddler="$:/state/tab/sidebar--595412856" text="$:/core/ui/SideBar/Recent"/>
Create Tiddler
</$button>
---
<details>
<summary>Show the code</summary>
<pre><code><$view><pre><code>
</details>

View File

@ -1,7 +1,16 @@
created: 20210104143940715
modified: 20210228141241657
tags: [[unusedtitle Macro]] [[Macro Examples]]
title: unusedtitle Macro (Examples)
type: text/vnd.tiddlywiki
<$macrocall $name=".example" n="1" eg="""<<unusedtitle>>"""/>
<$macrocall $name=".example" n="2" eg="""<<unusedtitle AnotherBase>>"""/>
<$macrocall $name=".example" n="3" eg="""<<unusedtitle TiddlyWiki>>"""/>
''The following example works best if there is an open tiddler in draft mode, or there is a tiddler named "New Tiddler".'' So you can see the automatic numbering.
<$macrocall $name=".example" n="2" eg="""<<unusedtitle separator:"-">>"""/>
<$macrocall $name=".example" n="3" eg="""<<unusedtitle baseName:"anotherBase">>"""/>
<$macrocall $name=".example" n="4" eg="""<<unusedtitle baseName:"About" separator:"-">>"""/>
<$macrocall $name=".example" n="5" eg="""<<unusedtitle template:"$count:2$-test">>"""/>
---
Working buttons can be found at: [[unusedtitle Macro (Examples 1)]]. You'll have to examine the code to see, what's going on.

View File

@ -1,5 +1,5 @@
created: 20181002112106875
modified: 20181002124355314
modified: 20210418100750769
tags: [[WebServer API]]
title: WebServer API: Delete Tiddler
type: text/vnd.tiddlywiki
@ -14,6 +14,10 @@ Parameters:
* ''title'' - URI encoded title of the tiddler to delete
Headers:
* ''x-requested-with'' - must be set to `TiddlyWiki` in order for the request to succeed, unless [[WebServer Parameter: csrf-disable]] is set to `yes`
Response:
* 204 No Content

View File

@ -1,5 +1,5 @@
created: 20181002131341062
modified: 20181002131556452
modified: 20210418100750769
tags: [[WebServer API]]
title: WebServer API: Put Tiddler
type: text/vnd.tiddlywiki
@ -16,6 +16,10 @@ Parameters:
* ''title'' - URI encoded title of the tiddler to save
Headers:
* ''x-requested-with'' - must be set to `TiddlyWiki` in order for the request to succeed, unless [[WebServer Parameter: csrf-disable]] is set to `yes`
Response:
* 204 No Content

View File

@ -1,6 +1,6 @@
caption: action-popup
created: 20200303114556528
modified: 20200421221304177
modified: 20210501203451387
tags: Widgets ActionWidgets
title: ActionPopupWidget
type: text/vnd.tiddlywiki
@ -16,9 +16,12 @@ The ''action-popup'' widget is invisible. Any content within it is ignored.
|!Attribute |!Description |
|$state |The title of the state tiddler for the popup |
|$coords |Optional coordinates for the handle to which popup is positioned (in the format `(x,y,w,h)`) |
|$floating |<<.from-version "5.1.24">> Optional. Defaults to `no`. Set to `yes` to create a popup that must be closed explicitly. |
<<.from-version "5.1.23">> If the ''$coords'' attribute is missing or empty then all popups are cancelled.
<<.tip "Delete the state tiddler for a floating popup to close it.">>
! Examples
Here is an example of button that triggers the "more" button in the sidebar "Tools" tab. You may need to scroll to see the popup

View File

@ -1,6 +1,6 @@
caption: dropzone
created: 20131024141900000
modified: 20200403103224328
modified: 20210519154948743
tags: Widgets
title: DropzoneWidget
type: text/vnd.tiddlywiki
@ -16,9 +16,14 @@ It sends a [[WidgetMessage: tm-import-tiddlers]] carrying a JSON representation
|!Attribute |!Description |
|deserializer |<<.from-version "5.1.15">> Optional name of deserializer to be used (by default the deserializer is derived from the file extension) |
|enable |<<.from-version "5.1.22">> Optional value "no" to disable the dropzone functionality (defaults to "yes") |
|class |<<.from-version "5.1.22">> Optional CSS class to be assigned to the dropzone (defaults to "tc-drag-over") |
|class |<<.from-version "5.1.22">> Optional CSS class to be assigned to the DOM node created by the dropzone (defaults to "tc-dropzone") |
|autoOpenOnImport |<<.from-version "5.1.23">> Optional value "no" or "yes" that can override tv-auto-open-on-import |
|importTitle|<<.from-version "5.1.23">> optional tiddler title to use for import process instead of ~$:/Import |
|importTitle|<<.from-version "5.1.23">> Optional tiddler title to use for import process instead of ~$:/Import |
|actions|<<.from-version "5.1.24">> Optional actions string to be invoked after the `tm-import-tiddlers` message has been sent. The variable `importTitle` provides the title of the tiddler used for the import process. |
|contentTypesFilter |<<.from-version "5.1.24">> Optional filter that specifies the [[content types|ContentType]] accepted by the dropzone. |
|filesOnly|<<.from-version "5.1.24">> Optional. When set to "yes", the dropzone only accepts files and not string data. Defaults to "no" |
<<.tip """Use the `prefix` filter operator to easily accept multiple related content types. For example this filter will accept all image content types: `[prefix[image/]]`""">>
The list of available deserializers can be inspected by executing `Object.keys($tw.Wiki.tiddlerDeserializerModules).sort().join("\n")` in the browser JavaScript console.
@ -26,7 +31,7 @@ The dropzone widget displays any contained content within the dropzone.
! Display
The dropzone widget creates an HTML `<div class="tc-dropzone">` to contain its content. During a drag operation the class `tc-dragover` is added. CSS is used to provide user feedback.
The dropzone widget creates an HTML `<div class="tc-dropzone">` to contain its content. During a drag operation the class `tc-dragover` is added. CSS is used to provide user feedback. For custom styling of this state where a drag is in progress, specify a custom class name with the `class` attribute and use CSS with the selector `.myclass.tc-dragover`.
! Data types supported

View File

@ -1,6 +1,6 @@
caption: edit-text
created: 20131024141900000
modified: 20201130184701532
modified: 20210519154352055
tags: Widgets
title: EditTextWidget
type: text/vnd.tiddlywiki
@ -36,6 +36,7 @@ The content of the `<$edit-text>` widget is ignored.
|inputActions |<<.from-version 5.1.23>> Optional actions that are triggered every time an input event occurs within the input field or textarea |
|refreshTitle |<<.from-version 5.1.23>> An optional tiddler title that makes the input field update whenever the specified tiddler changes |
|disabled|<<.from-version "5.1.23">> Optional, disables the text input if set to "yes". Defaults to "no"|
|fileDrop|<<.from-version "5.1.24">> Optional. When set to "yes" allows dropping or pasting images into the editor to import them. Defaults to "no"|
! Notes

View File

@ -1,6 +1,6 @@
caption: list
created: 20131024141900000
modified: 20190608162410684
modified: 20210416175333981
tags: Widgets Lists
title: ListWidget
type: text/vnd.tiddlywiki
@ -82,10 +82,43 @@ The action of the list widget depends on the results of the filter combined with
|template |The title of a template tiddler for transcluding each tiddler in the list. When no template is specified, the body of the ListWidget serves as the item template. With no body, a simple link to the tiddler is returned. |
|editTemplate |An alternative template to use for [[DraftTiddlers|DraftMechanism]] in edit mode |
|variable |The name for a [[variable|Variables]] in which the title of each listed tiddler is stored. Defaults to ''currentTiddler'' |
|counter |<<.from-version "5.1.24">> Optional name for a [[variable|Variables]] in which the 1-based numeric index of each listed tiddler is stored (see below) |
|emptyMessage |Message to be displayed when the list is empty |
|storyview |Optional name of module responsible for animating/processing the list |
|history |The title of the tiddler containing the navigation history |
!! `counter` attribute
The optional `counter` attribute specifies the name of a variable to hold the 1-based numeric index of the current item in the list.
Two additional variables are also set to indicate the first and last items in the list:
* `<counter-variable-name>-first` is set to `yes` for the first entry in the list, `no` for the others
* `<counter-variable-name>-last` is set to `yes` for the last entry in the list, `no` for the others
For example:
```
<$list filter="[tag[About]sort[title]]" counter="counter">
<div>
<<counter>>: ''<$text text=<<currentTiddler>>/>'' (is first: <<counter-first>>, is last: <<counter-last>>)
</div>
</$list>
```
Displays as:
<<<
<$list filter="[tag[About]sort[title]]" counter="counter">
<div>
<<counter>>: ''<$text text=<<currentTiddler>>/>'' (is first: <<counter-first>>, is last: <<counter-last>>)
</div>
</$list>
<<<
Note that using the `counter` attribute degrades the performance of the list widget because it prevents the optimisation of refreshes by moving list items around instead of rerendering them.
!! Edit mode
The `<$list>` widget can optionally render draft tiddlers through a different template to handle editing, see DraftMechanism.

View File

@ -25,7 +25,7 @@ The message catcher widget
|!Variables |!Description |
|`event-*` |All string-based properties of the `event` object, with the names prefixed with `event-` |
|`event-paramObject-*` |All string-based properties of the `event.paramObject` object, with the names prefixed with `event-paramObject-` |
|`modifier` |For messages that originated with browser events, the modifier keys that were pressed when the event was fired. The possible modifiers are ''norma'' (no modifiers), ''ctrl'', ''ctrl-alt'', ''ctrl-shift'', ''alt'', ''alt-shift'', ''shift'' and ''ctrl-alt-shift'' |
|`modifier` |For messages that originated with browser events, the modifier keys that were pressed when the event was fired. The possible modifiers are ''normal'' (no modifiers), ''ctrl'', ''ctrl-alt'', ''ctrl-shift'', ''alt'', ''alt-shift'', ''shift'' and ''ctrl-alt-shift'' |
! Example
@ -39,4 +39,4 @@ The message catcher widget
Click on [[this link]] to fire an action. See the browser JavaScript console for the output
</$messagecatcher>
"""/>
"""/>

View File

@ -1,6 +1,6 @@
caption: Images
created: 20131205160221762
modified: 20160617100358365
modified: 20210519160846733
tags: WikiText [[Working with TiddlyWiki]]
title: Images in WikiText
type: text/vnd.tiddlywiki
@ -64,3 +64,5 @@ Renders as:
Use the <<.button import>> button (under the <<.sidebar-tab Tools>> tab in the sidebar), or drag and drop.
See [[ImportingTiddlers]] for details.
<<.from-version "5.1.24">> You can also import images by dropping or pasting images into the tiddler editor.

View File

@ -24,7 +24,6 @@ Encryption/RepeatPassword: كرر/ي كلمة السر
Encryption/SetPassword: ضبط كلمة السر
Encryption/Username: اسم المستخدم
Error/Caption: خطأ
Error/EditConflict: تم تغيير الملف على الخادم
Error/Filter: خطأ في التصفية
Error/FilterSyntax: خطأ في بناء الجملة في تعبير عامل التصفية
Error/IsFilterOperator: خطأ في التصفية: معامل غير معروف لعامل التصفية 'is'
@ -32,6 +31,7 @@ Error/IsFilterOperator: خطأ في التصفية: معامل غير معروف
Error/LoadingPluginLibrary: خطأ في تحميل مكتبة المكونات الإضافية
Error/NetworkErrorAlert: `<h2>''خطأ في الشبكة''</h2> يبدو أن الاتصال بالخادم قد انقطع. قد يشير هذا إلى وجود مشكلة في اتصال الشبكة. يرجى محاولة استعادة اتصال الشبكة قبل المتابعة. <br> <br> '' ستتم مزامنة أي تغييرات غير محفوظة تلقائيًا عند استعادة الاتصال ''.
Error/PutEditConflict: تم تغيير الملف على الخادم
Error/WhileSaving: حدث خطأ أثناء الحفظ
InternalJavaScriptError/Hint: حسنا، هذا أمر محرج. من المستحسن إعادة تشغيل تدلي ويكي عن طريق إنعاش المتصفح الخاص بك

View File

@ -22,12 +22,12 @@ Encryption/RepeatPassword: Repetiu la contrasenya
Encryption/SetPassword: Indiqueu la contrasenya
Encryption/Username: Usuari
Error/Caption: S'ha produït un error
Error/EditConflict: El fitxer ha canviat al servidor
Error/Filter: S'ha produït un error del filtre
Error/FilterSyntax: S'ha produït un error de sintaxi en l'expressió del filtre
Error/IsFilterOperator: S'ha produït un error del filtre: operant desconegut per a loperador de filtre "is"
Error/LoadingPluginLibrary: S'ha produït un error en carregar la biblioteca del connector
Error/NetworkErrorAlert: `<h2>''Error de la xarxa''</h2>Sembla que s'ha perdut la connexió amb el servidor. Això pot indicar un problema amb la vostra connexió de la xarxa. Intenteu restaurar la connectivitat de xarxa abans de continuar.<br><br>' Qualsevol canvi no guardat es sincronitzarà automàticament quan es restableixi la connectivitat''.'
Error/PutEditConflict: El fitxer ha canviat al servidor
Error/RecursiveTransclusion: S'ha produït un error de transclusió recursiva en el widget de transclusió
Error/RetrievingSkinny: S'ha produït un error en recuperar la llista de tiddler parcials
Error/SavingToTWEdit: S'ha produït un error en desar a TWEdit

View File

@ -31,5 +31,6 @@ Mögliche Parameter:
* ''tls-key'' - Pfad zur "TLS key" Datei (relativ zum Wiki Verzeichnis)
* ''debug-level'' - "debug" bewikt eine detailierte Anzeige der HTTP Anfrage-Parameter. (Standard: "none")
* ''gzip'' - Wenn auf "yes" gesetzt, dann wird gzip Kompression aktiviert. (Standard: "no")
* ''use-browser-cache'' - Ist dieser Parameter auf "yes" gesetzt kann der Browser Inhalte zwischenspeichern um Übertragungsbandbreite zu sparen. (Standard: "no")
Für weitere Sicherheitshinweise und Informationen für die Verwendung in lokalen Netzwerken siehe: WebServer auf TiddlyWiki.com

View File

@ -24,7 +24,6 @@ Encryption/RepeatPassword: Passwort wiederholen
Encryption/PasswordNoMatch: Passwörter stimmen nicht überein
Encryption/SetPassword: Passwort setzen
Error/Caption: Fehler
Error/EditConflict: Datei auf Server verändert
Error/Filter: Filter Fehler
Error/FilterSyntax: Syntax Fehler im Filter-Ausdruck
Error/FilterRunPrefix: Filter Fehler: Unbekanntes Prefix für Filter lauf
@ -32,6 +31,7 @@ Error/IsFilterOperator: Filter Fehler: Unbekannter Operand für den 'is' Filter
Error/FormatFilterOperator: Filter Fehler: Unbekannter Operand für den 'format' Filter Operator
Error/LoadingPluginLibrary: Fehler beim Laden der "plugin library"
Error/NetworkErrorAlert: `<h2>''Netzwerk Fehler''</h2>Es scheint, die Verbindung zum Server ist ausgefallen. Das weist auf Probleme mit der Netzwerkverbindung hin. Bitte versuchen Sie die Verbingung wider herzustellen, bevor Sie weitermachen.<br><br>''Nicht gespeicherte Änderungen werden automatich synchronisiert, sobald die Verbindung wider hergestellt ist.
Error/PutEditConflict: Datei auf Server verändert
Error/RecursiveTransclusion: Recursive Transclusion: Fehler im "transclude widget"
Error/RetrievingSkinny: Fehler beim Empfangen einer "skinny" Tiddler Liste
Error/SavingToTWEdit: Fehler beim Speichern mit "TWEdit"

View File

@ -22,11 +22,11 @@ Encryption/RepeatPassword: Επαναλάβατε το συνθηματικό
Encryption/SetPassword: Ορίστε το συνθηματικό
Encryption/Username: Όνομα χρήστη
Error/Caption: Σφάλμα
Error/EditConflict: Το αρχείο στον εξυπηρετητή είναι διαφορετικό
Error/Filter: Σφάλμα φίλτρου
Error/FilterSyntax: Συντακτικό σφάλμα στην έκφραση του φίλτρου
Error/IsFilterOperator: Σφάλμα φίλτρου: Άγνωστος τελεστέος για τον τελεστή φίλτρου 'is'
Error/LoadingPluginLibrary: Σφάλμα κατά την φόρτωση της βιβλιοθήκης του πρόσθετου
Error/PutEditConflict: Το αρχείο στον εξυπηρετητή είναι διαφορετικό
Error/RecursiveTransclusion: Σφάλμα αναδρομής σε transclusion στο transclude widget
Error/RetrievingSkinny: Σφάλμα κατά την ανάκληση της skinny tiddler λίστας
Error/SavingToTWEdit: Σφάλμα κατά την αποθήκευση στο TWEdit

View File

@ -22,11 +22,11 @@ Encryption/RepeatPassword: تکرار رمز
Encryption/SetPassword: تعیین رمز
Encryption/Username: نام کاربری
Error/Caption: خطا
Error/EditConflict: فایل در سرور عوض شد
Error/Filter: خطای فیلتر
Error/FilterSyntax: در نحوه‌ی بیان فیلتر خطای نحوی وجود داشت
Error/IsFilterOperator: خطای فیلتر: عملکرد ناشناخته‌ای برای عملگر 'is' مشخص شده
Error/LoadingPluginLibrary: خطا در بارگزاری کتاب‌خانه افزونه
Error/PutEditConflict: فایل در سرور عوض شد
Error/RecursiveTransclusion: Recursive transclusion error in transclude widget
Error/RetrievingSkinny: طا در دریافت لیست‌های تیدلر لاغر
Error/SavingToTWEdit: خطا در ذخیره در TWEdit

View File

@ -23,7 +23,6 @@ Encryption/RepeatPassword: Répéter le mot de passe
Encryption/PasswordNoMatch: Les mots de passe ne correspondent pas
Encryption/SetPassword: Définir ce mot de passe
Error/Caption: Erreur
Error/EditConflict: Le fichier a changé sur le serveur
Error/Filter: Erreur de filtre
Error/FilterSyntax: Erreur de syntaxe dans l'expression du filtre
Error/FilterRunPrefix: Erreur de filtre : Préfixe de run inconnu pour le filtre
@ -31,6 +30,7 @@ Error/IsFilterOperator: Erreur de filtre : Opérande inconnu pour l'opérateur d
Error/FormatFilterOperator: Erreur de filtre : Suffixe inconnu pour l'opérateur de filtre 'format'
Error/LoadingPluginLibrary: Erreur lors du chargement de la bibliothèque de plugins
Error/NetworkErrorAlert: `<h2>''Erreur Réseau''</h2>Il semble que la connexion au serveur soit perdue. Cela peut indiquer un problème avec votre connexion réseau. Essayez de rétablir la connectivité du réseau avant de continuer.<br><br>''Toute modification non enregistrée sera automatiquement synchronisée lorsque la connectivité sera rétablie''.`
Error/PutEditConflict: Le fichier a changé sur le serveur
Error/RecursiveTransclusion: Erreur dans le widget //transclude// : transclusion récursive
Error/RetrievingSkinny: Erreur pendant la récupération de la liste des tiddlers partiels
Error/SavingToTWEdit: Erreur lors de l'enregistrement vers TWEdit

View File

@ -23,7 +23,6 @@ Encryption/RepeatPassword: Herhaal wachtwoord
Encryption/SetPassword: Vul wachtwoord in
Encryption/Username: Gebruikersnaam
Error/Caption: Fout
Error/EditConflict: Bestand gewijzigd op server
Error/Filter: Filterfout
Error/FilterRunPrefix: Filterfout: Onbekend voorvoegsel voor filter 'run'
Error/FilterSyntax: Syntaxfout in filter expressie
@ -31,6 +30,7 @@ Error/FormatFilterOperator: Filterfout: Onbekend achtervoegsel voor de 'format'
Error/IsFilterOperator: Filterfout: Onbekende operand voor het 'is' filter
Error/LoadingPluginLibrary: Fout bij laden van de pluginbibliotheek
Error/NetworkErrorAlert: `<h2>''Network fout''</h2>De verbinding met de server lijkt verbroken. Mogelijk een probleem met de netwerkverbinding. Herstel de netwerkverbinding voordat verder wordt gegaan.<br><br>''Niet opgeslagen veranderingen worden gesynchroniseerd als de verbinding hersteld is''.`
Error/PutEditConflict: Bestand gewijzigd op server
Error/RecursiveTransclusion: Recursieve transclusiefout in 'transclude' widget
Error/RetrievingSkinny: Fout bij ophalen van de 'skinny' tiddlerlijst
Error/SavingToTWEdit: Fout bij opslaan naar TWEdit

View File

@ -23,11 +23,11 @@ Encryption/RepeatPassword: Repetir palavra passe
Encryption/SetPassword: Definir palavra passe
Encryption/Username: Nome de utilizador
Error/Caption: Erro
Error/EditConflict: File changed on server
Error/Filter: Erro de filtro
Error/FilterSyntax: Erro de sintaxe na expressão do filtro
Error/IsFilterOperator: Erro de Filtro: Operando desconhecido para o operador de filtro 'is'
Error/LoadingPluginLibrary: Erro ao carregar a biblioteca de extensões
Error/PutEditConflict: File changed on server
Error/RecursiveTransclusion: Erro de transclusão recursiva na widget de transclusão
Error/RetrievingSkinny: Erro ao obter a lista simples de tiddlers
Error/SavingToTWEdit: Erro ao gravar em TWEdit

View File

@ -22,11 +22,11 @@ Encryption/RepeatPassword: Ponovite geslo
Encryption/SetPassword: Nastavite geslo
Encryption/Username: Uporabniško ime
Error/Caption: Napaka
Error/EditConflict: Datoteka je spremenjena na strežniku
Error/Filter: Filter napaka
Error/FilterSyntax: Sintaktična napaka v izrazu filtra
Error/IsFilterOperator: Filter napaka: neznan operand za 'is' operator filtra
Error/LoadingPluginLibrary: Napaka pri nalaganju knjižnice vtičnikov
Error/PutEditConflict: Datoteka je spremenjena na strežniku
Error/RecursiveTransclusion: Rekurzivna napaka transkluzije v "transclude widget"
Error/RetrievingSkinny: Napaka pri pridobivanju "skinny" seznama tiddlerjev
Error/SavingToTWEdit: Napaka pri shranjevanju v TWEdit

View File

@ -124,12 +124,12 @@ Saving/TiddlySpot/BackupDir: 备份文件夹
Saving/TiddlySpot/Backups: 备份
Saving/TiddlySpot/Caption: ~TiddlySpot 保存模块
Saving/TiddlySpot/ControlPanel: ~TiddlySpot 控制台
Saving/TiddlySpot/Description: 这些设置只适用于保存到 http://tiddlyspot.com 或兼容的远程服务器时
Saving/TiddlySpot/Description: 这些设置只适用于保存到 [[TiddlySpot|http://tiddlyspot.com]]、[[TiddlyHost|https://tiddlyhost.com]],或兼容的远程服务器。有关 ~TiddlySpot 和 ~TiddlyHost 的保存设置信息,请参阅[[此处|https://github.com/simonbaird/tiddlyhost/wiki/TiddlySpot-Saver-configuration-for-Tiddlyhost-and-Tiddlyspot]]
Saving/TiddlySpot/Filename: 上传文件名
Saving/TiddlySpot/Heading: ~TiddlySpot
Saving/TiddlySpot/Hint: //默认之服务器网址 `http://<wikiname>.tiddlyspot.com/store.cgi`,可改为定制之服务器网址,例如 `http://example.com/store.php`。//
Saving/TiddlySpot/Password: 密码
Saving/TiddlySpot/ReadOnly: ~TiddlySpot 服务目前仅以唯读形式提供。相关最新详细信息,请参阅 http://tiddlyspot.com/ 。~TiddlySpot 保存模块仍可用于保存到兼容的服务器
Saving/TiddlySpot/ReadOnly: 请注意,[[TiddlySpot|http://tiddlyspot.com]] 不再允许创建新网站。对于新网站,您可以使用 [[TiddlyHost|https://tiddlyhost.com]],一个新的取代 ~TiddlySpot 的讬管服务
Saving/TiddlySpot/ServerURL: 服务器网址
Saving/TiddlySpot/UploadDir: 上传文件夹
Saving/TiddlySpot/UserName: 用户

View File

@ -30,5 +30,6 @@ listen 命令使用[[命名的命令参数|NamedCommandParameters]]
* ''tls-key'' - TLS 密钥文件的路径名(相对于维基文件夹)
* ''debug-level'' - 可选的调试级别;设置为 "debug" 来查看请求的详细信息;(默认为 "none")
* ''gzip'' - 设为 "yes" 以启用某些 http 端点的 gzip 压缩 (默认为 "no")
* ''use-browser-cache'' - 设置为 "yes" 允许浏览器缓存响应,以节省带宽(默认为 "no")
有关向整个本地网络开启实例的信息,以及可能的安全问题,请参阅 TiddlyWiki.com 的 WebServer 条目。

View File

@ -24,7 +24,6 @@ Encryption/RepeatPassword: 重复输入密码
Encryption/PasswordNoMatch: 密码不匹配
Encryption/SetPassword: 设定密码
Error/Caption: 错误
Error/EditConflict: 服务器上的文件已更改
Error/Filter: 筛选器错误
Error/FilterRunPrefix: 筛选器错误:筛选器 run 的未知首码
Error/FilterSyntax: 筛选器运算式中的语法错误
@ -32,6 +31,9 @@ Error/FormatFilterOperator: 筛选器错误:`format` 筛选器运算符的未
Error/IsFilterOperator: 筛选器错误︰'is' 筛选器运算符的未知操作数
Error/LoadingPluginLibrary: 加载插件程式库时,发生错误
Error/NetworkErrorAlert: `<h2>''网络错误''</h2>与服务器的连缐似乎已中断。这可能表示您的网络连缐有问题。请尝试恢复网路连缐才能继续。<br><br>''恢复连缐时,所有未保存的更改,将自动同步''。`
Error/PutEditConflict: 服务器上的文件已更改
Error/PutForbidden: 没有权限
Error/PutUnauthorized: 需要身分验证
Error/RecursiveTransclusion: 于 transclude 小部件中的递回嵌入错误
Error/RetrievingSkinny: 简要条目清单撷取错误
Error/SavingToTWEdit: 保存到 TWEdit 时,发生错误

View File

@ -124,12 +124,12 @@ Saving/TiddlySpot/BackupDir: 備份資料夾
Saving/TiddlySpot/Backups: 備份
Saving/TiddlySpot/Caption: ~TiddlySpot 儲存模組
Saving/TiddlySpot/ControlPanel: ~TiddlySpot 控制台
Saving/TiddlySpot/Description: 這些設定只適用於儲存到 http://tiddlyspot.com 或相容的遠端伺服器時
Saving/TiddlySpot/Description: 這些設定只適用於儲存到 [[TiddlySpot|http://tiddlyspot.com]]、[[TiddlyHost|https://tiddlyhost.com]],或相容的遠端伺服器。有關 ~TiddlySpot 和 ~TiddlyHost 的儲存設定資訊,請參閱[[此處|https://github.com/simonbaird/tiddlyhost/wiki/TiddlySpot-Saver-configuration-for-Tiddlyhost-and-Tiddlyspot]]
Saving/TiddlySpot/Filename: 上傳檔名
Saving/TiddlySpot/Heading: ~TiddlySpot
Saving/TiddlySpot/Hint: //預設之伺服器網址 `http://<wikiname>.tiddlyspot.com/store.cgi`,可改為自訂之伺服器網址,例如 `http://example.com/store.php`。//
Saving/TiddlySpot/Password: 密碼
Saving/TiddlySpot/ReadOnly: ~TiddlySpot 服務目前僅以唯讀形式提供。相關最新詳細資訊,請參閱 http://tiddlyspot.com/ 。~TiddlySpot 儲存模組仍可用於儲存到相容的伺服器
Saving/TiddlySpot/ReadOnly: 請注意,[[TiddlySpot|http://tiddlyspot.com]] 不再允許建立新網站。對於新網站,您可以使用 [[TiddlyHost|https://tiddlyhost.com]],一個新的取代 ~TiddlySpot 的託管服務
Saving/TiddlySpot/ServerURL: 伺服器網址
Saving/TiddlySpot/UploadDir: 上傳資料夾
Saving/TiddlySpot/UserName: 帳號

View File

@ -30,5 +30,6 @@ listen 命令使用[[命名的命令參數|NamedCommandParameters]]
* ''tls-key'' - TLS 密鑰檔案的路徑名(相對於維基資料夾)
* ''debug-level'' - 可選的偵錯層級;設定為 "debug" 來檢視請求的詳細資訊;(預設為 "none")
* ''gzip'' - 設為 "yes" 以啟用某些 http 端點的 gzip 壓縮 (預設為 "no")
* ''use-browser-cache'' - 設定為 "yes" ,允許瀏覽器快取回應,以節省頻寬(預設值為 "no"
有關向整個本地網路開啟實例的資訊,以及可能的安全問題,請參閱 TiddlyWiki.com 的 WebServer 條目。

View File

@ -24,7 +24,6 @@ Encryption/RepeatPassword: 重複輸入密碼
Encryption/PasswordNoMatch: 密碼不匹配
Encryption/SetPassword: 設定密碼
Error/Caption: 錯誤
Error/EditConflict: 伺服器上的檔案已更改
Error/Filter: 篩選器錯誤
Error/FilterRunPrefix: 篩選器錯誤:篩選器 run 的未知首碼
Error/FilterSyntax: 篩選器運算式中的語法錯誤
@ -32,6 +31,9 @@ Error/FormatFilterOperator: 篩選器錯誤:`format` 篩選器運算子的未
Error/IsFilterOperator: 篩選器錯誤︰'is' 篩選器運算子的未知運算元
Error/LoadingPluginLibrary: 載入插件程式庫時,發生錯誤
Error/NetworkErrorAlert: `<h2>''網路錯誤''</h2>與伺服器的連線似乎已中斷。這可能表示您的網路連線有問題。請嘗試恢復網路連線才能繼續。<br><br>''恢復連線時,所有未儲存的變更,將自動同步''。`
Error/PutEditConflict: 伺服器上的檔案已更改
Error/PutForbidden: 沒有權限
Error/PutUnauthorized: 需要身份驗證
Error/RecursiveTransclusion: 於 transclude 小工具中的遞迴嵌入錯誤
Error/RetrievingSkinny: 簡要條目清單擷取錯誤
Error/SavingToTWEdit: 儲存到 TWEdit 時,發生錯誤

View File

@ -39,7 +39,7 @@ exports["application/x-bibtex"] = function(text,fields) {
"bibtex-entry-type": entry.entryType
};
$tw.utils.each(entry.entryTags,function(value,name) {
fields["bibtex-" + name] = value;
fields["bibtex-" + name.toLowerCase()] = value;
});
results.push(fields);
});

View File

@ -124,8 +124,11 @@ function CodeMirrorEngine(options) {
self.widget.invokeActionString(self.widget.editInputActions);
}
});
this.cm.on("drop",function(cm,event) {
event.stopPropagation(); // Otherwise TW's dropzone widget sees the drop event
if(!self.widget.isFileDropEnabled) {
event.stopPropagation(); // Otherwise TW's dropzone widget sees the drop event
}
return false;
});
this.cm.on("keydown",function(cm,event) {
@ -136,6 +139,32 @@ function CodeMirrorEngine(options) {
$tw.popup.cancel(0);
}
});
// Add drag and drop event listeners if fileDrop is enabled
if(this.widget.isFileDropEnabled) {
// If the drag event contains Files, prevent the default CodeMirror handling
this.cm.on("dragenter",function(cm,event) {
if($tw.utils.dragEventContainsFiles(event)) {
event.preventDefault();
}
return true;
});
this.cm.on("dragleave",function(cm,event) {
event.preventDefault();
});
this.cm.on("dragover",function(cm,event) {
if($tw.utils.dragEventContainsFiles(event)) {
event.preventDefault();
}
});
this.cm.on("drop",function(cm,event) {
if($tw.utils.dragEventContainsFiles(event)) {
event.preventDefault();
}
});
this.cm.on("paste",function(cm,event) {
self.widget.handlePasteEvent.call(self.widget,event);
});
}
}
/*

View File

@ -40,14 +40,17 @@ function debounce(callback) {
}
function setupEvents(host) {
var events = new EventSource(host + "events/plugins/tiddlywiki/tiddlyweb");
var debouncedSync = debounce($tw.syncer.syncFromServer.bind($tw.syncer));
events.addEventListener("change",debouncedSync);
events.onerror = function() {
events.close();
setTimeout(function() {
setupEvents(host);
},$tw.syncer.errorRetryInterval);
};
if(window.EventSource) {
var events = new EventSource(host + "events/plugins/tiddlywiki/tiddlyweb");
var debouncedSync = debounce($tw.syncer.syncFromServer.bind($tw.syncer));
events.addEventListener("change",debouncedSync);
events.onerror = function() {
events.close();
setTimeout(function() {
setupEvents(host);
},$tw.syncer.errorRetryInterval);
};
}
}
})();

View File

@ -8,17 +8,17 @@ Placeholder for a more thorough refinement of Snow White
*/
@font-face {
font-family: "Arvo";
font-style: normal;
font-weight: 400;
src: local("Arvo"), url(<<datauri "$:/themes/tiddlywiki/starlight/arvo.woff">>) format("woff");
font-family: "Arvo";
font-style: normal;
font-weight: 400;
src: local("Arvo"), url(<<datauri "$:/themes/tiddlywiki/starlight/arvo.woff">>) format("woff");
}
html body, .tc-sidebar-scrollable-backdrop {
font-family: "Arvo", "Times";
background: url(<<datauri "$:/themes/tiddlywiki/starlight/ltbg.jpg">>);
background: url(<<datauri "$:/themes/tiddlywiki/starlight/ltbg.jpg">>);
}
.tc-page-controls svg {
<<filter "drop-shadow(1px 1px 2px rgba(255,255,255,0.9))">>
<<filter "drop-shadow(1px 1px 2px rgba(255,255,255,0.9))">>
}

View File

@ -82,17 +82,17 @@ tags: [[$:/tags/Stylesheet]]
}
html body.tc-body .tc-tiddler-frame .tc-tiddler-info {
margin: 0 -13px 0 -13px;
margin: 0 -13px 0 -13px;
}
html body.tc-body .tc-tiddler-frame .tc-fold-banner {
width: 13px;
margin-left: -15px;
width: 13px;
margin-left: -15px;
}
html body.tc-body .tc-tiddler-frame .tc-unfold-banner {
margin-left: -13px;
margin-top: -4px;
margin-left: -13px;
margin-top: -4px;
}
}

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