1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2025-02-03 20:59:09 +00:00

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.
This commit is contained in:
jeremy@jermolene.com 2022-05-05 08:20:14 +01:00
parent 35430d09ed
commit f307f00e32
10 changed files with 68 additions and 62 deletions

View File

@ -12,8 +12,13 @@ Parse tree utility functions.
/*global $tw: false */ /*global $tw: false */
"use strict"; "use strict";
/*
Add attribute to parse tree node
Can be invoked as (node,name,value) or (node,attr)
*/
exports.addAttributeToParseTreeNode = function(node,name,value) { 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.attributes = node.attributes || {};
node.orderedAttributes = node.orderedAttributes || []; node.orderedAttributes = node.orderedAttributes || [];
node.attributes[name] = attribute; node.attributes[name] = attribute;

View File

@ -41,6 +41,7 @@ GenesisWidget.prototype.execute = function() {
// Collect attributes // Collect attributes
this.genesisType = this.getAttribute("$type","element"); this.genesisType = this.getAttribute("$type","element");
this.genesisTag = this.getAttribute("$tag","div"); this.genesisTag = this.getAttribute("$tag","div");
this.genesisRemappable = this.getAttribute("$remappable","yes") === "yes";
this.genesisNames = this.getAttribute("$names",""); this.genesisNames = this.getAttribute("$names","");
this.genesisValues = this.getAttribute("$values",""); this.genesisValues = this.getAttribute("$values","");
// Construct parse tree // Construct parse tree
@ -49,7 +50,8 @@ GenesisWidget.prototype.execute = function() {
tag: this.genesisTag, tag: this.genesisTag,
attributes: {}, attributes: {},
orderedAttributes: [], orderedAttributes: [],
children: this.parseTreeNode.children || [] children: this.parseTreeNode.children || [],
isNotRemappable: !this.genesisRemappable
}]; }];
// Apply attributes in $names/$values // Apply attributes in $names/$values
this.attributeNames = []; this.attributeNames = [];

View File

@ -54,8 +54,9 @@ ParametersWidget.prototype.execute = function() {
value = transclusionWidget.getTransclusionParameter(name,index,self.getAttribute(name)); value = transclusionWidget.getTransclusionParameter(name,index,self.getAttribute(name));
self.setVariable(name,value); self.setVariable(name,value);
}); });
this.setVariable("paramNames",$tw.utils.stringifyList(transclusionWidget.getTransclusionParameterNames())); $tw.utils.each(transclusionWidget.getTransclusionMetaVariables(),function(value,name) {
this.setVariable("paramValues",$tw.utils.stringifyList(transclusionWidget.getTransclusionParameterValues())); self.setVariable(name,value);
});
} }
// Construct the child widgets // Construct the child widgets
this.makeChildWidgets(); this.makeChildWidgets();

View File

@ -43,13 +43,24 @@ Compute the internal state of the widget
SlotWidget.prototype.execute = function() { SlotWidget.prototype.execute = function() {
var self = this; var self = this;
this.slotName = this.getAttribute("$name"); this.slotName = this.getAttribute("$name");
// Find the parent transclusion this.slotDepth = parseInt(this.getAttribute("$depth","1"),10) || 1;
var transclusionWidget = this.parentWidget; // Find the parent transclusions
while(transclusionWidget && !(transclusionWidget instanceof TranscludeWidget)) { var pointer = this.parentWidget,
transclusionWidget = transclusionWidget.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 // Construct the child widgets
this.makeChildWidgets(parseTreeNodes); this.makeChildWidgets(parseTreeNodes);
}; };
@ -59,7 +70,7 @@ Refresh the widget by ensuring our attributes are up to date
*/ */
SlotWidget.prototype.refresh = function(changedTiddlers) { SlotWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes(); var changedAttributes = this.computeAttributes();
if(changedAttributes["$name"]) { if(changedAttributes["$name"] || changedAttributes["$depth"]) {
this.refreshSelf(); this.refreshSelf();
return true; return true;
} }

View File

@ -46,13 +46,7 @@ TranscludeWidget.prototype.execute = function() {
parseTreeNodes = target.parseTreeNodes; parseTreeNodes = target.parseTreeNodes;
this.sourceText = target.source; this.sourceText = target.source;
this.sourceType = target.type; this.sourceType = target.type;
// Wrap the transcluded content if required this.parseAsInline = target.parseAsInline;
if(this.slotValueParseTrees["ts-wrapper"]) {
this.slotValueParseTrees["ts-wrapped"] = parseTreeNodes;
parseTreeNodes = this.slotValueParseTrees["ts-wrapper"];
this.sourceTest = undefined;
this.sourceType = undefined;
}
// Set context variables for recursion detection // Set context variables for recursion detection
var recursionMarker = this.makeRecursionMarker(); var recursionMarker = this.makeRecursionMarker();
if(this.recursionMarker === "yes") { if(this.recursionMarker === "yes") {
@ -135,6 +129,7 @@ TranscludeWidget.prototype.collectSlotValueParameters = function() {
if(this.legacyMode) { if(this.legacyMode) {
this.slotValueParseTrees["ts-missing"] = this.parseTreeNode.children; this.slotValueParseTrees["ts-missing"] = this.parseTreeNode.children;
} else { } else {
this.slotValueParseTrees["ts-raw"] = this.parseTreeNode.children;
var noValueWidgetsFound = true, var noValueWidgetsFound = true,
searchParseTreeNodes = function(nodes) { searchParseTreeNodes = function(nodes) {
$tw.utils.each(nodes,function(node) { $tw.utils.each(nodes,function(node) {
@ -171,11 +166,11 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
if(this.transcludeVariable) { if(this.transcludeVariable) {
var variableInfo = this.getVariableInfo(this.transcludeVariable).srcVariable; var variableInfo = this.getVariableInfo(this.transcludeVariable).srcVariable;
if(variableInfo) { if(variableInfo) {
var mode = this.parseTreeNode.isBlock ? "blockParser" : "inlineParser"; var mode = parseAsInline ? "inlineParser" : "blockParser";
if(variableInfo[mode]) { if(variableInfo[mode]) {
parser = variableInfo[mode]; parser = variableInfo[mode];
} else { } 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; variableInfo[mode] = parser;
} }
if(parser && variableInfo.isFunctionDefinition) { if(parser && variableInfo.isFunctionDefinition) {
@ -206,6 +201,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
return { return {
parser: parser, parser: parser,
parseTreeNodes: parser.tree, parseTreeNodes: parser.tree,
parseAsInline: parseAsInline,
text: parser.source, text: parser.source,
type: parser.type type: parser.type
}; };
@ -213,6 +209,7 @@ TranscludeWidget.prototype.getTransclusionTarget = function() {
return { return {
parser: null, parser: null,
parseTreeNodes: (this.slotValueParseTrees["ts-missing"] || []), parseTreeNodes: (this.slotValueParseTrees["ts-missing"] || []),
parseAsInline: parseAsInline,
text: null, text: null,
type: null type: null
}; };
@ -234,6 +231,17 @@ TranscludeWidget.prototype.getTransclusionParameter = function(name,index,defaul
return defaultValue; 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 Get an array of the names of all the provided transclusion parameters
*/ */
@ -248,7 +256,7 @@ TranscludeWidget.prototype.getTransclusionParameterValues = function() {
var self = this, var self = this,
values = []; values = [];
$tw.utils.each(Object.keys(this.stringParametersByName),function(name) { $tw.utils.each(Object.keys(this.stringParametersByName),function(name) {
values.push(self.stringParametersByName[name]); values.push(self.stringParametersByName[name] || "");
}); });
return values; return values;
}; };
@ -282,6 +290,7 @@ TranscludeWidget.prototype.makeRecursionMarker = function() {
}; };
TranscludeWidget.prototype.parserNeedsRefresh = 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}); 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) return (this.sourceText === undefined || parserInfo.sourceText !== this.sourceText || parserInfo.parserType !== this.parserType)
}; };

View File

@ -401,49 +401,23 @@ Widget.prototype.makeChildWidget = function(parseTreeNode,options) {
options = options || {}; options = options || {};
// Check whether this node type is defined by a custom macro definition // Check whether this node type is defined by a custom macro definition
var variableDefinitionName = "<$" + parseTreeNode.type + ">"; 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 = { var newParseTreeNode = {
type: "transclude", type: "transclude",
attributes: {
"$variable": {name: "$variable", type: "string", value: variableDefinitionName}
},
children: [ children: [
{ {
type: "value", type: "value",
attributes: {
"$name": {name: "$name", type: "string", value: "ts-body"}
},
children: parseTreeNode.children 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) { $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 // 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; name = name.charAt(0) === "$" ? "$" + name : name;
newParseTreeNode.attributes[name] = attr; $tw.utils.addAttributeToParseTreeNode(newParseTreeNode,$tw.utils.extend({},attr,{name: name}));
}); });
parseTreeNode = newParseTreeNode; parseTreeNode = newParseTreeNode;
} }

View File

@ -17,7 +17,7 @@ title: Definition
\whitespace trim \whitespace trim
\function <$codeblock>(code) \function <$codeblock>(code)
<$codeblock code={{{ [<code>addprefix[£]addsuffix[@]] }}}/> <$genesis $type="codeblock" $remappable="no" code={{{ [<code>addprefix[£]addsuffix[@]] }}}/>
\end \end
+ +
title: Subject title: Subject

View File

@ -24,9 +24,9 @@ title: TiddlerOne
Whale Whale
</$slot> </$slot>
\end \end
<$transclude $tiddler="TiddlerZero"> <$genesis $type="transclude" $remappable="no" $$tiddler="TiddlerZero">
Crocodile Crocodile
</$transclude> </$genesis>
+ +
title: ExpectedResult title: ExpectedResult

View File

@ -15,10 +15,12 @@ title: TiddlerOne
<!-- Redefine the <$text> widget by defining a transcludable variable with that name --> <!-- Redefine the <$text> widget by defining a transcludable variable with that name -->
\function <$text>(text:'Jaguar') \function <$text>(text:'Jaguar')
\whitespace trim \whitespace trim
<$text text=<<text>>/> <$genesis $type="text" $remappable="no" text=<<text>>/>
<$slot $name="ts-body"> <$set name="<$text>" value="">
Whale <$slot $name="ts-body">
</$slot> Whale
</$slot>
</$set>
\end \end
<$text text="Dingo"> <$text text="Dingo">
Crocodile Crocodile

View File

@ -6,6 +6,8 @@ tags: [[$:/tags/wiki-test-spec]]
title: Output title: Output
\whitespace trim \whitespace trim
<$transclude $tiddler="TiddlerOne" 0="" 1="" 2=""/>
{{TiddlerOne}} {{TiddlerOne}}
{{TiddlerOne|Ferret}} {{TiddlerOne|Ferret}}
{{TiddlerOne|Butterfly|Moth}} {{TiddlerOne|Butterfly|Moth}}
@ -17,7 +19,7 @@ title: TiddlerOne
\whitespace trim \whitespace trim
\parameters(zero:'Jaguar',one:'Lizard',two:'Mole') \parameters(zero:'Jaguar',one:'Lizard',two:'Mole')
<$list filter="[enlist<paramNames>]" counter="counter"> <$list filter="[enlist<paramNames>]" counter="counter">
{<$text text={{{ [enlist<paramNames>nth<counter>] }}}/>:<$text text={{{ [enlist<paramValues>nth<counter>] }}}/>} {<$text text={{{ [enlist:raw<paramNames>nth<counter>] }}}/>:<$text text={{{ [enlist:raw<paramValues>nth<counter>] }}}/>}
</$list> </$list>
+ +
title: TiddlerTwo title: TiddlerTwo
@ -28,4 +30,4 @@ title: TiddlerTwo
+ +
title: ExpectedResult title: ExpectedResult
<p></p><p>{0:Ferret}</p><p>{0:Butterfly}{1:Moth}</p><p>{0:Beetle}{1:Scorpion}{2:Snake}</p><p>({zero:Beetle}{one:Scorpion}{two:Snake})</p> <p>{0:}{1:}{2:}</p><p></p><p>{0:Ferret}</p><p>{0:Butterfly}{1:Moth}</p><p>{0:Beetle}{1:Scorpion}{2:Snake}</p><p>({zero:Beetle}{one:Scorpion}{two:Snake})</p>