feat: markdown rendering for response viewer

Add marked.js (39KB) for rich text display in the response viewer.
Renders headings, code blocks, lists, tables, blockquotes, and
inline formatting with dark theme styling.

Falls back to escaped plain text if marked.js fails to load.
This commit is contained in:
Teigen
2026-04-10 19:12:06 +08:00
parent 3cb7b510f8
commit d32f4debb2
4 changed files with 113 additions and 5 deletions
+14 -2
View File
@@ -873,6 +873,18 @@ class CodemanApp {
// Response Viewer — native-scroll panel for reading full Claude responses
// ═══════════════════════════════════════════════════════════════
/** Render markdown to sanitized HTML, falling back to plain text if marked.js unavailable */
_renderMarkdown(text) {
if (typeof marked !== 'undefined' && marked.parse) {
try {
return marked.parse(text, { breaks: true, gfm: true });
} catch { /* fall through */ }
}
// Fallback: escape HTML and preserve whitespace
const escaped = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return `<pre style="white-space:pre-wrap;word-break:break-word">${escaped}</pre>`;
}
async toggleResponseViewer() {
const viewer = document.getElementById('responseViewer');
const backdrop = document.getElementById('responseViewerBackdrop');
@@ -912,7 +924,7 @@ class CodemanApp {
}
const body = document.getElementById('responseViewerBody');
body.textContent = lastResponse;
body.innerHTML = this._renderMarkdown(lastResponse);
// Reset state for fresh open
const title = document.getElementById('responseViewerTitle');
@@ -958,7 +970,7 @@ class CodemanApp {
const text = document.createElement('div');
text.className = 'rv-text';
text.textContent = msg.text;
text.innerHTML = this._renderMarkdown(msg.text);
div.appendChild(text);
body.appendChild(div);
+1
View File
@@ -26,6 +26,7 @@
<!-- WebGL addon lazy-loaded by app.js on desktop only (skipped on mobile, saving 244KB) -->
<script defer src="vendor/xterm-addon-unicode11.min.js"></script>
<script defer src="vendor/xterm-zerolag-input.js"></script>
<script defer src="vendor/marked.min.js"></script>
<!-- Synchronous mobile detection — runs before first paint to prevent panel flash -->
<script>if(window.innerWidth<768||(('ontouchstart' in window||navigator.maxTouchPoints>0)&&window.innerWidth<1024))document.documentElement.classList.add('mobile-init');</script>
<!-- Inline critical CSS for instant skeleton paint (before styles.css loads) -->
+92 -3
View File
@@ -7865,9 +7865,100 @@ kbd {
color: #51cf66;
}
/* Markdown rendered content inside response viewer */
.rv-text {
white-space: pre-wrap;
word-break: break-word;
line-height: 1.6;
}
.rv-text p {
margin: 0 0 0.6em;
}
.rv-text p:last-child {
margin-bottom: 0;
}
.rv-text h1, .rv-text h2, .rv-text h3, .rv-text h4 {
color: #e0e0e0;
margin: 1em 0 0.4em;
line-height: 1.3;
}
.rv-text h1 { font-size: 1.3em; }
.rv-text h2 { font-size: 1.15em; }
.rv-text h3 { font-size: 1.05em; }
.rv-text code {
background: #2a2a3e;
padding: 1px 5px;
border-radius: 3px;
font-size: 0.9em;
}
.rv-text pre {
background: #1e1e2e;
border: 1px solid #333;
border-radius: 6px;
padding: 10px 12px;
overflow-x: auto;
margin: 0.6em 0;
}
.rv-text pre code {
background: none;
padding: 0;
font-size: 0.85em;
line-height: 1.5;
}
.rv-text ul, .rv-text ol {
margin: 0.4em 0;
padding-left: 1.4em;
}
.rv-text li {
margin-bottom: 0.2em;
}
.rv-text blockquote {
border-left: 3px solid #444;
margin: 0.6em 0;
padding: 0.3em 0 0.3em 12px;
color: #999;
}
.rv-text strong {
color: #f0f0f0;
}
.rv-text a {
color: #5c7cfa;
text-decoration: none;
}
.rv-text table {
border-collapse: collapse;
margin: 0.6em 0;
width: 100%;
font-size: 0.9em;
}
.rv-text th, .rv-text td {
border: 1px solid #333;
padding: 4px 8px;
text-align: left;
}
.rv-text th {
background: #2a2a3e;
color: #e0e0e0;
}
.rv-text hr {
border: none;
border-top: 1px solid #333;
margin: 1em 0;
}
.response-viewer-body {
@@ -7879,8 +7970,6 @@ kbd {
font-size: 13px;
line-height: 1.5;
color: #d4d4d4;
white-space: pre-wrap;
word-break: break-word;
}
.response-viewer-body:empty::after {
File diff suppressed because one or more lines are too long