From 2e695801b186cdadb888e6249375f19548bc058a Mon Sep 17 00:00:00 2001 From: Saq Imtiaz Date: Wed, 2 Jun 2021 14:58:30 +0200 Subject: [PATCH] Transclude widget: refresh selectively when needed (#5736) * Transclude widget: only refresh when transcluded text reference has changed, includes tests * Refactor wiki.parseTextReference so it is re-usable for getting the parser info * Re-arrange methods in wiki.js to improve diff readability --- core/modules/widgets/transclude.js | 9 +- core/modules/wiki.js | 56 +++++--- .../tiddlers/tests/test-parsetextreference.js | 123 ++++++++++++++++++ 3 files changed, 167 insertions(+), 21 deletions(-) create mode 100644 editions/test/tiddlers/tests/test-parsetextreference.js diff --git a/core/modules/widgets/transclude.js b/core/modules/widgets/transclude.js index 2acd8109b..18645cd27 100755 --- a/core/modules/widgets/transclude.js +++ b/core/modules/widgets/transclude.js @@ -60,6 +60,8 @@ TranscludeWidget.prototype.execute = function() { subTiddler: this.transcludeSubTiddler }), parseTreeNodes = parser ? parser.tree : this.parseTreeNode.children; + this.sourceText = parser ? parser.source : null; + this.parserType = parser? parser.type : null; // Set context variables for recursion detection var recursionMarker = this.makeRecursionMarker(); if(this.recursionMarker === "yes") { @@ -98,12 +100,17 @@ TranscludeWidget.prototype.makeRecursionMarker = function() { return output.join(""); }; +TranscludeWidget.prototype.parserNeedsRefresh = function() { + 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) +}; + /* Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering */ TranscludeWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.tiddler || changedAttributes.field || changedAttributes.index || changedTiddlers[this.transcludeTitle]) { + if(($tw.utils.count(changedAttributes) > 0) || (changedTiddlers[this.transcludeTitle] && this.parserNeedsRefresh())) { this.refreshSelf(); return true; } else { diff --git a/core/modules/wiki.js b/core/modules/wiki.js index 072b76dbf..5401f7b8c 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -937,41 +937,57 @@ exports.parseTiddler = function(title,options) { }; exports.parseTextReference = function(title,field,index,options) { - var tiddler,text; - if(options.subTiddler) { - tiddler = this.getSubTiddler(title,options.subTiddler); - } else { + var tiddler, + text, + parserInfo; + if(!options.subTiddler) { tiddler = this.getTiddler(title); if(field === "text" || (!field && !index)) { this.getTiddlerText(title); // Force the tiddler to be lazily loaded return this.parseTiddler(title,options); } + } + parserInfo = this.getTextReferenceParserInfo(title,field,index,options); + if(parserInfo.sourceText !== null) { + return this.parseText(parserInfo.parserType,parserInfo.sourceText,options); + } else { + return null; + } +}; + +exports.getTextReferenceParserInfo = function(title,field,index,options) { + var tiddler, + parserInfo = { + sourceText : null, + parserType : "text/vnd.tiddlywiki" + }; + if(options.subTiddler) { + tiddler = this.getSubTiddler(title,options.subTiddler); + } else { + tiddler = this.getTiddler(title); } if(field === "text" || (!field && !index)) { if(tiddler && tiddler.fields) { - return this.parseText(tiddler.fields.type,tiddler.fields.text,options); - } else { - return null; + parserInfo.sourceText = tiddler.fields.text || ""; + if(tiddler.fields.type) { + parserInfo.parserType = tiddler.fields.type; + } } } else if(field) { if(field === "title") { - text = title; - } else { - if(!tiddler || !tiddler.hasField(field)) { - return null; - } - text = tiddler.fields[field]; + parserInfo.sourceText = title; + } else if(tiddler && tiddler.fields) { + parserInfo.sourceText = tiddler.fields[field] ? tiddler.fields[field].toString() : null; } - return this.parseText("text/vnd.tiddlywiki",text.toString(),options); } else if(index) { this.getTiddlerText(title); // Force the tiddler to be lazily loaded - text = this.extractTiddlerDataItem(tiddler,index,undefined); - if(text === undefined) { - return null; - } - return this.parseText("text/vnd.tiddlywiki",text,options); + parserInfo.sourceText = this.extractTiddlerDataItem(tiddler,index,null); } -}; + if(parserInfo.sourceText === null) { + parserInfo.parserType = null; + } + return parserInfo; +} /* Make a widget tree for a parse tree diff --git a/editions/test/tiddlers/tests/test-parsetextreference.js b/editions/test/tiddlers/tests/test-parsetextreference.js new file mode 100644 index 000000000..411f7a45b --- /dev/null +++ b/editions/test/tiddlers/tests/test-parsetextreference.js @@ -0,0 +1,123 @@ +/*\ +title: test-parsetextreference.js +type: application/javascript +tags: [[$:/tags/test-spec]] + +Tests for source attribute in parser returned from wiki.parseTextReference + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +describe("Wiki.parseTextReference tests", function() { + + // Create a wiki + var wiki = new $tw.Wiki(); + wiki.addTiddler({ + title: "TiddlerOne", + text: "The quick brown fox in $:/TiddlerTwo", + tags: ["one"], + authors: "Joe Bloggs", + modifier: "JoeBloggs", + modified: "201304152222"}); + wiki.addTiddler({ + title: "$:/TiddlerTwo", + tags: ["two"], + authors: "[[John Doe]]", + modifier: "John", + modified: "201304152211"}); + wiki.addTiddler({ + title: "Tiddler Three", + text: '{"oct":31,"nov":30,"dec":31}', + tags: ["one","two"], + type: "application/json", + modifier: "John", + modified: "201304162202"}); + wiki.addTiddler({ + title: "TiddlerFour", + text: "The quick brown fox in $:/TiddlerTwo", + tags: ["one"], + type: "text/vnd.tiddlywiki", + authors: "Joe Bloggs", + modifier: "JoeBloggs", + modified: "201304152222"}); + // Add a plugin containing some shadow tiddlers + var shadowTiddlers = { + tiddlers: { + "$:/TiddlerFive": { + title: "$:/TiddlerFive", + text: "Everything in federation", + tags: ["two"] + }, + "TiddlerSix": { + title: "TiddlerSix", + text: "Missing inaction from TiddlerOne", + filter: "[[one]] [[a a]] [subfilter{hasList!!list}]", + tags: [] + }, + "TiddlerSeventh": { + title: "TiddlerSeventh", + text: "", + list: "TiddlerOne [[Tiddler Three]] [[a fourth tiddler]] MissingTiddler", + tags: ["one"] + }, + "Tiddler8": { + title: "Tiddler8", + text: "Tidd", + tags: ["one"], + "test-field": "JoeBloggs" + } + } + }; + wiki.addTiddler({ + title: "$:/ShadowPlugin", + text: JSON.stringify(shadowTiddlers), + "plugin-type": "plugin", + type: "application/json"}); + wiki.addTiddler({ + title: "TiddlerNine", + text: "this is plain text", + type: "text/plain" + }); + + // Define a parsing shortcut for souce attribute of parser returned by wiki.parseTextReference + var parseAndGetSource = function(title,field,index,subTiddler) { + var parser = wiki.parseTextReference(title,field,index,{subTiddler: subTiddler}); + return parser ? parser.source : null; + }; + + it("should parse text references and return correct source attribute", function(){ + // Existing tiddler with a text field, no field argument specified + expect(parseAndGetSource("TiddlerOne")).toEqual("The quick brown fox in $:/TiddlerTwo"); + // Existing tiddler with a text field, field argument specified as text + expect(parseAndGetSource("TiddlerOne","text")).toEqual("The quick brown fox in $:/TiddlerTwo"); + // Existing tiddler with no text field + expect(parseAndGetSource("$:/TiddlerTwo")).toEqual(""); + // Existing tiddler, field argument specified as authors + expect(parseAndGetSource("TiddlerOne","authors")).toEqual("Joe Bloggs"); + // Non-existent tiddler, no field argument + expect(parseAndGetSource("MissingTiddler")).toEqual(null); + // Non-existent tiddler, field argument + expect(parseAndGetSource("MissingTiddler","missing-field")).toEqual(null); + // Non-existent tiddler, index specified + expect(parseAndGetSource("MissingTiddler",null,"missing-index")).toEqual(null); + // Existing tiddler with non existent field + expect(parseAndGetSource("TiddlerOne","missing-field")).toEqual(null); + // Data tiddler with index specified + expect(parseAndGetSource("Tiddler Three",null,"oct")).toEqual("31"); + // Existing tiddler with a text field, type set to vnd.tiddlywiki + expect(parseAndGetSource("TiddlerFour")).toEqual("The quick brown fox in $:/TiddlerTwo"); + // Existing subtiddler of a plugin + expect(parseAndGetSource("$:/ShadowPlugin","text",null,"Tiddler8")).toEqual("Tidd"); + // Non-existent subtiddler of a plugin + expect(parseAndGetSource("$:/ShadowPlugin","text",null,"MyMissingTiddler")).toEqual(null); + // Plain text tiddler + expect(parseAndGetSource("TiddlerNine")).toEqual(undefined); + }); + +}); + +})();