diff --git a/community/people/LinOnetwo.tid b/community/people/LinOnetwo.tid new file mode 100644 index 000000000..02b4c46e2 --- /dev/null +++ b/community/people/LinOnetwo.tid @@ -0,0 +1,19 @@ +avatar: /9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAgICAgJCAkKCgkNDgwODRMREBARExwUFhQWFBwrGx8bGx8bKyYuJSMlLiZENS8vNUROQj5CTl9VVV93cXecnNEBCAgICAkICQoKCQ0ODA4NExEQEBETHBQWFBYUHCsbHxsbHxsrJi4lIyUuJkQ1Ly81RE5CPkJOX1VVX3dxd5yc0f/CABEIACAAIAMBIgACEQEDEQH/xAAuAAEAAwEBAAAAAAAAAAAAAAAGAwQHAgUBAAMBAAAAAAAAAAAAAAAAAAACAwT/2gAMAwEAAhADEAAAAOfCWAMdKKetM4wOvY5OcvZnrYf/xAApEAACAQQBBAECBwAAAAAAAAABAgMABAURQQYSIVETFCIxMkJicYKh/9oACAEBAAE/AEtysaStr7mPaPeuazWdMM4gEnfPryW8hBUuZvou2RXRxyreBWPmgyNqs8f8MOQalhdY7Vz+R4/s/qfP+1edNi/zl7HDcFbmS3E8CcMR4INP0PkBhklIm+sZNtFtQiV0nj57Owl+dSrSTFgD6/CtH4VV9lU3oAbPngAVY389lc5URuUZkMxhnR4pvW0VwDqsP1FNmLWYqCpikMbngmliJNY+aKzyTxXS6lRAyg/u5rq+5x2RsuyTa3MQMlvKniRGThTUd1JYXUdzAwDvqVxGdRXMbfrVOD7HBrG3mNEsU8z98TRhl9eRzX//xAAcEQACAgIDAAAAAAAAAAAAAAABAgARAzESIVH/2gAIAQIBAT8ARuXZPsul3Eoje5lBQWBP/8QAGREAAwEBAQAAAAAAAAAAAAAAAAECEiER/9oACAEDAQE/AM98Lk7LJe20z//Z +created: 20251110102157310 +first-sighting: 2019-03-01 +fullname: Lin Onetwo +github: linonetwo +homepage: https://wiki.onetwo.website/ +modified: 20251111184556193 +tags: Community/Person Community/Team/Contributors +talk.tiddlywiki.org: linonetwo +title: @linonetwo +type: text/vnd.tiddlywiki + +Since 2014, when I started college, I've been on a quest for a lifelong PKM tool. I cherish my life and all my experiences, and I don’t want to forget any of them. When I’m deeply focused on a task, it’s easy to lose sight of other important parts of my life—so I needed a system to help me stay balanced. + +Early on, I tried TiddlyWiki several times, but I was initially put off by its save mechanism and markup editing. That changed when I discovered an auto-backup script, which gave me the confidence to fully commit. Over time, I improved the script and eventually transitioned to using TidGi-Desktop and TidGi-Mobile. + +Today, my TiddlyWiki holds all my game design ideas and progress logs—it has truly become my second brain. With the help of LLM-powered programming tools, I’ve enhanced it with numerous plugins, allowing me to manage my mind in a more programmable and structured way. As a game developer, TiddlyWiki isn't the core of my professional work; But I've invested so much time because it's fundamentally about upgrading my mind. + +Most of my notes are open by default and shared publicly on my homepage as a digital garden. diff --git a/community/project/teams/Quality Assurance Team.tid b/community/project/teams/Quality Assurance Team.tid new file mode 100644 index 000000000..093a0ca15 --- /dev/null +++ b/community/project/teams/Quality Assurance Team.tid @@ -0,0 +1,11 @@ +title: Quality Assurance Team +created: 20251112125742296 +modified: 20251112125742296 +tags: Community/Team +team: +leader: @Leilei332 + +title: Quality Assurance Team + +The Quality Assurance Team is responsible for ensuring the quality and reliability of TiddlyWiki releases. This includes reviewing code submissions, testing new features, identifying bugs, and verifying that fixes are effective. + diff --git a/community/project/teams/tagCommunityTeam.tid b/community/project/teams/tagCommunityTeam.tid index 9ae81d76f..426e98f6c 100644 --- a/community/project/teams/tagCommunityTeam.tid +++ b/community/project/teams/tagCommunityTeam.tid @@ -1,5 +1,5 @@ title: Community/Team modified: 20250909171928024 created: 20250909171928024 -list: [[Project Team]] [[Core Team]] [[Documentation Team]] [[MultiWikiServer Team]] [[Newsletter Team]] [[Infrastructure Team]] [[Succession Team]] +list: [[Project Team]] [[Core Team]] [[Documentation Team]] [[Quality Assurance Team]] [[Infrastructure Team]] [[MultiWikiServer Team]] [[Newsletter Team]] [[Succession Team]] diff --git a/core/modules/filterrunprefixes/let.js b/core/modules/filterrunprefixes/let.js new file mode 100644 index 000000000..b4dad57d8 --- /dev/null +++ b/core/modules/filterrunprefixes/let.js @@ -0,0 +1,41 @@ +/*\ +title: $:/core/modules/filterrunprefixes/let.js +type: application/javascript +module-type: filterrunprefix + +Assign a value to a variable + +\*/ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter prefix function +*/ +exports.let = function(operationSubFunction,options) { + // Return the filter run prefix function + return function(results,source,widget) { + // Save the result list + var resultList = results.toArray(); + // Clear the results + results.clear(); + // Evaluate the subfunction to get the variable name + var subFunctionResults = operationSubFunction(source,widget); + if(subFunctionResults.length === 0) { + return; + } + var name = subFunctionResults[0]; + if(typeof name !== "string" || name.length === 0) { + return; + } + // Assign the result of the subfunction to the variable + var variables = {}; + variables[name] = resultList; + // Return the variables + return { + variables: variables + }; + }; +}; diff --git a/core/modules/filters.js b/core/modules/filters.js index 213268b1a..56bc533cc 100644 --- a/core/modules/filters.js +++ b/core/modules/filters.js @@ -35,7 +35,7 @@ function parseFilterOperation(operators,filterString,p) { operator.prefix = filterString.charAt(p++); } // Get the operator name - nextBracketPos = filterString.substring(p).search(/[\[\{<\/]/); + nextBracketPos = filterString.substring(p).search(/[\[\{<\/\(]/); if(nextBracketPos === -1) { throw "Missing [ in filter expression"; } @@ -79,6 +79,10 @@ function parseFilterOperation(operators,filterString,p) { operand.variable = true; nextBracketPos = filterString.indexOf(">",p); break; + case "(": // Round brackets + operand.multiValuedVariable = true; + nextBracketPos = filterString.indexOf(")",p); + break; case "/": // regexp brackets var rex = /^((?:[^\\\/]|\\.)*)\/(?:\(([mygi]+)\))?/g, rexMatch = rex.exec(filterString.substring(p)); @@ -112,7 +116,7 @@ function parseFilterOperation(operators,filterString,p) { // Check for multiple operands while(filterString.charAt(p) === ",") { p++; - if(/^[\[\{<\/]/.test(filterString.substring(p))) { + if(/^[\[\{<\/\(]/.test(filterString.substring(p))) { nextBracketPos = p; p++; parseOperand(filterString.charAt(nextBracketPos)); @@ -141,7 +145,15 @@ exports.parseFilter = function(filterString) { p = 0, // Current position in the filter string match; var whitespaceRegExp = /(\s+)/mg, - operandRegExp = /((?:\+|\-|~|=|\:(\w+)(?:\:([\w\:, ]*))?)?)(?:(\[)|(?:"([^"]*)")|(?:'([^']*)')|([^\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; while(p < filterString.length) { // Skip any whitespace whitespaceRegExp.lastIndex = p; @@ -152,38 +164,45 @@ exports.parseFilter = function(filterString) { // Match the start of the operation if(p < filterString.length) { operandRegExp.lastIndex = p; - match = operandRegExp.exec(filterString); - if(!match || match.index !== p) { - throw $tw.language.getString("Error/FilterSyntax"); - } var operation = { prefix: "", operators: [] }; - if(match[1]) { - operation.prefix = match[1]; - p = p + operation.prefix.length; - 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); - } + match = operandRegExp.exec(filterString); + if(match && match.index === p) { + // If there is a filter run prefix + if(match[1]) { + operation.prefix = match[1]; + p = p + operation.prefix.length; + // Name for named prefixes + if(match[2]) { + operation.namedPrefix = match[2]; + } + // Suffixes for filter run prefix + 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); + } + }); }); - }); + } + } + // Opening square bracket + if(match[4]) { + p = parseFilterOperation(operation.operators,filterString,p); + } else { + p = match.index + match[0].length; } - } - if(match[4]) { // Opening square bracket - p = parseFilterOperation(operation.operators,filterString,p); } else { - p = match.index + match[0].length; + // No filter run prefix + 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 operation.operators.push( {operator: "title", operands: [{text: match[5] || match[6] || match[7]}]} @@ -251,6 +270,8 @@ exports.compileFilter = function(filterString) { results = []; $tw.utils.each(operation.operators,function(operator) { var operands = [], + multiValueOperands = [], + isMultiValueOperand = [], operatorFunction; if(!operator.operator) { // Use the "title" operator if no operator is specified @@ -266,13 +287,29 @@ exports.compileFilter = function(filterString) { if(operand.indirect) { var currTiddlerTitle = widget && widget.getVariable("currentTiddler"); operand.value = self.getTextReference(operand.text,"",currTiddlerTitle); + operand.multiValue = [operand.value]; } else if(operand.variable) { var varTree = $tw.utils.parseFilterVariable(operand.text); operand.value = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source})[0] || ""; + operand.multiValue = [operand.value]; + } else if(operand.multiValuedVariable) { + var varTree = $tw.utils.parseFilterVariable(operand.text); + var resultList = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source}); + if((resultList.length > 0 && resultList[0] !== undefined) || resultList.length === 0) { + operand.multiValue = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source}) || []; + operand.value = operand.multiValue[0] || ""; + } else { + operand.value = ""; + operand.multiValue = []; + } + operand.isMultiValueOperand = true; } else { operand.value = operand.text; + operand.multiValue = [operand.value]; } operands.push(operand.value); + multiValueOperands.push(operand.multiValue); + isMultiValueOperand.push(!!operand.isMultiValueOperand); }); // Invoke the appropriate filteroperator module @@ -280,6 +317,8 @@ exports.compileFilter = function(filterString) { operator: operator.operator, operand: operands.length > 0 ? operands[0] : undefined, operands: operands, + multiValueOperands: multiValueOperands, + isMultiValueOperand: isMultiValueOperand, prefix: operator.prefix, suffix: operator.suffix, suffixes: operator.suffixes, @@ -319,6 +358,8 @@ exports.compileFilter = function(filterString) { 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); @@ -345,7 +386,13 @@ exports.compileFilter = function(filterString) { self.filterRecursionCount = (self.filterRecursionCount || 0) + 1; if(self.filterRecursionCount < MAX_FILTER_DEPTH) { $tw.utils.each(operationFunctions,function(operationFunction) { - operationFunction(results,source,widget); + var operationResult = operationFunction(results,source,widget); + if(operationResult) { + if(operationResult.variables) { + // If the filter run prefix has returned variables, create a new fake widget with those variables + widget = widget.makeFakeWidgetWithVariables(operationResult.variables); + } + } }); } else { results.push("/**-- Excessive filter recursion --**/"); diff --git a/core/modules/filters/function.js b/core/modules/filters/function.js index b6f2fa636..cf7a6bb0d 100644 --- a/core/modules/filters/function.js +++ b/core/modules/filters/function.js @@ -16,8 +16,8 @@ exports.function = function(source,operator,options) { var functionName = operator.operands[0], params = [], results; - $tw.utils.each(operator.operands.slice(1),function(param) { - params.push({value: param}); + $tw.utils.each(operator.multiValueOperands.slice(1),function(paramList) { + params.push({value: paramList[0] || "",multiValue: paramList}); }); // console.log(`Calling ${functionName} with params ${JSON.stringify(params)}`); var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(functionName,{params: params, source: source}); diff --git a/core/modules/filters/title.js b/core/modules/filters/title.js index a1dff909b..228676da4 100644 --- a/core/modules/filters/title.js +++ b/core/modules/filters/title.js @@ -16,12 +16,13 @@ exports.title = function(source,operator,options) { var results = []; if(operator.prefix === "!") { source(function(tiddler,title) { - if(tiddler && tiddler.fields.title !== operator.operand) { + var titleList = operator.multiValueOperands[0] || []; + if(tiddler && titleList.indexOf(tiddler.fields.title) === -1) { results.push(title); } }); } else { - results.push(operator.operand); + Array.prototype.push.apply(results,operator.multiValueOperands[0]); } return results; }; diff --git a/core/modules/filters/unknown.js b/core/modules/filters/unknown.js index 8fe2a6889..33b229092 100644 --- a/core/modules/filters/unknown.js +++ b/core/modules/filters/unknown.js @@ -20,8 +20,8 @@ exports["[unknown]"] = function(source,operator,options) { // Check for a user defined filter operator if(operator.operator.indexOf(".") !== -1) { var params = []; - $tw.utils.each(operator.operands,function(param) { - params.push({value: param}); + $tw.utils.each(operator.multiValueOperands,function(paramList) { + params.push({value: paramList[0] || "",multiValue: paramList}); }); var variableInfo = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator,{params: params, source: source}); if(variableInfo && variableInfo.srcVariable) { diff --git a/core/modules/utils/utils.js b/core/modules/utils/utils.js index 3e1cfabd5..a9c05975d 100644 --- a/core/modules/utils/utils.js +++ b/core/modules/utils/utils.js @@ -49,14 +49,26 @@ exports.warning = function(text) { }; /* -Log a table of name: value pairs +Log a table of name: value or name: [values...] pairs */ exports.logTable = function(data) { - if(console.table) { + var hasArrays = false; + $tw.utils.each(data,function(value,name) { + if($tw.utils.isArray(value)) { + hasArrays = true; + } + }); + if(console.table && !hasArrays) { console.table(data); } else { $tw.utils.each(data,function(value,name) { - console.log(name + ": " + value); + if($tw.utils.isArray(value)) { + for(var t=0; t 1 ? variableInfo.resultList : variableInfo.text; } } if(this.filter) { diff --git a/core/modules/widgets/let.js b/core/modules/widgets/let.js index 3cc09ad94..b09c67e91 100644 --- a/core/modules/widgets/let.js +++ b/core/modules/widgets/let.js @@ -46,7 +46,7 @@ LetWidget.prototype.computeAttributes = function() { self = this; this.currentValueFor = Object.create(null); $tw.utils.each($tw.utils.getOrderedAttributesFromParseTreeNode(this.parseTreeNode),function(attribute) { - var value = self.computeAttribute(attribute), + var value = self.computeAttribute(attribute,{asList: true}), name = attribute.name; // Now that it's prepped, we're allowed to look this variable up // when defining later variables @@ -56,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] !== value) { + if(!$tw.utils.isArrayEqual(self.attributes[name],value)) { self.attributes[name] = value; self.setVariable(name,value); changedAttributes[name] = true; @@ -69,8 +69,10 @@ 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)) { + var value = this.currentValueFor[name]; return { - text: this.currentValueFor[name] + text: value[0] || "", + resultList: value }; } return Widget.prototype.getVariableInfo.call(this,name,options); diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js index b89df34b7..67d32e976 100755 --- a/core/modules/widgets/widget.js +++ b/core/modules/widgets/widget.js @@ -80,7 +80,7 @@ Widget.prototype.execute = function() { /* Set the value of a context variable name: name of the variable -value: value of the variable +value: value of the variable, can be a string or an array params: array of {name:, default:} for each parameter isMacroDefinition: true if the variable is set via a \define macro pragma (and hence should have variable substitution performed) options includes: @@ -90,8 +90,10 @@ options includes: */ Widget.prototype.setVariable = function(name,value,params,isMacroDefinition,options) { options = options || {}; + var valueIsArray = $tw.utils.isArray(value); this.variables[name] = { - value: value, + value: valueIsArray ? (value[0] || "") : value, + resultList: valueIsArray ? value : [value], params: params, isMacroDefinition: !!isMacroDefinition, isFunctionDefinition: !!options.isFunctionDefinition, @@ -114,7 +116,7 @@ allowSelfAssigned: if true, includes the current widget in the context chain ins Returns an object with the following fields: -params: array of {name:,value:} or {value:} of parameters to be applied +params: array of {name:,value:,multiValue:} of parameters to be applied (name is optional) text: text of variable, with parameters properly substituted resultList: result of variable evaluation as an array srcVariable: reference to the object defining the variable @@ -140,7 +142,9 @@ Widget.prototype.getVariableInfo = function(name,options) { params = self.resolveVariableParameters(variable.params,actualParams); // Substitute any parameters specified in the definition $tw.utils.each(params,function(param) { - value = $tw.utils.replaceString(value,new RegExp("\\$" + $tw.utils.escapeRegExp(param.name) + "\\$","mg"),param.value); + if("name" in param) { + value = $tw.utils.replaceString(value,new RegExp("\\$" + $tw.utils.escapeRegExp(param.name) + "\\$","mg"),param.value); + } }); value = self.substituteVariableReferences(value,options); resultList = [value]; @@ -154,13 +158,20 @@ Widget.prototype.getVariableInfo = function(name,options) { variables[param.name] = param["default"]; } }); - // Parameters are an array of {value:} or {name:, value:} pairs + // Parameters are an array of {name:, value:, multivalue:} pairs (name and multivalue are optional) $tw.utils.each(params,function(param) { - variables[param.name] = param.value; + if(param.multiValue) { + variables[param.name] = param.multiValue; + } else { + variables[param.name] = param.value || ""; + } }); resultList = this.wiki.filterTiddlers(value,this.makeFakeWidgetWithVariables(variables),options.source); value = resultList[0] || ""; } else { + if(variable.resultList) { + resultList = variable.resultList; + } params = variable.params; } return { @@ -192,22 +203,24 @@ Widget.prototype.getVariable = function(name,options) { /* Maps actual parameters onto formal parameters, returning an array of {name:,value:} objects formalParams - Array of {name:,default:} (default value is optional) -actualParams - Array of string values or {name:,value:} (name is optional) +actualParams - Array of string values or {name:,value:,multiValue} (name and multiValue is optional) */ Widget.prototype.resolveVariableParameters = function(formalParams,actualParams) { formalParams = formalParams || []; actualParams = actualParams || []; var nextAnonParameter = 0, // Next candidate anonymous parameter in macro call - paramInfo, paramValue, + paramInfo, paramValue, paramMultiValue, results = []; // Step through each of the parameters in the macro definition for(var p=0; p ++ +title: ExpectedResult + +

$:/core,ExpectedResult,Output

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/ResultListUnnamedVariable.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/ResultListUnnamedVariable.tid new file mode 100644 index 000000000..58493f2f4 --- /dev/null +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/ResultListUnnamedVariable.tid @@ -0,0 +1,12 @@ +title: LetFilterRunPrefix/ResultListUnnamedVariable +description: Using the "let" filter run prefix to store result lists, not just single values +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +<$text text={{{ [all[tiddlers]] :let[all[]tag[nothing]] [(varname)sort[]join[,]] }}}/> ++ +title: ExpectedResult + +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/ShortcutSyntax.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/ShortcutSyntax.tid new file mode 100644 index 000000000..9c5b5997d --- /dev/null +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/ShortcutSyntax.tid @@ -0,0 +1,12 @@ +title: LetFilterRunPrefix/ShortcutSyntax +description: Simple usage of "let" filter run prefix +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +<$text text={{{ [[magpie]] =>varname [] +[join[-]] }}}/> ++ +title: ExpectedResult + +

magpie

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/let-filter-prefix/Simple.tid b/editions/test/tiddlers/tests/data/let-filter-prefix/Simple.tid new file mode 100644 index 000000000..f16ea107f --- /dev/null +++ b/editions/test/tiddlers/tests/data/let-filter-prefix/Simple.tid @@ -0,0 +1,12 @@ +title: LetFilterRunPrefix/Simple +description: Simple usage of "let" filter run prefix +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +<$text text={{{ [[magpie]] :let[[varname]] [] +[join[-]] }}}/> ++ +title: ExpectedResult + +

magpie

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/Function.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/Function.tid new file mode 100644 index 000000000..9d2ba9c06 --- /dev/null +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/Function.tid @@ -0,0 +1,18 @@ +title: MultiValuedVariables/Function +description: Multi-valued functions +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +\function myfunc() [all[tiddlers]sort[]] + +<$let varname=<>> +<$text text={{{ [(varname)] +[join[-]] }}}/> + ++ +title: ExpectedResult + +

+$:/core-ExpectedResult-Output +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/MissingVariable.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/MissingVariable.tid new file mode 100644 index 000000000..8481dcb44 --- /dev/null +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/MissingVariable.tid @@ -0,0 +1,12 @@ +title: MultiValuedVariables/MissingVariable +description: Using multivalued operands with a missing variable +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +<$text text={{{ [(varname)] }}}/> ++ +title: ExpectedResult + +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/NegatedTitle.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/NegatedTitle.tid new file mode 100644 index 000000000..576efdade --- /dev/null +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/NegatedTitle.tid @@ -0,0 +1,18 @@ +title: MultiValuedVariables/NegatedTitle +description: Multi-valued operands +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +<$let + exclude={{{ $:/core Output }}} +> +<$text text={{{ [all[tiddlers]!title(exclude)] +[join[-]] }}}/> + ++ +title: ExpectedResult + +

+ExpectedResult +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/Operands.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/Operands.tid new file mode 100644 index 000000000..902dc5699 --- /dev/null +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/Operands.tid @@ -0,0 +1,18 @@ +title: MultiValuedVariables/Operands +description: Multi-valued operands +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +\function myfunc() [all[tiddlers]sort[]] + +<$let varname=<>> +<$text text={{{ [(varname)] +[join[-]] }}}/> + ++ +title: ExpectedResult + +

+$:/core-ExpectedResult-Output +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/Parameters.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/Parameters.tid new file mode 100644 index 000000000..c3ee0fa81 --- /dev/null +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/Parameters.tid @@ -0,0 +1,21 @@ +title: MultiValuedVariables/Parameters +description: Multi-valued function parameters +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +\function myfunc(input) [(input)sort[]] + +<$let + input={{{ [all[tiddlers]] }}} + output={{{ [function[myfunc],(input)] }}} +> +<$text text={{{ [(output)] +[join[-]] }}}/> + ++ +title: ExpectedResult + +

+$:/core-ExpectedResult-Output +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/ParametersShortcut.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/ParametersShortcut.tid new file mode 100644 index 000000000..9dbb5a3f2 --- /dev/null +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/ParametersShortcut.tid @@ -0,0 +1,21 @@ +title: MultiValuedVariables/ParametersShortcut +description: Multi-valued function parameters using shortcut syntax +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +\function my.func(input) [(input)sort[]] + +<$let + input={{{ [all[tiddlers]] }}} + output={{{ [my.func(input)] }}} +> +<$text text={{{ [(output)] +[join[-]] }}}/> + ++ +title: ExpectedResult + +

+$:/core-ExpectedResult-Output +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/multi-valued-variables/Simple.tid b/editions/test/tiddlers/tests/data/multi-valued-variables/Simple.tid new file mode 100644 index 000000000..f52e1e6a1 --- /dev/null +++ b/editions/test/tiddlers/tests/data/multi-valued-variables/Simple.tid @@ -0,0 +1,17 @@ +title: MultiValuedVariables/Simple +description: Simple usage of multivalued assignments with the "let" widget +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +<$let varname={{{ [all[tiddlers]sort[]] }}} + varname2=<>> +<$text text={{{ [(varname2)] +[join[-]] }}}/> + ++ +title: ExpectedResult + +

+$:/core-ExpectedResult-Output +

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/test-widget-getVariableInfo.js b/editions/test/tiddlers/tests/test-widget-getVariableInfo.js index ae0f4ce20..23baf436d 100644 --- a/editions/test/tiddlers/tests/test-widget-getVariableInfo.js +++ b/editions/test/tiddlers/tests/test-widget-getVariableInfo.js @@ -60,10 +60,10 @@ describe("Widget module", function() { childNode = childNode.children[0]; } - expect(childNode.getVariableInfo("macro",{allowSelfAssigned:true}).params).toEqual([{name:"a",value:"aa"}]); + expect(childNode.getVariableInfo("macro",{allowSelfAssigned:true}).params).toEqual([{name:"a",value:"aa",multiValue:["aa"]}]); // function params - expect(childNode.getVariableInfo("fn", {allowSelfAssigned:true}).params).toEqual([{name:"f",value:"ff"}]); + expect(childNode.getVariableInfo("fn", {allowSelfAssigned:true}).params).toEqual([{name:"f",value:"ff",multiValue:["ff"]}]); // functions have a text and a value expect(childNode.getVariableInfo("x", {allowSelfAssigned:true}).text).toBe("fff"); expect(childNode.getVariableInfo("x", {allowSelfAssigned:true}).srcVariable.value).toBe("[]"); @@ -73,7 +73,7 @@ describe("Widget module", function() { expect(childNode.getVariableInfo("$my.widget", {allowSelfAssigned:true}).params).toEqual([{name:"w",default:"ww"}]); // no params expected - expect(childNode.getVariableInfo("abc", {allowSelfAssigned:true})).toEqual({text:"def"}); + expect(childNode.getVariableInfo("abc", {allowSelfAssigned:true})).toEqual({text:"def", resultList: [ "def" ]}); // debugger; Find code in browser diff --git a/editions/tw5.com/tiddlers/about/History of TiddlyWiki.tid b/editions/tw5.com/tiddlers/about/History of TiddlyWiki.tid index 80f9aeeb3..c758c82ee 100644 --- a/editions/tw5.com/tiddlers/about/History of TiddlyWiki.tid +++ b/editions/tw5.com/tiddlers/about/History of TiddlyWiki.tid @@ -1,12 +1,12 @@ created: 20140908114400000 -modified: 20250730154331065 +modified: 20251122174540932 tags: About title: History of TiddlyWiki type: text/vnd.tiddlywiki +Gathering the history of ~TiddlyWiki. This is an ongoing project. Contributions and reminiscences are welcome. -Here is a brief history of TiddlyWiki, its origins and its evolution since it was first released on 20th September 2004. Contributions and reminiscences are welcome. - -* [[The Story of TiddlyWiki]] – a personal account of the story of TiddlyWiki, its origins and evolution -* [[TiddlyWiki Anniversaries]] – relive the celebrations of TiddlyWiki's major anniversaries -* [[Filter Syntax History]] – gives a brief history of the evolution of the filter syntax in TiddlyWiki5 +* [[The Story of TiddlyWiki]] – A personal account from [[@Jermolene]] of the story of TiddlyWiki, its origins and evolution +* https://github.com/TiddlyWiki/LaunchArchive – Blog posts and tweets from TiddlyWiki's launch in 2004 +* [[TiddlyWiki Anniversaries]] – Relive the celebrations of TiddlyWiki's major anniversaries +* [[Filter Syntax History]] – A brief history of the evolution of the filter syntax in TiddlyWiki5 diff --git a/editions/tw5.com/tiddlers/filters/syntax/Interchangeable Filter Run Prefixes.tid b/editions/tw5.com/tiddlers/filters/syntax/Interchangeable Filter Run Prefixes.tid index 0313f543a..8eb9c7330 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Interchangeable Filter Run Prefixes.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Interchangeable Filter Run Prefixes.tid @@ -14,6 +14,7 @@ In technical / logical terms: |`-[run]` |`:except[run]` |difference of sets |... AND NOT run | |`~[run]` |`:else[run]` |else |... ELSE run | |`=[run]` |`:all[run]` |union of sets without de-duplication |... OR run | +|`=>[run]` |`:let[run]` |<<.from-version "5.4.0">> assign results to a variable |... LET run | The input of a run is normally a list of all the non-[[shadow|ShadowTiddlers]] tiddler titles in the wiki (in no particular order).
But the `+` prefix can change this: diff --git a/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid new file mode 100644 index 000000000..464011add --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/syntax/Let Filter Run Prefix.tid @@ -0,0 +1,28 @@ +created: 20250307212252946 +from-version: 5.4.0 +modified: 20250307212252946 +rp-input: all titles from previous filter runs +rp-output: an empty title list is always returned from the "let" filter run prefix +rp-purpose: assign the title list resulting from previous filter runs to a multi-valued variable +tags: [[Named Filter Run Prefix]] +title: Let Filter Run Prefix +type: text/vnd.tiddlywiki + +<$railroad text=""" +\start none +\end none +( ":let" ) +[[run|"Filter Run"]] +"""/> + +The `:let` filter run prefix assigns the title list resulting from previous filter runs to a [[multi-valued variable|Multi-Valued Variable]]. The variable is named with the first result returned by the filter run. + +The variable is made available to the remaining [[filter runs|Filter Run]] in the [[filter expression|Filter Expression]]. Only the first item in the result list is returned when the variable is accessed in the usual way (or an empty string if the result list is empty). Using round brackets instead of angle brackets around a variable name as an operand retrieves the complete list of items in the result list. + +This prefix has an optional [[shortcut syntax|Shortcut Filter Run Prefix]] symbol `=>run`. For example: + +``` +=[] =[] =>myvar +``` + +The `:let` filter run prefix always clears the current result list. \ No newline at end of file diff --git a/editions/tw5.com/tiddlers/filters/syntax/Named Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/Named Filter Run Prefix.tid index 31534479e..0706e4cff 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Named Filter Run Prefix.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Named Filter Run Prefix.tid @@ -23,6 +23,7 @@ A named filter run prefix can precede any [[run|Filter Run]] of a [[filter expre [[<":or"> |"Or Filter Run Prefix"]] | [[<":reduce"> |"Reduce Filter Run Prefix"]] | [[<":sort"> /"v5.2.0"/ |"Sort Filter Run Prefix"]] | +[[<":let"> /"v5.4.0"/ |"Let Filter Run Prefix"]] | [[<":then"> /"v5.3.0"/ |"Then Filter Run Prefix"]]) [[run|"Filter Run"]] """/> diff --git a/editions/tw5.com/tiddlers/filters/syntax/Shortcut Filter Run Prefixes.tid b/editions/tw5.com/tiddlers/filters/syntax/Shortcut Filter Run Prefixes.tid index fa8bc26e1..bf4326c41 100644 --- a/editions/tw5.com/tiddlers/filters/syntax/Shortcut Filter Run Prefixes.tid +++ b/editions/tw5.com/tiddlers/filters/syntax/Shortcut Filter Run Prefixes.tid @@ -9,7 +9,7 @@ Shortcut prefixes are commonly used by advanced users because they are fast to t <$railroad text=""" \start none \end none -(-|:"+"|"-"|"~"|"=") +(-|:"+"|"-"|"~"|"="|"=>") [[run|"Filter Run"]] """/> @@ -23,7 +23,8 @@ If a run has: * the prefix `~`, if the filter output so far is an empty list then the output titles of the run are [[dominantly appended|Dominant Append]] to the filter's output. If the filter output so far is not an empty list then the run is ignored. <<.from-version "5.1.18">> -* the prefix `=`, output titles are appended to the filter's output without de-duplication. <<.from-version "5.1.20">> +* the prefix `=`, output titles are appended to the filter's output without de-duplication. <<.from-version "5.1.20">> +* the prefix `=>`, the input is assigned to the variable named with the output title. <<.from-version "5.4.0">> {{Interchangeable Filter Run Prefixes}} \ No newline at end of file diff --git a/editions/tw5.com/tiddlers/filters/title.tid b/editions/tw5.com/tiddlers/filters/title.tid index 3bb59d73f..e6837ef0e 100644 --- a/editions/tw5.com/tiddlers/filters/title.tid +++ b/editions/tw5.com/tiddlers/filters/title.tid @@ -16,4 +16,6 @@ op-neg-output: the input, but with tiddler <<.place T>> filtered out if it exist <<.op title>> is a [[constructor|Selection Constructors]] (except in the form `!title`), but <<.olink2 "field:title" field>> is a [[modifier|Selection Constructors]]. +<<.from-version "5.4.0">> If the operand is quoted with round brackets then the <<.op title>> operator returns the complete list of titles assigned to the multi-valued variable. When negated, the title operator with multi-valued operands returns all the titles that are not present in the operand list. + <<.operator-examples "title">> diff --git a/editions/tw5.com/tiddlers/releasenotes/5.4.0/#8972.tid b/editions/tw5.com/tiddlers/releasenotes/5.4.0/#8972.tid new file mode 100644 index 000000000..45203e0eb --- /dev/null +++ b/editions/tw5.com/tiddlers/releasenotes/5.4.0/#8972.tid @@ -0,0 +1,16 @@ +title: $:/changenotes/5.4.0/#8972 +description: Multi-valued variables and let filter run prefix +release: 5.4.0 +tags: $:/tags/ChangeNote +change-type: enhancement +change-category: hackability +github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/8972 +github-contributors: Jermolene + +This PR introduces a new filter run prefix `:let` that assigns the result of the filter run to a variable that is made available for the remaining filter runs of the filter expression. It solves the problem that previously it was impossible to compute values for filter operator parameters; parameters could only be a literal string, text reference or variable reference. + +This PR also introduces multi-valued variables, the ability to store a list of results in a variable, not just a single string. They can be assigned with the new `:let` filter run prefix, or the existing `<$let>` widget. The full list of values can be retrieved using round brackets instead of the usual angle brackets. In all other contexts only the first item in the list is used as the variable value. + +* [[Multi-Valued Variables]] +* [[Let Filter Run Prefix]] +* [[LetWidget]] diff --git a/editions/tw5.com/tiddlers/system/DefaultTiddlers.tid b/editions/tw5.com/tiddlers/system/DefaultTiddlers.tid index 47d011885..f81c01aa8 100644 --- a/editions/tw5.com/tiddlers/system/DefaultTiddlers.tid +++ b/editions/tw5.com/tiddlers/system/DefaultTiddlers.tid @@ -10,4 +10,4 @@ HelloThere [[TiddlyWiki on the Web]] [[Testimonials and Reviews]] GettingStarted -Community +Community \ No newline at end of file diff --git a/editions/tw5.com/tiddlers/variables/Multi-Valued Variables.tid b/editions/tw5.com/tiddlers/variables/Multi-Valued Variables.tid new file mode 100644 index 000000000..3d239034e --- /dev/null +++ b/editions/tw5.com/tiddlers/variables/Multi-Valued Variables.tid @@ -0,0 +1,75 @@ +title: Multi-Valued Variables +created: 20250307212252946 +modified: 20250307212252946 +tags: Concepts Variables + +<<.from-version "5.4.0">> In ordinary usage, [[variables|Variables]] contain a single snippet of text. With the introduction of multi-valued variables. it is now possible to store a list of multiple values in a single variable. When accessed in the usual way, only the first value is returned, but using round brackets instead of angle brackets around the variable name allows access to the complete list of the values. This makes multi-valued variables largely invisible unless you specifically need to use them. + +! Setting Multi-Valued Variables + +Generally, all the methods for setting variables implicitly set multi-valued variables, with the exception of the [[Set Widget|Set Widget]]. + +!! LetWidget + +The <<.wid let>> widget allows multi-valued variables to be set in one operation, each to the complete list of results obtained from evaluating an attribute that is defined via a filtered transclusion. For example: + +``` +<$let + varname={{{ [all[tiddlers]sort[]] }}} +> +``` + +The <<.wid let>> widget also allows the complete list of return values from a function to be assigned to a multi-valued variable. For example: + +``` +<$let + varname=<> +> +``` + +!! [[Let Filter Run Prefix]] + +The `:let` filter run prefix (or its shortcut syntax `=>`) assigns the complete list of results of a filter run to a multi-valued variable. + +! Retrieving Multi-Valued Variables + +!! [[title Operator]] + +The simplest way to retrieve the complete list of values stored in a multi-valued variable is to use the [[title Operator]]. For example: + +``` +[title(varname)] +``` + +Because `title` is the default operator when the operator name is missing from a filter step, the following example is equivalent to the previous one: + +``` +[(varname)] +``` + +!! Multi-valued Parameters for Filter Operators + +Certain filter operators can accept multi-valued parameters: + +* [[function Operator]] +* [[title Operator]] + +For example: + +``` +\function myfunc(tiddlers) [(tiddlers)sort[]] + +<$let varname={{{ [all[tiddlers]limit[50]] }}}> +<$text text={{{ [function[myfunc],(varname)] +[join[-]] }}}/> + +``` + +! Examples + +For example: + +``` +<$let varname={{{ [all[tiddlers]sort[]] }}}> +<$text text={{{ [(varname)] +[join[-]] }}}/> + +``` diff --git a/editions/tw5.com/tiddlers/variables/Variables.tid b/editions/tw5.com/tiddlers/variables/Variables.tid index 43387eb4f..a4c38c893 100644 --- a/editions/tw5.com/tiddlers/variables/Variables.tid +++ b/editions/tw5.com/tiddlers/variables/Variables.tid @@ -1,13 +1,15 @@ created: 20141002133113496 -modified: 20240422084347375 +modified: 20250307212252946 tags: Concepts WikiText title: Variables type: text/vnd.tiddlywiki !! Introduction -* A <<.def variable>> is a ''snippet of text'' that can be accessed by name. -* The text is referred to as the variable's <<.def value>>. +* A <<.def variable>> is a ''snippet of text'' that can be accessed by name +* The text is referred to as the variable's <<.def value>> + +<<.from-version "5.4.0">> In ordinary usage, variables contain a single snippet of text. With the introduction of [[Multi-Valued Variables]] it is possible to store a list of multiple values in a single variable. When accessed in the usual way, only the first value is returned, but using round brackets instead of the usual angle brackets retrieves the complete list of values. This makes the existence of multi-valued variables invisible unless you specifically need to use them. Variables are defined by [[widgets|Widgets]]. Several core widgets define variables, the most common being the <<.wlink LetWidget>>, <<.wlink SetWidget>> and <<.wlink ListWidget>> widgets. diff --git a/editions/tw5.com/tiddlers/widgets/ActionLogWidget.tid b/editions/tw5.com/tiddlers/widgets/ActionLogWidget.tid index 2fa75cfad..6a807fcf8 100644 --- a/editions/tw5.com/tiddlers/widgets/ActionLogWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/ActionLogWidget.tid @@ -25,6 +25,7 @@ In addition there are optional attributes that can be used: |$$message |A message to display as the title of the information logged. Useful when several `action-log` widgets are used in sequence. | |$$all |Set to "yes" to log all variables in a collapsed table. Note that if there is nothing specified to log, all variables are always logged instead.| +<<.from-version "5.4.0">> Any [[multi-valued variables|Multi-Valued Variables]] or attributes are logged as a list of values. <<.tip """A handy tip if an action widget is not behaving as expected is to temporarily change it to an `<$action-log>` widget so that the attributes can be observed.""">> diff --git a/editions/tw5.com/tiddlers/widgets/LetWidget.tid b/editions/tw5.com/tiddlers/widgets/LetWidget.tid index b27306cfb..bcb779d9c 100644 --- a/editions/tw5.com/tiddlers/widgets/LetWidget.tid +++ b/editions/tw5.com/tiddlers/widgets/LetWidget.tid @@ -1,12 +1,12 @@ title: LetWidget created: 20211028115900000 -modified: 20221001094658854 +modified: 20250307212252946 tags: Widgets caption: let ! Introduction -<<.from-version "5.2.1">> The <<.wid let>> widget allows multiple variables to be set in one operation. In some situations it can result in simpler code than using the more flexible <<.wlink SetWidget>> widget. It differs from the <<.wlink VarsWidget>> widget in that variables you're defining may depend on earlier variables defined within the same <<.wid let>>. +<<.from-version "5.2.1">> The <<.wid let>> widget allows more than one variable to be set in one operation. In some situations it can result in simpler code than using the more flexible <<.wlink SetWidget>> widget. It differs from the <<.wlink VarsWidget>> widget in that variables you're defining may depend on earlier variables defined within the same <<.wid let>>. ! Content and Attributes @@ -19,6 +19,18 @@ Attributes are evaluated in the order they are written. Attributes with the same <<.note """<<.from-version "5.2.4">> There is no longer any restriction on using variable names that start with the $ character.""">> +! Multi-Valued Variables + +<<.from-version "5.4.0">> The <<.wid let>> widget also allows [[multi-valued variables|Multi-Valued Variables]] to be set in one operation to the complete list of results obtained from evaluating an attribute. + +Almost all operations that work with variables only consider the first item in the list. Using round brackets instead of angle brackets around the variable name gives access to the complete list of results. For example: + +``` +<$let varname={{{ [all[tiddlers]sort[]] }}}> +<$text text={{{ [(varname)] +[join[-]] }}}/> + +``` + ! Examples Consider a case where you need to set multiple variables, where some depend on the evaluation of others.