/*\ module-type: library Utility methods for relink. \*/ var macroFilter = "[[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]"; /**This works nearly identically to $tw.modules.getModulesByTypeAsHashmap * except that this also takes care of migrating V1 relink modules. */ exports.getModulesByTypeAsHashmap = function(moduleType, nameField) { var results = Object.create(null); $tw.modules.forEachModuleOfType(moduleType, function(title, module) { var key = module[nameField]; if (key !== undefined) { results[key] = module; } else { for (var entry in module) { results[entry] = { relink: module[entry], report: function() {}}; results[entry][nameField] = entry; } } }); return results; }; exports.getTiddlerRelinkReferences = function(wiki, title, context) { var tiddler = wiki.getTiddler(title), references = Object.create(null), options = {settings: context, wiki: wiki}; if (tiddler) { try { for (var relinker in getRelinkOperators()) { getRelinkOperators()[relinker].report(tiddler, function(title, blurb) { references[title] = references[title] || []; references[title].push(blurb || ''); }, options); } } catch (e) { if (e.message) { e.message = e.message + "\nWhen reporting '" + title + "' Relink references"; } throw e; } } return references; }; /** Returns a pair like this, * { title: {field: entry, ... }, ... } */ exports.getRelinkResults = function(wiki, fromTitle, toTitle, context, tiddlerList, options) { options = options || {}; options.wiki = options.wiki || wiki; fromTitle = (fromTitle || "").trim(); toTitle = (toTitle || "").trim(); var changeList = Object.create(null); if(fromTitle && toTitle !== undefined) { if (tiddlerList === undefined) { tiddlerList = wiki.getRelinkableTitles(); } for (var i = 0; i < tiddlerList.length; i++) { var title = tiddlerList[i]; var tiddler = wiki.getTiddler(title); if(tiddler) { try { var entries = Object.create(null), operators = getRelinkOperators(); options.settings = new Contexts.tiddler(wiki, context, title); for (var operation in operators) { operators[operation].relink(tiddler, fromTitle, toTitle, entries, options); } for (var field in entries) { // So long as there is one key, // add it to the change list. if (tiddler.fields["plugin-type"]) { // We never change plugins, even if they have links changeList[title] = {}; changeList[title][field] = {impossible: true}; } else { changeList[title] = entries; } break; } } catch (e) { // Should we test for instanceof Error instead?: yes // Does that work in the testing environment?: no if (e.message) { e.message = e.message + "\nWhen relinking '" + title + "'"; } throw e; } } } } return changeList; }; var Contexts = $tw.modules.applyMethods('relinkcontext'); exports.getContext = function(name) { return Contexts[name]; }; exports.getWikiContext = function(wiki) { // This gives a fresh context every time. It is up to the indexer or // the cache to preserve those contexts for as long as needed. var whitelist = new Contexts.whitelist(wiki); return new Contexts.import(wiki, whitelist, macroFilter); }; /** Returns the Relink indexer, or a dummy object which pretends to be one. */ exports.getIndexer = function(wiki) { if (!wiki._relink_indexer) { wiki._relink_indexer = (wiki.getIndexer && wiki.getIndexer("RelinkIndexer")) || new (require('$:/plugins/flibbles/relink/js/utils/backupIndexer.js'))(wiki); } return wiki._relink_indexer; }; /**Relinking supports a cache that persists throughout a whole relink op. * This is because the Tiddlywiki caches may get wiped multiple times * throughout the course of a relink. */ exports.getCacheForRun = function(options, cacheName, initializer) { options.cache = options.cache || Object.create(null); if (!$tw.utils.hop(options.cache, cacheName)) { options.cache[cacheName] = initializer(); } return options.cache[cacheName]; }; /**Returns a specific relinker. * This is useful for wikitext rules which need to parse a filter or a list */ exports.getType = function(name) { var Handler = getFieldTypes()[name]; return Handler ? new Handler() : undefined; }; exports.getTypes = function() { // We don't return fieldTypes, because we don't want it modified, // and we need to filter out legacy names. var rtn = Object.create(null); for (var type in getFieldTypes()) { var typeObject = getFieldTypes()[type]; rtn[typeObject.typeName] = typeObject; } return rtn; }; exports.getDefaultType = function(wiki) { var tiddler = wiki.getTiddler("$:/config/flibbles/relink/settings/default-type"); var defaultType = tiddler && tiddler.fields.text; // make sure the default actually exists, otherwise default return fieldTypes[defaultType] ? defaultType : "title"; }; exports.touchModifyField = function(wiki) { var tiddler = wiki.getTiddler("$:/config/flibbles/relink/touch-modify"); return tiddler && tiddler.fields.text.trim() === "yes"; }; /**Given some text, and a param or attribute within that text, this returns * what type of quotation that attribute is using. * * param: An object in the form {end:, ...} */ exports.determineQuote = function(text, param) { var pos = param.end-1; if (text[pos] === "'") { return "'"; } if (text[pos] === '"') { if (text.substr(pos-2, 3) === '"""') { return '"""'; } else { return '"'; } } if (text.substr(pos-1,2) === ']]' && text.substr((pos-param.value.length)-3, 2) === '[[') { return "[["; } return ''; }; var fieldTypes; function getFieldTypes() { if (!fieldTypes) { fieldTypes = Object.create(null); $tw.modules.forEachModuleOfType("relinkfieldtype", function(title, exports) { function NewType() {}; NewType.prototype = exports; NewType.typeName = exports.name; fieldTypes[exports.name] = NewType; // For legacy, if the NewType doesn't have a report method, we add one if (!exports.report) { exports.report = function() {}; } // Also for legacy, some of the field types can go by other names if (exports.aliases) { $tw.utils.each(exports.aliases, function(alias) { fieldTypes[alias] = NewType; }); } }); } return fieldTypes; } var relinkOperators; function getRelinkOperators() { if (!relinkOperators) { relinkOperators = exports.getModulesByTypeAsHashmap('relinkoperator', 'name'); } return relinkOperators; };