@rooode/dsh-plugin-preview 0.1.15 → 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 +164 -62
- 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
|
}
|
|
@@ -3987,7 +4086,7 @@
|
|
|
3987
4086
|
if (current && current !== lastKnownWorkspace) {
|
|
3988
4087
|
console.log('[Preview] Active workspace auto-switched:', lastKnownWorkspace, '->', current);
|
|
3989
4088
|
lastKnownWorkspace = current;
|
|
3990
|
-
|
|
4089
|
+
syncCurrentWorkspaceTabs(current);
|
|
3991
4090
|
}
|
|
3992
4091
|
};
|
|
3993
4092
|
|
|
@@ -6294,6 +6393,9 @@
|
|
|
6294
6393
|
window.__dsh_open_preview = openPreviewFile;
|
|
6295
6394
|
}
|
|
6296
6395
|
|
|
6396
|
+
loadAllWorkspaceTabsFromStorage();
|
|
6397
|
+
syncCurrentWorkspaceTabs(getCurrentWorkspaceRoot());
|
|
6398
|
+
|
|
6297
6399
|
injectStyles();
|
|
6298
6400
|
setupFileInterceptors(ctx);
|
|
6299
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",
|