@rooode/dsh-plugin-preview 0.1.13 → 0.1.14

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
@@ -252,12 +252,37 @@
252
252
 
253
253
  function getAllAvailableWorkspaces() {
254
254
  const map = new Map();
255
- const currentRoot = getCurrentWorkspaceRoot().replace(/[\\/]+$/, '').toLowerCase();
255
+ const currentRoot = (getCurrentWorkspaceRoot() || '').replace(/[\\/]+$/, '').toLowerCase();
256
256
 
257
- // 1. Collect from active & historical sessions in clientCtx.sessions
257
+ // 1. Collect from workspaces store in clientCtx.workspaces
258
+ if (clientCtx && clientCtx.workspaces) {
259
+ try {
260
+ const wSnap = clientCtx.workspaces.list?.getSnapshot?.();
261
+ const items = (wSnap && (wSnap.items || wSnap.workspaces || wSnap.byId)) || [];
262
+ const list = Array.isArray(items) ? items : Object.values(items);
263
+ for (const w of list) {
264
+ const dir = (w && (w.path || w.directory || w.cwd)) || '';
265
+ if (dir && typeof dir === 'string' && dir.trim()) {
266
+ const norm = dir.trim().replace(/[\\/]+$/, '');
267
+ const key = norm.toLowerCase();
268
+ if (!map.has(key)) {
269
+ map.set(key, {
270
+ id: w.workspaceId || w.id || norm,
271
+ path: norm,
272
+ name: w.title || w.name || getFileName(norm) || norm,
273
+ isCurrent: key === currentRoot,
274
+ source: 'workspace',
275
+ });
276
+ }
277
+ }
278
+ }
279
+ } catch (e) {}
280
+ }
281
+
282
+ // 2. Collect from active & historical sessions in clientCtx.sessions
258
283
  if (clientCtx && clientCtx.sessions) {
259
284
  try {
260
- const sSnap = clientCtx.sessions.list?.getSnapshot();
285
+ const sSnap = clientCtx.sessions.list?.getSnapshot?.();
261
286
  if (sSnap && sSnap.byId) {
262
287
  for (const sid of Object.keys(sSnap.byId)) {
263
288
  const s = sSnap.byId[sid];
@@ -279,33 +304,6 @@
279
304
  } catch (e) {}
280
305
  }
281
306
 
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
- }
305
- }
306
- } catch (e) {}
307
- }
308
-
309
307
  // 3. Collect from globalManualWorkspace if set
310
308
  if (globalManualWorkspace && typeof globalManualWorkspace === 'string' && globalManualWorkspace.trim()) {
311
309
  const norm = globalManualWorkspace.trim().replace(/[\\/]+$/, '');
@@ -331,51 +329,62 @@
331
329
  }
332
330
 
333
331
  function getCurrentWorkspaceRoot() {
334
- // Priority 1: User manual override selection
332
+ // Priority 1: User manual override selection (if explicitly chosen by user)
335
333
  if (globalManualWorkspace && typeof globalManualWorkspace === 'string' && globalManualWorkspace.trim()) {
336
334
  return globalManualWorkspace.trim();
337
335
  }
338
336
 
339
- // Priority 2: Active session working directory (cwd)
340
- if (clientCtx && clientCtx.sessions) {
337
+ // Priority 2: Derive active workspace from clientCtx (workspaces + sessions)
338
+ if (clientCtx) {
341
339
  try {
342
- const sSnap = clientCtx.sessions.list?.getSnapshot();
340
+ const sSnap = clientCtx.sessions?.list?.getSnapshot?.();
341
+ const wSnap = clientCtx.workspaces?.list?.getSnapshot?.();
343
342
  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();
343
+
344
+ // A. Find workspace in wSnap.items containing currentSessionId
345
+ if (currentSessionId && wSnap && Array.isArray(wSnap.items) && wSnap.items.length > 0) {
346
+ const match = wSnap.items.find(item => item.sessionIds && Array.isArray(item.sessionIds) && item.sessionIds.includes(currentSessionId));
347
+ if (match && match.path && typeof match.path === 'string' && match.path.trim()) {
348
+ return match.path.trim();
348
349
  }
349
350
  }
350
- } catch (e) {}
351
- }
352
351
 
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();
352
+ // B. Check active session summary cwd or workspaceId
353
+ if (currentSessionId && sSnap && sSnap.byId && sSnap.byId[currentSessionId]) {
354
+ const sess = sSnap.byId[currentSessionId];
355
+ if (sess.cwd && typeof sess.cwd === 'string' && sess.cwd.trim()) {
356
+ return sess.cwd.trim();
363
357
  }
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();
358
+ if (sess.workspaceId && wSnap && Array.isArray(wSnap.items)) {
359
+ const match = wSnap.items.find(item => item.workspaceId === sess.workspaceId);
360
+ if (match && match.path && typeof match.path === 'string' && match.path.trim()) {
361
+ return match.path.trim();
362
+ }
368
363
  }
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();
364
+ }
365
+
366
+ // C. Check recentWorkspaceId in wSnap
367
+ if (wSnap && wSnap.recentWorkspaceId && Array.isArray(wSnap.items)) {
368
+ const recent = wSnap.items.find(item => item.workspaceId === wSnap.recentWorkspaceId);
369
+ if (recent && recent.path && typeof recent.path === 'string' && recent.path.trim()) {
370
+ return recent.path.trim();
373
371
  }
374
372
  }
375
- } catch (e) {}
373
+
374
+ // D. Fallback to first workspace in wSnap.items
375
+ if (wSnap && Array.isArray(wSnap.items) && wSnap.items.length > 0) {
376
+ const first = wSnap.items[0];
377
+ const dir = first.path || first.directory || first.cwd;
378
+ if (dir && typeof dir === 'string' && dir.trim()) {
379
+ return dir.trim();
380
+ }
381
+ }
382
+ } catch (e) {
383
+ console.warn('[Preview] getCurrentWorkspaceRoot failed:', e);
384
+ }
376
385
  }
377
386
 
378
- // Priority 4: URL hash or search parameter
387
+ // Priority 3: URL hash or search parameter
379
388
  if (typeof location !== 'undefined') {
380
389
  if (location.hash) {
381
390
  const m = location.hash.match(/workspace=([^&]+)/);
@@ -1629,13 +1638,28 @@
1629
1638
  const [dragOverNode, setDragOverNode] = useState(null);
1630
1639
  const [clipboard, setClipboard] = useState(null); // { action: 'cut', path: string, name: string, isDir: boolean } | null
1631
1640
 
1632
- const currentWs = getCurrentWorkspaceRoot();
1641
+ const [currentWs, setCurrentWs] = useState(getCurrentWorkspaceRoot());
1633
1642
 
1634
- const loadTree = useCallback(async (force = false) => {
1643
+ useEffect(() => {
1644
+ const handleStateUpdate = () => {
1645
+ const newWs = getCurrentWorkspaceRoot();
1646
+ if (newWs !== currentWs) {
1647
+ setCurrentWs(newWs);
1648
+ }
1649
+ };
1650
+ stateListeners.add(handleStateUpdate);
1651
+ return () => stateListeners.delete(handleStateUpdate);
1652
+ }, [currentWs]);
1653
+
1654
+ const loadTree = useCallback(async (force = false, targetWs) => {
1635
1655
  setIsLoading(true);
1636
1656
  setError(null);
1637
1657
  try {
1638
- const rootPath = getCurrentWorkspaceRoot();
1658
+ const rootPath = targetWs || currentWs || getCurrentWorkspaceRoot();
1659
+ if (!rootPath) {
1660
+ setTreeData(null);
1661
+ return;
1662
+ }
1639
1663
  const query = encodeURIComponent(rootPath);
1640
1664
  const res = await fetch(`/api/preview/workspace-tree?path=${query}`);
1641
1665
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
@@ -1652,11 +1676,11 @@
1652
1676
  } finally {
1653
1677
  setIsLoading(false);
1654
1678
  }
1655
- }, []);
1679
+ }, [currentWs]);
1656
1680
 
1657
1681
  useEffect(() => {
1658
- loadTree();
1659
- }, [loadTree, globalManualWorkspace]);
1682
+ loadTree(false, currentWs);
1683
+ }, [loadTree, currentWs, globalManualWorkspace]);
1660
1684
 
1661
1685
  const handleToggleExpand = (dirPath) => {
1662
1686
  setExpandedPaths(prev => {
@@ -3951,6 +3975,37 @@
3951
3975
  }
3952
3976
  }, true); // Use capture phase to intercept early!
3953
3977
  }
3978
+
3979
+ // 3. Listen to DSH Workspaces and Sessions state changes for auto-switching
3980
+ let lastKnownWorkspace = getCurrentWorkspaceRoot();
3981
+
3982
+ const handleWorkspaceSync = () => {
3983
+ // Clear manual override when user switches workspace/session in host DSH
3984
+ globalManualWorkspace = null;
3985
+ const current = getCurrentWorkspaceRoot();
3986
+ if (current && current !== lastKnownWorkspace) {
3987
+ console.log('[Preview] Active workspace auto-switched:', lastKnownWorkspace, '->', current);
3988
+ lastKnownWorkspace = current;
3989
+ notifyStateChange();
3990
+ }
3991
+ };
3992
+
3993
+ if (ctx && ctx.workspaces && ctx.workspaces.list && typeof ctx.workspaces.list.subscribe === 'function') {
3994
+ try {
3995
+ ctx.workspaces.list.subscribe(handleWorkspaceSync);
3996
+ } catch (e) {}
3997
+ }
3998
+
3999
+ if (ctx && ctx.sessions && ctx.sessions.list && typeof ctx.sessions.list.subscribe === 'function') {
4000
+ try {
4001
+ ctx.sessions.list.subscribe(handleWorkspaceSync);
4002
+ } catch (e) {}
4003
+ }
4004
+
4005
+ if (typeof window !== 'undefined') {
4006
+ window.addEventListener('hashchange', handleWorkspaceSync);
4007
+ window.addEventListener('popstate', handleWorkspaceSync);
4008
+ }
3954
4009
  }
3955
4010
 
3956
4011
  // =========================================================================
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.13)');
32
+ console.log('[PreviewService] Registered /api/preview route on webServer (v0.1.14)');
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.13",
3
+ "version": "0.1.14",
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",