From f307f00e32b8394705625bffa7b3ceb3a01aed4e Mon Sep 17 00:00:00 2001 From: "jeremy@jermolene.com" Date: Thu, 5 May 2022 08:20:14 +0100 Subject: [PATCH] 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 \end -<$transclude $tiddler="TiddlerZero"> +<$genesis $type="transclude" $remappable="no" $$tiddler="TiddlerZero"> Crocodile - + + 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 - +<$genesis $type="text" $remappable="no" text=<>/> +<$set name="<$text>" value=""> + <$slot $name="ts-body"> + Whale + + \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] }}}/>} + 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