From 64448ae7743a5baa1317cf9ed46dedf792166d37 Mon Sep 17 00:00:00 2001 From: "jeremy@jermolene.com" Date: Sun, 8 May 2022 20:48:33 +0100 Subject: [PATCH] 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"/> + ++ +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,"")); });