feat(ui): render markdown in peek pane

- Add marked.js for full markdown rendering
- Stream peek body renders as markdown
- Card peek non-code content renders as markdown
- Code/snippet cards keep escaped pre/code display
- Styled: headers, lists, blockquotes, inline code, code blocks, links, hr
- Graceful fallback to escHtml if marked fails to load
This commit is contained in:
2026-05-16 19:47:53 -04:00
parent 4c3cdc55c6
commit b456dca4b3
3 changed files with 92 additions and 2 deletions
+12 -2
View File
@@ -768,7 +768,7 @@
<span class="peek-brow-ts">${fmtDateLong(e.created_at)}</span>
</div>
${e.title ? `<div class="peek-title" data-id="${e.id}">${escHtml(e.title)}</div>` : ''}
<div class="peek-body" data-id="${e.id}">${escHtml(e.body)}</div>
<div class="peek-body md" data-id="${e.id}">${renderMd(e.body)}</div>
${tags ? `<div class="peek-sec"><div class="peek-sec-lbl">tags</div><div class="peek-sec-inner tag-pills">${tags}</div></div>` : ''}
<div class="peek-sec">
<div class="peek-sec-lbl">context</div>
@@ -825,9 +825,13 @@
if (!hasDecision && e.body) {
const lang = data.lang || '';
const isCode = lang || e.card_type === 'snippet';
const bodyHtml = isCode
? `<div class="peek-code"><pre><code>${escHtml(e.body)}</code></pre></div>`
: `<div class="peek-body md">${renderMd(e.body)}</div>`;
sections += `<div class="peek-sec">
<div class="peek-sec-lbl">content${lang ? `<span class="peek-sec-lang">${lang}</span>` : ''}${hasFill ? `<button class="peek-sec-run" onclick="nibApp.enterMode('fill')">⤓ fill</button>` : ''}</div>
<div class="peek-sec-inner"><div class="peek-code"><pre><code>${escHtml(e.body)}</code></pre></div></div>
<div class="peek-sec-inner">${bodyHtml}</div>
</div>`;
}
@@ -1629,6 +1633,12 @@
return escHtml(s).replace(/'/g, '&#39;');
}
function renderMd(s) {
if (!s) return '';
if (typeof marked === 'undefined') return escHtml(s);
return marked.parse(s, { breaks: true });
}
function isSafeUrl(url) {
return /^https?:\/\//i.test(url);
}