2023-10-03 12:16:33 +00:00
|
|
|
import { findNodeOfType, renderToText, traverseTree } from "$sb/lib/tree.ts";
|
|
|
|
import { IndexTreeEvent } from "$sb/app_event.ts";
|
2023-07-30 17:31:04 +00:00
|
|
|
import { resolvePath } from "$sb/lib/resolve.ts";
|
2023-10-03 12:16:33 +00:00
|
|
|
import { indexObjects, queryObjects } from "./api.ts";
|
|
|
|
import { ObjectValue } from "$sb/types.ts";
|
2023-07-28 13:20:56 +00:00
|
|
|
|
2023-11-19 11:18:16 +00:00
|
|
|
const pageRefRegex = /\[\[([^\]]+)\]\]/g;
|
|
|
|
|
2023-10-03 12:16:33 +00:00
|
|
|
export type LinkObject = {
|
|
|
|
ref: string;
|
|
|
|
tags: string[];
|
|
|
|
// The page the link points to
|
|
|
|
toPage: string;
|
|
|
|
// The page the link occurs in
|
|
|
|
page: string;
|
|
|
|
pos: number;
|
|
|
|
snippet: string;
|
2023-07-28 13:20:56 +00:00
|
|
|
alias?: string;
|
2023-10-03 12:16:33 +00:00
|
|
|
inDirective: boolean;
|
|
|
|
asTemplate: boolean;
|
2023-07-28 13:20:56 +00:00
|
|
|
};
|
|
|
|
|
2023-10-03 12:16:33 +00:00
|
|
|
export function extractSnippet(text: string, pos: number): string {
|
|
|
|
let prefix = "";
|
|
|
|
for (let i = pos - 1; i > 0; i--) {
|
|
|
|
if (text[i] === "\n") {
|
|
|
|
break;
|
2023-07-28 13:20:56 +00:00
|
|
|
}
|
2023-10-03 12:16:33 +00:00
|
|
|
prefix = text[i] + prefix;
|
|
|
|
if (prefix.length > 25) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let suffix = "";
|
|
|
|
for (let i = pos; i < text.length; i++) {
|
|
|
|
if (text[i] === "\n") {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
suffix += text[i];
|
|
|
|
if (suffix.length > 25) {
|
|
|
|
break;
|
2023-08-23 17:08:21 +00:00
|
|
|
}
|
2023-07-28 13:20:56 +00:00
|
|
|
}
|
2023-10-03 12:16:33 +00:00
|
|
|
return prefix + suffix;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function indexLinks({ name, tree }: IndexTreeEvent) {
|
|
|
|
const links: ObjectValue<LinkObject>[] = [];
|
|
|
|
// [[Style Links]]
|
|
|
|
// console.log("Now indexing links for", name);
|
2023-07-28 13:20:56 +00:00
|
|
|
|
2023-10-03 12:16:33 +00:00
|
|
|
const pageText = renderToText(tree);
|
2023-08-01 19:35:19 +00:00
|
|
|
|
2023-07-28 13:20:56 +00:00
|
|
|
let directiveDepth = 0;
|
|
|
|
traverseTree(tree, (n): boolean => {
|
|
|
|
if (n.type === "DirectiveStart") {
|
|
|
|
directiveDepth++;
|
|
|
|
const pageRef = findNodeOfType(n, "PageRef")!;
|
|
|
|
if (pageRef) {
|
2023-07-30 17:31:04 +00:00
|
|
|
const pageRefName = resolvePath(
|
|
|
|
name,
|
|
|
|
pageRef.children![0].text!.slice(2, -2),
|
|
|
|
);
|
2023-10-03 12:16:33 +00:00
|
|
|
const pos = pageRef.from! + 2;
|
|
|
|
links.push({
|
|
|
|
ref: `${name}@${pos}`,
|
|
|
|
tags: ["link"],
|
|
|
|
toPage: pageRefName,
|
|
|
|
pos: pos,
|
|
|
|
snippet: extractSnippet(pageText, pos),
|
|
|
|
page: name,
|
|
|
|
asTemplate: true,
|
|
|
|
inDirective: false,
|
2023-07-28 13:20:56 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
const directiveText = n.children![0].text;
|
|
|
|
// #use or #import
|
|
|
|
if (directiveText) {
|
|
|
|
const match = /\[\[(.+)\]\]/.exec(directiveText);
|
|
|
|
if (match) {
|
2023-07-30 17:31:04 +00:00
|
|
|
const pageRefName = resolvePath(name, match[1]);
|
2023-10-03 12:16:33 +00:00
|
|
|
const pos = n.from! + match.index! + 2;
|
|
|
|
links.push({
|
|
|
|
ref: `${name}@${pos}`,
|
|
|
|
tags: ["link"],
|
|
|
|
toPage: pageRefName,
|
|
|
|
page: name,
|
|
|
|
snippet: extractSnippet(pageText, pos),
|
|
|
|
pos: pos,
|
|
|
|
asTemplate: true,
|
|
|
|
inDirective: false,
|
2023-07-28 13:20:56 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2023-10-03 12:16:33 +00:00
|
|
|
if (n.type === "DirectiveEnd") {
|
2023-07-28 13:20:56 +00:00
|
|
|
directiveDepth--;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (n.type === "WikiLink") {
|
|
|
|
const wikiLinkPage = findNodeOfType(n, "WikiLinkPage")!;
|
|
|
|
const wikiLinkAlias = findNodeOfType(n, "WikiLinkAlias");
|
2023-07-30 17:31:04 +00:00
|
|
|
let toPage = resolvePath(name, wikiLinkPage.children![0].text!);
|
2023-10-03 12:16:33 +00:00
|
|
|
const pos = wikiLinkPage.from!;
|
2023-11-03 11:01:33 +00:00
|
|
|
toPage = toPage.split(/[@$]/)[0];
|
2023-10-03 12:16:33 +00:00
|
|
|
const link: LinkObject = {
|
|
|
|
ref: `${name}@${pos}`,
|
|
|
|
tags: ["link"],
|
|
|
|
toPage: toPage,
|
|
|
|
snippet: extractSnippet(pageText, pos),
|
|
|
|
pos,
|
|
|
|
page: name,
|
|
|
|
inDirective: false,
|
|
|
|
asTemplate: false,
|
|
|
|
};
|
2023-07-28 13:20:56 +00:00
|
|
|
if (directiveDepth > 0) {
|
2023-10-03 12:16:33 +00:00
|
|
|
link.inDirective = true;
|
2023-07-28 13:20:56 +00:00
|
|
|
}
|
|
|
|
if (wikiLinkAlias) {
|
2023-10-03 12:16:33 +00:00
|
|
|
link.alias = wikiLinkAlias.children![0].text!;
|
2023-07-28 13:20:56 +00:00
|
|
|
}
|
2023-10-03 12:16:33 +00:00
|
|
|
links.push(link);
|
2023-07-28 13:20:56 +00:00
|
|
|
return true;
|
|
|
|
}
|
2023-11-19 11:18:16 +00:00
|
|
|
|
|
|
|
// Also index links used inside query and template fenced code blocks
|
|
|
|
if (n.type === "FencedCode") {
|
|
|
|
const codeInfo = findNodeOfType(n, "CodeInfo")!;
|
|
|
|
if (!codeInfo) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const codeLang = codeInfo.children![0].text!;
|
|
|
|
if (codeLang === "template" || codeLang === "query") {
|
|
|
|
const codeText = findNodeOfType(n, "CodeText");
|
|
|
|
if (!codeText) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const code = codeText.children![0].text!;
|
|
|
|
const matches = code.matchAll(pageRefRegex);
|
|
|
|
for (const match of matches) {
|
|
|
|
const pageRefName = resolvePath(name, match[1]);
|
|
|
|
const pos = codeText.from! + match.index! + 2;
|
|
|
|
links.push({
|
|
|
|
ref: `${name}@${pos}`,
|
|
|
|
tags: ["link"],
|
|
|
|
toPage: pageRefName,
|
|
|
|
page: name,
|
|
|
|
snippet: extractSnippet(pageText, pos),
|
|
|
|
pos: pos,
|
|
|
|
asTemplate: true,
|
|
|
|
inDirective: false,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-07-28 13:20:56 +00:00
|
|
|
return false;
|
|
|
|
});
|
2023-11-19 11:18:16 +00:00
|
|
|
// console.log("Found", links, "page link(s)");
|
2023-10-03 12:16:33 +00:00
|
|
|
await indexObjects(name, links);
|
2023-07-28 13:20:56 +00:00
|
|
|
}
|
|
|
|
|
2023-10-03 12:16:33 +00:00
|
|
|
export async function getBackLinks(
|
|
|
|
pageName: string,
|
|
|
|
): Promise<LinkObject[]> {
|
|
|
|
return (await queryObjects<LinkObject>("link", {
|
|
|
|
filter: ["=", ["attr", "toPage"], ["string", pageName]],
|
|
|
|
}));
|
2023-07-28 13:20:56 +00:00
|
|
|
}
|