2023-08-28 15:12:15 +00:00
|
|
|
import { index } from "$sb/syscalls.ts";
|
2023-07-28 13:20:56 +00:00
|
|
|
import { findNodeOfType, traverseTree } from "$sb/lib/tree.ts";
|
|
|
|
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
|
|
|
|
import { extractAttributes } from "$sb/lib/attribute.ts";
|
|
|
|
import { IndexTreeEvent, QueryProviderEvent } from "$sb/app_event.ts";
|
|
|
|
import { applyQuery } from "$sb/lib/query.ts";
|
2023-07-30 17:31:04 +00:00
|
|
|
import { resolvePath } from "$sb/lib/resolve.ts";
|
2023-08-01 19:35:19 +00:00
|
|
|
import { indexAttributes } from "./attributes.ts";
|
2023-07-28 13:20:56 +00:00
|
|
|
|
|
|
|
// Key space:
|
|
|
|
// l:toPage:pos => {name: pageName, inDirective: true, asTemplate: true}
|
|
|
|
|
|
|
|
export const backlinkPrefix = `l:`;
|
|
|
|
|
|
|
|
export type BacklinkEntry = {
|
|
|
|
name: string;
|
|
|
|
alias?: string;
|
|
|
|
inDirective?: boolean;
|
|
|
|
asTemplate?: boolean;
|
|
|
|
};
|
|
|
|
|
|
|
|
export async function indexLinks({ name, tree }: IndexTreeEvent) {
|
|
|
|
const backLinks: { key: string; value: BacklinkEntry }[] = [];
|
|
|
|
// [[Style Links]]
|
|
|
|
// console.log("Now indexing links for", name);
|
|
|
|
const pageMeta = await extractFrontmatter(tree);
|
|
|
|
const toplevelAttributes = await extractAttributes(tree, false);
|
|
|
|
if (
|
|
|
|
Object.keys(pageMeta).length > 0 ||
|
|
|
|
Object.keys(toplevelAttributes).length > 0
|
|
|
|
) {
|
|
|
|
for (const [k, v] of Object.entries(toplevelAttributes)) {
|
|
|
|
pageMeta[k] = v;
|
|
|
|
}
|
|
|
|
// Don't index meta data starting with $
|
2023-08-23 17:08:21 +00:00
|
|
|
for (const key in pageMeta) {
|
|
|
|
if (key.startsWith("$")) {
|
|
|
|
delete pageMeta[key];
|
|
|
|
}
|
|
|
|
}
|
2023-07-28 13:20:56 +00:00
|
|
|
// console.log("Extracted page meta data", pageMeta);
|
|
|
|
await index.set(name, "meta:", pageMeta);
|
|
|
|
}
|
|
|
|
|
2023-08-01 19:35:19 +00:00
|
|
|
await indexAttributes(name, pageMeta, "page");
|
|
|
|
|
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-07-28 13:20:56 +00:00
|
|
|
backLinks.push({
|
|
|
|
key: `${backlinkPrefix}${pageRefName}:${pageRef.from! + 2}`,
|
|
|
|
value: { name, asTemplate: true },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
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-07-28 13:20:56 +00:00
|
|
|
backLinks.push({
|
|
|
|
key: `${backlinkPrefix}${pageRefName}:${
|
|
|
|
n.from! + match.index! + 2
|
|
|
|
}`,
|
|
|
|
value: { name, asTemplate: true },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (n.type === "DirectiveStop") {
|
|
|
|
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-07-28 13:20:56 +00:00
|
|
|
if (toPage.includes("@")) {
|
|
|
|
toPage = toPage.split("@")[0];
|
|
|
|
}
|
|
|
|
const blEntry: BacklinkEntry = { name };
|
|
|
|
if (directiveDepth > 0) {
|
|
|
|
blEntry.inDirective = true;
|
|
|
|
}
|
|
|
|
if (wikiLinkAlias) {
|
|
|
|
blEntry.alias = wikiLinkAlias.children![0].text!;
|
|
|
|
}
|
|
|
|
backLinks.push({
|
|
|
|
key: `${backlinkPrefix}${toPage}:${wikiLinkPage.from}`,
|
|
|
|
value: blEntry,
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
2023-07-30 17:31:04 +00:00
|
|
|
// console.log("Found", backLinks, "page link(s)");
|
2023-07-28 13:20:56 +00:00
|
|
|
await index.batchSet(name, backLinks);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function linkQueryProvider({
|
|
|
|
query,
|
|
|
|
pageName,
|
|
|
|
}: QueryProviderEvent): Promise<any[]> {
|
|
|
|
const links: any[] = [];
|
|
|
|
for (
|
|
|
|
const { value: blEntry, key } of await index.queryPrefix(
|
|
|
|
`${backlinkPrefix}${pageName}:`,
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
const [, , pos] = key.split(":"); // Key: l:page:pos
|
|
|
|
if (!blEntry.inDirective) {
|
|
|
|
blEntry.inDirective = false;
|
|
|
|
}
|
|
|
|
if (!blEntry.asTemplate) {
|
|
|
|
blEntry.asTemplate = false;
|
|
|
|
}
|
|
|
|
links.push({ ...blEntry, pos });
|
|
|
|
}
|
|
|
|
return applyQuery(query, links);
|
|
|
|
}
|
|
|
|
|
|
|
|
type BackLinkPage = {
|
|
|
|
page: string;
|
|
|
|
pos: number;
|
|
|
|
};
|
|
|
|
|
|
|
|
export async function getBackLinks(pageName: string): Promise<BackLinkPage[]> {
|
|
|
|
const allBackLinks = await index.queryPrefix(
|
|
|
|
`${backlinkPrefix}${pageName}:`,
|
|
|
|
);
|
|
|
|
const pagesToUpdate: BackLinkPage[] = [];
|
|
|
|
for (const { key, value: { name } } of allBackLinks) {
|
|
|
|
const keyParts = key.split(":");
|
|
|
|
pagesToUpdate.push({
|
|
|
|
page: name,
|
|
|
|
pos: +keyParts[keyParts.length - 1],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return pagesToUpdate;
|
|
|
|
}
|