mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-12-25 01:20:30 +00:00
Add backlinks indexer (#4421)
* Add tests for backlinks * Add backlinks indexer * Use backlinks indexer in getTiddlerBacklinks if available * Extract link extraction into its own method This way we can provide an arbitrary parse tree, rather than just a title, which will allow us to compare lists of outgoing links between versions of a single tiddler * Use new extractLinks method in backlinks indexer ...rather than copy-pasting the implementation * Remove ES6-isms TiddlyWiki needs to work with browsers that only support ES5
This commit is contained in:
parent
2d9a9703cb
commit
ae04a425c0
86
core/modules/indexers/backlinks-index.js
Normal file
86
core/modules/indexers/backlinks-index.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*\
|
||||||
|
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;
|
||||||
|
|
||||||
|
})();
|
@ -415,14 +415,9 @@ exports.forEachTiddler = function(/* [options,]callback */) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Return an array of tiddler titles that are directly linked from the specified tiddler
|
Return an array of tiddler titles that are directly linked within the given parse tree
|
||||||
*/
|
*/
|
||||||
exports.getTiddlerLinks = function(title) {
|
exports.extractLinks = function(parseTreeRoot) {
|
||||||
var self = this;
|
|
||||||
// We'll cache the links so they only get computed if the tiddler changes
|
|
||||||
return this.getCacheForTiddler(title,"links",function() {
|
|
||||||
// Parse the tiddler
|
|
||||||
var parser = self.parseTiddler(title);
|
|
||||||
// Count up the links
|
// Count up the links
|
||||||
var links = [],
|
var links = [],
|
||||||
checkParseTree = function(parseTree) {
|
checkParseTree = function(parseTree) {
|
||||||
@ -439,10 +434,23 @@ exports.getTiddlerLinks = function(title) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if(parser) {
|
checkParseTree(parseTreeRoot);
|
||||||
checkParseTree(parser.tree);
|
|
||||||
}
|
|
||||||
return links;
|
return links;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return an array of tiddler titles that are directly linked from the specified tiddler
|
||||||
|
*/
|
||||||
|
exports.getTiddlerLinks = function(title) {
|
||||||
|
var self = this;
|
||||||
|
// We'll cache the links so they only get computed if the tiddler changes
|
||||||
|
return this.getCacheForTiddler(title,"links",function() {
|
||||||
|
// Parse the tiddler
|
||||||
|
var parser = self.parseTiddler(title);
|
||||||
|
if(parser) {
|
||||||
|
return self.extractLinks(parser.tree);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -451,6 +459,10 @@ Return an array of tiddler titles that link to the specified tiddler
|
|||||||
*/
|
*/
|
||||||
exports.getTiddlerBacklinks = function(targetTitle) {
|
exports.getTiddlerBacklinks = function(targetTitle) {
|
||||||
var self = this,
|
var self = this,
|
||||||
|
backlinksIndexer = this.getIndexer("BacklinksIndexer"),
|
||||||
|
backlinks = backlinksIndexer && backlinksIndexer.lookup(targetTitle);
|
||||||
|
|
||||||
|
if(!backlinks) {
|
||||||
backlinks = [];
|
backlinks = [];
|
||||||
this.forEachTiddler(function(title,tiddler) {
|
this.forEachTiddler(function(title,tiddler) {
|
||||||
var links = self.getTiddlerLinks(title);
|
var links = self.getTiddlerLinks(title);
|
||||||
@ -458,6 +470,7 @@ exports.getTiddlerBacklinks = function(targetTitle) {
|
|||||||
backlinks.push(title);
|
backlinks.push(title);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return backlinks;
|
return backlinks;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
132
editions/test/tiddlers/tests/test-backlinks.js
Normal file
132
editions/test/tiddlers/tests/test-backlinks.js
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/*\
|
||||||
|
title: test-backlinks.js
|
||||||
|
type: application/javascript
|
||||||
|
tags: [[$:/tags/test-spec]]
|
||||||
|
|
||||||
|
Tests the backlinks mechanism.
|
||||||
|
|
||||||
|
\*/
|
||||||
|
(function(){
|
||||||
|
/*jslint node: true, browser: true */
|
||||||
|
/*global $tw: false */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe('Backlinks tests', function() {
|
||||||
|
describe('a tiddler with no links to it', function() {
|
||||||
|
var wiki = new $tw.Wiki();
|
||||||
|
|
||||||
|
wiki.addTiddler({
|
||||||
|
title: 'TestIncoming',
|
||||||
|
text: ''});
|
||||||
|
|
||||||
|
it('should have no backlinks', function() {
|
||||||
|
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('A tiddler added to the wiki with a link to it', function() {
|
||||||
|
var wiki = new $tw.Wiki();
|
||||||
|
|
||||||
|
wiki.addTiddler({
|
||||||
|
title: 'TestIncoming',
|
||||||
|
text: ''});
|
||||||
|
|
||||||
|
wiki.addTiddler({
|
||||||
|
title: 'TestOutgoing',
|
||||||
|
text: 'A link to [[TestIncoming]]'});
|
||||||
|
|
||||||
|
it('should have a backlink', function() {
|
||||||
|
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('TestOutgoing');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('A tiddler that has a link added to it later', function() {
|
||||||
|
it('should have an additional backlink', function() {
|
||||||
|
var wiki = new $tw.Wiki();
|
||||||
|
|
||||||
|
wiki.addTiddler({
|
||||||
|
title: 'TestIncoming',
|
||||||
|
text: ''});
|
||||||
|
|
||||||
|
wiki.addTiddler({
|
||||||
|
title: 'TestOutgoing',
|
||||||
|
text: 'A link to [[TestIncoming]]'});
|
||||||
|
|
||||||
|
wiki.addTiddler({
|
||||||
|
title: 'TestOutgoing2',
|
||||||
|
text: 'Nothing yet!'});
|
||||||
|
|
||||||
|
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('TestOutgoing');
|
||||||
|
|
||||||
|
wiki.addTiddler({
|
||||||
|
title: 'TestOutgoing2',
|
||||||
|
text: 'Updated with link to [[TestIncoming]]'});
|
||||||
|
|
||||||
|
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('TestOutgoing,TestOutgoing2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('A tiddler that has a link remove from it later', function() {
|
||||||
|
var wiki = new $tw.Wiki();
|
||||||
|
|
||||||
|
wiki.addTiddler({
|
||||||
|
title: 'TestIncoming',
|
||||||
|
text: ''});
|
||||||
|
|
||||||
|
wiki.addTiddler({
|
||||||
|
title: 'TestOutgoing',
|
||||||
|
text: 'A link to [[TestIncoming]]'});
|
||||||
|
|
||||||
|
it('should have one fewer backlink', function() {
|
||||||
|
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('TestOutgoing');
|
||||||
|
|
||||||
|
wiki.addTiddler({
|
||||||
|
title: 'TestOutgoing',
|
||||||
|
text: 'No link to ~TestIncoming'});
|
||||||
|
|
||||||
|
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('A tiddler linking to another that gets renamed', function() {
|
||||||
|
var wiki = new $tw.Wiki();
|
||||||
|
|
||||||
|
wiki.addTiddler({
|
||||||
|
title: 'TestIncoming',
|
||||||
|
text: ''});
|
||||||
|
|
||||||
|
wiki.addTiddler({
|
||||||
|
title: 'TestOutgoing',
|
||||||
|
text: 'A link to [[TestIncoming]]'});
|
||||||
|
|
||||||
|
it('should have its name changed in the backlinks', function() {
|
||||||
|
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('TestOutgoing');
|
||||||
|
|
||||||
|
wiki.renameTiddler('TestOutgoing', 'TestExtroverted');
|
||||||
|
|
||||||
|
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('TestExtroverted');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('A tiddler linking to another that gets deleted', function() {
|
||||||
|
var wiki = new $tw.Wiki();
|
||||||
|
|
||||||
|
wiki.addTiddler({
|
||||||
|
title: 'TestIncoming',
|
||||||
|
text: ''});
|
||||||
|
|
||||||
|
wiki.addTiddler({
|
||||||
|
title: 'TestOutgoing',
|
||||||
|
text: 'A link to [[TestIncoming]]'});
|
||||||
|
|
||||||
|
it('should be removed from backlinks', function() {
|
||||||
|
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('TestOutgoing');
|
||||||
|
|
||||||
|
wiki.deleteTiddler('TestOutgoing');
|
||||||
|
|
||||||
|
expect(wiki.filterTiddlers('TestIncoming +[backlinks[]]').join(',')).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
Loading…
Reference in New Issue
Block a user