feat: render document previews with marked.js instead of raw pre tags
All checks were successful
Deploy / deploy (push) Successful in 11s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 16:58:26 +01:00
parent 0c55d1892f
commit 1b1cb4e989

View File

@@ -394,6 +394,16 @@ tr:last-child td{border-bottom:none}
.conflict-card{border:1px solid #ffc107;border-radius:6px;padding:14px;margin-bottom:12px} .conflict-card{border:1px solid #ffc107;border-radius:6px;padding:14px;margin-bottom:12px}
.conflict-card h3{margin:0 0 10px;font-size:.95rem;font-family:monospace} .conflict-card h3{margin:0 0 10px;font-size:.95rem;font-family:monospace}
.diff-container{margin-top:10px} .diff-container{margin-top:10px}
.md-preview{background:#f8f9fa;border-radius:4px;padding:12px 16px;max-height:340px;overflow:auto;font-size:.85rem;line-height:1.6}
.md-preview h1,.md-preview h2,.md-preview h3{margin:.6em 0 .3em;font-size:1em;font-weight:700}
.md-preview p{margin:.4em 0}
.md-preview ul,.md-preview ol{padding-left:1.4em;margin:.4em 0}
.md-preview code{background:#e9ecef;padding:1px 5px;border-radius:3px;font-size:.9em}
.md-preview pre{background:#e9ecef;padding:10px;border-radius:4px;overflow:auto}
.md-preview pre code{background:none;padding:0}
.md-preview blockquote{border-left:3px solid #ccc;margin:.4em 0;padding:.2em .8em;color:#666}
.md-preview a{color:#0066cc}
.md-preview table{width:auto;font-size:.85em}
""" """
_SCRIPT = r""" _SCRIPT = r"""
@@ -795,12 +805,12 @@ async def review_page():
<div style="display:flex;gap:8px;margin-top:8px"> <div style="display:flex;gap:8px;margin-top:8px">
<div style="flex:1"> <div style="flex:1">
<div style="font-size:.75rem;color:#888;margin-bottom:4px">remote · {r_ts}</div> <div style="font-size:.75rem;color:#888;margin-bottom:4px">remote · {r_ts}</div>
<div id="rc-{enc}" style="background:#f8f9fa;padding:8px;border-radius:4px;font-size:.8rem;white-space:pre-wrap;max-height:300px;overflow:auto"> <div id="rc-{enc}" class="md-preview">
<em style="color:#999">click View to load</em></div> <em style="color:#999">click View to load</em></div>
</div> </div>
<div style="flex:1"> <div style="flex:1">
<div style="font-size:.75rem;color:#888;margin-bottom:4px">local · {l_ts}</div> <div style="font-size:.75rem;color:#888;margin-bottom:4px">local · {l_ts}</div>
<div id="lc-{enc}" style="background:#f8f9fa;padding:8px;border-radius:4px;font-size:.8rem;white-space:pre-wrap;max-height:300px;overflow:auto"> <div id="lc-{enc}" class="md-preview">
<em style="color:#999">click View to load</em></div> <em style="color:#999">click View to load</em></div>
</div> </div>
</div> </div>
@@ -870,17 +880,27 @@ async def review_page():
{l_section} {l_section}
{c_section} {c_section}
<script src="https://cdn.jsdelivr.net/npm/marked/lib/marked.umd.js"></script>
<script> <script>
let resolved = 0; let resolved = 0;
const total = {total}; const total = {total};
function renderMd(text) {{
const html = marked.parse(text || '*(empty)*');
const el = document.createElement('div');
el.className = 'md-preview';
el.innerHTML = html;
return el;
}}
async function loadContent(enc, docId, instance) {{ async function loadContent(enc, docId, instance) {{
const el = document.getElementById('content-' + enc); const el = document.getElementById('content-' + enc);
el.style.display = 'block'; el.style.display = 'block';
el.innerHTML = '<em style="color:#999">Loading…</em>'; el.innerHTML = '<em style="color:#999">Loading…</em>';
const r = await fetch('/review/content?doc_id=' + docId + '&instance=' + instance); const r = await fetch('/review/content?doc_id=' + docId + '&instance=' + instance);
const d = await r.json(); const d = await r.json();
el.innerHTML = '<pre style="background:#f8f9fa;padding:8px;border-radius:4px;font-size:.78rem;white-space:pre-wrap;max-height:300px;overflow:auto">' + escHtml(d.text || '(empty)') + '</pre>'; el.innerHTML = '';
el.appendChild(renderMd(d.text));
}} }}
async function loadConflict(enc, remoteId, localId) {{ async function loadConflict(enc, remoteId, localId) {{
@@ -888,8 +908,10 @@ async function loadConflict(enc, remoteId, localId) {{
fetch('/review/content?doc_id=' + remoteId + '&instance=remote').then(r=>r.json()), fetch('/review/content?doc_id=' + remoteId + '&instance=remote').then(r=>r.json()),
fetch('/review/content?doc_id=' + localId + '&instance=local' ).then(r=>r.json()), fetch('/review/content?doc_id=' + localId + '&instance=local' ).then(r=>r.json()),
]); ]);
document.getElementById('rc-' + enc).textContent = rr.text || '(empty)'; const rc = document.getElementById('rc-' + enc);
document.getElementById('lc-' + enc).textContent = lr.text || '(empty)'; const lc = document.getElementById('lc-' + enc);
rc.innerHTML = ''; rc.appendChild(renderMd(rr.text));
lc.innerHTML = ''; lc.appendChild(renderMd(lr.text));
}} }}
async function applyAction(enc, action) {{ async function applyAction(enc, action) {{
@@ -913,10 +935,6 @@ async function applyAction(enc, action) {{
alert('Error: ' + (d.error || 'unknown')); alert('Error: ' + (d.error || 'unknown'));
}} }}
}} }}
function escHtml(s) {{
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}}
</script>""" </script>"""
return HTMLResponse(_page("Review Differences", body)) return HTMLResponse(_page("Review Differences", body))