diff --git a/core/modules/filterrunprefixes/then.js b/core/modules/filterrunprefixes/then.js new file mode 100644 index 000000000..9f6d5c76a --- /dev/null +++ b/core/modules/filterrunprefixes/then.js @@ -0,0 +1,32 @@ +/*\ +title: $:/core/modules/filterrunprefixes/then.js +type: application/javascript +module-type: filterrunprefix + +Replace results of previous runs unless empty + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter prefix function +*/ +exports.then = function(operationSubFunction) { + return function(results,source,widget) { + if(results.length !== 0) { + // Only run if previous run(s) produced results + var thisRunResult = operationSubFunction(source,widget); + if(thisRunResult.length !== 0) { + // Replace results only if this run actually produces a result + results.clear(); + results.pushTop(thisRunResult); + } + } + }; +}; + +})(); diff --git a/editions/test/tiddlers/tests/test-prefixes-filter.js b/editions/test/tiddlers/tests/test-prefixes-filter.js index 62f329d66..a8d109d73 100644 --- a/editions/test/tiddlers/tests/test-prefixes-filter.js +++ b/editions/test/tiddlers/tests/test-prefixes-filter.js @@ -434,6 +434,15 @@ describe("'reduce' and 'intersection' filter prefix tests", function() { expect(wiki.filterTiddlers("[tag[shopping]] :map[get[title]addprefix[-]addprefixaddprefix[of]addprefix]").join(",")).toBe("0of4-Brownies,1of4-Chick Peas,2of4-Milk,3of4-Rice Pudding"); }); + it("should handle the :then prefix", function() { + expect(wiki.filterTiddlers("[[one]] :then[[two]]").join(",")).toBe("two"); + expect(wiki.filterTiddlers("[[one]] :then[tag[shopping]]").join(",")).toBe("Brownies,Chick Peas,Milk,Rice Pudding"); + expect(wiki.filterTiddlers("[[one]] [[two]] [[three]] :then[[four]]").join(",")).toBe("four"); + expect(wiki.filterTiddlers("[[one]] :then[tag[nonexistent]]").join(",")).toBe("one"); + expect(wiki.filterTiddlers(":then[[two]]").length).toBe(0); + expect(wiki.filterTiddlers("[[notatiddler]is[tiddler]] :then[[two]]").length).toBe(0); + }); + it("should handle macro parameters for filter run prefixes",function() { var widget = require("$:/core/modules/widgets/widget.js"); var rootWidget = new widget.widget({ type:"widget", children:[ {type:"widget", children:[]} ] }, diff --git a/editions/tw5.com/tiddlers/filters/Conditional Operators.tid b/editions/tw5.com/tiddlers/filters/Conditional Operators.tid index 7028e6dad..6d21e0864 100644 --- a/editions/tw5.com/tiddlers/filters/Conditional Operators.tid +++ b/editions/tw5.com/tiddlers/filters/Conditional Operators.tid @@ -1,12 +1,12 @@ created: 20190802113703788 -modified: 20190802132727925 +modified: 20230501175143648 tags: Filters title: Conditional Operators type: text/vnd.tiddlywiki -<<.from-version "5.1.20">>The conditional filter operators allow ''if-then-else'' logic to be expressed within filters. +<<.from-version "5.1.20">>The conditional filter operators allow for ''if-then-else'' logic to be expressed within filters. -The foundation is the convention that an empty list can be used to represent the boolean value ''false'' and a list with at one (or more) entries to represent ''true''. +The foundation is the convention that an empty list can be used to represent the Boolean value <<.value false>> and a list with at one (or more) entries to represent <<.value true>>. The conditional operators are: @@ -19,10 +19,12 @@ The conditional operators are: These operators can be combined. For example: -<<.inline-operator-example "[[New Tiddler]is[missing]then[I am missing]else[No I am not missing]]">> +<<.operator-example 1 "[[New Tiddler]is[missing]then[I am missing]else[No I am not missing]]">> -The [[else Operator]] can be used to apply a defaults for missing values. In this example, we take advantage of the fact that the [[get Operator]] returns an empty list if the field or tiddler does not exist: +The <<.olink else>> operator can be used to apply a defaults for missing values. In this example, we take advantage of the fact that the <<.olink get>> operator returns an empty list if the field or tiddler does not exist: -<<.inline-operator-example "[[HelloThere]get[custom-field]else[default-value]]">> +<<.operator-example 2 "[[HelloThere]get[custom-field]else[default-value]]">> -<> +! Filter Run Prefixes + +The [[:then|:then Filter Run Prefix]] and [[:else|:else Filter Run Prefix]] filter run prefixes serve a similar purpose as the conditional operators. Refer to their documentation for more information. \ No newline at end of file diff --git a/editions/tw5.com/tiddlers/filters/syntax/Then Filter Run Prefix (Examples).tid b/editions/tw5.com/tiddlers/filters/syntax/Then Filter Run Prefix (Examples).tid new file mode 100644 index 000000000..b68ef58fc --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/syntax/Then Filter Run Prefix (Examples).tid @@ -0,0 +1,49 @@ +created: 20230617183745774 +modified: 20230617183745774 +tags: [[Then Filter Run Prefix]] +title: Then Filter Run Prefix (Examples) +type: text/vnd.tiddlywiki + + +!! Conditional Execution + +The <<.op :then>> filter run prefix can be used to avoid the need for nested [[ListWidget]]s or [[Macro Definitions in WikiText]]. + +<$macrocall $name='wikitext-example-without-html' +src="""<$edit-text field="search" placeholder="Search title"/> + +<$let searchTerm={{!!search}}> +<$list filter="[minlength[3]] :then[!is[system]search:title]" template="$:/core/ui/ListItemTemplate"/> +"""/> + + +!! Conditional (Sub)Filters + +The <<.op :then>> filter run prefix can be combined with the <<.op :else>> prefix to create conditional filters. In this example, the fields used in <<.var searchSubfilter>> for searching depend on the value of [[$:/temp/searchFields]] and the sort order used by <<.var sortSubfilter>> depends on the value of [[$:/temp/searchSort]]. Checkboxes are used to set the values of these tiddlers. + +<<.tip "Note that each filter run of the subfilter receives the input of the <<.olink subfilter>> operator as input">> + +Since the <<.olink then>> and <<.olink else>> operators cannot call subfilters or perform additional filter steps, they cannot be used for such applications. + +<$macrocall $name='wikitext-example-without-html' +src="""<$checkbox tiddler="$:/temp/searchSort" + field="text" + checked="chrono" unchecked="alpha" default="alpha"> + Sort chronologically (newest first) +
+<$checkbox tiddler="$:/temp/searchFields" + field="text" + checked="title" unchecked="default" default="title"> + Search <<.field title>> only +

+<$let searchSubfilter="[{$:/temp/searchFields}match[default]] :then[search[prefix]] :else[search:title[prefix]]" + sortSubfilter="[{$:/temp/searchSort}match[chrono]] :then[!nsort[modified]] :else[sort[title]]" + limit=10 > + <$list filter="[all[tiddlers]!is[system]subfiltersubfilterfirst]"> + <$link/> (<$text text={{{ [{!!modified}format:date[YYYY-0MM-0DD]] }}} />)
+ + <$list filter="[all[tiddlers]!is[system]subfilterrestcount[]]"> + ... and <> more. + +"""/> + diff --git a/editions/tw5.com/tiddlers/filters/syntax/then Filter Run Prefix.tid b/editions/tw5.com/tiddlers/filters/syntax/then Filter Run Prefix.tid new file mode 100644 index 000000000..2584bd65b --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/syntax/then Filter Run Prefix.tid @@ -0,0 +1,71 @@ +created: 20210618133745003 +from-version: 5.3.0 +modified: 20230506172920710 +rp-input: <<.olink all>> tiddler titles +rp-output: the output of this filter run replaces the output of previous runs unless it is an empty list (see below) +rp-purpose: replace any input to this filter run with its output, only evaluating the run when there is any input +search: +tags: [[Filter Run Prefix]] [[Filter Syntax]] +title: Then Filter Run Prefix +type: text/vnd.tiddlywiki + +\define .op-row() +<$macrocall $name=".if" + cond="""$(op-body)$""" + then="""$(op-head)$<<.op-place>>$(op-body)$""" + else=""/> +\end + +<$list filter="[all[current]has[from-version]]" variable="listItem"> + <$macrocall $name=".from-version" version={{!!from-version}}/> + +<$let op-head="" op-body="" op-name=""> + + + <$let op-head="purpose" op-body={{!!rp-purpose}}> + <<.op-row>> + + + <$let op-head="[[input|Filter Expression]]" op-body={{!!rp-input}}> + <<.op-row>> + + + <$let op-head="suffix" op-body={{!!rp-suffix}} op-name={{!!rp-suffix-name}}> + <<.op-row>> + + + <$let op-head="output" op-body={{!!rp-output}}> + <<.op-row>> + +
+ + +<$railroad text=""" +\start none +\end none +":then" +[[run|"Filter Run"]] +"""/> + +!Introduction + +The <<.op :then>> filter run prefix is used to replace the result of all previous filter runs with its output. + +If the result of all previous runs is an empty list, the <<.op :then>> prefixed filter run is not evaluated. + +If the output of a <<.op :then>> prefixed filter run is itself an empty list, the result of all previous filter runs is passed through unaltered. + +<<.tip "Note that a list with a single empty string item is not an empty list.">> + + +!! <<.op :then>> run prefix versus the <<.olink then>> operator + +The major difference between the <<.op then>> operator and a <<.op :then>> prefixed filter run is that the operator will replace //each item// of the input [[Title List]] with its parameter while <<.op :then>> will replace the //whole input list// with the result of its run. + +|doc-op-comparison tc-center|k +| !<<.op :then>> Filter Run Prefix | !<<.op then>> Operator | +|^<<.operator-example m1-1 "[tag[WikiText]] :then[[true]]">>|^<<.operator-example m1-2 "[tag[WikiText]then[true]]">>

To make them equivalent, additional filter steps may be added:

<<.operator-example m1-3 "[tag[WikiText]count[]compare:number:gt[0]then[true]]">>| + + +! [[Examples|Then Filter Run Prefix (Examples)]] + diff --git a/editions/tw5.com/tiddlers/filters/then Operator.tid b/editions/tw5.com/tiddlers/filters/then Operator.tid index e4ea5901e..e4d389a29 100644 --- a/editions/tw5.com/tiddlers/filters/then Operator.tid +++ b/editions/tw5.com/tiddlers/filters/then Operator.tid @@ -1,6 +1,6 @@ caption: then created: 20190802112756430 -modified: 20190802113849579 +modified: 20230501174334627 op-input: a [[selection of titles|Title Selection]] op-output: the input titles with each one replaced by the string <<.place T>> op-parameter: a string @@ -12,4 +12,6 @@ type: text/vnd.tiddlywiki <<.from-version "5.1.20">> See [[Conditional Operators]] for an overview. +<<.tip "The [[Then Filter Run Prefix]] has a similar purpose to the <<.op then>> operator. See its documentation for a comparison of usage.">> + <<.operator-examples "then">> diff --git a/editions/tw5.com/tiddlers/system/doc-styles.tid b/editions/tw5.com/tiddlers/system/doc-styles.tid index e406abb95..9ae22868f 100644 --- a/editions/tw5.com/tiddlers/system/doc-styles.tid +++ b/editions/tw5.com/tiddlers/system/doc-styles.tid @@ -285,6 +285,14 @@ ol.doc-github-contributors li { overflow: hidden; text-overflow: ellipsis; } +.doc-op-comparison { + table-layout: fixed; + width: 80%; +} +.doc-op-comparison th .doc-operator { + background-color: unset; + color: #666; +} .doc-tabs.tc-tab-buttons button { font-size: 1rem; padding: 0.5em; @@ -295,4 +303,4 @@ ol.doc-github-contributors li { } .doc-tab-link .doc-attr { color: unset; -} \ No newline at end of file +}