File size: 1,943 Bytes
3baea8e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
<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}
			<!-- eslint-disable-next-line svelte/no-at-html-tags -->
			{@html DOMPurify.sanitize(html)}
		{/await}
	{:else if token.type === "code"}
		<CodeBlock code={token.code} rawCode={token.rawCode} />
	{/if}
{/each}