Add annotated links, and corresponding filter
This commit is contained in:
parent
fa9bfa07a0
commit
cb83f260f7
|
@ -0,0 +1,45 @@
|
||||||
|
/*\
|
||||||
|
title: $:/core/modules/filters/annotatedlinks.js
|
||||||
|
type: application/javascript
|
||||||
|
module-type: filteroperator
|
||||||
|
|
||||||
|
Filter operator for returning all the links from a tiddler with a given annotation
|
||||||
|
|
||||||
|
\*/
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
/*jslint node: true, browser: true */
|
||||||
|
/*global $tw: false */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function parseAnnotations(annotationstring){
|
||||||
|
var annotations = {}
|
||||||
|
if (annotationstring.length > 0){
|
||||||
|
annotationstring.split(',').forEach(function(part){
|
||||||
|
if (part.includes('=')){
|
||||||
|
let subparts = part.split('=', 2);
|
||||||
|
let key = subparts[0],
|
||||||
|
value = subparts[1];
|
||||||
|
annotations[key] = value;
|
||||||
|
}else{
|
||||||
|
annotations[part] = "*";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Export our filter function
|
||||||
|
*/
|
||||||
|
exports.annotatedlinks = function(source,operator,options) {
|
||||||
|
var results = new $tw.utils.LinkedList();
|
||||||
|
source(function(tiddler,title) {
|
||||||
|
var annotations = parseAnnotations(operator.operands[0]);
|
||||||
|
results.pushTop(options.wiki.getTiddlerAnnotatedLinks(title, annotations));
|
||||||
|
});
|
||||||
|
return results.makeTiddlerIterator(options.wiki);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
})();
|
|
@ -0,0 +1,147 @@
|
||||||
|
/*\
|
||||||
|
title: $:/core/modules/parsers/wikiparser/rules/prettyannotatedlink.js
|
||||||
|
type: application/javascript
|
||||||
|
module-type: wikirule
|
||||||
|
|
||||||
|
Wiki text inline rule for pretty links with annotations. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
[link attribute="value" [Link description|TiddlerTitle]]
|
||||||
|
[link attribute="value" [TiddlerTitle]]
|
||||||
|
```
|
||||||
|
|
||||||
|
\*/
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
/*jslint node: true, browser: true */
|
||||||
|
/*global $tw: false */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
exports.name = "prettyannotatedlink";
|
||||||
|
exports.types = {inline: true};
|
||||||
|
|
||||||
|
exports.init = function(parser) {
|
||||||
|
this.parser = parser;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.findNextMatch = function(startPos) {
|
||||||
|
// Find the next tag
|
||||||
|
this.nextAnnotatedLink = this.findNextAnnotatedLink(this.parser.source,startPos);
|
||||||
|
return this.nextAnnotatedLink ? this.nextAnnotatedLink.start : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.parse = function() {
|
||||||
|
// Move past the match
|
||||||
|
this.parser.pos = this.nextAnnotatedLink.end;
|
||||||
|
if($tw.utils.isLinkExternal(this.nextAnnotatedLink.attributes.to)) {
|
||||||
|
return [{
|
||||||
|
type: "element",
|
||||||
|
tag: "a",
|
||||||
|
attributes: {
|
||||||
|
href: {type: "string", value: link},
|
||||||
|
"class": {type: "string", value: "tc-tiddlylink-external"},
|
||||||
|
target: {type: "string", value: "_blank"},
|
||||||
|
rel: {type: "string", value: "noopener noreferrer"},
|
||||||
|
...this.nextAnnotatedLink.attributes
|
||||||
|
},
|
||||||
|
children: [{
|
||||||
|
type: "text", text: this.nextAnnotatedLink.text
|
||||||
|
}]
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
return [{
|
||||||
|
type: "link",
|
||||||
|
attributes: this.nextAnnotatedLink.attributes,
|
||||||
|
children: [{
|
||||||
|
type: "text", text: this.nextAnnotatedLink.text
|
||||||
|
}]
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Find the next link from the current position
|
||||||
|
*/
|
||||||
|
exports.findNextAnnotatedLink = function(source,pos) {
|
||||||
|
// A regexp for finding candidate HTML tags
|
||||||
|
var reLookahead = /(\[link)/g;
|
||||||
|
// Find the next candidate
|
||||||
|
reLookahead.lastIndex = pos;
|
||||||
|
var match = reLookahead.exec(source);
|
||||||
|
while(match) {
|
||||||
|
// Try to parse the candidate as a tag
|
||||||
|
var tag = this.parseAnnotatedLink(source,match.index);
|
||||||
|
// Return success
|
||||||
|
if(tag) {
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
// Look for the next match
|
||||||
|
reLookahead.lastIndex = match.index + 1;
|
||||||
|
match = reLookahead.exec(source);
|
||||||
|
}
|
||||||
|
// Failed
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Look for an link at the specified position. Returns null if not found, otherwise returns {type: "link", attributes: [], isSelfClosing:, start:, end:,}
|
||||||
|
*/
|
||||||
|
exports.parseAnnotatedLink = function(source,pos) {
|
||||||
|
var token,
|
||||||
|
node = {
|
||||||
|
type: "link",
|
||||||
|
start: pos,
|
||||||
|
attributes: {},
|
||||||
|
text: ""
|
||||||
|
};
|
||||||
|
// Skip whitespace
|
||||||
|
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||||
|
// Look for the `[img`
|
||||||
|
token = $tw.utils.parseTokenString(source,pos,"[link");
|
||||||
|
if(!token) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
pos = token.end;
|
||||||
|
// Skip whitespace
|
||||||
|
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||||
|
// Process attributes
|
||||||
|
if(source.charAt(pos) !== "[") {
|
||||||
|
var attribute = $tw.utils.parseAttribute(source,pos);
|
||||||
|
while(attribute) {
|
||||||
|
node.attributes[attribute.name] = attribute;
|
||||||
|
pos = attribute.end;
|
||||||
|
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||||
|
if(source.charAt(pos) !== "[") {
|
||||||
|
// Get the next attribute
|
||||||
|
attribute = $tw.utils.parseAttribute(source,pos);
|
||||||
|
} else {
|
||||||
|
attribute = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Skip whitespace
|
||||||
|
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||||
|
// Look for the `[` after the attributes
|
||||||
|
token = $tw.utils.parseTokenString(source,pos,"[");
|
||||||
|
if(!token) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
pos = token.end;
|
||||||
|
// Skip whitespace
|
||||||
|
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||||
|
// Get the source up to the terminating `]]`
|
||||||
|
token = $tw.utils.parseTokenRegExp(source,pos,/(.*?)(?:\|(.*?))?\]\]/g);
|
||||||
|
if(!token) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
pos = token.end;
|
||||||
|
var text = token.match[1],
|
||||||
|
link = token.match[2] || text;
|
||||||
|
node.attributes.to = {type: "string", value: link};
|
||||||
|
node.text = text;
|
||||||
|
// Update the end position
|
||||||
|
node.end = pos;
|
||||||
|
return node;
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
|
@ -549,6 +549,114 @@ exports.getTiddlerBacklinks = function(targetTitle) {
|
||||||
return backlinks;
|
return backlinks;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return an array of tiddler titles that are directly linked within the given parse tree
|
||||||
|
annotations: This could either be:
|
||||||
|
* a string: to look for the availability of a single annotation
|
||||||
|
* an array: for any of the listed annotations
|
||||||
|
* an object: defining a number of key value pairs, where either
|
||||||
|
* the value is function which must return true
|
||||||
|
* the value is '*' which allows any value
|
||||||
|
* the value given matches exactly the annotation
|
||||||
|
*/
|
||||||
|
exports.extractAnnotatedLinks = function(parseTreeRoot, annotations) {
|
||||||
|
var annotation_list = {};
|
||||||
|
switch(typeof(annotations)){
|
||||||
|
case "string":
|
||||||
|
if (annotations.length==0){
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
let key = annotations;
|
||||||
|
annotation_list[key] = '*';
|
||||||
|
break;
|
||||||
|
case "object":
|
||||||
|
let original_annotations = annotations;
|
||||||
|
if (Array.isArray(annotations)){
|
||||||
|
annotations.forEach((key) => {
|
||||||
|
annotation_list[key] = '*';
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
annotation_list = annotations;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Count up the links
|
||||||
|
var links = [],
|
||||||
|
checkParseTree = function(parseTree) {
|
||||||
|
for(var t=0; t<parseTree.length; t++) {
|
||||||
|
var parseTreeNode = parseTree[t];
|
||||||
|
if(parseTreeNode.type === "link" && parseTreeNode.attributes.to && parseTreeNode.attributes.to.type === "string") {
|
||||||
|
var add = false;
|
||||||
|
for (const [key, value] of Object.entries(annotation_list)) {
|
||||||
|
console.log(key, value, parseTreeNode.attributes);
|
||||||
|
add = add || ((
|
||||||
|
key in parseTreeNode.attributes
|
||||||
|
) && (
|
||||||
|
value == '*'
|
||||||
|
|| value == parseTreeNode.attributes[key].value
|
||||||
|
|| (
|
||||||
|
typeof(value) == "function"
|
||||||
|
&& value(parseTreeNode.attributes[key].value)
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (add){
|
||||||
|
var value = parseTreeNode.attributes.to.value;
|
||||||
|
if(links.indexOf(value) === -1) {
|
||||||
|
links.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(parseTreeNode.children) {
|
||||||
|
checkParseTree(parseTreeNode.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
checkParseTree(parseTreeRoot);
|
||||||
|
return links;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return an array of tiddler titles that are directly linked from the specified tiddler
|
||||||
|
*/
|
||||||
|
exports.getTiddlerAnnotatedLinks = function(title, annotations) {
|
||||||
|
var self = this;
|
||||||
|
var parser = self.parseTiddler(title);
|
||||||
|
if(parser) {
|
||||||
|
return self.extractAnnotatedLinks(parser.tree, annotations);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
// We'll cache the links so they only get computed if the tiddler changes
|
||||||
|
return this.getCacheForTiddler(title,"annotatedlinks",function() {
|
||||||
|
// Parse the tiddler
|
||||||
|
var parser = self.parseTiddler(title);
|
||||||
|
if(parser) {
|
||||||
|
return self.extractAnnotatedLinks(parser.tree, annotations);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return an array of tiddler titles that link to the specified tiddler
|
||||||
|
*/
|
||||||
|
exports.getTiddlerAnnotatedBacklinks = function(targetTitle) {
|
||||||
|
var self = this,
|
||||||
|
backlinksIndexer = this.getIndexer("BacklinksIndexer"),
|
||||||
|
backlinks = backlinksIndexer && backlinksIndexer.lookup(targetTitle);
|
||||||
|
|
||||||
|
if(!backlinks) {
|
||||||
|
backlinks = [];
|
||||||
|
this.forEachTiddler(function(title,tiddler) {
|
||||||
|
var links = self.getTiddlerAnnotatedLinks(title);
|
||||||
|
if(links.indexOf(targetTitle) !== -1) {
|
||||||
|
backlinks.push(title);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return backlinks;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Return a hashmap of tiddler titles that are referenced but not defined. Each value is the number of times the missing tiddler is referenced
|
Return a hashmap of tiddler titles that are referenced but not defined. Each value is the number of times the missing tiddler is referenced
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue