From 36fc8170a4a86a74970d8ce5f57bcd791239f0d0 Mon Sep 17 00:00:00 2001 From: lin onetwo Date: Sun, 18 Feb 2024 17:14:23 +0800 Subject: [PATCH] Basic Backtranscludes filter (#6081) * feat: add transcludes and backtranscludes filter and its relying indexer * feat: add test about backtranscludes * docs: add doc about transcludes and backtranscludes Operator * refactor: merge backlinks and backtranscludes indexer * fix: test not executed * fix: latest transclude use $tiddler instead of tiddler * feat: A tiddler transclude with template will still use the tiddler as result. * docs: wrong comment --- core/modules/filters/backtranscludes.js | 26 +++ core/modules/filters/transcludes.js | 26 +++ core/modules/indexers/back-indexer.js | 119 ++++++++++++++ core/modules/indexers/backlinks-index.js | 86 ---------- core/modules/wiki.js | 66 +++++++- .../tiddlers/tests/test-backtranscludes.js | 148 ++++++++++++++++++ .../tiddlers/filters/backtranscludes.tid | 13 ++ .../filters/examples/backtransclude.tid | 7 + .../tiddlers/filters/examples/transclude.tid | 5 + .../tw5.com/tiddlers/filters/transcludes.tid | 13 ++ 10 files changed, 421 insertions(+), 88 deletions(-) create mode 100644 core/modules/filters/backtranscludes.js create mode 100644 core/modules/filters/transcludes.js create mode 100644 core/modules/indexers/back-indexer.js delete mode 100644 core/modules/indexers/backlinks-index.js create mode 100644 editions/test/tiddlers/tests/test-backtranscludes.js create mode 100644 editions/tw5.com/tiddlers/filters/backtranscludes.tid create mode 100644 editions/tw5.com/tiddlers/filters/examples/backtransclude.tid create mode 100644 editions/tw5.com/tiddlers/filters/examples/transclude.tid create mode 100644 editions/tw5.com/tiddlers/filters/transcludes.tid diff --git a/core/modules/filters/backtranscludes.js b/core/modules/filters/backtranscludes.js new file mode 100644 index 000000000..7d4215073 --- /dev/null +++ b/core/modules/filters/backtranscludes.js @@ -0,0 +1,26 @@ +/*\ +title: $:/core/modules/filters/backtranscludes.js +type: application/javascript +module-type: filteroperator + +Filter operator for returning all the backtranscludes from a tiddler + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter function +*/ +exports.backtranscludes = function(source,operator,options) { + var results = []; + source(function(tiddler,title) { + $tw.utils.pushTop(results,options.wiki.getTiddlerBacktranscludes(title)); + }); + return results; +}; + +})(); diff --git a/core/modules/filters/transcludes.js b/core/modules/filters/transcludes.js new file mode 100644 index 000000000..bd618296b --- /dev/null +++ b/core/modules/filters/transcludes.js @@ -0,0 +1,26 @@ +/*\ +title: $:/core/modules/filters/transcludes.js +type: application/javascript +module-type: filteroperator + +Filter operator for returning all the transcludes from a tiddler + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +/* +Export our filter function +*/ +exports.transcludes = function(source,operator,options) { + var results = new $tw.utils.LinkedList(); + source(function(tiddler,title) { + results.pushTop(options.wiki.getTiddlerTranscludes(title)); + }); + return results.toArray(); +}; + +})(); diff --git a/core/modules/indexers/back-indexer.js b/core/modules/indexers/back-indexer.js new file mode 100644 index 000000000..609d62bfc --- /dev/null +++ b/core/modules/indexers/back-indexer.js @@ -0,0 +1,119 @@ +/*\ +title: $:/core/modules/indexers/back-indexer.js +type: application/javascript +module-type: indexer + +By parsing the tiddler text, indexes the tiddlers' back links, back transclusions, block level back links. + +\*/ +function BackIndexer(wiki) { + this.wiki = wiki; +} + +BackIndexer.prototype.init = function() { + this.subIndexers = { + link: new BackSubIndexer(this,"extractLinks"), + transclude: new BackSubIndexer(this,"extractTranscludes"), + }; +}; + +BackIndexer.prototype.rebuild = function() { + $tw.utils.each(this.subIndexers,function(subIndexer) { + subIndexer.rebuild(); + }); +}; + +BackIndexer.prototype.update = function(updateDescriptor) { + $tw.utils.each(this.subIndexers,function(subIndexer) { + subIndexer.update(updateDescriptor); + }); +}; +function BackSubIndexer(indexer,extractor) { + this.wiki = indexer.wiki; + this.indexer = indexer; + this.extractor = extractor; + /** + * { + * [target title, e.g. tiddler title being linked to]: + * { + * [source title, e.g. tiddler title that has link syntax in its text]: true + * } + * } + */ + this.index = null; +} + +BackSubIndexer.prototype.init = function() { + // lazy init until first lookup + this.index = null; +} + +BackSubIndexer.prototype._init = function() { + this.index = Object.create(null); + var self = this; + this.wiki.forEachTiddler(function(sourceTitle,tiddler) { + var newTargets = self._getTarget(tiddler); + $tw.utils.each(newTargets, function(target) { + if(!self.index[target]) { + self.index[target] = Object.create(null); + } + self.index[target][sourceTitle] = true; + }); + }); +} + +BackSubIndexer.prototype.rebuild = function() { + this.index = null; +} + +/* +* Get things that is being referenced in the text, e.g. tiddler names in the link syntax. +*/ +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 []; +} + +BackSubIndexer.prototype.update = function(updateDescriptor) { + // lazy init/update until first lookup + if(!this.index) { + return; + } + var newTargets = [], + oldTargets = [], + self = this; + if(updateDescriptor.old.exists) { + oldTargets = this._getTarget(updateDescriptor.old.tiddler); + } + if(updateDescriptor.new.exists) { + newTargets = this._getTarget(updateDescriptor.new.tiddler); + } + + $tw.utils.each(oldTargets,function(target) { + if(self.index[target]) { + delete self.index[target][updateDescriptor.old.tiddler.fields.title]; + } + }); + $tw.utils.each(newTargets,function(target) { + if(!self.index[target]) { + self.index[target] = Object.create(null); + } + self.index[target][updateDescriptor.new.tiddler.fields.title] = true; + }); +} + +BackSubIndexer.prototype.lookup = function(title) { + if(!this.index) { + this._init(); + } + if(this.index[title]) { + return Object.keys(this.index[title]); + } else { + return []; + } +} + +exports.BackIndexer = BackIndexer; diff --git a/core/modules/indexers/backlinks-index.js b/core/modules/indexers/backlinks-index.js deleted file mode 100644 index 5902e2829..000000000 --- a/core/modules/indexers/backlinks-index.js +++ /dev/null @@ -1,86 +0,0 @@ -/*\ -title: $:/core/modules/indexers/backlinks-indexer.js -type: application/javascript -module-type: indexer - -Indexes the tiddlers' backlinks - -\*/ -(function(){ - -/*jslint node: true, browser: true */ -/*global modules: false */ -"use strict"; - - -function BacklinksIndexer(wiki) { - this.wiki = wiki; -} - -BacklinksIndexer.prototype.init = function() { - this.index = null; -} - -BacklinksIndexer.prototype.rebuild = function() { - this.index = null; -} - -BacklinksIndexer.prototype._getLinks = function(tiddler) { - var parser = this.wiki.parseText(tiddler.fields.type, tiddler.fields.text, {}); - if(parser) { - return this.wiki.extractLinks(parser.tree); - } - return []; -} - -BacklinksIndexer.prototype.update = function(updateDescriptor) { - if(!this.index) { - return; - } - var newLinks = [], - oldLinks = [], - self = this; - if(updateDescriptor.old.exists) { - oldLinks = this._getLinks(updateDescriptor.old.tiddler); - } - if(updateDescriptor.new.exists) { - newLinks = this._getLinks(updateDescriptor.new.tiddler); - } - - $tw.utils.each(oldLinks,function(link) { - if(self.index[link]) { - delete self.index[link][updateDescriptor.old.tiddler.fields.title]; - } - }); - $tw.utils.each(newLinks,function(link) { - if(!self.index[link]) { - self.index[link] = Object.create(null); - } - self.index[link][updateDescriptor.new.tiddler.fields.title] = true; - }); -} - -BacklinksIndexer.prototype.lookup = function(title) { - if(!this.index) { - this.index = Object.create(null); - var self = this; - this.wiki.forEachTiddler(function(title,tiddler) { - var links = self._getLinks(tiddler); - $tw.utils.each(links, function(link) { - if(!self.index[link]) { - self.index[link] = Object.create(null); - } - self.index[link][title] = true; - }); - }); - } - if(this.index[title]) { - return Object.keys(this.index[title]); - } else { - return []; - } -} - -exports.BacklinksIndexer = BacklinksIndexer; - -})(); diff --git a/core/modules/wiki.js b/core/modules/wiki.js index 430c46466..96e40a708 100755 --- a/core/modules/wiki.js +++ b/core/modules/wiki.js @@ -534,8 +534,8 @@ Return an array of tiddler titles that link to the specified tiddler */ exports.getTiddlerBacklinks = function(targetTitle) { var self = this, - backlinksIndexer = this.getIndexer("BacklinksIndexer"), - backlinks = backlinksIndexer && backlinksIndexer.lookup(targetTitle); + backIndexer = this.getIndexer("BackIndexer"), + backlinks = backIndexer && backIndexer.subIndexers.link.lookup(targetTitle); if(!backlinks) { backlinks = []; @@ -549,6 +549,68 @@ exports.getTiddlerBacklinks = function(targetTitle) { return backlinks; }; + +/* +Return an array of tiddler titles that are directly transcluded within the given parse tree + */ +exports.extractTranscludes = function(parseTreeRoot) { + // Count up the transcludes + var transcludes = [], + checkParseTree = function(parseTree, parentNode) { + for(var t=0; t> diff --git a/editions/tw5.com/tiddlers/filters/examples/backtransclude.tid b/editions/tw5.com/tiddlers/filters/examples/backtransclude.tid new file mode 100644 index 000000000..e70648576 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/examples/backtransclude.tid @@ -0,0 +1,7 @@ +tags: [[backtranscludes Operator]] [[Operator Examples]] +title: backtranscludes Operator (Examples) +type: text/vnd.tiddlywiki + +<<.operator-example 1 "[[Motovun Jack.jpg]backtranscludes[]]">> + +<<.operator-example 2 "[[Transclusion]backtranscludes[]]">> diff --git a/editions/tw5.com/tiddlers/filters/examples/transclude.tid b/editions/tw5.com/tiddlers/filters/examples/transclude.tid new file mode 100644 index 000000000..eefea2a57 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/examples/transclude.tid @@ -0,0 +1,5 @@ +tags: [[transcludes Operator]] [[Operator Examples]] +title: transcludes Operator (Examples) +type: text/vnd.tiddlywiki + +<<.operator-example 1 "[[Images in WikiText]transcludes[]]">> diff --git a/editions/tw5.com/tiddlers/filters/transcludes.tid b/editions/tw5.com/tiddlers/filters/transcludes.tid new file mode 100644 index 000000000..29b90eb54 --- /dev/null +++ b/editions/tw5.com/tiddlers/filters/transcludes.tid @@ -0,0 +1,13 @@ +created: 20211002204500000 +tags: [[Filter Operators]] [[Common Operators]] +title: transcludes Operator +type: text/vnd.tiddlywiki +caption: transcludes +op-purpose: find the titles linked to by each input title +op-input: a [[selection of titles|Title Selection]] +op-parameter: none +op-output: the titles to which the input tiddlers [[transcludes|Transclusion]] + +Each input title is processed in turn. The corresponding tiddler's list of transcludes is generated, in the order in which they appear in the tiddler's text, and [[dominantly appended|Dominant Append]] to the operator's overall output. + +<<.operator-examples "transcludes">>