This commit is contained in:
Matthias Bilger 2024-04-25 23:42:05 +08:00 committed by GitHub
commit 9ad0247436
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 333 additions and 0 deletions

View File

@ -0,0 +1,46 @@
/*\
title: $:/core/modules/filters/annotatedbacklinks.js
type: application/javascript
module-type: filteroperator
Filter operator for returning all the back 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.annotatedbacklinks = function(source,operator,options) {
var results = new $tw.utils.LinkedList();
source(function(tiddler,title) {
var annotations = parseAnnotations(operator.operands[0]);
console.log(annotations);
results.pushTop(options.wiki.getTiddlerAnnotatedBacklinks(title, annotations));
});
return results.makeTiddlerIterator(options.wiki);
};
})();

View File

@ -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);
};
})();

View File

@ -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;
};
})();

View File

@ -611,6 +611,101 @@ exports.getTiddlerBacktranscludes = function(targetTitle) {
return backtranscludes;
};
/*
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];
console.log(parseTreeNode);
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 [];
};
/*
Return an array of tiddler titles that link to the specified tiddler
*/
exports.getTiddlerAnnotatedBacklinks = function(targetTitle, annotations) {
var self = this;
var backlinks = [];
this.forEachTiddler(function(title,tiddler) {
var links = self.getTiddlerAnnotatedLinks(title, annotations);
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
*/