TokenRenderer.svelte 2.56 KB
Newer Older
Timothy J. Baek's avatar
revert  
Timothy J. Baek committed
1
2
3
4
5
6
7
8
9
10
11
12
<script lang="ts">
	import { revertSanitizedResponseContent } from '$lib/utils';

	import { marked } from 'marked';
	import CodeBlock from './CodeBlock.svelte';
	import Image from '$lib/components/common/Image.svelte';
	import { stringify } from 'postcss';

	export let token;
	export let tokenIdx = 0;
	export let id;

Timothy J. Baek's avatar
Timothy J. Baek committed
13
14
15
	let element;
	let content;

Timothy J. Baek's avatar
revert  
Timothy J. Baek committed
16
17
18
19
20
21
22
	const renderer = new marked.Renderer();

	// For code blocks with simple backticks
	renderer.codespan = (code) => {
		return `<code>${code.replaceAll('&amp;', '&')}</code>`;
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
23
24
25
26
27
28
	let codes = [];
	renderer.code = (code, lang) => {
		codes.push({ code, lang, id: codes.length });
		codes = codes;
		return `{{@CODE ${codes.length - 1}}}`;
	};
Timothy J. Baek's avatar
revert  
Timothy J. Baek committed
29

Timothy J. Baek's avatar
Timothy J. Baek committed
30
31
32
33
34
35
	let images = [];
	renderer.image = (href, title, text) => {
		images.push({ href, title, text });
		images = images;
		return `{{@IMAGE ${images.length - 1}}}`;
	};
Timothy J. Baek's avatar
revert  
Timothy J. Baek committed
36
37
38
39
40
41
42
43
44
45
46
47

	// Open all links in a new tab/window (from https://github.com/markedjs/marked/issues/655#issuecomment-383226346)
	const origLinkRenderer = renderer.link;
	renderer.link = (href, title, text) => {
		const html = origLinkRenderer.call(renderer, href, title, text);
		return html.replace(/^<a /, '<a target="_blank" rel="nofollow" ');
	};

	const { extensions, ...defaults } = marked.getDefaults() as marked.MarkedOptions & {
		// eslint-disable-next-line @typescript-eslint/no-explicit-any
		extensions: any;
	};
Timothy J. Baek's avatar
Timothy J. Baek committed
48
49
50
51
52
53
54
55
56
57
58
59
60

	$: if (token) {
		images = [];
		codes = [];
		content = marked
			.parse(token.raw, {
				...defaults,
				gfm: true,
				breaks: true,
				renderer
			})
			.split(/({{@IMAGE [^}]+}}|{{@CODE [^}]+}})/g);
	}
Timothy J. Baek's avatar
revert  
Timothy J. Baek committed
61
62
</script>

Timothy J. Baek's avatar
Timothy J. Baek committed
63
64
65
66
67
68
69
70
71
72
73
74
75
<div bind:this={element}>
	{#if token.type === 'code'}
		{#if token.lang === 'mermaid'}
			<pre class="mermaid">{revertSanitizedResponseContent(token.text)}</pre>
		{:else}
			<CodeBlock
				id={`${id}-${tokenIdx}`}
				lang={token?.lang ?? ''}
				code={revertSanitizedResponseContent(token?.text ?? '')}
			/>
		{/if}
	{:else if token.type === 'image'}
		<Image src={token.href} alt={token.text} />
Timothy J. Baek's avatar
revert  
Timothy J. Baek committed
76
	{:else}
Timothy J. Baek's avatar
Timothy J. Baek committed
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
		{#each content as part}
			{@html part.startsWith('{{@IMAGE ') || part.startsWith('{{@CODE ') ? '' : part}

			{#if images.length > 0 && part.startsWith('{{@IMAGE ')}
				{@const img = images[parseInt(part.match(/{{@IMAGE (\d+)}}/)[1])]}

				<div class="mt-6">
					<Image src={img.href} text={img.text} />
				</div>
			{:else if codes.length > 0 && part.startsWith('{{@CODE ')}
				{@const _code = codes[parseInt(part.match(/{{@CODE (\d+)}}/)[1])]}
				<div class="my-10 -mb-6">
					<CodeBlock id={`${id}-${tokenIdx}-${_code.id}`} lang={_code.lang} code={_code.code} />
				</div>
			{/if}
		{/each}
Timothy J. Baek's avatar
revert  
Timothy J. Baek committed
93
	{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
94
</div>