fix: HTML code blocks no longer break chat flow

- HTML code blocks now show as syntax-highlighted code with a Preview button
- Added sandboxed iframe HTML preview panel
- Added 'Open in Browser' button for full-screen preview
- Raw HTML from AI responses no longer injected into chat DOM
This commit is contained in:
admin
2026-05-21 18:23:15 +04:00
Unverified
parent 5125725ea7
commit e2a97a958a
3 changed files with 98 additions and 2 deletions

View File

@@ -431,6 +431,33 @@ a:hover { text-decoration: underline; }
} }
.copy-btn:hover, .download-btn:hover { background: var(--accent); color: white; } .copy-btn:hover, .download-btn:hover { background: var(--accent); color: white; }
.html-preview-block { margin: 8px 0; }
.html-preview-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 12px;
background: var(--bg-tertiary);
border-radius: 8px 8px 0 0;
font-size: 12px;
color: var(--text-secondary);
border: 1px solid var(--border);
border-bottom: none;
}
.html-preview-bar + pre { border-top-left-radius: 0; border-top-right-radius: 0; }
.html-preview-btn {
background: linear-gradient(135deg, #6c63ff, #a855f7);
border: none;
color: white;
padding: 4px 14px;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
cursor: pointer;
transition: opacity var(--transition);
}
.html-preview-btn:hover { opacity: 0.85; }
.msg-actions { .msg-actions {
display: flex; display: flex;
gap: 6px; gap: 6px;

View File

@@ -132,6 +132,19 @@
</div> </div>
</div> </div>
<div id="html-preview" class="file-viewer" style="display:none;z-index:2500;">
<div class="file-viewer-header">
<div class="file-viewer-title">
<span id="html-preview-title">HTML Preview</span>
</div>
<div class="file-viewer-actions">
<button id="html-preview-newtab" class="fv-btn">Open in Browser</button>
<button id="html-preview-close" class="icon-btn" aria-label="Close preview">&times;</button>
</div>
</div>
<iframe id="html-preview-frame" sandbox="allow-scripts allow-same-origin" style="flex:1;width:100%;border:none;background:white;border-radius:0 0 12px 12px;"></iframe>
</div>
<div id="messages" class="messages"></div> <div id="messages" class="messages"></div>
<div id="terminal-panel" class="terminal-panel"> <div id="terminal-panel" class="terminal-panel">

View File

@@ -330,9 +330,20 @@
return code; return code;
}, },
breaks: true, breaks: true,
gfm: true gfm: true,
sanitize: false
}); });
return marked.parse(text); var html = marked.parse(text);
html = html.replace(/<pre><code class="language-html">([\s\S]*?)<\/code><\/pre>/gi, function(match, codeContent) {
var decoded = codeContent.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&#39;/g, "'");
var uid = 'hprev_' + Math.random().toString(36).substr(2, 9);
return '<div class="html-preview-block" data-uid="' + uid + '">' +
'<div class="html-preview-bar"><span>HTML</span><button class="html-preview-btn" data-uid="' + uid + '">Preview</button></div>' +
'<pre><code class="language-html">' + codeContent + '</code></pre>' +
'<textarea class="html-preview-src" style="display:none">' + escapeHtml(decoded) + '</textarea>' +
'</div>';
});
return html;
} }
return text.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>'); return text.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>');
} }
@@ -405,6 +416,43 @@
}); });
} }
}); });
container.querySelectorAll('.html-preview-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var uid = this.getAttribute('data-uid');
var wrapper = container.querySelector('.html-preview-block[data-uid="' + uid + '"]');
if (!wrapper) return;
var srcEl = wrapper.querySelector('.html-preview-src');
if (!srcEl) return;
var src = srcEl.textContent;
openHtmlPreview(src);
});
});
}
function openHtmlPreview(htmlSource) {
var preview = $('#html-preview');
var frame = $('#html-preview-frame');
var title = $('#html-preview-title');
if (!preview || !frame) return;
if (title) title.textContent = 'HTML Preview';
preview.style.display = 'flex';
var doc = frame.contentDocument || frame.contentWindow.document;
doc.open();
doc.write(htmlSource);
doc.close();
}
function closeHtmlPreview() {
var preview = $('#html-preview');
if (preview) preview.style.display = 'none';
}
function openHtmlInNewTab(htmlSource) {
var blob = new Blob([htmlSource], { type: 'text/html' });
var url = URL.createObjectURL(blob);
window.open(url, '_blank');
setTimeout(function() { URL.revokeObjectURL(url); }, 60000);
} }
function highlightFilePaths(html) { function highlightFilePaths(html) {
@@ -3519,6 +3567,14 @@
if (approvalState && approvalState.onApprove) approvalState.onApprove(); if (approvalState && approvalState.onApprove) approvalState.onApprove();
}); });
$('#approval-deny').addEventListener('click', closeApproval); $('#approval-deny').addEventListener('click', closeApproval);
$('#html-preview-close').addEventListener('click', closeHtmlPreview);
$('#html-preview-newtab').addEventListener('click', function() {
var frame = $('#html-preview-frame');
if (!frame) return;
var doc = frame.contentDocument || frame.contentWindow.document;
var src = doc.documentElement.outerHTML;
openHtmlInNewTab('<!DOCTYPE html>' + src);
});
$('#theme-toggle-header').addEventListener('click', toggleTheme); $('#theme-toggle-header').addEventListener('click', toggleTheme);