Files
notebook-ai-extension/style/base.css
T
tao.chenandClaude Fable 5 b5642ec5cd 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>
2026-07-23 19:19:29 +08:00

213 lines
5.4 KiB
CSS

/*
See the JupyterLab Developer Guide for useful CSS Patterns:
https://jupyterlab.readthedocs.io/en/stable/developer/css.html
*/
/* Single AI action button inside the native Cell toolbar (active cell, top-right). */
.opencode-cell-actions {
display: flex;
align-items: center;
}
.opencode-cell-actions .opencode-btn {
border: none;
background: transparent;
padding: 0 6px;
font-size: var(--jp-ui-font-size1);
line-height: 20px;
cursor: pointer;
white-space: nowrap;
}
.opencode-cell-actions .opencode-btn:hover:enabled {
background: var(--jp-layout-color2);
border-radius: 2px;
}
.opencode-cell-actions .opencode-btn:disabled {
opacity: 0.4;
cursor: default;
}
/* Inline prompt panel attached to the cell when the AI button is clicked. */
.opencode-inline-prompt {
display: flex;
flex-direction: column;
gap: 4px;
padding: 6px;
margin: 4px 8px;
border: 1px solid var(--jp-border-color2, #ddd);
border-radius: 4px;
background: var(--jp-layout-color1, #fff);
}
.opencode-inline-prompt .opencode-prompt-providers {
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
}
.opencode-inline-prompt .opencode-prompt-providers label {
display: flex;
align-items: center;
gap: 4px;
font-size: var(--jp-ui-font-size1);
color: var(--jp-ui-font-color1, #333);
}
.opencode-inline-prompt .opencode-provider-select,
.opencode-inline-prompt .opencode-model-select {
flex: 1;
min-width: 120px;
padding: 2px 4px;
font-size: var(--jp-ui-font-size1);
font-family: inherit;
border: 1px solid var(--jp-border-color2, #ccc);
border-radius: 3px;
background: var(--jp-layout-color1, #fff);
}
.opencode-inline-prompt .opencode-provider-select:disabled,
.opencode-inline-prompt .opencode-model-select:disabled {
opacity: 0.5;
cursor: default;
}
/* Scrollable history of the current session's messages. The owning
cell action populates it via setMessages(). max-height keeps the
inline prompt compact; the user scrolls to see older messages. */
.opencode-inline-prompt .opencode-inline-history {
max-height: 320px;
overflow-y: auto;
margin: 4px 0;
padding: 4px 6px;
border: 1px solid var(--jp-border-color1, #c0c0c0);
border-radius: 3px;
background: var(--jp-layout-color2, #f7f7f7);
font-size: var(--jp-ui-font-size1);
line-height: 1.45;
}
.opencode-inline-prompt .opencode-msg {
margin-bottom: 6px;
}
.opencode-inline-prompt .opencode-msg:last-child {
margin-bottom: 0;
}
.opencode-inline-prompt .opencode-msg-user {
white-space: pre-wrap;
padding: 4px 6px;
border-radius: 3px;
background: var(--jp-layout-color3, #eaeaea);
color: var(--jp-ui-font-color1, #333);
}
.opencode-inline-prompt .opencode-msg-assistant {
padding: 4px 6px;
color: var(--jp-ui-font-color0, #000);
}
/* Per-code-block toolbar: wraps every rendered <pre> so the Copy /
Insert / Replace buttons live INSIDE the fenced section (not as a
message-level header). */
.opencode-inline-prompt .opencode-code-block {
margin: 6px 0;
}
.opencode-inline-prompt .opencode-code-toolbar {
display: flex;
gap: 4px;
padding: 2px 6px;
background: var(--jp-layout-color2, #f0f0f0);
border: 1px solid var(--jp-border-color2, #ccc);
border-bottom: none;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
font-size: var(--jp-ui-font-size0, 11px);
align-items: center;
}
.opencode-inline-prompt .opencode-code-btn {
border: 1px solid var(--jp-border-color2, #ccc);
background: var(--jp-layout-color1, #fff);
padding: 0 6px;
border-radius: 3px;
cursor: pointer;
font-size: var(--jp-ui-font-size0, 11px);
line-height: 1.4;
color: var(--jp-ui-font-color1, #333);
}
.opencode-inline-prompt .opencode-code-btn:hover {
background: var(--jp-layout-color2, #f7f7f7);
border-color: var(--jp-border-color1, #999);
}
/* The wrapped <pre> connects visually to the toolbar above (no top
border/radius, no top margin) so the block reads as one unit. */
.opencode-inline-prompt .opencode-code-block > pre {
margin-top: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.opencode-inline-prompt .opencode-inline-history pre {
background: var(--jp-layout-color1, #fff);
padding: 6px 8px;
border-radius: 3px;
overflow-x: auto;
margin: 4px 0;
}
.opencode-inline-prompt .opencode-inline-history code {
font-family: var(--jp-code-font-family, monospace);
font-size: var(--jp-code-font-size, 13px);
}
.opencode-inline-prompt .opencode-inline-history p {
margin: 2px 0;
}
.opencode-inline-prompt .opencode-inline-history h1,
.opencode-inline-prompt .opencode-inline-history h2,
.opencode-inline-prompt .opencode-inline-history h3 {
margin: 6px 0 4px;
font-size: var(--jp-ui-font-size2, 14px);
}
.opencode-inline-prompt textarea {
width: 100%;
resize: vertical;
font-family: var(--jp-code-font-family, monospace);
font-size: var(--jp-code-font-size, 13px);
box-sizing: border-box;
}
.opencode-inline-prompt .opencode-inline-actions {
display: flex;
justify-content: flex-end;
gap: 4px;
}
.opencode-inline-prompt .opencode-inline-actions button {
border: 1px solid var(--jp-border-color2, #ccc);
background: var(--jp-layout-color2, #f7f7f7);
padding: 2px 8px;
border-radius: 3px;
cursor: pointer;
font-size: var(--jp-ui-font-size1);
}
.opencode-inline-prompt .opencode-inline-actions button:hover:enabled {
background: var(--jp-layout-color3, #eaeaea);
}
.opencode-inline-prompt .opencode-inline-actions button:disabled {
opacity: 0.4;
cursor: default;
}