Skip to content

Commit

Permalink
Basic Backtranscludes filter (#6081)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
linonetwo committed Feb 18, 2024
1 parent 96e11fa commit 36fc817
Show file tree
Hide file tree
Showing 10 changed files with 421 additions and 88 deletions.
26 changes: 26 additions & 0 deletions core/modules/filters/backtranscludes.js
Original file line number Diff line number Diff line change
@@ -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;
};

})();
26 changes: 26 additions & 0 deletions core/modules/filters/transcludes.js
Original file line number Diff line number Diff line change
@@ -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();
};

})();
119 changes: 119 additions & 0 deletions core/modules/indexers/back-indexer.js
Original file line number Diff line number Diff line change
@@ -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;
86 changes: 0 additions & 86 deletions core/modules/indexers/backlinks-index.js

This file was deleted.

66 changes: 64 additions & 2 deletions core/modules/wiki.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand All @@ -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<parseTree.length; t++) {
var parseTreeNode = parseTree[t];
if(parseTreeNode.type === "transclude" && parseTreeNode.attributes.$tiddler && parseTreeNode.attributes.$tiddler.type === "string") {
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(parentNode && parentNode.type === "tiddler" && parentNode.attributes.tiddler && parentNode.attributes.tiddler.type === "string") {
value = parentNode.attributes.tiddler.value;
} else {
value = parseTreeNode.attributes.$tiddler.value;
}
if(transcludes.indexOf(value) === -1) {
transcludes.push(value);
}
}
if(parseTreeNode.children) {
checkParseTree(parseTreeNode.children, parseTreeNode);
}
}
};
checkParseTree(parseTreeRoot);
return transcludes;
};


/*
Return an array of tiddler titles that are transcluded from the specified tiddler
*/
exports.getTiddlerTranscludes = function(title) {
var self = this;
// We'll cache the transcludes so they only get computed if the tiddler changes
return this.getCacheForTiddler(title,"transcludes",function() {
// Parse the tiddler
var parser = self.parseTiddler(title);
if(parser) {
return self.extractTranscludes(parser.tree);
}
return [];
});
};

/*
Return an array of tiddler titles that transclude to the specified tiddler
*/
exports.getTiddlerBacktranscludes = function(targetTitle) {
var self = this,
backIndexer = this.getIndexer("BackIndexer"),
backtranscludes = backIndexer && backIndexer.subIndexers.transclude.lookup(targetTitle);

if(!backtranscludes) {
backtranscludes = [];
}
return backtranscludes;
};

/*
Return a hashmap of tiddler titles that are referenced but not defined. Each value is the number of times the missing tiddler is referenced
*/
Expand Down
Loading

0 comments on commit 36fc817

Please sign in to comment.