From 741aef55e46830d547f6c27c8fd3c19658fc5211 Mon Sep 17 00:00:00 2001 From: lin onetwo Date: Wed, 19 Jun 2024 16:38:02 +0800 Subject: [PATCH] Fix: transcludes and backtranscludes operators to always include self-referential transclusion (#8257) * fix: ignore self-referential transclusion * feat: support old <$transclude tiddler param * fix: restore old behavior: include itself like backlinks[] * refactor: use LinkedList in transcludes[] and backtranscludes[] * fix: only fallback to title when {{!!xxx}}, not when input is empty * refactor: move transcludes ast extractor to a file * refactor: move links ast extractor to a file * Revert "refactor: move links ast extractor to a file" This reverts commit 5600a00cd80257208f5165c1f6b12f97bd3e1302. * Revert "refactor: move transcludes ast extractor to a file" This reverts commit 61d5484f09629ae53d125af2240b657dddcd7a74. * lint: use pushTop and remove space --- core/modules/filters/backtranscludes.js | 6 +-- core/modules/filters/transcludes.js | 2 +- core/modules/indexers/back-indexer.js | 2 +- core/modules/wiki.js | 40 +++++++++++++------ .../tiddlers/tests/test-backtranscludes.js | 37 +++++++++++++---- 5 files changed, 62 insertions(+), 25 deletions(-) diff --git a/core/modules/filters/backtranscludes.js b/core/modules/filters/backtranscludes.js index 7d4215073..253b9dd7b 100644 --- a/core/modules/filters/backtranscludes.js +++ b/core/modules/filters/backtranscludes.js @@ -16,11 +16,11 @@ Filter operator for returning all the backtranscludes from a tiddler Export our filter function */ exports.backtranscludes = function(source,operator,options) { - var results = []; + var results = new $tw.utils.LinkedList(); source(function(tiddler,title) { - $tw.utils.pushTop(results,options.wiki.getTiddlerBacktranscludes(title)); + results.pushTop(options.wiki.getTiddlerBacktranscludes(title)); }); - return results; + return results.makeTiddlerIterator(options.wiki); }; })(); diff --git a/core/modules/filters/transcludes.js b/core/modules/filters/transcludes.js index bd618296b..8f42b3bae 100644 --- a/core/modules/filters/transcludes.js +++ b/core/modules/filters/transcludes.js @@ -20,7 +20,7 @@ exports.transcludes = function(source,operator,options) { source(function(tiddler,title) { results.pushTop(options.wiki.getTiddlerTranscludes(title)); }); - return results.toArray(); + return results.makeTiddlerIterator(options.wiki); }; })(); diff --git a/core/modules/indexers/back-indexer.js b/core/modules/indexers/back-indexer.js index b9daf3328..77b51b819 100644 --- a/core/modules/indexers/back-indexer.js +++ b/core/modules/indexers/back-indexer.js @@ -75,7 +75,7 @@ BackSubIndexer.prototype._getTarget = function(tiddler) { } var parser = this.wiki.parseText(tiddler.fields.type, tiddler.fields.text, {}); if(parser) { - return this.wiki[this.extractor](parser.tree); + return this.wiki[this.extractor](parser.tree, tiddler.fields.title); } return []; } diff --git a/core/modules/wiki.js b/core/modules/wiki.js index 2850dec5f..2954454d5 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -551,28 +551,41 @@ exports.getTiddlerBacklinks = function(targetTitle) { /* -Return an array of tiddler titles that are directly transcluded within the given parse tree +Return an array of tiddler titles that are directly transcluded within the given parse tree. `title` is the tiddler being parsed, we will ignore its self-referential transclusions, only return */ -exports.extractTranscludes = function(parseTreeRoot) { +exports.extractTranscludes = function(parseTreeRoot, title) { // Count up the transcludes var transcludes = [], checkParseTree = function(parseTree, parentNode) { for(var t=0; t`) means self-referential transclusion. + value = title; + } else if(parseTreeNode.attributes.field && parseTreeNode.attributes.field.type === "string") { + // Old usage with Empty value (like `<$transclude field='created'/>`) + value = title; } - if(transcludes.indexOf(value) === -1 && value !== undefined) { - transcludes.push(value); + // Deduplicate the result. + if(value && transcludes.indexOf(value) === -1) { + $tw.utils.pushTop(transcludes,value); } } if(parseTreeNode.children) { - checkParseTree(parseTreeNode.children, parseTreeNode); + checkParseTree(parseTreeNode.children,parseTreeNode); } } }; @@ -591,7 +604,8 @@ exports.getTiddlerTranscludes = function(title) { // Parse the tiddler var parser = self.parseTiddler(title); if(parser) { - return self.extractTranscludes(parser.tree); + // this will ignore self-referential transclusions from `title` + return self.extractTranscludes(parser.tree,title); } return []; }); diff --git a/editions/test/tiddlers/tests/test-backtranscludes.js b/editions/test/tiddlers/tests/test-backtranscludes.js index fe6d09706..cd089df94 100644 --- a/editions/test/tiddlers/tests/test-backtranscludes.js +++ b/editions/test/tiddlers/tests/test-backtranscludes.js @@ -22,6 +22,9 @@ describe('Backtranscludes and transclude filter tests', function() { it('should have no backtranscludes', function() { expect(wiki.filterTiddlers('TestIncoming +[backtranscludes[]]').join(',')).toBe(''); }); + it('should have no transcludes', function() { + expect(wiki.filterTiddlers('TestIncoming +[transcludes[]]').join(',')).toBe(''); + }); }); describe('A tiddler added to the wiki with a transclude to it', function() { @@ -38,6 +41,9 @@ describe('Backtranscludes and transclude filter tests', function() { it('should have a backtransclude', function() { expect(wiki.filterTiddlers('TestIncoming +[backtranscludes[]]').join(',')).toBe('TestOutgoing'); }); + it('should have a transclude', function() { + expect(wiki.filterTiddlers('TestOutgoing +[transcludes[]]').join(',')).toBe('TestIncoming'); + }); }); describe('A tiddler transclude with template will still use the tiddler as result.', function() { @@ -182,35 +188,52 @@ describe('Backtranscludes and transclude filter tests', function() { }); }); - describe('ignore self transclusion', function() { + describe('include implicit self transclusion', function() { var wiki = new $tw.Wiki(); wiki.addTiddler({ title: 'TestOutgoing', - text: "{{!!created}}\n\nA transclude to {{!!title}}"}); + text: "{{!!created}}\n\nAn implicit self-referential transclude to <$transclude $field='created'/> and <$transclude field='created'/>"}); it('should have no transclude', function() { - expect(wiki.filterTiddlers('TestOutgoing +[transcludes[]]').join(',')).toBe(''); + expect(wiki.filterTiddlers('TestOutgoing +[transcludes[]]').join(',')).toBe('TestOutgoing'); }); it('should have no back transcludes', function() { - expect(wiki.filterTiddlers('TestOutgoing +[backtranscludes[]]').join(',')).toBe(''); + expect(wiki.filterTiddlers('TestOutgoing +[backtranscludes[]]').join(',')).toBe('TestOutgoing'); }); }); - describe('recognize soft transclusion defined by widget', function() { + describe('include explicit self transclusion', function() { var wiki = new $tw.Wiki(); wiki.addTiddler({ title: 'TestOutgoing', - text: "<$tiddler tiddler='TestIncoming'><$transclude $tiddler />"}); + text: "{{TestOutgoing!!created}}\n\n<$transclude $tiddler='TestOutgoing' $field='created'/> and <$transclude tiddler='TestOutgoing' field='created'/>"}); + + it('should have no transclude', function() { + expect(wiki.filterTiddlers('TestOutgoing +[transcludes[]]').join(',')).toBe('TestOutgoing'); + }); + + it('should have no back transcludes', function() { + expect(wiki.filterTiddlers('TestOutgoing +[backtranscludes[]]').join(',')).toBe('TestOutgoing'); + }); + }); + + describe('recognize transclusion defined by widget', function() { + var wiki = new $tw.Wiki(); + + wiki.addTiddler({ + title: 'TestOutgoing', + text: "<$tiddler tiddler='TestIncoming'><$transclude $tiddler />\n\n<$transclude tiddler='TiddlyWiki Pre-release'/>"}); it('should have a transclude', function() { - expect(wiki.filterTiddlers('TestOutgoing +[transcludes[]]').join(',')).toBe('TestIncoming'); + expect(wiki.filterTiddlers('TestOutgoing +[transcludes[]]').join(',')).toBe('TestIncoming,TiddlyWiki Pre-release'); }); it('should have a back transclude', function() { expect(wiki.filterTiddlers('TestIncoming +[backtranscludes[]]').join(',')).toBe('TestOutgoing'); + expect(wiki.filterTiddlers('[[TiddlyWiki Pre-release]] +[backtranscludes[]]').join(',')).toBe('TestOutgoing'); }); }); });