From 326039c4e56524bdfe07788063a9fa5fa55a0080 Mon Sep 17 00:00:00 2001 From: Codebuddy Date: Thu, 18 Jun 2026 16:46:35 +0800 Subject: [PATCH] =?utf8?q?=E4=BF=AE=E5=A4=8D=E7=B2=98=E8=B4=B4=E6=A1=86?= =?utf8?q?=E5=85=B3=E9=97=AD=E4=B8=8E=E7=BB=88=E7=AB=AF=E5=A4=8D=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 问题1: 粘贴框无法关闭 - ESC 监听提升到 document 级(避免焦点不在 pasteInput 时失效) - 点击遮罩层(modal 外部)关闭 modal - 移除 pasteInput 上的重复 ESC 处理 问题2: 终端选区无法复制 - 加 attachCustomKeyEventHandler: 有选区时 Ctrl+C 复制到剪贴板(不发中断) - 优先用 navigator.clipboard.writeText,HTTP 下用临时 textarea + execCommand 兜底 - 无选区时 Ctrl+C 正常发中断信号 --- frontend/index.html | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 9f30e5c..285e8ba 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -435,6 +435,18 @@ html { font-size: 15px; } html, body { height: 100%; background: #1a1a2e; color: } document.getElementById('btn-paste').onclick = openPasteModal; + // ESC 关闭 modal(监听 document,避免焦点不在 pasteInput 时失效) + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && pasteModal.style.display === 'flex') { + e.preventDefault(); + closePasteModal(); + } + }); + // 点击遮罩层(modal 外部)关闭 + pasteModal.addEventListener('click', (e) => { + if (e.target === pasteModal) closePasteModal(); + }); + // textarea 的 paste 事件:文字直接插入,图片上传后加 chip pasteInput.addEventListener('paste', async (e) => { if (!e.clipboardData) return; @@ -516,10 +528,8 @@ html { font-size: 15px; } html, body { height: 100%; background: #1a1a2e; color: if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); sendPasteContent(); - } else if (e.key === 'Escape') { - e.preventDefault(); - closePasteModal(); } + // ESC 关闭由 document 级监听器统一处理 }); function initTerminal() { @@ -544,6 +554,30 @@ html { font-size: 15px; } html, body { height: 100%; background: #1a1a2e; color: term.open(termContainer); fitAddon.fit(); + // 复制支持:有选区时 Ctrl+C 复制到剪贴板(不发中断信号) + // Ctrl+Shift+C 也复制(xterm 默认行为) + term.attachCustomKeyEventHandler((e) => { + // 复制:Ctrl+C 且有选区 + if (e.type === 'keydown' && e.ctrlKey && !e.shiftKey && !e.altKey && e.key === 'c') { + const sel = term.getSelection(); + if (sel) { + // 写入剪贴板 + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(sel).catch(() => {}); + } else { + // HTTP 下 navigator.clipboard 可能不可用,用临时 textarea 兜底 + const ta = document.createElement('textarea'); + ta.value = sel; ta.style.position = 'fixed'; ta.style.opacity = '0'; + document.body.appendChild(ta); ta.select(); + try { document.execCommand('copy'); } catch(_) {} + document.body.removeChild(ta); + } + return false; // 不传给终端 + } + } + return true; + }); + term.onData(data => sendInput(data)); term.onResize(({ cols, rows }) => { if (ws && ws.readyState === WebSocket.OPEN && pid) ws.send(JSON.stringify({ type: 'resize', cols, rows })); -- 2.43.0