<script lang="ts">
import { marked, Renderer } from 'marked';
import DOMPurify from 'dompurify';
let { text }: { text: string } = $props();
// Custom renderer: TUI-style code blocks (╭── lang ──╮ / ╰──────────╯)
const renderer = new Renderer();
renderer.code = ({ text: code, lang }) => {
const language = lang || 'text';
const escaped = code
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
const header = `╭── ${language} `;
const footer = `╰─────────────────────────`;
return (
`<div class="code-block">` +
`<div class="code-header">${header}</div>` +
`<pre class="code-body"><code>${escaped}</code></pre>` +
`<div class="code-footer">${footer}</div>` +
`</div>`
);
};
marked.setOptions({ gfm: true, breaks: true });
let html = $derived(
text ? DOMPurify.sanitize(marked.parse(text, { renderer }) as string) : ''
);
</script>
<div class="markdown-content">{@html html}</div>
<style>
.markdown-content {
line-height: 1.6;
word-break: break-word;
}
.markdown-content :global(h1),
.markdown-content :global(h2),
.markdown-content :global(h3),
.markdown-content :global(h4),
.markdown-content :global(h5),
.markdown-content :global(h6) {
color: var(--fg-bright);
margin: 0.75em 0 0.25em;
}
.markdown-content :global(h1) { font-size: 1.3em; }
.markdown-content :global(h2) { font-size: 1.15em; }
.markdown-content :global(h3) { font-size: 1.05em; }
.markdown-content :global(p) {
margin: 0.35em 0;
}
.markdown-content :global(a) {
color: var(--accent);
text-decoration: underline;
text-underline-offset: 2px;
}
.markdown-content :global(a:hover) {
opacity: 0.8;
}
/* TUI-style code block */
.markdown-content :global(.code-block) {
margin: 0.625em 0;
font-family: var(--font-mono);
}
.markdown-content :global(.code-header) {
color: var(--fg-dim);
font-size: 0.8125rem;
line-height: 1.2;
letter-spacing: 0.01em;
padding: 0 0.25rem;
}
.markdown-content :global(.code-body) {
background: var(--bg-elevated);
padding: 0.625rem 0.75rem;
overflow-x: auto;
margin: 0;
border-left: 1px solid var(--border);
border-right: 1px solid var(--border);
font-size: 0.875rem;
line-height: 1.5;
}
.markdown-content :global(.code-body code) {
font-family: inherit;
color: var(--fg);
}
.markdown-content :global(.code-footer) {
color: var(--fg-dim);
font-size: 0.8125rem;
line-height: 1.2;
letter-spacing: 0.01em;
padding: 0 0.25rem;
}
/* Inline code */
.markdown-content :global(:not(pre) > code) {
background: var(--bg-elevated);
padding: 0.125em 0.35em;
border-radius: 3px;
font-size: 0.875em;
font-family: var(--font-mono);
}
.markdown-content :global(blockquote) {
margin: 0.5em 0;
padding: 0.25em 0.75em;
border-left: 2px solid color-mix(in srgb, var(--accent) 50%, transparent);
color: var(--fg-dim);
}
.markdown-content :global(ul),
.markdown-content :global(ol) {
margin: 0.35em 0;
padding-left: 1.5em;
}
.markdown-content :global(li) {
margin: 0.15em 0;
}
.markdown-content :global(table) {
border-collapse: collapse;
width: 100%;
margin: 0.5em 0;
font-size: 0.85em;
}
.markdown-content :global(th),
.markdown-content :global(td) {
border: 1px solid var(--border);
padding: 0.35em 0.6em;
text-align: left;
}
.markdown-content :global(th) {
background: var(--bg-surface);
color: var(--fg-bright);
}
.markdown-content :global(hr) {
border: none;
border-top: 1px solid var(--border);
margin: 0.75em 0;
}
.markdown-content :global(img) {
max-width: 100%;
height: auto;
}
.markdown-content :global(strong) {
color: var(--fg-bright);
}
</style>