File size: 1,943 Bytes
1bcd186
 
7d6fc19
 
1bcd186
7d6fc19
 
 
 
1bcd186
a1a6daf
 
 
 
 
7d6fc19
cbd723d
7d6fc19
cbd723d
7d6fc19
1bcd186
7d6fc19
 
 
 
 
 
1bcd186
7d6fc19
 
 
 
 
 
 
 
 
1bcd186
 
7d6fc19
 
 
 
 
b95ec8b
7d6fc19
 
6f93b29
 
1bcd186
 
 
7d6fc19
1bcd186
 
 
 
7d6fc19
 
 
6f7beab
7d6fc19
6f7beab
7d6fc19
10c62e9
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
<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}