spa: markdown-renderer mit sanitize (tdd)
This commit is contained in:
parent
8af049a9ff
commit
2bcb2451b4
|
|
@ -0,0 +1,41 @@
|
|||
import { marked } from 'marked';
|
||||
import DOMPurify from 'dompurify';
|
||||
import hljs from 'highlight.js/lib/core';
|
||||
import javascript from 'highlight.js/lib/languages/javascript';
|
||||
import bash from 'highlight.js/lib/languages/bash';
|
||||
import typescript from 'highlight.js/lib/languages/typescript';
|
||||
import json from 'highlight.js/lib/languages/json';
|
||||
|
||||
hljs.registerLanguage('javascript', javascript);
|
||||
hljs.registerLanguage('js', javascript);
|
||||
hljs.registerLanguage('typescript', typescript);
|
||||
hljs.registerLanguage('ts', typescript);
|
||||
hljs.registerLanguage('bash', bash);
|
||||
hljs.registerLanguage('sh', bash);
|
||||
hljs.registerLanguage('json', json);
|
||||
|
||||
marked.use({
|
||||
breaks: true,
|
||||
gfm: true,
|
||||
renderer: {
|
||||
code({ text, lang }) {
|
||||
const language = lang && hljs.getLanguage(lang) ? lang : undefined;
|
||||
const highlighted = language
|
||||
? hljs.highlight(text, { language }).value
|
||||
: hljs.highlightAuto(text).value;
|
||||
const cls = language ? ` language-${language}` : '';
|
||||
return `<pre><code class="hljs${cls}">${highlighted}</code></pre>`;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Rendert einen Markdown-String zu sanitized HTML.
|
||||
* Einziger Export des Moduls — so bleibt Austausch der Engine lokal.
|
||||
*/
|
||||
export function renderMarkdown(md: string): string {
|
||||
const raw = marked.parse(md, { async: false }) as string;
|
||||
return DOMPurify.sanitize(raw, {
|
||||
ADD_ATTR: ['target', 'rel'],
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { renderMarkdown } from '$lib/render/markdown';
|
||||
|
||||
describe('renderMarkdown', () => {
|
||||
it('rendert einfachen Markdown-Text zu HTML', () => {
|
||||
const html = renderMarkdown('**bold** and *italic*');
|
||||
expect(html).toContain('<strong>bold</strong>');
|
||||
expect(html).toContain('<em>italic</em>');
|
||||
});
|
||||
|
||||
it('entfernt <script>-Tags (DOMPurify)', () => {
|
||||
const html = renderMarkdown('hello <script>alert("x")</script> world');
|
||||
expect(html).not.toContain('<script>');
|
||||
});
|
||||
|
||||
it('entfernt javascript:-URLs', () => {
|
||||
const html = renderMarkdown('[click](javascript:alert(1))');
|
||||
expect(html).not.toMatch(/javascript:/i);
|
||||
});
|
||||
|
||||
it('rendert Links mit http:// und erhält das href', () => {
|
||||
const html = renderMarkdown('[nostr](https://nostr.com)');
|
||||
expect(html).toContain('href="https://nostr.com"');
|
||||
});
|
||||
|
||||
it('rendert horizontale Linie aus ---', () => {
|
||||
const html = renderMarkdown('oben\n\n---\n\nunten');
|
||||
expect(html).toContain('<hr>');
|
||||
});
|
||||
|
||||
it('rendert fenced code blocks', () => {
|
||||
const html = renderMarkdown('```js\nconst x = 1;\n```');
|
||||
expect(html).toContain('<pre>');
|
||||
expect(html).toContain('<code');
|
||||
});
|
||||
|
||||
it('rendert GFM tables', () => {
|
||||
const md = '| a | b |\n|---|---|\n| 1 | 2 |';
|
||||
const html = renderMarkdown(md);
|
||||
expect(html).toContain('<table');
|
||||
expect(html).toContain('<td>1</td>');
|
||||
});
|
||||
|
||||
it('rendert Bilder', () => {
|
||||
const html = renderMarkdown('');
|
||||
expect(html).toContain('<img');
|
||||
expect(html).toContain('src="https://example.com/img.png"');
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue