Spaces:
Sleeping
Sleeping
File size: 2,735 Bytes
1bcd186 7d6fc19 47f81d6 1bcd186 be6d225 7d6fc19 be6d225 1bcd186 6e2a902 a1a6daf 7bf1507 a1a6daf be6d225 cbd723d 7bf1507 cbd723d 7d6fc19 1bcd186 7bf1507 be6d225 1bcd186 7d6fc19 be6d225 6e2a902 be6d225 7d6fc19 6f93b29 6e2a902 47f81d6 be6d225 6e2a902 be6d225 1bcd186 7d6fc19 be6d225 7d6fc19 3b53c7a 6f7beab |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
<script lang="ts">
import { processTokens, processTokensSync, type Token } from "$lib/utils/marked";
// import MarkdownWorker from "$lib/workers/markdownWorker?worker";
import CodeBlock from "../CodeBlock.svelte";
import type { IncomingMessage, OutgoingMessage } from "$lib/workers/markdownWorker";
import { browser } from "$app/environment";
import { onMount } from "svelte";
import { updateDebouncer } from "$lib/utils/updates";
let DOMPurify: typeof import("isomorphic-dompurify").default | null = null;
interface Props {
content: string;
sources?: { title?: string; link: string }[];
loading?: boolean;
}
let worker: Worker | null = null;
let { content, sources = [], loading = false }: Props = $props();
let tokens: Token[] = $state(processTokensSync(content, sources));
async function processContent(
content: string,
sources: { title?: string; link: string }[]
): Promise<Token[]> {
if (worker) {
return new Promise((resolve) => {
if (!worker) {
throw new Error("Worker not initialized");
}
worker.onmessage = (event: MessageEvent<OutgoingMessage>) => {
if (event.data.type !== "processed") {
throw new Error("Invalid message type");
}
resolve(event.data.tokens);
};
worker.postMessage(
JSON.parse(JSON.stringify({ content, sources, type: "process" })) as IncomingMessage
);
});
} else {
return processTokens(content, sources);
}
}
$effect(() => {
if (!browser) {
tokens = processTokensSync(content, sources);
} else {
(async () => {
updateDebouncer.startRender();
tokens = await processContent(content, sources).then(
async (tokens) =>
await Promise.all(
tokens.map(async (token) => {
if (token.type === "text" && DOMPurify) {
token.html = DOMPurify.sanitize(await token.html);
}
return token;
})
)
);
updateDebouncer.endRender();
})();
}
});
onMount(async () => {
// todo: fix worker, seems to be transmitting a lot of data
// worker = browser && window.Worker ? new MarkdownWorker() : null;
// Dynamically import DOMPurify only on the client
const { default: purify } = await import("isomorphic-dompurify");
DOMPurify = purify;
DOMPurify.addHook("afterSanitizeAttributes", (node) => {
if (node.tagName === "A") {
node.setAttribute("target", "_blank");
node.setAttribute("rel", "noreferrer");
}
});
});
</script>
{#each tokens as token}
{#if token.type === "text"}
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html token.html}
{:else if token.type === "code"}
<CodeBlock code={token.code} rawCode={token.rawCode} loading={loading && !token.isClosed} />
{/if}
{/each}
|