1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2026-01-25 04:14:40 +00:00

Compare commits

..

16 Commits

Author SHA1 Message Date
Jeremy Ruston
f41ed1c88c Add todos for the remaining docs 2026-01-23 09:03:35 +00:00
Jeremy Ruston
73cd48cada Release note 2026-01-22 21:01:39 +00:00
Jeremy Ruston
3b84a7042c Improve English 2026-01-22 20:43:54 +00:00
Jeremy Ruston
3f81e3651e Add test for invalid pragma 2026-01-22 20:43:54 +00:00
Jeremy Ruston
c4d0b61827 Fix parsing bug 2026-01-22 20:43:54 +00:00
Jeremy Ruston
adf9853ce4 More joy of linting 2026-01-20 15:54:10 +00:00
Jeremy Ruston
5f03d1e6ac Default to the filter run prefix "all" for certain widgets
See https://github.com/TiddlyWiki/TiddlyWiki5/pull/9595#issuecomment-3773451043
2026-01-20 15:49:44 +00:00
Jeremy Ruston
0ffb4d988b Fix comment
Co-authored-by: Saq Imtiaz <saq.imtiaz@gmail.com>
2026-01-20 15:43:04 +00:00
Jeremy Ruston
f2fbcf60a2 Kill linter error 2026-01-20 14:52:47 +00:00
Jeremy Ruston
8276f66edf Add filter run pragma for setting default filter run prefix 2026-01-20 14:49:07 +00:00
Jeremy Ruston
b6859c5a2d Add some tests 2026-01-20 12:10:47 +00:00
Jeremy Ruston
beb6839a35 Update filter caching to take into account the default filter run prefix 2026-01-20 12:01:12 +00:00
Jeremy Ruston
f3e9beb1e1 Comment update 2026-01-20 09:58:10 +00:00
Jeremy Ruston
2a1c607450 Update filter operator to support specifying a default filter run prefix 2026-01-19 21:23:43 +00:00
Jeremy Ruston
9b56734451 Extend subfilter operator with suffix for specifying the default filter run prefix 2026-01-19 11:31:09 +00:00
Jeremy Ruston
2f8d53d3f7 Allow default filter run prefix to be specified for filter evaluation 2026-01-19 11:30:50 +00:00
38 changed files with 364 additions and 312 deletions

View File

@@ -1,95 +0,0 @@
/*\
title: $:/core-server/modules/utils/escapecss.js
type: application/javascript
module-type: utils-node
Provides CSS.escape() functionality.
\*/
"use strict";
exports.escapeCSS = (function() {
// see also https://drafts.csswg.org/cssom/#serialize-an-identifier
/* eslint-disable */
/*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */
return function(value) {
if (arguments.length == 0) {
throw new TypeError('`CSS.escape` requires an argument.');
}
var string = String(value);
var length = string.length;
var index = -1;
var codeUnit;
var result = '';
var firstCodeUnit = string.charCodeAt(0);
while (++index < length) {
codeUnit = string.charCodeAt(index);
// Note: theres no need to special-case astral symbols, surrogate
// pairs, or lone surrogates.
// If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
// (U+FFFD).
if (codeUnit == 0x0000) {
result += '\uFFFD';
continue;
}
if (
// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
// U+007F, […]
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
// If the character is the first character and is in the range [0-9]
// (U+0030 to U+0039), […]
(index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
// If the character is the second character and is in the range [0-9]
// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
(
index == 1 &&
codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
firstCodeUnit == 0x002D
)
) {
// https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
result += '\\' + codeUnit.toString(16) + ' ';
continue;
}
if (
// If the character is the first character and is a `-` (U+002D), and
// there is no second character, […]
index == 0 &&
length == 1 &&
codeUnit == 0x002D
) {
result += '\\' + string.charAt(index);
continue;
}
// If the character is not handled by one of the above rules and is
// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
// U+005A), or [a-z] (U+0061 to U+007A), […]
if (
codeUnit >= 0x0080 ||
codeUnit == 0x002D ||
codeUnit == 0x005F ||
codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
codeUnit >= 0x0041 && codeUnit <= 0x005A ||
codeUnit >= 0x0061 && codeUnit <= 0x007A
) {
// the character itself
result += string.charAt(index);
continue;
}
// Otherwise, the escaped character.
// https://drafts.csswg.org/cssom/#escape-a-character
result += '\\' + string.charAt(index);
}
return result;
};
/* eslint-enable */
})();

View File

@@ -30,6 +30,7 @@ Error/DeserializeOperator/MissingOperand: Filter Error: Missing operand for 'des
Error/DeserializeOperator/UnknownDeserializer: Filter Error: Unknown deserializer provided as operand for the 'deserialize' operator
Error/Filter: Filter error
Error/FilterSyntax: Syntax error in filter expression
Error/FilterPragma: Filter Error: Unknown filter pragma
Error/FilterRunPrefix: Filter Error: Unknown prefix for filter run
Error/IsFilterOperator: Filter Error: Unknown parameter for the 'is' filter operator
Error/FormatFilterOperator: Filter Error: Unknown suffix for the 'format' filter operator

View File

@@ -146,14 +146,16 @@ exports.parseFilter = function(filterString) {
match;
var whitespaceRegExp = /(\s+)/mg,
// Groups:
// 1 - entire filter run prefix
// 2 - filter run prefix itself
// 3 - filter run prefix suffixes
// 4 - opening square bracket following filter run prefix
// 5 - double quoted string following filter run prefix
// 6 - single quoted string following filter run prefix
// 7 - anything except for whitespace and square brackets
operandRegExp = /((?:\+|\-|~|(?:=>?)|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+))/mg;
// 1 - pragma
// 2 - pragma suffix
// 3 - entire filter run prefix
// 4 - filter run prefix name
// 5 - filter run prefix suffixes
// 6 - opening square bracket following filter run prefix
// 7 - double quoted string following filter run prefix
// 8 - single quoted string following filter run prefix
// 9 - anything except for whitespace and square brackets
operandRegExp = /(?:::(\w+)(?:\:(\w+))?(?=\s|$)|((?:\+|\-|~|(?:=>?)|:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\s\[\]]+)))/mg;
while(p < filterString.length) {
// Skip any whitespace
whitespaceRegExp.lastIndex = p;
@@ -170,18 +172,23 @@ exports.parseFilter = function(filterString) {
};
match = operandRegExp.exec(filterString);
if(match && match.index === p) {
// If there is a filter run prefix
if(match[1]) {
operation.prefix = match[1];
// If there is a filter pragma
operation.pragma = match[1];
operation.suffix = match[2];
p = match.index + match[0].length;
} else if(match[3]) {
// If there is a filter run prefix
operation.prefix = match[3];
p = p + operation.prefix.length;
// Name for named prefixes
if(match[2]) {
operation.namedPrefix = match[2];
if(match[4]) {
operation.namedPrefix = match[4];
}
// Suffixes for filter run prefix
if(match[3]) {
if(match[5]) {
operation.suffixes = [];
$tw.utils.each(match[3].split(":"),function(subsuffix) {
$tw.utils.each(match[5].split(":"),function(subsuffix) {
operation.suffixes.push([]);
$tw.utils.each(subsuffix.split(","),function(entry) {
entry = $tw.utils.trim(entry);
@@ -193,7 +200,7 @@ exports.parseFilter = function(filterString) {
}
}
// Opening square bracket
if(match[4]) {
if(match[6]) {
p = parseFilterOperation(operation.operators,filterString,p);
} else {
p = match.index + match[0].length;
@@ -203,9 +210,9 @@ exports.parseFilter = function(filterString) {
p = parseFilterOperation(operation.operators,filterString,p);
}
// Quoted strings and unquoted title
if(match[5] || match[6] || match[7]) { // Double quoted string, single quoted string or unquoted title
if(match[7] || match[8] || match[9]) { // Double quoted string, single quoted string or unquoted title
operation.operators.push(
{operator: "title", operands: [{text: match[5] || match[6] || match[7]}]}
{operator: "title", operands: [{text: match[7] || match[8] || match[9]}]}
);
}
results.push(operation);
@@ -230,8 +237,8 @@ exports.getFilterRunPrefixes = function() {
return this.filterRunPrefixes;
}
exports.filterTiddlers = function(filterString,widget,source) {
var fn = this.compileFilter(filterString);
exports.filterTiddlers = function(filterString,widget,source,options) {
var fn = this.compileFilter(filterString,options);
try {
const fnResult = fn.call(this,source,widget);
return fnResult;
@@ -241,17 +248,21 @@ exports.filterTiddlers = function(filterString,widget,source) {
};
/*
Compile a filter into a function with the signature fn(source,widget) where:
Compile a filter into a function with the signature fn(source,widget,options) where:
source: an iterator function for the source tiddlers, called source(iterator), where iterator is called as iterator(tiddler,title)
widget: an optional widget node for retrieving the current tiddler etc.
options: optional hashmap of options
options.defaultFilterRunPrefix: the default filter run prefix to use when none is specified
*/
exports.compileFilter = function(filterString) {
exports.compileFilter = function(filterString,options) {
var defaultFilterRunPrefix = (options || {}).defaultFilterRunPrefix || "or";
var cacheKey = filterString + "|" + defaultFilterRunPrefix;
if(!this.filterCache) {
this.filterCache = Object.create(null);
this.filterCacheCount = 0;
}
if(this.filterCache[filterString] !== undefined) {
return this.filterCache[filterString];
if(this.filterCache[cacheKey] !== undefined) {
return this.filterCache[cacheKey];
}
var filterParseTree;
try {
@@ -330,7 +341,8 @@ exports.compileFilter = function(filterString) {
regexp: operator.regexp
},{
wiki: self,
widget: widget
widget: widget,
defaultFilterRunPrefix: defaultFilterRunPrefix
});
if($tw.utils.isArray(results)) {
accumulator = self.makeTiddlerIterator(results);
@@ -351,29 +363,45 @@ 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, suffixes: operation.suffixes || []};
switch(operation.prefix || "") {
case "": // No prefix means that the operation is unioned into the result
return filterRunPrefixes["or"](operationSubFunction, options);
case "=": // The results of the operation are pushed into the result without deduplication
return filterRunPrefixes["all"](operationSubFunction, options);
case "-": // The results of this operation are removed from the main result
return filterRunPrefixes["except"](operationSubFunction, options);
case "+": // This operation is applied to the main results so far
return filterRunPrefixes["and"](operationSubFunction, options);
case "~": // This operation is unioned into the result only if the main result so far is empty
return filterRunPrefixes["else"](operationSubFunction, options);
case "=>": // This operation is applied to the main results so far, and the results are assigned to a variable
return filterRunPrefixes["let"](operationSubFunction, options);
default:
if(operation.namedPrefix && filterRunPrefixes[operation.namedPrefix]) {
return filterRunPrefixes[operation.namedPrefix](operationSubFunction, options);
} else {
if(operation.pragma) {
switch(operation.pragma) {
case "defaultprefix":
defaultFilterRunPrefix = operation.suffix || "or";
break;
default:
return function(results,source,widget) {
results.clear();
results.push($tw.language.getString("Error/FilterRunPrefix"));
results.push($tw.language.getString("Error/FilterPragma"));
};
}
}
return function(results,source,widget) {
// Dummy response
};
} else {
var options = {wiki: self, suffixes: operation.suffixes || []};
switch(operation.prefix || "") {
case "": // Use the default filter run prefix if none is specified
return filterRunPrefixes[defaultFilterRunPrefix](operationSubFunction, options);
case "=": // The results of the operation are pushed into the result without deduplication
return filterRunPrefixes["all"](operationSubFunction, options);
case "-": // The results of this operation are removed from the main result
return filterRunPrefixes["except"](operationSubFunction, options);
case "+": // This operation is applied to the main results so far
return filterRunPrefixes["and"](operationSubFunction, options);
case "~": // This operation is unioned into the result only if the main result so far is empty
return filterRunPrefixes["else"](operationSubFunction, options);
case "=>": // This operation is applied to the main results so far, and the results are assigned to a variable
return filterRunPrefixes["let"](operationSubFunction, options);
default:
if(operation.namedPrefix && filterRunPrefixes[operation.namedPrefix]) {
return filterRunPrefixes[operation.namedPrefix](operationSubFunction, options);
} else {
return function(results,source,widget) {
results.clear();
results.push($tw.language.getString("Error/FilterRunPrefix"));
};
}
}
}
})());
});
@@ -412,7 +440,7 @@ exports.compileFilter = function(filterString) {
this.filterCache = Object.create(null);
this.filterCacheCount = 0;
}
this.filterCache[filterString] = fnMeasured;
this.filterCache[cacheKey] = fnMeasured;
this.filterCacheCount++;
return fnMeasured;
};

View File

@@ -13,7 +13,9 @@ Filter operator returning those input titles that pass a subfilter
Export our filter function
*/
exports.filter = function(source,operator,options) {
var filterFn = options.wiki.compileFilter(operator.operand),
var suffixes = operator.suffixes || [],
defaultFilterRunPrefix = (suffixes[0] || [options.defaultFilterRunPrefix] || [])[0] || "or",
filterFn = options.wiki.compileFilter(operator.operand,{defaultFilterRunPrefix}),
results = [],
target = operator.prefix !== "!";
source(function(tiddler,title) {

View File

@@ -13,7 +13,9 @@ Filter operator returning its operand evaluated as a filter
Export our filter function
*/
exports.subfilter = function(source,operator,options) {
var list = options.wiki.filterTiddlers(operator.operand,options.widget,source);
var suffixes = operator.suffixes || [],
defaultFilterRunPrefix = (suffixes[0] || [options.defaultFilterRunPrefix] || [])[0] || "or";
var list = options.wiki.filterTiddlers(operator.operand,options.widget,source,{defaultFilterRunPrefix});
if(operator.prefix === "!") {
var results = [];
source(function(tiddler,title) {

View File

@@ -1,7 +1,7 @@
/*\
title: $:/core/modules/utils/escapecss.js
type: application/javascript
module-type: utils-browser
module-type: utils
Provides CSS.escape() functionality.
@@ -9,6 +9,92 @@ Provides CSS.escape() functionality.
"use strict";
// TODO -- resolve this construction
exports.escapeCSS = (function() {
return window.CSS.escape;
// use browser's native CSS.escape() function if available
if ($tw.browser && window.CSS && window.CSS.escape) {
return window.CSS.escape;
}
// otherwise, a utility method is provided
// see also https://drafts.csswg.org/cssom/#serialize-an-identifier
/*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */
return function(value) {
if (arguments.length == 0) {
throw new TypeError('`CSS.escape` requires an argument.');
}
var string = String(value);
var length = string.length;
var index = -1;
var codeUnit;
var result = '';
var firstCodeUnit = string.charCodeAt(0);
while (++index < length) {
codeUnit = string.charCodeAt(index);
// Note: theres no need to special-case astral symbols, surrogate
// pairs, or lone surrogates.
// If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
// (U+FFFD).
if (codeUnit == 0x0000) {
result += '\uFFFD';
continue;
}
if (
// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
// U+007F, […]
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
// If the character is the first character and is in the range [0-9]
// (U+0030 to U+0039), […]
(index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
// If the character is the second character and is in the range [0-9]
// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
(
index == 1 &&
codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
firstCodeUnit == 0x002D
)
) {
// https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
result += '\\' + codeUnit.toString(16) + ' ';
continue;
}
if (
// If the character is the first character and is a `-` (U+002D), and
// there is no second character, […]
index == 0 &&
length == 1 &&
codeUnit == 0x002D
) {
result += '\\' + string.charAt(index);
continue;
}
// If the character is not handled by one of the above rules and is
// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
// U+005A), or [a-z] (U+0061 to U+007A), […]
if (
codeUnit >= 0x0080 ||
codeUnit == 0x002D ||
codeUnit == 0x005F ||
codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
codeUnit >= 0x0041 && codeUnit <= 0x005A ||
codeUnit >= 0x0061 && codeUnit <= 0x007A
) {
// the character itself
result += string.charAt(index);
continue;
}
// Otherwise, the escaped character.
// https://drafts.csswg.org/cssom/#escape-a-character
result += '\\' + string.charAt(index);
}
return result;
};
})();

View File

@@ -62,8 +62,8 @@ SendMessageWidget.prototype.invokeAction = function(triggeringWidget,event) {
var paramObject = Object.create(null);
// Add names/values pairs if present
if(this.actionNames && this.actionValues) {
var names = this.wiki.filterTiddlers(this.actionNames,this),
values = this.wiki.filterTiddlers(this.actionValues,this);
var names = this.wiki.filterTiddlers(this.actionNames,this,{defaultFilterRunPrefix: "all"}),
values = this.wiki.filterTiddlers(this.actionValues,this,{defaultFilterRunPrefix: "all"});
$tw.utils.each(names,function(name,index) {
paramObject[name] = values[index] || "";
});

View File

@@ -56,10 +56,10 @@ Invoke the action associated with this widget
*/
SetMultipleFieldsWidget.prototype.invokeAction = function(triggeringWidget,event) {
var tiddler = this.wiki.getTiddler(this.actionTiddler),
names, values = this.wiki.filterTiddlers(this.actionValues,this);
names, values = this.wiki.filterTiddlers(this.actionValues,this,{defaultFilterRunPrefix: "all"});
if(this.actionFields) {
var additions = {};
names = this.wiki.filterTiddlers(this.actionFields,this);
names = this.wiki.filterTiddlers(this.actionFields,this,{defaultFilterRunPrefix: "all"});
$tw.utils.each(names,function(fieldname,index) {
additions[fieldname] = values[index] || "";
});
@@ -68,7 +68,7 @@ SetMultipleFieldsWidget.prototype.invokeAction = function(triggeringWidget,event
this.wiki.addTiddler(new $tw.Tiddler(creationFields,tiddler,{title: this.actionTiddler},modificationFields,additions));
} else if(this.actionIndexes) {
var data = this.wiki.getTiddlerData(this.actionTiddler,Object.create(null));
names = this.wiki.filterTiddlers(this.actionIndexes,this);
names = this.wiki.filterTiddlers(this.actionIndexes,this,{defaultFilterRunPrefix: "all"});
$tw.utils.each(names,function(name,index) {
data[name] = values[index] || "";
});

View File

@@ -72,8 +72,8 @@ GenesisWidget.prototype.execute = function() {
this.attributeNames = [];
this.attributeValues = [];
if(this.genesisNames && this.genesisValues) {
this.attributeNames = this.wiki.filterTiddlers(self.genesisNames,this);
this.attributeValues = this.wiki.filterTiddlers(self.genesisValues,this);
this.attributeNames = this.wiki.filterTiddlers(self.genesisNames,this,{defaultFilterRunPrefix: "all"});
this.attributeValues = this.wiki.filterTiddlers(self.genesisValues,this,{defaultFilterRunPrefix: "all"});
$tw.utils.each(this.attributeNames,function(varname,index) {
$tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],varname,self.attributeValues[index] || "");
});
@@ -103,8 +103,8 @@ GenesisWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(),
filterNames = this.getAttribute("$names",""),
filterValues = this.getAttribute("$values",""),
attributeNames = this.wiki.filterTiddlers(filterNames,this),
attributeValues = this.wiki.filterTiddlers(filterValues,this);
attributeNames = this.wiki.filterTiddlers(filterNames,this,{defaultFilterRunPrefix: "all"}),
attributeValues = this.wiki.filterTiddlers(filterValues,this,{defaultFilterRunPrefix: "all"});
if($tw.utils.count(changedAttributes) > 0 || !$tw.utils.isArrayEqual(this.attributeNames,attributeNames) || !$tw.utils.isArrayEqual(this.attributeValues,attributeValues)) {
this.refreshSelf();
return true;

View File

@@ -7,6 +7,7 @@ This widget allows defining multiple variables at once, while allowing
the later variables to depend upon the earlier ones.
```
\define helloworld() Hello world!
<$let currentTiddler="target" value={{!!value}} currentTiddler="different">
{{!!value}} will be different from <<value>>
</$let>
@@ -55,7 +56,7 @@ LetWidget.prototype.computeAttributes = function() {
});
// Run through again, setting variables and looking for differences
$tw.utils.each(this.currentValueFor,function(value,name) {
if(self.attributes[name] === undefined || !$tw.utils.isArrayEqual(self.attributes[name],value)) {
if(!$tw.utils.isArrayEqual(self.attributes[name],value)) {
self.attributes[name] = value;
self.setVariable(name,value);
changedAttributes[name] = true;
@@ -67,7 +68,7 @@ LetWidget.prototype.computeAttributes = function() {
LetWidget.prototype.getVariableInfo = function(name,options) {
// Special handling: If this variable exists in this very $let, we can
// use it, but only if it's been staged.
if($tw.utils.hop(this.currentValueFor,name)) {
if ($tw.utils.hop(this.currentValueFor,name)) {
var value = this.currentValueFor[name];
return {
text: value[0] || "",

View File

@@ -12,7 +12,7 @@ Widget to set multiple variables at once from a list of names and a list of valu
var Widget = require("$:/core/modules/widgets/widget.js").widget;
var SetMultipleVariablesWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
this.initialise(parseTreeNode,options);
};
/*
@@ -24,52 +24,52 @@ SetMultipleVariablesWidget.prototype = new Widget();
Render this widget into the DOM
*/
SetMultipleVariablesWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
this.parentDomNode = parent;
this.computeAttributes();
this.execute();
this.renderChildren(parent,nextSibling);
};
/*
Compute the internal state of the widget
*/
SetMultipleVariablesWidget.prototype.execute = function() {
// Setup our variables
this.setVariables();
// Construct the child widgets
this.makeChildWidgets();
// Setup our variables
this.setVariables();
// Construct the child widgets
this.makeChildWidgets();
};
SetMultipleVariablesWidget.prototype.setVariables = function() {
// Set the variables
var self = this,
filterNames = this.getAttribute("$names",""),
filterValues = this.getAttribute("$values","");
this.variableNames = [];
this.variableValues = [];
if(filterNames && filterValues) {
this.variableNames = this.wiki.filterTiddlers(filterNames,this);
this.variableValues = this.wiki.filterTiddlers(filterValues,this);
$tw.utils.each(this.variableNames,function(varname,index) {
self.setVariable(varname,self.variableValues[index]);
});
}
// Set the variables
var self = this,
filterNames = this.getAttribute("$names",""),
filterValues = this.getAttribute("$values","");
this.variableNames = [];
this.variableValues = [];
if(filterNames && filterValues) {
this.variableNames = this.wiki.filterTiddlers(filterNames,this,{defaultFilterRunPrefix: "all"});
this.variableValues = this.wiki.filterTiddlers(filterValues,this,{defaultFilterRunPrefix: "all"});
$tw.utils.each(this.variableNames,function(varname,index) {
self.setVariable(varname,self.variableValues[index]);
});
}
};
/*
Refresh the widget by ensuring our attributes are up to date
*/
SetMultipleVariablesWidget.prototype.refresh = function(changedTiddlers) {
var filterNames = this.getAttribute("$names",""),
filterValues = this.getAttribute("$values",""),
variableNames = this.wiki.filterTiddlers(filterNames,this),
variableValues = this.wiki.filterTiddlers(filterValues,this);
if(!$tw.utils.isArrayEqual(this.variableNames,variableNames) || !$tw.utils.isArrayEqual(this.variableValues,variableValues)) {
this.refreshSelf();
return true;
}
return this.refreshChildren(changedTiddlers);
var filterNames = this.getAttribute("$names",""),
filterValues = this.getAttribute("$values",""),
variableNames = this.wiki.filterTiddlers(filterNames,this,{defaultFilterRunPrefix: "all"}),
variableValues = this.wiki.filterTiddlers(filterValues,this,{defaultFilterRunPrefix: "all"});
if(!$tw.utils.isArrayEqual(this.variableNames,variableNames) || !$tw.utils.isArrayEqual(this.variableValues,variableValues)) {
this.refreshSelf();
return true;
}
return this.refreshChildren(changedTiddlers);
};
exports["setmultiplevariables"] = SetMultipleVariablesWidget;

View File

@@ -15,7 +15,7 @@ caption: {{$:/language/SideBar/Open/Caption}}
\define droppable-item(button)
\whitespace trim
<$droppable actions=<<drop-actions>> enable=<<tv-enable-drag-and-drop>> tag="div">
<$droppable actions=<<drop-actions>> enable=<<tv-allow-drag-and-drop>> tag="div">
<<placeholder>>
<div>
$button$

View File

@@ -92,7 +92,7 @@ tags: $:/tags/Macro
</$set>
\end
\define list-tagged-draggable(tag,subFilter,emptyMessage,itemTemplate,elementTag:"div",storyview:"",displayField:"title")
\define list-tagged-draggable(tag,subFilter,emptyMessage,itemTemplate,elementTag:"div",storyview:"",displayField:"caption")
\whitespace trim
<span class="tc-tagged-draggable-list">
<$set name="tag" value=<<__tag__>>>
@@ -115,6 +115,7 @@ tags: $:/tags/Macro
<$view field="title"/>
</$transclude>
</$let>
<$view field="title"/>
</$link>
</$transclude>
</$genesis>

View File

@@ -21,7 +21,7 @@ second-search-filter: [subfilter<tagListFilter>is[system]search:title<userInput>
<!-- clean up temporary tiddlers, so the next "pick" starts with a clean input -->
<!-- This could probably be optimized / removed if we would use different temp-tiddlers
(future improvement because keeping track is complex for humans)
(future improvement because keeping track is comlex for humans)
-->
\procedure delete-tag-state-tiddlers()
<$action-deletetiddler $filter="[<newTagNameTiddler>] [<storeTitle>] [<tagSelectionState>]"/>
@@ -111,7 +111,6 @@ The second ESC tries to close the "draft tiddler"
refreshTitle=<<refreshTitle>>
selectionStateTitle=<<tagSelectionState>>
inputAcceptActions=<<add-tag-actions>>
inputAcceptVariantActions=<<save-tiddler-actions>>
inputCancelActions=<<clear-tags-actions>>
tag="input"
placeholder={{$:/language/EditTemplate/Tags/Add/Placeholder}}

View File

@@ -0,0 +1,21 @@
title: Filters/DefaultFilterRunPrefixPragma
description: Test Default Filter Run Prefix Pragma
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
\procedure mysubfilter() 1 1 +[join[Y]]
(<$text text={{{ ::defaultprefix:all 1 1 [subfilter<mysubfilter>] +[join[X]] }}}/>)
(<$text text={{{ 1 1 ::defaultprefix:all 1 1 +[join[X]] }}}/>)
(<$text text={{{ ::nonexistent X }}}/>)
+
title: ExpectedResult
<p>(1X1X1Y1)</p><p>(1X1X1)</p><p>(Filter Error: Unknown filter pragma)</p>

View File

@@ -0,0 +1,22 @@
title: Filters/Filter
description: Test filter operator
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
\procedure test-filter() 1 1 +[join[X]] +[!match<currentTiddler>]
(<$text text={{{ [filter<test-filter>] +[join[ ]] }}}/>)
(<$text text={{{ [filter:all<test-filter>] +[join[ ]] }}}/>)
+
title: 1X1
+
title: ExpectedResult
<p>($:/core 1X1 ExpectedResult Output)</p><p>($:/core ExpectedResult Output)</p>

View File

@@ -0,0 +1,20 @@
title: Filters/Subfilter
description: Test subfilter operator
type: text/vnd.tiddlywiki-multiple
tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
\procedure test-data() 1 2 1 3 3 4
(<$text text={{{ [subfilter<test-data>] +[join[ ]] }}}/>)
(<$text text={{{ [subfilter:all<test-data>] +[join[ ]] }}}/>)
+
title: ExpectedResult
<p>(2 1 3 4)</p><p>(1 2 1 3 3 4)</p>

View File

@@ -4,6 +4,8 @@ tags: Filters
title: Dominant Append
type: text/vnd.tiddlywiki
TODO:docs-default-prefix-for-subfilter: Description and link to new escape hatches
[[Filters]] manipulate [[sets of titles|Title Selection]] in which no title may appear more than once. Furthermore, they often need to append one such set to another.
This is done in such a way that, if a title would be duplicated, the earlier copy of that title is discarded. The titles being appended are dominant.

View File

@@ -12,6 +12,8 @@ tags: [[Filter Operators]] [[Negatable Operators]]
title: filter Operator
type: text/vnd.tiddlywiki
TODO:docs-default-prefix-for-subfilter: Description of new parameter
<<.from-version "5.1.23">> The <<.op filter>> operator runs a subfilter for each input title, and returns those input titles for which the subfilter returns a non-empty result (in other words the result is not an empty list). The results of the subfilter are thrown away.
Simple filter operations can be concatenated together directly (eg `[tag[HelloThere]search[po]]`) but this doesn't work when the filtering operations require intermediate results to be computed. The <<.op filter>> operator can be used to filter on an intermediate result which is discarded. To take the same example but to also filter by those tiddlers whose text field is longer than 1000 characters:

View File

@@ -12,6 +12,8 @@ tags: [[Filter Operators]] [[Field Operators]] [[Selection Constructors]] [[Nega
title: subfilter Operator
type: text/vnd.tiddlywiki
TODO:docs-default-prefix-for-subfilter: Description of new parameter
<<.from-version "5.1.18">> Note that the <<.op subfilter>> operator was introduced in version 5.1.18 and is not available in earlier versions.
<<.tip " Literal filter parameters cannot contain square brackets but you can work around the issue by using a variable:">>

View File

@@ -4,6 +4,8 @@ tags: [[Filter Syntax]]
title: Filter Expression
type: text/vnd.tiddlywiki
TODO:docs-default-prefix-for-subfilter: Update railroad diagram
A <<.def "filter expression">> is the outermost level of the [[filter syntax|Filter Syntax]]. It consists of [[filter runs|Filter Run]] with optional [[filter run prefixes|Filter Run Prefix]]. Multiple filter runs are separated by [[whitespace|Filter Whitespace]].
<$railroad text="""

View File

@@ -0,0 +1,11 @@
created: 20260122212128206
modified: 20260122212128206
tags: [[Filter Expression]]
title: Filter Pragma
type: text/vnd.tiddlywiki
TODO:docs-default-prefix-for-subfilter: High level pragma docs & railroad diagram
A <<.def "filter pragma">> is a special instruction embedded within a filter expression that affects the behavior of subsequent filter operations. Filter pragmas are similar to wikitext pragmas and are used to control aspects of filter evaluation.
The `::defaultprefix` pragma sets the default filter run prefix for the remainder of the filter expression. This allows users to control deduplication behavior without having to specify prefixes for each individual operation.

View File

@@ -4,6 +4,8 @@ tags: [[Filter Expression]]
title: Filter Run Prefix
type: text/vnd.tiddlywiki
TODO:docs-default-prefix-for-subfilter: Link to how the default filter run prefix can be specified
There are 2 types of filter run prefixes that are interchangeable; [[named prefixes|Named Filter Run Prefix]] and [[shortcut prefixes|Shortcut Filter Run Prefix]].
<$railroad text="""

View File

@@ -0,0 +1,13 @@
created: 20260122212128206
modified: 20260122212128206
tags: [[Filter Pragma]]
title: defaultprefix Filter Pragma
type: text/vnd.tiddlywiki
TODO:docs-default-prefix-for-subfilter: Purpose and docs of new pragma
A <<.def "defaultprefix filter pragma">> is used to set the default [[Filter Run Prefix]] for the remainder of the filter expression. This allows users to control deduplication behavior without having to specify prefixes for each individual operation.
Any of the [[named prefixes|Named Filter Run Prefix]] can be specified as the default prefix, but `all` is the most commonly used value to disable deduplication.

View File

@@ -1,17 +0,0 @@
change-category: nodejs
change-type: feature
created: 20260120154012282
description: Allows server routes to be prioritized via ordering.
github-contributors: saqimtiaz
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9206
modified: 20260120160656948
release: 5.4.0
tags: $:/tags/ChangeNote
title: $:/changenotes/5.4.0/#9206
type: text/vnd.tiddlywiki
This PR adds support for an info property to server route module exports. The info object may include a priority field, which determines the routes order of precedence.
Priorities are numeric and follow a descending order: routes with higher priority values are processed first, similar to how saver modules are prioritized.
To maintain backward compatibility with existing code, any module that omits info or info.priority is assigned a default priority of 100. Core server routes have been updated to explicitly use this default value of 100.

View File

@@ -1,13 +0,0 @@
change-category: nodejs
change-type: feature
created: 20260120154249928
description: Allows server routes to support multiple HTTP methods.
github-contributors: saqimtiaz
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9207
modified: 20260120160159661
release: 5.4.0
tags: $:/tags/ChangeNote
title: $:/changenotes/5.4.0/#9207
type: text/vnd.tiddlywiki
Allows server routes to support multiple HTTP methods by introducing an `exports.methods` array.

View File

@@ -1,13 +0,0 @@
change-category: widget
change-type: deprecation
created: 20260120154533983
description: The deprecated events and actions-* atrributes for the eventcatcher widget have been removed.
github-contributors: saqimtiaz
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9259
modified: 20260120160236297
release: 5.4.0
tags: $:/tags/ChangeNote
title: $:/changenotes/5.4.0/#9259
type: text/vnd.tiddlywiki
Deprecates and removes the `events` and `actions-*` attributes for the $eventcatcher widget.

View File

@@ -1,8 +0,0 @@
changenote: $:/changenotes/5.4.0/#9259
created: 20260120154817011
description: Deprecated events and actons-* attributes from the eventcatcher widget
impact-type: deprecation
modified: 20260120154900978
tags: $:/tags/ImpactNote
title: $:/changenotes/5.4.0/#9259/impacts/deprecate-eventcatcher-attributes
type: text/vnd.tiddlywiki

View File

@@ -1,29 +0,0 @@
change-category: hackability
change-type: feature
created: 20260120154445701
description: Adds info tiddlers for viewport dimensions that are updated on window resize.
github-contributors: saqimtiaz
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9260
modified: 20260120160515462
release: 5.4.0
tags: $:/tags/ChangeNote
title: $:/changenotes/5.4.0/#9260
type: text/vnd.tiddlywiki
Adds info tiddlers for viewport dimensions that are updated on window resize.
!! Example: Main and a user-created window with windowID my-window
|Window | Info tiddler | Meaning |h
|system/main |`$:/info/browser/window/system/main/outer/width` | Full browser window including chrome, tabs, toolbars |
|system/main |`$:/info/browser/window/system/main/outer/height` | Full browser window including chrome, tabs, toolbars |
|system/main |`$:/info/browser/window/system/main/inner/width` | Viewport width including scrollbars |
|system/main |`$:/info/browser/window/system/main/inner/height` | Viewport height including scrollbars |
|system/main |`$:/info/browser/window/system/main/client/width` | Content width excluding scrollbars |
|system/main |`$:/info/browser/window/system/main/client/height` | Content height excluding scrollbars |
|user/my-window |`$:/info/browser/window/user/my-window/outer/width` | Full browser window including chrome, tabs, toolbars |
|user/my-window |`$:/info/browser/window/user/my-window/outer/height` | Full browser window including chrome, tabs, toolbars |
|user/my-window |`$:/info/browser/window/user/my-window/inner/width` | Viewport width including scrollbars |
|user/my-window |`$:/info/browser/window/user/my-window/inner/height` | Viewport height including scrollbars |
|user/my-window |`$:/info/browser/window/user/my-window/client/width` | Content width excluding scrollbars |
|user/my-window |`$:/info/browser/window/user/my-window/client/height` | Content height excluding scrollbars |

View File

@@ -1,7 +1,17 @@
title: $:/changenotes/5.4.0/#9337/impacts/math-filters
title: $:/changenotes/5.4.0/#9337/compatibility-break/math-filters
changenote: $:/changenotes/5.4.0/#9337
created: 20251112152513384
modified: 20251209160312626
created - 20251112152513384
modified - 20251112152513384
tags: $:/tags/ImpactNote
description: filter output changes for some math filter operators when input list is empty
impact-type: compatibility-break
description: filter output with empty input changes for some math filter operators
impact-type: compatibility-break
These math operators will now output an empty list when the input list is empty:
* sum[] - previously returned 0
* product[] - previously returned 1
* maxall[] - previously returned -Infinity
* minall[] - previously returned Infinity
* average[] - previously returned NaN
* variance[] - previously returned NaN
* standard-deviation[] - previously returned NaN

View File

@@ -4,8 +4,7 @@ release: 5.4.0
tags: $:/tags/ChangeNote
change-type: enhancement
change-category: translation
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9375 https://github.com/TiddlyWiki/TiddlyWiki5/pull/9576
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9375
github-contributors: BramChen
* change camel-case hint text for chinese translations
* add alerts ARIA message
* change camel-case hint text for chinese translations

View File

@@ -1,10 +0,0 @@
title: $:/changenotes/5.4.0/#9494
description: Fix LetWidget to always set all staged variables on first render
release: 5.4.0
tags: $:/tags/ChangeNote
change-type: bugfix
change-category: widget
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9494
github-contributors: yaisog
This PR corrects a bug where the LetWidget did not set variables if their value was the empty output of a filtered transclusion.

View File

@@ -0,0 +1,34 @@
title: $:/changenotes/5.4.0/#9595
description: Easier avoidance of deduplication in filters
release: 5.4.0
tags: $:/tags/ChangeNote
change-type: enhancement
change-category: filters
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9595
github-contributors: Jermolene
Resolves a long-standing issue with filters where there has been no easy way to avoid deduplication of results during filter evaluation. There are two distinct new capabilities.
First, there is a new filter pragma `::defaultprefix` that sets the default filter run prefix for the remainder of the filter expression. Filter pragmas are a new concept akin to wikitext pragmas, allowing special instructions to be embedded in filter expressions.
The `::defaultprefix` pragma takes as its first parameter the desired default filter run prefix, followed by any number of filter operations to which it applies. The default prefix `all` can be used to disable deduplication for the subsequent operations (`or` is the default prefix that performs deduplication).
For example:
```
::defaultprefix:all 1 2 2 1 4 +[join[ ]]
```
Returns `1 2 2 1 4`.
Contrast to the result without the pragma which returns `2 1 4`:
```
1 2 2 1 4 +[join[ ]]
```
Second, there is a new parameter to the `subfilter` and `filter` operators to specify the default filter run prefix to use when the filter is evaluated. This overrides any default set with the `::defaultprefix` pragma.
```
[subfilter:all<my-subfilter>]
```

View File

@@ -1,10 +0,0 @@
title: $:/changenotes/5.4.0/#9600
description: Fix Ctrl-Enter not working in EditTemplate tag name input
release: 5.4.0
tags: $:/tags/ChangeNote
change-type: bugfix
change-category: internal
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9600
github-contributors: yaisog
The tag name input now calls `<<save-tiddler-actions>>` from the EditTemplate when Ctrl-Enter (or whichever key is assigned to input-accept-variant) is pressed.

View File

@@ -1,10 +0,0 @@
title: $:/changenotes/5.4.0/#9475
description: Split escapecss.js into two platforms
release: 5.4.0
tags: $:/tags/ChangeNote
change-type: performance
change-category: internal
github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9475
github-contributors: Leilei332
Split [[$:/core/modules/utils/escapecss.js]] for two platforms: one for the browser, the other for Node.js. The `CSS.escape` polyfill is moved to `core-server`.

View File

@@ -104,7 +104,7 @@ js.configs.recommended,
"init-declarations": "off",
"@stylistic/jsx-quotes": "error",
"@stylistic/key-spacing": "off",
"@stylistic/keyword-spacing": ["warn", {
"@stylistic/keyword-spacing": ["error", {
before: true,
after: false,
overrides: {
@@ -114,7 +114,6 @@ js.configs.recommended,
return: { after: true },
throw: { after: true },
try: { after: true },
const: { after: true }
},
}],
"@stylistic/line-comment-position": "off",

View File

@@ -1,6 +1,5 @@
title: $:/language/
Alerts: 警报信息
AboveStory/ClassicPlugin/Warning: 您似乎要加载为 ~TiddlyWiki 经典版设计的插件。请注意,[[这些插件无法运行于 TiddlyWiki 5.x.x 版|https://tiddlywiki.com/#TiddlyWikiClassic]]。检测到 ~TiddlyWiki 经典版插件:
BinaryWarning/Prompt: 此条目包含二进制数据
ClassicWarning/Hint: 此条目以经典版 TiddlyWiki 标记格式撰写,不完全兼容新版 TiddlyWiki 的格式详细信息请参阅https://tiddlywiki.com/static/Upgrading。

View File

@@ -1,6 +1,5 @@
title: $:/language/
Alerts: 警示訊息
AboveStory/ClassicPlugin/Warning: 您似乎要載入為 ~TiddlyWiki 經典版設計的插件。請注意,[[這些插件無法運行於 TiddlyWiki 5.x.x 版|https://tiddlywiki.com/#TiddlyWikiClassic]]。偵測到 ~TiddlyWiki 經典版插件:
BinaryWarning/Prompt: 此條目包含二進位資料
ClassicWarning/Hint: 此條目以經典版 TiddlyWiki 標記格式撰寫,不完全相容新版 TiddlyWiki 的格式詳細資訊請參閱https://tiddlywiki.com/static/Upgrading。