OcttKB/Wiki-OcttKB/plugins/relink/$__plugins_flibbles_relink_...

197 lines
5.5 KiB
JavaScript

/*\
module-type: indexer
Indexes results from tiddler reference reports so we don't have to call them
so much.
\*/
"use strict";
var utils = require("./utils.js");
var TiddlerContext = utils.getContext('tiddler');
function Indexer(wiki) {
this.wiki = wiki;
};
Indexer.prototype.init = function() {
this.rebuild();
};
Indexer.prototype.rebuild = function() {
this.index = null;
this.backIndex = null;
this.contexts = Object.create(null);
this.changedTiddlers = undefined;
this.lastRelinks = Object.create(null);
};
Indexer.prototype.update = function(updateDescriptor) {
if (!this.index) {
return;
}
var title;
if (!this.changedTiddlers) {
this.changedTiddlers = Object.create(null);
}
if (updateDescriptor.old.exists) {
title = updateDescriptor.old.tiddler.fields.title;
this.changedTiddlers[title] = {deleted: true};
this._purge(title);
}
if (updateDescriptor['new'].exists) {
// If its the same tiddler as old, this overrides the 'deleted' entry
title = updateDescriptor['new'].tiddler.fields.title;
this.changedTiddlers[title] = {modified: true};
}
};
Indexer.prototype.lookup = function(title) {
this._upkeep();
return this.index[title];
};
Indexer.prototype.reverseLookup = function(title) {
this._upkeep();
return this.backIndex[title] || Object.create(null);
};
Indexer.prototype.relinkLookup = function(fromTitle, toTitle, options) {
this._upkeep();
var shortlist = undefined;
var lastRelink = this.lastRelinks[fromTitle];
if (lastRelink) {
if (lastRelink.to === toTitle) {
// We need to reintroduce the relink cache, where temporary info
// was stored.
options.cache = lastRelink.cache;
return lastRelink.results;
}
shortlist = buildShortlist(lastRelink);
}
var results = utils.getRelinkResults(this.wiki, fromTitle, toTitle, this.context, shortlist, options);
if (Object.keys(this.lastRelinks).length > 3) {
// The cache got a little large. wipe it clean.
this.lastRelinks = Object.create(null);
}
this.lastRelinks[fromTitle] = {
from: fromTitle,
results: results,
to: toTitle,
cache: options.cache,
maybeRelevant: Object.create(null)};
return results;
};
// Returns all tiddlers that don't have anything referencing it.
Indexer.prototype.orphans = function() {
this._upkeep();
var results = [];
for (var title in this.index) {
if (!this.backIndex[title]
|| Object.keys(this.backIndex[title]).length === 0) {
results.push(title);
}
}
return results;
};
Indexer.prototype._upkeep = function() {
var title;
if (this.changedTiddlers && (this.context.changed(this.changedTiddlers) || this.context.parent.changed(this.changedTiddlers))) {
// If global macro context or whitelist context changed, wipe all
this.rebuild();
}
if (!this.index) {
this.index = Object.create(null);
this.backIndex = Object.create(null);
this.context = utils.getWikiContext(this.wiki);
var titles = this.wiki.getRelinkableTitles();
for (var i = 0; i < titles.length; i++) {
this._populate(titles[i]);
};
} else if (this.changedTiddlers) {
// If there are cached changes, we apply them now.
for (title in this.contexts) {
var tiddlerContext = this.contexts[title];
if (tiddlerContext.changed(this.changedTiddlers)) {
this._purge(title);
this._populate(title);
this._decacheRelink(title);
// Wipe this change, so we don't risk updating it twice.
this.changedTiddlers[title] = undefined;
}
}
for (title in this.changedTiddlers) {
var change = this.changedTiddlers[title];
if (change && change.modified) {
this._purge(title);
this._populate(title);
this._decacheRelink(title);
}
}
this.changedTiddlers = undefined;
}
};
Indexer.prototype._purge = function(title) {
for (var entry in this.index[title]) {
delete this.backIndex[entry][title];
}
delete this.contexts[title];
delete this.index[title];
};
// This drops the cached relink results if unsanctioned tiddlers were changed
Indexer.prototype._decacheRelink = function(title) {
var tiddler = this.wiki.getTiddler(title);
for (var from in this.lastRelinks) {
var lastRelink = this.lastRelinks[from];
if (title !== from
&& title !== lastRelink.to
&& (!tiddler
|| !$tw.utils.hop(tiddler.fields, 'draft.of') // is a draft
|| tiddler.fields['draft.of'] !== from// draft of target
|| references(this.index[title], from))) { // draft references target
// This is not the draft of the last relinked title,
// so our cached results should be wiped.
lastRelink.maybeRelevant[title] = true;
// Force this cached relink to partially refresh when it comes time
lastRelink.to = undefined;
}
}
};
function references(list, item) {
return list !== undefined && list[item];
};
// Compiles a short list of tiddlers we need to check for a rename.
// This list will be much faster to relink again.
function buildShortlist(lastRelink) {
var shortlist = Object.keys(lastRelink.results);
for (var title in lastRelink.maybeRelevant) {
if (lastRelink.results[title] === undefined) {
shortlist.push(title);
}
}
return shortlist;
};
Indexer.prototype._populate = function(title) {
// Fetch the report for a title, and populate the indexes with result
var tiddlerContext = new TiddlerContext(this.wiki, this.context, title);
var references = utils.getTiddlerRelinkReferences(this.wiki, title, tiddlerContext);
this.index[title] = references;
if (tiddlerContext.hasImports()) {
this.contexts[title] = tiddlerContext;
}
for (var ref in references) {
this.backIndex[ref] = this.backIndex[ref] || Object.create(null);
this.backIndex[ref][title] = references[ref];
}
};
exports.RelinkIndexer = Indexer;