@rooode/dsh-plugin-preview 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/lib/client.js +1568 -274
- package/lib/index.js +206 -2
- package/package.json +4 -3
package/lib/client.js
CHANGED
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
const STORAGE_KEY_WIDTH = 'dsh:preview:panel_width:v1';
|
|
26
26
|
const STORAGE_KEY_TREE_OPEN = 'dsh:preview:tree:open:v1';
|
|
27
27
|
const STORAGE_KEY_TREE_WIDTH = 'dsh:preview:tree:width:v1';
|
|
28
|
-
const
|
|
28
|
+
const STORAGE_KEY_MANUAL_WS = 'dsh:preview:manual_workspace:v1';
|
|
29
|
+
const DEFAULT_PANEL_WIDTH = 620;
|
|
29
30
|
const MIN_PANEL_WIDTH = 380;
|
|
30
31
|
const DEFAULT_TREE_WIDTH = 240;
|
|
31
32
|
const MIN_TREE_WIDTH = 180;
|
|
@@ -50,11 +51,20 @@
|
|
|
50
51
|
let globalActiveTabId = null;
|
|
51
52
|
let globalIsPanelOpen = false;
|
|
52
53
|
let globalIsTreeOpen = true;
|
|
54
|
+
let globalIsMaximized = false;
|
|
55
|
+
let globalManualWorkspace = null;
|
|
53
56
|
let globalPanelWidth = DEFAULT_PANEL_WIDTH;
|
|
54
57
|
let globalTreeWidth = DEFAULT_TREE_WIDTH;
|
|
55
58
|
let globalLastOpenTime = 0;
|
|
56
59
|
const stateListeners = new Set();
|
|
57
60
|
|
|
61
|
+
try {
|
|
62
|
+
const savedManualWs = localStorage.getItem(STORAGE_KEY_MANUAL_WS);
|
|
63
|
+
if (savedManualWs && savedManualWs.trim()) {
|
|
64
|
+
globalManualWorkspace = savedManualWs.trim();
|
|
65
|
+
}
|
|
66
|
+
} catch (e) {}
|
|
67
|
+
|
|
58
68
|
try {
|
|
59
69
|
const savedWidth = localStorage.getItem(STORAGE_KEY_WIDTH);
|
|
60
70
|
if (savedWidth) {
|
|
@@ -121,6 +131,26 @@
|
|
|
121
131
|
notifyStateChange();
|
|
122
132
|
}
|
|
123
133
|
|
|
134
|
+
function setIsMaximized(maximized) {
|
|
135
|
+
globalIsMaximized = maximized;
|
|
136
|
+
notifyStateChange();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function setManualWorkspace(dirPath) {
|
|
140
|
+
if (dirPath && typeof dirPath === 'string' && dirPath.trim()) {
|
|
141
|
+
globalManualWorkspace = dirPath.trim();
|
|
142
|
+
try {
|
|
143
|
+
localStorage.setItem(STORAGE_KEY_MANUAL_WS, globalManualWorkspace);
|
|
144
|
+
} catch (e) {}
|
|
145
|
+
} else {
|
|
146
|
+
globalManualWorkspace = null;
|
|
147
|
+
try {
|
|
148
|
+
localStorage.removeItem(STORAGE_KEY_MANUAL_WS);
|
|
149
|
+
} catch (e) {}
|
|
150
|
+
}
|
|
151
|
+
notifyStateChange();
|
|
152
|
+
}
|
|
153
|
+
|
|
124
154
|
function setTreeWidth(width) {
|
|
125
155
|
globalTreeWidth = Math.max(MIN_TREE_WIDTH, Math.min(width, MAX_TREE_WIDTH));
|
|
126
156
|
try {
|
|
@@ -220,31 +250,143 @@
|
|
|
220
250
|
return 'code';
|
|
221
251
|
}
|
|
222
252
|
|
|
223
|
-
function
|
|
224
|
-
|
|
253
|
+
function getAllAvailableWorkspaces() {
|
|
254
|
+
const map = new Map();
|
|
255
|
+
const currentRoot = getCurrentWorkspaceRoot().replace(/[\\/]+$/, '').toLowerCase();
|
|
256
|
+
|
|
257
|
+
// 1. Collect from active & historical sessions in clientCtx.sessions
|
|
258
|
+
if (clientCtx && clientCtx.sessions) {
|
|
225
259
|
try {
|
|
226
|
-
|
|
227
|
-
|
|
260
|
+
const sSnap = clientCtx.sessions.list?.getSnapshot();
|
|
261
|
+
if (sSnap && sSnap.byId) {
|
|
262
|
+
for (const sid of Object.keys(sSnap.byId)) {
|
|
263
|
+
const s = sSnap.byId[sid];
|
|
264
|
+
if (s && s.cwd && typeof s.cwd === 'string' && s.cwd.trim()) {
|
|
265
|
+
const norm = s.cwd.trim().replace(/[\\/]+$/, '');
|
|
266
|
+
const key = norm.toLowerCase();
|
|
267
|
+
if (!map.has(key)) {
|
|
268
|
+
map.set(key, {
|
|
269
|
+
id: s.workspaceId || norm,
|
|
270
|
+
path: norm,
|
|
271
|
+
name: s.workspaceTitle || getFileName(norm) || norm,
|
|
272
|
+
isCurrent: key === currentRoot,
|
|
273
|
+
source: 'session',
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
228
278
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
279
|
+
} catch (e) {}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 2. Collect from workspaces store in clientCtx.workspaces
|
|
283
|
+
if (clientCtx && clientCtx.workspaces) {
|
|
284
|
+
try {
|
|
285
|
+
const wSnap = clientCtx.workspaces.list?.getSnapshot();
|
|
286
|
+
if (wSnap) {
|
|
287
|
+
const rawList = wSnap.workspaces || wSnap.items || wSnap.byId || [];
|
|
288
|
+
const list = Array.isArray(rawList) ? rawList : Object.values(rawList);
|
|
289
|
+
for (const w of list) {
|
|
290
|
+
const dir = (w && (w.directory || w.path || w.cwd)) || '';
|
|
291
|
+
if (dir && typeof dir === 'string' && dir.trim()) {
|
|
292
|
+
const norm = dir.trim().replace(/[\\/]+$/, '');
|
|
293
|
+
const key = norm.toLowerCase();
|
|
294
|
+
if (!map.has(key)) {
|
|
295
|
+
map.set(key, {
|
|
296
|
+
id: w.id || norm,
|
|
297
|
+
path: norm,
|
|
298
|
+
name: w.title || w.name || getFileName(norm) || norm,
|
|
299
|
+
isCurrent: key === currentRoot,
|
|
300
|
+
source: 'workspace',
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
232
305
|
}
|
|
233
306
|
} catch (e) {}
|
|
234
307
|
}
|
|
308
|
+
|
|
309
|
+
// 3. Collect from globalManualWorkspace if set
|
|
310
|
+
if (globalManualWorkspace && typeof globalManualWorkspace === 'string' && globalManualWorkspace.trim()) {
|
|
311
|
+
const norm = globalManualWorkspace.trim().replace(/[\\/]+$/, '');
|
|
312
|
+
const key = norm.toLowerCase();
|
|
313
|
+
if (!map.has(key)) {
|
|
314
|
+
map.set(key, {
|
|
315
|
+
id: 'manual',
|
|
316
|
+
path: norm,
|
|
317
|
+
name: getFileName(norm) || norm,
|
|
318
|
+
isCurrent: true,
|
|
319
|
+
source: 'manual',
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const result = Array.from(map.values());
|
|
325
|
+
// Update isCurrent flags
|
|
326
|
+
result.forEach(ws => {
|
|
327
|
+
ws.isCurrent = ws.path.replace(/[\\/]+$/, '').toLowerCase() === currentRoot;
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
return result;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function getCurrentWorkspaceRoot() {
|
|
334
|
+
// Priority 1: User manual override selection
|
|
335
|
+
if (globalManualWorkspace && typeof globalManualWorkspace === 'string' && globalManualWorkspace.trim()) {
|
|
336
|
+
return globalManualWorkspace.trim();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Priority 2: Active session working directory (cwd)
|
|
235
340
|
if (clientCtx && clientCtx.sessions) {
|
|
236
341
|
try {
|
|
237
|
-
const
|
|
238
|
-
const currentSessionId =
|
|
239
|
-
if (currentSessionId &&
|
|
240
|
-
|
|
342
|
+
const sSnap = clientCtx.sessions.list?.getSnapshot();
|
|
343
|
+
const currentSessionId = sSnap?.current;
|
|
344
|
+
if (currentSessionId && sSnap?.byId?.[currentSessionId]?.cwd) {
|
|
345
|
+
const cwd = sSnap.byId[currentSessionId].cwd;
|
|
346
|
+
if (cwd && typeof cwd === 'string' && cwd.trim()) {
|
|
347
|
+
return cwd.trim();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
} catch (e) {}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Priority 3: Current workspace in workspaces store
|
|
354
|
+
if (clientCtx && clientCtx.workspaces) {
|
|
355
|
+
try {
|
|
356
|
+
const wSnap = clientCtx.workspaces.list?.getSnapshot();
|
|
357
|
+
if (wSnap) {
|
|
358
|
+
const curId = wSnap.current;
|
|
359
|
+
if (curId && wSnap.workspaces && wSnap.workspaces[curId]) {
|
|
360
|
+
const w = wSnap.workspaces[curId];
|
|
361
|
+
const dir = w.directory || w.path || w.cwd;
|
|
362
|
+
if (dir) return dir.trim();
|
|
363
|
+
}
|
|
364
|
+
if (wSnap.byId && curId && wSnap.byId[curId]) {
|
|
365
|
+
const w = wSnap.byId[curId];
|
|
366
|
+
const dir = w.directory || w.path || w.cwd;
|
|
367
|
+
if (dir) return dir.trim();
|
|
368
|
+
}
|
|
369
|
+
if (Array.isArray(wSnap.items) && wSnap.items.length > 0) {
|
|
370
|
+
const first = wSnap.items[0];
|
|
371
|
+
const dir = first.directory || first.path || first.cwd;
|
|
372
|
+
if (dir) return dir.trim();
|
|
373
|
+
}
|
|
241
374
|
}
|
|
242
375
|
} catch (e) {}
|
|
243
376
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
377
|
+
|
|
378
|
+
// Priority 4: URL hash or search parameter
|
|
379
|
+
if (typeof location !== 'undefined') {
|
|
380
|
+
if (location.hash) {
|
|
381
|
+
const m = location.hash.match(/workspace=([^&]+)/);
|
|
382
|
+
if (m) return decodeURIComponent(m[1]);
|
|
383
|
+
}
|
|
384
|
+
if (location.search) {
|
|
385
|
+
const m = location.search.match(/workspace=([^&]+)/);
|
|
386
|
+
if (m) return decodeURIComponent(m[1]);
|
|
387
|
+
}
|
|
247
388
|
}
|
|
389
|
+
|
|
248
390
|
return '';
|
|
249
391
|
}
|
|
250
392
|
|
|
@@ -352,6 +494,97 @@
|
|
|
352
494
|
}
|
|
353
495
|
}
|
|
354
496
|
|
|
497
|
+
function renameOpenTabPaths(oldPath, newPath, isDir = false) {
|
|
498
|
+
if (!oldPath || !newPath) return;
|
|
499
|
+
let changed = false;
|
|
500
|
+
const oldNorm = oldPath.replace(/\\/g, '/').toLowerCase();
|
|
501
|
+
const newNorm = newPath.replace(/\\/g, '/');
|
|
502
|
+
|
|
503
|
+
globalTabs = globalTabs.map(tab => {
|
|
504
|
+
const tabNorm = tab.filePath.replace(/\\/g, '/').toLowerCase();
|
|
505
|
+
if (!isDir) {
|
|
506
|
+
if (tabNorm === oldNorm) {
|
|
507
|
+
changed = true;
|
|
508
|
+
const newFileName = getFileName(newPath);
|
|
509
|
+
const newExt = getFileExtension(newPath);
|
|
510
|
+
const newCat = getFileTypeCategory(newPath);
|
|
511
|
+
return {
|
|
512
|
+
...tab,
|
|
513
|
+
id: 'tab:' + newPath.toLowerCase(),
|
|
514
|
+
filePath: newPath,
|
|
515
|
+
title: newFileName,
|
|
516
|
+
extension: newExt,
|
|
517
|
+
category: newCat,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
} else {
|
|
521
|
+
if (tabNorm === oldNorm || tabNorm.startsWith(oldNorm + '/')) {
|
|
522
|
+
changed = true;
|
|
523
|
+
const subPath = tab.filePath.replace(/\\/g, '/').slice(oldNorm.length);
|
|
524
|
+
const updatedFilePath = newNorm + subPath;
|
|
525
|
+
const newFileName = getFileName(updatedFilePath);
|
|
526
|
+
const newExt = getFileExtension(updatedFilePath);
|
|
527
|
+
const newCat = getFileTypeCategory(updatedFilePath);
|
|
528
|
+
return {
|
|
529
|
+
...tab,
|
|
530
|
+
id: 'tab:' + updatedFilePath.toLowerCase(),
|
|
531
|
+
filePath: updatedFilePath,
|
|
532
|
+
title: newFileName,
|
|
533
|
+
extension: newExt,
|
|
534
|
+
category: newCat,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return tab;
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
if (changed) {
|
|
542
|
+
if (globalActiveTabId) {
|
|
543
|
+
const activeNorm = globalActiveTabId.toLowerCase();
|
|
544
|
+
const targetPrefix = 'tab:' + oldNorm;
|
|
545
|
+
if (activeNorm === targetPrefix) {
|
|
546
|
+
globalActiveTabId = 'tab:' + newPath.toLowerCase();
|
|
547
|
+
} else if (isDir && activeNorm.startsWith(targetPrefix + '/')) {
|
|
548
|
+
const sub = activeNorm.slice(targetPrefix.length);
|
|
549
|
+
globalActiveTabId = 'tab:' + newNorm.toLowerCase() + sub;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
saveTabsToStorage();
|
|
553
|
+
notifyStateChange();
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function removeOpenTabsForPath(targetPath, isDir = false) {
|
|
558
|
+
if (!targetPath) return;
|
|
559
|
+
let changed = false;
|
|
560
|
+
const delNorm = targetPath.replace(/\\/g, '/').toLowerCase();
|
|
561
|
+
const nextTabs = globalTabs.filter(tab => {
|
|
562
|
+
const tabNorm = tab.filePath.replace(/\\/g, '/').toLowerCase();
|
|
563
|
+
if (!isDir) {
|
|
564
|
+
if (tabNorm === delNorm) {
|
|
565
|
+
changed = true;
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
} else {
|
|
569
|
+
if (tabNorm === delNorm || tabNorm.startsWith(delNorm + '/')) {
|
|
570
|
+
changed = true;
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return true;
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
if (changed) {
|
|
578
|
+
globalTabs = nextTabs;
|
|
579
|
+
if (!globalTabs.some(t => t.id === globalActiveTabId)) {
|
|
580
|
+
globalActiveTabId = globalTabs.length > 0 ? globalTabs[0].id : null;
|
|
581
|
+
}
|
|
582
|
+
saveTabsToStorage();
|
|
583
|
+
notifyStateChange();
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
|
|
355
588
|
// =========================================================================
|
|
356
589
|
// Toast Notification Utility
|
|
357
590
|
// =========================================================================
|
|
@@ -807,12 +1040,262 @@
|
|
|
807
1040
|
}
|
|
808
1041
|
|
|
809
1042
|
// =========================================================================
|
|
810
|
-
// React Components: Workspace File Tree Explorer
|
|
1043
|
+
// React Components: Workspace File Tree Explorer (with DnD, Create & Move)
|
|
811
1044
|
// =========================================================================
|
|
812
|
-
|
|
1045
|
+
|
|
1046
|
+
function isValidDropTarget(draggedItem, targetNode) {
|
|
1047
|
+
if (!draggedItem || !targetNode) return false;
|
|
1048
|
+
if (!targetNode.isDir) return false;
|
|
1049
|
+
const src = draggedItem.path.replace(/\\/g, '/').toLowerCase();
|
|
1050
|
+
const dest = targetNode.path.replace(/\\/g, '/').toLowerCase();
|
|
1051
|
+
// Cannot drop on itself
|
|
1052
|
+
if (src === dest) return false;
|
|
1053
|
+
// Cannot drop into current parent directory (already there)
|
|
1054
|
+
const lastSlash = src.lastIndexOf('/');
|
|
1055
|
+
const srcParent = lastSlash > 0 ? src.substring(0, lastSlash) : '';
|
|
1056
|
+
if (srcParent && srcParent === dest) return false;
|
|
1057
|
+
// If dragged is a dir, cannot drop into its own children or itself
|
|
1058
|
+
if (draggedItem.isDir) {
|
|
1059
|
+
if (dest.startsWith(src + '/')) return false;
|
|
1060
|
+
}
|
|
1061
|
+
return true;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function InlineCreateInputNode({ depth = 0, type, onConfirm, onCancel }) {
|
|
1065
|
+
const [val, setVal] = useState('');
|
|
1066
|
+
const inputRef = useRef(null);
|
|
1067
|
+
|
|
1068
|
+
useEffect(() => {
|
|
1069
|
+
if (inputRef.current) {
|
|
1070
|
+
inputRef.current.focus();
|
|
1071
|
+
inputRef.current.select();
|
|
1072
|
+
}
|
|
1073
|
+
}, []);
|
|
1074
|
+
|
|
1075
|
+
const handleKeyDown = (e) => {
|
|
1076
|
+
if (e.key === 'Enter') {
|
|
1077
|
+
e.preventDefault();
|
|
1078
|
+
e.stopPropagation();
|
|
1079
|
+
if (val.trim()) onConfirm(val.trim());
|
|
1080
|
+
else onCancel();
|
|
1081
|
+
} else if (e.key === 'Escape') {
|
|
1082
|
+
e.preventDefault();
|
|
1083
|
+
e.stopPropagation();
|
|
1084
|
+
onCancel();
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
const handleBlur = () => {
|
|
1089
|
+
if (!val.trim()) onCancel();
|
|
1090
|
+
};
|
|
1091
|
+
|
|
1092
|
+
return h('div', {
|
|
1093
|
+
className: 'dsh-tree-inline-input-row',
|
|
1094
|
+
style: { paddingLeft: `${depth * 14 + 6}px` },
|
|
1095
|
+
onClick: (e) => e.stopPropagation(),
|
|
1096
|
+
},
|
|
1097
|
+
h('span', { className: 'dsh-tree-chevron-placeholder' }),
|
|
1098
|
+
h('span', {
|
|
1099
|
+
className: 'dsh-tree-icon',
|
|
1100
|
+
style: { color: type === 'dir' ? '#eab308' : '#38bdf8' },
|
|
1101
|
+
}, type === 'dir' ? '📁' : '📄'),
|
|
1102
|
+
h('input', {
|
|
1103
|
+
ref: inputRef,
|
|
1104
|
+
type: 'text',
|
|
1105
|
+
className: 'dsh-tree-inline-input',
|
|
1106
|
+
value: val,
|
|
1107
|
+
placeholder: type === 'dir' ? '新文件夹名称...' : '新文件名 (如 doc.md)...',
|
|
1108
|
+
onChange: (e) => setVal(e.target.value),
|
|
1109
|
+
onKeyDown: handleKeyDown,
|
|
1110
|
+
onBlur: handleBlur,
|
|
1111
|
+
})
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function InlineRenameInputNode({ node, depth = 0, onConfirm, onCancel }) {
|
|
1116
|
+
const [val, setVal] = useState(node.name);
|
|
1117
|
+
const inputRef = useRef(null);
|
|
1118
|
+
|
|
1119
|
+
useEffect(() => {
|
|
1120
|
+
if (inputRef.current) {
|
|
1121
|
+
inputRef.current.focus();
|
|
1122
|
+
if (!node.isDir && node.name.includes('.')) {
|
|
1123
|
+
const lastDot = node.name.lastIndexOf('.');
|
|
1124
|
+
if (lastDot > 0) {
|
|
1125
|
+
inputRef.current.setSelectionRange(0, lastDot);
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
inputRef.current.select();
|
|
1130
|
+
}
|
|
1131
|
+
}, [node]);
|
|
1132
|
+
|
|
1133
|
+
const handleKeyDown = (e) => {
|
|
1134
|
+
if (e.key === 'Enter') {
|
|
1135
|
+
e.preventDefault();
|
|
1136
|
+
e.stopPropagation();
|
|
1137
|
+
if (val.trim() && val.trim() !== node.name) {
|
|
1138
|
+
onConfirm(val.trim());
|
|
1139
|
+
} else {
|
|
1140
|
+
onCancel();
|
|
1141
|
+
}
|
|
1142
|
+
} else if (e.key === 'Escape') {
|
|
1143
|
+
e.preventDefault();
|
|
1144
|
+
e.stopPropagation();
|
|
1145
|
+
onCancel();
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
const handleBlur = () => {
|
|
1150
|
+
onCancel();
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
const iconInfo = getFileIcon(node);
|
|
1154
|
+
|
|
1155
|
+
return h('div', {
|
|
1156
|
+
className: 'dsh-tree-inline-input-row',
|
|
1157
|
+
style: { paddingLeft: `${depth * 14 + 6}px` },
|
|
1158
|
+
onClick: (e) => e.stopPropagation(),
|
|
1159
|
+
},
|
|
1160
|
+
h('span', { className: 'dsh-tree-chevron-placeholder' }),
|
|
1161
|
+
h('span', {
|
|
1162
|
+
className: 'dsh-tree-icon',
|
|
1163
|
+
style: { color: node.isDir ? '#eab308' : iconInfo.color },
|
|
1164
|
+
}, node.isDir ? '📁' : iconInfo.icon),
|
|
1165
|
+
h('input', {
|
|
1166
|
+
ref: inputRef,
|
|
1167
|
+
type: 'text',
|
|
1168
|
+
className: 'dsh-tree-inline-input',
|
|
1169
|
+
value: val,
|
|
1170
|
+
onChange: (e) => setVal(e.target.value),
|
|
1171
|
+
onKeyDown: handleKeyDown,
|
|
1172
|
+
onBlur: handleBlur,
|
|
1173
|
+
})
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function TreeContextMenu({ x, y, node, isRoot, clipboard, onAction, onClose }) {
|
|
1178
|
+
const menuRef = useRef(null);
|
|
1179
|
+
useEffect(() => {
|
|
1180
|
+
const handleDown = (e) => {
|
|
1181
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
1182
|
+
onClose();
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
window.addEventListener('mousedown', handleDown, true);
|
|
1186
|
+
window.addEventListener('scroll', onClose, true);
|
|
1187
|
+
return () => {
|
|
1188
|
+
window.removeEventListener('mousedown', handleDown, true);
|
|
1189
|
+
window.removeEventListener('scroll', onClose, true);
|
|
1190
|
+
};
|
|
1191
|
+
}, [onClose]);
|
|
1192
|
+
|
|
1193
|
+
const style = {
|
|
1194
|
+
position: 'fixed',
|
|
1195
|
+
top: `${Math.min(Math.max(10, y), window.innerHeight - 300)}px`,
|
|
1196
|
+
left: `${Math.min(Math.max(10, x), window.innerWidth - 210)}px`,
|
|
1197
|
+
zIndex: 99999,
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
const isDir = isRoot || (node && node.isDir);
|
|
1201
|
+
|
|
1202
|
+
return h('div', { ref: menuRef, className: 'dsh-tree-context-menu', style },
|
|
1203
|
+
isDir ? [
|
|
1204
|
+
h('div', { key: 'new-file', className: 'dsh-menu-item', onClick: () => { onClose(); onAction('new-file', node); } },
|
|
1205
|
+
h('span', { className: 'dsh-menu-icon' }, '📄'),
|
|
1206
|
+
h('span', null, '新建文件')
|
|
1207
|
+
),
|
|
1208
|
+
h('div', { key: 'new-dir', className: 'dsh-menu-item', onClick: () => { onClose(); onAction('new-dir', node); } },
|
|
1209
|
+
h('span', { className: 'dsh-menu-icon' }, '📁'),
|
|
1210
|
+
h('span', null, '新建文件夹')
|
|
1211
|
+
),
|
|
1212
|
+
h('div', { key: 'div-1', className: 'dsh-menu-divider' }),
|
|
1213
|
+
] : null,
|
|
1214
|
+
|
|
1215
|
+
!isRoot && !isDir ? h('div', { key: 'preview', className: 'dsh-menu-item', onClick: () => { onClose(); onAction('preview', node); } },
|
|
1216
|
+
h('span', { className: 'dsh-menu-icon' }, '📖'),
|
|
1217
|
+
h('span', null, '打开预览')
|
|
1218
|
+
) : null,
|
|
1219
|
+
|
|
1220
|
+
!isRoot ? h('div', { key: 'cut', className: 'dsh-menu-item', onClick: () => { onClose(); onAction('cut', node); } },
|
|
1221
|
+
h('span', { className: 'dsh-menu-icon' }, '✂️'),
|
|
1222
|
+
h('span', null, '剪切')
|
|
1223
|
+
) : null,
|
|
1224
|
+
|
|
1225
|
+
isDir && clipboard && clipboard.action === 'cut' ? h('div', {
|
|
1226
|
+
key: 'paste',
|
|
1227
|
+
className: 'dsh-menu-item',
|
|
1228
|
+
onClick: () => { onClose(); onAction('paste', node); }
|
|
1229
|
+
},
|
|
1230
|
+
h('span', { className: 'dsh-menu-icon' }, '📋'),
|
|
1231
|
+
h('span', null, `粘贴 "${clipboard.name}" 到此处`)
|
|
1232
|
+
) : null,
|
|
1233
|
+
|
|
1234
|
+
!isRoot ? h('div', { key: 'rename', className: 'dsh-menu-item', onClick: () => { onClose(); onAction('rename', node); } },
|
|
1235
|
+
h('span', { className: 'dsh-menu-icon' }, '🏷️'),
|
|
1236
|
+
h('span', null, '重命名')
|
|
1237
|
+
) : null,
|
|
1238
|
+
|
|
1239
|
+
!isRoot ? h('div', { key: 'div-2', className: 'dsh-menu-divider' }) : null,
|
|
1240
|
+
|
|
1241
|
+
!isRoot ? h('div', { key: 'reveal', className: 'dsh-menu-item', onClick: () => { onClose(); onAction('reveal', node); } },
|
|
1242
|
+
h('span', { className: 'dsh-menu-icon' }, '📂'),
|
|
1243
|
+
h('span', null, '在文件管理器中定位')
|
|
1244
|
+
) : null,
|
|
1245
|
+
|
|
1246
|
+
!isRoot && !isDir ? h('div', { key: 'native', className: 'dsh-menu-item', onClick: () => { onClose(); onAction('native', node); } },
|
|
1247
|
+
h('span', { className: 'dsh-menu-icon' }, '🌐'),
|
|
1248
|
+
h('span', null, '系统默认应用打开')
|
|
1249
|
+
) : null,
|
|
1250
|
+
|
|
1251
|
+
!isRoot ? [
|
|
1252
|
+
h('div', { key: 'div-3', className: 'dsh-menu-divider' }),
|
|
1253
|
+
h('div', { key: 'delete', className: 'dsh-menu-item dsh-menu-item-danger', onClick: () => { onClose(); onAction('delete', node); } },
|
|
1254
|
+
h('span', { className: 'dsh-menu-icon' }, '🗑️'),
|
|
1255
|
+
h('span', null, '删除')
|
|
1256
|
+
)
|
|
1257
|
+
] : null,
|
|
1258
|
+
|
|
1259
|
+
isRoot ? [
|
|
1260
|
+
h('div', { key: 'refresh', className: 'dsh-menu-item', onClick: () => { onClose(); onAction('refresh'); } },
|
|
1261
|
+
h('span', { className: 'dsh-menu-icon' }, '🔄'),
|
|
1262
|
+
h('span', null, '刷新目录树')
|
|
1263
|
+
)
|
|
1264
|
+
] : null
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
function FileTreeNodeItem({
|
|
1269
|
+
node,
|
|
1270
|
+
depth = 0,
|
|
1271
|
+
expandedPaths,
|
|
1272
|
+
onToggleExpand,
|
|
1273
|
+
activeFilePath,
|
|
1274
|
+
onOpenFile,
|
|
1275
|
+
searchQuery,
|
|
1276
|
+
creatingItem,
|
|
1277
|
+
onConfirmCreate,
|
|
1278
|
+
onCancelCreate,
|
|
1279
|
+
renamingPath,
|
|
1280
|
+
onConfirmRename,
|
|
1281
|
+
onCancelRename,
|
|
1282
|
+
onContextMenu,
|
|
1283
|
+
draggedNode,
|
|
1284
|
+
dragOverNode,
|
|
1285
|
+
onDragStart,
|
|
1286
|
+
onDragEnd,
|
|
1287
|
+
onDragOver,
|
|
1288
|
+
onDragLeave,
|
|
1289
|
+
onDrop,
|
|
1290
|
+
clipboard,
|
|
1291
|
+
}) {
|
|
813
1292
|
const isDir = node.isDir;
|
|
814
1293
|
const isExpanded = expandedPaths.has(node.path);
|
|
815
1294
|
const isActive = !isDir && activeFilePath && activeFilePath.toLowerCase() === node.path.toLowerCase();
|
|
1295
|
+
const isRenaming = renamingPath && renamingPath.toLowerCase() === node.path.toLowerCase();
|
|
1296
|
+
const isDragged = draggedNode && draggedNode.path && draggedNode.path.toLowerCase() === node.path.toLowerCase();
|
|
1297
|
+
const isDropTarget = isDir && dragOverNode && dragOverNode.path && dragOverNode.path.toLowerCase() === node.path.toLowerCase();
|
|
1298
|
+
const isCut = clipboard && clipboard.action === 'cut' && clipboard.path && clipboard.path.toLowerCase() === node.path.toLowerCase();
|
|
816
1299
|
const iconInfo = getFileIcon(node);
|
|
817
1300
|
|
|
818
1301
|
const indentStyle = { paddingLeft: `${depth * 14 + 6}px` };
|
|
@@ -826,16 +1309,70 @@
|
|
|
826
1309
|
}
|
|
827
1310
|
};
|
|
828
1311
|
|
|
1312
|
+
const handleContextMenu = (e) => {
|
|
1313
|
+
e.preventDefault();
|
|
1314
|
+
e.stopPropagation();
|
|
1315
|
+
onContextMenu(node, e);
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1318
|
+
const handleDragStart = (e) => {
|
|
1319
|
+
e.stopPropagation();
|
|
1320
|
+
e.dataTransfer.setData('text/plain', node.path);
|
|
1321
|
+
e.dataTransfer.effectAllowed = 'move';
|
|
1322
|
+
onDragStart(node);
|
|
1323
|
+
};
|
|
1324
|
+
|
|
1325
|
+
const handleDragOver = (e) => {
|
|
1326
|
+
if (isDir && isValidDropTarget(draggedNode, node)) {
|
|
1327
|
+
e.preventDefault();
|
|
1328
|
+
e.stopPropagation();
|
|
1329
|
+
e.dataTransfer.dropEffect = 'move';
|
|
1330
|
+
onDragOver(node);
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
const handleDragLeave = (e) => {
|
|
1335
|
+
if (isDir) {
|
|
1336
|
+
e.stopPropagation();
|
|
1337
|
+
onDragLeave(node);
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
|
|
1341
|
+
const handleDrop = (e) => {
|
|
1342
|
+
if (isDir && isValidDropTarget(draggedNode, node)) {
|
|
1343
|
+
e.preventDefault();
|
|
1344
|
+
e.stopPropagation();
|
|
1345
|
+
onDrop(draggedNode, node);
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
|
|
1349
|
+
if (isRenaming) {
|
|
1350
|
+
return h(InlineRenameInputNode, {
|
|
1351
|
+
node,
|
|
1352
|
+
depth,
|
|
1353
|
+
onConfirm: (newName) => onConfirmRename(node, newName),
|
|
1354
|
+
onCancel: onCancelRename,
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
|
|
829
1358
|
return h(Fragment, null,
|
|
830
1359
|
h('div', {
|
|
831
|
-
className: `dsh-tree-node-row ${isDir ? 'is-dir' : 'is-file'} ${isActive ? 'active' : ''}`,
|
|
1360
|
+
className: `dsh-tree-node-row ${isDir ? 'is-dir' : 'is-file'} ${isActive ? 'active' : ''} ${isDropTarget ? 'drop-target' : ''} ${isDragged ? 'is-dragged' : ''} ${isCut ? 'is-cut' : ''}`,
|
|
832
1361
|
style: indentStyle,
|
|
833
1362
|
onClick: handleClick,
|
|
1363
|
+
onContextMenu: handleContextMenu,
|
|
1364
|
+
draggable: true,
|
|
1365
|
+
onDragStart: handleDragStart,
|
|
1366
|
+
onDragEnd: onDragEnd,
|
|
1367
|
+
onDragOver: handleDragOver,
|
|
1368
|
+
onDragLeave: handleDragLeave,
|
|
1369
|
+
onDrop: handleDrop,
|
|
834
1370
|
title: `${node.name}\n${node.path}${node.size ? ' (' + formatBytes(node.size) + ')' : ''}`,
|
|
835
1371
|
},
|
|
836
1372
|
// Chevron indicator for directory
|
|
837
1373
|
isDir ? h('span', {
|
|
838
1374
|
className: `dsh-tree-chevron ${isExpanded ? 'expanded' : ''}`,
|
|
1375
|
+
onClick: (e) => { e.stopPropagation(); onToggleExpand(node.path); },
|
|
839
1376
|
}, isExpanded ? '▼' : '▶') : h('span', { className: 'dsh-tree-chevron-placeholder' }),
|
|
840
1377
|
|
|
841
1378
|
// File / Directory Icon
|
|
@@ -853,18 +1390,47 @@
|
|
|
853
1390
|
)
|
|
854
1391
|
),
|
|
855
1392
|
|
|
856
|
-
// Children if expanded
|
|
857
|
-
isDir && isExpanded
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
node
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1393
|
+
// Children or Inline creation node if expanded
|
|
1394
|
+
isDir && isExpanded ? (
|
|
1395
|
+
h(Fragment, null,
|
|
1396
|
+
// If creating inside this directory, render inline creation node first
|
|
1397
|
+
creatingItem && creatingItem.parentPath && creatingItem.parentPath.replace(/\\/g, '/').toLowerCase() === node.path.replace(/\\/g, '/').toLowerCase() ? (
|
|
1398
|
+
h(InlineCreateInputNode, {
|
|
1399
|
+
depth: depth + 1,
|
|
1400
|
+
type: creatingItem.type,
|
|
1401
|
+
onConfirm: onConfirmCreate,
|
|
1402
|
+
onCancel: onCancelCreate,
|
|
1403
|
+
})
|
|
1404
|
+
) : null,
|
|
1405
|
+
|
|
1406
|
+
node.children && node.children.length > 0 ? (
|
|
1407
|
+
node.children.map(child => h(FileTreeNodeItem, {
|
|
1408
|
+
key: child.path,
|
|
1409
|
+
node: child,
|
|
1410
|
+
depth: depth + 1,
|
|
1411
|
+
expandedPaths,
|
|
1412
|
+
onToggleExpand,
|
|
1413
|
+
activeFilePath,
|
|
1414
|
+
onOpenFile,
|
|
1415
|
+
searchQuery,
|
|
1416
|
+
creatingItem,
|
|
1417
|
+
onConfirmCreate,
|
|
1418
|
+
onCancelCreate,
|
|
1419
|
+
renamingPath,
|
|
1420
|
+
onConfirmRename,
|
|
1421
|
+
onCancelRename,
|
|
1422
|
+
onContextMenu,
|
|
1423
|
+
draggedNode,
|
|
1424
|
+
dragOverNode,
|
|
1425
|
+
onDragStart,
|
|
1426
|
+
onDragEnd,
|
|
1427
|
+
onDragOver,
|
|
1428
|
+
onDragLeave,
|
|
1429
|
+
onDrop,
|
|
1430
|
+
clipboard,
|
|
1431
|
+
}))
|
|
1432
|
+
) : null
|
|
1433
|
+
)
|
|
868
1434
|
) : null
|
|
869
1435
|
);
|
|
870
1436
|
}
|
|
@@ -915,6 +1481,18 @@
|
|
|
915
1481
|
const [error, setError] = useState(null);
|
|
916
1482
|
const [searchQuery, setSearchQuery] = useState('');
|
|
917
1483
|
const [expandedPaths, setExpandedPaths] = useState(new Set());
|
|
1484
|
+
const [isSelectorOpen, setIsSelectorOpen] = useState(false);
|
|
1485
|
+
const [customPathInput, setCustomPathInput] = useState('');
|
|
1486
|
+
const [validating, setValidating] = useState(false);
|
|
1487
|
+
|
|
1488
|
+
// File operations state
|
|
1489
|
+
const [creatingItem, setCreatingItem] = useState(null); // { parentPath: string, type: 'dir' | 'file' } | null
|
|
1490
|
+
const [renamingPath, setRenamingPath] = useState(null); // string | null
|
|
1491
|
+
const [contextMenu, setContextMenu] = useState(null); // { x: number, y: number, node: any, isRoot: boolean } | null
|
|
1492
|
+
const [draggedNode, setDraggedNode] = useState(null);
|
|
1493
|
+
const [dragOverNode, setDragOverNode] = useState(null);
|
|
1494
|
+
const [clipboard, setClipboard] = useState(null); // { action: 'cut', path: string, name: string, isDir: boolean } | null
|
|
1495
|
+
|
|
918
1496
|
const currentWs = getCurrentWorkspaceRoot();
|
|
919
1497
|
|
|
920
1498
|
const loadTree = useCallback(async (force = false) => {
|
|
@@ -942,7 +1520,7 @@
|
|
|
942
1520
|
|
|
943
1521
|
useEffect(() => {
|
|
944
1522
|
loadTree();
|
|
945
|
-
}, [loadTree]);
|
|
1523
|
+
}, [loadTree, globalManualWorkspace]);
|
|
946
1524
|
|
|
947
1525
|
const handleToggleExpand = (dirPath) => {
|
|
948
1526
|
setExpandedPaths(prev => {
|
|
@@ -976,15 +1554,225 @@
|
|
|
976
1554
|
}
|
|
977
1555
|
}, [searchQuery, treeData]);
|
|
978
1556
|
|
|
1557
|
+
const availableWorkspaces = useMemo(() => {
|
|
1558
|
+
return getAllAvailableWorkspaces();
|
|
1559
|
+
}, [isSelectorOpen, globalManualWorkspace]);
|
|
1560
|
+
|
|
1561
|
+
const handleSelectWs = (wsPath) => {
|
|
1562
|
+
setManualWorkspace(wsPath);
|
|
1563
|
+
setIsSelectorOpen(false);
|
|
1564
|
+
showToast(`已切换工作区: ${getFileName(wsPath)}`, 'success');
|
|
1565
|
+
};
|
|
1566
|
+
|
|
1567
|
+
const handleCustomSubmit = async (e) => {
|
|
1568
|
+
if (e) e.preventDefault();
|
|
1569
|
+
const target = customPathInput.trim();
|
|
1570
|
+
if (!target) return;
|
|
1571
|
+
setValidating(true);
|
|
1572
|
+
try {
|
|
1573
|
+
const res = await fetch('/api/preview/validate-dir', {
|
|
1574
|
+
method: 'POST',
|
|
1575
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1576
|
+
body: JSON.stringify({ path: target }),
|
|
1577
|
+
});
|
|
1578
|
+
const json = await res.json();
|
|
1579
|
+
if (json.ok && json.path) {
|
|
1580
|
+
setManualWorkspace(json.path);
|
|
1581
|
+
setCustomPathInput('');
|
|
1582
|
+
setIsSelectorOpen(false);
|
|
1583
|
+
showToast(`已切换工作空间: ${json.name}`, 'success');
|
|
1584
|
+
} else {
|
|
1585
|
+
showToast(json?.error?.message || '指定目录不存在或不是文件夹', 'error');
|
|
1586
|
+
}
|
|
1587
|
+
} catch (err) {
|
|
1588
|
+
showToast('验证目录路径失败', 'error');
|
|
1589
|
+
} finally {
|
|
1590
|
+
setValidating(false);
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
|
|
1594
|
+
const handleResetAuto = () => {
|
|
1595
|
+
setManualWorkspace(null);
|
|
1596
|
+
setIsSelectorOpen(false);
|
|
1597
|
+
showToast('已恢复根据当前会话自动识别工作空间', 'info');
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
// =========================================================================
|
|
1601
|
+
// File Operations Handlers
|
|
1602
|
+
// =========================================================================
|
|
1603
|
+
const handleStartCreate = (parentPath, type) => {
|
|
1604
|
+
const root = treeData?.workspaceRoot || currentWs;
|
|
1605
|
+
const target = parentPath || root;
|
|
1606
|
+
setCreatingItem({ parentPath: target, type });
|
|
1607
|
+
if (target) {
|
|
1608
|
+
setExpandedPaths(prev => new Set(prev).add(target));
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
|
|
1612
|
+
const handleConfirmCreate = async (name) => {
|
|
1613
|
+
if (!creatingItem || !name) return;
|
|
1614
|
+
const isDir = creatingItem.type === 'dir';
|
|
1615
|
+
const endpoint = isDir ? '/api/preview/create-dir' : '/api/preview/create-file';
|
|
1616
|
+
try {
|
|
1617
|
+
const res = await fetch(endpoint, {
|
|
1618
|
+
method: 'POST',
|
|
1619
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1620
|
+
body: JSON.stringify({
|
|
1621
|
+
parentPath: creatingItem.parentPath,
|
|
1622
|
+
name: name.trim(),
|
|
1623
|
+
}),
|
|
1624
|
+
});
|
|
1625
|
+
const json = await res.json();
|
|
1626
|
+
if (json.ok) {
|
|
1627
|
+
setCreatingItem(null);
|
|
1628
|
+
setExpandedPaths(prev => new Set(prev).add(creatingItem.parentPath));
|
|
1629
|
+
await loadTree(true);
|
|
1630
|
+
showToast(`已创建${isDir ? '文件夹' : '文件'}: ${json.name}`, 'success');
|
|
1631
|
+
if (!isDir && json.path) {
|
|
1632
|
+
openPreviewFile(json.path);
|
|
1633
|
+
}
|
|
1634
|
+
} else {
|
|
1635
|
+
showToast(json?.error?.message || `创建${isDir ? '文件夹' : '文件'}失败`, 'error');
|
|
1636
|
+
}
|
|
1637
|
+
} catch (err) {
|
|
1638
|
+
showToast(`创建失败: ${err.message}`, 'error');
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
|
|
1642
|
+
const handleConfirmRename = async (node, newName) => {
|
|
1643
|
+
if (!node || !newName || newName.trim() === node.name) {
|
|
1644
|
+
setRenamingPath(null);
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
try {
|
|
1648
|
+
const res = await fetch('/api/preview/rename', {
|
|
1649
|
+
method: 'POST',
|
|
1650
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1651
|
+
body: JSON.stringify({
|
|
1652
|
+
path: node.path,
|
|
1653
|
+
newName: newName.trim(),
|
|
1654
|
+
}),
|
|
1655
|
+
});
|
|
1656
|
+
const json = await res.json();
|
|
1657
|
+
if (json.ok) {
|
|
1658
|
+
setRenamingPath(null);
|
|
1659
|
+
renameOpenTabPaths(json.sourcePath, json.targetPath, node.isDir);
|
|
1660
|
+
await loadTree(true);
|
|
1661
|
+
showToast(`已重命名为: ${json.name}`, 'success');
|
|
1662
|
+
} else {
|
|
1663
|
+
showToast(json?.error?.message || '重命名失败', 'error');
|
|
1664
|
+
}
|
|
1665
|
+
} catch (err) {
|
|
1666
|
+
showToast(`重命名失败: ${err.message}`, 'error');
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
|
|
1670
|
+
const handleMove = async (sourcePath, targetDirPath) => {
|
|
1671
|
+
if (!sourcePath || !targetDirPath) return;
|
|
1672
|
+
try {
|
|
1673
|
+
const res = await fetch('/api/preview/move', {
|
|
1674
|
+
method: 'POST',
|
|
1675
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1676
|
+
body: JSON.stringify({
|
|
1677
|
+
sourcePath,
|
|
1678
|
+
targetPath: targetDirPath,
|
|
1679
|
+
}),
|
|
1680
|
+
});
|
|
1681
|
+
const json = await res.json();
|
|
1682
|
+
if (json.ok) {
|
|
1683
|
+
if (json.noop) {
|
|
1684
|
+
showToast('目标路径与源路径相同', 'info');
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1687
|
+
renameOpenTabPaths(json.sourcePath, json.targetPath, json.isDir);
|
|
1688
|
+
setExpandedPaths(prev => new Set(prev).add(targetDirPath));
|
|
1689
|
+
await loadTree(true);
|
|
1690
|
+
showToast(`已移动 ${json.name} 到 ${getFileName(targetDirPath)}`, 'success');
|
|
1691
|
+
} else {
|
|
1692
|
+
showToast(json?.error?.message || '移动文件失败', 'error');
|
|
1693
|
+
}
|
|
1694
|
+
} catch (err) {
|
|
1695
|
+
showToast(`移动失败: ${err.message}`, 'error');
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
|
|
1699
|
+
const handleDelete = async (node) => {
|
|
1700
|
+
if (!node) return;
|
|
1701
|
+
const isDir = node.isDir;
|
|
1702
|
+
const msg = `确定要永久删除${isDir ? '文件夹' : '文件'} "${node.name}" 吗?此操作不可恢复。`;
|
|
1703
|
+
if (typeof window !== 'undefined' && !window.confirm(msg)) return;
|
|
1704
|
+
|
|
1705
|
+
try {
|
|
1706
|
+
const res = await fetch('/api/preview/delete', {
|
|
1707
|
+
method: 'POST',
|
|
1708
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1709
|
+
body: JSON.stringify({ path: node.path }),
|
|
1710
|
+
});
|
|
1711
|
+
const json = await res.json();
|
|
1712
|
+
if (json.ok) {
|
|
1713
|
+
removeOpenTabsForPath(node.path, isDir);
|
|
1714
|
+
await loadTree(true);
|
|
1715
|
+
showToast(`已删除${isDir ? '文件夹' : '文件'}: ${node.name}`, 'info');
|
|
1716
|
+
} else {
|
|
1717
|
+
showToast(json?.error?.message || '删除失败', 'error');
|
|
1718
|
+
}
|
|
1719
|
+
} catch (err) {
|
|
1720
|
+
showToast(`删除失败: ${err.message}`, 'error');
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
|
|
1724
|
+
const handleContextMenuAction = (action, node) => {
|
|
1725
|
+
const rootPath = treeData?.workspaceRoot || currentWs;
|
|
1726
|
+
if (action === 'new-file') {
|
|
1727
|
+
const target = node ? (node.isDir ? node.path : (node.path.substring(0, Math.max(node.path.lastIndexOf('/'), node.path.lastIndexOf('\\'))))) : rootPath;
|
|
1728
|
+
handleStartCreate(target, 'file');
|
|
1729
|
+
} else if (action === 'new-dir') {
|
|
1730
|
+
const target = node ? (node.isDir ? node.path : (node.path.substring(0, Math.max(node.path.lastIndexOf('/'), node.path.lastIndexOf('\\'))))) : rootPath;
|
|
1731
|
+
handleStartCreate(target, 'dir');
|
|
1732
|
+
} else if (action === 'preview') {
|
|
1733
|
+
if (node && !node.isDir) onOpenFile(node.path);
|
|
1734
|
+
} else if (action === 'cut') {
|
|
1735
|
+
if (node) {
|
|
1736
|
+
setClipboard({ action: 'cut', path: node.path, name: node.name, isDir: node.isDir });
|
|
1737
|
+
showToast(`已剪切: ${node.name}`, 'info');
|
|
1738
|
+
}
|
|
1739
|
+
} else if (action === 'paste') {
|
|
1740
|
+
if (clipboard && clipboard.action === 'cut') {
|
|
1741
|
+
const target = node ? (node.isDir ? node.path : (node.path.substring(0, Math.max(node.path.lastIndexOf('/'), node.path.lastIndexOf('\\'))))) : rootPath;
|
|
1742
|
+
handleMove(clipboard.path, target);
|
|
1743
|
+
setClipboard(null);
|
|
1744
|
+
}
|
|
1745
|
+
} else if (action === 'rename') {
|
|
1746
|
+
if (node) setRenamingPath(node.path);
|
|
1747
|
+
} else if (action === 'reveal') {
|
|
1748
|
+
if (node) triggerRevealInExplorer(node.path);
|
|
1749
|
+
} else if (action === 'native') {
|
|
1750
|
+
if (node) triggerOpenNative(node.path);
|
|
1751
|
+
} else if (action === 'delete') {
|
|
1752
|
+
if (node) handleDelete(node);
|
|
1753
|
+
} else if (action === 'refresh') {
|
|
1754
|
+
loadTree(true);
|
|
1755
|
+
}
|
|
1756
|
+
};
|
|
1757
|
+
|
|
1758
|
+
const rootPath = treeData?.workspaceRoot || currentWs;
|
|
1759
|
+
const isCreatingAtRoot = creatingItem && creatingItem.parentPath && rootPath && creatingItem.parentPath.replace(/\\/g, '/').toLowerCase() === rootPath.replace(/\\/g, '/').toLowerCase();
|
|
1760
|
+
const isRootDropTarget = dragOverNode && rootPath && dragOverNode.path && dragOverNode.path.replace(/\\/g, '/').toLowerCase() === rootPath.replace(/\\/g, '/').toLowerCase();
|
|
1761
|
+
|
|
979
1762
|
return h('div', {
|
|
980
1763
|
className: 'dsh-tree-sidebar',
|
|
981
1764
|
style: { width: `${width}px` },
|
|
982
1765
|
},
|
|
983
1766
|
// Sidebar Header
|
|
984
1767
|
h('div', { className: 'dsh-tree-sidebar-header' },
|
|
985
|
-
h('div', {
|
|
1768
|
+
h('div', {
|
|
1769
|
+
className: 'dsh-tree-sidebar-title dsh-tree-title-clickable',
|
|
1770
|
+
onClick: () => setIsSelectorOpen(!isSelectorOpen),
|
|
1771
|
+
title: `当前工作空间: ${treeData?.workspaceRoot || currentWs || '未指定'}\n点击切换工作空间`,
|
|
1772
|
+
},
|
|
986
1773
|
h('span', { className: 'dsh-tree-header-icon' }, '📁'),
|
|
987
|
-
h('span', { className: 'dsh-tree-header-text' }, treeData?.workspaceName || getFileName(currentWs) || '工作区')
|
|
1774
|
+
h('span', { className: 'dsh-tree-header-text' }, treeData?.workspaceName || getFileName(currentWs) || '工作区'),
|
|
1775
|
+
h('span', { className: 'dsh-tree-header-chevron' }, isSelectorOpen ? '▲' : '▼')
|
|
988
1776
|
),
|
|
989
1777
|
h('div', { className: 'dsh-tree-header-actions' },
|
|
990
1778
|
h('button', {
|
|
@@ -996,8 +1784,20 @@
|
|
|
996
1784
|
h('button', {
|
|
997
1785
|
type: 'button',
|
|
998
1786
|
className: 'dsh-tree-action-btn',
|
|
999
|
-
onClick:
|
|
1000
|
-
title: '
|
|
1787
|
+
onClick: () => handleStartCreate(rootPath, 'file'),
|
|
1788
|
+
title: '在根目录新建文件',
|
|
1789
|
+
}, '📄➕'),
|
|
1790
|
+
h('button', {
|
|
1791
|
+
type: 'button',
|
|
1792
|
+
className: 'dsh-tree-action-btn',
|
|
1793
|
+
onClick: () => handleStartCreate(rootPath, 'dir'),
|
|
1794
|
+
title: '在根目录新建文件夹',
|
|
1795
|
+
}, '📁➕'),
|
|
1796
|
+
h('button', {
|
|
1797
|
+
type: 'button',
|
|
1798
|
+
className: 'dsh-tree-action-btn',
|
|
1799
|
+
onClick: handleExpandAll,
|
|
1800
|
+
title: '全部展开',
|
|
1001
1801
|
}, '📂'),
|
|
1002
1802
|
h('button', {
|
|
1003
1803
|
type: 'button',
|
|
@@ -1014,6 +1814,62 @@
|
|
|
1014
1814
|
)
|
|
1015
1815
|
),
|
|
1016
1816
|
|
|
1817
|
+
// Workspace Selector Dropdown Popover
|
|
1818
|
+
isSelectorOpen ? h('div', { className: 'dsh-ws-selector-popover' },
|
|
1819
|
+
h('div', { className: 'dsh-ws-popover-header' },
|
|
1820
|
+
h('span', { style: { fontWeight: 600, fontSize: 12 } }, '选择工作空间'),
|
|
1821
|
+
h('button', {
|
|
1822
|
+
type: 'button',
|
|
1823
|
+
className: 'dsh-ws-popover-close',
|
|
1824
|
+
onClick: () => setIsSelectorOpen(false),
|
|
1825
|
+
}, '✕')
|
|
1826
|
+
),
|
|
1827
|
+
|
|
1828
|
+
// List of detected workspaces
|
|
1829
|
+
h('div', { className: 'dsh-ws-list' },
|
|
1830
|
+
availableWorkspaces.length > 0 ? availableWorkspaces.map(ws => (
|
|
1831
|
+
h('div', {
|
|
1832
|
+
key: ws.path,
|
|
1833
|
+
className: `dsh-ws-item ${ws.isCurrent ? 'active' : ''}`,
|
|
1834
|
+
onClick: () => handleSelectWs(ws.path),
|
|
1835
|
+
title: ws.path,
|
|
1836
|
+
},
|
|
1837
|
+
h('div', { className: 'dsh-ws-item-info' },
|
|
1838
|
+
h('span', { className: 'dsh-ws-item-icon' }, '📁'),
|
|
1839
|
+
h('span', { className: 'dsh-ws-item-name' }, ws.name),
|
|
1840
|
+
h('span', { className: 'dsh-ws-item-path' }, ws.path)
|
|
1841
|
+
),
|
|
1842
|
+
ws.isCurrent ? h('span', { className: 'dsh-ws-item-check' }, '✓') : null
|
|
1843
|
+
)
|
|
1844
|
+
)) : h('div', { className: 'dsh-ws-empty' }, '暂无其他检测到的工作空间')
|
|
1845
|
+
),
|
|
1846
|
+
|
|
1847
|
+
// Custom Path Input
|
|
1848
|
+
h('form', { className: 'dsh-ws-custom-form', onSubmit: handleCustomSubmit },
|
|
1849
|
+
h('div', { className: 'dsh-ws-input-row' },
|
|
1850
|
+
h('input', {
|
|
1851
|
+
type: 'text',
|
|
1852
|
+
className: 'dsh-ws-input',
|
|
1853
|
+
placeholder: '输入或粘贴本地目录路径...',
|
|
1854
|
+
value: customPathInput,
|
|
1855
|
+
onChange: (e) => setCustomPathInput(e.target.value),
|
|
1856
|
+
}),
|
|
1857
|
+
h('button', {
|
|
1858
|
+
type: 'submit',
|
|
1859
|
+
className: 'dsh-ws-btn',
|
|
1860
|
+
disabled: validating || !customPathInput.trim(),
|
|
1861
|
+
}, validating ? '验证...' : '切换')
|
|
1862
|
+
)
|
|
1863
|
+
),
|
|
1864
|
+
|
|
1865
|
+
// Auto-detect reset link
|
|
1866
|
+
globalManualWorkspace ? h('button', {
|
|
1867
|
+
type: 'button',
|
|
1868
|
+
className: 'dsh-ws-reset-btn',
|
|
1869
|
+
onClick: handleResetAuto,
|
|
1870
|
+
}, '🔄 恢复自动识别当前会话工作区') : null
|
|
1871
|
+
) : null,
|
|
1872
|
+
|
|
1017
1873
|
// Search Input
|
|
1018
1874
|
h('div', { className: 'dsh-tree-sidebar-search' },
|
|
1019
1875
|
h('div', { className: 'dsh-tree-search-wrapper' },
|
|
@@ -1034,7 +1890,40 @@
|
|
|
1034
1890
|
),
|
|
1035
1891
|
|
|
1036
1892
|
// Tree Content
|
|
1037
|
-
h('div', {
|
|
1893
|
+
h('div', {
|
|
1894
|
+
className: `dsh-tree-node-list ${isRootDropTarget ? 'drop-target-root' : ''}`,
|
|
1895
|
+
onContextMenu: (e) => {
|
|
1896
|
+
e.preventDefault();
|
|
1897
|
+
setContextMenu({ x: e.clientX, y: e.clientY, node: null, isRoot: true });
|
|
1898
|
+
},
|
|
1899
|
+
onDragOver: (e) => {
|
|
1900
|
+
if (draggedNode && isValidDropTarget(draggedNode, { path: rootPath, isDir: true })) {
|
|
1901
|
+
e.preventDefault();
|
|
1902
|
+
e.dataTransfer.dropEffect = 'move';
|
|
1903
|
+
setDragOverNode({ path: rootPath, isDir: true });
|
|
1904
|
+
}
|
|
1905
|
+
},
|
|
1906
|
+
onDragLeave: () => {
|
|
1907
|
+
if (dragOverNode && dragOverNode.path === rootPath) {
|
|
1908
|
+
setDragOverNode(null);
|
|
1909
|
+
}
|
|
1910
|
+
},
|
|
1911
|
+
onDrop: (e) => {
|
|
1912
|
+
e.preventDefault();
|
|
1913
|
+
setDragOverNode(null);
|
|
1914
|
+
if (draggedNode && isValidDropTarget(draggedNode, { path: rootPath, isDir: true })) {
|
|
1915
|
+
handleMove(draggedNode.path, rootPath);
|
|
1916
|
+
}
|
|
1917
|
+
},
|
|
1918
|
+
},
|
|
1919
|
+
// If creating at root level, render inline create input at top
|
|
1920
|
+
isCreatingAtRoot ? h(InlineCreateInputNode, {
|
|
1921
|
+
depth: 0,
|
|
1922
|
+
type: creatingItem.type,
|
|
1923
|
+
onConfirm: handleConfirmCreate,
|
|
1924
|
+
onCancel: () => setCreatingItem(null),
|
|
1925
|
+
}) : null,
|
|
1926
|
+
|
|
1038
1927
|
isLoading ? (
|
|
1039
1928
|
h('div', { className: 'dsh-tree-loading' }, '正在扫描工作区文件...')
|
|
1040
1929
|
) : error ? (
|
|
@@ -1046,7 +1935,7 @@
|
|
|
1046
1935
|
onClick: () => loadTree(true),
|
|
1047
1936
|
}, '重试')
|
|
1048
1937
|
)
|
|
1049
|
-
) : displayTree.length === 0 ? (
|
|
1938
|
+
) : displayTree.length === 0 && !isCreatingAtRoot ? (
|
|
1050
1939
|
h('div', { className: 'dsh-tree-empty' }, searchQuery ? '未匹配到相关文件' : '工作区暂无文件')
|
|
1051
1940
|
) : (
|
|
1052
1941
|
displayTree.map(node => h(FileTreeNodeItem, {
|
|
@@ -1058,10 +1947,42 @@
|
|
|
1058
1947
|
activeFilePath,
|
|
1059
1948
|
onOpenFile,
|
|
1060
1949
|
searchQuery,
|
|
1950
|
+
creatingItem,
|
|
1951
|
+
onConfirmCreate: handleConfirmCreate,
|
|
1952
|
+
onCancelCreate: () => setCreatingItem(null),
|
|
1953
|
+
renamingPath,
|
|
1954
|
+
onConfirmRename: handleConfirmRename,
|
|
1955
|
+
onCancelRename: () => setRenamingPath(null),
|
|
1956
|
+
onContextMenu: (targetNode, e) => {
|
|
1957
|
+
setContextMenu({ x: e.clientX, y: e.clientY, node: targetNode, isRoot: false });
|
|
1958
|
+
},
|
|
1959
|
+
draggedNode,
|
|
1960
|
+
dragOverNode,
|
|
1961
|
+
onDragStart: (targetNode) => setDraggedNode(targetNode),
|
|
1962
|
+
onDragEnd: () => { setDraggedNode(null); setDragOverNode(null); },
|
|
1963
|
+
onDragOver: (targetNode) => setDragOverNode(targetNode),
|
|
1964
|
+
onDragLeave: () => setDragOverNode(null),
|
|
1965
|
+
onDrop: (source, target) => {
|
|
1966
|
+
setDraggedNode(null);
|
|
1967
|
+
setDragOverNode(null);
|
|
1968
|
+
handleMove(source.path, target.path);
|
|
1969
|
+
},
|
|
1970
|
+
clipboard,
|
|
1061
1971
|
}))
|
|
1062
1972
|
)
|
|
1063
1973
|
),
|
|
1064
1974
|
|
|
1975
|
+
// Context Menu Overlay
|
|
1976
|
+
contextMenu ? h(TreeContextMenu, {
|
|
1977
|
+
x: contextMenu.x,
|
|
1978
|
+
y: contextMenu.y,
|
|
1979
|
+
node: contextMenu.node,
|
|
1980
|
+
isRoot: contextMenu.isRoot,
|
|
1981
|
+
clipboard,
|
|
1982
|
+
onAction: handleContextMenuAction,
|
|
1983
|
+
onClose: () => setContextMenu(null),
|
|
1984
|
+
}) : null,
|
|
1985
|
+
|
|
1065
1986
|
// Sidebar Footer with file count stats
|
|
1066
1987
|
treeData ? h('div', { className: 'dsh-tree-sidebar-footer' },
|
|
1067
1988
|
h('span', null, `${treeData.totalDirs || 0} 目录`),
|
|
@@ -1993,22 +2914,62 @@
|
|
|
1993
2914
|
}
|
|
1994
2915
|
|
|
1995
2916
|
// =========================================================================
|
|
1996
|
-
// React Component: CodeEditorView
|
|
2917
|
+
// React Component: CodeEditorView (Unified line-row architecture)
|
|
1997
2918
|
// =========================================================================
|
|
1998
|
-
function
|
|
1999
|
-
|
|
2919
|
+
function splitHighlightedHtmlIntoLines(html) {
|
|
2920
|
+
if (!html) return [];
|
|
2921
|
+
const rawLines = html.split('\n');
|
|
2922
|
+
const lines = [];
|
|
2923
|
+
const openTags = [];
|
|
2924
|
+
|
|
2925
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
2926
|
+
let line = rawLines[i];
|
|
2927
|
+
let prefix = '';
|
|
2928
|
+
if (openTags.length > 0) {
|
|
2929
|
+
prefix = openTags.map(t => '<' + t.name + (t.attrs ? ' ' + t.attrs : '') + '>').join('');
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
const tagRegex = /<\/?([a-zA-Z0-9\-]+)([^>]*)>/g;
|
|
2933
|
+
let match;
|
|
2934
|
+
while ((match = tagRegex.exec(line)) !== null) {
|
|
2935
|
+
const isClosing = match[0].startsWith('</');
|
|
2936
|
+
const tagName = match[1];
|
|
2937
|
+
const attrs = match[2];
|
|
2938
|
+
if (isClosing) {
|
|
2939
|
+
const lastIdx = openTags.map(t => t.name).lastIndexOf(tagName);
|
|
2940
|
+
if (lastIdx !== -1) {
|
|
2941
|
+
openTags.splice(lastIdx, 1);
|
|
2942
|
+
}
|
|
2943
|
+
} else if (!match[0].endsWith('/>')) {
|
|
2944
|
+
openTags.push({ name: tagName, attrs: attrs.trim() });
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2000
2947
|
|
|
2001
|
-
|
|
2948
|
+
let suffix = '';
|
|
2949
|
+
if (openTags.length > 0) {
|
|
2950
|
+
suffix = openTags.slice().reverse().map(t => '</' + t.name + '>').join('');
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
lines.push(prefix + line + suffix);
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
return lines;
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
function CodeEditorView({ content, category, isHighlighted = true, isWordWrap = true, searchQuery = '', onLineClick }) {
|
|
2960
|
+
const normalizedContent = useMemo(() => {
|
|
2002
2961
|
if (!content) return '';
|
|
2962
|
+
return content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
2963
|
+
}, [content]);
|
|
2964
|
+
|
|
2965
|
+
const highlightedLines = useMemo(() => {
|
|
2966
|
+
if (!normalizedContent) return [];
|
|
2967
|
+
let html = '';
|
|
2003
2968
|
if (!isHighlighted) {
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
escaped = escaped.replace(new RegExp(`(${q})`, 'gi'), '<mark class="dsh-search-highlight">$1</mark>');
|
|
2008
|
-
}
|
|
2009
|
-
return escaped;
|
|
2969
|
+
html = escapeHtml(normalizedContent);
|
|
2970
|
+
} else {
|
|
2971
|
+
html = highlightCode(normalizedContent, category);
|
|
2010
2972
|
}
|
|
2011
|
-
let html = highlightCode(content, category);
|
|
2012
2973
|
if (searchQuery.trim()) {
|
|
2013
2974
|
try {
|
|
2014
2975
|
const q = escapeHtml(searchQuery.trim());
|
|
@@ -2016,27 +2977,34 @@
|
|
|
2016
2977
|
html = html.replace(regex, '<mark class="dsh-search-highlight">$1</mark>');
|
|
2017
2978
|
} catch (e) {}
|
|
2018
2979
|
}
|
|
2019
|
-
return html;
|
|
2020
|
-
}, [
|
|
2980
|
+
return splitHighlightedHtmlIntoLines(html);
|
|
2981
|
+
}, [normalizedContent, category, isHighlighted, searchQuery]);
|
|
2982
|
+
|
|
2983
|
+
const lineCount = highlightedLines.length;
|
|
2984
|
+
const digitCount = Math.max(2, String(lineCount).length);
|
|
2985
|
+
const gutterWidth = Math.max(24, digitCount * 7.5 + 12);
|
|
2021
2986
|
|
|
2022
2987
|
return h('div', { className: `dsh-code-editor-view ${isWordWrap ? 'word-wrap' : ''}` },
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2988
|
+
highlightedLines.map((lineHtml, i) => {
|
|
2989
|
+
const lineNum = i + 1;
|
|
2990
|
+
return h('div', {
|
|
2991
|
+
key: lineNum,
|
|
2992
|
+
id: `line-row-${lineNum}`,
|
|
2993
|
+
className: 'dsh-code-line',
|
|
2994
|
+
},
|
|
2995
|
+
h('span', {
|
|
2996
|
+
id: `line-gutter-${lineNum}`,
|
|
2997
|
+
className: 'dsh-line-gutter',
|
|
2998
|
+
style: { minWidth: `${gutterWidth}px` },
|
|
2999
|
+
onClick: () => onLineClick && onLineClick(lineNum),
|
|
3000
|
+
title: `第 ${lineNum} 行 (点击定位)`,
|
|
3001
|
+
}, lineNum),
|
|
3002
|
+
h('span', {
|
|
3003
|
+
className: 'dsh-line-code',
|
|
3004
|
+
dangerouslySetInnerHTML: { __html: lineHtml ? lineHtml : ' ' },
|
|
2037
3005
|
})
|
|
2038
|
-
)
|
|
2039
|
-
)
|
|
3006
|
+
);
|
|
3007
|
+
})
|
|
2040
3008
|
);
|
|
2041
3009
|
}
|
|
2042
3010
|
|
|
@@ -2138,7 +3106,9 @@
|
|
|
2138
3106
|
const el = contentRef.current.querySelector(`#${item.id}`);
|
|
2139
3107
|
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
2140
3108
|
} else {
|
|
2141
|
-
const
|
|
3109
|
+
const rowEl = document.getElementById(`line-row-${item.line}`);
|
|
3110
|
+
const gutterEl = document.getElementById(`line-gutter-${item.line}`);
|
|
3111
|
+
const el = rowEl || gutterEl;
|
|
2142
3112
|
if (el) {
|
|
2143
3113
|
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
2144
3114
|
el.classList.add('dsh-line-highlight-flash');
|
|
@@ -2463,6 +3433,7 @@
|
|
|
2463
3433
|
const [activeTabId, setActiveTabId] = useState(globalActiveTabId);
|
|
2464
3434
|
const [isOpen, setIsOpen] = useState(globalIsPanelOpen);
|
|
2465
3435
|
const [width, setWidth] = useState(globalPanelWidth);
|
|
3436
|
+
const [isMaximized, setLocalIsMaximized] = useState(globalIsMaximized);
|
|
2466
3437
|
const [isTreeOpen, setLocalIsTreeOpen] = useState(globalIsTreeOpen);
|
|
2467
3438
|
const [treeWidth, setLocalTreeWidth] = useState(globalTreeWidth);
|
|
2468
3439
|
const [isTreeDragging, setIsTreeDragging] = useState(false);
|
|
@@ -2482,6 +3453,7 @@
|
|
|
2482
3453
|
setActiveTabId(globalActiveTabId);
|
|
2483
3454
|
setIsOpen(globalIsPanelOpen);
|
|
2484
3455
|
setWidth(globalPanelWidth);
|
|
3456
|
+
setLocalIsMaximized(globalIsMaximized);
|
|
2485
3457
|
setLocalIsTreeOpen(globalIsTreeOpen);
|
|
2486
3458
|
setLocalTreeWidth(globalTreeWidth);
|
|
2487
3459
|
};
|
|
@@ -2489,6 +3461,30 @@
|
|
|
2489
3461
|
return () => stateListeners.delete(update);
|
|
2490
3462
|
}, []);
|
|
2491
3463
|
|
|
3464
|
+
// Synchronize side-by-side layout CSS variables with document.body
|
|
3465
|
+
useEffect(() => {
|
|
3466
|
+
if (typeof document === 'undefined') return;
|
|
3467
|
+
if (isOpen) {
|
|
3468
|
+
document.body.classList.add('dsh-preview-sidebar-active');
|
|
3469
|
+
if (isMaximized) {
|
|
3470
|
+
document.body.classList.add('dsh-preview-maximized');
|
|
3471
|
+
document.body.style.setProperty('--dsh-preview-width', '100vw');
|
|
3472
|
+
} else {
|
|
3473
|
+
document.body.classList.remove('dsh-preview-maximized');
|
|
3474
|
+
document.body.style.setProperty('--dsh-preview-width', `${width}px`);
|
|
3475
|
+
}
|
|
3476
|
+
} else {
|
|
3477
|
+
document.body.classList.remove('dsh-preview-sidebar-active');
|
|
3478
|
+
document.body.classList.remove('dsh-preview-maximized');
|
|
3479
|
+
document.body.style.removeProperty('--dsh-preview-width');
|
|
3480
|
+
}
|
|
3481
|
+
return () => {
|
|
3482
|
+
document.body.classList.remove('dsh-preview-sidebar-active');
|
|
3483
|
+
document.body.classList.remove('dsh-preview-maximized');
|
|
3484
|
+
document.body.style.removeProperty('--dsh-preview-width');
|
|
3485
|
+
};
|
|
3486
|
+
}, [isOpen, width, isMaximized]);
|
|
3487
|
+
|
|
2492
3488
|
const activeTab = useMemo(() => {
|
|
2493
3489
|
return tabs.find(t => t.id === activeTabId) || tabs[0] || null;
|
|
2494
3490
|
}, [tabs, activeTabId]);
|
|
@@ -2567,54 +3563,6 @@
|
|
|
2567
3563
|
|
|
2568
3564
|
const drawerRef = useRef(null);
|
|
2569
3565
|
|
|
2570
|
-
// Outside Click Listener: Click on the chat conversation / dialog box outside the drawer to auto close/hide it
|
|
2571
|
-
useEffect(() => {
|
|
2572
|
-
if (!isOpen) return;
|
|
2573
|
-
|
|
2574
|
-
const handleOutsidePointerDown = (e) => {
|
|
2575
|
-
// 1. Ignore if just opened within 250ms
|
|
2576
|
-
if (Date.now() - globalLastOpenTime < 250) return;
|
|
2577
|
-
// 2. Ignore during dragging resize handle
|
|
2578
|
-
if (isDragging || isTreeDragging) return;
|
|
2579
|
-
|
|
2580
|
-
const target = e.target;
|
|
2581
|
-
if (!target) return;
|
|
2582
|
-
|
|
2583
|
-
// 3. If clicking inside the preview drawer, do not close
|
|
2584
|
-
const drawerEl = drawerRef.current || document.querySelector('.dsh-preview-drawer-container');
|
|
2585
|
-
if (drawerEl && (drawerEl === target || drawerEl.contains(target))) {
|
|
2586
|
-
return;
|
|
2587
|
-
}
|
|
2588
|
-
|
|
2589
|
-
// 4. If clicking on the right activity rail, preview drawer or git drawer, do not close
|
|
2590
|
-
if (target.closest && target.closest('.dsh-right-activity-rail, .dsh-rail-tab-item, .dsh-preview-drawer-container, .dsh-git-drawer-container')) {
|
|
2591
|
-
return;
|
|
2592
|
-
}
|
|
2593
|
-
|
|
2594
|
-
// 5. If clicking on a file link or button that opens a file preview, do not close (let interceptor open it)
|
|
2595
|
-
if (target.closest) {
|
|
2596
|
-
const btn = target.closest('button');
|
|
2597
|
-
if (btn) {
|
|
2598
|
-
const candidate = btn.getAttribute('title') || btn.getAttribute('aria-label') || btn.textContent || '';
|
|
2599
|
-
if (isPreviewableFile(candidate.trim())) return;
|
|
2600
|
-
}
|
|
2601
|
-
const anchor = target.closest('a');
|
|
2602
|
-
if (anchor) {
|
|
2603
|
-
const href = anchor.getAttribute('href') || '';
|
|
2604
|
-
if (isPreviewableFile(href)) return;
|
|
2605
|
-
}
|
|
2606
|
-
}
|
|
2607
|
-
|
|
2608
|
-
// 6. Outside click in dialog/conversation/background: Auto hide the preview drawer!
|
|
2609
|
-
setPanelOpen(false);
|
|
2610
|
-
};
|
|
2611
|
-
|
|
2612
|
-
window.addEventListener('pointerdown', handleOutsidePointerDown, true);
|
|
2613
|
-
return () => {
|
|
2614
|
-
window.removeEventListener('pointerdown', handleOutsidePointerDown, true);
|
|
2615
|
-
};
|
|
2616
|
-
}, [isOpen, isDragging, isTreeDragging]);
|
|
2617
|
-
|
|
2618
3566
|
// Mutual exclusion with other tool windows (e.g. Git modal)
|
|
2619
3567
|
useEffect(() => {
|
|
2620
3568
|
const handleToolWindowOpen = (e) => {
|
|
@@ -2679,17 +3627,17 @@
|
|
|
2679
3627
|
null,
|
|
2680
3628
|
isOpen ? h('div', {
|
|
2681
3629
|
ref: drawerRef,
|
|
2682
|
-
className: `dsh-preview-drawer-container ${isDragging || isTreeDragging ? 'dragging' : ''}`,
|
|
2683
|
-
style: { width: `${width}px` },
|
|
3630
|
+
className: `dsh-preview-drawer-container ${isDragging || isTreeDragging ? 'dragging' : ''} ${isMaximized ? 'maximized' : ''}`,
|
|
3631
|
+
style: { width: isMaximized ? '100vw' : `${width}px` },
|
|
2684
3632
|
},
|
|
2685
|
-
// Drag Handle on Left Border
|
|
2686
|
-
h('div', {
|
|
3633
|
+
// Drag Handle on Left Border (only when not maximized)
|
|
3634
|
+
!isMaximized ? h('div', {
|
|
2687
3635
|
className: 'dsh-preview-drag-handle',
|
|
2688
3636
|
onPointerDown: onResizePointerDown,
|
|
2689
3637
|
onPointerMove: onResizePointerMove,
|
|
2690
3638
|
onPointerUp: onResizePointerUp,
|
|
2691
3639
|
title: '左右拖拽调整预览面板宽度',
|
|
2692
|
-
}),
|
|
3640
|
+
}) : null,
|
|
2693
3641
|
|
|
2694
3642
|
// Drawer Header with FileTabs
|
|
2695
3643
|
h('div', { className: 'dsh-preview-drawer-header' },
|
|
@@ -2734,11 +3682,17 @@
|
|
|
2734
3682
|
onClick: closeAllTabs,
|
|
2735
3683
|
title: '关闭所有标签页',
|
|
2736
3684
|
}, '✕ 全部') : null,
|
|
3685
|
+
h('button', {
|
|
3686
|
+
type: 'button',
|
|
3687
|
+
className: 'dsh-win-ctrl-btn',
|
|
3688
|
+
onClick: () => setIsMaximized(!globalIsMaximized),
|
|
3689
|
+
title: isMaximized ? '还原为分栏并排模式' : '最大化窗口',
|
|
3690
|
+
}, isMaximized ? '🗗 还原' : '🗖 最大化'),
|
|
2737
3691
|
h('button', {
|
|
2738
3692
|
type: 'button',
|
|
2739
3693
|
className: 'dsh-win-ctrl-btn dsh-win-ctrl-close',
|
|
2740
3694
|
onClick: () => setPanelOpen(false),
|
|
2741
|
-
title: '
|
|
3695
|
+
title: '收起预览侧边栏 (Esc)',
|
|
2742
3696
|
}, '✕')
|
|
2743
3697
|
)
|
|
2744
3698
|
),
|
|
@@ -2923,151 +3877,369 @@
|
|
|
2923
3877
|
transform: translateX(-4px);
|
|
2924
3878
|
box-shadow: -5px 5px 18px rgba(59, 130, 246, 0.22);
|
|
2925
3879
|
}
|
|
2926
|
-
body[data-ds-dark-theme] .dsh-rail-tab-item:hover,
|
|
2927
|
-
body[data-ds-dark-theme] .dsh-preview-right-rail-tab:hover,
|
|
2928
|
-
body[data-ds-dark-theme] .dsh-preview-float-pill:hover,
|
|
2929
|
-
body[data-ds-dark-theme] .dsh-git-right-rail-tab:hover {
|
|
2930
|
-
background: var(--dsw-alias-bg-layer-1, #252a36);
|
|
2931
|
-
color: #60a5fa;
|
|
2932
|
-
border-color: #3b82f6;
|
|
3880
|
+
body[data-ds-dark-theme] .dsh-rail-tab-item:hover,
|
|
3881
|
+
body[data-ds-dark-theme] .dsh-preview-right-rail-tab:hover,
|
|
3882
|
+
body[data-ds-dark-theme] .dsh-preview-float-pill:hover,
|
|
3883
|
+
body[data-ds-dark-theme] .dsh-git-right-rail-tab:hover {
|
|
3884
|
+
background: var(--dsw-alias-bg-layer-1, #252a36);
|
|
3885
|
+
color: #60a5fa;
|
|
3886
|
+
border-color: #3b82f6;
|
|
3887
|
+
}
|
|
3888
|
+
.dsh-rail-tab-item.active,
|
|
3889
|
+
.dsh-preview-right-rail-tab.active,
|
|
3890
|
+
.dsh-git-right-rail-tab.active {
|
|
3891
|
+
background: #2563eb;
|
|
3892
|
+
color: #ffffff;
|
|
3893
|
+
border-color: #1d4ed8;
|
|
3894
|
+
transform: translateX(-3px);
|
|
3895
|
+
box-shadow: -5px 5px 18px rgba(37, 99, 235, 0.35);
|
|
3896
|
+
}
|
|
3897
|
+
|
|
3898
|
+
.dsh-rail-tab-icon {
|
|
3899
|
+
display: flex;
|
|
3900
|
+
align-items: center;
|
|
3901
|
+
justify-content: center;
|
|
3902
|
+
width: 16px;
|
|
3903
|
+
height: 16px;
|
|
3904
|
+
min-width: 16px;
|
|
3905
|
+
min-height: 16px;
|
|
3906
|
+
max-width: 16px;
|
|
3907
|
+
max-height: 16px;
|
|
3908
|
+
line-height: 1;
|
|
3909
|
+
flex-shrink: 0;
|
|
3910
|
+
transition: none !important;
|
|
3911
|
+
animation: none !important;
|
|
3912
|
+
transform: none !important;
|
|
3913
|
+
}
|
|
3914
|
+
.dsh-rail-tab-icon svg {
|
|
3915
|
+
width: 14px !important;
|
|
3916
|
+
height: 14px !important;
|
|
3917
|
+
min-width: 14px !important;
|
|
3918
|
+
min-height: 14px !important;
|
|
3919
|
+
max-width: 14px !important;
|
|
3920
|
+
max-height: 14px !important;
|
|
3921
|
+
flex-shrink: 0 !important;
|
|
3922
|
+
display: block !important;
|
|
3923
|
+
transition: none !important;
|
|
3924
|
+
animation: none !important;
|
|
3925
|
+
transform: none !important;
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
.dsh-rail-tab-title, .dsh-preview-rail-title, .dsh-git-rail-title {
|
|
3929
|
+
writing-mode: vertical-rl;
|
|
3930
|
+
text-orientation: mixed;
|
|
3931
|
+
font-size: 11.5px;
|
|
3932
|
+
font-weight: 600;
|
|
3933
|
+
letter-spacing: 2px;
|
|
3934
|
+
line-height: 1;
|
|
3935
|
+
flex-shrink: 0;
|
|
3936
|
+
user-select: none;
|
|
3937
|
+
transition: none !important;
|
|
3938
|
+
animation: none !important;
|
|
3939
|
+
transform: none !important;
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
.dsh-rail-tab-badge, .dsh-float-pill-badge, .dsh-preview-rail-badge, .dsh-git-rail-badge {
|
|
3943
|
+
writing-mode: horizontal-tb;
|
|
3944
|
+
display: inline-flex;
|
|
3945
|
+
align-items: center;
|
|
3946
|
+
justify-content: center;
|
|
3947
|
+
min-width: 17px;
|
|
3948
|
+
height: 17px;
|
|
3949
|
+
min-height: 17px;
|
|
3950
|
+
max-height: 17px;
|
|
3951
|
+
padding: 0 4px;
|
|
3952
|
+
border-radius: 9px;
|
|
3953
|
+
background: #2563eb;
|
|
3954
|
+
color: #ffffff;
|
|
3955
|
+
font-size: 10px;
|
|
3956
|
+
font-weight: 700;
|
|
3957
|
+
line-height: 1;
|
|
3958
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
3959
|
+
flex-shrink: 0;
|
|
3960
|
+
box-sizing: border-box;
|
|
3961
|
+
transition: none !important;
|
|
3962
|
+
animation: none !important;
|
|
3963
|
+
transform: none !important;
|
|
3964
|
+
}
|
|
3965
|
+
body[data-ds-dark-theme] .dsh-rail-tab-badge,
|
|
3966
|
+
body[data-ds-dark-theme] .dsh-float-pill-badge,
|
|
3967
|
+
body[data-ds-dark-theme] .dsh-preview-rail-badge,
|
|
3968
|
+
body[data-ds-dark-theme] .dsh-git-rail-badge {
|
|
3969
|
+
background: #3b82f6;
|
|
3970
|
+
}
|
|
3971
|
+
.dsh-rail-tab-item.active .dsh-rail-tab-badge,
|
|
3972
|
+
.dsh-preview-right-rail-tab.active .dsh-preview-rail-badge,
|
|
3973
|
+
.dsh-git-right-rail-tab.active .dsh-git-rail-badge {
|
|
3974
|
+
background: #ffffff;
|
|
3975
|
+
color: #2563eb;
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3978
|
+
.dsh-rail-tab-badge-placeholder {
|
|
3979
|
+
width: 17px;
|
|
3980
|
+
height: 17px;
|
|
3981
|
+
min-height: 17px;
|
|
3982
|
+
max-height: 17px;
|
|
3983
|
+
flex-shrink: 0;
|
|
3984
|
+
visibility: hidden;
|
|
3985
|
+
}
|
|
3986
|
+
body[data-ds-dark-theme] .dsh-rail-tab-badge,
|
|
3987
|
+
body[data-ds-dark-theme] .dsh-float-pill-badge,
|
|
3988
|
+
body[data-ds-dark-theme] .dsh-preview-rail-badge {
|
|
3989
|
+
background: #3b82f6;
|
|
3990
|
+
}
|
|
3991
|
+
.dsh-rail-tab-item.active .dsh-rail-tab-badge,
|
|
3992
|
+
.dsh-preview-right-rail-tab.active .dsh-float-pill-badge,
|
|
3993
|
+
.dsh-preview-right-rail-tab.active .dsh-preview-rail-badge {
|
|
3994
|
+
background: #ffffff;
|
|
3995
|
+
color: #2563eb;
|
|
3996
|
+
}
|
|
3997
|
+
|
|
3998
|
+
/* Side-by-Side Right Sidebar Layout Integration */
|
|
3999
|
+
body.dsh-preview-sidebar-active div[class*="centerCol"] {
|
|
4000
|
+
margin-right: var(--dsh-preview-width, 620px) !important;
|
|
4001
|
+
max-width: calc(100% - var(--dsh-preview-width, 620px)) !important;
|
|
4002
|
+
box-sizing: border-box !important;
|
|
4003
|
+
transition: margin-right 0.18s cubic-bezier(0.16, 1, 0.3, 1), max-width 0.18s cubic-bezier(0.16, 1, 0.3, 1) !important;
|
|
4004
|
+
}
|
|
4005
|
+
body.dsh-preview-sidebar-active.dsh-preview-maximized div[class*="centerCol"] {
|
|
4006
|
+
display: none !important;
|
|
4007
|
+
}
|
|
4008
|
+
body.dsh-preview-sidebar-active.dsh-preview-maximized .dsh-preview-drawer-container {
|
|
4009
|
+
width: 100vw !important;
|
|
4010
|
+
}
|
|
4011
|
+
|
|
4012
|
+
/* Workspace Switcher Header & Popover */
|
|
4013
|
+
.dsh-tree-sidebar-title.dsh-tree-title-clickable {
|
|
4014
|
+
cursor: pointer;
|
|
4015
|
+
padding: 2px 6px;
|
|
4016
|
+
border-radius: 4px;
|
|
4017
|
+
transition: background 0.15s;
|
|
4018
|
+
gap: 4px;
|
|
4019
|
+
}
|
|
4020
|
+
.dsh-tree-sidebar-title.dsh-tree-title-clickable:hover {
|
|
4021
|
+
background: rgba(0, 0, 0, 0.06);
|
|
4022
|
+
}
|
|
4023
|
+
body[data-ds-dark-theme] .dsh-tree-sidebar-title.dsh-tree-title-clickable:hover {
|
|
4024
|
+
background: rgba(255, 255, 255, 0.08);
|
|
4025
|
+
}
|
|
4026
|
+
.dsh-tree-header-chevron {
|
|
4027
|
+
font-size: 8px;
|
|
4028
|
+
opacity: 0.6;
|
|
4029
|
+
margin-left: 2px;
|
|
4030
|
+
}
|
|
4031
|
+
|
|
4032
|
+
.dsh-ws-selector-popover {
|
|
4033
|
+
position: absolute;
|
|
4034
|
+
top: 38px;
|
|
4035
|
+
left: 6px;
|
|
4036
|
+
right: 6px;
|
|
4037
|
+
max-width: 380px;
|
|
4038
|
+
z-index: 1000;
|
|
4039
|
+
background: var(--dsw-alias-bg-layer-1, #ffffff);
|
|
4040
|
+
border: 1px solid var(--dsw-alias-border-l2, #cbd5e1);
|
|
4041
|
+
border-radius: 8px;
|
|
4042
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
|
|
4043
|
+
padding: 10px;
|
|
4044
|
+
display: flex;
|
|
4045
|
+
flex-direction: column;
|
|
4046
|
+
gap: 8px;
|
|
4047
|
+
box-sizing: border-box;
|
|
4048
|
+
animation: dshPopoverFadeIn 0.15s ease;
|
|
4049
|
+
}
|
|
4050
|
+
body[data-ds-dark-theme] .dsh-ws-selector-popover {
|
|
4051
|
+
background: var(--dsw-alias-bg-layer-2, #1b202c);
|
|
4052
|
+
border-color: var(--dsw-alias-border-l2, #334155);
|
|
4053
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
|
|
4054
|
+
}
|
|
4055
|
+
@keyframes dshPopoverFadeIn {
|
|
4056
|
+
from { opacity: 0; transform: translateY(-4px); }
|
|
4057
|
+
to { opacity: 1; transform: translateY(0); }
|
|
4058
|
+
}
|
|
4059
|
+
|
|
4060
|
+
.dsh-ws-popover-header {
|
|
4061
|
+
display: flex;
|
|
4062
|
+
align-items: center;
|
|
4063
|
+
justify-content: space-between;
|
|
4064
|
+
padding-bottom: 6px;
|
|
4065
|
+
border-bottom: 1px solid var(--dsw-alias-border-l3, #e2e8f0);
|
|
4066
|
+
color: var(--dsw-alias-label-primary, #0f172a);
|
|
4067
|
+
}
|
|
4068
|
+
body[data-ds-dark-theme] .dsh-ws-popover-header {
|
|
4069
|
+
border-bottom-color: var(--dsw-alias-border-l3, #2d3748);
|
|
4070
|
+
color: #f1f5f9;
|
|
4071
|
+
}
|
|
4072
|
+
.dsh-ws-popover-close {
|
|
4073
|
+
background: none;
|
|
4074
|
+
border: none;
|
|
4075
|
+
color: var(--dsw-alias-label-secondary, #64748b);
|
|
4076
|
+
cursor: pointer;
|
|
4077
|
+
font-size: 11px;
|
|
4078
|
+
padding: 2px 4px;
|
|
4079
|
+
border-radius: 3px;
|
|
4080
|
+
}
|
|
4081
|
+
.dsh-ws-popover-close:hover {
|
|
4082
|
+
background: rgba(0, 0, 0, 0.06);
|
|
4083
|
+
color: var(--dsw-alias-label-primary, #0f172a);
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4086
|
+
.dsh-ws-list {
|
|
4087
|
+
display: flex;
|
|
4088
|
+
flex-direction: column;
|
|
4089
|
+
gap: 3px;
|
|
4090
|
+
max-height: 180px;
|
|
4091
|
+
overflow-y: auto;
|
|
4092
|
+
padding: 2px 0;
|
|
4093
|
+
}
|
|
4094
|
+
.dsh-ws-item {
|
|
4095
|
+
display: flex;
|
|
4096
|
+
align-items: center;
|
|
4097
|
+
justify-content: space-between;
|
|
4098
|
+
padding: 6px 8px;
|
|
4099
|
+
border-radius: 6px;
|
|
4100
|
+
cursor: pointer;
|
|
4101
|
+
transition: background 0.12s;
|
|
4102
|
+
font-size: 12px;
|
|
4103
|
+
}
|
|
4104
|
+
.dsh-ws-item:hover {
|
|
4105
|
+
background: var(--dsw-alias-bg-hover, rgba(0, 0, 0, 0.05));
|
|
4106
|
+
}
|
|
4107
|
+
body[data-ds-dark-theme] .dsh-ws-item:hover {
|
|
4108
|
+
background: rgba(255, 255, 255, 0.06);
|
|
4109
|
+
}
|
|
4110
|
+
.dsh-ws-item.active {
|
|
4111
|
+
background: rgba(14, 165, 233, 0.12);
|
|
4112
|
+
color: #0284c7;
|
|
4113
|
+
font-weight: 600;
|
|
4114
|
+
}
|
|
4115
|
+
body[data-ds-dark-theme] .dsh-ws-item.active {
|
|
4116
|
+
background: rgba(56, 189, 248, 0.16);
|
|
4117
|
+
color: #38bdf8;
|
|
4118
|
+
}
|
|
4119
|
+
.dsh-ws-item-info {
|
|
4120
|
+
display: flex;
|
|
4121
|
+
flex-direction: column;
|
|
4122
|
+
gap: 1px;
|
|
4123
|
+
overflow: hidden;
|
|
4124
|
+
flex: 1;
|
|
4125
|
+
}
|
|
4126
|
+
.dsh-ws-item-name {
|
|
4127
|
+
font-size: 12px;
|
|
4128
|
+
font-weight: 600;
|
|
4129
|
+
overflow: hidden;
|
|
4130
|
+
text-overflow: ellipsis;
|
|
4131
|
+
white-space: nowrap;
|
|
4132
|
+
}
|
|
4133
|
+
.dsh-ws-item-path {
|
|
4134
|
+
font-size: 10px;
|
|
4135
|
+
color: var(--dsw-alias-label-tertiary, #94a3b8);
|
|
4136
|
+
overflow: hidden;
|
|
4137
|
+
text-overflow: ellipsis;
|
|
4138
|
+
white-space: nowrap;
|
|
4139
|
+
}
|
|
4140
|
+
.dsh-ws-item-check {
|
|
4141
|
+
color: #0284c7;
|
|
4142
|
+
font-weight: bold;
|
|
4143
|
+
margin-left: 6px;
|
|
2933
4144
|
}
|
|
2934
|
-
.dsh-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
color: #
|
|
2939
|
-
border-color: #1d4ed8;
|
|
2940
|
-
transform: translateX(-3px);
|
|
2941
|
-
box-shadow: -5px 5px 18px rgba(37, 99, 235, 0.35);
|
|
4145
|
+
.dsh-ws-empty {
|
|
4146
|
+
padding: 12px;
|
|
4147
|
+
text-align: center;
|
|
4148
|
+
font-size: 11px;
|
|
4149
|
+
color: var(--dsw-alias-label-secondary, #64748b);
|
|
2942
4150
|
}
|
|
2943
4151
|
|
|
2944
|
-
.dsh-
|
|
4152
|
+
.dsh-ws-custom-form {
|
|
4153
|
+
margin-top: 4px;
|
|
4154
|
+
padding-top: 6px;
|
|
4155
|
+
border-top: 1px solid var(--dsw-alias-border-l3, #e2e8f0);
|
|
4156
|
+
}
|
|
4157
|
+
body[data-ds-dark-theme] .dsh-ws-custom-form {
|
|
4158
|
+
border-top-color: var(--dsw-alias-border-l3, #2d3748);
|
|
4159
|
+
}
|
|
4160
|
+
.dsh-ws-input-row {
|
|
2945
4161
|
display: flex;
|
|
4162
|
+
gap: 6px;
|
|
2946
4163
|
align-items: center;
|
|
2947
|
-
justify-content: center;
|
|
2948
|
-
width: 16px;
|
|
2949
|
-
height: 16px;
|
|
2950
|
-
min-width: 16px;
|
|
2951
|
-
min-height: 16px;
|
|
2952
|
-
max-width: 16px;
|
|
2953
|
-
max-height: 16px;
|
|
2954
|
-
line-height: 1;
|
|
2955
|
-
flex-shrink: 0;
|
|
2956
|
-
transition: none !important;
|
|
2957
|
-
animation: none !important;
|
|
2958
|
-
transform: none !important;
|
|
2959
4164
|
}
|
|
2960
|
-
.dsh-
|
|
2961
|
-
|
|
2962
|
-
height:
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
animation: none !important;
|
|
2971
|
-
transform: none !important;
|
|
4165
|
+
.dsh-ws-input {
|
|
4166
|
+
flex: 1;
|
|
4167
|
+
height: 26px;
|
|
4168
|
+
border-radius: 4px;
|
|
4169
|
+
border: 1px solid var(--dsw-alias-border-l2, #cbd5e1);
|
|
4170
|
+
padding: 0 8px;
|
|
4171
|
+
font-size: 11px;
|
|
4172
|
+
background: var(--dsw-alias-bg-base, #ffffff);
|
|
4173
|
+
color: inherit;
|
|
4174
|
+
outline: none;
|
|
2972
4175
|
}
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
font-size: 11.5px;
|
|
2978
|
-
font-weight: 600;
|
|
2979
|
-
letter-spacing: 2px;
|
|
2980
|
-
line-height: 1;
|
|
2981
|
-
flex-shrink: 0;
|
|
2982
|
-
user-select: none;
|
|
2983
|
-
transition: none !important;
|
|
2984
|
-
animation: none !important;
|
|
2985
|
-
transform: none !important;
|
|
4176
|
+
body[data-ds-dark-theme] .dsh-ws-input {
|
|
4177
|
+
background: #141720;
|
|
4178
|
+
border-color: #334155;
|
|
4179
|
+
color: #f1f5f9;
|
|
2986
4180
|
}
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
min-width: 17px;
|
|
2994
|
-
height: 17px;
|
|
2995
|
-
min-height: 17px;
|
|
2996
|
-
max-height: 17px;
|
|
2997
|
-
padding: 0 4px;
|
|
2998
|
-
border-radius: 9px;
|
|
2999
|
-
background: #2563eb;
|
|
4181
|
+
.dsh-ws-btn {
|
|
4182
|
+
height: 26px;
|
|
4183
|
+
padding: 0 10px;
|
|
4184
|
+
border-radius: 4px;
|
|
4185
|
+
border: none;
|
|
4186
|
+
background: var(--dsw-static-deepseek-500, #4176e6);
|
|
3000
4187
|
color: #ffffff;
|
|
3001
|
-
font-size:
|
|
3002
|
-
font-weight:
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
flex-shrink: 0;
|
|
3006
|
-
box-sizing: border-box;
|
|
3007
|
-
transition: none !important;
|
|
3008
|
-
animation: none !important;
|
|
3009
|
-
transform: none !important;
|
|
3010
|
-
}
|
|
3011
|
-
body[data-ds-dark-theme] .dsh-rail-tab-badge,
|
|
3012
|
-
body[data-ds-dark-theme] .dsh-float-pill-badge,
|
|
3013
|
-
body[data-ds-dark-theme] .dsh-preview-rail-badge,
|
|
3014
|
-
body[data-ds-dark-theme] .dsh-git-rail-badge {
|
|
3015
|
-
background: #3b82f6;
|
|
4188
|
+
font-size: 11px;
|
|
4189
|
+
font-weight: 500;
|
|
4190
|
+
cursor: pointer;
|
|
4191
|
+
white-space: nowrap;
|
|
3016
4192
|
}
|
|
3017
|
-
.dsh-
|
|
3018
|
-
|
|
3019
|
-
.dsh-git-right-rail-tab.active .dsh-git-rail-badge {
|
|
3020
|
-
background: #ffffff;
|
|
3021
|
-
color: #2563eb;
|
|
4193
|
+
.dsh-ws-btn:hover {
|
|
4194
|
+
opacity: 0.9;
|
|
3022
4195
|
}
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
height: 17px;
|
|
3027
|
-
min-height: 17px;
|
|
3028
|
-
max-height: 17px;
|
|
3029
|
-
flex-shrink: 0;
|
|
3030
|
-
visibility: hidden;
|
|
4196
|
+
.dsh-ws-btn:disabled {
|
|
4197
|
+
opacity: 0.5;
|
|
4198
|
+
cursor: not-allowed;
|
|
3031
4199
|
}
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
background:
|
|
4200
|
+
.dsh-ws-reset-btn {
|
|
4201
|
+
font-size: 11px;
|
|
4202
|
+
color: var(--dsw-static-deepseek-500, #4176e6);
|
|
4203
|
+
background: none;
|
|
4204
|
+
border: none;
|
|
4205
|
+
cursor: pointer;
|
|
4206
|
+
padding: 4px 0 0;
|
|
4207
|
+
text-align: left;
|
|
4208
|
+
text-decoration: underline;
|
|
3036
4209
|
}
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
.dsh-preview-right-rail-tab.active .dsh-preview-rail-badge {
|
|
3040
|
-
background: #ffffff;
|
|
3041
|
-
color: #2563eb;
|
|
4210
|
+
body[data-ds-dark-theme] .dsh-ws-reset-btn {
|
|
4211
|
+
color: #60a5fa;
|
|
3042
4212
|
}
|
|
3043
4213
|
|
|
3044
|
-
/* Right-Side
|
|
4214
|
+
/* Right-Side Persistent / Split Sidebar Container */
|
|
3045
4215
|
.dsh-preview-drawer-container {
|
|
3046
4216
|
position: fixed;
|
|
3047
4217
|
top: 0;
|
|
3048
4218
|
right: 0;
|
|
3049
4219
|
bottom: 0;
|
|
4220
|
+
width: var(--dsh-preview-width, 620px);
|
|
3050
4221
|
height: 100vh;
|
|
3051
|
-
z-index:
|
|
4222
|
+
z-index: 990;
|
|
3052
4223
|
background: var(--dsw-alias-bg-layer-1, #ffffff);
|
|
3053
4224
|
color: var(--dsw-alias-label-primary, #0f172a);
|
|
3054
4225
|
border-left: 1px solid var(--dsw-alias-border-l3, #e2e8f0);
|
|
3055
|
-
box-shadow: -
|
|
4226
|
+
box-shadow: -4px 0 16px rgba(0, 0, 0, 0.08);
|
|
3056
4227
|
display: flex;
|
|
3057
4228
|
flex-direction: column;
|
|
3058
4229
|
box-sizing: border-box;
|
|
3059
4230
|
overflow: hidden;
|
|
4231
|
+
transition: width 0.18s cubic-bezier(0.16, 1, 0.3, 1);
|
|
3060
4232
|
animation: dshPreviewSlideIn 0.24s cubic-bezier(0.16, 1, 0.3, 1);
|
|
3061
4233
|
}
|
|
3062
4234
|
body[data-ds-dark-theme] .dsh-preview-drawer-container {
|
|
3063
4235
|
background: var(--dsw-alias-bg-layer-1, #161922);
|
|
3064
4236
|
color: var(--dsw-alias-label-primary, #e2e8f0);
|
|
3065
4237
|
border-left-color: var(--dsw-alias-border-l3, #2d3748);
|
|
3066
|
-
box-shadow: -
|
|
4238
|
+
box-shadow: -6px 0 20px rgba(0, 0, 0, 0.35);
|
|
3067
4239
|
}
|
|
3068
4240
|
.dsh-preview-drawer-container.dragging {
|
|
3069
4241
|
user-select: none;
|
|
3070
|
-
transition: none;
|
|
4242
|
+
transition: none !important;
|
|
3071
4243
|
}
|
|
3072
4244
|
@keyframes dshPreviewSlideIn {
|
|
3073
4245
|
from { transform: translateX(100%); }
|
|
@@ -4290,6 +5462,124 @@
|
|
|
4290
5462
|
}
|
|
4291
5463
|
.dsh-tree-footer-dot { font-size: 8px; }
|
|
4292
5464
|
|
|
5465
|
+
/* Drag & Drop & Cut Styles */
|
|
5466
|
+
.dsh-tree-node-row.drop-target {
|
|
5467
|
+
background: rgba(14, 165, 233, 0.22) !important;
|
|
5468
|
+
outline: 1px dashed #0284c7 !important;
|
|
5469
|
+
color: #0284c7 !important;
|
|
5470
|
+
}
|
|
5471
|
+
body[data-ds-dark-theme] .dsh-tree-node-row.drop-target {
|
|
5472
|
+
background: rgba(56, 189, 248, 0.25) !important;
|
|
5473
|
+
outline-color: #38bdf8 !important;
|
|
5474
|
+
color: #38bdf8 !important;
|
|
5475
|
+
}
|
|
5476
|
+
.dsh-tree-node-list.drop-target-root {
|
|
5477
|
+
background: rgba(14, 165, 233, 0.08) !important;
|
|
5478
|
+
outline: 2px dashed #0284c7 !important;
|
|
5479
|
+
outline-offset: -4px;
|
|
5480
|
+
}
|
|
5481
|
+
body[data-ds-dark-theme] .dsh-tree-node-list.drop-target-root {
|
|
5482
|
+
background: rgba(56, 189, 248, 0.1) !important;
|
|
5483
|
+
outline-color: #38bdf8 !important;
|
|
5484
|
+
}
|
|
5485
|
+
.dsh-tree-node-row.is-dragged {
|
|
5486
|
+
opacity: 0.45;
|
|
5487
|
+
filter: grayscale(0.6);
|
|
5488
|
+
}
|
|
5489
|
+
.dsh-tree-node-row.is-cut {
|
|
5490
|
+
opacity: 0.5;
|
|
5491
|
+
filter: grayscale(0.5);
|
|
5492
|
+
border: 1px dashed #94a3b8;
|
|
5493
|
+
}
|
|
5494
|
+
|
|
5495
|
+
/* Inline Creation & Rename Input */
|
|
5496
|
+
.dsh-tree-inline-input-row {
|
|
5497
|
+
display: flex;
|
|
5498
|
+
align-items: center;
|
|
5499
|
+
height: 24px;
|
|
5500
|
+
margin: 1px 4px;
|
|
5501
|
+
gap: 3px;
|
|
5502
|
+
box-sizing: border-box;
|
|
5503
|
+
}
|
|
5504
|
+
.dsh-tree-inline-input {
|
|
5505
|
+
flex: 1;
|
|
5506
|
+
height: 20px;
|
|
5507
|
+
border-radius: 3px;
|
|
5508
|
+
border: 1px solid #38bdf8;
|
|
5509
|
+
background: var(--dsw-alias-bg-base, #ffffff);
|
|
5510
|
+
color: inherit;
|
|
5511
|
+
font-size: 11px;
|
|
5512
|
+
padding: 0 4px;
|
|
5513
|
+
outline: none;
|
|
5514
|
+
box-sizing: border-box;
|
|
5515
|
+
box-shadow: 0 0 0 1px rgba(56, 189, 248, 0.4);
|
|
5516
|
+
}
|
|
5517
|
+
body[data-ds-dark-theme] .dsh-tree-inline-input {
|
|
5518
|
+
background: #1e2330;
|
|
5519
|
+
color: #f1f5f9;
|
|
5520
|
+
border-color: #38bdf8;
|
|
5521
|
+
}
|
|
5522
|
+
|
|
5523
|
+
/* Tree Context Menu */
|
|
5524
|
+
.dsh-tree-context-menu {
|
|
5525
|
+
position: fixed;
|
|
5526
|
+
min-width: 170px;
|
|
5527
|
+
background: var(--dsw-alias-bg-layer-1, #ffffff);
|
|
5528
|
+
border: 1px solid var(--dsw-alias-border-l3, #e2e8f0);
|
|
5529
|
+
border-radius: 6px;
|
|
5530
|
+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.2), 0 8px 10px -6px rgba(0, 0, 0, 0.15);
|
|
5531
|
+
padding: 4px;
|
|
5532
|
+
user-select: none;
|
|
5533
|
+
font-family: inherit;
|
|
5534
|
+
}
|
|
5535
|
+
body[data-ds-dark-theme] .dsh-tree-context-menu {
|
|
5536
|
+
background: #1a1e29;
|
|
5537
|
+
border-color: #2e384d;
|
|
5538
|
+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
|
|
5539
|
+
}
|
|
5540
|
+
.dsh-menu-item {
|
|
5541
|
+
display: flex;
|
|
5542
|
+
align-items: center;
|
|
5543
|
+
gap: 8px;
|
|
5544
|
+
padding: 5px 8px;
|
|
5545
|
+
border-radius: 4px;
|
|
5546
|
+
font-size: 12px;
|
|
5547
|
+
cursor: pointer;
|
|
5548
|
+
color: var(--dsw-alias-label-primary, #1e293b);
|
|
5549
|
+
transition: background 0.1s;
|
|
5550
|
+
}
|
|
5551
|
+
body[data-ds-dark-theme] .dsh-menu-item {
|
|
5552
|
+
color: #e2e8f0;
|
|
5553
|
+
}
|
|
5554
|
+
.dsh-menu-item:hover {
|
|
5555
|
+
background: var(--dsw-alias-bg-hover, rgba(0, 0, 0, 0.06));
|
|
5556
|
+
}
|
|
5557
|
+
body[data-ds-dark-theme] .dsh-menu-item:hover {
|
|
5558
|
+
background: rgba(255, 255, 255, 0.09);
|
|
5559
|
+
}
|
|
5560
|
+
.dsh-menu-item-danger {
|
|
5561
|
+
color: #ef4444 !important;
|
|
5562
|
+
}
|
|
5563
|
+
.dsh-menu-item-danger:hover {
|
|
5564
|
+
background: rgba(239, 68, 68, 0.12) !important;
|
|
5565
|
+
}
|
|
5566
|
+
.dsh-menu-icon {
|
|
5567
|
+
font-size: 13px;
|
|
5568
|
+
width: 16px;
|
|
5569
|
+
display: inline-flex;
|
|
5570
|
+
align-items: center;
|
|
5571
|
+
justify-content: center;
|
|
5572
|
+
flex-shrink: 0;
|
|
5573
|
+
}
|
|
5574
|
+
.dsh-menu-divider {
|
|
5575
|
+
height: 1px;
|
|
5576
|
+
background: var(--dsw-alias-border-l3, #e2e8f0);
|
|
5577
|
+
margin: 3px 4px;
|
|
5578
|
+
}
|
|
5579
|
+
body[data-ds-dark-theme] .dsh-menu-divider {
|
|
5580
|
+
background: #2a3140;
|
|
5581
|
+
}
|
|
5582
|
+
|
|
4293
5583
|
/* Splitter between Tree and Preview */
|
|
4294
5584
|
.dsh-tree-splitter {
|
|
4295
5585
|
width: 4px;
|
|
@@ -4409,85 +5699,89 @@
|
|
|
4409
5699
|
body[data-ds-dark-theme] .tok-prop { color: #9cdcfe; }
|
|
4410
5700
|
body[data-ds-dark-theme] .tok-selector { color: #d7ba7d; }
|
|
4411
5701
|
|
|
4412
|
-
/* Code Editor Viewer */
|
|
5702
|
+
/* Code Editor Viewer (with unified line row architecture for 100% perfect alignment) */
|
|
4413
5703
|
.dsh-code-editor-view {
|
|
4414
5704
|
display: flex;
|
|
5705
|
+
flex-direction: column;
|
|
5706
|
+
width: 100%;
|
|
4415
5707
|
min-height: 100%;
|
|
4416
|
-
|
|
4417
|
-
font-
|
|
5708
|
+
overflow: auto;
|
|
5709
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Cascadia Code", "Fira Code", monospace;
|
|
5710
|
+
font-size: 12px;
|
|
4418
5711
|
line-height: 20px;
|
|
4419
5712
|
background: var(--dsw-alias-bg-layer-1, #ffffff);
|
|
4420
5713
|
color: var(--dsw-alias-label-primary, #1e293b);
|
|
5714
|
+
padding: 8px 0;
|
|
5715
|
+
box-sizing: border-box;
|
|
5716
|
+
user-select: text;
|
|
4421
5717
|
}
|
|
4422
5718
|
body[data-ds-dark-theme] .dsh-code-editor-view {
|
|
4423
|
-
background: #
|
|
5719
|
+
background: #161922;
|
|
4424
5720
|
color: #d4d4d4;
|
|
4425
5721
|
}
|
|
4426
5722
|
|
|
4427
|
-
.dsh-code-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
5723
|
+
.dsh-code-line {
|
|
5724
|
+
display: flex;
|
|
5725
|
+
flex-direction: row;
|
|
5726
|
+
align-items: flex-start;
|
|
5727
|
+
min-height: 20px;
|
|
5728
|
+
line-height: 20px;
|
|
5729
|
+
width: 100%;
|
|
5730
|
+
box-sizing: border-box;
|
|
5731
|
+
transition: background 0.1s;
|
|
5732
|
+
}
|
|
5733
|
+
.dsh-code-line:hover {
|
|
5734
|
+
background: rgba(0, 0, 0, 0.035);
|
|
5735
|
+
}
|
|
5736
|
+
body[data-ds-dark-theme] .dsh-code-line:hover {
|
|
5737
|
+
background: rgba(255, 255, 255, 0.035);
|
|
5738
|
+
}
|
|
5739
|
+
.dsh-code-line.dsh-line-highlight-flash {
|
|
5740
|
+
background: rgba(14, 165, 233, 0.22) !important;
|
|
5741
|
+
}
|
|
5742
|
+
|
|
5743
|
+
.dsh-line-gutter {
|
|
5744
|
+
display: inline-block;
|
|
5745
|
+
box-sizing: border-box;
|
|
5746
|
+
padding: 0 8px 0 6px;
|
|
4431
5747
|
text-align: right;
|
|
4432
5748
|
user-select: none;
|
|
4433
|
-
background: var(--dsw-alias-bg-layer-2, #f8fafc);
|
|
4434
|
-
border-right: 1px solid var(--dsw-alias-border-l3, #e2e8f0);
|
|
4435
5749
|
color: var(--dsw-alias-label-tertiary, #94a3b8);
|
|
4436
|
-
font-size:
|
|
5750
|
+
font-size: 11px;
|
|
4437
5751
|
line-height: 20px;
|
|
4438
5752
|
flex-shrink: 0;
|
|
5753
|
+
border-right: 1px solid var(--dsw-alias-border-l3, #e2e8f0);
|
|
5754
|
+
cursor: pointer;
|
|
5755
|
+
transition: color 0.1s;
|
|
4439
5756
|
}
|
|
4440
|
-
body[data-ds-dark-theme] .dsh-
|
|
4441
|
-
background: #141720;
|
|
5757
|
+
body[data-ds-dark-theme] .dsh-line-gutter {
|
|
4442
5758
|
border-right-color: #2a3140;
|
|
4443
|
-
color: #
|
|
4444
|
-
}
|
|
4445
|
-
|
|
4446
|
-
.dsh-gutter-line {
|
|
4447
|
-
height: 20px;
|
|
4448
|
-
cursor: pointer;
|
|
4449
|
-
padding-right: 8px;
|
|
5759
|
+
color: #55637a;
|
|
4450
5760
|
}
|
|
4451
|
-
.dsh-gutter
|
|
5761
|
+
.dsh-line-gutter:hover {
|
|
4452
5762
|
color: var(--dsw-brand-primary, #0284c7);
|
|
4453
|
-
font-weight:
|
|
5763
|
+
font-weight: 600;
|
|
4454
5764
|
}
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
color: #0284c7;
|
|
5765
|
+
body[data-ds-dark-theme] .dsh-line-gutter:hover {
|
|
5766
|
+
color: #38bdf8;
|
|
4458
5767
|
}
|
|
4459
5768
|
|
|
4460
|
-
.dsh-code
|
|
5769
|
+
.dsh-line-code {
|
|
4461
5770
|
flex: 1;
|
|
4462
5771
|
min-width: 0;
|
|
4463
|
-
padding: 12px
|
|
4464
|
-
|
|
4465
|
-
}
|
|
4466
|
-
.dsh-code-pre {
|
|
4467
|
-
margin: 0;
|
|
5772
|
+
padding: 0 12px 0 10px;
|
|
5773
|
+
white-space: pre;
|
|
4468
5774
|
font-family: inherit;
|
|
4469
5775
|
font-size: inherit;
|
|
4470
5776
|
line-height: 20px;
|
|
4471
5777
|
tab-size: 2;
|
|
4472
|
-
|
|
5778
|
+
box-sizing: border-box;
|
|
4473
5779
|
}
|
|
4474
|
-
.dsh-code-editor-view.word-wrap .dsh-code
|
|
5780
|
+
.dsh-code-editor-view.word-wrap .dsh-line-code {
|
|
4475
5781
|
white-space: pre-wrap;
|
|
4476
5782
|
word-break: normal;
|
|
4477
5783
|
overflow-wrap: break-word;
|
|
4478
5784
|
}
|
|
4479
|
-
.dsh-code-editor-view {
|
|
4480
|
-
display: flex;
|
|
4481
|
-
min-height: 100%;
|
|
4482
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
4483
|
-
font-size: 12.5px;
|
|
4484
|
-
line-height: 20px;
|
|
4485
|
-
background: var(--dsw-alias-bg-layer-1, #ffffff);
|
|
4486
|
-
color: var(--dsw-alias-label-primary, #1e293b);
|
|
4487
|
-
}
|
|
4488
|
-
.dsh-code-pre code {
|
|
4489
|
-
font-family: inherit;
|
|
4490
|
-
}
|
|
4491
5785
|
|
|
4492
5786
|
/* Interactive JSON Inspector */
|
|
4493
5787
|
.dsh-json-inspector {
|