From 21b10a225f29134d4c786de2420bf65fdcfedc03 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com" Antelope DingoCrocodile DingoCrocodile DingoCrocodile FerretJaguar FerretWhale FerretHippopotamus PiranhaFerretPiranhaFerretBadgerFerretBadgerFerret Dingo FerretFerretPigeonPigeonFerretFerretJaguar [{Jaguar}{Lizard}{Mole}] [{Ferret}{Lizard}{Mole}] [{Butterfly}{Moth}{Mole}] [{Beetle}{Scorpion}{Snake}] ([{Beetle}{Scorpion}{Snake}]) New value Aval Bval Cval Bval Antelope FerretFerretPigeonPigeonFerretFerretJaguar Antelope FerretFerretPigeonPigeonFerretFerretJaguar {Ferret}{Jaguar} [{Jaguar}{Lizard}{Mole}] [{Ferret}{Lizard}{Mole}] [{Butterfly}{Moth}{Mole}] [{Beetle}{Scorpion}{Snake}] ([{Beetle}{Scorpion}{Snake}]) FerretJaguar {zero:Jaguar}{one:Lizard}{two:Mole} {zero:Ferret}{one:Lizard}{two:Mole} {zero:Butterfly}{one:Moth}{two:Mole} {zero:Beetle}{one:Scorpion}{two:Snake} ({zero:Beetle}{one:Scorpion}{two:Snake})addprefix[£]addsuffix[@]] }}}/>
+\end
+_
+title: Subject
+
+Python
+_
+title: ExpectedResult
+
+
£Kangaroo@
£Python@
£Tiger@
{zero:Jaguar}{one:Lizard}{two:Mole}
{zero:Ferret}{one:Lizard}{two:Mole}
{zero:Butterfly}{one:Moth}{two:Mole}
{zero:Beetle}{one:Scorpion}{two:Snake}
({zero:Beetle}{one:Scorpion}{two:Snake})
\ No newline at end of file +{0:Ferret}
{0:Butterfly}{1:Moth}
{0:Beetle}{1:Scorpion}{2:Snake}
({zero:Beetle}{one:Scorpion}{two:Snake})
\ No newline at end of file From 56c2242e4ef06a5d8799dc50084447e3a3741efa Mon Sep 17 00:00:00 2001 From: "jeremy@jermolene.com"(Kitten)(Kitten)
\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid b/editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid new file mode 100644 index 000000000..1edca2887 --- /dev/null +++ b/editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid @@ -0,0 +1,14 @@ +title: Genesis/MultipleAttributes +description: Usage of genesis widget with multiple attributes +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +\whitespace trim +<$genesis $type="let" $names="myvar other" $values="Kitten Donkey" myvar="Shark">(<$text text=<(Shark|Donkey)(Shark|Donkey)
\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid new file mode 100644 index 000000000..88abc84c8 --- /dev/null +++ b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid @@ -0,0 +1,33 @@ +title: Genesis/RedefineLet +description: Using the genesis widget to override the let widget +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +\whitespace trim +\function <$let> +\whitespace trim +<$setmultiplevariables $names="[enlist(--Elephant--) +(--Kangaroo--) +(--Giraffe--)
\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/genesis-widget/Simple.tid b/editions/test/tiddlers/tests/data/genesis-widget/Simple.tid new file mode 100644 index 000000000..ff232dad6 --- /dev/null +++ b/editions/test/tiddlers/tests/data/genesis-widget/Simple.tid @@ -0,0 +1,14 @@ +title: Genesis/Simple +description: Simple usage of genesis widget +type: text/vnd.tiddlywiki-multiple +tags: [[$:/tags/wiki-test-spec]] + +title: Output + +\whitespace trim +<$genesis $tag="div">Mouse$genesis> +<$genesis $tag="div" class="tc-thing" label="Squeak">Mouse$genesis> +_ +title: ExpectedResult + +(Kitten)(Kitten)
\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid b/editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid index 1edca2887..4ec14ce36 100644 --- a/editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid +++ b/editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid @@ -8,7 +8,7 @@ title: Output \whitespace trim <$genesis $type="let" $names="myvar other" $values="Kitten Donkey" myvar="Shark">(<$text text=<(Shark|Donkey)(Shark|Donkey)
\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid index 88abc84c8..110145307 100644 --- a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid +++ b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid @@ -21,11 +21,11 @@ title: Output (<$text text=<<$two>>/>) (<$text text=<<$$three>>/>) $let> -_ ++ title: Definition \whitespace trim -_ ++ title: ExpectedResult(--Elephant--) diff --git a/editions/test/tiddlers/tests/data/genesis-widget/Simple.tid b/editions/test/tiddlers/tests/data/genesis-widget/Simple.tid index ff232dad6..9052d621d 100644 --- a/editions/test/tiddlers/tests/data/genesis-widget/Simple.tid +++ b/editions/test/tiddlers/tests/data/genesis-widget/Simple.tid @@ -8,7 +8,7 @@ title: Output \whitespace trim <$genesis $tag="div">Mouse$genesis> <$genesis $tag="div" class="tc-thing" label="Squeak">Mouse$genesis> -_ ++ title: ExpectedResult
Dingo
\ No newline at end of file diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid index 6b204fe7c..014404d8c 100644 --- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid +++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid @@ -12,18 +12,18 @@ title: Output <$let test="Tiger"> <$codeblock code=<addprefix[£]addsuffix[@]] }}}/>
\end
-_
++
title: Subject
Python
-_
++
title: ExpectedResult
£Kangaroo@
£Python@
£Tiger@
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
index 57c980ea7..25c39bf35 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
@@ -8,11 +8,11 @@ title: Output
\whitespace trim
<$transclude $tiddler='TiddlerOne' one='Ferret'>
$transclude>
-_
++
title: TiddlerZero
Antelope
-_
++
title: TiddlerOne
\whitespace trim
@@ -27,7 +27,7 @@ title: TiddlerOne
<$transclude $tiddler="TiddlerZero">
Crocodile
$transclude>
-_
++
title: ExpectedResult
Antelope
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
index 86b08d239..9cc318b12 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
@@ -8,7 +8,7 @@ title: Output
\whitespace trim
<$transclude $tiddler='TiddlerOne' one='Ferret'>
$transclude>
-_
++
title: TiddlerOne
\whitespace trim
@@ -23,7 +23,7 @@ title: TiddlerOne
<$mywidget one="Dingo">
Crocodile
$mywidget>
-_
++
title: ExpectedResult
DingoCrocodile
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
index db603b298..63710c5c9 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
@@ -8,7 +8,7 @@ title: Output
\whitespace trim
<$transclude $tiddler='TiddlerOne'>
$transclude>
-_
++
title: TiddlerOne
\whitespace trim
@@ -23,7 +23,7 @@ title: TiddlerOne
<$text text="Dingo">
Crocodile
$text>
-_
++
title: ExpectedResult
DingoCrocodile
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
index 673be223f..70e1b1d09 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
@@ -8,7 +8,7 @@ title: Output
\whitespace trim
<$transclude $tiddler='TiddlerOne' one='Ferret'>
$transclude>
-_
++
title: TiddlerOne
\whitespace trim
@@ -23,7 +23,7 @@ title: TiddlerOne
<$mywidget $variable="Dingo">
Crocodile
$mywidget>
-_
++
title: ExpectedResult
DingoCrocodile
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/MissingTarget.tid b/editions/test/tiddlers/tests/data/transclude/MissingTarget.tid
index 89d9f95fe..5fa2d12c7 100644
--- a/editions/test/tiddlers/tests/data/transclude/MissingTarget.tid
+++ b/editions/test/tiddlers/tests/data/transclude/MissingTarget.tid
@@ -34,7 +34,7 @@ title: Output
$parameters>
$value>
$transclude>
-_
++
title: TiddlerOne
\whitespace trim
@@ -42,7 +42,7 @@ title: TiddlerOne
Piranha
<$text text=<>/>
$parameters>
-_
++
title: ExpectedResult
PiranhaFerretPiranhaFerretBadgerFerretBadgerFerret
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid
index 07cf5c316..5be4eea74 100644
--- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid
@@ -11,7 +11,7 @@ title: Output
{{TiddlerOne|Butterfly|Moth}}
{{TiddlerOne|Beetle|Scorpion|Snake}}
{{TiddlerOne||TiddlerTwo|Beetle|Scorpion|Snake}}
-_
++
title: TiddlerOne
\whitespace trim
@@ -19,13 +19,13 @@ title: TiddlerOne
<$list filter="[enlist]" counter="counter">
{<$text text={{{ [enlistnth] }}}/>:<$text text={{{ [enlistnth] }}}/>}
$list>
-_
++
title: TiddlerTwo
\whitespace trim
\parameters(zero:'Mouse',one:'Horse',two:'Owl')
(<$transclude $tiddler=<> zero=<> one=<> two=<>/>)
-_
++
title: ExpectedResult
{0:Ferret}
{0:Butterfly}{1:Moth}
{0:Beetle}{1:Scorpion}{2:Snake}
({zero:Beetle}{one:Scorpion}{two:Snake})
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut-Parameters.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut-Parameters.tid
index b4b3eac0c..abf444adb 100644
--- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut-Parameters.tid
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut-Parameters.tid
@@ -11,19 +11,19 @@ title: Output
{{TiddlerOne|Butterfly|Moth}}
{{TiddlerOne|Beetle|Scorpion|Snake}}
{{TiddlerOne||TiddlerTwo|Beetle|Scorpion|Snake}}
-_
++
title: TiddlerOne
\whitespace trim
\parameters(zero:Jaguar,one:'Lizard',two:'Mole')
[{<$text text=<>/>}{<$text text=<>/>}{<$text text=<>/>}]
-_
++
title: TiddlerTwo
\whitespace trim
\parameters(zero:'Mouse',one:Horse,two:'Owl')
(<$transclude $tiddler=<> zero=<> one=<> two=<>/>)
-_
++
title: ExpectedResult
[{Jaguar}{Lizard}{Mole}]
[{Ferret}{Lizard}{Mole}]
[{Butterfly}{Moth}{Mole}]
[{Beetle}{Scorpion}{Snake}]
([{Beetle}{Scorpion}{Snake}])
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut.tid
index 6f37d4797..7792e6c66 100644
--- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut.tid
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional-Shortcut.tid
@@ -11,19 +11,19 @@ title: Output
{{TiddlerOne|Butterfly|Moth}}
{{TiddlerOne|Beetle|Scorpion|Snake}}
{{TiddlerOne||TiddlerTwo|Beetle|Scorpion|Snake}}
-_
++
title: TiddlerOne
\whitespace trim
<$parameters zero='Jaguar' one='Lizard' two='Mole'>[{<$text text=<>/>}{<$text text=<>/>}{<$text text=<>/>}]$parameters>
-_
++
title: TiddlerTwo
\whitespace trim
<$parameters zero='Mouse' one='Horse' two='Owl'>
(<$transclude $tiddler=<> zero=<> one=<> two=<>/>)
$parameters>
-_
++
title: ExpectedResult
[{Jaguar}{Lizard}{Mole}]
[{Ferret}{Lizard}{Mole}]
[{Butterfly}{Moth}{Mole}]
[{Beetle}{Scorpion}{Snake}]
([{Beetle}{Scorpion}{Snake}])
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional.tid
index caaa3a6ef..d7eb9090e 100644
--- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional.tid
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Positional.tid
@@ -13,14 +13,14 @@ title: Output
<$transclude $tiddler='TiddlerOne' zero='Ferret' 0='Pigeon'/>
<$transclude zero='Ferret' 0='Pigeon' $tiddler='TiddlerOne'/>
<$transclude $tiddler='TiddlerOne'/>
-_
++
title: TiddlerOne
\whitespace trim
<$parameters zero='Jaguar'>
<$text text=<>/>
$parameters>
-_
++
title: ExpectedResult
FerretFerretPigeonPigeonFerretFerretJaguar
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut-Parameters.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut-Parameters.tid
index 841718ee9..375964199 100644
--- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut-Parameters.tid
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut-Parameters.tid
@@ -8,13 +8,13 @@ title: Output
\whitespace trim
<$transclude $tiddler='TiddlerOne' one='Ferret'/>
<$transclude $tiddler='TiddlerOne'/>
-_
++
title: TiddlerOne
\whitespace trim
\parameters(one:'Jaguar')
<$text text=<>/>
-_
++
title: ExpectedResult
FerretJaguar
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid
index a183fe2d4..88017392e 100644
--- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid
@@ -12,7 +12,7 @@ title: Output
<$transclude $variable='test' one='Ferret'/>
<$transclude $variable='test'/>
-_
++
title: ExpectedResult
{Ferret}{Jaguar}
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Simple.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Simple.tid
index e05e3bec4..a6228d625 100644
--- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Simple.tid
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Simple.tid
@@ -8,14 +8,14 @@ title: Output
\whitespace trim
<$transclude $tiddler='TiddlerOne' one='Ferret'/>
<$transclude $tiddler='TiddlerOne'/>
-_
++
title: TiddlerOne
\whitespace trim
<$parameters one='Jaguar'>
<$text text=<>/>
$parameters>
-_
++
title: ExpectedResult
FerretJaguar
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted-Missing.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted-Missing.tid
index 983cdd5b5..fe399d572 100644
--- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted-Missing.tid
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted-Missing.tid
@@ -8,7 +8,7 @@ title: Output
\whitespace trim
<$transclude $tiddler='TiddlerOne' one='Ferret'>
$transclude>
-_
++
title: TiddlerOne
\whitespace trim
@@ -18,7 +18,7 @@ title: TiddlerOne
Whale
$slot>
$parameters>
-_
++
title: ExpectedResult
FerretWhale
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted.tid
index fa2ede489..eeff9b33e 100644
--- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted.tid
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted.tid
@@ -11,7 +11,7 @@ title: Output
Hippopotamus
$value>
$transclude>
-_
++
title: TiddlerOne
\whitespace trim
@@ -21,7 +21,7 @@ title: TiddlerOne
Whale
$slot>
$parameters>
-_
++
title: ExpectedResult
FerretHippopotamus
\ No newline at end of file
diff --git a/plugins/tiddlywiki/jasmine/run-wiki-based-tests.js b/plugins/tiddlywiki/jasmine/run-wiki-based-tests.js
index 17033af15..4db3e232f 100644
--- a/plugins/tiddlywiki/jasmine/run-wiki-based-tests.js
+++ b/plugins/tiddlywiki/jasmine/run-wiki-based-tests.js
@@ -49,7 +49,7 @@ describe("Wiki-based tests", function() {
});
function readMultipleTiddlersTiddler(title) {
- var rawTiddlers = $tw.wiki.getTiddlerText(title).split("\n_\n");
+ var rawTiddlers = $tw.wiki.getTiddlerText(title).split("\n+\n");
var tiddlers = [];
$tw.utils.each(rawTiddlers,function(rawTiddler) {
var fields = Object.create(null),
From e99137f4cc7977b1259199f9c870f62a5d1c64ab Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Tue, 3 May 2022 17:59:45 +0100
Subject: [PATCH 036/405] Cache parse trees when transcluding variables
---
core/modules/widgets/transclude.js | 8 +++++++-
.../tests/data/transclude/CustomWidget-Simple.tid | 5 ++++-
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index 734e17f25..4d40cadcc 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -171,7 +171,13 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
if(this.transcludeVariable) {
var variableInfo = this.getVariableInfo(this.transcludeVariable).srcVariable;
if(variableInfo) {
- parser = this.wiki.parseText(this.transcludeType,variableInfo.value || "",{parseAsInline: !this.parseTreeNode.isBlock});
+ var mode = this.parseTreeNode.isBlock ? "blockParser" : "inlineParser";
+ if(variableInfo[mode]) {
+ parser = variableInfo[mode];
+ } else {
+ parser = this.wiki.parseText(this.transcludeType,variableInfo.value || "",{parseAsInline: !this.parseTreeNode.isBlock});
+ variableInfo[mode] = parser;
+ }
if(parser && variableInfo.isFunctionDefinition) {
parser = {
tree: [
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
index 9cc318b12..74ecb575d 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
@@ -23,7 +23,10 @@ title: TiddlerOne
<$mywidget one="Dingo">
Crocodile
$mywidget>
+<$mywidget one="BumbleBee">
+ Squirrel
+$mywidget>
+
title: ExpectedResult
-DingoCrocodile
\ No newline at end of file
+DingoCrocodileBumbleBeeSquirrel
\ No newline at end of file
From 35430d09ed8bc9ddd7a12635450514d1bbe7352f Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Thu, 5 May 2022 08:16:20 +0100
Subject: [PATCH 037/405] Fix bug with empty strings ignored in
$tw.utils.stringifyList/parseStringArray
I will pull this out into a separate PR. Fixing it doesn't cause problems for the core but I imagine it might cause issues for 3rd party code.
---
boot/boot.js | 4 ++--
editions/test/tiddlers/tests/test-utils.js | 4 +++-
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/boot/boot.js b/boot/boot.js
index f902e9956..4bdedf4ef 100644
--- a/boot/boot.js
+++ b/boot/boot.js
@@ -375,7 +375,7 @@ $tw.utils.stringifyList = function(value) {
var result = new Array(value.length);
for(var t=0, l=value.length; t
Date: Thu, 5 May 2022 08:20:14 +0100
Subject: [PATCH 038/405] Fixes to enable the transclude widget itself to be
overridden
There are two big changes here:
Replace the previous "ts-wrapper" mechanism, which we had been using to redefine custom widgets inside their definitions to prevent recursive calls. Now we've got the genesis widget we can instead control recursion through a new "$remappable" attribute that allows the custom widget mechanism to be skipped.
We also extend the slot widget to allow a depth to be specified; it then reaches up by the indicated number of transclusion widgets to find the one from which it should retrieve the slot value.
---
core/modules/utils/parsetree.js | 7 +++-
core/modules/widgets/genesis.js | 4 +-
core/modules/widgets/parameters.js | 5 ++-
core/modules/widgets/slot.js | 25 ++++++++----
core/modules/widgets/transclude.js | 29 +++++++++-----
core/modules/widgets/widget.js | 38 +++----------------
.../CustomWidget-Override-Codeblock.tid | 2 +-
.../CustomWidget-OverrideTransclude.tid | 4 +-
.../CustomWidget-TextWidgetOverride.tid | 10 +++--
.../transclude/Parameterised-Name-Values.tid | 6 ++-
10 files changed, 68 insertions(+), 62 deletions(-)
diff --git a/core/modules/utils/parsetree.js b/core/modules/utils/parsetree.js
index fa6d7ef1a..5bab10706 100644
--- a/core/modules/utils/parsetree.js
+++ b/core/modules/utils/parsetree.js
@@ -12,8 +12,13 @@ Parse tree utility functions.
/*global $tw: false */
"use strict";
+/*
+Add attribute to parse tree node
+Can be invoked as (node,name,value) or (node,attr)
+*/
exports.addAttributeToParseTreeNode = function(node,name,value) {
- var attribute = {name: name, type: "string", value: value};
+ var attribute = typeof name === "object" ? name : {name: name, type: "string", value: value};
+ name = attribute.name;
node.attributes = node.attributes || {};
node.orderedAttributes = node.orderedAttributes || [];
node.attributes[name] = attribute;
diff --git a/core/modules/widgets/genesis.js b/core/modules/widgets/genesis.js
index d6017453c..c4bd62139 100644
--- a/core/modules/widgets/genesis.js
+++ b/core/modules/widgets/genesis.js
@@ -41,6 +41,7 @@ GenesisWidget.prototype.execute = function() {
// Collect attributes
this.genesisType = this.getAttribute("$type","element");
this.genesisTag = this.getAttribute("$tag","div");
+ this.genesisRemappable = this.getAttribute("$remappable","yes") === "yes";
this.genesisNames = this.getAttribute("$names","");
this.genesisValues = this.getAttribute("$values","");
// Construct parse tree
@@ -49,7 +50,8 @@ GenesisWidget.prototype.execute = function() {
tag: this.genesisTag,
attributes: {},
orderedAttributes: [],
- children: this.parseTreeNode.children || []
+ children: this.parseTreeNode.children || [],
+ isNotRemappable: !this.genesisRemappable
}];
// Apply attributes in $names/$values
this.attributeNames = [];
diff --git a/core/modules/widgets/parameters.js b/core/modules/widgets/parameters.js
index f3bb4ebbe..0b49d4bf7 100644
--- a/core/modules/widgets/parameters.js
+++ b/core/modules/widgets/parameters.js
@@ -54,8 +54,9 @@ ParametersWidget.prototype.execute = function() {
value = transclusionWidget.getTransclusionParameter(name,index,self.getAttribute(name));
self.setVariable(name,value);
});
- this.setVariable("paramNames",$tw.utils.stringifyList(transclusionWidget.getTransclusionParameterNames()));
- this.setVariable("paramValues",$tw.utils.stringifyList(transclusionWidget.getTransclusionParameterValues()));
+ $tw.utils.each(transclusionWidget.getTransclusionMetaVariables(),function(value,name) {
+ self.setVariable(name,value);
+ });
}
// Construct the child widgets
this.makeChildWidgets();
diff --git a/core/modules/widgets/slot.js b/core/modules/widgets/slot.js
index b009deeb5..31df12a75 100644
--- a/core/modules/widgets/slot.js
+++ b/core/modules/widgets/slot.js
@@ -43,13 +43,24 @@ Compute the internal state of the widget
SlotWidget.prototype.execute = function() {
var self = this;
this.slotName = this.getAttribute("$name");
- // Find the parent transclusion
- var transclusionWidget = this.parentWidget;
- while(transclusionWidget && !(transclusionWidget instanceof TranscludeWidget)) {
- transclusionWidget = transclusionWidget.parentWidget;
+ this.slotDepth = parseInt(this.getAttribute("$depth","1"),10) || 1;
+ // Find the parent transclusions
+ var pointer = this.parentWidget,
+ depth = this.slotDepth;
+ while(pointer) {
+ if(pointer instanceof TranscludeWidget) {
+ depth--;
+ if(depth === 0) {
+ break;
+ }
+ }
+ pointer = pointer.parentWidget;
+ }
+ var parseTreeNodes = [{type: "text", attributes: {text: {type: "string", value: "Missing slot reference!"}}}];
+ if(pointer instanceof TranscludeWidget) {
+ // Get the parse tree nodes comprising the slot contents
+ parseTreeNodes = pointer.getTransclusionSlotValue(this.slotName,this.parseTreeNode.children);
}
- // Get the parse tree nodes comprising the slot contents
- var parseTreeNodes = transclusionWidget.getTransclusionSlotValue(this.slotName,this.parseTreeNode.children);
// Construct the child widgets
this.makeChildWidgets(parseTreeNodes);
};
@@ -59,7 +70,7 @@ Refresh the widget by ensuring our attributes are up to date
*/
SlotWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
- if(changedAttributes["$name"]) {
+ if(changedAttributes["$name"] || changedAttributes["$depth"]) {
this.refreshSelf();
return true;
}
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index 4d40cadcc..f3122b092 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -46,13 +46,7 @@ TranscludeWidget.prototype.execute = function() {
parseTreeNodes = target.parseTreeNodes;
this.sourceText = target.source;
this.sourceType = target.type;
- // Wrap the transcluded content if required
- if(this.slotValueParseTrees["ts-wrapper"]) {
- this.slotValueParseTrees["ts-wrapped"] = parseTreeNodes;
- parseTreeNodes = this.slotValueParseTrees["ts-wrapper"];
- this.sourceTest = undefined;
- this.sourceType = undefined;
- }
+ this.parseAsInline = target.parseAsInline;
// Set context variables for recursion detection
var recursionMarker = this.makeRecursionMarker();
if(this.recursionMarker === "yes") {
@@ -135,6 +129,7 @@ TranscludeWidget.prototype.collectSlotValueParameters = function() {
if(this.legacyMode) {
this.slotValueParseTrees["ts-missing"] = this.parseTreeNode.children;
} else {
+ this.slotValueParseTrees["ts-raw"] = this.parseTreeNode.children;
var noValueWidgetsFound = true,
searchParseTreeNodes = function(nodes) {
$tw.utils.each(nodes,function(node) {
@@ -171,11 +166,11 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
if(this.transcludeVariable) {
var variableInfo = this.getVariableInfo(this.transcludeVariable).srcVariable;
if(variableInfo) {
- var mode = this.parseTreeNode.isBlock ? "blockParser" : "inlineParser";
+ var mode = parseAsInline ? "inlineParser" : "blockParser";
if(variableInfo[mode]) {
parser = variableInfo[mode];
} else {
- parser = this.wiki.parseText(this.transcludeType,variableInfo.value || "",{parseAsInline: !this.parseTreeNode.isBlock});
+ parser = this.wiki.parseText(this.transcludeType,variableInfo.value || "",{parseAsInline: parseAsInline});
variableInfo[mode] = parser;
}
if(parser && variableInfo.isFunctionDefinition) {
@@ -206,6 +201,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
return {
parser: parser,
parseTreeNodes: parser.tree,
+ parseAsInline: parseAsInline,
text: parser.source,
type: parser.type
};
@@ -213,6 +209,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
return {
parser: null,
parseTreeNodes: (this.slotValueParseTrees["ts-missing"] || []),
+ parseAsInline: parseAsInline,
text: null,
type: null
};
@@ -234,6 +231,17 @@ TranscludeWidget.prototype.getTransclusionParameter = function(name,index,defaul
return defaultValue;
};
+/*
+Get a hashmap of the special variables to be provided by the parameters widget
+*/
+TranscludeWidget.prototype.getTransclusionMetaVariables = function() {
+ return {
+ paramNames: $tw.utils.stringifyList(this.getTransclusionParameterNames()),
+ paramValues: $tw.utils.stringifyList(this.getTransclusionParameterValues()),
+ parseAsInline: this.parseAsInline ? "yes" : "no"
+ }
+};
+
/*
Get an array of the names of all the provided transclusion parameters
*/
@@ -248,7 +256,7 @@ TranscludeWidget.prototype.getTransclusionParameterValues = function() {
var self = this,
values = [];
$tw.utils.each(Object.keys(this.stringParametersByName),function(name) {
- values.push(self.stringParametersByName[name]);
+ values.push(self.stringParametersByName[name] || "");
});
return values;
};
@@ -282,6 +290,7 @@ TranscludeWidget.prototype.makeRecursionMarker = function() {
};
TranscludeWidget.prototype.parserNeedsRefresh = function() {
+ // TODO: Doesn't consider transcluded variables
var parserInfo = this.wiki.getTextReferenceParserInfo(this.transcludeTitle,this.transcludeField,this.transcludeIndex,{subTiddler:this.transcludeSubTiddler});
return (this.sourceText === undefined || parserInfo.sourceText !== this.sourceText || parserInfo.parserType !== this.parserType)
};
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index 71b9fe4dc..082c2eaee 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -401,49 +401,23 @@ Widget.prototype.makeChildWidget = function(parseTreeNode,options) {
options = options || {};
// Check whether this node type is defined by a custom macro definition
var variableDefinitionName = "<$" + parseTreeNode.type + ">";
- if(parseTreeNode.type !== "transclude" && this.variables[variableDefinitionName] && this.variables[variableDefinitionName].value) {
+ if(!parseTreeNode.isNotRemappable && this.variables[variableDefinitionName] && this.variables[variableDefinitionName].value) {
var newParseTreeNode = {
type: "transclude",
- attributes: {
- "$variable": {name: "$variable", type: "string", value: variableDefinitionName}
- },
children: [
{
type: "value",
- attributes: {
- "$name": {name: "$name", type: "string", value: "ts-body"}
- },
children: parseTreeNode.children
- },
- {
- type: "value",
- attributes: {
- "$name": {name: "$name", type: "string", value: "ts-wrapper"}
- },
- children: [
- {
- type: "setvariable",
- attributes: {
- "name": {name: "name", type: "string", value: variableDefinitionName},
- "value": {name: "value", type: "string", value: ""}
- },
- children: [
- {
- type: "slot",
- attributes: {
- "$name": {name: "$name", type: "string", value: "ts-wrapped"}
- }
- }
- ]
- }
- ]
}
- ]
+ ],
+ isBlock: parseTreeNode.isBlock
};
+ $tw.utils.addAttributeToParseTreeNode(newParseTreeNode,"$variable",variableDefinitionName);
+ $tw.utils.addAttributeToParseTreeNode(newParseTreeNode.children[0],"$name","ts-body");
$tw.utils.each(parseTreeNode.attributes,function(attr,name) {
// If the attribute starts with a dollar then add an extra dollar so that it doesn't clash with the $xxx attributes of transclude
name = name.charAt(0) === "$" ? "$" + name : name;
- newParseTreeNode.attributes[name] = attr;
+ $tw.utils.addAttributeToParseTreeNode(newParseTreeNode,$tw.utils.extend({},attr,{name: name}));
});
parseTreeNode = newParseTreeNode;
}
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid
index 014404d8c..4da0f2033 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid
@@ -17,7 +17,7 @@ title: Definition
\whitespace trim
\function <$codeblock>(code)
-<$codeblock code={{{ [addprefix[£]addsuffix[@]] }}}/>
+<$genesis $type="codeblock" $remappable="no" code={{{ [addprefix[£]addsuffix[@]] }}}/>
\end
+
title: Subject
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
index 25c39bf35..08290b2bb 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
@@ -24,9 +24,9 @@ title: TiddlerOne
Whale
$slot>
\end
-<$transclude $tiddler="TiddlerZero">
+<$genesis $type="transclude" $remappable="no" $$tiddler="TiddlerZero">
Crocodile
-$transclude>
+$genesis>
+
title: ExpectedResult
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
index 63710c5c9..62e52c7a8 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
@@ -15,10 +15,12 @@ title: TiddlerOne
\function <$text>(text:'Jaguar')
\whitespace trim
-<$text text=<>/>
-<$slot $name="ts-body">
- Whale
-$slot>
+<$genesis $type="text" $remappable="no" text=<>/>
+<$set name="<$text>" value="">
+ <$slot $name="ts-body">
+ Whale
+ $slot>
+$set>
\end
<$text text="Dingo">
Crocodile
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid
index 5be4eea74..d47ea440b 100644
--- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid
@@ -6,6 +6,8 @@ tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
+<$transclude $tiddler="TiddlerOne" 0="" 1="" 2=""/>
+
{{TiddlerOne}}
{{TiddlerOne|Ferret}}
{{TiddlerOne|Butterfly|Moth}}
@@ -17,7 +19,7 @@ title: TiddlerOne
\whitespace trim
\parameters(zero:'Jaguar',one:'Lizard',two:'Mole')
<$list filter="[enlist]" counter="counter">
-{<$text text={{{ [enlistnth] }}}/>:<$text text={{{ [enlistnth] }}}/>}
+{<$text text={{{ [enlist:rawnth] }}}/>:<$text text={{{ [enlist:rawnth] }}}/>}
$list>
+
title: TiddlerTwo
@@ -28,4 +30,4 @@ title: TiddlerTwo
+
title: ExpectedResult
-{0:Ferret}
{0:Butterfly}{1:Moth}
{0:Beetle}{1:Scorpion}{2:Snake}
({zero:Beetle}{one:Scorpion}{two:Snake})
\ No newline at end of file
+{0:}{1:}{2:}
{0:Ferret}
{0:Butterfly}{1:Moth}
{0:Beetle}{1:Scorpion}{2:Snake}
({zero:Beetle}{one:Scorpion}{two:Snake})
\ No newline at end of file
From f56e316c86f5cc730407bd7831cc80692eb55994 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Thu, 5 May 2022 11:34:06 +0100
Subject: [PATCH 039/405] Fix genesis widget example
---
editions/tw5.com/tiddlers/widgets/GenesisWidget.tid | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/editions/tw5.com/tiddlers/widgets/GenesisWidget.tid b/editions/tw5.com/tiddlers/widgets/GenesisWidget.tid
index 7adb8983b..51356f865 100644
--- a/editions/tw5.com/tiddlers/widgets/GenesisWidget.tid
+++ b/editions/tw5.com/tiddlers/widgets/GenesisWidget.tid
@@ -27,8 +27,4 @@ Note that attributes explicitly specified take precedence over attributes with t
! Examples
<$macrocall $name='wikitext-example-without-html'
-src='<$set name="myTiddler" value="HelloThere">
- <$set name="myVariable" tiddler=<> field={{$:/docs/anyField!!field}}>
- <$text text=<>/>
- $set>
-$set>'/>
+src='<$genesis $tag="div" class="tc-thing" label="Squeak">Mouse$genesis>'/>
From 7ead87bd2940eaeb553e199e6a2809a7c45aea0c Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Thu, 5 May 2022 11:34:30 +0100
Subject: [PATCH 040/405] Use enlist:raw to preserve duplicates
---
.../test/tiddlers/tests/data/genesis-widget/RedefineLet.tid | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
index 110145307..8ef331924 100644
--- a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
+++ b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
@@ -8,7 +8,7 @@ title: Output
\whitespace trim
\function <$let>
\whitespace trim
-<$setmultiplevariables $names="[enlist]" $values="[enlistaddprefix[--]addsuffix[--]]">
+<$setmultiplevariables $names="[enlist:raw]" $values="[enlist:rawaddprefix[--]addsuffix[--]]">
<$slot $name="ts-body"/>
$setmultiplevariables>
\end
From e5164113c478b461eaf035bf62c3beea33e426a7 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Thu, 5 May 2022 14:47:22 +0100
Subject: [PATCH 041/405] Don't create variables with value undefined for
missing parameters
---
core/modules/widgets/parameters.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/core/modules/widgets/parameters.js b/core/modules/widgets/parameters.js
index 0b49d4bf7..d741ffe2c 100644
--- a/core/modules/widgets/parameters.js
+++ b/core/modules/widgets/parameters.js
@@ -51,7 +51,7 @@ ParametersWidget.prototype.execute = function() {
if(transclusionWidget) {
$tw.utils.each($tw.utils.getOrderedAttributesFromParseTreeNode(self.parseTreeNode),function(attr,index) {
var name = attr.name,
- value = transclusionWidget.getTransclusionParameter(name,index,self.getAttribute(name));
+ value = transclusionWidget.getTransclusionParameter(name,index,self.getAttribute(name,""));
self.setVariable(name,value);
});
$tw.utils.each(transclusionWidget.getTransclusionMetaVariables(),function(value,name) {
From 7caaf82571ce8c5081ae71b8f12352b2e0c39b44 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Fri, 6 May 2022 10:32:20 +0100
Subject: [PATCH 042/405] Fix variable retrieval bug with test harness
---
editions/test/tiddlers/tests/test-filters.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/editions/test/tiddlers/tests/test-filters.js b/editions/test/tiddlers/tests/test-filters.js
index 18545221b..ca36cac62 100644
--- a/editions/test/tiddlers/tests/test-filters.js
+++ b/editions/test/tiddlers/tests/test-filters.js
@@ -422,7 +422,7 @@ Tests the filtering mechanism.
expect(wiki.filterTiddlers("[[one]tagging[]sort[title]]").join(",")).toBe("Tiddler Three,Tiddler8,TiddlerOne,TiddlerSeventh");
expect(wiki.filterTiddlers("[[one]tagging[]]").join(",")).toBe("Tiddler Three,TiddlerOne,TiddlerSeventh,Tiddler8");
expect(wiki.filterTiddlers("[[two]tagging[]sort[title]]").join(",")).toBe("$:/TiddlerFive,$:/TiddlerTwo,Tiddler Three");
- var fakeWidget = {getVariable: function() {return "one";}};
+ var fakeWidget = {wiki: wiki, getVariable: function(name) {return name === "currentTiddler" ? "one": undefined;}};
expect(wiki.filterTiddlers("[all[current]tagging[]]",fakeWidget).join(",")).toBe("Tiddler Three,TiddlerOne,TiddlerSeventh,Tiddler8");
});
@@ -623,7 +623,7 @@ Tests the filtering mechanism.
it("should handle indirect operands", function() {
expect(wiki.filterTiddlers("[prefix{Tiddler8}] +[sort[title]]").join(",")).toBe("Tiddler Three,TiddlerOne");
expect(wiki.filterTiddlers("[modifier{Tiddler8!!test-field}] +[sort[title]]").join(",")).toBe("TiddlerOne");
- var fakeWidget = {wiki: wiki, getVariable: function() {return "Tiddler Three";}};
+ var fakeWidget = {wiki: wiki, getVariable: function(name) {return name === "currentTiddler" ? "Tiddler Three": undefined;}};
expect(wiki.filterTiddlers("[modifier{!!modifier}] +[sort[title]]",fakeWidget).join(",")).toBe("$:/TiddlerTwo,a fourth tiddler,one,Tiddler Three");
});
From a9938a6c671f40de4e5ad70d60149721c37ad7a2 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Fri, 6 May 2022 15:00:10 +0100
Subject: [PATCH 043/405] Improve recursion detection
While retaining backwards compatibility
---
core/modules/widgets/transclude.js | 35 +++++++++++++++++----
editions/test/tiddlers/tests/test-widget.js | 2 +-
2 files changed, 30 insertions(+), 7 deletions(-)
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index f3122b092..787e6a26d 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -48,13 +48,15 @@ TranscludeWidget.prototype.execute = function() {
this.sourceType = target.type;
this.parseAsInline = target.parseAsInline;
// Set context variables for recursion detection
- var recursionMarker = this.makeRecursionMarker();
+ var recursionMarker = this.makeLegacyRecursionMarker(),
+ newRecursionMarker = this.makeRecursionMarker();
if(this.recursionMarker === "yes") {
this.setVariable("transclusion",recursionMarker);
+ this.setVariable("$transclusion",newRecursionMarker);
}
// Check for recursion
if(target.parser) {
- if(this.parentWidget && this.parentWidget.hasVariable("transclusion",recursionMarker)) {
+ if(this.parentWidget && this.parentWidget.hasVariable("$transclusion",newRecursionMarker)) {
parseTreeNodes = [{type: "element", tag: "span", attributes: {
"class": {type: "string", value: "tc-error"}
}, children: [
@@ -273,18 +275,39 @@ TranscludeWidget.prototype.getTransclusionSlotValue = function(name,defaultParse
};
/*
-Compose a string comprising the title, field and/or index to identify this transclusion for recursion detection
+Compose a string comprising the attributes and variables to identify this transclusion for recursion detection
*/
TranscludeWidget.prototype.makeRecursionMarker = function() {
- var attributes = Object.create(null);
+ var marker = {
+ attributes: {},
+ variables: {}
+ }
$tw.utils.each(this.attributes,function(value,name) {
- attributes[name] = value;
+ marker.attributes[name] = value;
});
+ for(var name in this.variables) {
+ if(name !== "$transclusion") {
+ marker.variables[name] = this.getVariable(name);
+ }
+ };
+ return JSON.stringify(marker);
+};
+
+/*
+Compose a string comprising the title, field and/or index to identify this transclusion for recursion detection
+*/
+TranscludeWidget.prototype.makeLegacyRecursionMarker = function() {
var output = [];
output.push("{");
output.push(this.getVariable("currentTiddler",{defaultValue: ""}));
output.push("|");
- output.push(JSON.stringify(attributes));
+ output.push(this.transcludeTitle || "");
+ output.push("|");
+ output.push(this.transcludeField || "");
+ output.push("|");
+ output.push(this.transcludeIndex || "");
+ output.push("|");
+ output.push(this.transcludeSubTiddler || "");
output.push("}");
return output.join("");
};
diff --git a/editions/test/tiddlers/tests/test-widget.js b/editions/test/tiddlers/tests/test-widget.js
index 8d9c734a0..10c45317e 100755
--- a/editions/test/tiddlers/tests/test-widget.js
+++ b/editions/test/tiddlers/tests/test-widget.js
@@ -157,7 +157,7 @@ describe("Widget module", function() {
// Render the widget node to the DOM
var wrapper = renderWidgetNode(widgetNode);
// Test the rendering
- expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget\n");
+ expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget\n\n");
});
it("should deal with SVG elements", function() {
From e01dfa150716494017bca00aac98909643aea58b Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Fri, 6 May 2022 15:01:17 +0100
Subject: [PATCH 044/405] Experimental support for custom filter operators
Just as we can define custom widgets we can also define custom parameterised filter operators
---
core/modules/filters.js | 6 +-
core/modules/filters/unknown.js | 59 +++++++++++++++++++
.../custom-operators/NestedParameterised.tid | 24 ++++++++
.../data/custom-operators/Parameterised.tid | 20 +++++++
.../tests/data/custom-operators/Simple.tid | 16 +++++
5 files changed, 123 insertions(+), 2 deletions(-)
create mode 100644 core/modules/filters/unknown.js
create mode 100644 editions/test/tiddlers/tests/data/custom-operators/NestedParameterised.tid
create mode 100644 editions/test/tiddlers/tests/data/custom-operators/Parameterised.tid
create mode 100644 editions/test/tiddlers/tests/data/custom-operators/Simple.tid
diff --git a/core/modules/filters.js b/core/modules/filters.js
index e0a2779e3..5f4eb6492 100644
--- a/core/modules/filters.js
+++ b/core/modules/filters.js
@@ -244,13 +244,15 @@ exports.compileFilter = function(filterString) {
var operands = [],
operatorFunction;
if(!operator.operator) {
+ // Use the "title" operator if no operator is specified
operatorFunction = filterOperators.title;
} else if(!filterOperators[operator.operator]) {
- operatorFunction = filterOperators.field;
+ // Unknown operators treated as "unknown" - at run time we can distinguish between a custom operator and falling back to the default "field" operator
+ operatorFunction = filterOperators.unknown;
} else {
+ // Use the operator function
operatorFunction = filterOperators[operator.operator];
}
-
$tw.utils.each(operator.operands,function(operand) {
if(operand.indirect) {
operand.value = self.getTextReference(operand.text,"",currTiddlerTitle);
diff --git a/core/modules/filters/unknown.js b/core/modules/filters/unknown.js
new file mode 100644
index 000000000..93b42c706
--- /dev/null
+++ b/core/modules/filters/unknown.js
@@ -0,0 +1,59 @@
+/*\
+title: $:/core/modules/filters/unknown.js
+type: application/javascript
+module-type: filteroperator
+
+Filter operator for handling unknown filter operators
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+var fieldFilterOperatorFn = require("$:/core/modules/filters/field.js").field;
+
+/*
+Export our filter function
+*/
+exports.unknown = function(source,operator,options) {
+ var customDefinitionTitle = "[" + operator.operator + "[]]",
+ customDefinition = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(customDefinitionTitle);
+ if(customDefinition && customDefinition.srcVariable) {
+ var variables = Object.create(null);
+ $tw.utils.each(customDefinition.srcVariable.variableParams,function(param,index) {
+ var value = operator.operands[index];
+ if(value === undefined) {
+ value = param["default"] || "";
+ }
+ variables[param.name] = value;
+ });
+ var getVariable = function(name,opts) {
+ if(name in variables) {
+ return variables[name];
+ } else {
+ return options.widget.getVariable(name,opts);
+ };
+ };
+ var getVariableInfo = function(name,opts) {
+ return options.widget.getVariableInfo(name,opts);
+ }
+ var list = options.wiki.filterTiddlers(customDefinition.srcVariable.value,{getVariable: getVariable,getVariableInfo: getVariableInfo},source);
+ if(operator.prefix === "!") {
+ var results = [];
+ source(function(tiddler,title) {
+ if(list.indexOf(title) === -1) {
+ results.push(title);
+ }
+ });
+ return results;
+ } else {
+ return list;
+ }
+ } else {
+ return fieldFilterOperatorFn(source,operator,options);
+ }
+};
+
+})();
diff --git a/editions/test/tiddlers/tests/data/custom-operators/NestedParameterised.tid b/editions/test/tiddlers/tests/data/custom-operators/NestedParameterised.tid
new file mode 100644
index 000000000..cbeb1570d
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/custom-operators/NestedParameterised.tid
@@ -0,0 +1,24 @@
+title: CustomOperators/NestedParameterised
+description: Nested parameterised custom operator usage
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\function [dividebysomething[]](first:ignored,factor:0.5)
+[divide[2]multiply]
+\end
+
+\function [multiplebysomething[]](first:ignored,factor:2)
+[multiply[2]dividebysomething[],]
+\end
+
+<$text text={{{ [[123]multiplebysomething[]] }}}/>
+-
+<$text text={{{ [[123]multiplebysomething[x],[4]] }}}/>
+
++
+title: ExpectedResult
+
+246-492
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/custom-operators/Parameterised.tid b/editions/test/tiddlers/tests/data/custom-operators/Parameterised.tid
new file mode 100644
index 000000000..42ca40ec6
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/custom-operators/Parameterised.tid
@@ -0,0 +1,20 @@
+title: CustomOperators/Parameterised
+description: Parameterised custom operator usage
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\function [multiplybysomething[]](first:ignored,factor:2)
+[multiply[2]multiply]
+\end
+
+<$text text={{{ [[123]multiplybysomething[]] }}}/>
+-
+<$text text={{{ [[123]multiplybysomething[x],[4]] }}}/>
+
++
+title: ExpectedResult
+
+492-984
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/custom-operators/Simple.tid b/editions/test/tiddlers/tests/data/custom-operators/Simple.tid
new file mode 100644
index 000000000..076a54dad
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/custom-operators/Simple.tid
@@ -0,0 +1,16 @@
+title: CustomOperators/Simple
+description: Simple custom operator usage
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\function [multiplybytwo[]]()
+[multiply[2]]
+\end
+
+<$text text={{{ [[123]multiplybytwo[]] }}}/>
++
+title: ExpectedResult
+
+246
\ No newline at end of file
From c5b10d5c1d3ee456e1f164e20ee393b964624ab3 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Fri, 6 May 2022 15:39:45 +0100
Subject: [PATCH 045/405] Add visible transclusions component and demo
Very useful to see transclusions explicitly
Makes a good demo of a super-complicated widget override.
---
core/ui/Components/VisibleTransclude.tid | 45 +++++++++++++++++++
.../tiddlers/howtos/Visible Transclusions.tid | 11 +++++
2 files changed, 56 insertions(+)
create mode 100644 core/ui/Components/VisibleTransclude.tid
create mode 100644 editions/tw5.com/tiddlers/howtos/Visible Transclusions.tid
diff --git a/core/ui/Components/VisibleTransclude.tid b/core/ui/Components/VisibleTransclude.tid
new file mode 100644
index 000000000..93b17f202
--- /dev/null
+++ b/core/ui/Components/VisibleTransclude.tid
@@ -0,0 +1,45 @@
+title: $:/core/ui/VisibleTransclude
+
+
+\function <$transclude>(tiddler,$tiddler,mode,$mode)
+
+<$let
+ mode={{{ [[$mode]is[variable]then<$mode>!is[blank]] :else[[mode]is[variable]then!is[blank]] :else[match[yes]then[inline]else[block]] }}}
+ outputTag={{{ [match[inline]then[span]else[div]] }}}
+ outputColour={{{ [match[inline]then[green]else[red]] }}}
+>
+
+ <$genesis $type="element" $tag=<> style="color:white;padding:4px;" style.background=<>>
+ <$genesis $type="element" $tag=<> style="display: inline-block;">
+
+
+ <$list filter="[enlist:raw]" counter="counter" emptyMessage="(none)">
+
+ <$text text=<>/><$text text=": "/><$text text={{{ [enlist:rawnth] }}}/>
+
+ $list>
+
+ $genesis>
+ <$genesis $type="element" $tag=<> style="background:white;color:black;padding:4px;">
+
+ <$list filter="[enlist:raw] :filter[prefix[$]] +[limit[1]]" variable="ignore" emptyMessage="""
+
+ <$genesis $type="transclude" $remappable="no" $names="[enlist:raw]" $values="[enlist:raw]" recursionMarker="no" mode=<>>
+
+ <$slot $name="ts-raw" $depth="2"/>
+ $genesis>
+ """>
+
+ <$genesis $type="transclude" $remappable="no" $names="[enlist:raw]" $values="[enlist:raw]" $$recursionMarker="no" $$mode=<>>
+
+ <$slot $name="ts-raw" $depth="2"/>
+ $genesis>
+ $list>
+ $genesis>
+ $genesis>
+<$let>
+\end
diff --git a/editions/tw5.com/tiddlers/howtos/Visible Transclusions.tid b/editions/tw5.com/tiddlers/howtos/Visible Transclusions.tid
new file mode 100644
index 000000000..6b7d26024
--- /dev/null
+++ b/editions/tw5.com/tiddlers/howtos/Visible Transclusions.tid
@@ -0,0 +1,11 @@
+title: Visible Transclusions
+tags: Learning
+
+!! Visible Transclusions
+
+Block transclusions are shown in red, and inline transclusions are shown in green.
+
+<$button>
+<$action-setfield $tiddler="$:/temp/VisibleTransclusions" tags="$:/tags/Macro/View/Body" text={{$:/core/ui/VisibleTransclude}}/>
+Click here to make transclusions visible within story river tiddlers
+$button>
From 2fe2d20ddf8f59922ae175726f0cb358007f8c47 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Sat, 7 May 2022 11:41:28 +0100
Subject: [PATCH 046/405] Genesis widget should pass raw attributes onto child
widget...
...so that it can more efficiently handle refreshing itself.
---
core/modules/widgets/genesis.js | 38 +++++++++++++------
core/modules/widgets/widget.js | 12 +++++-
.../genesis-widget/MultipleAttributes.tid | 4 +-
3 files changed, 39 insertions(+), 15 deletions(-)
diff --git a/core/modules/widgets/genesis.js b/core/modules/widgets/genesis.js
index c4bd62139..cda389321 100644
--- a/core/modules/widgets/genesis.js
+++ b/core/modules/widgets/genesis.js
@@ -28,7 +28,10 @@ Render this widget into the DOM
*/
GenesisWidget.prototype.render = function(parent,nextSibling) {
this.parentDomNode = parent;
- this.computeAttributes();
+ this.computeAttributes({filterFn: function(name) {
+ // Only compute our own attributes which start with a single dollar
+ return name.charAt(0) === "$" && name.charAt(1) !== "$";
+ }});
this.execute();
this.renderChildren(parent,nextSibling);
};
@@ -53,17 +56,20 @@ GenesisWidget.prototype.execute = function() {
children: this.parseTreeNode.children || [],
isNotRemappable: !this.genesisRemappable
}];
- // Apply attributes in $names/$values
- 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);
- $tw.utils.each(this.attributeNames,function(varname,index) {
- $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],varname,self.attributeValues[index] || "");
- });
- }
// Apply explicit attributes
+ $tw.utils.each($tw.utils.getOrderedAttributesFromParseTreeNode(this.parseTreeNode),function(attribute) {
+ var name = attribute.name;
+ if(name.charAt(0) === "$") {
+ if(name.charAt(1) === "$") {
+ // Double $$ is changed to a single $
+ name = name.substr(1);
+ } else {
+ // Single dollar is ignored
+ return;
+ }
+ }
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],$tw.utils.extend({},attribute,{name: name}));
+ });
$tw.utils.each(this.attributes,function(value,name) {
if(name.charAt(0) === "$") {
if(name.charAt(1) === "$") {
@@ -76,6 +82,16 @@ GenesisWidget.prototype.execute = function() {
}
$tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],name,value);
});
+ // Apply attributes in $names/$values
+ 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);
+ $tw.utils.each(this.attributeNames,function(varname,index) {
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],varname,self.attributeValues[index] || "");
+ });
+ }
// Construct the child widgets
this.makeChildWidgets(parseTreeNodes);
};
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index 082c2eaee..6c201177c 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -269,12 +269,20 @@ Widget.prototype.getStateQualifier = function(name) {
};
/*
-Compute the current values of the attributes of the widget. Returns a hashmap of the names of the attributes that have changed
+Compute the current values of the attributes of the widget. Returns a hashmap of the names of the attributes that have changed.
+Options include:
+filterFn: only include attributes where filterFn(name) returns true
*/
-Widget.prototype.computeAttributes = function() {
+Widget.prototype.computeAttributes = function(options) {
+ options = options || {};
var changedAttributes = {},
self = this;
$tw.utils.each(this.parseTreeNode.attributes,function(attribute,name) {
+ if(options.filterFn) {
+ if(!options.filterFn(name)) {
+ return;
+ }
+ }
var value = self.computeAttribute(attribute);
if(self.attributes[name] !== value) {
self.attributes[name] = value;
diff --git a/editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid b/editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid
index 4ec14ce36..4892dbffc 100644
--- a/editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid
+++ b/editions/test/tiddlers/tests/data/genesis-widget/MultipleAttributes.tid
@@ -6,9 +6,9 @@ tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
-<$genesis $type="let" $names="myvar other" $values="Kitten Donkey" myvar="Shark">(<$text text=<>/>|<$text text=<>/>)$genesis>
+<$genesis $type="let" $names="myvar other" $values="Kitten Donkey" myvar={{{ Shark }}}>(<$text text=<>/>|<$text text=<>/>)$genesis>
<$genesis $type="let" $names="$myvar $other" $values="Kitten Donkey" $$myvar="Shark">(<$text text=<<$myvar>>/>|<$text text=<<$other>>/>)$genesis>
+
title: ExpectedResult
-(Shark|Donkey)(Shark|Donkey)
\ No newline at end of file
+(Kitten|Donkey)(Kitten|Donkey)
\ No newline at end of file
From 9be05f6f383a9439323422bb06f251b9b576df87 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Sat, 7 May 2022 13:22:53 +0100
Subject: [PATCH 047/405] Use consistent parse tree node property for params
---
core/modules/filters/unknown.js | 2 +-
core/modules/parsers/wikiparser/rules/functiondef.js | 2 +-
core/modules/widgets/importvariables.js | 1 -
core/modules/widgets/setvariable.js | 2 +-
core/modules/widgets/transclude.js | 2 +-
core/modules/widgets/widget.js | 4 +---
editions/test/tiddlers/tests/test-wikitext-parser.js | 8 ++++----
7 files changed, 9 insertions(+), 12 deletions(-)
diff --git a/core/modules/filters/unknown.js b/core/modules/filters/unknown.js
index 93b42c706..7e962b30f 100644
--- a/core/modules/filters/unknown.js
+++ b/core/modules/filters/unknown.js
@@ -22,7 +22,7 @@ exports.unknown = function(source,operator,options) {
customDefinition = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(customDefinitionTitle);
if(customDefinition && customDefinition.srcVariable) {
var variables = Object.create(null);
- $tw.utils.each(customDefinition.srcVariable.variableParams,function(param,index) {
+ $tw.utils.each(customDefinition.srcVariable.params,function(param,index) {
var value = operator.operands[index];
if(value === undefined) {
value = param["default"] || "";
diff --git a/core/modules/parsers/wikiparser/rules/functiondef.js b/core/modules/parsers/wikiparser/rules/functiondef.js
index 1c6430bbd..59d66f9c0 100644
--- a/core/modules/parsers/wikiparser/rules/functiondef.js
+++ b/core/modules/parsers/wikiparser/rules/functiondef.js
@@ -84,7 +84,7 @@ exports.parse = function() {
value: {type: "string", value: text}
},
children: [],
- variableParams: params,
+ params: params,
isFunctionDefinition: true
}];
};
diff --git a/core/modules/widgets/importvariables.js b/core/modules/widgets/importvariables.js
index b4f0f7c9e..bae920d67 100644
--- a/core/modules/widgets/importvariables.js
+++ b/core/modules/widgets/importvariables.js
@@ -55,7 +55,6 @@ ImportVariablesWidget.prototype.execute = function(tiddlerList) {
type: "set",
attributes: parseTreeNode.attributes,
params: parseTreeNode.params,
- variableParams: parseTreeNode.variableParams,
isMacroDefinition: parseTreeNode.isMacroDefinition,
isFunctionDefinition: parseTreeNode.isFunctionDefinition
};
diff --git a/core/modules/widgets/setvariable.js b/core/modules/widgets/setvariable.js
index 8a35ffada..e176cd21d 100755
--- a/core/modules/widgets/setvariable.js
+++ b/core/modules/widgets/setvariable.js
@@ -51,7 +51,7 @@ SetWidget.prototype.execute = function() {
if(this.parseTreeNode.isMacroDefinition) {
this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,!!this.parseTreeNode.isMacroDefinition);
} else if(this.parseTreeNode.isFunctionDefinition) {
- this.setVariable(this.setName,this.getValue(),undefined,undefined,{isFunctionDefinition: this.parseTreeNode.isFunctionDefinition,variableParams: this.parseTreeNode.variableParams});
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isFunctionDefinition: this.parseTreeNode.isFunctionDefinition});
} else {
this.setVariable(this.setName,this.getValue());
}
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index 787e6a26d..5ccfa5921 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -184,7 +184,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
}
]
}
- $tw.utils.each(variableInfo.variableParams,function(param) {
+ $tw.utils.each(variableInfo.params,function(param) {
$tw.utils.addAttributeToParseTreeNode(parser.tree[0],param.name,param["default"])
});
}
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index 6c201177c..6a9a9d1d2 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -90,7 +90,6 @@ 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:
isFunctionDefinition: true if the variable is set via a \function pragma (and hence should not have variable substitution performed)
- variableParams: array of {name:, default:} for each function parameter
*/
Widget.prototype.setVariable = function(name,value,params,isMacroDefinition,options) {
options = options || {};
@@ -98,8 +97,7 @@ Widget.prototype.setVariable = function(name,value,params,isMacroDefinition,opti
value: value,
params: params,
isMacroDefinition: !!isMacroDefinition,
- isFunctionDefinition: !!options.isFunctionDefinition,
- variableParams: options.variableParams
+ isFunctionDefinition: !!options.isFunctionDefinition
};
};
diff --git a/editions/test/tiddlers/tests/test-wikitext-parser.js b/editions/test/tiddlers/tests/test-wikitext-parser.js
index 6c3d15ac6..bfaf00093 100644
--- a/editions/test/tiddlers/tests/test-wikitext-parser.js
+++ b/editions/test/tiddlers/tests/test-wikitext-parser.js
@@ -122,7 +122,7 @@ describe("WikiText parser tests", function() {
it("should parse function definitions with no parameters", function() {
expect(parse("\\function myMacro\nnothing\n\\end\n")).toEqual(
- [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], variableParams : [ ], isFunctionDefinition : true } ]
+ [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], params : [ ], isFunctionDefinition : true } ]
);
});
@@ -130,7 +130,7 @@ describe("WikiText parser tests", function() {
it("should parse single line function definitions with no parameters", function() {
expect(parse("\\function myMacro nothing\n")).toEqual(
- [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], variableParams : [ ], isFunctionDefinition : true } ]
+ [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], params : [ ], isFunctionDefinition : true } ]
);
});
@@ -138,7 +138,7 @@ describe("WikiText parser tests", function() {
it("should parse function definitions with parameters", function() {
expect(parse("\\function myMacro(one,two,three,four:elephant)\nnothing\n\\end\n")).toEqual(
- [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], variableParams : [ { name: 'one' }, { name: 'two' }, { name: 'three' }, { name: 'four', default: 'elephant' } ], isFunctionDefinition : true } ]
+ [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], params : [ { name: 'one' }, { name: 'two' }, { name: 'three' }, { name: 'four', default: 'elephant' } ], isFunctionDefinition : true } ]
);
});
@@ -146,7 +146,7 @@ describe("WikiText parser tests", function() {
it("should parse function definitions", function() {
expect(parse("\\function myMacro(one:'Jaguar')\n<$text text=<>/>\n\\end\n\n")).toEqual(
- [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : '<$text text=<>/>' } }, children : [ ], variableParams : [ { name: 'one', "default": 'Jaguar' } ], isFunctionDefinition : true } ]
+ [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : '<$text text=<>/>' } }, children : [ ], params : [ { name: 'one', "default": 'Jaguar' } ], isFunctionDefinition : true } ]
);
});
From e9630328f14c9c5de696ff468c945b0b24fb800c Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Sun, 8 May 2022 15:59:20 +0100
Subject: [PATCH 048/405] Extend transclude widget to work with old-style
macros and use it for the macrocall shortcut syntax
---
core/modules/parsers/parseutils.js | 31 ++++++-
.../wikiparser/rules/macrocallblock.js | 2 +-
.../wikiparser/rules/macrocallinline.js | 2 +-
core/modules/widgets/transclude.js | 87 +++++++++++++++----
core/modules/widgets/widget.js | 14 +--
.../tests/data/transclude/Macro-Simple.tid | 26 ++++++
.../tiddlers/tests/test-wikitext-parser.js | 36 ++++----
7 files changed, 152 insertions(+), 46 deletions(-)
create mode 100644 editions/test/tiddlers/tests/data/transclude/Macro-Simple.tid
diff --git a/core/modules/parsers/parseutils.js b/core/modules/parsers/parseutils.js
index 925674056..4a25259c8 100644
--- a/core/modules/parsers/parseutils.js
+++ b/core/modules/parsers/parseutils.js
@@ -175,7 +175,36 @@ exports.parseMacroParameter = function(source,pos) {
};
/*
-Look for a macro invocation. Returns null if not found, or {type: "macrocall", name:, parameters:, start:, end:}
+Look for a macro invocation. Returns null if not found, or {type: "transclude", attributes:, start:, end:}
+*/
+exports.parseMacroInvocationAsTransclusion = function(source,pos) {
+ var node = $tw.utils.parseMacroInvocation(source,pos);
+ if(node) {
+ var positionalName = 0,
+ transclusion = {
+ type: "transclude",
+ start: node.start,
+ end: node.end
+ };
+ $tw.utils.addAttributeToParseTreeNode(transclusion,"$variable",node.name);
+ $tw.utils.each(node.params,function(param) {
+ var name = param.name;
+ if(name) {
+ if(name.charAt(0) === "$") {
+ name = "$" + name;
+ }
+ $tw.utils.addAttributeToParseTreeNode(transclusion,{name: name,type: "string", value: param.value, start: param.start, end: param.end});
+ } else {
+ $tw.utils.addAttributeToParseTreeNode(transclusion,{name: (positionalName++) + "",type: "string", value: param.value, start: param.start, end: param.end});
+ }
+ });
+ return transclusion;
+ }
+ return node;
+};
+
+/*
+Look for a macro invocation. Returns null if not found, or {type: "macrocall", name:, params:, start:, end:}
*/
exports.parseMacroInvocation = function(source,pos) {
var node = {
diff --git a/core/modules/parsers/wikiparser/rules/macrocallblock.js b/core/modules/parsers/wikiparser/rules/macrocallblock.js
index 6f50fdbb0..a2c10e04a 100644
--- a/core/modules/parsers/wikiparser/rules/macrocallblock.js
+++ b/core/modules/parsers/wikiparser/rules/macrocallblock.js
@@ -27,7 +27,7 @@ exports.findNextMatch = function(startPos) {
var nextStart = startPos;
// Try parsing at all possible macrocall openers until we match
while((nextStart = this.parser.source.indexOf("<<",nextStart)) >= 0) {
- var nextCall = $tw.utils.parseMacroInvocation(this.parser.source,nextStart);
+ var nextCall = $tw.utils.parseMacroInvocationAsTransclusion(this.parser.source,nextStart);
if(nextCall) {
var c = this.parser.source.charAt(nextCall.end);
// Ensure EOL after parsed macro
diff --git a/core/modules/parsers/wikiparser/rules/macrocallinline.js b/core/modules/parsers/wikiparser/rules/macrocallinline.js
index 165a70dce..e9f79f09e 100644
--- a/core/modules/parsers/wikiparser/rules/macrocallinline.js
+++ b/core/modules/parsers/wikiparser/rules/macrocallinline.js
@@ -27,7 +27,7 @@ exports.findNextMatch = function(startPos) {
var nextStart = startPos;
// Try parsing at all possible macrocall openers until we match
while((nextStart = this.parser.source.indexOf("<<",nextStart)) >= 0) {
- this.nextCall = $tw.utils.parseMacroInvocation(this.parser.source,nextStart);
+ this.nextCall = $tw.utils.parseMacroInvocationAsTransclusion(this.parser.source,nextStart);
if(this.nextCall) {
return nextStart;
}
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index 5ccfa5921..499bb2379 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -166,27 +166,45 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
}
var parser;
if(this.transcludeVariable) {
- var variableInfo = this.getVariableInfo(this.transcludeVariable).srcVariable;
- if(variableInfo) {
+ var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()}),
+ srcVariable = variableInfo.srcVariable;
+ if(srcVariable) {
var mode = parseAsInline ? "inlineParser" : "blockParser";
- if(variableInfo[mode]) {
- parser = variableInfo[mode];
+ if(srcVariable.isCacheable && srcVariable[mode]) {
+ parser = srcVariable[mode];
} else {
- parser = this.wiki.parseText(this.transcludeType,variableInfo.value || "",{parseAsInline: parseAsInline});
- variableInfo[mode] = parser;
- }
- if(parser && variableInfo.isFunctionDefinition) {
- parser = {
- tree: [
- {
- type: "parameters",
- children: parser.tree
- }
- ]
+ parser = this.wiki.parseText(this.transcludeType,variableInfo.text || "",{parseAsInline: parseAsInline});
+ if(srcVariable.isCacheable) {
+ srcVariable[mode] = parser;
+ }
+ }
+ if(parser) {
+ if(srcVariable.isFunctionDefinition) {
+ parser = {
+ tree: [
+ {
+ type: "parameters",
+ children: parser.tree
+ }
+ ]
+ }
+ $tw.utils.each(srcVariable.params,function(param) {
+ $tw.utils.addAttributeToParseTreeNode(parser.tree[0],param.name,param["default"])
+ });
+ } else if(srcVariable.isMacroDefinition) {
+ // Wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__"
+ parser = {
+ tree: [
+ {
+ type: "vars",
+ children: parser.tree
+ }
+ ]
+ }
+ $tw.utils.each(variableInfo.params,function(param) {
+ $tw.utils.addAttributeToParseTreeNode(parser.tree[0],"__" + param.name + "__",param.value)
+ });
}
- $tw.utils.each(variableInfo.params,function(param) {
- $tw.utils.addAttributeToParseTreeNode(parser.tree[0],param.name,param["default"])
- });
}
}
} else {
@@ -218,6 +236,39 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
}
};
+/*
+Fetch all the string parameters as an ordered array of {name:, value:} where the name is optional
+*/
+TranscludeWidget.prototype.getOrderedTransclusionParameters = function() {
+ var result = [];
+ // Collect the parameters
+ for(var name in this.stringParametersByName) {
+ var value = this.stringParametersByName[name];
+ result.push({name: name, value: value});
+ }
+ // Sort numerical parameter names first
+ result.sort(function(a,b) {
+ var aIsNumeric = !isNaN(a.name),
+ bIsNumeric = !isNaN(b.name);
+ if(aIsNumeric && bIsNumeric) {
+ return a.name - b.name;
+ } else if(aIsNumeric) {
+ return -1;
+ } else if(bIsNumeric) {
+ return 1;
+ } else {
+ return a.name === b.name ? 0 : (a.name < b.name ? -1 : 1);
+ }
+ });
+ // Remove names from numerical parameters
+ $tw.utils.each(result,function(param,index) {
+ if(!isNaN(param.name)) {
+ delete param.name;
+ }
+ });
+ return result;
+};
+
/*
Fetch the value of a parameter
*/
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index 6a9a9d1d2..74162e52b 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -122,14 +122,14 @@ Widget.prototype.getVariableInfo = function(name,options) {
if(parentWidget && name in parentWidget.variables) {
var variable = parentWidget.variables[name],
originalValue = variable.value,
- value = originalValue,
- params = this.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);
- });
- // Only substitute variable references if this variable was defined with the \define pragma
+ value = originalValue;
+ // Only substitute parameter and variable references if this variable was defined with the \define pragma
if(variable.isMacroDefinition) {
+ var params = this.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);
+ });
value = this.substituteVariableReferences(value,options);
}
return {
diff --git a/editions/test/tiddlers/tests/data/transclude/Macro-Simple.tid b/editions/test/tiddlers/tests/data/transclude/Macro-Simple.tid
new file mode 100644
index 000000000..71db5efe4
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Macro-Simple.tid
@@ -0,0 +1,26 @@
+title: Transclude/Macro/Simple
+description: Transcluding a macro
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\define mamacro(one:"red",two:"green")
+It is $one$ and $two$ or <<__one__>> and <<__two__>>.
+\end
+
+<$macrocall $name="mamacro"/>
+
+<$transclude $variable="mamacro"/>
+
+<$transclude $variable="mamacro" one="orange"/>
+
+<$transclude $variable="mamacro" 0="pink"/>
+
+<$transclude $variable="mamacro" one="purple" 1="pink"/>
+
++
+title: ExpectedResult
+
+It is red and green or red and green.
It is red and green or red and green.
It is orange and green or orange and green.
It is pink and green or pink and green.
It is purple and pink or purple and pink.
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/test-wikitext-parser.js b/editions/test/tiddlers/tests/test-wikitext-parser.js
index bfaf00093..25d5c3437 100644
--- a/editions/test/tiddlers/tests/test-wikitext-parser.js
+++ b/editions/test/tiddlers/tests/test-wikitext-parser.js
@@ -175,38 +175,38 @@ describe("WikiText parser tests", function() {
it("should parse inline macro calls", function() {
expect(parse("<><><><>")).toEqual(
- [ { type: 'element', tag: 'p', start: 0, end: 35, children: [ { type: 'macrocall', start: 0, params: [ ], name: 'john', end: 8 }, { type: 'macrocall', start: 8, params: [ ], name: 'paul', end: 16 }, { type: 'macrocall', start: 16, params: [ ], name: 'george', end: 26 }, { type: 'macrocall', start: 26, params: [ ], name: 'ringo', end: 35 } ] } ]
+ [{"type":"element","tag":"p","children":[{"type":"transclude","start":0,"end":8,"attributes":{"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"}]},{"type":"transclude","start":8,"end":16,"attributes":{"$variable":{"name":"$variable","type":"string","value":"paul"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"paul"}]},{"type":"transclude","start":16,"end":26,"attributes":{"$variable":{"name":"$variable","type":"string","value":"george"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"george"}]},{"type":"transclude","start":26,"end":35,"attributes":{"$variable":{"name":"$variable","type":"string","value":"ringo"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"ringo"}]}],"start":0,"end":35}]
);
expect(parse("text <>")).toEqual(
- [{ type: 'element', tag: 'p', start: 0, end: 92, children: [ { type: 'text', text: 'text ', start: 0, end: 5 }, { type: 'macrocall', name: 'john', start: 5, params: [ { type: 'macro-parameter', start: 11, value: 'val1', name: 'one', end: 20 }, { type: 'macro-parameter', start: 20, value: 'val "2"', name: 'two', end: 35 }, { type: 'macro-parameter', start: 35, value: 'val \'3\'', name: 'three', end: 52 }, { type: 'macro-parameter', start: 52, value: 'val 4"5\'', name: 'four', end: 73 }, { type: 'macro-parameter', start: 73, value: 'val 5', name: 'five', end: 89 } ], end: 92 } ] } ]
+ [{"type":"element","tag":"p","children":[{"type":"text","text":"text ","start":0,"end":5},{"type":"transclude","start":5,"end":92,"attributes":{"$variable":{"name":"$variable","type":"string","value":"john"},"one":{"name":"one","type":"string","value":"val1","start":11,"end":20},"two":{"name":"two","type":"string","value":"val \"2\"","start":20,"end":35},"three":{"name":"three","type":"string","value":"val '3'","start":35,"end":52},"four":{"name":"four","type":"string","value":"val 4\"5'","start":52,"end":73},"five":{"name":"five","type":"string","value":"val 5","start":73,"end":89}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"one","type":"string","value":"val1","start":11,"end":20},{"name":"two","type":"string","value":"val \"2\"","start":20,"end":35},{"name":"three","type":"string","value":"val '3'","start":35,"end":52},{"name":"four","type":"string","value":"val 4\"5'","start":52,"end":73},{"name":"five","type":"string","value":"val 5","start":73,"end":89}]}],"start":0,"end":92}]
);
expect(parse("ignored << carrots <>")).toEqual(
- [ { type: 'element', tag: 'p', start: 0, end: 27, children: [ { type: 'text', text: 'ignored << carrots ', start: 0, end: 19 }, { type: 'macrocall', name: 'john', start: 19, params: [ ], end: 27 } ] } ]
+ [{"type":"element","tag":"p","children":[{"type":"text","text":"ignored << carrots ","start":0,"end":19},{"type":"transclude","start":19,"end":27,"attributes":{"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"}]}],"start":0,"end":27}]
);
expect(parse("text <<>")).toEqual(
- [ { type: 'element', tag: 'p', start: 0, end: 14, children: [ { type: 'text', text: 'text ', start: 0, end: 5 }, { type: 'macrocall', name: '>")).toEqual(
- [ { type: 'element', tag: 'p', start: 0, end: 15, children: [ { type: 'text', text: 'before\n', start: 0, end: 7 }, { type: 'macrocall', start: 7, params: [ ], name: 'john', end: 15 } ] } ]
+ [{"type":"element","tag":"p","children":[{"type":"text","text":"before\n","start":0,"end":7},{"type":"transclude","start":7,"end":15,"attributes":{"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"}]}],"start":0,"end":15}]
);
// A single space will cause it to be inline
expect(parse("<> ")).toEqual(
- [ { type: 'element', tag: 'p', start: 0, end: 9, children: [ { type: 'macrocall', start: 0, params: [ ], name: 'john', end: 8 }, { type: 'text', text: ' ', start: 8, end: 9 } ] } ]
+ [{"type":"element","tag":"p","children":[{"type":"transclude","start":0,"end":8,"attributes":{"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"}]},{"type":"text","text":" ","start":8,"end":9}],"start":0,"end":9}]
);
expect(parse("text <>' >>")).toEqual(
- [ { type: 'element', tag: 'p', start: 0, end: 34, children: [ { type: 'text', text: 'text ', start: 0, end: 5 }, { type: 'macrocall', start: 5, params: [ { type: 'macro-parameter', start: 12, value: 'my <>', name: 'one', end: 31 } ], name: 'outie', end: 34 } ] } ]
+ [{"type":"element","tag":"p","children":[{"type":"text","text":"text ","start":0,"end":5},{"type":"transclude","start":5,"end":34,"attributes":{"$variable":{"name":"$variable","type":"string","value":"outie"},"one":{"name":"one","type":"string","value":"my <>","start":12,"end":31}},"orderedAttributes":[{"name":"$variable","type":"string","value":"outie"},{"name":"one","type":"string","value":"my <>","start":12,"end":31}]}],"start":0,"end":34}]
);
@@ -215,37 +215,37 @@ describe("WikiText parser tests", function() {
it("should parse block macro calls", function() {
expect(parse("<>\n<>\r\n<>\n<>")).toEqual(
- [ { type: 'macrocall', start: 0, name: 'john', params: [ ], end: 8, isBlock: true }, { type: 'macrocall', start: 9, name: 'paul', params: [ ], end: 17, isBlock: true }, { type: 'macrocall', start: 19, name: 'george', params: [ ], end: 29, isBlock: true }, { type: 'macrocall', start: 30, name: 'ringo', params: [ ], end: 39, isBlock: true } ]
+ [ { type: 'transclude', start: 0, attributes: { $variable: { name: "$variable", type: "string", value: "john" }}, orderedAttributes: [ { name: "$variable", type: "string", value: "john" }], end: 8, isBlock: true }, { type: 'transclude', start: 9, attributes: { $variable: { name: "$variable", type: "string", value: "paul" }}, orderedAttributes: [ { name: "$variable", type: "string", value: "paul" }], end: 17, isBlock: true }, { type: 'transclude', start: 19, attributes: { $variable: { name: "$variable", type: "string", value: "george" }}, orderedAttributes: [ { name: "$variable", type: "string", value: "george" }], end: 29, isBlock: true }, { type: 'transclude', start: 30, attributes: { $variable: { name: "$variable", type: "string", value: "ringo" }}, orderedAttributes: [ { name: "$variable", type: "string", value: "ringo" }], end: 39, isBlock: true } ]
);
expect(parse("<>")).toEqual(
- [ { type: 'macrocall', start: 0, name: 'john', params: [ { type: 'macro-parameter', start: 6, value: 'val1', name: 'one', end: 15 }, { type: 'macro-parameter', start: 15, value: 'val "2"', name: 'two', end: 30 }, { type: 'macro-parameter', start: 30, value: 'val \'3\'', name: 'three', end: 47 }, { type: 'macro-parameter', start: 47, value: 'val 4"5\'', name: 'four', end: 68 }, { type: 'macro-parameter', start: 68, value: 'val 5', name: 'five', end: 84 }], end: 87, isBlock: true } ]
+ [{"type":"transclude","start":0,"end":87,"attributes":{"$variable":{"name":"$variable","type":"string","value":"john"},"one":{"name":"one","type":"string","value":"val1","start":6,"end":15},"two":{"name":"two","type":"string","value":"val \"2\"","start":15,"end":30},"three":{"name":"three","type":"string","value":"val '3'","start":30,"end":47},"four":{"name":"four","type":"string","value":"val 4\"5'","start":47,"end":68},"five":{"name":"five","type":"string","value":"val 5","start":68,"end":84}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"one","type":"string","value":"val1","start":6,"end":15},{"name":"two","type":"string","value":"val \"2\"","start":15,"end":30},{"name":"three","type":"string","value":"val '3'","start":30,"end":47},{"name":"four","type":"string","value":"val 4\"5'","start":47,"end":68},{"name":"five","type":"string","value":"val 5","start":68,"end":84}],"isBlock":true}]
);
expect(parse("<< carrots\n\n<>")).toEqual(
- [ { type: 'element', tag: 'p', start : 0, end : 10, children: [ { type: 'text', text: '<< carrots', start : 0, end : 10 } ] }, { type: 'macrocall', start: 12, params: [ ], name: 'john', end: 20, isBlock: true } ]
+ [ { type: 'element', tag: 'p', start : 0, end : 10, children: [ { type: 'text', text: '<< carrots', start : 0, end : 10 } ] }, { type: 'transclude', start: 12, attributes: { $variable: {name: "$variable", type:"string", value: "john"} }, orderedAttributes: [ {name: "$variable", type:"string", value: "john"} ], end: 20, isBlock: true } ]
);
expect(parse("before\n\n<>")).toEqual(
- [ { type: 'element', tag: 'p', start : 0, end : 6, children: [ { type: 'text', text: 'before', start : 0, end : 6 } ] }, { type: 'macrocall', start: 8, name: 'john', params: [ ], end: 16, isBlock: true } ]
+ [ { type: 'element', tag: 'p', start : 0, end : 6, children: [ { type: 'text', text: 'before', start : 0, end : 6 } ] }, { type: 'transclude', start: 8, attributes: { $variable: {name: "$variable", type:"string", value: "john"} }, orderedAttributes: [ {name: "$variable", type:"string", value: "john"} ], end: 16, isBlock: true } ]
);
expect(parse("<>\nafter")).toEqual(
- [ { type: 'macrocall', start: 0, name: 'john', params: [ ], end: 8, isBlock: true }, { type: 'element', tag: 'p', start: 9, end: 14, children: [ { type: 'text', text: 'after', start: 9, end: 14 } ] } ]
+ [ { type: 'transclude', start: 0, attributes: { $variable: {name: "$variable", type:"string", value: "john"} }, orderedAttributes: [ {name: "$variable", type:"string", value: "john"} ], end: 8, isBlock: true }, { type: 'element', tag: 'p', start: 9, end: 14, children: [ { type: 'text', text: 'after', start: 9, end: 14 } ] } ]
);
expect(parse("<>")).toEqual(
- [ { type: 'macrocall', start: 0, params: [ { type: 'macro-parameter', start: 11, value: '\n\nwikitext\n', name: 'arg', end: 33 } ], name: 'multiline', end: 36, isBlock: true }]
+ [{"type":"transclude","start":0,"end":36,"attributes":{"$variable":{"name":"$variable","type":"string","value":"multiline"},"arg":{"name":"arg","type":"string","value":"\n\nwikitext\n","start":11,"end":33}},"orderedAttributes":[{"name":"$variable","type":"string","value":"multiline"},{"name":"arg","type":"string","value":"\n\nwikitext\n","start":11,"end":33}],"isBlock":true}]
);
expect(parse("<>' >>")).toEqual(
- [ { type: 'macrocall', start: 0, params: [ { type: 'macro-parameter', start: 7, value: 'my <>', name: 'one', end: 26 } ], name: 'outie', end: 29, isBlock: true } ]
+ [ { type: 'transclude', start: 0, attributes: { $variable: {name: "$variable", type:"string", value: "outie"}, one: {name: "one", type:"string", value: "my <>", start: 7, end: 26} }, orderedAttributes: [ {name: "$variable", type:"string", value: "outie"}, {name: "one", type:"string", value: "my <>", start: 7, end: 26} ], end: 29, isBlock: true } ]
);
});
@@ -253,23 +253,23 @@ describe("WikiText parser tests", function() {
it("should parse tricky macrocall parameters", function() {
expect(parse("<am>>")).toEqual(
- [ { type: 'macrocall', start: 0, params: [ { type: 'macro-parameter', start: 6, value: 'pa>am', end: 12 } ], name: 'john', end: 14, isBlock: true } ]
+ [{"type":"transclude","start":0,"end":14,"attributes":{"0":{"name":"0","type":"string","value":"pa>am","start":6,"end":12},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"pa>am","start":6,"end":12}],"isBlock":true}]
);
expect(parse("< >>")).toEqual(
- [ { type: 'macrocall', start: 0, params: [ { type: 'macro-parameter', start: 6, value: 'param>', end: 13 } ], name: 'john', end: 16, isBlock: true } ]
+ [{"type":"transclude","start":0,"end":16,"attributes":{"0":{"name":"0","type":"string","value":"param>","start":6,"end":13},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"param>","start":6,"end":13}],"isBlock":true}]
);
expect(parse("<>>")).toEqual(
- [ { type: 'element', tag: 'p', start: 0, end: 15, children: [ { type: 'macrocall', start: 0, params: [ { type: 'macro-parameter', start: 6, value: 'param', end: 12 } ], name: 'john', end: 14 }, { type: 'text', text: '>', start: 14, end: 15 } ] } ]
+ [{"type":"element","tag":"p","children":[{"type":"transclude","start":0,"end":14,"attributes":{"0":{"name":"0","type":"string","value":"param","start":6,"end":12},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"param","start":6,"end":12}]},{"type":"text","text":">","start":14,"end":15}],"start":0,"end":15}]
);
// equals signs should be allowed
expect(parse("<=4 >>")).toEqual(
- [ { type: 'macrocall', start: 0, params: [ { type: 'macro-parameter', start: 6, value: 'var>=4', end: 13 } ], name: 'john', end: 16, isBlock: true } ]
+ [{"type":"transclude","start":0,"end":16,"attributes":{"0":{"name":"0","type":"string","value":"var>=4","start":6,"end":13},"$variable":{"name":"$variable","type":"string","value":"john"}},"orderedAttributes":[{"name":"$variable","type":"string","value":"john"},{"name":"0","type":"string","value":"var>=4","start":6,"end":13}],"isBlock":true}]
);
From 4f2f689ab9498ae0692c59363204e0a3c8264528 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Sun, 8 May 2022 16:05:32 +0100
Subject: [PATCH 049/405] Clarify that the recent changes allow functions to be
invoked with the double bracket syntax
In other words, the transclude widget distinguishes between functions and macros and handles the parameters appropriately
---
.../tests/data/transclude/Parameterised-Shortcut.tid | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid
index 88017392e..5612793cc 100644
--- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid
@@ -12,7 +12,10 @@ title: Output
<$transclude $variable='test' one='Ferret'/>
<$transclude $variable='test'/>
+<>
+<>
+
+
title: ExpectedResult
-{Ferret}{Jaguar}
\ No newline at end of file
+{Ferret}{Jaguar}{Rat}{Mouse}
\ No newline at end of file
From 64448ae7743a5baa1317cf9ed46dedf792166d37 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Sun, 8 May 2022 20:48:33 +0100
Subject: [PATCH 050/405] Make the macrocall widget delegate to the transclude
widget
---
core/modules/parsers/audioparser.js | 2 +
core/modules/parsers/binaryparser.js | 2 +
core/modules/parsers/csvparser.js | 2 +
core/modules/parsers/htmlparser.js | 2 +
core/modules/parsers/imageparser.js | 2 +
core/modules/parsers/pdfparser.js | 2 +
core/modules/parsers/textparser.js | 2 +
core/modules/parsers/videoparser.js | 2 +
core/modules/widgets/macrocall.js | 61 ++++++-------------
core/modules/widgets/transclude.js | 40 +++++++++---
.../tests/data/transclude/Macro-Plain.tid | 17 ++++++
.../tiddlers/tests/test-parsetextreference.js | 2 +-
.../tests/test-wikitext-tabs-macro.js | 2 +-
13 files changed, 87 insertions(+), 51 deletions(-)
create mode 100644 editions/test/tiddlers/tests/data/transclude/Macro-Plain.tid
diff --git a/core/modules/parsers/audioparser.js b/core/modules/parsers/audioparser.js
index 95380bf80..5eb2ff985 100644
--- a/core/modules/parsers/audioparser.js
+++ b/core/modules/parsers/audioparser.js
@@ -28,6 +28,8 @@ var AudioParser = function(type,text,options) {
element.attributes.src = {type: "string", value: "data:" + type + ";base64," + text};
}
this.tree = [element];
+ this.source = text;
+ this.type = type;
};
exports["audio/ogg"] = AudioParser;
diff --git a/core/modules/parsers/binaryparser.js b/core/modules/parsers/binaryparser.js
index b7dce4a56..60e7b5ef0 100644
--- a/core/modules/parsers/binaryparser.js
+++ b/core/modules/parsers/binaryparser.js
@@ -64,6 +64,8 @@ var BinaryParser = function(type,text,options) {
children: [warn, link]
}
this.tree = [element];
+ this.source = text;
+ this.type = type;
};
exports["application/octet-stream"] = BinaryParser;
diff --git a/core/modules/parsers/csvparser.js b/core/modules/parsers/csvparser.js
index 0e6c9f7bc..6565a6f43 100644
--- a/core/modules/parsers/csvparser.js
+++ b/core/modules/parsers/csvparser.js
@@ -45,6 +45,8 @@ var CsvParser = function(type,text,options) {
this.tree[0].children[0].children[0].children.push(row);
}
}
+ this.source = text;
+ this.type = type;
};
exports["text/csv"] = CsvParser;
diff --git a/core/modules/parsers/htmlparser.js b/core/modules/parsers/htmlparser.js
index 206ab9c78..24c9f5d3e 100644
--- a/core/modules/parsers/htmlparser.js
+++ b/core/modules/parsers/htmlparser.js
@@ -29,6 +29,8 @@ var HtmlParser = function(type,text,options) {
if($tw.wiki.getTiddlerText("$:/config/HtmlParser/DisableSandbox","no") !== "yes") {
this.tree[0].attributes.sandbox = {type: "string", value: $tw.wiki.getTiddlerText("$:/config/HtmlParser/SandboxTokens","")};
}
+ this.source = text;
+ this.type = type;
};
exports["text/html"] = HtmlParser;
diff --git a/core/modules/parsers/imageparser.js b/core/modules/parsers/imageparser.js
index e3b8fb60a..a964a4ba8 100644
--- a/core/modules/parsers/imageparser.js
+++ b/core/modules/parsers/imageparser.js
@@ -28,6 +28,8 @@ var ImageParser = function(type,text,options) {
}
}
this.tree = [element];
+ this.source = text;
+ this.type = type;
};
exports["image/svg+xml"] = ImageParser;
diff --git a/core/modules/parsers/pdfparser.js b/core/modules/parsers/pdfparser.js
index 95d74ef4b..358046629 100644
--- a/core/modules/parsers/pdfparser.js
+++ b/core/modules/parsers/pdfparser.js
@@ -25,6 +25,8 @@ var ImageParser = function(type,text,options) {
element.attributes.src = {type: "string", value: "data:application/pdf;base64," + text};
}
this.tree = [element];
+ this.source = text;
+ this.type = type;
};
exports["application/pdf"] = ImageParser;
diff --git a/core/modules/parsers/textparser.js b/core/modules/parsers/textparser.js
index 4f55f6f0c..06b08f30f 100644
--- a/core/modules/parsers/textparser.js
+++ b/core/modules/parsers/textparser.js
@@ -20,6 +20,8 @@ var TextParser = function(type,text,options) {
language: {type: "string", value: type}
}
}];
+ this.source = text;
+ this.type = type;
};
exports["text/plain"] = TextParser;
diff --git a/core/modules/parsers/videoparser.js b/core/modules/parsers/videoparser.js
index f1c281c7c..1c8a38bb2 100644
--- a/core/modules/parsers/videoparser.js
+++ b/core/modules/parsers/videoparser.js
@@ -28,6 +28,8 @@ var VideoParser = function(type,text,options) {
element.attributes.src = {type: "string", value: "data:" + type + ";base64," + text};
}
this.tree = [element];
+ this.source = text;
+ this.type = type;
};
exports["video/ogg"] = VideoParser;
diff --git a/core/modules/widgets/macrocall.js b/core/modules/widgets/macrocall.js
index 9de2e5d67..e49eadfe0 100644
--- a/core/modules/widgets/macrocall.js
+++ b/core/modules/widgets/macrocall.js
@@ -37,7 +37,7 @@ MacroCallWidget.prototype.render = function(parent,nextSibling) {
Compute the internal state of the widget
*/
MacroCallWidget.prototype.execute = function() {
- // Get the parse type if specified
+ this.macroName = this.parseTreeNode.name || this.getAttribute("$name"),
this.parseType = this.getAttribute("$type","text/vnd.tiddlywiki");
this.renderOutput = this.getAttribute("$output","text/html");
// Merge together the parameters specified in the parse tree with the specified attributes
@@ -47,49 +47,26 @@ MacroCallWidget.prototype.execute = function() {
params.push({name: name, value: attribute});
}
});
- // Get the macro value
- var macroName = this.parseTreeNode.name || this.getAttribute("$name"),
- variableInfo = this.getVariableInfo(macroName,{params: params}),
- text = variableInfo.text,
- parseTreeNodes;
- // Are we rendering to HTML?
- if(this.renderOutput === "text/html") {
- // If so we'll return the parsed macro
- // Check if we've already cached parsing this macro
- var mode = this.parseTreeNode.isBlock ? "blockParser" : "inlineParser",
- parser;
- if(variableInfo.srcVariable && variableInfo.srcVariable[mode]) {
- parser = variableInfo.srcVariable[mode];
- } else {
- parser = this.wiki.parseText(this.parseType,text,
- {parseAsInline: !this.parseTreeNode.isBlock});
- if(variableInfo.isCacheable && variableInfo.srcVariable) {
- variableInfo.srcVariable[mode] = parser;
- }
- }
- var parseTreeNodes = parser ? parser.tree : [];
- // Wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__"
- var attributes = {};
- $tw.utils.each(variableInfo.params,function(param) {
- var name = "__" + param.name + "__";
- attributes[name] = {
- name: name,
- type: "string",
- value: param.value
- };
- });
+ // Make a transclude widget
+ var positionalName = 0,
parseTreeNodes = [{
- type: "vars",
- attributes: attributes,
- children: parseTreeNodes
+ type: "transclude",
+ isBlock: this.parseTreeNode.isBlock
}];
- } else if(this.renderOutput === "text/raw") {
- parseTreeNodes = [{type: "text", text: text}];
- } else {
- // Otherwise, we'll render the text
- var plainText = this.wiki.renderText("text/plain",this.parseType,text,{parentWidget: this});
- parseTreeNodes = [{type: "text", text: plainText}];
- }
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"$variable",this.macroName);
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"$type",this.parseType);
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"$output",this.renderOutput);
+ $tw.utils.each(params,function(param) {
+ var name = param.name;
+ if(name) {
+ if(name.charAt(0) === "$") {
+ name = "$" + name;
+ }
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],name,param.value);
+ } else {
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],(positionalName++) + "",param.value);
+ }
+ });
// Construct the child widgets
this.makeChildWidgets(parseTreeNodes);
};
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index 499bb2379..afe247ad7 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -44,9 +44,24 @@ TranscludeWidget.prototype.execute = function() {
// Get the parse tree nodes that we are transcluding
var target = this.getTransclusionTarget(),
parseTreeNodes = target.parseTreeNodes;
- this.sourceText = target.source;
+ this.sourceText = target.text;
this.sourceType = target.type;
this.parseAsInline = target.parseAsInline;
+ // Process the transclusion according to the output type
+ switch(this.transcludeOutput || "text/html") {
+ case "text/html":
+ // No further processing required
+ break;
+ case "text/raw":
+ // Just return the raw text
+ parseTreeNodes = [{type: "text", text: this.sourceText}];
+ break;
+ default:
+ // text/plain
+ var plainText = this.wiki.renderText("text/plain",this.sourceType,this.sourceText,{parentWidget: this});
+ parseTreeNodes = [{type: "text", text: plainText}];
+ break;
+ }
// Set context variables for recursion detection
var recursionMarker = this.makeLegacyRecursionMarker(),
newRecursionMarker = this.makeRecursionMarker();
@@ -91,6 +106,7 @@ TranscludeWidget.prototype.collectAttributes = function() {
} else {
this.transcludeVariable = this.getAttribute("$variable");
this.transcludeType = this.getAttribute("$type");
+ this.transcludeOutput = this.getAttribute("$output","text/html");
this.transcludeTitle = this.getAttribute("$tiddler",this.getVariable("currentTiddler"));
this.transcludeSubTiddler = this.getAttribute("$subtiddler");
this.transcludeField = this.getAttribute("$field");
@@ -157,7 +173,7 @@ TranscludeWidget.prototype.collectSlotValueParameters = function() {
Get transcluded parse tree nodes as an object {parser:,text:,type:}
*/
TranscludeWidget.prototype.getTransclusionTarget = function() {
- // Parse the text reference
+ // Determine whether we're being used in inline or block mode
var parseAsInline = !this.parseTreeNode.isBlock;
if(this.transcludeMode === "inline") {
parseAsInline = true;
@@ -165,9 +181,11 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
parseAsInline = false;
}
var parser;
+ // Get the parse tree
if(this.transcludeVariable) {
+ // Transcluding a variable
var variableInfo = this.getVariableInfo(this.transcludeVariable,{params: this.getOrderedTransclusionParameters()}),
- srcVariable = variableInfo.srcVariable;
+ srcVariable = variableInfo && variableInfo.srcVariable;
if(srcVariable) {
var mode = parseAsInline ? "inlineParser" : "blockParser";
if(srcVariable.isCacheable && srcVariable[mode]) {
@@ -179,6 +197,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
}
}
if(parser) {
+ // Add parameters widget for functions
if(srcVariable.isFunctionDefinition) {
parser = {
tree: [
@@ -186,20 +205,24 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
type: "parameters",
children: parser.tree
}
- ]
+ ],
+ source: parser.source,
+ type: parser.type
}
$tw.utils.each(srcVariable.params,function(param) {
$tw.utils.addAttributeToParseTreeNode(parser.tree[0],param.name,param["default"])
});
} else if(srcVariable.isMacroDefinition) {
- // Wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__"
+ // For macros, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__"
parser = {
tree: [
{
type: "vars",
children: parser.tree
}
- ]
+ ],
+ source: parser.source,
+ type: parser.type
}
$tw.utils.each(variableInfo.params,function(param) {
$tw.utils.addAttributeToParseTreeNode(parser.tree[0],"__" + param.name + "__",param.value)
@@ -208,6 +231,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
}
}
} else {
+ // Transcluding a text reference
parser = this.wiki.parseTextReference(
this.transcludeTitle,
this.transcludeField,
@@ -217,6 +241,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
subTiddler: this.transcludeSubTiddler
});
}
+ // Return the parse tree
if(parser) {
return {
parser: parser,
@@ -226,6 +251,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
type: parser.type
};
} else {
+ // If there's no parse tree then return the missing slot value
return {
parser: null,
parseTreeNodes: (this.slotValueParseTrees["ts-missing"] || []),
@@ -364,7 +390,7 @@ TranscludeWidget.prototype.makeLegacyRecursionMarker = function() {
};
TranscludeWidget.prototype.parserNeedsRefresh = function() {
- // TODO: Doesn't consider transcluded variables
+ // Doesn't need to consider transcluded variables because a parent variable can't change once a widget has been created
var parserInfo = this.wiki.getTextReferenceParserInfo(this.transcludeTitle,this.transcludeField,this.transcludeIndex,{subTiddler:this.transcludeSubTiddler});
return (this.sourceText === undefined || parserInfo.sourceText !== this.sourceText || parserInfo.parserType !== this.parserType)
};
diff --git a/editions/test/tiddlers/tests/data/transclude/Macro-Plain.tid b/editions/test/tiddlers/tests/data/transclude/Macro-Plain.tid
new file mode 100644
index 000000000..410144153
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Macro-Plain.tid
@@ -0,0 +1,17 @@
+title: Transclude/Macro/Plain
+description: Transcluding a macro as plain text
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+<$let currentTab="Jeremy">
+<$macrocall $name="currentTab" $type="text/plain" $output="text/plain"/>
+|
+<$transclude $variable="currentTab" $type="text/plain" $output="text/plain"/>
+$let>
++
+title: ExpectedResult
+
+Jeremy|Jeremy
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/test-parsetextreference.js b/editions/test/tiddlers/tests/test-parsetextreference.js
index 376ad9ec4..59f885232 100644
--- a/editions/test/tiddlers/tests/test-parsetextreference.js
+++ b/editions/test/tiddlers/tests/test-parsetextreference.js
@@ -124,7 +124,7 @@ describe("Wiki.parseTextReference tests", function() {
// Non-existent subtiddler of a plugin
expect(parseAndGetSource("$:/ShadowPlugin","text",null,"MyMissingTiddler")).toEqual(null);
// Plain text tiddler
- expect(parseAndGetSource("TiddlerNine")).toEqual(undefined);
+ expect(parseAndGetSource("TiddlerNine")).toEqual("this is plain text");
});
});
diff --git a/editions/test/tiddlers/tests/test-wikitext-tabs-macro.js b/editions/test/tiddlers/tests/test-wikitext-tabs-macro.js
index b37f402cc..295bd365e 100644
--- a/editions/test/tiddlers/tests/test-wikitext-tabs-macro.js
+++ b/editions/test/tiddlers/tests/test-wikitext-tabs-macro.js
@@ -74,7 +74,7 @@ describe("Tabs-macro HTML tests", function() {
expect(wiki.renderTiddler("text/html","test-tabs-macro-horizontal")).toBe(expected.fields.text.replace(/\n/g,""));
});
- it("should render 'horizontal' tabs from v5.2.2 and up with whitespace trim", function() {
+ it("should render all 'horizontal' tabs from v5.2.2 and up with whitespace trim", function() {
expect(wiki.renderTiddler("text/html","test-tabs-macro-horizontal-all")).toBe(expectedAll.fields.text.replace(/\n/g,""));
});
From e092113f9fa29194b603accf0e088d80cc9dc136 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Mon, 9 May 2022 18:00:09 +0100
Subject: [PATCH 051/405] Switch to using \procedure to define new-style
macros, and \function for custom filter operator functions
I now need to update the OP!
---
core/modules/filters/unknown.js | 10 ++++--
.../rules/{functiondef.js => fnprocdef.js} | 31 ++++++++++-------
core/modules/widgets/importvariables.js | 5 +--
core/modules/widgets/setvariable.js | 6 ++--
core/modules/widgets/transclude.js | 4 +--
core/modules/widgets/widget.js | 4 ++-
.../tests/data/genesis-widget/RedefineLet.tid | 2 +-
.../transclude/CustomWidget-ActionWidget.tid | 2 +-
.../CustomWidget-Override-Codeblock.tid | 2 +-
.../CustomWidget-OverrideTransclude.tid | 2 +-
.../data/transclude/CustomWidget-Simple.tid | 2 +-
.../CustomWidget-TextWidgetOverride.tid | 2 +-
.../CustomWidget-VariableAttribute.tid | 2 +-
.../transclude/Parameterised-Shortcut.tid | 2 +-
.../tiddlers/tests/test-wikitext-parser.js | 33 ++++++++++++++++++-
15 files changed, 80 insertions(+), 29 deletions(-)
rename core/modules/parsers/wikiparser/rules/{functiondef.js => fnprocdef.js} (74%)
diff --git a/core/modules/filters/unknown.js b/core/modules/filters/unknown.js
index 7e962b30f..c2e2912a4 100644
--- a/core/modules/filters/unknown.js
+++ b/core/modules/filters/unknown.js
@@ -20,7 +20,7 @@ Export our filter function
exports.unknown = function(source,operator,options) {
var customDefinitionTitle = "[" + operator.operator + "[]]",
customDefinition = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(customDefinitionTitle);
- if(customDefinition && customDefinition.srcVariable) {
+ if(customDefinition && customDefinition.srcVariable && customDefinition.srcVariable.isFunctionDefinition) {
var variables = Object.create(null);
$tw.utils.each(customDefinition.srcVariable.params,function(param,index) {
var value = operator.operands[index];
@@ -37,7 +37,13 @@ exports.unknown = function(source,operator,options) {
};
};
var getVariableInfo = function(name,opts) {
- return options.widget.getVariableInfo(name,opts);
+ if(name in variables) {
+ return {
+ text: variables[name]
+ };
+ } else {
+ return options.widget.getVariableInfo(name,opts);
+ };
}
var list = options.wiki.filterTiddlers(customDefinition.srcVariable.value,{getVariable: getVariable,getVariableInfo: getVariableInfo},source);
if(operator.prefix === "!") {
diff --git a/core/modules/parsers/wikiparser/rules/functiondef.js b/core/modules/parsers/wikiparser/rules/fnprocdef.js
similarity index 74%
rename from core/modules/parsers/wikiparser/rules/functiondef.js
rename to core/modules/parsers/wikiparser/rules/fnprocdef.js
index 59d66f9c0..037172c57 100644
--- a/core/modules/parsers/wikiparser/rules/functiondef.js
+++ b/core/modules/parsers/wikiparser/rules/fnprocdef.js
@@ -1,14 +1,18 @@
/*\
-title: $:/core/modules/parsers/wikiparser/rules/functiondef.js
+title: $:/core/modules/parsers/wikiparser/rules/fnprocdef.js
type: application/javascript
module-type: wikirule
-Wiki pragma rule for function definitions
+Wiki pragma rule for function and procedure definitions
```
\function name(param:defaultvalue,param2:defaultvalue)
definition text
\end
+
+\procedure name(param:defaultvalue,param2:defaultvalue)
+definition text
+\end
```
\*/
@@ -18,7 +22,7 @@ definition text
/*global $tw: false */
"use strict";
-exports.name = "functiondef";
+exports.name = "fnprocdef";
exports.types = {pragma: true};
/*
@@ -27,7 +31,7 @@ Instantiate parse rule
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /^\\function\s+([^(\s]+)(\(\s*([^)]*)\))?(\s*\r?\n)?/mg;
+ this.matchRegExp = /^\\(function|procedure)\s+([^(\s]+)(\(\s*([^)]*)\))?(\s*\r?\n)?/mg;
};
/*
@@ -37,9 +41,9 @@ exports.parse = function() {
// Move past the macro name and parameters
this.parser.pos = this.matchRegExp.lastIndex;
// Parse the parameters
- var paramString = this.match[3],
+ var paramString = this.match[4],
params = [];
- if(this.match[2]) {
+ if(this.match[3]) {
var reParam = /\s*([^:),\s]+)(?:\s*:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|([^,"'\s]+)))?/mg,
paramMatch = reParam.exec(paramString);
while(paramMatch) {
@@ -56,7 +60,7 @@ exports.parse = function() {
}
// Is this a multiline definition?
var reEnd;
- if(this.match[4]) {
+ if(this.match[5]) {
// If so, the end of the body is marked with \end
reEnd = /(\r?\n\\end[^\S\n\r]*(?:$|\r?\n))/mg;
} else {
@@ -77,16 +81,21 @@ exports.parse = function() {
text = "";
}
// Save the macro definition
- return [{
+ var parseTreeNodes = [{
type: "set",
attributes: {
- name: {type: "string", value: this.match[1]},
+ name: {type: "string", value: this.match[2]},
value: {type: "string", value: text}
},
children: [],
- params: params,
- isFunctionDefinition: true
+ params: params
}];
+ if(this.match[1] === "function") {
+ parseTreeNodes[0].isFunctionDefinition = true;
+ } else if(this.match[1] === "procedure") {
+ parseTreeNodes[0].isProcedureDefinition = true;
+ }
+ return parseTreeNodes;
};
})();
diff --git a/core/modules/widgets/importvariables.js b/core/modules/widgets/importvariables.js
index bae920d67..edea63311 100644
--- a/core/modules/widgets/importvariables.js
+++ b/core/modules/widgets/importvariables.js
@@ -56,9 +56,10 @@ ImportVariablesWidget.prototype.execute = function(tiddlerList) {
attributes: parseTreeNode.attributes,
params: parseTreeNode.params,
isMacroDefinition: parseTreeNode.isMacroDefinition,
- isFunctionDefinition: parseTreeNode.isFunctionDefinition
+ isFunctionDefinition: parseTreeNode.isFunctionDefinition,
+ isProcedureDefinition: parseTreeNode.isProcedureDefinition
};
- if (parseTreeNode.isMacroDefinition || parseTreeNode.isFunctionDefinition) {
+ if (parseTreeNode.isMacroDefinition || parseTreeNode.isProcedureDefinition) {
// Macro definitions can be folded into
// current widget instead of adding
// another link to the chain.
diff --git a/core/modules/widgets/setvariable.js b/core/modules/widgets/setvariable.js
index e176cd21d..41a5d95c8 100755
--- a/core/modules/widgets/setvariable.js
+++ b/core/modules/widgets/setvariable.js
@@ -49,9 +49,11 @@ SetWidget.prototype.execute = function() {
this.setEmptyValue = this.getAttribute("emptyValue");
// Set context variable
if(this.parseTreeNode.isMacroDefinition) {
- this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,!!this.parseTreeNode.isMacroDefinition);
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,true);
} else if(this.parseTreeNode.isFunctionDefinition) {
- this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isFunctionDefinition: this.parseTreeNode.isFunctionDefinition});
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isFunctionDefinition: true});
+ } else if(this.parseTreeNode.isProcedureDefinition) {
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isProcedureDefinition: true});
} else {
this.setVariable(this.setName,this.getValue());
}
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index afe247ad7..dcb31ad01 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -197,8 +197,8 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
}
}
if(parser) {
- // Add parameters widget for functions
- if(srcVariable.isFunctionDefinition) {
+ // Add parameters widget for procedures
+ if(srcVariable.isProcedureDefinition) {
parser = {
tree: [
{
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index 74162e52b..fc90a7dc2 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -89,6 +89,7 @@ value: value of the variable
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:
+ isProcedureDefinition: true if the variable is set via a \procedure pragma (and hence should not have variable substitution performed)
isFunctionDefinition: true if the variable is set via a \function pragma (and hence should not have variable substitution performed)
*/
Widget.prototype.setVariable = function(name,value,params,isMacroDefinition,options) {
@@ -97,7 +98,8 @@ Widget.prototype.setVariable = function(name,value,params,isMacroDefinition,opti
value: value,
params: params,
isMacroDefinition: !!isMacroDefinition,
- isFunctionDefinition: !!options.isFunctionDefinition
+ isFunctionDefinition: !!options.isFunctionDefinition,
+ isProcedureDefinition: !!options.isProcedureDefinition
};
};
diff --git a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
index 8ef331924..136b16fb8 100644
--- a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
+++ b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
@@ -6,7 +6,7 @@ tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
-\function <$let>
+\procedure <$let>
\whitespace trim
<$setmultiplevariables $names="[enlist:raw]" $values="[enlist:rawaddprefix[--]addsuffix[--]]">
<$slot $name="ts-body"/>
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid
index 19e617d68..62793dbda 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid
@@ -13,7 +13,7 @@ title: Actions
\whitespace trim
-\function <$action-mywidget>(one:'Jaguar')
+\procedure <$action-mywidget>(one:'Jaguar')
\whitespace trim
<$action-setfield $tiddler="Result" $field="text" $value=<>/>
\end
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid
index 4da0f2033..0741e378b 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid
@@ -16,7 +16,7 @@ title: Output
title: Definition
\whitespace trim
-\function <$codeblock>(code)
+\procedure <$codeblock>(code)
<$genesis $type="codeblock" $remappable="no" code={{{ [addprefix[£]addsuffix[@]] }}}/>
\end
+
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
index 08290b2bb..15a289da8 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
@@ -17,7 +17,7 @@ title: TiddlerOne
\whitespace trim
-\function <$transclude>(one:'Jaguar')
+\procedure <$transclude>(one:'Jaguar')
\whitespace trim
<$text text=<>/>
<$slot $name="body">
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
index 74ecb575d..3dadabd03 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
@@ -13,7 +13,7 @@ title: TiddlerOne
\whitespace trim
-\function <$mywidget>(one:'Jaguar')
+\procedure <$mywidget>(one:'Jaguar')
\whitespace trim
<$text text=<>/>
<$slot $name="ts-body">
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
index 62e52c7a8..53ba3d7e8 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
@@ -13,7 +13,7 @@ title: TiddlerOne
\whitespace trim
-\function <$text>(text:'Jaguar')
+\procedure <$text>(text:'Jaguar')
\whitespace trim
<$genesis $type="text" $remappable="no" text=<>/>
<$set name="<$text>" value="">
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
index 70e1b1d09..0b5ba9e81 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
@@ -13,7 +13,7 @@ title: TiddlerOne
\whitespace trim
-\function <$mywidget>($variable:'Jaguar')
+\procedure <$mywidget>($variable:'Jaguar')
\whitespace trim
<$text text=<<$variable>>/>
<$slot $name="ts-body">
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid
index 5612793cc..0499cf2d6 100644
--- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Shortcut.tid
@@ -6,7 +6,7 @@ tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
-\function test(one:'Jaguar')
+\procedure test(one:'Jaguar')
{<$text text=<>/>}
\end
diff --git a/editions/test/tiddlers/tests/test-wikitext-parser.js b/editions/test/tiddlers/tests/test-wikitext-parser.js
index 25d5c3437..95d68a258 100644
--- a/editions/test/tiddlers/tests/test-wikitext-parser.js
+++ b/editions/test/tiddlers/tests/test-wikitext-parser.js
@@ -119,7 +119,38 @@ describe("WikiText parser tests", function() {
);
});
- it("should parse function definitions with no parameters", function() {
+ it("should parse procedure definitions with no parameters", function() {
+ expect(parse("\\procedure myMacro\nnothing\n\\end\n")).toEqual(
+
+ [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], params : [ ], isProcedureDefinition : true } ]
+
+ );
+ });
+
+ it("should parse single line procedure definitions with no parameters", function() {
+ expect(parse("\\procedure myMacro nothing\n")).toEqual(
+
+ [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], params : [ ], isProcedureDefinition : true } ]
+
+ );
+ });
+
+ it("should parse procedure definitions with parameters", function() {
+ expect(parse("\\procedure myMacro(one,two,three,four:elephant)\nnothing\n\\end\n")).toEqual(
+
+ [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], params : [ { name: 'one' }, { name: 'two' }, { name: 'three' }, { name: 'four', default: 'elephant' } ], isProcedureDefinition : true } ]
+
+ );
+ });
+
+ it("should parse procedure definitions", function() {
+ expect(parse("\\procedure myMacro(one:'Jaguar')\n<$text text=<>/>\n\\end\n\n")).toEqual(
+
+ [ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : '<$text text=<>/>' } }, children : [ ], params : [ { name: 'one', "default": 'Jaguar' } ], isProcedureDefinition : true } ]
+
+ );
+
+ }); it("should parse function definitions with no parameters", function() {
expect(parse("\\function myMacro\nnothing\n\\end\n")).toEqual(
[ { type : 'set', attributes : { name : { type : 'string', value : 'myMacro' }, value : { type : 'string', value : 'nothing' } }, children : [ ], params : [ ], isFunctionDefinition : true } ]
From 8e4c13382b089787b7a74e0ed51db6a53772d1ae Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Tue, 10 May 2022 08:10:32 +0100
Subject: [PATCH 052/405] Fix visible transclusion example
---
core/ui/Components/VisibleTransclude.tid | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/core/ui/Components/VisibleTransclude.tid b/core/ui/Components/VisibleTransclude.tid
index 93b17f202..d53f83b97 100644
--- a/core/ui/Components/VisibleTransclude.tid
+++ b/core/ui/Components/VisibleTransclude.tid
@@ -5,7 +5,7 @@ Import this component to make all the child transclusions visible.
Block transclusions are shown in red, and inline transclusions are shown in green.
-->
-\function <$transclude>(tiddler,$tiddler,mode,$mode)
+\procedure <$transclude>(tiddler,$tiddler,mode,$mode)
<$let
mode={{{ [[$mode]is[variable]then<$mode>!is[blank]] :else[[mode]is[variable]then!is[blank]] :else[match[yes]then[inline]else[block]] }}}
From eef7d180a546b315715240beffaedc1e87d59c9e Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Tue, 10 May 2022 10:21:19 +0100
Subject: [PATCH 053/405] Remove obsolete code
Left over after refactoring
---
core/modules/widgets/genesis.js | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/core/modules/widgets/genesis.js b/core/modules/widgets/genesis.js
index cda389321..92993d23e 100644
--- a/core/modules/widgets/genesis.js
+++ b/core/modules/widgets/genesis.js
@@ -70,18 +70,6 @@ GenesisWidget.prototype.execute = function() {
}
$tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],$tw.utils.extend({},attribute,{name: name}));
});
- $tw.utils.each(this.attributes,function(value,name) {
- if(name.charAt(0) === "$") {
- if(name.charAt(1) === "$") {
- // Double $$ is changed to a single $
- name = name.substr(1);
- } else {
- // Single dollar is ignored
- return;
- }
- }
- $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],name,value);
- });
// Apply attributes in $names/$values
this.attributeNames = [];
this.attributeValues = [];
From 0b11b499c27da317ee803543e768847612ad1e27 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Tue, 10 May 2022 10:21:56 +0100
Subject: [PATCH 054/405] Better backwards compatibility for legacy recursion
marker
Fixes the problem with tag dropdowns @btheado
---
core/modules/widgets/transclude.js | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index dcb31ad01..384f77ef4 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -63,15 +63,18 @@ TranscludeWidget.prototype.execute = function() {
break;
}
// Set context variables for recursion detection
- var recursionMarker = this.makeLegacyRecursionMarker(),
- newRecursionMarker = this.makeRecursionMarker();
+ var recursionMarker = this.makeRecursionMarker();
if(this.recursionMarker === "yes") {
- this.setVariable("transclusion",recursionMarker);
- this.setVariable("$transclusion",newRecursionMarker);
+ this.setVariable("$transclusion",recursionMarker);
+ }
+ // Set the legacy transclusion context variables only if we're not transcluding a variable
+ if(!this.transcludeVariable) {
+ var legacyRecursionMarker = this.makeLegacyRecursionMarker();
+ this.setVariable("transclusion",legacyRecursionMarker);
}
// Check for recursion
if(target.parser) {
- if(this.parentWidget && this.parentWidget.hasVariable("$transclusion",newRecursionMarker)) {
+ if(this.parentWidget && this.parentWidget.hasVariable("$transclusion",recursionMarker)) {
parseTreeNodes = [{type: "element", tag: "span", attributes: {
"class": {type: "string", value: "tc-error"}
}, children: [
From a8272903325dd411dc99e90c04ed139f046a1367 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Tue, 10 May 2022 10:22:35 +0100
Subject: [PATCH 055/405] Fix stringifying/parsing string arrays containing
newlines
A very old bug.
Fixes the ActionListOpsWidget problem @btheado
---
boot/boot.js | 4 ++--
editions/test/tiddlers/tests/test-utils.js | 2 ++
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/boot/boot.js b/boot/boot.js
index 4bdedf4ef..d7f00e8c9 100644
--- a/boot/boot.js
+++ b/boot/boot.js
@@ -375,7 +375,7 @@ $tw.utils.stringifyList = function(value) {
var result = new Array(value.length);
for(var t=0, l=value.length; t
Date: Wed, 11 May 2022 13:51:11 +0100
Subject: [PATCH 056/405] Transclude: replace paramNames/paramValues with more
robust JSON payload
More details at https://github.com/Jermolene/TiddlyWiki5/pull/6666#issuecomment-1123719153
---
core/modules/filters/json-ops.js | 181 ++++++++++++++++++
core/modules/widgets/transclude.js | 29 +--
core/ui/Components/VisibleTransclude.tid | 10 +-
.../tests/data/genesis-widget/RedefineLet.tid | 2 +-
.../transclude/Parameterised-Name-Values.tid | 6 +-
.../test/tiddlers/tests/test-json-filters.js | 98 ++++++++++
6 files changed, 293 insertions(+), 33 deletions(-)
create mode 100644 core/modules/filters/json-ops.js
create mode 100644 editions/test/tiddlers/tests/test-json-filters.js
diff --git a/core/modules/filters/json-ops.js b/core/modules/filters/json-ops.js
new file mode 100644
index 000000000..a44c95c7a
--- /dev/null
+++ b/core/modules/filters/json-ops.js
@@ -0,0 +1,181 @@
+/*\
+title: $:/core/modules/filters/json-ops.js
+type: application/javascript
+module-type: filteroperator
+
+Filter operators for JSON operations
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+exports["jsonget"] = function(source,operator,options) {
+ var results = [];
+ source(function(tiddler,title) {
+ var data = $tw.utils.parseJSONSafe(title,{});
+ if(data) {
+ var item = getDataItemValueAsStrings(data,operator.operands);
+ if(item !== undefined) {
+ results.push.apply(results,item);
+ }
+ }
+ });
+ return results;
+};
+
+exports["jsonextract"] = function(source,operator,options) {
+ var results = [];
+ source(function(tiddler,title) {
+ var data = $tw.utils.parseJSONSafe(title,{});
+ if(data) {
+ var item = getDataItem(data,operator.operands);
+ if(item !== undefined) {
+ results.push(JSON.stringify(item));
+ }
+ }
+ });
+ return results;
+};
+
+exports["jsonindexes"] = function(source,operator,options) {
+ var results = [];
+ source(function(tiddler,title) {
+ var data = $tw.utils.parseJSONSafe(title,{});
+ if(data) {
+ var item = getDataItemKeysAsStrings(data,operator.operands);
+ if(item !== undefined) {
+ results.push.apply(results,item);
+ }
+ }
+ });
+ return results;
+};
+
+exports["jsontype"] = function(source,operator,options) {
+ var results = [];
+ source(function(tiddler,title) {
+ var data = $tw.utils.parseJSONSafe(title,{});
+ if(data) {
+ var item = getDataItemType(data,operator.operands);
+ if(item !== undefined) {
+ results.push(item);
+ }
+ }
+ });
+ return results;
+};
+
+/*
+Given a JSON data structure and an array of index strings, return an array of the string representation of the values at the end of the index chain, or "undefined" if any of the index strings are invalid
+*/
+function getDataItemValueAsStrings(data,indexes) {
+ // Get the item
+ var item = getDataItem(data,indexes);
+ // Return the item as a string
+ return convertDataItemValueToStrings(item);
+}
+
+/*
+Given a JSON data structure and an array of index strings, return an array of the string representation of the keys of the item at the end of the index chain, or "undefined" if any of the index strings are invalid
+*/
+function getDataItemKeysAsStrings(data,indexes) {
+ // Get the item
+ var item = getDataItem(data,indexes);
+ // Return the item keys as a string
+ return convertDataItemKeysToStrings(item);
+}
+
+/*
+Return an array of the string representation of the values of a data item, or "undefined" if the item is undefined
+*/
+function convertDataItemValueToStrings(item) {
+ // Return the item as a string
+ if(item === undefined) {
+ return item;
+ }
+ if(typeof item === "object") {
+ if(item === null) {
+ return ["null"];
+ }
+ var results = [];
+ if($tw.utils.isArray(item)) {
+ $tw.utils.each(item,function(value) {
+ results.push.apply(results,convertDataItemValueToStrings(value));
+ });
+ return results;
+ } else {
+ $tw.utils.each(Object.keys(item).sort(),function(key) {
+ results.push.apply(results,convertDataItemValueToStrings(item[key]));
+ });
+ return results;
+ }
+ }
+ return [item.toString()];
+}
+
+/*
+Return an array of the string representation of the keys of a data item, or "undefined" if the item is undefined
+*/
+function convertDataItemKeysToStrings(item) {
+ // Return the item as a string
+ if(item === undefined) {
+ return item;
+ } else if(typeof item === "object") {
+ if(item === null) {
+ return [];
+ }
+ var results = [];
+ if($tw.utils.isArray(item)) {
+ for(var i=0; i> style="display: inline-block;">
- <$list filter="[enlist:raw]" counter="counter" emptyMessage="(none)">
+ <$list filter="[<@params>jsonindexes[]]" emptyMessage="(none)">
- <$text text=<>/><$text text=": "/><$text text={{{ [enlist:rawnth] }}}/>
+ <$text text=<>/><$text text=": "/><$text text={{{ [<@params>jsonget] }}}/>
$list>
$genesis>
<$genesis $type="element" $tag=<> style="background:white;color:black;padding:4px;">
- <$list filter="[enlist:raw] :filter[prefix[$]] +[limit[1]]" variable="ignore" emptyMessage="""
+ <$list filter="[<@params>jsonindexes[]] :filter[prefix[$]] +[limit[1]]" variable="ignore" emptyMessage="""
- <$genesis $type="transclude" $remappable="no" $names="[enlist:raw]" $values="[enlist:raw]" recursionMarker="no" mode=<>>
+ <$genesis $type="transclude" $remappable="no" $names="[<@params>jsonindexes[]]" $values="[<@params>jsonindexes[]] :map[<@params>jsonget]" recursionMarker="no" mode=<>>
<$slot $name="ts-raw" $depth="2"/>
$genesis>
""">
- <$genesis $type="transclude" $remappable="no" $names="[enlist:raw]" $values="[enlist:raw]" $$recursionMarker="no" $$mode=<>>
+ <$genesis $type="transclude" $remappable="no" $names="[<@params>jsonindexes[]]" $values="[<@params>jsonindexes[]] :map[<@params>jsonget]" $$recursionMarker="no" $$mode=<>>
<$slot $name="ts-raw" $depth="2"/>
$genesis>
diff --git a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
index 136b16fb8..5f2a70f58 100644
--- a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
+++ b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
@@ -8,7 +8,7 @@ title: Output
\whitespace trim
\procedure <$let>
\whitespace trim
-<$setmultiplevariables $names="[enlist:raw]" $values="[enlist:rawaddprefix[--]addsuffix[--]]">
+<$setmultiplevariables $names="[<@params>jsonindexes[]]" $values="[<@params>jsonindexes[]] :map[<@params>jsongetaddprefix[--]addsuffix[--]]">
<$slot $name="ts-body"/>
$setmultiplevariables>
\end
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid
index d47ea440b..a80abc00c 100644
--- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid
@@ -18,8 +18,8 @@ title: TiddlerOne
\whitespace trim
\parameters(zero:'Jaguar',one:'Lizard',two:'Mole')
-<$list filter="[enlist]" counter="counter">
-{<$text text={{{ [enlist:rawnth] }}}/>:<$text text={{{ [enlist:rawnth] }}}/>}
+<$list filter="[<@params>jsonindexes[]]">
+{<$text text=<>/>: <$text text={{{ [<@params>jsonget] }}}/>}
$list>
+
title: TiddlerTwo
@@ -30,4 +30,4 @@ title: TiddlerTwo
+
title: ExpectedResult
-{0:}{1:}{2:}
{0:Ferret}
{0:Butterfly}{1:Moth}
{0:Beetle}{1:Scorpion}{2:Snake}
({zero:Beetle}{one:Scorpion}{two:Snake})
\ No newline at end of file
+{0:}{1:}{2:}
{0:Ferret}
{0:Butterfly}{1:Moth}
{0:Beetle}{1:Scorpion}{2:Snake}
({one:Scorpion}{two:Snake}{zero:Beetle})
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/test-json-filters.js b/editions/test/tiddlers/tests/test-json-filters.js
new file mode 100644
index 000000000..c5be2333f
--- /dev/null
+++ b/editions/test/tiddlers/tests/test-json-filters.js
@@ -0,0 +1,98 @@
+/*\
+title: test-json-filters.js
+type: application/javascript
+tags: [[$:/tags/test-spec]]
+
+Tests the JSON filters.
+
+\*/
+(function(){
+
+/* jslint node: true, browser: true */
+/* eslint-env node, browser, jasmine */
+/* eslint no-mixed-spaces-and-tabs: ["error", "smart-tabs"]*/
+/* global $tw, require */
+"use strict";
+
+describe("json filter tests", function() {
+
+ var wiki = new $tw.Wiki();
+ var tiddlers = [{
+ title: "First",
+ text: '{"a":"one","b":"","c":1.618,"d": {"e": "four","f": ["five","six",true,false,null]}}',
+ type: "application/json"
+ },{
+ title: "Second",
+ text: '["une","deux","trois"]',
+ type: "application/json"
+ }];
+ wiki.addTiddlers(tiddlers);
+
+ it("should support the getindex operator", function() {
+ expect(wiki.filterTiddlers("[{First}getindex[b]]")).toEqual([]);
+ });
+
+ it("should support the jsonget operator", function() {
+ expect(wiki.filterTiddlers("[{First}jsonget[]]")).toEqual(["one","","1.618","four","five","six","true","false","null"]);
+ expect(wiki.filterTiddlers("[{First}jsonget[a]]")).toEqual(["one"]);
+ expect(wiki.filterTiddlers("[{First}jsonget[b]]")).toEqual([""]);
+ expect(wiki.filterTiddlers("[{First}jsonget[missing-property]]")).toEqual([]);
+ expect(wiki.filterTiddlers("[{First}jsonget[d]]")).toEqual(["four","five","six","true","false","null"]);
+ expect(wiki.filterTiddlers("[{First}jsonget[d],[e]]")).toEqual(["four"]);
+ expect(wiki.filterTiddlers("[{First}jsonget[d],[f]]")).toEqual(["five","six","true","false","null"]);
+ expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[0]]")).toEqual(["five"]);
+ expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[1]]")).toEqual(["six"]);
+ expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[2]]")).toEqual(["true"]);
+ expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[3]]")).toEqual(["false"]);
+ expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[4]]")).toEqual(["null"]);
+ });
+
+ it("should support the jsonextract operator", function() {
+ expect(wiki.filterTiddlers("[{First}jsonextract[]]")).toEqual([`{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}`]);
+ expect(wiki.filterTiddlers("[{First}jsonextract[a]]")).toEqual([`"one"`]);
+ expect(wiki.filterTiddlers("[{First}jsonextract[b]]")).toEqual([`""`]);
+ expect(wiki.filterTiddlers("[{First}jsonextract[d]]")).toEqual([`{"e":"four","f":["five","six",true,false,null]}`]);
+ expect(wiki.filterTiddlers("[{First}jsonextract[missing-property]]")).toEqual([]);
+ expect(wiki.filterTiddlers("[{First}jsonextract[d],[e]]")).toEqual([`"four"`]);
+ expect(wiki.filterTiddlers("[{First}jsonextract[d],[f]]")).toEqual([`["five","six",true,false,null]`]);
+ expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[0]]")).toEqual([`"five"`]);
+ expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[1]]")).toEqual([`"six"`]);
+ expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[2]]")).toEqual([`true`]);
+ expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[3]]")).toEqual([`false`]);
+ expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[4]]")).toEqual([`null`]);
+ });
+
+ it("should support the jsonindexes operator", function() {
+ expect(wiki.filterTiddlers("[{Second}jsonindexes[]]")).toEqual(["0","1","2"]);
+ expect(wiki.filterTiddlers("[{First}jsonindexes[]]")).toEqual(["a","b","c","d"]);
+ expect(wiki.filterTiddlers("[{First}jsonindexes[a]]")).toEqual([]);
+ expect(wiki.filterTiddlers("[{First}jsonindexes[b]]")).toEqual([]);
+ expect(wiki.filterTiddlers("[{First}jsonindexes[d]]")).toEqual(["e","f"]);
+ expect(wiki.filterTiddlers("[{First}jsonindexes[d],[e]]")).toEqual([]);
+ expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f]]")).toEqual(["0","1","2","3","4"]);
+ expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[0]]")).toEqual([]);
+ expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[1]]")).toEqual([]);
+ expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[2]]")).toEqual([]);
+ expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[3]]")).toEqual([]);
+ expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[4]]")).toEqual([]);
+ });
+
+ it("should support the jsontype operator", function() {
+ expect(wiki.filterTiddlers("[{First}jsontype[]]")).toEqual(["object"]);
+ expect(wiki.filterTiddlers("[{First}jsontype[a]]")).toEqual(["string"]);
+ expect(wiki.filterTiddlers("[{First}jsontype[b]]")).toEqual(["string"]);
+ expect(wiki.filterTiddlers("[{First}jsontype[c]]")).toEqual(["number"]);
+ expect(wiki.filterTiddlers("[{First}jsontype[d]]")).toEqual(["object"]);
+ expect(wiki.filterTiddlers("[{First}jsontype[d],[e]]")).toEqual(["string"]);
+ expect(wiki.filterTiddlers("[{First}jsontype[d],[f]]")).toEqual(["array"]);
+ expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[0]]")).toEqual(["string"]);
+ expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[1]]")).toEqual(["string"]);
+ expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[2]]")).toEqual(["boolean"]);
+ expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[3]]")).toEqual(["boolean"]);
+ expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[4]]")).toEqual(["null"]);
+ });
+
+});
+
+})();
+
\ No newline at end of file
From 413dc86d05cc2d5f84b519dff43b3169db7640c8 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Wed, 11 May 2022 14:52:25 +0100
Subject: [PATCH 057/405] Rename internal "unknown" filter operator so that
users cannot invoke it
---
core/modules/filters.js | 4 ++--
core/modules/filters/unknown.js | 6 ++++--
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/core/modules/filters.js b/core/modules/filters.js
index 5f4eb6492..808628acb 100644
--- a/core/modules/filters.js
+++ b/core/modules/filters.js
@@ -247,8 +247,8 @@ exports.compileFilter = function(filterString) {
// Use the "title" operator if no operator is specified
operatorFunction = filterOperators.title;
} else if(!filterOperators[operator.operator]) {
- // Unknown operators treated as "unknown" - at run time we can distinguish between a custom operator and falling back to the default "field" operator
- operatorFunction = filterOperators.unknown;
+ // Unknown operators treated as "[unknown]" - at run time we can distinguish between a custom operator and falling back to the default "field" operator
+ operatorFunction = filterOperators["[unknown]"];
} else {
// Use the operator function
operatorFunction = filterOperators[operator.operator];
diff --git a/core/modules/filters/unknown.js b/core/modules/filters/unknown.js
index c2e2912a4..2eca09a7c 100644
--- a/core/modules/filters/unknown.js
+++ b/core/modules/filters/unknown.js
@@ -3,7 +3,9 @@ title: $:/core/modules/filters/unknown.js
type: application/javascript
module-type: filteroperator
-Filter operator for handling unknown filter operators
+Filter operator for handling unknown filter operators.
+
+Not intended to be used directly by end users, hence the square brackets around the name.
\*/
(function(){
@@ -17,7 +19,7 @@ var fieldFilterOperatorFn = require("$:/core/modules/filters/field.js").field;
/*
Export our filter function
*/
-exports.unknown = function(source,operator,options) {
+exports["[unknown]"] = function(source,operator,options) {
var customDefinitionTitle = "[" + operator.operator + "[]]",
customDefinition = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(customDefinitionTitle);
if(customDefinition && customDefinition.srcVariable && customDefinition.srcVariable.isFunctionDefinition) {
From 904e30a0e2add577426a46cac7f21e697fd17fe3 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Thu, 12 May 2022 16:26:33 +0100
Subject: [PATCH 058/405] Detect recursion by tracking widget tree depth
The old recursion marker approach was very slow, and didn't catch test cases like editions/test/tiddlers/tests/data/transclude/Recursion.tid
---
core/modules/widgets/error.js | 63 +++++++++++++++++++
core/modules/widgets/transclude.js | 40 +-----------
core/modules/widgets/widget.js | 56 ++++++++++++-----
.../tests/data/transclude/Recursion.tid | 17 +++++
editions/test/tiddlers/tests/test-widget.js | 4 +-
5 files changed, 126 insertions(+), 54 deletions(-)
create mode 100644 core/modules/widgets/error.js
create mode 100644 editions/test/tiddlers/tests/data/transclude/Recursion.tid
diff --git a/core/modules/widgets/error.js b/core/modules/widgets/error.js
new file mode 100644
index 000000000..6a4a607f1
--- /dev/null
+++ b/core/modules/widgets/error.js
@@ -0,0 +1,63 @@
+/*\
+title: $:/core/modules/widgets/error.js
+type: application/javascript
+module-type: widget
+
+Error widget
+
+\*/
+(function(){
+
+/*jslint node: true, browser: true */
+/*global $tw: false */
+"use strict";
+
+var Widget = require("$:/core/modules/widgets/widget.js").widget;
+
+var ErrorWidget = function(parseTreeNode,options) {
+ this.initialise(parseTreeNode,options);
+};
+
+/*
+Inherit from the base widget class
+*/
+ErrorWidget.prototype = new Widget();
+
+/*
+Render this widget into the DOM
+*/
+ErrorWidget.prototype.render = function(parent,nextSibling) {
+ this.parentDomNode = parent;
+ this.computeAttributes();
+ this.execute();
+ var message = this.getAttribute("$message","Unknown error"),
+ domNode = this.document.createElement("span");
+ domNode.appendChild(this.document.createTextNode(message));
+ domNode.className = "tc-error";
+ parent.insertBefore(domNode,nextSibling);
+ this.domNodes.push(domNode);
+};
+
+/*
+Compute the internal state of the widget
+*/
+ErrorWidget.prototype.execute = function() {
+ // Nothing to do for a text node
+};
+
+/*
+Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
+*/
+ErrorWidget.prototype.refresh = function(changedTiddlers) {
+ var changedAttributes = this.computeAttributes();
+ if(changedAttributes["$message"]) {
+ this.refreshSelf();
+ return true;
+ } else {
+ return false;
+ }
+};
+
+exports.error = ErrorWidget;
+
+})();
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index 27d380c54..940ce814c 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -62,25 +62,10 @@ TranscludeWidget.prototype.execute = function() {
parseTreeNodes = [{type: "text", text: plainText}];
break;
}
- // Set context variables for recursion detection
- var recursionMarker = this.makeRecursionMarker();
- if(this.recursionMarker === "yes") {
- this.setVariable("$transclusion",recursionMarker);
- }
// Set the legacy transclusion context variables only if we're not transcluding a variable
if(!this.transcludeVariable) {
- var legacyRecursionMarker = this.makeLegacyRecursionMarker();
- this.setVariable("transclusion",legacyRecursionMarker);
- }
- // Check for recursion
- if(target.parser) {
- if(this.parentWidget && this.parentWidget.hasVariable("$transclusion",recursionMarker)) {
- parseTreeNodes = [{type: "element", tag: "span", attributes: {
- "class": {type: "string", value: "tc-error"}
- }, children: [
- {type: "text", text: $tw.language.getString("Error/RecursiveTransclusion")}
- ]}];
- }
+ var recursionMarker = this.makeRecursionMarker();
+ this.setVariable("transclusion",recursionMarker);
}
// Construct the child widgets
this.makeChildWidgets(parseTreeNodes);
@@ -335,29 +320,10 @@ TranscludeWidget.prototype.getTransclusionSlotValue = function(name,defaultParse
}
};
-/*
-Compose a string comprising the attributes and variables to identify this transclusion for recursion detection
-*/
-TranscludeWidget.prototype.makeRecursionMarker = function() {
- var marker = {
- attributes: {},
- variables: {}
- }
- $tw.utils.each(this.attributes,function(value,name) {
- marker.attributes[name] = value;
- });
- for(var name in this.variables) {
- if(name !== "$transclusion") {
- marker.variables[name] = this.getVariable(name);
- }
- };
- return JSON.stringify(marker);
-};
-
/*
Compose a string comprising the title, field and/or index to identify this transclusion for recursion detection
*/
-TranscludeWidget.prototype.makeLegacyRecursionMarker = function() {
+TranscludeWidget.prototype.makeRecursionMarker = function() {
var output = [];
output.push("{");
output.push(this.getVariable("currentTiddler",{defaultValue: ""}));
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index fc90a7dc2..e0ee547c3 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -12,6 +12,9 @@ Widget base class
/*global $tw: false */
"use strict";
+/* Maximum permitted depth of the widget tree for recursion detection */
+var MAX_WIDGET_TREE_DEPTH = 1000;
+
/*
Create a widget object for a parse tree node
parseTreeNode: reference to the parse tree node to be rendered
@@ -375,6 +378,20 @@ Widget.prototype.assignAttributes = function(domNode,options) {
}
};
+/*
+Get the number of ancestor widgets for this widget
+*/
+Widget.prototype.getAncestorCount = function() {
+ if(this.ancestorCount === undefined) {
+ if(this.parentWidget) {
+ this.ancestorCount = this.parentWidget.getAncestorCount() + 1;
+ } else {
+ this.ancestorCount = 0;
+ }
+ }
+ return this.ancestorCount;
+};
+
/*
Make child widgets correspondng to specified parseTreeNodes
*/
@@ -382,21 +399,30 @@ Widget.prototype.makeChildWidgets = function(parseTreeNodes,options) {
options = options || {};
this.children = [];
var self = this;
- // Create set variable widgets for each variable
- $tw.utils.each(options.variables,function(value,name) {
- var setVariableWidget = {
- type: "set",
- attributes: {
- name: {type: "string", value: name},
- value: {type: "string", value: value}
- },
- children: parseTreeNodes
- };
- parseTreeNodes = [setVariableWidget];
- });
- $tw.utils.each(parseTreeNodes || (this.parseTreeNode && this.parseTreeNode.children),function(childNode) {
- self.children.push(self.makeChildWidget(childNode));
- });
+ // Check for too much recursion
+ if(this.getAncestorCount() > MAX_WIDGET_TREE_DEPTH) {
+ // Error message needs special permission not to cause a recursive error loop
+ this.children.push(this.makeChildWidget({type: "error", attributes: {
+ "$message": {type: "string", value: $tw.language.getString("Error/RecursiveTransclusion")}
+ }}));
+ } else {
+ // Create set variable widgets for each variable
+ $tw.utils.each(options.variables,function(value,name) {
+ var setVariableWidget = {
+ type: "set",
+ attributes: {
+ name: {type: "string", value: name},
+ value: {type: "string", value: value}
+ },
+ children: parseTreeNodes
+ };
+ parseTreeNodes = [setVariableWidget];
+ });
+ // Create the child widgets
+ $tw.utils.each(parseTreeNodes || (this.parseTreeNode && this.parseTreeNode.children),function(childNode) {
+ self.children.push(self.makeChildWidget(childNode));
+ });
+ }
};
/*
diff --git a/editions/test/tiddlers/tests/data/transclude/Recursion.tid b/editions/test/tiddlers/tests/data/transclude/Recursion.tid
new file mode 100644
index 000000000..23ec97b13
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Recursion.tid
@@ -0,0 +1,17 @@
+title: Transclude/Recursion
+description: Transclusion recursion detection
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\procedure recurse(a:0)
+<$transclude $variable="recurse" a={{{ [add[1]] }}}/>
+\end
+
+<>
++
+title: ExpectedResult
+
+Recursive transclusion error in transclude widget
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/test-widget.js b/editions/test/tiddlers/tests/test-widget.js
index 10c45317e..51f5dfbd1 100755
--- a/editions/test/tiddlers/tests/test-widget.js
+++ b/editions/test/tiddlers/tests/test-widget.js
@@ -143,7 +143,7 @@ describe("Widget module", function() {
var wiki = new $tw.Wiki();
// Add a tiddler
wiki.addTiddlers([
- {title: "TiddlerOne", text: "<$transclude tiddler='TiddlerTwo'/>\n"},
+ {title: "TiddlerOne", text: "<$transclude tiddler='TiddlerTwo'/>"},
{title: "TiddlerTwo", text: "<$transclude tiddler='TiddlerOne'/>"}
]);
// Test parse tree
@@ -157,7 +157,7 @@ describe("Widget module", function() {
// Render the widget node to the DOM
var wrapper = renderWidgetNode(widgetNode);
// Test the rendering
- expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget\n\n");
+ expect(wrapper.innerHTML).toBe("Recursive transclusion error in transclude widget");
});
it("should deal with SVG elements", function() {
From 36cf50aa9647e1b33331188d0d7ae57fe0731bfb Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Fri, 13 May 2022 08:49:53 +0100
Subject: [PATCH 059/405] Use \widget for custom widget definitions, and remove
need for angle brackets
Need to do some refactoring of all those isFunctionDefinition/isProcedureDefinition/isWidgetDefinition flags into a single property
---
core/modules/parsers/wikiparser/rules/fnprocdef.js | 10 ++++++++--
core/modules/widgets/importvariables.js | 3 ++-
core/modules/widgets/setvariable.js | 2 ++
core/modules/widgets/transclude.js | 4 ++--
core/modules/widgets/widget.js | 9 ++++++---
.../tiddlers/tests/data/genesis-widget/RedefineLet.tid | 2 +-
.../data/transclude/CustomWidget-ActionWidget.tid | 2 +-
.../transclude/CustomWidget-Override-Codeblock.tid | 2 +-
.../transclude/CustomWidget-OverrideTransclude.tid | 2 +-
.../tests/data/transclude/CustomWidget-Simple.tid | 2 +-
.../transclude/CustomWidget-TextWidgetOverride.tid | 4 ++--
.../data/transclude/CustomWidget-VariableAttribute.tid | 2 +-
12 files changed, 28 insertions(+), 16 deletions(-)
diff --git a/core/modules/parsers/wikiparser/rules/fnprocdef.js b/core/modules/parsers/wikiparser/rules/fnprocdef.js
index 037172c57..34ac6d72d 100644
--- a/core/modules/parsers/wikiparser/rules/fnprocdef.js
+++ b/core/modules/parsers/wikiparser/rules/fnprocdef.js
@@ -3,7 +3,7 @@ title: $:/core/modules/parsers/wikiparser/rules/fnprocdef.js
type: application/javascript
module-type: wikirule
-Wiki pragma rule for function and procedure definitions
+Wiki pragma rule for function, procedure and widget definitions
```
\function name(param:defaultvalue,param2:defaultvalue)
@@ -13,6 +13,10 @@ definition text
\procedure name(param:defaultvalue,param2:defaultvalue)
definition text
\end
+
+\widget $mywidget(param:defaultvalue,param2:defaultvalue)
+definition text
+\end
```
\*/
@@ -31,7 +35,7 @@ Instantiate parse rule
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /^\\(function|procedure)\s+([^(\s]+)(\(\s*([^)]*)\))?(\s*\r?\n)?/mg;
+ this.matchRegExp = /^\\(function|procedure|widget)\s+([^(\s]+)(\(\s*([^)]*)\))?(\s*\r?\n)?/mg;
};
/*
@@ -94,6 +98,8 @@ exports.parse = function() {
parseTreeNodes[0].isFunctionDefinition = true;
} else if(this.match[1] === "procedure") {
parseTreeNodes[0].isProcedureDefinition = true;
+ } else if(this.match[1] === "widget") {
+ parseTreeNodes[0].isWidgetDefinition = true;
}
return parseTreeNodes;
};
diff --git a/core/modules/widgets/importvariables.js b/core/modules/widgets/importvariables.js
index edea63311..7cbe387b0 100644
--- a/core/modules/widgets/importvariables.js
+++ b/core/modules/widgets/importvariables.js
@@ -57,7 +57,8 @@ ImportVariablesWidget.prototype.execute = function(tiddlerList) {
params: parseTreeNode.params,
isMacroDefinition: parseTreeNode.isMacroDefinition,
isFunctionDefinition: parseTreeNode.isFunctionDefinition,
- isProcedureDefinition: parseTreeNode.isProcedureDefinition
+ isProcedureDefinition: parseTreeNode.isProcedureDefinition,
+ isWidgetDefinition: parseTreeNode.isWidgetDefinition
};
if (parseTreeNode.isMacroDefinition || parseTreeNode.isProcedureDefinition) {
// Macro definitions can be folded into
diff --git a/core/modules/widgets/setvariable.js b/core/modules/widgets/setvariable.js
index 41a5d95c8..f9e7b5a85 100755
--- a/core/modules/widgets/setvariable.js
+++ b/core/modules/widgets/setvariable.js
@@ -54,6 +54,8 @@ SetWidget.prototype.execute = function() {
this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isFunctionDefinition: true});
} else if(this.parseTreeNode.isProcedureDefinition) {
this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isProcedureDefinition: true});
+ } else if(this.parseTreeNode.isWidgetDefinition) {
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isWidgetDefinition: true});
} else {
this.setVariable(this.setName,this.getValue());
}
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index 940ce814c..7fcc39fac 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -185,8 +185,8 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
}
}
if(parser) {
- // Add parameters widget for procedures
- if(srcVariable.isProcedureDefinition) {
+ // Add parameters widget for procedures and custom widgets
+ if(srcVariable.isProcedureDefinition || srcVariable.isWidgetDefinition) {
parser = {
tree: [
{
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index e0ee547c3..cf32e71c4 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -94,6 +94,7 @@ isMacroDefinition: true if the variable is set via a \define macro pragma (and h
options includes:
isProcedureDefinition: true if the variable is set via a \procedure pragma (and hence should not have variable substitution performed)
isFunctionDefinition: true if the variable is set via a \function pragma (and hence should not have variable substitution performed)
+ isWidgetDefinition: true if the variable is set via a \widget pragma (and hence should not have variable substitution performed)
*/
Widget.prototype.setVariable = function(name,value,params,isMacroDefinition,options) {
options = options || {};
@@ -102,7 +103,8 @@ Widget.prototype.setVariable = function(name,value,params,isMacroDefinition,opti
params: params,
isMacroDefinition: !!isMacroDefinition,
isFunctionDefinition: !!options.isFunctionDefinition,
- isProcedureDefinition: !!options.isProcedureDefinition
+ isProcedureDefinition: !!options.isProcedureDefinition,
+ isWidgetDefinition: !!options.isWidgetDefinition
};
};
@@ -434,8 +436,9 @@ Widget.prototype.makeChildWidget = function(parseTreeNode,options) {
var self = this;
options = options || {};
// Check whether this node type is defined by a custom macro definition
- var variableDefinitionName = "<$" + parseTreeNode.type + ">";
- if(!parseTreeNode.isNotRemappable && this.variables[variableDefinitionName] && this.variables[variableDefinitionName].value) {
+ var variableDefinitionName = "$" + parseTreeNode.type,
+ variableInfo = this.variables[variableDefinitionName];
+ if(!parseTreeNode.isNotRemappable && variableInfo && variableInfo.value && variableInfo.isWidgetDefinition) {
var newParseTreeNode = {
type: "transclude",
children: [
diff --git a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
index 5f2a70f58..395411dd1 100644
--- a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
+++ b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
@@ -6,7 +6,7 @@ tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
-\procedure <$let>
+\widget $let
\whitespace trim
<$setmultiplevariables $names="[<@params>jsonindexes[]]" $values="[<@params>jsonindexes[]] :map[<@params>jsongetaddprefix[--]addsuffix[--]]">
<$slot $name="ts-body"/>
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid
index 62793dbda..3cddb63cf 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid
@@ -13,7 +13,7 @@ title: Actions
\whitespace trim
-\procedure <$action-mywidget>(one:'Jaguar')
+\widget $action-mywidget(one:'Jaguar')
\whitespace trim
<$action-setfield $tiddler="Result" $field="text" $value=<>/>
\end
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid
index 0741e378b..842a92b70 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Override-Codeblock.tid
@@ -16,7 +16,7 @@ title: Output
title: Definition
\whitespace trim
-\procedure <$codeblock>(code)
+\widget $codeblock(code)
<$genesis $type="codeblock" $remappable="no" code={{{ [addprefix[£]addsuffix[@]] }}}/>
\end
+
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
index 15a289da8..7b4a8d8aa 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-OverrideTransclude.tid
@@ -17,7 +17,7 @@ title: TiddlerOne
\whitespace trim
-\procedure <$transclude>(one:'Jaguar')
+\widget $transclude(one:'Jaguar')
\whitespace trim
<$text text=<>/>
<$slot $name="body">
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
index 3dadabd03..86d181042 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
@@ -13,7 +13,7 @@ title: TiddlerOne
\whitespace trim
-\procedure <$mywidget>(one:'Jaguar')
+\widget $mywidget(one:'Jaguar')
\whitespace trim
<$text text=<>/>
<$slot $name="ts-body">
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
index 53ba3d7e8..0a65533f6 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-TextWidgetOverride.tid
@@ -13,10 +13,10 @@ title: TiddlerOne
\whitespace trim
-\procedure <$text>(text:'Jaguar')
+\widget $text(text:'Jaguar')
\whitespace trim
<$genesis $type="text" $remappable="no" text=<>/>
-<$set name="<$text>" value="">
+<$set name="$text" value="">
<$slot $name="ts-body">
Whale
$slot>
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
index 0b5ba9e81..a7e0e62e3 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
@@ -13,7 +13,7 @@ title: TiddlerOne
\whitespace trim
-\procedure <$mywidget>($variable:'Jaguar')
+\widget $mywidget($variable:'Jaguar')
\whitespace trim
<$text text=<<$variable>>/>
<$slot $name="ts-body">
From bbd9e2f24300f6a73668c783f94ad6de64cf28d1 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Fri, 13 May 2022 09:18:25 +0100
Subject: [PATCH 060/405] Rename <$value> widget to <$fill>
---
core/modules/widgets/{value.js => fill.js} | 12 ++++----
core/modules/widgets/slot.js | 2 +-
core/modules/widgets/transclude.js | 30 +++++++++----------
core/modules/widgets/widget.js | 2 +-
core/ui/Components/VisibleTransclude.tid | 4 +--
.../tests/data/transclude/MissingTarget.tid | 8 ++---
.../data/transclude/Parameterised-Slotted.tid | 4 +--
7 files changed, 31 insertions(+), 31 deletions(-)
rename core/modules/widgets/{value.js => fill.js} (77%)
diff --git a/core/modules/widgets/value.js b/core/modules/widgets/fill.js
similarity index 77%
rename from core/modules/widgets/value.js
rename to core/modules/widgets/fill.js
index 38fef1d29..c9a8a94de 100644
--- a/core/modules/widgets/value.js
+++ b/core/modules/widgets/fill.js
@@ -14,7 +14,7 @@ Sub-widget used by the transclude widget for specifying values for slots within
var Widget = require("$:/core/modules/widgets/widget.js").widget;
-var ValueWidget = function(parseTreeNode,options) {
+var FillWidget = function(parseTreeNode,options) {
// Initialise
this.initialise(parseTreeNode,options);
};
@@ -22,12 +22,12 @@ var ValueWidget = function(parseTreeNode,options) {
/*
Inherit from the base widget class
*/
-ValueWidget.prototype = Object.create(Widget.prototype);
+FillWidget.prototype = Object.create(Widget.prototype);
/*
Render this widget into the DOM
*/
-ValueWidget.prototype.render = function(parent,nextSibling) {
+FillWidget.prototype.render = function(parent,nextSibling) {
// Call the constructor
Widget.call(this);
this.parentDomNode = parent;
@@ -39,7 +39,7 @@ ValueWidget.prototype.render = function(parent,nextSibling) {
/*
Compute the internal state of the widget
*/
-ValueWidget.prototype.execute = function() {
+FillWidget.prototype.execute = function() {
// Construct the child widgets
this.makeChildWidgets();
};
@@ -47,11 +47,11 @@ ValueWidget.prototype.execute = function() {
/*
Refresh the widget by ensuring our attributes are up to date
*/
-ValueWidget.prototype.refresh = function(changedTiddlers) {
+FillWidget.prototype.refresh = function(changedTiddlers) {
return this.refreshChildren(changedTiddlers);
};
-exports.value = ValueWidget;
+exports.fill = FillWidget;
})();
\ No newline at end of file
diff --git a/core/modules/widgets/slot.js b/core/modules/widgets/slot.js
index 31df12a75..8ca6992d7 100644
--- a/core/modules/widgets/slot.js
+++ b/core/modules/widgets/slot.js
@@ -59,7 +59,7 @@ SlotWidget.prototype.execute = function() {
var parseTreeNodes = [{type: "text", attributes: {text: {type: "string", value: "Missing slot reference!"}}}];
if(pointer instanceof TranscludeWidget) {
// Get the parse tree nodes comprising the slot contents
- parseTreeNodes = pointer.getTransclusionSlotValue(this.slotName,this.parseTreeNode.children);
+ parseTreeNodes = pointer.getTransclusionSlotFill(this.slotName,this.parseTreeNode.children);
}
// Construct the child widgets
this.makeChildWidgets(parseTreeNodes);
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index 7fcc39fac..38970efb9 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -40,7 +40,7 @@ TranscludeWidget.prototype.execute = function() {
// Get our attributes, string parameters, and slot values into properties of the widget object
this.collectAttributes();
this.collectStringParameters();
- this.collectSlotValueParameters();
+ this.collectSlotFillParameters();
// Get the parse tree nodes that we are transcluding
var target = this.getTransclusionTarget(),
parseTreeNodes = target.parseTreeNodes;
@@ -129,30 +129,30 @@ TranscludeWidget.prototype.collectStringParameters = function() {
/*
Collect slot value parameters
*/
-TranscludeWidget.prototype.collectSlotValueParameters = function() {
+TranscludeWidget.prototype.collectSlotFillParameters = function() {
var self = this;
- this.slotValueParseTrees = Object.create(null);
+ this.slotFillParseTrees = Object.create(null);
if(this.legacyMode) {
- this.slotValueParseTrees["ts-missing"] = this.parseTreeNode.children;
+ this.slotFillParseTrees["ts-missing"] = this.parseTreeNode.children;
} else {
- this.slotValueParseTrees["ts-raw"] = this.parseTreeNode.children;
- var noValueWidgetsFound = true,
+ this.slotFillParseTrees["ts-raw"] = this.parseTreeNode.children;
+ var noFillWidgetsFound = true,
searchParseTreeNodes = function(nodes) {
$tw.utils.each(nodes,function(node) {
- if(node.type === "value") {
+ if(node.type === "fill") {
if(node.attributes["$name"] && node.attributes["$name"].type === "string") {
var slotValueName = node.attributes["$name"].value;
- self.slotValueParseTrees[slotValueName] = node.children;
+ self.slotFillParseTrees[slotValueName] = node.children;
}
- noValueWidgetsFound = false;
+ noFillWidgetsFound = false;
} else {
searchParseTreeNodes(node.children);
}
});
};
searchParseTreeNodes(this.parseTreeNode.children);
- if(noValueWidgetsFound) {
- this.slotValueParseTrees["ts-missing"] = this.parseTreeNode.children;
+ if(noFillWidgetsFound) {
+ this.slotFillParseTrees["ts-missing"] = this.parseTreeNode.children;
}
}
};
@@ -242,7 +242,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
// If there's no parse tree then return the missing slot value
return {
parser: null,
- parseTreeNodes: (this.slotValueParseTrees["ts-missing"] || []),
+ parseTreeNodes: (this.slotFillParseTrees["ts-missing"] || []),
parseAsInline: parseAsInline,
text: null,
type: null
@@ -312,9 +312,9 @@ TranscludeWidget.prototype.getTransclusionMetaVariables = function() {
/*
Fetch the value of a slot
*/
-TranscludeWidget.prototype.getTransclusionSlotValue = function(name,defaultParseTreeNodes) {
- if(name && this.slotValueParseTrees[name]) {
- return this.slotValueParseTrees[name];
+TranscludeWidget.prototype.getTransclusionSlotFill = function(name,defaultParseTreeNodes) {
+ if(name && this.slotFillParseTrees[name]) {
+ return this.slotFillParseTrees[name];
} else {
return defaultParseTreeNodes || [];
}
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index cf32e71c4..bc6c4b3fe 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -443,7 +443,7 @@ Widget.prototype.makeChildWidget = function(parseTreeNode,options) {
type: "transclude",
children: [
{
- type: "value",
+ type: "fill",
children: parseTreeNode.children
}
],
diff --git a/core/ui/Components/VisibleTransclude.tid b/core/ui/Components/VisibleTransclude.tid
index 8063b2a77..f654c0520 100644
--- a/core/ui/Components/VisibleTransclude.tid
+++ b/core/ui/Components/VisibleTransclude.tid
@@ -5,7 +5,7 @@ Import this component to make all the child transclusions visible.
Block transclusions are shown in red, and inline transclusions are shown in green.
-->
-\procedure <$transclude>(tiddler,$tiddler,mode,$mode)
+\widget $transclude(tiddler,$tiddler,mode,$mode)
<$let
mode={{{ [[$mode]is[variable]then<$mode>!is[blank]] :else[[mode]is[variable]then!is[blank]] :else[match[yes]then[inline]else[block]] }}}
@@ -35,7 +35,7 @@ Block transclusions are shown in red, and inline transclusions are shown in gree
""">
<$genesis $type="transclude" $remappable="no" $names="[<@params>jsonindexes[]]" $values="[<@params>jsonindexes[]] :map[<@params>jsonget]" $$recursionMarker="no" $$mode=<>>
-
+
<$slot $name="ts-raw" $depth="2"/>
$genesis>
$list>
diff --git a/editions/test/tiddlers/tests/data/transclude/MissingTarget.tid b/editions/test/tiddlers/tests/data/transclude/MissingTarget.tid
index 5fa2d12c7..8bdc86eaa 100644
--- a/editions/test/tiddlers/tests/data/transclude/MissingTarget.tid
+++ b/editions/test/tiddlers/tests/data/transclude/MissingTarget.tid
@@ -13,12 +13,12 @@ title: Output
$parameters>
$transclude>
<$transclude $tiddler='TiddlerOne' one='Ferret'>
- <$value $name="ts-missing">
+ <$fill $name="ts-missing">
<$parameters one='Ferret'>
Badger
<$text text=<>/>
$parameters>
- $value>
+ $fill>
$transclude>
<$transclude $tiddler='MissingTiddler' one='Ferret'>
<$parameters one='Ferret'>
@@ -27,12 +27,12 @@ title: Output
$parameters>
$transclude>
<$transclude $tiddler='MissingTiddler' one='Ferret'>
- <$value $name="ts-missing">
+ <$fill $name="ts-missing">
<$parameters one='Ferret'>
Badger
<$text text=<>/>
$parameters>
- $value>
+ $fill>
$transclude>
+
title: TiddlerOne
diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted.tid
index eeff9b33e..c795621ef 100644
--- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted.tid
+++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Slotted.tid
@@ -7,9 +7,9 @@ title: Output
\whitespace trim
<$transclude $tiddler='TiddlerOne' one='Ferret'>
- <$value $name="content">
+ <$fill $name="content">
Hippopotamus
- $value>
+ $fill>
$transclude>
+
title: TiddlerOne
From e50101322f4caebeded229e91df0e8f7b4fc2c88 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Sat, 21 May 2022 15:47:19 +0100
Subject: [PATCH 061/405] Require $$ for custom widgets, and that overridden JS
widgets must exist
See https://github.com/Jermolene/TiddlyWiki5/pull/6666#issuecomment-1133637763
---
core/modules/parsers/wikiparser/rules/html.js | 3 ---
core/modules/widgets/widget.js | 10 ++++---
.../transclude/CustomWidget-ActionWidget.tid | 8 +++---
.../data/transclude/CustomWidget-Fail.tid | 26 +++++++++++++++++++
.../data/transclude/CustomWidget-Simple.tid | 12 ++++-----
.../CustomWidget-VariableAttribute.tid | 8 +++---
6 files changed, 47 insertions(+), 20 deletions(-)
create mode 100644 editions/test/tiddlers/tests/data/transclude/CustomWidget-Fail.tid
diff --git a/core/modules/parsers/wikiparser/rules/html.js b/core/modules/parsers/wikiparser/rules/html.js
index 7fc4bb96e..64469e3b2 100644
--- a/core/modules/parsers/wikiparser/rules/html.js
+++ b/core/modules/parsers/wikiparser/rules/html.js
@@ -93,9 +93,6 @@ exports.parseTag = function(source,pos,options) {
return null;
}
node.tag = token.match[1];
- if(node.tag.slice(1).indexOf("$") !== -1) {
- return null;
- }
if(node.tag.charAt(0) === "$") {
node.type = node.tag.substr(1);
}
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index bc6c4b3fe..9fcda8e51 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -435,10 +435,14 @@ options include:
Widget.prototype.makeChildWidget = function(parseTreeNode,options) {
var self = this;
options = options || {};
- // Check whether this node type is defined by a custom macro definition
+ // Check whether this node type is defined by a custom widget definition
var variableDefinitionName = "$" + parseTreeNode.type,
- variableInfo = this.variables[variableDefinitionName];
- if(!parseTreeNode.isNotRemappable && variableInfo && variableInfo.value && variableInfo.isWidgetDefinition) {
+ variableInfo = this.variables[variableDefinitionName],
+ isOverrideable = function() {
+ // Widget is overrideable if it has a double dollar user defined name, or if it is an existing JS widget
+ return parseTreeNode.type.charAt(0) === "$" || !!self.widgetClasses[parseTreeNode.type];
+ };
+ if(!parseTreeNode.isNotRemappable && isOverrideable() && variableInfo && variableInfo.value && variableInfo.isWidgetDefinition) {
var newParseTreeNode = {
type: "transclude",
children: [
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid
index 3cddb63cf..0be77a9a3 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-ActionWidget.tid
@@ -12,15 +12,15 @@ title: Output
title: Actions
\whitespace trim
-
-\widget $action-mywidget(one:'Jaguar')
+
+\widget $$action-mywidget(one:'Jaguar')
\whitespace trim
<$action-setfield $tiddler="Result" $field="text" $value=<>/>
\end
-<$action-mywidget one="Dingo">
+<$$action-mywidget one="Dingo">
Crocodile
-$action-mywidget>
+$$action-mywidget>
+
title: ExpectedResult
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Fail.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Fail.tid
new file mode 100644
index 000000000..15be21b05
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Fail.tid
@@ -0,0 +1,26 @@
+title: Transclude/CustomWidget/Fail
+description: Custom widget failed definition
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+
+\widget $non-existent-widget(one:'Jaguar')
+\whitespace trim
+<$text text=<>/>
+<$slot $name="ts-body">
+ Whale
+$slot>
+\end
+<$non-existent-widget one="Dingo">
+ Crocodile
+$non-existent-widget>
+<$non-existent-widget one="BumbleBee">
+ Squirrel
+$non-existent-widget>
++
+title: ExpectedResult
+
+Undefined widget 'non-existent-widget'Undefined widget 'non-existent-widget'
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
index 86d181042..f98b2079a 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-Simple.tid
@@ -12,20 +12,20 @@ title: Output
title: TiddlerOne
\whitespace trim
-
-\widget $mywidget(one:'Jaguar')
+
+\widget $$mywidget(one:'Jaguar')
\whitespace trim
<$text text=<>/>
<$slot $name="ts-body">
Whale
$slot>
\end
-<$mywidget one="Dingo">
+<$$mywidget one="Dingo">
Crocodile
-$mywidget>
-<$mywidget one="BumbleBee">
+$$mywidget>
+<$$mywidget one="BumbleBee">
Squirrel
-$mywidget>
+$$mywidget>
+
title: ExpectedResult
diff --git a/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid b/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
index a7e0e62e3..63fd5f87a 100644
--- a/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
+++ b/editions/test/tiddlers/tests/data/transclude/CustomWidget-VariableAttribute.tid
@@ -12,17 +12,17 @@ title: Output
title: TiddlerOne
\whitespace trim
-
-\widget $mywidget($variable:'Jaguar')
+
+\widget $$mywidget($variable:'Jaguar')
\whitespace trim
<$text text=<<$variable>>/>
<$slot $name="ts-body">
Whale
$slot>
\end
-<$mywidget $variable="Dingo">
+<$$mywidget $variable="Dingo">
Crocodile
-$mywidget>
+$$mywidget>
+
title: ExpectedResult
From ec1ec8ccd8942adb33f6aa148219bc7759c0bca7 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Sat, 21 May 2022 16:31:34 +0100
Subject: [PATCH 062/405] Fix invocation of JS macros
---
core/modules/widgets/transclude.js | 4 ++--
core/modules/widgets/widget.js | 3 ++-
.../tests/data/transclude/JavaScript-Macro.tid | 17 +++++++++++++++++
3 files changed, 21 insertions(+), 3 deletions(-)
create mode 100644 editions/test/tiddlers/tests/data/transclude/JavaScript-Macro.tid
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index 38970efb9..c113c1861 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -200,8 +200,8 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
$tw.utils.each(srcVariable.params,function(param) {
$tw.utils.addAttributeToParseTreeNode(parser.tree[0],param.name,param["default"])
});
- } else if(srcVariable.isMacroDefinition) {
- // For macros, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__"
+ } else {
+ // For macros and ordinary variables, wrap the parse tree in a vars widget assigning the parameters to variables named "__paramname__"
parser = {
tree: [
{
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index 9fcda8e51..18b395ec0 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -148,7 +148,8 @@ Widget.prototype.getVariableInfo = function(name,options) {
}
// If the variable doesn't exist in the parent widget then look for a macro module
return {
- text: this.evaluateMacroModule(name,actualParams,options.defaultValue)
+ text: this.evaluateMacroModule(name,actualParams,options.defaultValue),
+ srcVariable: {}
};
};
diff --git a/editions/test/tiddlers/tests/data/transclude/JavaScript-Macro.tid b/editions/test/tiddlers/tests/data/transclude/JavaScript-Macro.tid
new file mode 100644
index 000000000..216a89dc8
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/JavaScript-Macro.tid
@@ -0,0 +1,17 @@
+title: Transclude/Macro/JavaScript
+description: Transcluding a javascript macro
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+
+<>
+
+<$macrocall $name="makedatauri" text="Wildebeest" type="text/plain"/>
+
++
+title: ExpectedResult
+
+
\ No newline at end of file
From d11893752c5c4efe6dc41aa43ad3fde9a7233280 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Mon, 23 May 2022 10:24:51 +0100
Subject: [PATCH 063/405] Experimental update of the parse-tree preview
visualisation
An experiment to try out using the new JSON operators for rendering the JSON parse tree that we get back from the wikify widget.
As usual with these experiments, this one is going to require quite a lot more work to finish up:
* The formatting is via direct styles rather than classes
* The formatting for attributes and properties is not yet completed
* The same thing needs to also be done to the widget tree preview
---
.../internals/editpreviews/parse-tree.tid | 121 +++++++++++++++++-
1 file changed, 114 insertions(+), 7 deletions(-)
diff --git a/plugins/tiddlywiki/internals/editpreviews/parse-tree.tid b/plugins/tiddlywiki/internals/editpreviews/parse-tree.tid
index aafa30ecf..97da36a40 100644
--- a/plugins/tiddlywiki/internals/editpreviews/parse-tree.tid
+++ b/plugins/tiddlywiki/internals/editpreviews/parse-tree.tid
@@ -3,13 +3,120 @@ tags: $:/tags/EditPreview
list-after: $:/core/ui/EditTemplate/body/preview/output
caption: parse tree
-\define preview(mode)
-<$wikify name="preview-text" text={{!!text}} type={{!!type}} mode="$mode$" output="parsetree">
-
-
-<$text text=<>/>
-
-
+\procedure preview-node-properties(node)
+<$let excludeProperties="text type tag children attributes orderedAttributes">
+<$list filter="[jsonindexes[]] -[subfilter] +[limit[1]]" variable="ignore">
+
+
+<$list filter="[jsonindexes[]] -[subfilter] +[sort[]]" variable="index">
+
+
+<$text text=<>/>
+
+
+<$text text={{{ [jsonget] }}}/>
+
+
+$list>
+
+
+$list>
+$let>
+\end
+
+\procedure preview-node-attribute-string(attribute)
+<$text text={{{ [jsonget[value]] }}}/>
+\end
+
+\procedure preview-node-attribute-indirect(attribute)
+{{<$text text={{{ [jsonget[textReference]] }}}/>}}
+\end
+
+\procedure preview-node-attribute-macro(attribute)
+\whitespace trim
+<<
+<$text text={{{ [jsonget[value],[name]] }}}/>
+<$list filter="[jsonindexes[value],[params]]" variable="index">
+
+<$list filter="[jsonget[value],[params],,[name]]" variable="ignore">
+<$text text={{{ [jsonget[value],[params],,[name]] }}}/>
+:
+$list>
+<$text text={{{ [jsonget[value],[params],,[value]] }}}/>
+$list>
+>>
+\end
+
+\procedure preview-node-attributes(node)
+<$list filter="[jsonindexes[attributes]limit[1]]" variable="ignore">
+
+
+<$list filter="[jsonindexes[attributes]sort[]]" variable="index">
+
+
+<$text text=<>/>
+
+
+<$let type={{{ [jsonget[attributes],,[type]] }}}>
+<$transclude $variable={{{ [match[string]then[preview-node-attribute-string]] :else[match[indirect]then[preview-node-attribute-indirect]] :else[match[macro]then[preview-node-attribute-macro]] }}} attribute={{{ [jsonextract[attributes],] }}}/>
+$let>
+
+
+$list>
+
+
+$list>
+\end
+
+\procedure preview-node-children(node)
+
+<$transclude $variable="preview-node-properties" node=<>/>
+<$transclude $variable="preview-node-attributes" node=<>/>
+<$transclude $variable="preview-node-list" nodeList={{{ [jsonextract[children]] }}}/>
+
+\end
+
+\procedure preview-node-title-widget(node)
+
+
+<$<$text text={{{ [jsonget[type]] }}}/>>
+
+<$transclude $variable="preview-node-children" node=<>/>
+
+\end
+
+\procedure preview-node-title-element(node)
+
+
+<<$text text={{{ [jsonget[tag]] }}}/>>
+
+<$transclude $variable="preview-node-children" node=<>/>
+
+\end
+
+\procedure preview-node-title-text(node)
+
+
+"<$text text={{{ [jsonget[text]] }}}/> "
+
+
+\end
+
+\procedure preview-node(node)
+<$let type={{{ [jsonget[type]] }}}>
+<$transclude $variable={{{ [match[element]then[preview-node-title-element]] :else[match[text]then[preview-node-title-text]] :else[[preview-node-title-widget]] }}} node=<>/>
+$let>
+\end
+
+\procedure preview-node-list(nodeList)
+<$list filter="[jsonindexes[]]" variable="index">
+<$transclude $variable="preview-node" node={{{ [jsonextract] }}}/>
+$list>
+\end
+
+\procedure preview(mode)
+<$wikify name="preview-json" text={{!!text}} type={{!!type}} mode=<> output="parsetree">
+<$transclude $variable="preview-node-list" nodeList=<>/>
$wikify>
\end
From 22e7ec23811b137a119295b5ce72bccdc18a697a Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Mon, 23 May 2022 15:30:33 +0100
Subject: [PATCH 064/405] Procedures and widgets inherit whitespace trim
setting from their definition
---
core/modules/parsers/wikiparser/rules/fnprocdef.js | 3 +++
core/modules/parsers/wikiparser/wikiparser.js | 3 ++-
core/modules/widgets/importvariables.js | 3 ++-
core/modules/widgets/setvariable.js | 4 ++--
core/modules/widgets/transclude.js | 2 +-
core/modules/widgets/widget.js | 3 ++-
core/modules/wiki.js | 3 ++-
7 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/core/modules/parsers/wikiparser/rules/fnprocdef.js b/core/modules/parsers/wikiparser/rules/fnprocdef.js
index 34ac6d72d..940567eff 100644
--- a/core/modules/parsers/wikiparser/rules/fnprocdef.js
+++ b/core/modules/parsers/wikiparser/rules/fnprocdef.js
@@ -101,6 +101,9 @@ exports.parse = function() {
} else if(this.match[1] === "widget") {
parseTreeNodes[0].isWidgetDefinition = true;
}
+ if(this.parser.configTrimWhiteSpace) {
+ parseTreeNodes[0].configTrimWhiteSpace = true;
+ }
return parseTreeNodes;
};
diff --git a/core/modules/parsers/wikiparser/wikiparser.js b/core/modules/parsers/wikiparser/wikiparser.js
index 90a3e7446..1572fe702 100644
--- a/core/modules/parsers/wikiparser/wikiparser.js
+++ b/core/modules/parsers/wikiparser/wikiparser.js
@@ -32,6 +32,7 @@ options: see below:
parseAsInline: true to parse text as inline instead of block
wiki: reference to wiki to use
_canonical_uri: optional URI of content if text is missing or empty
+ configTrimWhiteSpace: true to trim whitespace
*/
var WikiParser = function(type,text,options) {
this.wiki = options.wiki;
@@ -46,7 +47,7 @@ var WikiParser = function(type,text,options) {
this.source = text || "";
this.sourceLength = this.source.length;
// Flag for ignoring whitespace
- this.configTrimWhiteSpace = false;
+ this.configTrimWhiteSpace = options.configTrimWhiteSpace !== undefined ? options.configTrimWhiteSpace : false;
// Set current parse position
this.pos = 0;
// Start with empty output
diff --git a/core/modules/widgets/importvariables.js b/core/modules/widgets/importvariables.js
index 7cbe387b0..0c8ef5f29 100644
--- a/core/modules/widgets/importvariables.js
+++ b/core/modules/widgets/importvariables.js
@@ -58,7 +58,8 @@ ImportVariablesWidget.prototype.execute = function(tiddlerList) {
isMacroDefinition: parseTreeNode.isMacroDefinition,
isFunctionDefinition: parseTreeNode.isFunctionDefinition,
isProcedureDefinition: parseTreeNode.isProcedureDefinition,
- isWidgetDefinition: parseTreeNode.isWidgetDefinition
+ isWidgetDefinition: parseTreeNode.isWidgetDefinition,
+ configTrimWhiteSpace: parseTreeNode.configTrimWhiteSpace
};
if (parseTreeNode.isMacroDefinition || parseTreeNode.isProcedureDefinition) {
// Macro definitions can be folded into
diff --git a/core/modules/widgets/setvariable.js b/core/modules/widgets/setvariable.js
index f9e7b5a85..f8e98f390 100755
--- a/core/modules/widgets/setvariable.js
+++ b/core/modules/widgets/setvariable.js
@@ -53,9 +53,9 @@ SetWidget.prototype.execute = function() {
} else if(this.parseTreeNode.isFunctionDefinition) {
this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isFunctionDefinition: true});
} else if(this.parseTreeNode.isProcedureDefinition) {
- this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isProcedureDefinition: true});
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isProcedureDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace});
} else if(this.parseTreeNode.isWidgetDefinition) {
- this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isWidgetDefinition: true});
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isWidgetDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace});
} else {
this.setVariable(this.setName,this.getValue());
}
diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js
index c113c1861..c4c4c0321 100755
--- a/core/modules/widgets/transclude.js
+++ b/core/modules/widgets/transclude.js
@@ -179,7 +179,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
if(srcVariable.isCacheable && srcVariable[mode]) {
parser = srcVariable[mode];
} else {
- parser = this.wiki.parseText(this.transcludeType,variableInfo.text || "",{parseAsInline: parseAsInline});
+ parser = this.wiki.parseText(this.transcludeType,variableInfo.text || "",{parseAsInline: parseAsInline, configTrimWhiteSpace: srcVariable.configTrimWhiteSpace});
if(srcVariable.isCacheable) {
srcVariable[mode] = parser;
}
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index 18b395ec0..627c3490c 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -104,7 +104,8 @@ Widget.prototype.setVariable = function(name,value,params,isMacroDefinition,opti
isMacroDefinition: !!isMacroDefinition,
isFunctionDefinition: !!options.isFunctionDefinition,
isProcedureDefinition: !!options.isProcedureDefinition,
- isWidgetDefinition: !!options.isWidgetDefinition
+ isWidgetDefinition: !!options.isWidgetDefinition,
+ configTrimWhiteSpace: !!options.configTrimWhiteSpace
};
};
diff --git a/core/modules/wiki.js b/core/modules/wiki.js
index 87d78344e..49c81930e 100755
--- a/core/modules/wiki.js
+++ b/core/modules/wiki.js
@@ -988,7 +988,8 @@ exports.parseText = function(type,text,options) {
return new Parser(type,text,{
parseAsInline: options.parseAsInline,
wiki: this,
- _canonical_uri: options._canonical_uri
+ _canonical_uri: options._canonical_uri,
+ configTrimWhiteSpace: options.configTrimWhiteSpace
});
};
From 3e09eacd207cf1f986f34b98ccb018163cbeb26d Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Mon, 23 May 2022 16:32:19 +0100
Subject: [PATCH 065/405] Missed off 22e7ec23811b137a119295b5ce72bccdc18a697a
---
.../data/transclude/Procedures-Whitespace.tid | 25 +++++++++++++++++++
.../internals/editpreviews/parse-tree.tid | 3 ++-
2 files changed, 27 insertions(+), 1 deletion(-)
create mode 100644 editions/test/tiddlers/tests/data/transclude/Procedures-Whitespace.tid
diff --git a/editions/test/tiddlers/tests/data/transclude/Procedures-Whitespace.tid b/editions/test/tiddlers/tests/data/transclude/Procedures-Whitespace.tid
new file mode 100644
index 000000000..d2bded70c
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Procedures-Whitespace.tid
@@ -0,0 +1,25 @@
+title: Transclude/Procedures/Whitespace
+description: Procedures should inherit whitespace settings from definition site
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\procedure testproc()
+This is a sentence
+\end
+
+\define testmacro()
+This is a sentence
+\end
+This is a sentence
+[<>]
+[<>]
+
++
+title: ExpectedResult
+
+This is a sentence
+[This is a sentence]
+[This is a sentence ]
\ No newline at end of file
diff --git a/plugins/tiddlywiki/internals/editpreviews/parse-tree.tid b/plugins/tiddlywiki/internals/editpreviews/parse-tree.tid
index 97da36a40..995c26983 100644
--- a/plugins/tiddlywiki/internals/editpreviews/parse-tree.tid
+++ b/plugins/tiddlywiki/internals/editpreviews/parse-tree.tid
@@ -3,6 +3,8 @@ tags: $:/tags/EditPreview
list-after: $:/core/ui/EditTemplate/body/preview/output
caption: parse tree
+\whitespace trim
+
\procedure preview-node-properties(node)
<$let excludeProperties="text type tag children attributes orderedAttributes">
<$list filter="[jsonindexes[]] -[subfilter] +[limit[1]]" variable="ignore">
@@ -33,7 +35,6 @@ caption: parse tree
\end
\procedure preview-node-attribute-macro(attribute)
-\whitespace trim
<<
<$text text={{{ [jsonget[value],[name]] }}}/>
<$list filter="[jsonindexes[value],[params]]" variable="index">
From 9e8d05f69905cbd3b79c19395f37542def4103b3 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Mon, 23 May 2022 16:40:21 +0100
Subject: [PATCH 066/405] Require period prefix for custom filter operator
functions
To ensure that custom filter operators cannot clash with future core operators.
---
core/modules/filters/unknown.js | 76 +++++++++----------
.../custom-operators/NestedParameterised.tid | 10 +--
.../data/custom-operators/Parameterised.tid | 6 +-
.../tests/data/custom-operators/Simple.tid | 4 +-
4 files changed, 48 insertions(+), 48 deletions(-)
diff --git a/core/modules/filters/unknown.js b/core/modules/filters/unknown.js
index 2eca09a7c..81f868427 100644
--- a/core/modules/filters/unknown.js
+++ b/core/modules/filters/unknown.js
@@ -20,48 +20,48 @@ var fieldFilterOperatorFn = require("$:/core/modules/filters/field.js").field;
Export our filter function
*/
exports["[unknown]"] = function(source,operator,options) {
- var customDefinitionTitle = "[" + operator.operator + "[]]",
- customDefinition = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(customDefinitionTitle);
- if(customDefinition && customDefinition.srcVariable && customDefinition.srcVariable.isFunctionDefinition) {
- var variables = Object.create(null);
- $tw.utils.each(customDefinition.srcVariable.params,function(param,index) {
- var value = operator.operands[index];
- if(value === undefined) {
- value = param["default"] || "";
- }
- variables[param.name] = value;
- });
- var getVariable = function(name,opts) {
- if(name in variables) {
- return variables[name];
- } else {
- return options.widget.getVariable(name,opts);
- };
- };
- var getVariableInfo = function(name,opts) {
- if(name in variables) {
- return {
- text: variables[name]
- };
- } else {
- return options.widget.getVariableInfo(name,opts);
- };
- }
- var list = options.wiki.filterTiddlers(customDefinition.srcVariable.value,{getVariable: getVariable,getVariableInfo: getVariableInfo},source);
- if(operator.prefix === "!") {
- var results = [];
- source(function(tiddler,title) {
- if(list.indexOf(title) === -1) {
- results.push(title);
+ if(operator.operator.charAt(0) === ".") {
+ var customDefinition = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator);
+ if(customDefinition && customDefinition.srcVariable && customDefinition.srcVariable.isFunctionDefinition) {
+ var variables = Object.create(null);
+ $tw.utils.each(customDefinition.srcVariable.params,function(param,index) {
+ var value = operator.operands[index];
+ if(value === undefined) {
+ value = param["default"] || "";
}
+ variables[param.name] = value;
});
- return results;
- } else {
- return list;
+ var getVariable = function(name,opts) {
+ if(name in variables) {
+ return variables[name];
+ } else {
+ return options.widget.getVariable(name,opts);
+ };
+ };
+ var getVariableInfo = function(name,opts) {
+ if(name in variables) {
+ return {
+ text: variables[name]
+ };
+ } else {
+ return options.widget.getVariableInfo(name,opts);
+ };
+ }
+ var list = options.wiki.filterTiddlers(customDefinition.srcVariable.value,{getVariable: getVariable,getVariableInfo: getVariableInfo},source);
+ if(operator.prefix === "!") {
+ var results = [];
+ source(function(tiddler,title) {
+ if(list.indexOf(title) === -1) {
+ results.push(title);
+ }
+ });
+ return results;
+ } else {
+ return list;
+ }
}
- } else {
- return fieldFilterOperatorFn(source,operator,options);
}
+ return fieldFilterOperatorFn(source,operator,options);
};
})();
diff --git a/editions/test/tiddlers/tests/data/custom-operators/NestedParameterised.tid b/editions/test/tiddlers/tests/data/custom-operators/NestedParameterised.tid
index cbeb1570d..3e4d610d0 100644
--- a/editions/test/tiddlers/tests/data/custom-operators/NestedParameterised.tid
+++ b/editions/test/tiddlers/tests/data/custom-operators/NestedParameterised.tid
@@ -6,17 +6,17 @@ tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
-\function [dividebysomething[]](first:ignored,factor:0.5)
+\function .dividebysomething(first:ignored,factor:0.5)
[divide[2]multiply]
\end
-\function [multiplebysomething[]](first:ignored,factor:2)
-[multiply[2]dividebysomething[],]
+\function .multiplebysomething(first:ignored,factor:2)
+[multiply[2].dividebysomething[],]
\end
-<$text text={{{ [[123]multiplebysomething[]] }}}/>
+<$text text={{{ [[123].multiplebysomething[]] }}}/>
-
-<$text text={{{ [[123]multiplebysomething[x],[4]] }}}/>
+<$text text={{{ [[123].multiplebysomething[x],[4]] }}}/>
+
title: ExpectedResult
diff --git a/editions/test/tiddlers/tests/data/custom-operators/Parameterised.tid b/editions/test/tiddlers/tests/data/custom-operators/Parameterised.tid
index 42ca40ec6..6901710b7 100644
--- a/editions/test/tiddlers/tests/data/custom-operators/Parameterised.tid
+++ b/editions/test/tiddlers/tests/data/custom-operators/Parameterised.tid
@@ -6,13 +6,13 @@ tags: [[$:/tags/wiki-test-spec]]
title: Output
\whitespace trim
-\function [multiplybysomething[]](first:ignored,factor:2)
+\function .multiplybysomething(first:ignored,factor:2)
[multiply[2]multiply]
\end
-<$text text={{{ [[123]multiplybysomething[]] }}}/>
+<$text text={{{ [[123].multiplybysomething[]] }}}/>
-
-<$text text={{{ [[123]multiplybysomething[x],[4]] }}}/>
+<$text text={{{ [[123].multiplybysomething[x],[4]] }}}/>
+
title: ExpectedResult
diff --git a/editions/test/tiddlers/tests/data/custom-operators/Simple.tid b/editions/test/tiddlers/tests/data/custom-operators/Simple.tid
index 076a54dad..73d46d689 100644
--- a/editions/test/tiddlers/tests/data/custom-operators/Simple.tid
+++ b/editions/test/tiddlers/tests/data/custom-operators/Simple.tid
@@ -5,11 +5,11 @@ tags: [[$:/tags/wiki-test-spec]]
title: Output
-\function [multiplybytwo[]]()
+\function .multiplybytwo()
[multiply[2]]
\end
-<$text text={{{ [[123]multiplybytwo[]] }}}/>
+<$text text={{{ [[123].multiplybytwo[]] }}}/>
+
title: ExpectedResult
From cbce4ebb7b63812c9ba62eea8e5c708c5601b19c Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Mon, 23 May 2022 20:18:54 +0100
Subject: [PATCH 067/405] Allow custom functions to be invoked as attributes
---
core/modules/filters/unknown.js | 21 ++-------
core/modules/widgets/widget.js | 44 ++++++++++++++++++-
.../data/functions/FunctionAttributes.tid | 24 ++++++++++
3 files changed, 69 insertions(+), 20 deletions(-)
create mode 100644 editions/test/tiddlers/tests/data/functions/FunctionAttributes.tid
diff --git a/core/modules/filters/unknown.js b/core/modules/filters/unknown.js
index 81f868427..c52045474 100644
--- a/core/modules/filters/unknown.js
+++ b/core/modules/filters/unknown.js
@@ -24,30 +24,15 @@ exports["[unknown]"] = function(source,operator,options) {
var customDefinition = options.widget && options.widget.getVariableInfo && options.widget.getVariableInfo(operator.operator);
if(customDefinition && customDefinition.srcVariable && customDefinition.srcVariable.isFunctionDefinition) {
var variables = Object.create(null);
+ // Go through each of the defined parameters, and make a variable with the value of the corresponding operand
$tw.utils.each(customDefinition.srcVariable.params,function(param,index) {
- var value = operator.operands[index];
+ var value = operator.operands["" + index];
if(value === undefined) {
value = param["default"] || "";
}
variables[param.name] = value;
});
- var getVariable = function(name,opts) {
- if(name in variables) {
- return variables[name];
- } else {
- return options.widget.getVariable(name,opts);
- };
- };
- var getVariableInfo = function(name,opts) {
- if(name in variables) {
- return {
- text: variables[name]
- };
- } else {
- return options.widget.getVariableInfo(name,opts);
- };
- }
- var list = options.wiki.filterTiddlers(customDefinition.srcVariable.value,{getVariable: getVariable,getVariableInfo: getVariableInfo},source);
+ var list = options.wiki.filterTiddlers(customDefinition.srcVariable.value,options.widget.makeFakeWidgetWithVariables(variables),source);
if(operator.prefix === "!") {
var results = [];
source(function(tiddler,title) {
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index 627c3490c..444e0ce87 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -275,6 +275,32 @@ Widget.prototype.getStateQualifier = function(name) {
}
};
+/*
+Make a fake widget with specified variables, suitable for variable lookup in filters
+*/
+Widget.prototype.makeFakeWidgetWithVariables = function(variables) {
+ var self = this;
+ return {
+ getVariable: function(name,opts) {
+ if(name in variables) {
+ return variables[name];
+ } else {
+ return self.getVariable(name,opts);
+ };
+ },
+ getVariableInfo: function(name,opts) {
+ if(name in variables) {
+ return {
+ text: variables[name]
+ };
+ } else {
+ return self.getVariableInfo(name,opts);
+ };
+ },
+ makeFakeWidgetWithVariables: self.makeFakeWidgetWithVariables
+ };
+};
+
/*
Compute the current values of the attributes of the widget. Returns a hashmap of the names of the attributes that have changed.
Options include:
@@ -300,13 +326,27 @@ Widget.prototype.computeAttributes = function(options) {
};
Widget.prototype.computeAttribute = function(attribute) {
- var value;
+ var self = this,
+ value;
if(attribute.type === "filtered") {
value = this.wiki.filterTiddlers(attribute.filter,this)[0] || "";
} else if(attribute.type === "indirect") {
value = this.wiki.getTextReference(attribute.textReference,"",this.getVariable("currentTiddler"));
} else if(attribute.type === "macro") {
- value = this.getVariable(attribute.value.name,{params: attribute.value.params});
+ var variableInfo = this.getVariableInfo(attribute.value.name,{params: attribute.value.params});
+ if(variableInfo.srcVariable && variableInfo.srcVariable.isFunctionDefinition) {
+ // It's a function definition
+ var variables = Object.create(null);
+ // Go through each of the defined parameters, and make a variable with the value of the corresponding provided parameter
+ var params = this.resolveVariableParameters(variableInfo.srcVariable.params,attribute.value.params);
+ $tw.utils.each(params,function(param,index) {
+ variables[param.name] = param.value;
+ });
+ var list = self.wiki.filterTiddlers(variableInfo.text,this.makeFakeWidgetWithVariables(variables));
+ value = list[0] || "";
+ } else {
+ value = variableInfo.text;
+ }
} else { // String attribute
value = attribute.value;
}
diff --git a/editions/test/tiddlers/tests/data/functions/FunctionAttributes.tid b/editions/test/tiddlers/tests/data/functions/FunctionAttributes.tid
new file mode 100644
index 000000000..2deb49bdc
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/functions/FunctionAttributes.tid
@@ -0,0 +1,24 @@
+title: Functions/FunctionAttributes
+description: Attributes specified as function invocations
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\function .dividebysomething(factor:0.5)
+[divide]
+\end
+
+\function multiplebysomething(first:ignored,factor:2)
+[multiply[2].dividebysomething[0.25]]
+\end
+
+<$text text=<>/>
+|
+<$text text=<>/>
+
++
+title: ExpectedResult
+
+16|32
\ No newline at end of file
From 6cc99fcbe33da47cfcb1335d0742b16ea1b9ce03 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Wed, 25 May 2022 15:15:17 +0100
Subject: [PATCH 068/405] WIP
---
.../system/TiddlyWiki Pre-release.tid | 2 ++
.../tiddlers/tests/data/transclude/Mario.tid | 28 +++++++++++++++++++
.../tiddlers/hellothere/HelloThere.tid | 4 +++
.../internals/state-editpreviewtype.tid | 2 ++
.../internals/state-showeditpreview.tid | 2 ++
5 files changed, 38 insertions(+)
create mode 100644 editions/test/tiddlers/tests/data/transclude/Mario.tid
create mode 100644 plugins/tiddlywiki/internals/state-editpreviewtype.tid
create mode 100644 plugins/tiddlywiki/internals/state-showeditpreview.tid
diff --git a/editions/prerelease/tiddlers/system/TiddlyWiki Pre-release.tid b/editions/prerelease/tiddlers/system/TiddlyWiki Pre-release.tid
index a1fa201dc..35d6665bd 100644
--- a/editions/prerelease/tiddlers/system/TiddlyWiki Pre-release.tid
+++ b/editions/prerelease/tiddlers/system/TiddlyWiki Pre-release.tid
@@ -1,6 +1,8 @@
title: TiddlyWiki Pre-release
modified: 20150428204930183
+{{HelloThere||$:/plugins/tiddlywiki/internals/EditTemplate/body/preview/parse-tree}}
+
This is a pre-release build of TiddlyWiki, [[also available in empty form|https://tiddlywiki.com/prerelease/empty.html]]. It is provided for testing purposes. ''Please don't try to use it for anything important'' -- you should use the latest official release from https://tiddlywiki.com.
<$list filter="[tag[ReleaseNotes]!has[released]!sort[created]]">
diff --git a/editions/test/tiddlers/tests/data/transclude/Mario.tid b/editions/test/tiddlers/tests/data/transclude/Mario.tid
new file mode 100644
index 000000000..6bb6b7543
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/transclude/Mario.tid
@@ -0,0 +1,28 @@
+title: UnwantedParagraphs
+description: Unwanted paragraph elements when using slot/fill
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+\procedure hello
+\whitespace trim
+
+<$slot $name="greeting"/>
+
+\end
+
+
+<$transclude $variable="hello" mode="inline">
+<$fill $name="greeting">
+A heading
+A paragraph
+$fill>
+$transclude>
+
+
++
+title: ExpectedResult
+
+A heading
A paragraph
\ No newline at end of file
diff --git a/editions/tw5.com/tiddlers/hellothere/HelloThere.tid b/editions/tw5.com/tiddlers/hellothere/HelloThere.tid
index 7a464c7db..21cf7f110 100644
--- a/editions/tw5.com/tiddlers/hellothere/HelloThere.tid
+++ b/editions/tw5.com/tiddlers/hellothere/HelloThere.tid
@@ -5,6 +5,10 @@ tags: TableOfContents
title: HelloThere
type: text/vnd.tiddlywiki
+>>
+This
+
+
''Have you ever had the feeling that your head is not quite big enough to hold everything you need to remember?''
Welcome to TiddlyWiki, a unique [[non-linear|Philosophy of Tiddlers]] notebook for [[capturing|Creating and editing tiddlers]], [[organising|Structuring TiddlyWiki]] and [[sharing|Sharing your tiddlers with others]] complex information.
diff --git a/plugins/tiddlywiki/internals/state-editpreviewtype.tid b/plugins/tiddlywiki/internals/state-editpreviewtype.tid
new file mode 100644
index 000000000..3879f1fde
--- /dev/null
+++ b/plugins/tiddlywiki/internals/state-editpreviewtype.tid
@@ -0,0 +1,2 @@
+title: $:/state/editpreviewtype
+text: $:/plugins/tiddlywiki/internals/EditTemplate/body/preview/parse-tree
\ No newline at end of file
diff --git a/plugins/tiddlywiki/internals/state-showeditpreview.tid b/plugins/tiddlywiki/internals/state-showeditpreview.tid
new file mode 100644
index 000000000..9a458102a
--- /dev/null
+++ b/plugins/tiddlywiki/internals/state-showeditpreview.tid
@@ -0,0 +1,2 @@
+title: $:/state/showeditpreview
+text: yes
\ No newline at end of file
From 0e0815002838c85de2f0416b55ede846effe3938 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Wed, 25 May 2022 18:04:32 +0100
Subject: [PATCH 069/405] Remove unneeded test tiddler
---
.../test/tiddlers/tests/data/genesis-widget/RedefineLet.tid | 4 ----
1 file changed, 4 deletions(-)
diff --git a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
index 395411dd1..126d1bf33 100644
--- a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
+++ b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid
@@ -22,10 +22,6 @@ title: Output
(<$text text=<<$$three>>/>)
$let>
+
-title: Definition
-
-\whitespace trim
-+
title: ExpectedResult
(--Elephant--)
From 45b7b4bc6daa024d072b5b0d012c2ca4d6b61c04 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Thu, 26 May 2022 08:23:54 +0100
Subject: [PATCH 070/405] Make is[variable] and variables[] operators resilient
to fake widgets
---
core/modules/filters/is/variable.js | 4 ++--
core/modules/filters/variables.js | 12 +++++++++---
2 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/core/modules/filters/is/variable.js b/core/modules/filters/is/variable.js
index 1f2e5d1b3..110d9a7c8 100644
--- a/core/modules/filters/is/variable.js
+++ b/core/modules/filters/is/variable.js
@@ -19,13 +19,13 @@ exports.variable = function(source,prefix,options) {
var results = [];
if(prefix === "!") {
source(function(tiddler,title) {
- if(!(title in options.widget.variables)) {
+ if(options.widget.getVariable(title) === undefined) {
results.push(title);
}
});
} else {
source(function(tiddler,title) {
- if(title in options.widget.variables) {
+ if(options.widget.getVariable(title) !== undefined) {
results.push(title);
}
});
diff --git a/core/modules/filters/variables.js b/core/modules/filters/variables.js
index fda40a404..c92b780d2 100644
--- a/core/modules/filters/variables.js
+++ b/core/modules/filters/variables.js
@@ -16,9 +16,15 @@ Filter operator for returning the names of the active variables
Export our filter function
*/
exports.variables = function(source,operator,options) {
- var names = [];
- for(var variable in options.widget.variables) {
- names.push(variable);
+ var names = [],
+ widget = options.widget;
+ while(widget && !widget.hasOwnProperty("variables")) {
+ widget = widget.parentWidget;
+ }
+ if(widget && widget.variables) {
+ for(var variable in widget.variables) {
+ names.push(variable);
+ }
}
return names.sort();
};
From dec45f0fc3ce1b934ff224cc869629bf2f1ebab5 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Thu, 26 May 2022 21:11:32 +0100
Subject: [PATCH 071/405] Fix importvariables to work with setvariables as well
as set (they are aliases)
---
core/modules/widgets/importvariables.js | 26 ++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/core/modules/widgets/importvariables.js b/core/modules/widgets/importvariables.js
index 0c8ef5f29..1ef739253 100644
--- a/core/modules/widgets/importvariables.js
+++ b/core/modules/widgets/importvariables.js
@@ -49,19 +49,19 @@ ImportVariablesWidget.prototype.execute = function(tiddlerList) {
var parser = widgetPointer.wiki.parseTiddler(title,{parseAsInline:true});
if(parser) {
var parseTreeNode = parser.tree[0];
- while(parseTreeNode && ["set","parameters"].indexOf(parseTreeNode.type) !== -1) {
- if(parseTreeNode.type === "set") {
- var node = {
- type: "set",
- attributes: parseTreeNode.attributes,
- params: parseTreeNode.params,
- isMacroDefinition: parseTreeNode.isMacroDefinition,
- isFunctionDefinition: parseTreeNode.isFunctionDefinition,
- isProcedureDefinition: parseTreeNode.isProcedureDefinition,
- isWidgetDefinition: parseTreeNode.isWidgetDefinition,
- configTrimWhiteSpace: parseTreeNode.configTrimWhiteSpace
- };
- if (parseTreeNode.isMacroDefinition || parseTreeNode.isProcedureDefinition) {
+ while(parseTreeNode && ["setvariable","set","parameters"].indexOf(parseTreeNode.type) !== -1) {
+ var node = {
+ type: "set",
+ attributes: parseTreeNode.attributes,
+ params: parseTreeNode.params,
+ isMacroDefinition: parseTreeNode.isMacroDefinition,
+ isFunctionDefinition: parseTreeNode.isFunctionDefinition,
+ isProcedureDefinition: parseTreeNode.isProcedureDefinition,
+ isWidgetDefinition: parseTreeNode.isWidgetDefinition,
+ configTrimWhiteSpace: parseTreeNode.configTrimWhiteSpace
+ };
+ if(parseTreeNode.type === "set" || parseTreeNode.type === "setvariable") {
+ if(parseTreeNode.isMacroDefinition || parseTreeNode.isProcedureDefinition || parseTreeNode.isWidgetDefinition || parseTreeNode.isFunctionDefinition) {
// Macro definitions can be folded into
// current widget instead of adding
// another link to the chain.
From 6f9f92fa699067b0912e6f36a93888bf80f3f5d9 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Thu, 26 May 2022 21:11:53 +0100
Subject: [PATCH 072/405] Add support for $:/tags/Global
---
core/modules/wiki.js | 38 +++++++++++++------
.../tiddlers/tests/data/globals/Simple.tid | 19 ++++++++++
.../jasmine/run-wiki-based-tests.js | 10 ++---
3 files changed, 49 insertions(+), 18 deletions(-)
create mode 100644 editions/test/tiddlers/tests/data/globals/Simple.tid
diff --git a/core/modules/wiki.js b/core/modules/wiki.js
index 49c81930e..9fd254be9 100755
--- a/core/modules/wiki.js
+++ b/core/modules/wiki.js
@@ -1075,22 +1075,38 @@ exports.makeWidget = function(parser,options) {
options = options || {};
var widgetNode = {
type: "widget",
- children: []
+ children: [{
+ type: "importvariables",
+ attributes: {
+ filter: {
+ name: "filter",
+ type: "string",
+ value: "[all[shadows+tiddlers]tag[$:/tags/Global]!is[draft]]"
+ }
+ },
+ isBlock: false,
+ children: []
+ }]
},
- currWidgetNode = widgetNode;
- // Create set variable widgets for each variable
- $tw.utils.each(options.variables,function(value,name) {
- var setVariableWidget = {
- type: "set",
+ currWidgetNode = widgetNode.children[0];
+ // Create let variable widget for variables
+ if($tw.utils.count(options.variables) > 0) {
+ var letVariableWidget = {
+ type: "let",
attributes: {
- name: {type: "string", value: name},
- value: {type: "string", value: value}
},
children: []
};
- currWidgetNode.children = [setVariableWidget];
- currWidgetNode = setVariableWidget;
- });
+ $tw.utils.each(options.variables,function(value,name) {
+ letVariableWidget.attributes[name] = {
+ name: name,
+ type: "string",
+ value: "" + value
+ }
+ });
+ currWidgetNode.children = [letVariableWidget];
+ currWidgetNode = letVariableWidget;
+ }
// Add in the supplied parse tree nodes
currWidgetNode.children = parser ? parser.tree : [];
// Create the widget
diff --git a/editions/test/tiddlers/tests/data/globals/Simple.tid b/editions/test/tiddlers/tests/data/globals/Simple.tid
new file mode 100644
index 000000000..6e4d0fc06
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/globals/Simple.tid
@@ -0,0 +1,19 @@
+title: Globals/Simple
+description: Global procedures
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+
+<>
++
+title: One
+tags: $:/tags/Global
+
+\procedure this-is-one() ONE
++
+title: ExpectedResult
+
+ONE
\ No newline at end of file
diff --git a/plugins/tiddlywiki/jasmine/run-wiki-based-tests.js b/plugins/tiddlywiki/jasmine/run-wiki-based-tests.js
index 4db3e232f..6f12aaf89 100644
--- a/plugins/tiddlywiki/jasmine/run-wiki-based-tests.js
+++ b/plugins/tiddlywiki/jasmine/run-wiki-based-tests.js
@@ -65,16 +65,12 @@ describe("Wiki-based tests", function() {
return tiddlers;
}
- function createWidgetNode(parseTreeNode,wiki) {
- return new widget.widget(parseTreeNode,{
- wiki: wiki,
- document: $tw.fakeDocument
- });
+ function createWidgetNode(parser,wiki) {
+ return wiki.makeWidget(parser);
}
function parseText(text,wiki,options) {
- var parser = wiki.parseText("text/vnd.tiddlywiki",text,options);
- return parser ? {type: "widget", children: parser.tree} : undefined;
+ return wiki.parseText("text/vnd.tiddlywiki",text,options);
}
function renderWidgetNode(widgetNode) {
From 7fc65d0d1a8cb6b9ba4ec0aad8bd486488aaae7b Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Fri, 27 May 2022 08:36:31 +0100
Subject: [PATCH 073/405] Remove accidental commit
6cc99fcbe33da47cfcb1335d0742b16ea1b9ce03
---
.../system/TiddlyWiki Pre-release.tid | 2 --
.../tiddlers/tests/data/transclude/Mario.tid | 28 -------------------
.../tiddlers/hellothere/HelloThere.tid | 4 ---
.../internals/state-editpreviewtype.tid | 2 --
.../internals/state-showeditpreview.tid | 2 --
5 files changed, 38 deletions(-)
delete mode 100644 editions/test/tiddlers/tests/data/transclude/Mario.tid
delete mode 100644 plugins/tiddlywiki/internals/state-editpreviewtype.tid
delete mode 100644 plugins/tiddlywiki/internals/state-showeditpreview.tid
diff --git a/editions/prerelease/tiddlers/system/TiddlyWiki Pre-release.tid b/editions/prerelease/tiddlers/system/TiddlyWiki Pre-release.tid
index 35d6665bd..a1fa201dc 100644
--- a/editions/prerelease/tiddlers/system/TiddlyWiki Pre-release.tid
+++ b/editions/prerelease/tiddlers/system/TiddlyWiki Pre-release.tid
@@ -1,8 +1,6 @@
title: TiddlyWiki Pre-release
modified: 20150428204930183
-{{HelloThere||$:/plugins/tiddlywiki/internals/EditTemplate/body/preview/parse-tree}}
-
This is a pre-release build of TiddlyWiki, [[also available in empty form|https://tiddlywiki.com/prerelease/empty.html]]. It is provided for testing purposes. ''Please don't try to use it for anything important'' -- you should use the latest official release from https://tiddlywiki.com.
<$list filter="[tag[ReleaseNotes]!has[released]!sort[created]]">
diff --git a/editions/test/tiddlers/tests/data/transclude/Mario.tid b/editions/test/tiddlers/tests/data/transclude/Mario.tid
deleted file mode 100644
index 6bb6b7543..000000000
--- a/editions/test/tiddlers/tests/data/transclude/Mario.tid
+++ /dev/null
@@ -1,28 +0,0 @@
-title: UnwantedParagraphs
-description: Unwanted paragraph elements when using slot/fill
-type: text/vnd.tiddlywiki-multiple
-tags: [[$:/tags/wiki-test-spec]]
-
-title: Output
-
-\whitespace trim
-\procedure hello
-\whitespace trim
-
-<$slot $name="greeting"/>
-
-\end
-
-
-<$transclude $variable="hello" mode="inline">
-<$fill $name="greeting">
-A heading
-A paragraph
-$fill>
-$transclude>
-
-
-+
-title: ExpectedResult
-
-A heading
A paragraph
\ No newline at end of file
diff --git a/editions/tw5.com/tiddlers/hellothere/HelloThere.tid b/editions/tw5.com/tiddlers/hellothere/HelloThere.tid
index 21cf7f110..7a464c7db 100644
--- a/editions/tw5.com/tiddlers/hellothere/HelloThere.tid
+++ b/editions/tw5.com/tiddlers/hellothere/HelloThere.tid
@@ -5,10 +5,6 @@ tags: TableOfContents
title: HelloThere
type: text/vnd.tiddlywiki
->>
-This
-
-
''Have you ever had the feeling that your head is not quite big enough to hold everything you need to remember?''
Welcome to TiddlyWiki, a unique [[non-linear|Philosophy of Tiddlers]] notebook for [[capturing|Creating and editing tiddlers]], [[organising|Structuring TiddlyWiki]] and [[sharing|Sharing your tiddlers with others]] complex information.
diff --git a/plugins/tiddlywiki/internals/state-editpreviewtype.tid b/plugins/tiddlywiki/internals/state-editpreviewtype.tid
deleted file mode 100644
index 3879f1fde..000000000
--- a/plugins/tiddlywiki/internals/state-editpreviewtype.tid
+++ /dev/null
@@ -1,2 +0,0 @@
-title: $:/state/editpreviewtype
-text: $:/plugins/tiddlywiki/internals/EditTemplate/body/preview/parse-tree
\ No newline at end of file
diff --git a/plugins/tiddlywiki/internals/state-showeditpreview.tid b/plugins/tiddlywiki/internals/state-showeditpreview.tid
deleted file mode 100644
index 9a458102a..000000000
--- a/plugins/tiddlywiki/internals/state-showeditpreview.tid
+++ /dev/null
@@ -1,2 +0,0 @@
-title: $:/state/showeditpreview
-text: yes
\ No newline at end of file
From a2fbebf50974de64c2781ebc26640471af4c8013 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Fri, 27 May 2022 18:37:42 +0100
Subject: [PATCH 074/405] Add utility function for parsing macro parameter
definitions
---
core/modules/parsers/parseutils.js | 30 +++++++++++++++++++
.../parsers/wikiparser/rules/fnprocdef.js | 17 ++---------
.../parsers/wikiparser/rules/parameters.js | 20 +++++--------
3 files changed, 39 insertions(+), 28 deletions(-)
diff --git a/core/modules/parsers/parseutils.js b/core/modules/parsers/parseutils.js
index 4a25259c8..6a0902c6f 100644
--- a/core/modules/parsers/parseutils.js
+++ b/core/modules/parsers/parseutils.js
@@ -123,6 +123,36 @@ exports.parseStringLiteral = function(source,pos) {
}
};
+/*
+Returns an array of {name:} with an optional "default" property. Options include:
+requireParenthesis: require the parameter definition to be wrapped in parenthesis
+*/
+exports.parseParameterDefinition = function(paramString,options) {
+ options = options || {};
+ if(options.requireParenthesis) {
+ var parenMatch = /^\s*\((.*)\)\s*$/g.exec(paramString);
+ if(!parenMatch) {
+ return [];
+ }
+ paramString = parenMatch[1];
+ }
+ var params = [],
+ reParam = /\s*([^:),\s]+)(?:\s*:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|([^,"'\s]+)))?/mg,
+ paramMatch = reParam.exec(paramString);
+ while(paramMatch) {
+ // Save the parameter details
+ var paramInfo = {name: paramMatch[1]},
+ defaultValue = paramMatch[2] || paramMatch[3] || paramMatch[4] || paramMatch[5];
+ if(defaultValue !== undefined) {
+ paramInfo["default"] = defaultValue;
+ }
+ params.push(paramInfo);
+ // Look for the next parameter
+ paramMatch = reParam.exec(paramString);
+ }
+ return params;
+};
+
exports.parseMacroParameters = function(node,source,pos) {
// Process parameters
var parameter = $tw.utils.parseMacroParameter(source,pos);
diff --git a/core/modules/parsers/wikiparser/rules/fnprocdef.js b/core/modules/parsers/wikiparser/rules/fnprocdef.js
index 940567eff..15443bc27 100644
--- a/core/modules/parsers/wikiparser/rules/fnprocdef.js
+++ b/core/modules/parsers/wikiparser/rules/fnprocdef.js
@@ -45,22 +45,9 @@ exports.parse = function() {
// Move past the macro name and parameters
this.parser.pos = this.matchRegExp.lastIndex;
// Parse the parameters
- var paramString = this.match[4],
- params = [];
+ var params = [];
if(this.match[3]) {
- var reParam = /\s*([^:),\s]+)(?:\s*:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|([^,"'\s]+)))?/mg,
- paramMatch = reParam.exec(paramString);
- while(paramMatch) {
- // Save the parameter details
- var paramInfo = {name: paramMatch[1]},
- defaultValue = paramMatch[2] || paramMatch[3] || paramMatch[4] || paramMatch[5];
- if(defaultValue !== undefined) {
- paramInfo["default"] = defaultValue;
- }
- params.push(paramInfo);
- // Look for the next parameter
- paramMatch = reParam.exec(paramString);
- }
+ params = $tw.utils.parseParameterDefinition(this.match[4]);
}
// Is this a multiline definition?
var reEnd;
diff --git a/core/modules/parsers/wikiparser/rules/parameters.js b/core/modules/parsers/wikiparser/rules/parameters.js
index 86b6074d7..745d7b7dd 100644
--- a/core/modules/parsers/wikiparser/rules/parameters.js
+++ b/core/modules/parsers/wikiparser/rules/parameters.js
@@ -36,20 +36,14 @@ exports.parse = function() {
// Move past the macro name and parameters
this.parser.pos = this.matchRegExp.lastIndex;
// Parse the parameters
- var paramString = this.match[1],
- attributes = Object.create(null),
- orderedAttributes = [],
- reParam = /\s*([^:),\s]+)(?:\s*:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|([^,"'\s]+)))?/mg,
- paramMatch = reParam.exec(paramString);
- while(paramMatch) {
- // Save the parameter details
- var name = paramMatch[1],
- attribute = {name: name, type: "string", value: paramMatch[2] || paramMatch[3] || paramMatch[4] || paramMatch[5]};
- attributes[name] = attribute;
+ var params = $tw.utils.parseParameterDefinition(this.match[1]);
+ var attributes = Object.create(null),
+ orderedAttributes = [];
+ $tw.utils.each(params,function(param) {
+ var attribute = {name: param.name, type: "string", value: param["default"] || ""};
+ attributes[param.name] = attribute;
orderedAttributes.push(attribute);
- // Look for the next parameter
- paramMatch = reParam.exec(paramString);
- }
+ });
// Save the macro definition
return [{
type: "parameters",
From f63634900724eda937286d946b2e6f65fcf6d503 Mon Sep 17 00:00:00 2001
From: "jeremy@jermolene.com"
Date: Sat, 28 May 2022 12:23:50 +0100
Subject: [PATCH 075/405] Introduce true global variables
The basic idea is that if we don't find a variable `foo` then we fallback to retrieving the value from the tiddler `$:/global/foo`, if it exists.
This allows us to replace the usual importvariables-based mechanism for global definitions, avoiding cluttering up the variable namespace with every macro.
In order to permit subprocedures to be overridden, we also introduce a mechanism for conditional definitions: preceding the word definition|procedure|function|widget with a + causes the definition only to occur if the specified variable doesn't already exist. In the next commit we'll apply this mechanism to the tabs macro
---
.../parsers/wikiparser/rules/fnprocdef.js | 19 ++---
.../parsers/wikiparser/rules/macrodef.js | 16 +++--
core/modules/widgets/setvariable.js | 27 +++----
core/modules/widgets/widget.js | 71 ++++++++++++-------
.../tests/data/globals/CustomWidget.tid | 22 ++++++
.../tiddlers/tests/data/globals/Functions.tid | 22 ++++++
.../tests/data/globals/Procedures.tid | 27 +++++++
.../ProceduresWithConditionalDefinitions.tid | 26 +++++++
.../tiddlers/tests/test-wikitext-parser.js | 4 +-
9 files changed, 183 insertions(+), 51 deletions(-)
create mode 100644 editions/test/tiddlers/tests/data/globals/CustomWidget.tid
create mode 100644 editions/test/tiddlers/tests/data/globals/Functions.tid
create mode 100644 editions/test/tiddlers/tests/data/globals/Procedures.tid
create mode 100644 editions/test/tiddlers/tests/data/globals/ProceduresWithConditionalDefinitions.tid
diff --git a/core/modules/parsers/wikiparser/rules/fnprocdef.js b/core/modules/parsers/wikiparser/rules/fnprocdef.js
index 15443bc27..17e069397 100644
--- a/core/modules/parsers/wikiparser/rules/fnprocdef.js
+++ b/core/modules/parsers/wikiparser/rules/fnprocdef.js
@@ -35,7 +35,7 @@ Instantiate parse rule
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /^\\(function|procedure|widget)\s+([^(\s]+)(\(\s*([^)]*)\))?(\s*\r?\n)?/mg;
+ this.matchRegExp = /^\\(\+?)(function|procedure|widget)\s+([^(\s]+)(\(\s*([^)]*)\))?(\s*\r?\n)?/mg;
};
/*
@@ -46,12 +46,12 @@ exports.parse = function() {
this.parser.pos = this.matchRegExp.lastIndex;
// Parse the parameters
var params = [];
- if(this.match[3]) {
- params = $tw.utils.parseParameterDefinition(this.match[4]);
+ if(this.match[4]) {
+ params = $tw.utils.parseParameterDefinition(this.match[5]);
}
// Is this a multiline definition?
var reEnd;
- if(this.match[5]) {
+ if(this.match[6]) {
// If so, the end of the body is marked with \end
reEnd = /(\r?\n\\end[^\S\n\r]*(?:$|\r?\n))/mg;
} else {
@@ -75,22 +75,25 @@ exports.parse = function() {
var parseTreeNodes = [{
type: "set",
attributes: {
- name: {type: "string", value: this.match[2]},
+ name: {type: "string", value: this.match[3]},
value: {type: "string", value: text}
},
children: [],
params: params
}];
- if(this.match[1] === "function") {
+ if(this.match[2] === "function") {
parseTreeNodes[0].isFunctionDefinition = true;
- } else if(this.match[1] === "procedure") {
+ } else if(this.match[2] === "procedure") {
parseTreeNodes[0].isProcedureDefinition = true;
- } else if(this.match[1] === "widget") {
+ } else if(this.match[2] === "widget") {
parseTreeNodes[0].isWidgetDefinition = true;
}
if(this.parser.configTrimWhiteSpace) {
parseTreeNodes[0].configTrimWhiteSpace = true;
}
+ if(this.match[1] === "+") {
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"conditional","yes");
+ }
return parseTreeNodes;
};
diff --git a/core/modules/parsers/wikiparser/rules/macrodef.js b/core/modules/parsers/wikiparser/rules/macrodef.js
index cc76ca7ec..c8e88f740 100644
--- a/core/modules/parsers/wikiparser/rules/macrodef.js
+++ b/core/modules/parsers/wikiparser/rules/macrodef.js
@@ -27,7 +27,7 @@ Instantiate parse rule
exports.init = function(parser) {
this.parser = parser;
// Regexp to match
- this.matchRegExp = /^\\define\s+([^(\s]+)\(\s*([^)]*)\)(\s*\r?\n)?/mg;
+ this.matchRegExp = /^\\(\+?)define\s+([^(\s]+)\(\s*([^)]*)\)(\s*\r?\n)?/mg;
};
/*
@@ -37,7 +37,7 @@ exports.parse = function() {
// Move past the macro name and parameters
this.parser.pos = this.matchRegExp.lastIndex;
// Parse the parameters
- var paramString = this.match[2],
+ var paramString = this.match[3],
params = [];
if(paramString !== "") {
var reParam = /\s*([A-Za-z0-9\-_]+)(?:\s*:\s*(?:"""([\s\S]*?)"""|"([^"]*)"|'([^']*)'|\[\[([^\]]*)\]\]|([^"'\s]+)))?/mg,
@@ -56,7 +56,7 @@ exports.parse = function() {
}
// Is this a multiline definition?
var reEnd;
- if(this.match[3]) {
+ if(this.match[4]) {
// If so, the end of the body is marked with \end
reEnd = /(\r?\n\\end[^\S\n\r]*(?:$|\r?\n))/mg;
} else {
@@ -77,16 +77,22 @@ exports.parse = function() {
text = "";
}
// Save the macro definition
- return [{
+ var parseTreeNodes = [{
type: "set",
attributes: {
- name: {type: "string", value: this.match[1]},
+ name: {type: "string", value: this.match[2]},
value: {type: "string", value: text}
},
children: [],
params: params,
isMacroDefinition: true
}];
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"name",this.match[2]);
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"value",text);
+ if(this.match[1] === "+") {
+ $tw.utils.addAttributeToParseTreeNode(parseTreeNodes[0],"conditional","yes");
+ }
+ return parseTreeNodes;
};
})();
diff --git a/core/modules/widgets/setvariable.js b/core/modules/widgets/setvariable.js
index f8e98f390..06bc78678 100755
--- a/core/modules/widgets/setvariable.js
+++ b/core/modules/widgets/setvariable.js
@@ -47,17 +47,20 @@ SetWidget.prototype.execute = function() {
this.setIndex = this.getAttribute("index");
this.setValue = this.getAttribute("value");
this.setEmptyValue = this.getAttribute("emptyValue");
- // Set context variable
- if(this.parseTreeNode.isMacroDefinition) {
- this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,true);
- } else if(this.parseTreeNode.isFunctionDefinition) {
- this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isFunctionDefinition: true});
- } else if(this.parseTreeNode.isProcedureDefinition) {
- this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isProcedureDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace});
- } else if(this.parseTreeNode.isWidgetDefinition) {
- this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isWidgetDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace});
- } else {
- this.setVariable(this.setName,this.getValue());
+ this.setConditional = this.getAttribute("conditional","no") === "yes";
+ // Set context variable, checking for a conditional assignment
+ if(!this.setConditional || this.getVariableInfo(this.setName).text === undefined) {
+ if(this.parseTreeNode.isMacroDefinition) {
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,true);
+ } else if(this.parseTreeNode.isFunctionDefinition) {
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isFunctionDefinition: true});
+ } else if(this.parseTreeNode.isProcedureDefinition) {
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isProcedureDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace});
+ } else if(this.parseTreeNode.isWidgetDefinition) {
+ this.setVariable(this.setName,this.getValue(),this.parseTreeNode.params,undefined,{isWidgetDefinition: true, configTrimWhiteSpace: this.parseTreeNode.configTrimWhiteSpace});
+ } else {
+ this.setVariable(this.setName,this.getValue());
+ }
}
// Construct the child widgets
this.makeChildWidgets();
@@ -111,7 +114,7 @@ Selectively refreshes the widget if needed. Returns true if the widget or any of
*/
SetWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
- if(changedAttributes.name || changedAttributes.filter || changedAttributes.select || changedAttributes.tiddler || (this.setTiddler && changedTiddlers[this.setTiddler]) || changedAttributes.field || changedAttributes.index || changedAttributes.value || changedAttributes.emptyValue ||
+ if(changedAttributes.name || changedAttributes.filter || changedAttributes.select || changedAttributes.tiddler || (this.setTiddler && changedTiddlers[this.setTiddler]) || changedAttributes.field || changedAttributes.index || changedAttributes.value || changedAttributes.emptyValue || changedAttributes.conditional ||
(this.setFilter && this.getValue() != this.variables[this.setName].value)) {
this.refreshSelf();
return true;
diff --git a/core/modules/widgets/widget.js b/core/modules/widgets/widget.js
index 444e0ce87..d8b201554 100755
--- a/core/modules/widgets/widget.js
+++ b/core/modules/widgets/widget.js
@@ -116,6 +116,7 @@ options: see below
Options include
params: array of {name:, value:} for each parameter
defaultValue: default value if the variable is not defined
+allowSelfAssigned: if true, includes the current widget in the context chain instead of just the parent
Returns an object with the following fields:
@@ -124,32 +125,54 @@ text: text of variable, with parameters properly substituted
*/
Widget.prototype.getVariableInfo = function(name,options) {
options = options || {};
- var actualParams = options.params || [],
- parentWidget = this.parentWidget;
- // Check for the variable defined in the parent widget (or an ancestor in the prototype chain)
- if(parentWidget && name in parentWidget.variables) {
- var variable = parentWidget.variables[name],
- originalValue = variable.value,
- value = originalValue;
- // Only substitute parameter and variable references if this variable was defined with the \define pragma
- if(variable.isMacroDefinition) {
- var params = this.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);
- });
- value = this.substituteVariableReferences(value,options);
- }
- return {
- text: value,
- params: params,
- srcVariable: variable,
- isCacheable: originalValue === value
+ var self = this,
+ actualParams = options.params || [],
+ currWidget = options.allowSelfAssigned ? this : this.parentWidget,
+ processVariable = function(variable) {
+ var originalValue = variable.value,
+ value = originalValue,
+ params = [];
+ // Only substitute parameter and variable references if this variable was defined with the \define pragma
+ if(variable.isMacroDefinition) {
+ 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);
+ });
+ value = self.substituteVariableReferences(value,options);
+ }
+ return {
+ text: value,
+ params: params,
+ srcVariable: variable,
+ isCacheable: originalValue === value
+ };
};
+ // Check for the variable defined in the parent widget (or an ancestor in the prototype chain)
+ if(currWidget && name in currWidget.variables) {
+ return processVariable(currWidget.variables[name]);
}
// If the variable doesn't exist in the parent widget then look for a macro module
+ var text = this.evaluateMacroModule(name,actualParams);
+ if(text === undefined) {
+ // Check for a shadow variable tiddler
+ var tiddler = this.wiki.getTiddler("$:/global/" + name);
+ if(tiddler) {
+ return processVariable({
+ value: tiddler.getFieldString("text"),
+ params: $tw.utils.parseParameterDefinition(tiddler.getFieldString("parameters"),{requireParenthesis: true}),
+ isMacroDefinition: tiddler.getFieldString("is-macro") === "yes",
+ isWidgetDefinition: tiddler.getFieldString("is-widget") === "yes",
+ isProcedureDefinition: tiddler.getFieldString("is-procedure") === "yes",
+ isFunctionDefinition: tiddler.getFieldString("is-function") === "yes"
+ });
+ }
+ }
+ if(text === undefined) {
+ text = options.defaultValue;
+ }
return {
- text: this.evaluateMacroModule(name,actualParams,options.defaultValue),
+ text: text,
srcVariable: {}
};
};
@@ -479,12 +502,12 @@ Widget.prototype.makeChildWidget = function(parseTreeNode,options) {
options = options || {};
// Check whether this node type is defined by a custom widget definition
var variableDefinitionName = "$" + parseTreeNode.type,
- variableInfo = this.variables[variableDefinitionName],
+ variableInfo = this.getVariableInfo(variableDefinitionName,{allowSelfAssigned: true}),
isOverrideable = function() {
// Widget is overrideable if it has a double dollar user defined name, or if it is an existing JS widget
return parseTreeNode.type.charAt(0) === "$" || !!self.widgetClasses[parseTreeNode.type];
};
- if(!parseTreeNode.isNotRemappable && isOverrideable() && variableInfo && variableInfo.value && variableInfo.isWidgetDefinition) {
+ if(!parseTreeNode.isNotRemappable && isOverrideable() && variableInfo && variableInfo.srcVariable && variableInfo.srcVariable.value && variableInfo.srcVariable.isWidgetDefinition) {
var newParseTreeNode = {
type: "transclude",
children: [
diff --git a/editions/test/tiddlers/tests/data/globals/CustomWidget.tid b/editions/test/tiddlers/tests/data/globals/CustomWidget.tid
new file mode 100644
index 000000000..027602661
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/globals/CustomWidget.tid
@@ -0,0 +1,22 @@
+title: Globals/CustomWidget
+description: Global shadow variable defining a custom widget
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+
+<$$mywidget foo="Mahogany">
+Sycamore!
+$$mywidget>
++
+title: $:/global/$$mywidget
+is-widget: yes
+parameters: (foo:"bar")
+
+Koala! <$text text=<>/>, <$slot $name="ts-body"/>
++
+title: ExpectedResult
+
+Koala! Mahogany, Sycamore!
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/globals/Functions.tid b/editions/test/tiddlers/tests/data/globals/Functions.tid
new file mode 100644
index 000000000..d8febc369
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/globals/Functions.tid
@@ -0,0 +1,22 @@
+title: Globals/Functions
+description: Global functions in shadow variables
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+
+<$text text=<>/>
+|
+<$text text=<>/>
++
+title: $:/global/this-is-one
+is-function: yes
+parameters: (foo:"2")
+
+[multiply[2.5]]
++
+title: ExpectedResult
+
+5|17.5
\ No newline at end of file
diff --git a/editions/test/tiddlers/tests/data/globals/Procedures.tid b/editions/test/tiddlers/tests/data/globals/Procedures.tid
new file mode 100644
index 000000000..b20ad396b
--- /dev/null
+++ b/editions/test/tiddlers/tests/data/globals/Procedures.tid
@@ -0,0 +1,27 @@
+title: Globals/Procedures
+description: Global procedures in shadow variables
+type: text/vnd.tiddlywiki-multiple
+tags: [[$:/tags/wiki-test-spec]]
+
+title: Output
+
+\whitespace trim
+
+<>
+|
+<>
++
+title: $:/global/this-is-one
+
+\whitespace trim
+
+\procedure example()
+ONE
+\end
+
+\parameters (foo:"nothing")
+<>-<$text text=<>/>
++
+title: ExpectedResult
+
+