2024-02-29 22:23:05 +08:00
|
|
|
import { findNodeOfType, renderToText, traverseTree } from "$sb/lib/tree.ts";
|
2024-05-28 02:33:41 +08:00
|
|
|
import { IndexTreeEvent, ObjectValue } from "$sb/types.ts";
|
|
|
|
import { isLocalPath, resolvePath } from "$sb/lib/resolve.ts";
|
2023-10-03 20:16:33 +08:00
|
|
|
import { indexObjects, queryObjects } from "./api.ts";
|
2024-01-11 20:20:50 +08:00
|
|
|
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
|
|
|
|
import { updateITags } from "$sb/lib/tags.ts";
|
2024-02-09 04:00:45 +08:00
|
|
|
import { parsePageRef } from "$sb/lib/page_ref.ts";
|
2024-03-02 21:48:02 +08:00
|
|
|
import { extractSnippetAroundIndex } from "./snippet_extractor.ts";
|
2024-05-28 02:33:41 +08:00
|
|
|
import { mdLinkRegex, wikiLinkRegex } from "$common/markdown_parser/parser.ts";
|
2023-07-28 21:20:56 +08:00
|
|
|
|
2024-05-28 02:33:41 +08:00
|
|
|
export type LinkObject = ObjectValue<
|
|
|
|
{
|
|
|
|
//Page Link
|
|
|
|
// The page the link points to
|
|
|
|
toPage: string;
|
|
|
|
// The page the link occurs in
|
|
|
|
page: string;
|
|
|
|
pos: number;
|
|
|
|
snippet: string;
|
|
|
|
alias?: string;
|
|
|
|
asTemplate: boolean;
|
|
|
|
toFile?: never;
|
|
|
|
} | {
|
|
|
|
// Attachment Link
|
|
|
|
// The file the link points to
|
|
|
|
toFile: string;
|
|
|
|
// The page the link occurs in
|
|
|
|
page: string;
|
|
|
|
pos: number;
|
|
|
|
snippet: string;
|
|
|
|
alias?: string;
|
|
|
|
asTemplate: boolean;
|
|
|
|
toPage?: never;
|
|
|
|
}
|
|
|
|
>;
|
2023-07-28 21:20:56 +08:00
|
|
|
|
2023-10-03 20:16:33 +08:00
|
|
|
export async function indexLinks({ name, tree }: IndexTreeEvent) {
|
|
|
|
const links: ObjectValue<LinkObject>[] = [];
|
2024-01-11 20:20:50 +08:00
|
|
|
const frontmatter = await extractFrontmatter(tree);
|
2023-10-03 20:16:33 +08:00
|
|
|
const pageText = renderToText(tree);
|
2023-08-02 03:35:19 +08:00
|
|
|
|
2023-07-28 21:20:56 +08:00
|
|
|
traverseTree(tree, (n): boolean => {
|
2024-05-28 02:33:41 +08:00
|
|
|
// Index [[WikiLinks]]
|
2023-07-28 21:20:56 +08:00
|
|
|
if (n.type === "WikiLink") {
|
|
|
|
const wikiLinkPage = findNodeOfType(n, "WikiLinkPage")!;
|
|
|
|
const wikiLinkAlias = findNodeOfType(n, "WikiLinkAlias");
|
2024-05-28 02:33:41 +08:00
|
|
|
const url = resolvePath(name, "/" + wikiLinkPage.children![0].text!);
|
2023-10-03 20:16:33 +08:00
|
|
|
const pos = wikiLinkPage.from!;
|
2024-05-28 02:33:41 +08:00
|
|
|
|
|
|
|
const link: any = {
|
2023-10-03 20:16:33 +08:00
|
|
|
ref: `${name}@${pos}`,
|
2024-01-11 20:20:50 +08:00
|
|
|
tag: "link",
|
2024-03-02 21:48:02 +08:00
|
|
|
snippet: extractSnippetAroundIndex(pageText, pos),
|
2023-10-03 20:16:33 +08:00
|
|
|
pos,
|
|
|
|
page: name,
|
|
|
|
asTemplate: false,
|
|
|
|
};
|
2024-05-28 02:33:41 +08:00
|
|
|
// Assume link is to an attachment if it has
|
|
|
|
// an extension, to a page otherwise
|
|
|
|
if (/\.[a-zA-Z0-9]+$/.test(url)) {
|
|
|
|
link.toFile = url;
|
|
|
|
} else {
|
|
|
|
link.toPage = parsePageRef(url).page;
|
|
|
|
}
|
2023-07-28 21:20:56 +08:00
|
|
|
if (wikiLinkAlias) {
|
2023-10-03 20:16:33 +08:00
|
|
|
link.alias = wikiLinkAlias.children![0].text!;
|
2023-07-28 21:20:56 +08:00
|
|
|
}
|
2024-01-11 20:20:50 +08:00
|
|
|
updateITags(link, frontmatter);
|
2023-10-03 20:16:33 +08:00
|
|
|
links.push(link);
|
2023-07-28 21:20:56 +08:00
|
|
|
return true;
|
|
|
|
}
|
2023-11-19 19:18:16 +08:00
|
|
|
|
2024-05-28 02:33:41 +08:00
|
|
|
// Also index [Markdown style]() links
|
|
|
|
if (n.type === "URL") {
|
|
|
|
const linkNode = findNodeOfType(n, "URL")!;
|
|
|
|
if (!linkNode) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const text = /\[(?<title>[^\]]*)\]\((?<url>.+)\)/
|
|
|
|
.exec(renderToText(linkNode.parent));
|
|
|
|
if (!text) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
let [/* fullMatch */, alias, url] = text;
|
|
|
|
|
|
|
|
// Check if local link
|
|
|
|
if (!isLocalPath(url)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const pos = linkNode.from!;
|
|
|
|
url = resolvePath(name, decodeURI(url));
|
|
|
|
|
|
|
|
const link: any = {
|
|
|
|
ref: `${name}@${pos}`,
|
|
|
|
tag: "link",
|
|
|
|
snippet: extractSnippetAroundIndex(pageText, pos),
|
|
|
|
pos,
|
|
|
|
page: name,
|
|
|
|
asTemplate: false,
|
|
|
|
};
|
|
|
|
// Assume link is to an attachment if it has
|
|
|
|
// an extension, to a page otherwise
|
|
|
|
if (/\.[a-zA-Z0-9]+$/.test(url)) {
|
|
|
|
link.toFile = url;
|
|
|
|
} else {
|
|
|
|
link.toPage = parsePageRef(url).page;
|
|
|
|
}
|
|
|
|
if (alias) {
|
|
|
|
link.alias = alias;
|
|
|
|
}
|
|
|
|
updateITags(link, frontmatter);
|
|
|
|
links.push(link);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-11-19 19:18:16 +08: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!;
|
2024-05-28 02:33:41 +08:00
|
|
|
const wikiLinkMatches = code.matchAll(wikiLinkRegex);
|
|
|
|
for (const match of wikiLinkMatches) {
|
|
|
|
const [_fullMatch, firstMark, url, alias, _lastMark] = match;
|
|
|
|
const pos = codeText.from! + match.index! + firstMark.length;
|
|
|
|
const link: any = {
|
|
|
|
ref: `${name}@${pos}`,
|
|
|
|
tag: "link",
|
|
|
|
page: name,
|
|
|
|
snippet: extractSnippetAroundIndex(pageText, pos),
|
|
|
|
pos: pos,
|
|
|
|
asTemplate: true,
|
|
|
|
};
|
|
|
|
// Assume link is to an attachment if it has
|
|
|
|
// an extension, to a page otherwise
|
|
|
|
if (/\.[a-zA-Z0-9]+$/.test(url)) {
|
|
|
|
link.toFile = resolvePath(name, "/" + url);
|
|
|
|
} else {
|
|
|
|
link.toPage = resolvePath(name, "/" + parsePageRef(url).page);
|
|
|
|
}
|
|
|
|
if (alias) {
|
|
|
|
link.alias = alias;
|
|
|
|
}
|
|
|
|
updateITags(link, frontmatter);
|
|
|
|
links.push(link);
|
|
|
|
}
|
|
|
|
const mdLinkMatches = code.matchAll(mdLinkRegex);
|
|
|
|
for (const match of mdLinkMatches) {
|
|
|
|
const [_fullMatch, alias, url] = match;
|
|
|
|
const pos = codeText.from! + match.index! + 1;
|
|
|
|
const link: any = {
|
2023-11-19 19:18:16 +08:00
|
|
|
ref: `${name}@${pos}`,
|
2024-01-11 20:20:50 +08:00
|
|
|
tag: "link",
|
2023-11-19 19:18:16 +08:00
|
|
|
page: name,
|
2024-03-02 21:48:02 +08:00
|
|
|
snippet: extractSnippetAroundIndex(pageText, pos),
|
2023-11-19 19:18:16 +08:00
|
|
|
pos: pos,
|
|
|
|
asTemplate: true,
|
2024-01-11 20:20:50 +08:00
|
|
|
};
|
2024-05-28 02:33:41 +08:00
|
|
|
if (/\.[a-zA-Z0-9]+$/.test(url)) {
|
|
|
|
link.toFile = resolvePath(name, url);
|
|
|
|
} else {
|
|
|
|
link.toPage = resolvePath(name, parsePageRef(url).page);
|
|
|
|
}
|
|
|
|
if (alias) {
|
|
|
|
link.alias = alias;
|
|
|
|
}
|
2024-01-11 20:20:50 +08:00
|
|
|
updateITags(link, frontmatter);
|
|
|
|
links.push(link);
|
2023-11-19 19:18:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-07-28 21:20:56 +08:00
|
|
|
return false;
|
|
|
|
});
|
2023-11-19 19:18:16 +08:00
|
|
|
// console.log("Found", links, "page link(s)");
|
2023-10-03 20:16:33 +08:00
|
|
|
await indexObjects(name, links);
|
2023-07-28 21:20:56 +08:00
|
|
|
}
|
|
|
|
|
2023-10-03 20:16:33 +08:00
|
|
|
export async function getBackLinks(
|
2024-05-28 02:33:41 +08:00
|
|
|
name: string,
|
2023-10-03 20:16:33 +08:00
|
|
|
): Promise<LinkObject[]> {
|
|
|
|
return (await queryObjects<LinkObject>("link", {
|
2024-05-28 02:33:41 +08:00
|
|
|
filter: ["or", ["=", ["attr", "toPage"], ["string", name]], ["=", [
|
|
|
|
"attr",
|
|
|
|
"toFile",
|
|
|
|
], ["string", name]]],
|
2023-10-03 20:16:33 +08:00
|
|
|
}));
|
2023-07-28 21:20:56 +08:00
|
|
|
}
|