import { browser } from "$app/environment";
import { isMarkdownFile } from "$lib/markup/format";
import type { MarkupContext } from "$lib/markup/paths";
// hydration re-runs every load, so the browser hands back the server's render
// instead of loading the markdown pipeline again
const MARKUP_KEY = "data-markup-key";
// the key is only compared against markup already on this page, so stable and
// short is enough, not cryptographic
const digest = (value: string): string => {
let hash = 5381;
for (let i = 0; i < value.length; i += 1) hash = (hash * 33) ^ value.charCodeAt(i);
return (hash >>> 0).toString(36);
};
const keyOf = (filename: string, contents: string, ctx: MarkupContext): string =>
[
digest(
[
ctx.host ?? "",
ctx.repo,
ctx.ref,
ctx.dir ?? "",
String(!!ctx.sourceLines),
String(!!ctx.collapseCode),
filename
].join("\n")
),
contents.length.toString(36),
digest(contents)
].join(".");
const mark = (key: string, html: string): string => `
${html}
`;
// reads back a render this module wrote for the same contents and context
const rendered = (key: string): string | null => {
if (!browser) return null;
const node = document.querySelector(`[${MARKUP_KEY}="${key}"]`);
return node instanceof HTMLElement ? node.innerHTML : null;
};
const render = async (
key: string,
contents: string,
ctx: MarkupContext
): Promise => {
const reused = rendered(key);
if (reused !== null) return mark(key, reused);
const { SOURCE_LIMIT, renderMarkdown } = await import("./markdown");
if (contents.length > SOURCE_LIMIT) return null;
const html = await renderMarkdown(contents, ctx);
return html === null ? null : mark(key, html);
};
export const renderDocument = async (
filename: string,
contents: string,
ctx: MarkupContext
): Promise =>
isMarkdownFile(filename) ? render(keyOf(filename, contents, ctx), contents, ctx) : null;
// like renderDocument, but for content that is always markdown (issue and
// comment bodies) rather than a repo file with an extension to sniff
export const renderMarkup = (contents: string, ctx: MarkupContext): Promise => {
const body = { ...ctx, collapseCode: true };
return render(keyOf("", contents, body), contents, body);
};