1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-12-02 14:29:55 +00:00

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 5600a00cd8.

* Revert "refactor: move transcludes ast extractor to a file"

This reverts commit 61d5484f09.

* lint: use pushTop and remove space
This commit is contained in:
lin onetwo 2024-06-19 16:38:02 +08:00 committed by GitHub
parent 8eb08820ac
commit 741aef55e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 62 additions and 25 deletions

View File

@ -16,11 +16,11 @@ Filter operator for returning all the backtranscludes from a tiddler
Export our filter function Export our filter function
*/ */
exports.backtranscludes = function(source,operator,options) { exports.backtranscludes = function(source,operator,options) {
var results = []; var results = new $tw.utils.LinkedList();
source(function(tiddler,title) { 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);
}; };
})(); })();

View File

@ -20,7 +20,7 @@ exports.transcludes = function(source,operator,options) {
source(function(tiddler,title) { source(function(tiddler,title) {
results.pushTop(options.wiki.getTiddlerTranscludes(title)); results.pushTop(options.wiki.getTiddlerTranscludes(title));
}); });
return results.toArray(); return results.makeTiddlerIterator(options.wiki);
}; };
})(); })();

View File

@ -75,7 +75,7 @@ BackSubIndexer.prototype._getTarget = function(tiddler) {
} }
var parser = this.wiki.parseText(tiddler.fields.type, tiddler.fields.text, {}); var parser = this.wiki.parseText(tiddler.fields.type, tiddler.fields.text, {});
if(parser) { if(parser) {
return this.wiki[this.extractor](parser.tree); return this.wiki[this.extractor](parser.tree, tiddler.fields.title);
} }
return []; return [];
} }

View File

@ -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 // Count up the transcludes
var transcludes = [], var transcludes = [],
checkParseTree = function(parseTree, parentNode) { checkParseTree = function(parseTree, parentNode) {
for(var t=0; t<parseTree.length; t++) { for(var t=0; t<parseTree.length; t++) {
var parseTreeNode = parseTree[t]; var parseTreeNode = parseTree[t];
if(parseTreeNode.type === "transclude" && parseTreeNode.attributes.$tiddler && parseTreeNode.attributes.$tiddler.type === "string") { if(parseTreeNode.type === "transclude") {
if(parseTreeNode.attributes.$tiddler && parseTreeNode.attributes.$tiddler.type === "string") {
var value; var value;
// if it is Transclusion with Templates like `{{Index||$:/core/ui/TagTemplate}}`, the `$tiddler` will point to the template. We need to find the actual target tiddler from parent node // if it is Transclusion with Templates like `{{Index||$:/core/ui/TagTemplate}}`, the `$tiddler` will point to the template. We need to find the actual target tiddler from parent node
if(parentNode && parentNode.type === "tiddler" && parentNode.attributes.tiddler && parentNode.attributes.tiddler.type === "string") { if(parentNode && parentNode.type === "tiddler" && parentNode.attributes.tiddler && parentNode.attributes.tiddler.type === "string") {
value = parentNode.attributes.tiddler.value; // Empty value (like `{{!!field}}`) means self-referential transclusion.
value = parentNode.attributes.tiddler.value || title;
} else { } else {
value = parseTreeNode.attributes.$tiddler.value; value = parseTreeNode.attributes.$tiddler.value;
} }
if(transcludes.indexOf(value) === -1 && value !== undefined) { } else if(parseTreeNode.attributes.tiddler && parseTreeNode.attributes.tiddler.type === "string") {
transcludes.push(value); // Old transclude widget usage
value = parseTreeNode.attributes.tiddler.value;
} else if(parseTreeNode.attributes.$field && parseTreeNode.attributes.$field.type === "string") {
// Empty value (like `<$transclude $field='created'/>`) 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;
}
// Deduplicate the result.
if(value && transcludes.indexOf(value) === -1) {
$tw.utils.pushTop(transcludes,value);
} }
} }
if(parseTreeNode.children) { if(parseTreeNode.children) {
checkParseTree(parseTreeNode.children, parseTreeNode); checkParseTree(parseTreeNode.children,parseTreeNode);
} }
} }
}; };
@ -591,7 +604,8 @@ exports.getTiddlerTranscludes = function(title) {
// Parse the tiddler // Parse the tiddler
var parser = self.parseTiddler(title); var parser = self.parseTiddler(title);
if(parser) { if(parser) {
return self.extractTranscludes(parser.tree); // this will ignore self-referential transclusions from `title`
return self.extractTranscludes(parser.tree,title);
} }
return []; return [];
}); });

View File

@ -22,6 +22,9 @@ describe('Backtranscludes and transclude filter tests', function() {
it('should have no backtranscludes', function() { it('should have no backtranscludes', function() {
expect(wiki.filterTiddlers('TestIncoming +[backtranscludes[]]').join(',')).toBe(''); 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() { 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() { it('should have a backtransclude', function() {
expect(wiki.filterTiddlers('TestIncoming +[backtranscludes[]]').join(',')).toBe('TestOutgoing'); 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() { 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(); var wiki = new $tw.Wiki();
wiki.addTiddler({ wiki.addTiddler({
title: 'TestOutgoing', 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() { 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() { 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(); var wiki = new $tw.Wiki();
wiki.addTiddler({ wiki.addTiddler({
title: 'TestOutgoing', title: 'TestOutgoing',
text: "<$tiddler tiddler='TestIncoming'><$transclude $tiddler /></$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 /></$tiddler>\n\n<$transclude tiddler='TiddlyWiki Pre-release'/>"});
it('should have a transclude', function() { 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() { it('should have a back transclude', function() {
expect(wiki.filterTiddlers('TestIncoming +[backtranscludes[]]').join(',')).toBe('TestOutgoing'); expect(wiki.filterTiddlers('TestIncoming +[backtranscludes[]]').join(',')).toBe('TestOutgoing');
expect(wiki.filterTiddlers('[[TiddlyWiki Pre-release]] +[backtranscludes[]]').join(',')).toBe('TestOutgoing');
}); });
}); });
}); });