|
|
<script lang="ts"> |
|
|
import type { WebSearchSource } from "$lib/types/WebSearch"; |
|
|
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 DOMPurify from "isomorphic-dompurify"; |
|
|
|
|
|
interface Props { |
|
|
content: string; |
|
|
sources?: WebSearchSource[]; |
|
|
} |
|
|
|
|
|
const worker = browser && window.Worker ? new MarkdownWorker() : null; |
|
|
|
|
|
let { content, sources = [] }: Props = $props(); |
|
|
|
|
|
let tokens: Token[] = $state(processTokensSync(content, sources)); |
|
|
|
|
|
async function processContent(content: string, sources: WebSearchSource[]): Promise<Token[]> { |
|
|
if (worker) { |
|
|
return new Promise((resolve) => { |
|
|
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 () => { |
|
|
tokens = await processContent(content, sources); |
|
|
})(); |
|
|
} |
|
|
}); |
|
|
|
|
|
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"} |
|
|
{#await token.html then html} |
|
|
|
|
|
{@html DOMPurify.sanitize(html)} |
|
|
{/await} |
|
|
{:else if token.type === "code"} |
|
|
<CodeBlock code={token.code} rawCode={token.rawCode} /> |
|
|
{/if} |
|
|
{/each} |
|
|
|