146 lines
3.8 KiB
TypeScript
146 lines
3.8 KiB
TypeScript
import { unified } from "https://esm.sh/unified@10.1.2";
|
|
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 remarkRehype from "https://esm.sh/remark-rehype@10.1.0";
|
|
import rehypeSanitize from "https://esm.sh/rehype-sanitize@5.0.1";
|
|
import rehypeStringify from "https://esm.sh/rehype-stringify@9.0.3";
|
|
import * as cache from "@lib/cache/documents.ts";
|
|
import { SILVERBULLET_SERVER } from "@lib/env.ts";
|
|
|
|
export type Document = {
|
|
name: string;
|
|
lastModified: number;
|
|
contentType: string;
|
|
size: number;
|
|
perm: string;
|
|
};
|
|
|
|
export async function getDocuments(): Promise<Document[]> {
|
|
const cachedDocuments = await cache.getDocuments();
|
|
if (cachedDocuments) return cachedDocuments;
|
|
|
|
const headers = new Headers();
|
|
headers.append("Accept", "application/json");
|
|
|
|
const response = await fetch(`${SILVERBULLET_SERVER}/index.json`, {
|
|
headers: headers,
|
|
});
|
|
|
|
const documents = await response.json();
|
|
cache.setDocuments(documents);
|
|
|
|
return documents;
|
|
}
|
|
|
|
export function createDocument(
|
|
name: string,
|
|
content: string | ArrayBuffer,
|
|
mediaType?: string,
|
|
) {
|
|
const headers = new Headers();
|
|
|
|
if (mediaType) {
|
|
headers.append("Content-Type", mediaType);
|
|
}
|
|
|
|
return fetch(SILVERBULLET_SERVER + "/" + name, {
|
|
body: content,
|
|
method: "PUT",
|
|
headers,
|
|
});
|
|
}
|
|
|
|
export async function getDocument(name: string): Promise<string> {
|
|
const cachedDocument = await cache.getDocument(name);
|
|
if (cachedDocument) return cachedDocument;
|
|
|
|
const response = await fetch(SILVERBULLET_SERVER + "/" + name);
|
|
const text = await response.text();
|
|
|
|
cache.setDocument(name, text);
|
|
|
|
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 String(out)
|
|
.replace("***\n", "---")
|
|
.replace("----------------", "---")
|
|
.replace("\n---", "---")
|
|
.replace(/^(date:[^'\n]*)'|'/gm, (match, p1, p2) => {
|
|
if (p1) {
|
|
// This is a line starting with date: followed by single quotes
|
|
return p1.replace(/'/gm, "");
|
|
} else if (p2) {
|
|
return "";
|
|
} else {
|
|
// This is a line with single quotes, but not starting with date:
|
|
return match;
|
|
}
|
|
});
|
|
}
|
|
|
|
export function parseDocument(doc: string) {
|
|
return unified()
|
|
.use(remarkParse).use(remarkFrontmatter, ["yaml", "toml"])
|
|
.parse(doc);
|
|
}
|
|
|
|
export function renderMarkdown(doc: string) {
|
|
const out = unified()
|
|
.use(remarkParse)
|
|
.use(remarkRehype)
|
|
.use(rehypeSanitize)
|
|
.use(rehypeStringify)
|
|
.processSync(doc);
|
|
|
|
return String(out);
|
|
}
|
|
|
|
export type ParsedDocument = ReturnType<typeof parseDocument>;
|
|
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;
|
|
}
|