@rooode/dsh-plugin-preview 0.1.14 → 0.1.16
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/lib/client.js +174 -68
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
// Storage & State Constants
|
|
23
23
|
// =========================================================================
|
|
24
24
|
const STORAGE_KEY_TABS = 'dsh:preview:tabs:v1';
|
|
25
|
+
const STORAGE_KEY_WS_TABS = 'dsh:preview:ws_tabs:v2';
|
|
25
26
|
const STORAGE_KEY_WIDTH = 'dsh:preview:panel_width:v1';
|
|
26
27
|
const STORAGE_KEY_TREE_OPEN = 'dsh:preview:tree:open:v1';
|
|
27
28
|
const STORAGE_KEY_TREE_WIDTH = 'dsh:preview:tree:width:v1';
|
|
@@ -44,9 +45,11 @@
|
|
|
44
45
|
]);
|
|
45
46
|
|
|
46
47
|
// =========================================================================
|
|
47
|
-
// Global Event Bus & State Store
|
|
48
|
+
// Global Event Bus & State Store (Workspace-Scoped Tabs)
|
|
48
49
|
// =========================================================================
|
|
49
50
|
let clientCtx = null;
|
|
51
|
+
let workspaceTabsMap = {}; // { [normalizedWsKey: string]: { tabs: Tab[], activeTabId: string | null } }
|
|
52
|
+
let currentWorkspaceKey = '';
|
|
50
53
|
let globalTabs = [];
|
|
51
54
|
let globalActiveTabId = null;
|
|
52
55
|
let globalIsPanelOpen = false;
|
|
@@ -58,6 +61,11 @@
|
|
|
58
61
|
let globalLastOpenTime = 0;
|
|
59
62
|
const stateListeners = new Set();
|
|
60
63
|
|
|
64
|
+
function normalizeWorkspaceKey(rawPath) {
|
|
65
|
+
if (!rawPath || typeof rawPath !== 'string' || !rawPath.trim()) return '__default__';
|
|
66
|
+
return rawPath.trim().replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase();
|
|
67
|
+
}
|
|
68
|
+
|
|
61
69
|
try {
|
|
62
70
|
const savedManualWs = localStorage.getItem(STORAGE_KEY_MANUAL_WS);
|
|
63
71
|
if (savedManualWs && savedManualWs.trim()) {
|
|
@@ -92,28 +100,86 @@
|
|
|
92
100
|
}
|
|
93
101
|
} catch (e) {}
|
|
94
102
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
function loadAllWorkspaceTabsFromStorage() {
|
|
104
|
+
try {
|
|
105
|
+
const raw = localStorage.getItem(STORAGE_KEY_WS_TABS);
|
|
106
|
+
if (raw) {
|
|
107
|
+
workspaceTabsMap = JSON.parse(raw) || {};
|
|
108
|
+
} else {
|
|
109
|
+
// Backward-compatible fallback for legacy STORAGE_KEY_TABS
|
|
110
|
+
const oldRaw = localStorage.getItem(STORAGE_KEY_TABS);
|
|
111
|
+
if (oldRaw) {
|
|
112
|
+
const oldTabs = JSON.parse(oldRaw);
|
|
113
|
+
if (Array.isArray(oldTabs) && oldTabs.length > 0) {
|
|
114
|
+
const wsKey = normalizeWorkspaceKey(getCurrentWorkspaceRoot());
|
|
115
|
+
workspaceTabsMap[wsKey] = {
|
|
116
|
+
tabs: oldTabs,
|
|
117
|
+
activeTabId: oldTabs[0] ? oldTabs[0].id : null,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
101
121
|
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
function notifyStateChange() {
|
|
106
|
-
for (const fn of stateListeners) {
|
|
107
|
-
try { fn(); } catch (err) { console.error('[Preview] State notify error:', err); }
|
|
122
|
+
} catch (e) {
|
|
123
|
+
workspaceTabsMap = {};
|
|
108
124
|
}
|
|
109
125
|
}
|
|
110
126
|
|
|
111
127
|
function saveTabsToStorage() {
|
|
112
128
|
try {
|
|
113
|
-
|
|
129
|
+
const wsKey = currentWorkspaceKey || normalizeWorkspaceKey(getCurrentWorkspaceRoot());
|
|
130
|
+
if (!workspaceTabsMap || typeof workspaceTabsMap !== 'object') {
|
|
131
|
+
workspaceTabsMap = {};
|
|
132
|
+
}
|
|
133
|
+
workspaceTabsMap[wsKey] = {
|
|
134
|
+
tabs: (globalTabs || []).slice(0, 20),
|
|
135
|
+
activeTabId: globalActiveTabId || null,
|
|
136
|
+
};
|
|
137
|
+
localStorage.setItem(STORAGE_KEY_WS_TABS, JSON.stringify(workspaceTabsMap));
|
|
114
138
|
} catch (e) {}
|
|
115
139
|
}
|
|
116
140
|
|
|
141
|
+
function syncCurrentWorkspaceTabs(targetWsRoot) {
|
|
142
|
+
const newWsRoot = targetWsRoot || getCurrentWorkspaceRoot();
|
|
143
|
+
const newKey = normalizeWorkspaceKey(newWsRoot);
|
|
144
|
+
|
|
145
|
+
// Save previous workspace's tabs
|
|
146
|
+
if (currentWorkspaceKey && currentWorkspaceKey !== newKey) {
|
|
147
|
+
if (!workspaceTabsMap || typeof workspaceTabsMap !== 'object') {
|
|
148
|
+
workspaceTabsMap = {};
|
|
149
|
+
}
|
|
150
|
+
workspaceTabsMap[currentWorkspaceKey] = {
|
|
151
|
+
tabs: (globalTabs || []).slice(0, 20),
|
|
152
|
+
activeTabId: globalActiveTabId || null,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
currentWorkspaceKey = newKey;
|
|
157
|
+
|
|
158
|
+
// Load target workspace's tabs
|
|
159
|
+
const stored = workspaceTabsMap && workspaceTabsMap[newKey];
|
|
160
|
+
if (stored && Array.isArray(stored.tabs)) {
|
|
161
|
+
globalTabs = [...stored.tabs];
|
|
162
|
+
globalActiveTabId = stored.activeTabId && globalTabs.some(t => t.id === stored.activeTabId)
|
|
163
|
+
? stored.activeTabId
|
|
164
|
+
: (globalTabs[0] ? globalTabs[0].id : null);
|
|
165
|
+
} else {
|
|
166
|
+
globalTabs = [];
|
|
167
|
+
globalActiveTabId = null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
saveTabsToStorage();
|
|
171
|
+
notifyStateChange();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Initial load
|
|
175
|
+
loadAllWorkspaceTabsFromStorage();
|
|
176
|
+
|
|
177
|
+
function notifyStateChange() {
|
|
178
|
+
for (const fn of stateListeners) {
|
|
179
|
+
try { fn(); } catch (err) { console.error('[Preview] State notify error:', err); }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
117
183
|
function setPanelWidth(width) {
|
|
118
184
|
const maxW = Math.floor(window.innerWidth * 0.92);
|
|
119
185
|
globalPanelWidth = Math.max(MIN_PANEL_WIDTH, Math.min(width, maxW));
|
|
@@ -435,6 +501,13 @@
|
|
|
435
501
|
const ext = getFileExtension(fullPath);
|
|
436
502
|
const tabId = 'tab:' + fullPath.toLowerCase();
|
|
437
503
|
|
|
504
|
+
// Ensure current workspace tabs are loaded
|
|
505
|
+
const currentWs = getCurrentWorkspaceRoot();
|
|
506
|
+
const currentKey = normalizeWorkspaceKey(currentWs);
|
|
507
|
+
if (currentWorkspaceKey !== currentKey) {
|
|
508
|
+
syncCurrentWorkspaceTabs(currentWs);
|
|
509
|
+
}
|
|
510
|
+
|
|
438
511
|
const existingIndex = globalTabs.findIndex(t => t.id === tabId || t.filePath.toLowerCase() === fullPath.toLowerCase());
|
|
439
512
|
if (existingIndex >= 0) {
|
|
440
513
|
globalActiveTabId = globalTabs[existingIndex].id;
|
|
@@ -509,43 +582,56 @@
|
|
|
509
582
|
const oldNorm = oldPath.replace(/\\/g, '/').toLowerCase();
|
|
510
583
|
const newNorm = newPath.replace(/\\/g, '/');
|
|
511
584
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
585
|
+
const updateList = (list) => {
|
|
586
|
+
if (!Array.isArray(list)) return [];
|
|
587
|
+
return list.map(tab => {
|
|
588
|
+
const tabNorm = tab.filePath.replace(/\\/g, '/').toLowerCase();
|
|
589
|
+
if (!isDir) {
|
|
590
|
+
if (tabNorm === oldNorm) {
|
|
591
|
+
changed = true;
|
|
592
|
+
const newFileName = getFileName(newPath);
|
|
593
|
+
const newExt = getFileExtension(newPath);
|
|
594
|
+
const newCat = getFileTypeCategory(newPath);
|
|
595
|
+
return {
|
|
596
|
+
...tab,
|
|
597
|
+
id: 'tab:' + newPath.toLowerCase(),
|
|
598
|
+
filePath: newPath,
|
|
599
|
+
title: newFileName,
|
|
600
|
+
extension: newExt,
|
|
601
|
+
category: newCat,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
} else {
|
|
605
|
+
if (tabNorm === oldNorm || tabNorm.startsWith(oldNorm + '/')) {
|
|
606
|
+
changed = true;
|
|
607
|
+
const subPath = tab.filePath.replace(/\\/g, '/').slice(oldNorm.length);
|
|
608
|
+
const updatedFilePath = newNorm + subPath;
|
|
609
|
+
const newFileName = getFileName(updatedFilePath);
|
|
610
|
+
const newExt = getFileExtension(updatedFilePath);
|
|
611
|
+
const newCat = getFileTypeCategory(updatedFilePath);
|
|
612
|
+
return {
|
|
613
|
+
...tab,
|
|
614
|
+
id: 'tab:' + updatedFilePath.toLowerCase(),
|
|
615
|
+
filePath: updatedFilePath,
|
|
616
|
+
title: newFileName,
|
|
617
|
+
extension: newExt,
|
|
618
|
+
category: newCat,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
528
621
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
id: 'tab:' + updatedFilePath.toLowerCase(),
|
|
540
|
-
filePath: updatedFilePath,
|
|
541
|
-
title: newFileName,
|
|
542
|
-
extension: newExt,
|
|
543
|
-
category: newCat,
|
|
544
|
-
};
|
|
622
|
+
return tab;
|
|
623
|
+
});
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
globalTabs = updateList(globalTabs);
|
|
627
|
+
|
|
628
|
+
if (workspaceTabsMap && typeof workspaceTabsMap === 'object') {
|
|
629
|
+
for (const k of Object.keys(workspaceTabsMap)) {
|
|
630
|
+
if (workspaceTabsMap[k] && Array.isArray(workspaceTabsMap[k].tabs)) {
|
|
631
|
+
workspaceTabsMap[k].tabs = updateList(workspaceTabsMap[k].tabs);
|
|
545
632
|
}
|
|
546
633
|
}
|
|
547
|
-
|
|
548
|
-
});
|
|
634
|
+
}
|
|
549
635
|
|
|
550
636
|
if (changed) {
|
|
551
637
|
if (globalActiveTabId) {
|
|
@@ -567,24 +653,37 @@
|
|
|
567
653
|
if (!targetPath) return;
|
|
568
654
|
let changed = false;
|
|
569
655
|
const delNorm = targetPath.replace(/\\/g, '/').toLowerCase();
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
if (!
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
656
|
+
|
|
657
|
+
const filterList = (list) => {
|
|
658
|
+
if (!Array.isArray(list)) return [];
|
|
659
|
+
return list.filter(tab => {
|
|
660
|
+
const tabNorm = tab.filePath.replace(/\\/g, '/').toLowerCase();
|
|
661
|
+
if (!isDir) {
|
|
662
|
+
if (tabNorm === delNorm) {
|
|
663
|
+
changed = true;
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
} else {
|
|
667
|
+
if (tabNorm === delNorm || tabNorm.startsWith(delNorm + '/')) {
|
|
668
|
+
changed = true;
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
576
671
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
672
|
+
return true;
|
|
673
|
+
});
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
globalTabs = filterList(globalTabs);
|
|
677
|
+
|
|
678
|
+
if (workspaceTabsMap && typeof workspaceTabsMap === 'object') {
|
|
679
|
+
for (const k of Object.keys(workspaceTabsMap)) {
|
|
680
|
+
if (workspaceTabsMap[k] && Array.isArray(workspaceTabsMap[k].tabs)) {
|
|
681
|
+
workspaceTabsMap[k].tabs = filterList(workspaceTabsMap[k].tabs);
|
|
581
682
|
}
|
|
582
683
|
}
|
|
583
|
-
|
|
584
|
-
});
|
|
684
|
+
}
|
|
585
685
|
|
|
586
686
|
if (changed) {
|
|
587
|
-
globalTabs = nextTabs;
|
|
588
687
|
if (!globalTabs.some(t => t.id === globalActiveTabId)) {
|
|
589
688
|
globalActiveTabId = globalTabs.length > 0 ? globalTabs[0].id : null;
|
|
590
689
|
}
|
|
@@ -3158,10 +3257,12 @@
|
|
|
3158
3257
|
}, [normalizedContent, category, isHighlighted, searchQuery]);
|
|
3159
3258
|
|
|
3160
3259
|
const lineCount = highlightedLines.length;
|
|
3161
|
-
const digitCount = Math.max(
|
|
3162
|
-
const gutterWidth = Math.max(24, digitCount * 7.5 + 12);
|
|
3260
|
+
const digitCount = Math.max(1, String(lineCount).length);
|
|
3163
3261
|
|
|
3164
|
-
return h('div', {
|
|
3262
|
+
return h('div', {
|
|
3263
|
+
className: `dsh-code-editor-view ${isWordWrap ? 'word-wrap' : ''}`,
|
|
3264
|
+
style: { '--dsh-gutter-digits': digitCount }
|
|
3265
|
+
},
|
|
3165
3266
|
highlightedLines.map((lineHtml, i) => {
|
|
3166
3267
|
const lineNum = i + 1;
|
|
3167
3268
|
return h('div', {
|
|
@@ -3172,7 +3273,6 @@
|
|
|
3172
3273
|
h('span', {
|
|
3173
3274
|
id: `line-gutter-${lineNum}`,
|
|
3174
3275
|
className: 'dsh-line-gutter',
|
|
3175
|
-
style: { minWidth: `${gutterWidth}px` },
|
|
3176
3276
|
onClick: () => onLineClick && onLineClick(lineNum),
|
|
3177
3277
|
title: `第 ${lineNum} 行 (点击定位)`,
|
|
3178
3278
|
}, lineNum),
|
|
@@ -3986,7 +4086,7 @@
|
|
|
3986
4086
|
if (current && current !== lastKnownWorkspace) {
|
|
3987
4087
|
console.log('[Preview] Active workspace auto-switched:', lastKnownWorkspace, '->', current);
|
|
3988
4088
|
lastKnownWorkspace = current;
|
|
3989
|
-
|
|
4089
|
+
syncCurrentWorkspaceTabs(current);
|
|
3990
4090
|
}
|
|
3991
4091
|
};
|
|
3992
4092
|
|
|
@@ -5961,10 +6061,13 @@
|
|
|
5961
6061
|
.dsh-line-gutter {
|
|
5962
6062
|
display: inline-block;
|
|
5963
6063
|
box-sizing: border-box;
|
|
5964
|
-
|
|
6064
|
+
width: calc(var(--dsh-gutter-digits, 2) * 1ch + 8px);
|
|
6065
|
+
min-width: calc(var(--dsh-gutter-digits, 2) * 1ch + 8px);
|
|
6066
|
+
padding: 0 4px 0 2px;
|
|
5965
6067
|
text-align: right;
|
|
5966
6068
|
user-select: none;
|
|
5967
6069
|
color: var(--dsw-alias-label-tertiary, #94a3b8);
|
|
6070
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Cascadia Code", "Fira Code", monospace;
|
|
5968
6071
|
font-size: 11px;
|
|
5969
6072
|
line-height: 20px;
|
|
5970
6073
|
flex-shrink: 0;
|
|
@@ -5987,7 +6090,7 @@
|
|
|
5987
6090
|
.dsh-line-code {
|
|
5988
6091
|
flex: 1;
|
|
5989
6092
|
min-width: 0;
|
|
5990
|
-
padding: 0
|
|
6093
|
+
padding: 0 8px 0 6px;
|
|
5991
6094
|
white-space: pre;
|
|
5992
6095
|
font-family: inherit;
|
|
5993
6096
|
font-size: inherit;
|
|
@@ -6290,6 +6393,9 @@
|
|
|
6290
6393
|
window.__dsh_open_preview = openPreviewFile;
|
|
6291
6394
|
}
|
|
6292
6395
|
|
|
6396
|
+
loadAllWorkspaceTabsFromStorage();
|
|
6397
|
+
syncCurrentWorkspaceTabs(getCurrentWorkspaceRoot());
|
|
6398
|
+
|
|
6293
6399
|
injectStyles();
|
|
6294
6400
|
setupFileInterceptors(ctx);
|
|
6295
6401
|
|
package/lib/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export class PreviewService extends Service {
|
|
|
29
29
|
handler: this.handleHttpRequest.bind(this),
|
|
30
30
|
});
|
|
31
31
|
});
|
|
32
|
-
console.log('[PreviewService] Registered /api/preview route on webServer (v0.1.
|
|
32
|
+
console.log('[PreviewService] Registered /api/preview route on webServer (v0.1.16)');
|
|
33
33
|
} else {
|
|
34
34
|
console.warn('[PreviewService] webServer not available yet in ctx');
|
|
35
35
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rooode/dsh-plugin-preview",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "DeepSeek Harness Markdown 文档与工作空间文件浏览器右侧预览插件 (支持工作区文件树、文件夹创建与文件移动、JSON 交互结构树/格式化、Java/C++/Python/JS/TS/YAML/Go/Rust 多语言语法高亮与符号大纲、自动换行与多标签 FileTabs)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|