From 447cd56da9db2ee169607f32923081ac47e78354 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Fri, 3 Jan 2020 10:40:35 +0000 Subject: [PATCH] Initial commit of freelinks plugin --- .../freelinks/config-Freelinks-Enable.tid | 2 + plugins/tiddlywiki/freelinks/macros-view.tid | 4 + plugins/tiddlywiki/freelinks/plain-text.js | 62 ++++++++ plugins/tiddlywiki/freelinks/plugin.info | 6 + plugins/tiddlywiki/freelinks/readme.tid | 12 ++ plugins/tiddlywiki/freelinks/settings.tid | 3 + plugins/tiddlywiki/freelinks/text.js | 148 ++++++++++++++++++ 7 files changed, 237 insertions(+) create mode 100644 plugins/tiddlywiki/freelinks/config-Freelinks-Enable.tid create mode 100644 plugins/tiddlywiki/freelinks/macros-view.tid create mode 100644 plugins/tiddlywiki/freelinks/plain-text.js create mode 100644 plugins/tiddlywiki/freelinks/plugin.info create mode 100644 plugins/tiddlywiki/freelinks/readme.tid create mode 100644 plugins/tiddlywiki/freelinks/settings.tid create mode 100755 plugins/tiddlywiki/freelinks/text.js diff --git a/plugins/tiddlywiki/freelinks/config-Freelinks-Enable.tid b/plugins/tiddlywiki/freelinks/config-Freelinks-Enable.tid new file mode 100644 index 000000000..dfbd771dc --- /dev/null +++ b/plugins/tiddlywiki/freelinks/config-Freelinks-Enable.tid @@ -0,0 +1,2 @@ +title: $:/config/Freelinks/Enable +text: yes diff --git a/plugins/tiddlywiki/freelinks/macros-view.tid b/plugins/tiddlywiki/freelinks/macros-view.tid new file mode 100644 index 000000000..20d85b088 --- /dev/null +++ b/plugins/tiddlywiki/freelinks/macros-view.tid @@ -0,0 +1,4 @@ +title: $:/plugins/tiddlywiki/freelinks/macros/view +tags: $:/tags/Macro/View + +<$set name="tv-freelinks" value={{$:/config/Freelinks/Enable}}/> diff --git a/plugins/tiddlywiki/freelinks/plain-text.js b/plugins/tiddlywiki/freelinks/plain-text.js new file mode 100644 index 000000000..2e7e0d707 --- /dev/null +++ b/plugins/tiddlywiki/freelinks/plain-text.js @@ -0,0 +1,62 @@ +/*\ +title: $:/core/modules/widgets/plain-text.js +type: application/javascript +module-type: widget + +A copy of the core text widget under a different name + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/widgets/widget.js").widget; + +var PlainTextNodeWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +PlainTextNodeWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +PlainTextNodeWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + var text = this.getAttribute("text",this.parseTreeNode.text || ""); + text = text.replace(/\r/mg,""); + var textNode = this.document.createTextNode(text); + parent.insertBefore(textNode,nextSibling); + this.domNodes.push(textNode); +}; + +/* +Compute the internal state of the widget +*/ +PlainTextNodeWidget.prototype.execute = function() { + // Nothing to do for a text node +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +PlainTextNodeWidget.prototype.refresh = function(changedTiddlers) { + var changedAttributes = this.computeAttributes(); + if(changedAttributes.text) { + this.refreshSelf(); + return true; + } else { + return false; + } +}; + +exports["plain-text"] = PlainTextNodeWidget; + +})(); diff --git a/plugins/tiddlywiki/freelinks/plugin.info b/plugins/tiddlywiki/freelinks/plugin.info new file mode 100644 index 000000000..300edf75e --- /dev/null +++ b/plugins/tiddlywiki/freelinks/plugin.info @@ -0,0 +1,6 @@ +{ + "title": "$:/plugins/tiddlywiki/freelinks", + "name": "Freelinks", + "description": "Freelinking of tiddler titles", + "list": "readme settings" +} diff --git a/plugins/tiddlywiki/freelinks/readme.tid b/plugins/tiddlywiki/freelinks/readme.tid new file mode 100644 index 000000000..6ea98963b --- /dev/null +++ b/plugins/tiddlywiki/freelinks/readme.tid @@ -0,0 +1,12 @@ +title: $:/plugins/tiddlywiki/freelinks/readme + +This plugin adds automatic generation of links to tiddler titles. + +''Note that automatic link generation can be very slow when there are a large number of tiddlers''. + +Freelinking is activated for runs of text that have the following variables set: + +* `tv-wikilinks` is NOT equal to `no` +* `tv-freelinks` is set to `yes` + +Within view templates, the variable `tv-freelinks` is automatically set to the content of $:/config/Freelinks/Enable, which can be set via the settings panel of this plugin. diff --git a/plugins/tiddlywiki/freelinks/settings.tid b/plugins/tiddlywiki/freelinks/settings.tid new file mode 100644 index 000000000..0b6cb247c --- /dev/null +++ b/plugins/tiddlywiki/freelinks/settings.tid @@ -0,0 +1,3 @@ +title: $:/plugins/tiddlywiki/freelinks/settings + +<$checkbox tiddler="$:/config/Freelinks/Enable" field="text" checked="yes" unchecked="no" default="no"> <$link to="$:/config/Freelinks/Enable">Enable freelinking within tiddler view templates diff --git a/plugins/tiddlywiki/freelinks/text.js b/plugins/tiddlywiki/freelinks/text.js new file mode 100755 index 000000000..8803c8cd2 --- /dev/null +++ b/plugins/tiddlywiki/freelinks/text.js @@ -0,0 +1,148 @@ +/*\ +title: $:/core/modules/widgets/text.js +type: application/javascript +module-type: widget + +An override of the core text widget that automatically linkifies the text + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +var Widget = require("$:/core/modules/widgets/widget.js").widget; + +var TextNodeWidget = function(parseTreeNode,options) { + this.initialise(parseTreeNode,options); +}; + +/* +Inherit from the base widget class +*/ +TextNodeWidget.prototype = new Widget(); + +/* +Render this widget into the DOM +*/ +TextNodeWidget.prototype.render = function(parent,nextSibling) { + this.parentDomNode = parent; + this.computeAttributes(); + this.execute(); + this.renderChildren(parent,nextSibling); +}; + +/* +Compute the internal state of the widget +*/ +TextNodeWidget.prototype.execute = function() { + var self = this; + // Get our parameters + var childParseTree = [{ + type: "plain-text", + text: this.getAttribute("text",this.parseTreeNode.text || "") + }]; + // Only process links if not disabled + if(this.getVariable("tv-wikilinks",{defaultValue:"yes"}).trim() !== "no" && this.getVariable("tv-freelinks",{defaultValue:"no"}).trim() === "yes") { + // Get the information about the current tiddler titles, and construct a regexp + this.tiddlerTitleInfo = this.wiki.getGlobalCache("tiddler-title-info",function() { + var titles = [], + reparts = [], + sortedTitles = self.wiki.allTitles().sort(function(a,b) { + var lenA = a.length, + lenB = b.length; + // First sort by length, so longer titles are first + if(lenA !== lenB) { + if(lenA < lenB) { + return +1; + } else { + return -1; + } + } else { + // Then sort alphabetically within titles of the same length + if(a < b) { + return -1; + } else if(a > b) { + return +1; + } else { + return 0; + } + } + }); + $tw.utils.each(sortedTitles,function(title) { + if(title.substring(0,3) !== "$:/") { + titles.push(title); + reparts.push("(\\b" + $tw.utils.escapeRegExp(title) + "\\b)"); + } + }); + return { + titles: titles, + regexp: new RegExp(reparts.join("|"),"") + }; + }); + // Repeatedly linkify + if(this.tiddlerTitleInfo.titles.length > 0) { + var index,text,match,matchEnd; + do { + index = childParseTree.length - 1; + text = childParseTree[index].text; + match = this.tiddlerTitleInfo.regexp.exec(text); + if(match) { + // Make a text node for any text before the match + if(match.index > 0) { + childParseTree[index].text = text.substring(0,match.index); + index += 1; + } + // Make a link node for the match + childParseTree[index] = { + type: "link", + attributes: { + to: {type: "string", value: match[0]} + }, + children: [{ + type: "plain-text", text: match[0] + }] + }; + index += 1; + // Make a text node for any text after the match + matchEnd = match.index + match[0].length; + if(matchEnd < text.length) { + childParseTree[index] = { + type: "plain-text", + text: text.substring(matchEnd) + }; + } + } + } while(match && childParseTree[childParseTree.length - 1].type === "plain-text"); + } + } + // Make the child widgets + this.makeChildWidgets(childParseTree); +}; + +/* +Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering +*/ +TextNodeWidget.prototype.refresh = function(changedTiddlers) { + var self = this, + changedAttributes = this.computeAttributes(), + titlesHaveChanged = false; + $tw.utils.each(changedTiddlers,function(change,title) { + if(change.isDeleted) { + titlesHaveChanged = true + } else { + titlesHaveChanged = titlesHaveChanged || !self.tiddlerTitleInfo || self.tiddlerTitleInfo.titles.indexOf(title) === -1; + } + }); + if(changedAttributes.text || titlesHaveChanged) { + this.refreshSelf(); + return true; + } else { + return false; + } +}; + +exports.text = TextNodeWidget; + +})();