fix: move Copy/Insert/Replace toolbar INSIDE each code block
The previous commit put the toolbar at the top of every assistant
message. The intent was actually to put the buttons inside the
fenced code blocks (the parts wrapped in ```python etc.), not above
the whole result section.
Changes:
- opencode_inline_prompt.ts: removed the message-level toolbar on
.opencode-msg-assistant. Instead, after marked.parse the assistant
markdown, for every rendered <pre> we wrap it in
.opencode-code-block and prepend a small toolbar with the same
three buttons. Each button acts on the code text of THAT specific
block (snapshot of <code>.textContent taken before DOM mutation,
so the toolbar label text never leaks into the copied/inserted/
replaced value). Messages with no code fences get no toolbar.
- style/base.css: replaced .opencode-msg-toolbar / .opencode-msg-btn
with .opencode-code-block / .opencode-code-toolbar /
.opencode-code-btn. The wrapped <pre> connects visually to the
toolbar (no top border/radius, no top margin) so each fenced
block reads as one unit (GitHub-style).
- Tests: updated the marked mock to produce real <pre><code> for
fenced code (so the per-block post-processing can find them)
and <h1>/<p> for headings. The 4 button tests now look for the
toolbar inside .opencode-code-block and assert the button acts
on the code block text ('print(1)'), not the whole markdown.
Added a test that an assistant message with no code fences has
no toolbar.
Verification: pytest 37/37, jest 34/34, build OK.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
cb3576c83a
commit
b5642ec5cd
@@ -225,40 +225,62 @@ export class OpenCodeInlinePrompt extends Widget {
|
||||
if (msg.role === 'user') {
|
||||
wrap.textContent = msg.content;
|
||||
} else {
|
||||
// Assistant: small action toolbar (copy / insert at cursor /
|
||||
// replace cell) + the rendered markdown content below.
|
||||
const toolbar = document.createElement('div');
|
||||
toolbar.className = 'opencode-msg-toolbar';
|
||||
const content = msg.content;
|
||||
const mkBtn = (label: string, title: string, onClick: () => void) => {
|
||||
const b = document.createElement('button');
|
||||
b.type = 'button';
|
||||
b.className = 'opencode-msg-btn';
|
||||
b.textContent = label;
|
||||
b.title = title;
|
||||
b.addEventListener('click', onClick);
|
||||
return b;
|
||||
};
|
||||
toolbar.appendChild(
|
||||
mkBtn('📋 复制', '复制内容到剪贴板', () => {
|
||||
void this._copyToClipboard(content);
|
||||
})
|
||||
);
|
||||
toolbar.appendChild(
|
||||
mkBtn('↪ 插入', '在光标位置插入内容', () => {
|
||||
this._insertAtCursor(content);
|
||||
})
|
||||
);
|
||||
toolbar.appendChild(
|
||||
mkBtn('⟳ 替换', '替换整个单元格内容', () => {
|
||||
this._replaceCell(content);
|
||||
})
|
||||
);
|
||||
wrap.appendChild(toolbar);
|
||||
|
||||
// Assistant: render the markdown, then for every fenced code
|
||||
// block (<pre>) attach a small toolbar with Copy / Insert /
|
||||
// Replace buttons that act on THAT block's code (not the whole
|
||||
// message). Messages without code fences get no toolbar.
|
||||
const rendered = document.createElement('div');
|
||||
rendered.className = 'opencode-msg-content';
|
||||
rendered.innerHTML = marked.parse(content) as string;
|
||||
rendered.innerHTML = marked.parse(msg.content) as string;
|
||||
|
||||
const codeBlocks = rendered.querySelectorAll('pre');
|
||||
codeBlocks.forEach(pre => {
|
||||
const code = pre.querySelector('code');
|
||||
if (!code) {
|
||||
return;
|
||||
}
|
||||
// Snapshot the code text before we mutate the DOM so the
|
||||
// buttons act on the exact code inside this block (no
|
||||
// surrounding ```fences```, no toolbar label text).
|
||||
const codeText = code.textContent ?? '';
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'opencode-code-block';
|
||||
const toolbar = document.createElement('div');
|
||||
toolbar.className = 'opencode-code-toolbar';
|
||||
const mkBtn = (
|
||||
label: string,
|
||||
title: string,
|
||||
onClick: () => void
|
||||
) => {
|
||||
const b = document.createElement('button');
|
||||
b.type = 'button';
|
||||
b.className = 'opencode-code-btn';
|
||||
b.textContent = label;
|
||||
b.title = title;
|
||||
b.addEventListener('click', onClick);
|
||||
return b;
|
||||
};
|
||||
toolbar.appendChild(
|
||||
mkBtn('📋 复制', '复制代码', () => {
|
||||
void this._copyToClipboard(codeText);
|
||||
})
|
||||
);
|
||||
toolbar.appendChild(
|
||||
mkBtn('↪ 插入', '在光标位置插入', () => {
|
||||
this._insertAtCursor(codeText);
|
||||
})
|
||||
);
|
||||
toolbar.appendChild(
|
||||
mkBtn('⟳ 替换', '替换单元格内容', () => {
|
||||
this._replaceCell(codeText);
|
||||
})
|
||||
);
|
||||
wrap.appendChild(toolbar);
|
||||
// Move the original <pre> into the wrapper.
|
||||
pre.parentNode!.insertBefore(wrap, pre);
|
||||
wrap.appendChild(pre);
|
||||
});
|
||||
|
||||
wrap.appendChild(rendered);
|
||||
}
|
||||
this._historyEl.appendChild(wrap);
|
||||
|
||||
Reference in New Issue
Block a user