@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 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
- try {
96
- const savedTabs = localStorage.getItem(STORAGE_KEY_TABS);
97
- if (savedTabs) {
98
- globalTabs = JSON.parse(savedTabs);
99
- if (globalTabs.length > 0) {
100
- globalActiveTabId = globalTabs[0].id;
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
- } catch (e) {}
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
- localStorage.setItem(STORAGE_KEY_TABS, JSON.stringify(globalTabs.slice(0, 15)));
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
- globalTabs = globalTabs.map(tab => {
513
- const tabNorm = tab.filePath.replace(/\\/g, '/').toLowerCase();
514
- if (!isDir) {
515
- if (tabNorm === oldNorm) {
516
- changed = true;
517
- const newFileName = getFileName(newPath);
518
- const newExt = getFileExtension(newPath);
519
- const newCat = getFileTypeCategory(newPath);
520
- return {
521
- ...tab,
522
- id: 'tab:' + newPath.toLowerCase(),
523
- filePath: newPath,
524
- title: newFileName,
525
- extension: newExt,
526
- category: newCat,
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
- } else {
530
- if (tabNorm === oldNorm || tabNorm.startsWith(oldNorm + '/')) {
531
- changed = true;
532
- const subPath = tab.filePath.replace(/\\/g, '/').slice(oldNorm.length);
533
- const updatedFilePath = newNorm + subPath;
534
- const newFileName = getFileName(updatedFilePath);
535
- const newExt = getFileExtension(updatedFilePath);
536
- const newCat = getFileTypeCategory(updatedFilePath);
537
- return {
538
- ...tab,
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
- return tab;
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
- const nextTabs = globalTabs.filter(tab => {
571
- const tabNorm = tab.filePath.replace(/\\/g, '/').toLowerCase();
572
- if (!isDir) {
573
- if (tabNorm === delNorm) {
574
- changed = true;
575
- return false;
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
- } else {
578
- if (tabNorm === delNorm || tabNorm.startsWith(delNorm + '/')) {
579
- changed = true;
580
- return false;
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
- return true;
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
- notifyStateChange();
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.15)');
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.15",
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",