Skip to content

Commit fe3263b

Browse files
committed
feat: add table horizontal scroll and inline color swatches in AI chat
Tables now use display:block with overflow-x:auto so wide tables scroll horizontally instead of crushing columns. Cells use white-space:nowrap to prevent character-level wrapping. Color hex codes in assistant messages are post-processed into inline swatches showing the actual color next to the code.
1 parent eec9ac4 commit fe3263b

File tree

2 files changed

+85
-1
lines changed

2 files changed

+85
-1
lines changed

src/core-ai/AIChatPanel.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,13 +1173,73 @@ define(function (require, exports, module) {
11731173
if ($target.length) {
11741174
try {
11751175
$target.html(marked.parse(_segmentText, { breaks: true, gfm: true }));
1176+
_enhanceColorCodes($target);
11761177
} catch (e) {
11771178
$target.text(_segmentText);
11781179
}
11791180
_scrollToBottom();
11801181
}
11811182
}
11821183

1184+
const HEX_COLOR_RE = /#[a-f0-9]{3,8}\b/gi;
1185+
1186+
/**
1187+
* Scan text nodes inside an element for hex color codes and wrap each
1188+
* with an inline swatch element showing the actual color.
1189+
*/
1190+
function _enhanceColorCodes($el) {
1191+
// Walk text nodes inside the element, but skip <pre> blocks and already-enhanced swatches
1192+
const walker = document.createTreeWalker(
1193+
$el[0],
1194+
NodeFilter.SHOW_TEXT,
1195+
{
1196+
acceptNode: function (node) {
1197+
const parent = node.parentNode;
1198+
if (parent.closest && parent.closest("pre, .ai-color-swatch")) {
1199+
return NodeFilter.FILTER_REJECT;
1200+
}
1201+
return HEX_COLOR_RE.test(node.nodeValue)
1202+
? NodeFilter.FILTER_ACCEPT
1203+
: NodeFilter.FILTER_REJECT;
1204+
}
1205+
}
1206+
);
1207+
1208+
const textNodes = [];
1209+
let n;
1210+
while ((n = walker.nextNode())) {
1211+
textNodes.push(n);
1212+
}
1213+
1214+
textNodes.forEach(function (textNode) {
1215+
const text = textNode.nodeValue;
1216+
HEX_COLOR_RE.lastIndex = 0;
1217+
const frag = document.createDocumentFragment();
1218+
let lastIndex = 0;
1219+
let match;
1220+
while ((match = HEX_COLOR_RE.exec(text)) !== null) {
1221+
// Append any text before the match
1222+
if (match.index > lastIndex) {
1223+
frag.appendChild(document.createTextNode(text.slice(lastIndex, match.index)));
1224+
}
1225+
const color = match[0];
1226+
const swatch = document.createElement("span");
1227+
swatch.className = "ai-color-swatch";
1228+
swatch.style.setProperty("--swatch-color", color);
1229+
const preview = document.createElement("span");
1230+
preview.className = "ai-color-swatch-preview";
1231+
swatch.appendChild(preview);
1232+
swatch.appendChild(document.createTextNode(color));
1233+
frag.appendChild(swatch);
1234+
lastIndex = HEX_COLOR_RE.lastIndex;
1235+
}
1236+
if (lastIndex < text.length) {
1237+
frag.appendChild(document.createTextNode(text.slice(lastIndex)));
1238+
}
1239+
textNode.parentNode.replaceChild(frag, textNode);
1240+
});
1241+
}
1242+
11831243
function _appendToolIndicator(toolName, toolId) {
11841244
// Remove thinking indicator on first content
11851245
if (!_hasReceivedContent) {

src/styles/Extn-AIChatPanel.less

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,16 +201,21 @@
201201
h4 { font-size: @sidebar-content-font-size; margin: 6px 0 2px 0; opacity: 0.85; }
202202

203203
table {
204-
width: 100%;
204+
display: block;
205+
overflow-x: auto;
205206
border-collapse: collapse;
206207
margin: 6px 0;
207208
font-size: @sidebar-small-font-size;
209+
cursor: default;
208210
}
209211

210212
th, td {
211213
padding: 4px 8px;
212214
text-align: left;
213215
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
216+
max-width: 50%;
217+
white-space: nowrap;
218+
overflow-wrap: normal;
214219
}
215220

216221
th {
@@ -460,6 +465,25 @@
460465
}
461466
}
462467

468+
/* ── Inline color swatch ───────────────────────────────────────────── */
469+
.ai-color-swatch {
470+
display: inline-flex;
471+
align-items: center;
472+
gap: 3px;
473+
font-family: 'SourceCodePro-Medium', 'SourceCodePro', monospace;
474+
font-size: @sidebar-small-font-size;
475+
}
476+
477+
.ai-color-swatch-preview {
478+
display: inline-block;
479+
width: 10px;
480+
height: 10px;
481+
border-radius: 2px;
482+
background-color: var(--swatch-color);
483+
border: 1px solid rgba(255, 255, 255, 0.15);
484+
flex-shrink: 0;
485+
}
486+
463487
@keyframes ai-dot-pulse {
464488
0%, 60%, 100% { opacity: 0.2; transform: scale(0.8); }
465489
30% { opacity: 0.8; transform: scale(1); }

0 commit comments

Comments
 (0)