import { unified } from "https://esm.sh/unified@10.1.2"; import { render } from "https://deno.land/x/gfm@0.2.5/mod.ts"; import "https://esm.sh/prismjs@1.29.0/components/prism-typescript?no-check"; import "https://esm.sh/prismjs@1.29.0/components/prism-bash?no-check"; import "https://esm.sh/prismjs@1.29.0/components/prism-rust?no-check"; import remarkParse from "https://esm.sh/remark-parse@10.0.2"; import remarkStringify from "https://esm.sh/remark-stringify@10.0.3"; import remarkFrontmatter, { Root, } from "https://esm.sh/remark-frontmatter@4.0.1"; import * as cache from "@lib/cache/documents.ts"; import { SILVERBULLET_SERVER } from "@lib/env.ts"; import { fixRenderedMarkdown } from "@lib/helpers.ts"; import { createLogger } from "@lib/log.ts"; import * as typesense from "@lib/typesense.ts"; export type Document = { name: string; lastModified: number; contentType: string; size: number; perm: string; }; const log = createLogger("documents"); export async function getDocuments(): Promise { const cachedDocuments = await cache.getDocuments(); if (cachedDocuments) return cachedDocuments; const headers = new Headers(); headers.append("Accept", "application/json"); headers.append("X-Sync-Mode", "true"); log.debug("fetching all documents"); const response = await fetch(`${SILVERBULLET_SERVER}/index.json`, { headers: headers, }); const documents = await response.json(); cache.setDocuments(documents); typesense.synchronize(); return documents; } export function createDocument( name: string, content: string | ArrayBuffer, mediaType?: string, ) { const headers = new Headers(); if (mediaType) { headers.append("Content-Type", mediaType); } log.info("creating document", { name }); typesense.synchronize(); return fetch(SILVERBULLET_SERVER + "/" + name, { body: content, method: "PUT", headers, }); } export async function getDocument(name: string): Promise { const cachedDocument = await cache.getDocument(name); if (cachedDocument) return cachedDocument; log.debug("fetching document", { name }); const response = await fetch(SILVERBULLET_SERVER + "/" + name); const text = await response.text(); cache.setDocument(name, text); typesense.synchronize(); return text; } export function transformDocument(input: string, cb: (r: Root) => Root) { const out = unified() .use(remarkParse) .use(remarkFrontmatter, ["yaml"]) .use(() => (tree) => { return cb(tree); }) .use(remarkStringify) .processSync(input); return fixRenderedMarkdown(String(out)); } export function parseDocument(doc: string) { return unified() .use(remarkParse) .use(remarkFrontmatter, ["yaml", "toml"]) .parse(doc); } export function renderMarkdown(doc: string) { return render(doc, { allowMath: true, }); } export type ParsedDocument = ReturnType; export type DocumentChild = ParsedDocument["children"][number]; export function findRangeOfChildren(children: DocumentChild[]) { const firstChild = children[0]; const lastChild = children.length > 1 ? children[children.length - 1] : firstChild; const start = firstChild.position?.start.offset; const end = lastChild.position?.end.offset; if (typeof start !== "number" || typeof end !== "number") return; return [start, end]; } export function getTextOfRange(children: DocumentChild[], text: string) { if (!children || children.length === 0) { return; } const range = findRangeOfChildren(children); if (!range) return; return text.substring(range[0], range[1]); } export function getTextOfChild(child: DocumentChild): string | undefined { if ("value" in child) return child.value; if ("children" in child) { return getTextOfChild(child.children[0]); } return; }