mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(ui): code-block light-mode theme + cap header height at 38px
This commit is contained in:
parent
e2df16893e
commit
fa5eaf83b4
@ -86,13 +86,15 @@
|
||||
--mc-input-border: #DDD5CC;
|
||||
--mc-input-text: #1C1410;
|
||||
|
||||
/* code blocks */
|
||||
--mc-code-bg: #1E1814;
|
||||
--mc-code-header-bg: #2A221C;
|
||||
--mc-code-header-border: rgba(255, 255, 255, 0.06);
|
||||
--mc-code-lang-color: #A09080;
|
||||
--mc-code-copy-color: #8A7A6A;
|
||||
--mc-code-text: #abb2bf;
|
||||
/* code blocks — light mode: warm beige bg, dark text */
|
||||
--mc-code-bg: #FAF6F1;
|
||||
--mc-code-header-bg: #EFE8DE;
|
||||
--mc-code-header-border: rgba(28, 20, 16, 0.08);
|
||||
--mc-code-lang-color: #6B5344;
|
||||
--mc-code-copy-color: #6B5344;
|
||||
--mc-code-copy-hover-bg: rgba(28, 20, 16, 0.06);
|
||||
--mc-code-copy-hover-color: #1C1410;
|
||||
--mc-code-text: #1C1410;
|
||||
--mc-code-shadow: rgba(0, 0, 0, 0.08);
|
||||
|
||||
/* inline code */
|
||||
@ -119,6 +121,9 @@
|
||||
--mc-thinking-text: #5A4030;
|
||||
--mc-mermaid-bg: #FAFAF8;
|
||||
--mc-mermaid-border: #DDD5CC;
|
||||
/* Code block gutter: now theme-aware along with --mc-code-bg.
|
||||
Light mode = darker gutter on light bg; dark fork below flips it. */
|
||||
--mc-code-line-number: rgba(28, 20, 16, 0.38);
|
||||
--mc-thinking-hover: rgba(217, 119, 87, 0.1);
|
||||
--mc-thinking-icon-bg: rgba(217, 119, 87, 0.12);
|
||||
--mc-thinking-border: rgba(217, 119, 87, 0.2);
|
||||
@ -203,12 +208,14 @@ html.dark {
|
||||
--mc-input-border: #3D3028;
|
||||
--mc-input-text: #F0EAE4;
|
||||
|
||||
/* code blocks */
|
||||
/* code blocks — dark mode: deep brown bg, soft text */
|
||||
--mc-code-bg: #161210;
|
||||
--mc-code-header-bg: #1E1814;
|
||||
--mc-code-header-border: rgba(255, 255, 255, 0.06);
|
||||
--mc-code-lang-color: #8A7060;
|
||||
--mc-code-copy-color: #7A6050;
|
||||
--mc-code-copy-color: #8A7060;
|
||||
--mc-code-copy-hover-bg: rgba(255, 255, 255, 0.08);
|
||||
--mc-code-copy-hover-color: #F0EAE4;
|
||||
--mc-code-text: #abb2bf;
|
||||
--mc-code-shadow: rgba(0, 0, 0, 0.2);
|
||||
|
||||
@ -236,6 +243,7 @@ html.dark {
|
||||
--mc-thinking-text: #C4A898;
|
||||
--mc-mermaid-bg: #1A1410;
|
||||
--mc-mermaid-border: #3D3028;
|
||||
--mc-code-line-number: rgba(138, 112, 96, 0.95);
|
||||
--mc-thinking-hover: rgba(224, 136, 96, 0.1);
|
||||
--mc-thinking-icon-bg: rgba(224, 136, 96, 0.12);
|
||||
--mc-thinking-border: rgba(224, 136, 96, 0.2);
|
||||
@ -369,46 +377,151 @@ html.dark body::before {
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* code-block container (from useMarkdownRenderer) */
|
||||
/* code-block container (from useMarkdownRenderer)
|
||||
---------------------------------------------------------------
|
||||
These rules live globally so they apply consistently to every
|
||||
markdown-body context (chat bubbles, agent context preview, etc.)
|
||||
and don't depend on the Vue scope hash. The ! markers on padding
|
||||
and height defeat the global `.markdown-body pre` rule and any
|
||||
stray <summary> browser defaults that previously inflated the
|
||||
header to 400+px tall. */
|
||||
.markdown-body .code-block {
|
||||
margin: 14px 0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: var(--mc-code-bg);
|
||||
border: 1px solid var(--mc-code-header-border);
|
||||
}
|
||||
.markdown-body .code-block__header {
|
||||
display: flex;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 16px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
padding: 0 14px !important;
|
||||
margin: 0 !important;
|
||||
/* Hard cap — earlier versions (auto / min-height: 0) were being inflated
|
||||
by the SVG inside the copy button rendering at its UA-default 300×150
|
||||
when DOMPurify normalised attributes. Fixed height + overflow:hidden
|
||||
defeats that regardless of child sizing. */
|
||||
height: 38px !important;
|
||||
min-height: 38px !important;
|
||||
max-height: 38px !important;
|
||||
line-height: 1 !important;
|
||||
font-size: 12px !important;
|
||||
background: var(--mc-code-header-bg);
|
||||
border-bottom: 1px solid var(--mc-code-header-border);
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* Defence in depth: pin the icon to its declared 14×14 even if the inline
|
||||
width/height attrs get normalised away. Without this the SVG can default
|
||||
to 300×150, which is what was inflating the header. */
|
||||
.markdown-body .code-block__header svg {
|
||||
width: 14px !important;
|
||||
height: 14px !important;
|
||||
flex-shrink: 0;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.markdown-body .code-block__header > * {
|
||||
flex-shrink: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
.markdown-body .code-block__lang {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
color: var(--mc-code-lang-color);
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.markdown-body .code-block__lines {
|
||||
/* Hidden by default — only shown for collapsed details (rule below). */
|
||||
display: none;
|
||||
}
|
||||
.markdown-body .code-block__copy {
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
padding: 3px 8px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: #94a3b8;
|
||||
color: var(--mc-code-copy-color);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
.markdown-body .code-block__copy:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #e2e8f0;
|
||||
background: var(--mc-code-copy-hover-bg);
|
||||
color: var(--mc-code-copy-hover-color);
|
||||
}
|
||||
/* Kill the GLOBAL .markdown-body pre padding (16px) and margin (14px) — those
|
||||
are what created the visible "tall header" gap between summary and code. */
|
||||
.markdown-body .code-block pre {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
.markdown-body .code-block pre code,
|
||||
.markdown-body .code-block pre code.hljs {
|
||||
background: transparent !important;
|
||||
padding: 0 !important;
|
||||
color: var(--mc-code-text);
|
||||
display: block;
|
||||
line-height: 1.55;
|
||||
}
|
||||
/* Line-numbered gutter via CSS counter */
|
||||
.markdown-body .code-block ol.hljs-lines {
|
||||
counter-reset: ln;
|
||||
padding: 12px 0;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
list-style: none;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
||||
}
|
||||
.markdown-body .code-block ol.hljs-lines > li {
|
||||
counter-increment: ln;
|
||||
display: block;
|
||||
padding: 0 16px 0 4em;
|
||||
position: relative;
|
||||
white-space: pre;
|
||||
min-height: 1.55em;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.markdown-body .code-block ol.hljs-lines > li::before {
|
||||
content: counter(ln);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 3em;
|
||||
text-align: right;
|
||||
color: var(--mc-code-line-number);
|
||||
user-select: none;
|
||||
font-size: 0.85em;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
/* <details>/<summary> reset — strip browser-default marker spacing that
|
||||
pushed the header sky-high. */
|
||||
.markdown-body details.code-block--collapsible > summary {
|
||||
display: block;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.markdown-body details.code-block--collapsible > summary::-webkit-details-marker,
|
||||
.markdown-body details.code-block--collapsible > summary::marker {
|
||||
display: none;
|
||||
content: '';
|
||||
}
|
||||
/* Show the line-count badge only when the block is collapsed. */
|
||||
.markdown-body details.code-block--collapsible:not([open]) .code-block__lines {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
color: var(--mc-code-lang-color);
|
||||
margin: 0 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
background: var(--mc-code-copy-hover-bg);
|
||||
user-select: none;
|
||||
}
|
||||
.markdown-body details.code-block--collapsible:not([open]) > pre {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ECharts chart block */
|
||||
@ -584,23 +697,43 @@ html.dark .mc-surface-card {
|
||||
}
|
||||
}
|
||||
|
||||
/* highlight.js token colors (One Dark inspired — works on dark bg) */
|
||||
.hljs-keyword, .hljs-selector-tag, .hljs-built_in, .hljs-literal { color: #c678dd; }
|
||||
.hljs-string, .hljs-attr { color: #98c379; }
|
||||
.hljs-number, .hljs-literal { color: #d19a66; }
|
||||
.hljs-comment, .hljs-quote { color: #5c6370; font-style: italic; }
|
||||
.hljs-function .hljs-title, .hljs-title.function_ { color: #61afef; }
|
||||
.hljs-type, .hljs-title.class_ { color: #e5c07b; }
|
||||
.hljs-variable, .hljs-template-variable { color: #e06c75; }
|
||||
.hljs-tag { color: #e06c75; }
|
||||
.hljs-name { color: #e06c75; }
|
||||
.hljs-attribute { color: #d19a66; }
|
||||
.hljs-symbol, .hljs-bullet { color: #56b6c2; }
|
||||
.hljs-meta { color: #61afef; }
|
||||
.hljs-params { color: #abb2bf; }
|
||||
.hljs-section { color: #e06c75; font-weight: bold; }
|
||||
.hljs-addition { color: #98c379; background: rgba(152, 195, 121, 0.1); }
|
||||
.hljs-deletion { color: #e06c75; background: rgba(224, 108, 117, 0.1); }
|
||||
/* highlight.js token colors — Atom One Light (works on the warm beige
|
||||
light-mode code bg). Dark mode swaps to One Dark in the html.dark block
|
||||
below so colours stay legible against the deep brown #161210. */
|
||||
.hljs-keyword, .hljs-selector-tag, .hljs-built_in, .hljs-literal { color: #a626a4; }
|
||||
.hljs-string, .hljs-attr { color: #50a14f; }
|
||||
.hljs-number { color: #c18401; }
|
||||
.hljs-comment, .hljs-quote { color: #a0a1a7; font-style: italic; }
|
||||
.hljs-function .hljs-title, .hljs-title.function_ { color: #4078f2; }
|
||||
.hljs-type, .hljs-title.class_ { color: #c18401; }
|
||||
.hljs-variable, .hljs-template-variable { color: #e45649; }
|
||||
.hljs-tag { color: #e45649; }
|
||||
.hljs-name { color: #e45649; }
|
||||
.hljs-attribute { color: #c18401; }
|
||||
.hljs-symbol, .hljs-bullet { color: #0184bc; }
|
||||
.hljs-meta { color: #4078f2; }
|
||||
.hljs-params { color: #383a42; }
|
||||
.hljs-section { color: #e45649; font-weight: bold; }
|
||||
.hljs-addition { color: #50a14f; background: rgba(80, 161, 79, 0.1); }
|
||||
.hljs-deletion { color: #e45649; background: rgba(228, 86, 73, 0.1); }
|
||||
|
||||
/* Dark mode: One Dark token palette */
|
||||
html.dark .hljs-keyword, html.dark .hljs-selector-tag, html.dark .hljs-built_in, html.dark .hljs-literal { color: #c678dd; }
|
||||
html.dark .hljs-string, html.dark .hljs-attr { color: #98c379; }
|
||||
html.dark .hljs-number { color: #d19a66; }
|
||||
html.dark .hljs-comment, html.dark .hljs-quote { color: #5c6370; font-style: italic; }
|
||||
html.dark .hljs-function .hljs-title, html.dark .hljs-title.function_ { color: #61afef; }
|
||||
html.dark .hljs-type, html.dark .hljs-title.class_ { color: #e5c07b; }
|
||||
html.dark .hljs-variable, html.dark .hljs-template-variable { color: #e06c75; }
|
||||
html.dark .hljs-tag { color: #e06c75; }
|
||||
html.dark .hljs-name { color: #e06c75; }
|
||||
html.dark .hljs-attribute { color: #d19a66; }
|
||||
html.dark .hljs-symbol, html.dark .hljs-bullet { color: #56b6c2; }
|
||||
html.dark .hljs-meta { color: #61afef; }
|
||||
html.dark .hljs-params { color: #abb2bf; }
|
||||
html.dark .hljs-section { color: #e06c75; font-weight: bold; }
|
||||
html.dark .hljs-addition { color: #98c379; background: rgba(152, 195, 121, 0.1); }
|
||||
html.dark .hljs-deletion { color: #e06c75; background: rgba(224, 108, 117, 0.1); }
|
||||
|
||||
.markdown-body p { margin: 8px 0; }
|
||||
.markdown-body ul, .markdown-body ol { padding-left: 1.5rem; margin: 8px 0; }
|
||||
|
||||
@ -1551,108 +1551,11 @@ watch(isGenerating, (generating) => {
|
||||
font-size: 0.92em;
|
||||
}
|
||||
|
||||
.markdown-body :deep(.code-block) {
|
||||
margin: 14px 0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: var(--mc-code-bg, #1e293b);
|
||||
}
|
||||
|
||||
.markdown-body :deep(.code-block__header) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 16px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.markdown-body :deep(.code-block__lang) {
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.markdown-body :deep(.code-block__copy) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.markdown-body :deep(.code-block__copy:hover) {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.markdown-body :deep(.code-block pre) {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* ===== Code block: line numbers via CSS counter (keeps DOM minimal) ===== */
|
||||
.markdown-body :deep(.code-block ol.hljs-lines) {
|
||||
counter-reset: ln;
|
||||
padding: 12px 16px;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
||||
}
|
||||
.markdown-body :deep(.code-block ol.hljs-lines > li) {
|
||||
counter-increment: ln;
|
||||
padding-left: 3.5em;
|
||||
position: relative;
|
||||
white-space: pre;
|
||||
min-height: 1.3em;
|
||||
}
|
||||
.markdown-body :deep(.code-block ol.hljs-lines > li::before) {
|
||||
content: counter(ln);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 2.8em;
|
||||
text-align: right;
|
||||
color: #94a3b8;
|
||||
user-select: none;
|
||||
opacity: 0.55;
|
||||
font-size: 0.92em;
|
||||
}
|
||||
/* Highlighted line spans inherit highlight.js colours; keep them on the line itself. */
|
||||
|
||||
/* ===== Code block: collapsible (long blocks / large JSON) ===== */
|
||||
.markdown-body :deep(details.code-block--collapsible) {
|
||||
/* Reuse the .code-block visuals — already applied via the shared class. */
|
||||
}
|
||||
.markdown-body :deep(details.code-block--collapsible > summary) {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.markdown-body :deep(details.code-block--collapsible > summary::-webkit-details-marker) {
|
||||
display: none;
|
||||
}
|
||||
.markdown-body :deep(.code-block__lines) {
|
||||
display: none;
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
margin: 0 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
user-select: none;
|
||||
}
|
||||
/* Show line-count badge only when the block is collapsible AND collapsed. */
|
||||
.markdown-body :deep(details.code-block--collapsible:not([open]) .code-block__lines) {
|
||||
display: inline-block;
|
||||
}
|
||||
.markdown-body :deep(details.code-block--collapsible:not([open]) > pre) {
|
||||
display: none;
|
||||
}
|
||||
/* Code-block CSS lives globally in main.css now (.markdown-body .code-block*)
|
||||
so the rules apply consistently across MessageBubble, AgentContext, and
|
||||
any future markdown-body context, and don't depend on Vue's per-component
|
||||
scope hash. Keep this comment as a breadcrumb so future edits don't get
|
||||
re-added here by reflex. */
|
||||
|
||||
/* ===== Mermaid block ===== */
|
||||
.markdown-body :deep(.mermaid-block) {
|
||||
|
||||
@ -220,27 +220,32 @@ const customRenderer = {
|
||||
// giant JSON blobs, which are typically noisy tool-call output.
|
||||
const openByDefault = !isLongJson
|
||||
|
||||
const headerHtml = `<div class="code-block__header">`
|
||||
+ `<span class="code-block__lang">${escapeHtml(langLabel)}</span>`
|
||||
// Header content: lang badge (left) — line-count badge (only shown when
|
||||
// collapsed) — copy button (right). We render the SAME inner content into
|
||||
// either a <div class="code-block__header"> (non-collapsible) or directly
|
||||
// into <summary class="code-block__header"> (collapsible). Nesting a div
|
||||
// inside <summary> caused weird browser-native height behavior and made
|
||||
// the header visibly inflate; flattening fixes it.
|
||||
const headerInner = `<span class="code-block__lang">${escapeHtml(langLabel)}</span>`
|
||||
+ `<span class="code-block__lines">${lineCount} lines</span>`
|
||||
+ `<button class="code-block__copy" type="button" data-code="${encodedCode}" aria-label="Copy code">`
|
||||
+ `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`
|
||||
+ `<span class="code-block__copy-text">Copy</span>`
|
||||
+ `</button></div>`
|
||||
+ `</button>`
|
||||
|
||||
const codeBody = `<pre><code class="hljs${langClass}">${linesHtml}</code></pre>`
|
||||
|
||||
if (shouldCollapse) {
|
||||
// <details>/<summary> gives a native, no-JS collapse affordance. The
|
||||
// summary holds the header (lang badge + line count + copy button); the
|
||||
// <pre> sits in the <details> body and is hidden by CSS until expanded.
|
||||
const openAttr = openByDefault ? ' open' : ''
|
||||
return `<details class="code-block code-block--collapsible"${openAttr}>`
|
||||
+ `<summary>${headerHtml}</summary>`
|
||||
+ `<summary class="code-block__header">${headerInner}</summary>`
|
||||
+ codeBody
|
||||
+ `</details>`
|
||||
}
|
||||
return `<div class="code-block">${headerHtml}${codeBody}</div>`
|
||||
return `<div class="code-block">`
|
||||
+ `<div class="code-block__header">${headerInner}</div>`
|
||||
+ codeBody
|
||||
+ `</div>`
|
||||
},
|
||||
|
||||
link({ href, title, tokens }: Tokens.Link): string {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user