traces-replay / src /lib /markdown.js
mishig's picture
mishig HF Staff
Copy button on code blocks
e3223f8 verified
import { marked } from 'marked';
import DOMPurify from 'dompurify';
marked.setOptions({
gfm: true,
breaks: true,
});
function escapeHtml(s) {
return String(s)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
// Wrap each fenced code block in a container with a copy button.
// The click handler lives in TraceViewer (event delegation on .copy-btn).
marked.use({
renderer: {
code({ text, lang }) {
const langClass = lang
? ` class="language-${String(lang).replace(/[^\w-]/g, '')}"`
: '';
return `<div class="code-block"><button type="button" class="copy-btn" aria-label="Copy code">Copy</button><pre><code${langClass}>${escapeHtml(text)}</code></pre></div>`;
},
},
});
// All rendered links open in a new tab and are sandboxed against
// window.opener / referrer leaks.
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
if (node.tagName === 'A') {
node.setAttribute('target', '_blank');
node.setAttribute('rel', 'noopener noreferrer');
}
});
export function renderMarkdown(text) {
if (!text) return '';
const html = marked.parse(String(text));
return DOMPurify.sanitize(html, { ADD_ATTR: ['target', 'rel'] });
}