diff --git a/src/lib/actions/renderSanitizedHtml.spec.ts b/src/lib/actions/renderSanitizedHtml.spec.ts new file mode 100644 index 0000000..e35c358 --- /dev/null +++ b/src/lib/actions/renderSanitizedHtml.spec.ts @@ -0,0 +1,25 @@ +import { renderSanitizedHtml } from './renderSanitizedHtml'; +import { describe, it, expect, beforeEach } from 'vitest'; + +describe('renderSanitizedHtml action', () => { + let node: HTMLElement; + + beforeEach(() => { + node = document.createElement('div'); + }); + + it('should sanitize and render HTML', () => { + renderSanitizedHtml(node, '

Hello

'); + expect(node.innerHTML).toBe('

Hello

'); + }); + + it('should update the HTML', () => { + const action = renderSanitizedHtml(node, '

Hello

'); + expect(node.innerHTML).toBe('

Hello

'); + + if (action && action.update) { + action.update('

World

'); + } + expect(node.innerHTML).toBe('

World

'); + }); +}); diff --git a/src/lib/actions/renderSanitizedHtml.ts b/src/lib/actions/renderSanitizedHtml.ts new file mode 100644 index 0000000..447bb7a --- /dev/null +++ b/src/lib/actions/renderSanitizedHtml.ts @@ -0,0 +1,14 @@ +import purify from 'isomorphic-dompurify'; +import type { Action } from 'svelte/action'; + +export const renderSanitizedHtml: Action = (node, html) => { + const fragment = purify.sanitize(html, { RETURN_DOM_FRAGMENT: true }); + node.replaceChildren(fragment); + + return { + update(newHtml: string) { + const fragment = purify.sanitize(newHtml, { RETURN_DOM_FRAGMENT: true }); + node.replaceChildren(fragment); + } + }; +}; diff --git a/src/routes/blog/[entry]/+page.svelte b/src/routes/blog/[entry]/+page.svelte index 10a0362..f323494 100644 --- a/src/routes/blog/[entry]/+page.svelte +++ b/src/routes/blog/[entry]/+page.svelte @@ -2,9 +2,9 @@ import { page } from '$app/stores'; import { blogEntries } from '$lib/helpers/blogProvider'; import type { BlogEntry } from '$lib/types'; - import purify from 'isomorphic-dompurify'; import { parse } from 'marked'; import { reveal } from '$lib/actions/reveal'; + import { renderSanitizedHtml } from '$lib/actions/renderSanitizedHtml'; let content = $state(''); @@ -17,7 +17,7 @@ async function loadContent(entry: BlogEntry | undefined) { if (!entry) return; const response = await fetch(`/blog_md/${entry.content_md}`); - content = purify.sanitize(await parse(await response.text())); + content = await parse(await response.text()); } @@ -62,9 +62,7 @@ prose-code:bg-background-card prose-code:px-1.5 prose-code:py-0.5 prose-code:text-sm prose-code:text-text prose-ol:text-text-muted prose-ul:text-text-muted prose-li:marker:text-primary/40 [&_a]:break-all [&_p]:whitespace-pre-wrap" - > - - {@html content} - + use:renderSanitizedHtml={content} + >