2024-07-09 15:22:08 +08:00
|
|
|
import { FunctionMap, Query } from "$sb/types.ts";
|
|
|
|
import { builtinFunctions } from "$lib/builtin_query_functions.ts";
|
|
|
|
import { System } from "$lib/plugos/system.ts";
|
2024-02-09 04:00:45 +08:00
|
|
|
import { LimitedMap } from "$lib/limited_map.ts";
|
2024-07-09 15:22:08 +08:00
|
|
|
import { parsePageRef } from "$sb/lib/page_ref.ts";
|
2024-07-10 14:50:54 +08:00
|
|
|
import { parse } from "$common/markdown_parser/parse_tree.ts";
|
|
|
|
import { extendedMarkdownLanguage } from "$common/markdown_parser/parser.ts";
|
|
|
|
import { traverseTree } from "$sb/lib/tree.ts";
|
|
|
|
import { renderToText } from "$sb/lib/tree.ts";
|
|
|
|
import { findNodeOfType } from "$sb/lib/tree.ts";
|
2024-07-23 20:53:36 +08:00
|
|
|
import { isFederationPath } from "$sb/lib/resolve.ts";
|
|
|
|
import { rewritePageRefs } from "$sb/lib/resolve.ts";
|
2024-01-21 02:16:07 +08:00
|
|
|
|
2024-02-03 02:19:07 +08:00
|
|
|
const pageCacheTtl = 10 * 1000; // 10s
|
|
|
|
|
2024-02-07 21:50:01 +08:00
|
|
|
export function buildQueryFunctions(
|
2024-05-28 02:33:41 +08:00
|
|
|
allKnownFiles: Set<string>,
|
2024-02-03 02:19:07 +08:00
|
|
|
system: System<any>,
|
2024-02-07 21:50:01 +08:00
|
|
|
): FunctionMap {
|
2024-02-03 02:19:07 +08:00
|
|
|
const pageCache = new LimitedMap<string>(10);
|
2024-02-07 21:50:01 +08:00
|
|
|
|
2024-01-21 02:16:07 +08:00
|
|
|
return {
|
|
|
|
...builtinFunctions,
|
2024-02-03 02:19:07 +08:00
|
|
|
pageExists(name: string) {
|
2024-02-26 13:46:03 +08:00
|
|
|
if (typeof name !== "string") {
|
|
|
|
throw new Error("pageExists(): name is not a string");
|
|
|
|
}
|
|
|
|
|
2024-01-21 02:16:07 +08:00
|
|
|
if (name.startsWith("!") || name.startsWith("{{")) {
|
|
|
|
// Let's assume federated pages exist, and ignore template variable ones
|
|
|
|
return true;
|
|
|
|
}
|
2024-06-24 12:21:57 +08:00
|
|
|
|
|
|
|
const flattendFiles = new Set(
|
|
|
|
[...allKnownFiles].flatMap((file) =>
|
|
|
|
(file.endsWith(".md")) ? [file.slice(0, -3)] : []
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
return flattendFiles.has(name);
|
2024-01-21 02:16:07 +08:00
|
|
|
},
|
2024-02-26 13:46:03 +08:00
|
|
|
async template(template: unknown, obj: unknown) {
|
|
|
|
if (typeof template !== "string") {
|
|
|
|
throw new Error("template(): template is not a string");
|
|
|
|
}
|
|
|
|
|
2024-02-03 02:19:07 +08:00
|
|
|
return (await system.invokeFunction("template.renderTemplate", [
|
|
|
|
template,
|
|
|
|
obj,
|
|
|
|
])).text;
|
|
|
|
},
|
|
|
|
// INTERNAL: Used for implementing the { query } syntax in expressions
|
|
|
|
$query(query: Query, variables: Record<string, any>) {
|
|
|
|
return system.invokeFunction("query.renderQuery", [
|
|
|
|
query,
|
|
|
|
variables,
|
|
|
|
]);
|
|
|
|
},
|
2024-07-10 14:50:54 +08:00
|
|
|
// INTERNAL: Used to rewrite task references in transclusions
|
2024-07-23 20:53:36 +08:00
|
|
|
rewriteRefsAndFederationLinks(template: string, page: string) {
|
2024-07-10 14:50:54 +08:00
|
|
|
// Rewrite all task references to include a page ref
|
|
|
|
// Parse template into a tree
|
|
|
|
const tree = parse(extendedMarkdownLanguage, template);
|
2024-07-23 20:53:36 +08:00
|
|
|
// Find tasks and rewrite them
|
2024-07-10 14:50:54 +08:00
|
|
|
traverseTree(tree, (node) => {
|
|
|
|
if (node.type === "Task") {
|
|
|
|
const taskRefWikiLink = findNodeOfType(node, "WikiLinkPage");
|
|
|
|
if (taskRefWikiLink) {
|
|
|
|
// Check if this looks like a task reference
|
|
|
|
const taskRef = taskRefWikiLink.children![0].text!;
|
|
|
|
if (taskRef.includes("@")) {
|
|
|
|
// Ok, this already has a page ref, skipping
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// No task ref found, let's splice it in
|
|
|
|
node.children!.splice(1, 0, { text: ` [[${page}@${node.from!}]]` });
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
2024-07-23 20:53:36 +08:00
|
|
|
// And rewrite federation links as well
|
|
|
|
if (isFederationPath(page)) {
|
|
|
|
rewritePageRefs(tree, page);
|
|
|
|
}
|
2024-07-10 14:50:54 +08:00
|
|
|
return renderToText(tree);
|
|
|
|
},
|
2024-07-23 20:53:36 +08:00
|
|
|
|
2024-07-09 15:22:08 +08:00
|
|
|
// INTERNAL: Used to implement resolving [[links]] in expressions, also supports [[link#header]] and [[link$pos]] as well as [[link$anchor]]
|
|
|
|
async readPage(name: string): Promise<string> {
|
2024-02-03 02:19:07 +08:00
|
|
|
const cachedPage = pageCache.get(name);
|
|
|
|
if (cachedPage) {
|
|
|
|
return cachedPage;
|
|
|
|
} else {
|
2024-07-09 15:22:08 +08:00
|
|
|
const pageRef = parsePageRef(name);
|
|
|
|
try {
|
|
|
|
let page: string = await system.localSyscall("space.readPage", [
|
|
|
|
pageRef.page,
|
|
|
|
]);
|
|
|
|
|
|
|
|
// Extract page section if pos, anchor, or header are included
|
|
|
|
if (pageRef.pos) {
|
|
|
|
// If the page link includes a position, slice the page from that position
|
|
|
|
page = page.slice(pageRef.pos);
|
|
|
|
} else if (pageRef.anchor) {
|
|
|
|
// If the page link includes an anchor, slice the page from that anchor
|
|
|
|
const pos = page.indexOf(`$${pageRef.anchor}`);
|
|
|
|
page = page.slice(pos);
|
|
|
|
} else if (pageRef.header) {
|
|
|
|
// If the page link includes a header, select that header (up to the next header at the same level)
|
|
|
|
// Note: this an approximation, should ideally use the AST
|
|
|
|
let pos = page.indexOf(`# ${pageRef.header}\n`);
|
|
|
|
let headingLevel = 1;
|
|
|
|
while (page.charAt(pos - 1) === "#") {
|
|
|
|
pos--;
|
|
|
|
headingLevel++;
|
|
|
|
}
|
|
|
|
page = page.slice(pos);
|
|
|
|
|
|
|
|
// Slice up to the next equal or higher level heading
|
|
|
|
const headRegex = new RegExp(
|
|
|
|
`[^#]#{1,${headingLevel}} [^\n]*\n`,
|
|
|
|
"g",
|
|
|
|
);
|
|
|
|
const endPos = page.slice(headingLevel).search(headRegex) +
|
|
|
|
headingLevel;
|
|
|
|
if (endPos) {
|
|
|
|
page = page.slice(0, endPos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-03 02:19:07 +08:00
|
|
|
pageCache.set(name, page, pageCacheTtl);
|
2024-07-09 15:22:08 +08:00
|
|
|
|
2024-02-03 02:19:07 +08:00
|
|
|
return page;
|
2024-07-09 15:22:08 +08:00
|
|
|
} catch (e: any) {
|
2024-02-03 02:19:07 +08:00
|
|
|
if (e.message === "Not found") {
|
|
|
|
throw new Error(`Page not found: ${name}`);
|
|
|
|
}
|
2024-07-09 15:22:08 +08:00
|
|
|
throw e;
|
|
|
|
}
|
2024-02-03 02:19:07 +08:00
|
|
|
}
|
|
|
|
},
|
2024-01-21 02:16:07 +08:00
|
|
|
};
|
|
|
|
}
|