@rooode/dsh-plugin-preview 0.1.17 → 0.1.19

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
@@ -27,10 +27,10 @@
27
27
  const STORAGE_KEY_TREE_OPEN = 'dsh:preview:tree:open:v1';
28
28
  const STORAGE_KEY_TREE_WIDTH = 'dsh:preview:tree:width:v1';
29
29
  const STORAGE_KEY_MANUAL_WS = 'dsh:preview:manual_workspace:v1';
30
- const DEFAULT_PANEL_WIDTH = 620;
30
+ const DEFAULT_PANEL_WIDTH = 700;
31
31
  const MIN_PANEL_WIDTH = 380;
32
- const DEFAULT_TREE_WIDTH = 240;
33
- const MIN_TREE_WIDTH = 180;
32
+ const DEFAULT_TREE_WIDTH = 220;
33
+ const MIN_TREE_WIDTH = 170;
34
34
  const MAX_TREE_WIDTH = 450;
35
35
  const SUPPORTED_EXTENSIONS = new Set([
36
36
  '.md', '.markdown', '.mdown', '.mkdn', '.mdwn',
@@ -394,75 +394,108 @@
394
394
  return result;
395
395
  }
396
396
 
397
- function getCurrentWorkspaceRoot() {
398
- // Priority 1: User manual override selection (if explicitly chosen by user)
397
+ function resolveActiveWorkspace(ctx) {
399
398
  if (globalManualWorkspace && typeof globalManualWorkspace === 'string' && globalManualWorkspace.trim()) {
400
- return globalManualWorkspace.trim();
399
+ const p = globalManualWorkspace.trim().replace(/[\\/]+$/, '');
400
+ return { path: p, id: 'manual', title: getFileName(p) || p };
401
401
  }
402
-
403
- // Priority 2: Derive active workspace from clientCtx (workspaces + sessions)
404
- if (clientCtx) {
402
+ if (ctx) {
405
403
  try {
406
- const sSnap = clientCtx.sessions?.list?.getSnapshot?.();
407
- const wSnap = clientCtx.workspaces?.list?.getSnapshot?.();
408
- const currentSessionId = sSnap?.current;
409
-
410
- // A. Find workspace in wSnap.items containing currentSessionId
411
- if (currentSessionId && wSnap && Array.isArray(wSnap.items) && wSnap.items.length > 0) {
412
- const match = wSnap.items.find(item => item.sessionIds && Array.isArray(item.sessionIds) && item.sessionIds.includes(currentSessionId));
413
- if (match && match.path && typeof match.path === 'string' && match.path.trim()) {
414
- return match.path.trim();
404
+ const sSnap = ctx.sessions?.list?.getSnapshot?.();
405
+ const wSnap = ctx.workspaces?.list?.getSnapshot?.();
406
+ const curSessionId = sSnap?.current;
407
+
408
+ // 1. Session cwd or workspaceId
409
+ if (curSessionId && sSnap?.byId?.[curSessionId]) {
410
+ const sess = sSnap.byId[curSessionId];
411
+ let wsPath = sess.cwd;
412
+ let wsId = sess.workspaceId;
413
+ let wsTitle = sess.title;
414
+
415
+ if (sess.workspaceId && wSnap?.items) {
416
+ const match = wSnap.items.find(item => item.workspaceId === sess.workspaceId);
417
+ if (match) {
418
+ if (match.path) wsPath = match.path;
419
+ if (match.title) wsTitle = match.title;
420
+ wsId = match.workspaceId;
421
+ }
422
+ }
423
+ if (wsPath && typeof wsPath === 'string' && wsPath.trim()) {
424
+ const clean = wsPath.trim().replace(/[\\/]+$/, '');
425
+ return {
426
+ path: clean,
427
+ id: wsId || clean,
428
+ title: wsTitle || getFileName(clean) || '工作区',
429
+ };
415
430
  }
416
431
  }
417
432
 
418
- // B. Check active session summary cwd or workspaceId
419
- if (currentSessionId && sSnap && sSnap.byId && sSnap.byId[currentSessionId]) {
420
- const sess = sSnap.byId[currentSessionId];
421
- if (sess.cwd && typeof sess.cwd === 'string' && sess.cwd.trim()) {
422
- return sess.cwd.trim();
423
- }
424
- if (sess.workspaceId && wSnap && Array.isArray(wSnap.items)) {
425
- const match = wSnap.items.find(item => item.workspaceId === sess.workspaceId);
426
- if (match && match.path && typeof match.path === 'string' && match.path.trim()) {
427
- return match.path.trim();
428
- }
433
+ // 2. Find in wSnap.items by sessionIds
434
+ if (curSessionId && wSnap?.items) {
435
+ const match = wSnap.items.find(item => Array.isArray(item.sessionIds) && item.sessionIds.includes(curSessionId));
436
+ if (match?.path) {
437
+ const clean = match.path.trim().replace(/[\\/]+$/, '');
438
+ return {
439
+ path: clean,
440
+ id: match.workspaceId || clean,
441
+ title: match.title || getFileName(clean) || '工作区',
442
+ };
429
443
  }
430
444
  }
431
445
 
432
- // C. Check recentWorkspaceId in wSnap
433
- if (wSnap && wSnap.recentWorkspaceId && Array.isArray(wSnap.items)) {
446
+ // 3. Recent workspace
447
+ if (wSnap?.recentWorkspaceId && wSnap?.items) {
434
448
  const recent = wSnap.items.find(item => item.workspaceId === wSnap.recentWorkspaceId);
435
- if (recent && recent.path && typeof recent.path === 'string' && recent.path.trim()) {
436
- return recent.path.trim();
449
+ if (recent?.path) {
450
+ const clean = recent.path.trim().replace(/[\\/]+$/, '');
451
+ return {
452
+ path: clean,
453
+ id: recent.workspaceId || clean,
454
+ title: recent.title || getFileName(clean) || '工作区',
455
+ };
437
456
  }
438
457
  }
439
458
 
440
- // D. Fallback to first workspace in wSnap.items
441
- if (wSnap && Array.isArray(wSnap.items) && wSnap.items.length > 0) {
459
+ // 4. First workspace item
460
+ if (wSnap?.items && wSnap.items.length > 0) {
442
461
  const first = wSnap.items[0];
443
- const dir = first.path || first.directory || first.cwd;
444
- if (dir && typeof dir === 'string' && dir.trim()) {
445
- return dir.trim();
462
+ const p = first.path || first.directory || first.cwd;
463
+ if (p && typeof p === 'string' && p.trim()) {
464
+ const clean = p.trim().replace(/[\\/]+$/, '');
465
+ return {
466
+ path: clean,
467
+ id: first.workspaceId || clean,
468
+ title: first.title || getFileName(clean) || '工作区',
469
+ };
446
470
  }
447
471
  }
448
472
  } catch (e) {
449
- console.warn('[Preview] getCurrentWorkspaceRoot failed:', e);
473
+ console.warn('[Preview] resolveActiveWorkspace error:', e);
450
474
  }
451
475
  }
452
476
 
453
- // Priority 3: URL hash or search parameter
454
477
  if (typeof location !== 'undefined') {
455
478
  if (location.hash) {
456
479
  const m = location.hash.match(/workspace=([^&]+)/);
457
- if (m) return decodeURIComponent(m[1]);
480
+ if (m) {
481
+ const p = decodeURIComponent(m[1]).replace(/[\\/]+$/, '');
482
+ return { path: p, id: p, title: getFileName(p) || p };
483
+ }
458
484
  }
459
485
  if (location.search) {
460
486
  const m = location.search.match(/workspace=([^&]+)/);
461
- if (m) return decodeURIComponent(m[1]);
487
+ if (m) {
488
+ const p = decodeURIComponent(m[1]).replace(/[\\/]+$/, '');
489
+ return { path: p, id: p, title: getFileName(p) || p };
490
+ }
462
491
  }
463
492
  }
464
493
 
465
- return '';
494
+ return { path: '.', id: 'default', title: '工作区' };
495
+ }
496
+
497
+ function getCurrentWorkspaceRoot() {
498
+ return resolveActiveWorkspace(clientCtx).path || '.';
466
499
  }
467
500
 
468
501
  function resolveAbsoluteFilePath(targetPath) {
@@ -1813,11 +1846,11 @@
1813
1846
  }
1814
1847
  }, [searchQuery, treeData]);
1815
1848
 
1816
- // Listen to external workspace switch events (e.g. from Git plugin)
1849
+ // Listen to external workspace switch events (e.g. from DSH sidebar or Git plugin)
1817
1850
  useEffect(() => {
1818
1851
  const handleWsSwitch = (e) => {
1819
- if (e.detail?.path && e.detail.path !== globalManualWorkspace) {
1820
- setManualWorkspace(e.detail.path);
1852
+ if (e.detail?.path) {
1853
+ setCurrentWs(e.detail.path);
1821
1854
  }
1822
1855
  };
1823
1856
  window.addEventListener('dsh:workspace:switch', handleWsSwitch);
@@ -3404,13 +3437,6 @@
3404
3437
  h('div', { className: 'dsh-preview-toolbar' },
3405
3438
  // Left: View mode toggles
3406
3439
  h('div', { className: 'dsh-preview-toolbar-left' },
3407
- h('button', {
3408
- type: 'button',
3409
- className: `dsh-preview-tool-btn ${isTreeOpen ? 'active' : ''}`,
3410
- onClick: onToggleTree,
3411
- title: isTreeOpen ? '隐藏工作区目录树 (Ctrl/Cmd+B)' : '展开工作区目录树 (Ctrl/Cmd+B)',
3412
- }, '🗂️ 目录树'),
3413
-
3414
3440
  // Adaptive View Mode buttons based on File Category
3415
3441
  category === 'markdown' ? (
3416
3442
  h('div', { className: 'dsh-preview-mode-group' },
@@ -4304,19 +4330,28 @@
4304
4330
  }
4305
4331
 
4306
4332
  /* Side-by-Side Right Sidebar Layout Integration */
4307
- body.dsh-preview-sidebar-active div[class*="centerCol"] {
4308
- margin-right: var(--dsh-preview-width, 620px) !important;
4309
- max-width: calc(100% - var(--dsh-preview-width, 620px)) !important;
4333
+ body.dsh-preview-sidebar-active div[class*="frame"] {
4334
+ width: calc(100% - var(--dsh-preview-width, 700px)) !important;
4335
+ max-width: calc(100% - var(--dsh-preview-width, 700px)) !important;
4310
4336
  box-sizing: border-box !important;
4311
- 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;
4337
+ transition: width 0.18s cubic-bezier(0.16, 1, 0.3, 1), max-width 0.18s cubic-bezier(0.16, 1, 0.3, 1) !important;
4312
4338
  }
4313
- body.dsh-preview-sidebar-active.dsh-preview-maximized div[class*="centerCol"] {
4339
+ body.dsh-preview-sidebar-active.dsh-preview-maximized div[class*="frame"] {
4314
4340
  display: none !important;
4315
4341
  }
4316
4342
  body.dsh-preview-sidebar-active.dsh-preview-maximized .dsh-preview-drawer-container {
4317
4343
  width: 100vw !important;
4318
4344
  }
4319
4345
 
4346
+ /* Shift Right Activity Rail to dock on the preview sidebar left border when open */
4347
+ body.dsh-preview-sidebar-active .dsh-right-activity-rail {
4348
+ right: var(--dsh-preview-width, 700px) !important;
4349
+ transition: right 0.18s cubic-bezier(0.16, 1, 0.3, 1) !important;
4350
+ }
4351
+ body.dsh-preview-sidebar-active.dsh-preview-maximized .dsh-right-activity-rail {
4352
+ display: none !important;
4353
+ }
4354
+
4320
4355
  /* Workspace Switcher Header & Popover */
4321
4356
  .dsh-tree-sidebar-title.dsh-tree-title-clickable {
4322
4357
  cursor: pointer;
@@ -4713,15 +4748,18 @@
4713
4748
  display: flex;
4714
4749
  align-items: center;
4715
4750
  justify-content: space-between;
4716
- padding: 6px 12px;
4751
+ padding: 4px 8px;
4717
4752
  height: 38px;
4718
4753
  min-height: 38px;
4719
4754
  flex-shrink: 0;
4720
4755
  background: var(--dsw-alias-bg-layer-2, #f8fafc);
4721
4756
  border-bottom: 1px solid var(--dsw-alias-border-l2, #e2e8f0);
4722
- gap: 10px;
4757
+ gap: 6px;
4723
4758
  box-sizing: border-box;
4759
+ overflow-x: auto;
4760
+ scrollbar-width: none;
4724
4761
  }
4762
+ .dsh-preview-toolbar::-webkit-scrollbar { display: none; }
4725
4763
  body[data-ds-dark-theme] .dsh-preview-toolbar {
4726
4764
  background: var(--dsw-alias-bg-layer-2, #161a24);
4727
4765
  border-bottom-color: var(--dsw-alias-border-l2, #252d3d);
@@ -4730,7 +4768,7 @@
4730
4768
  .dsh-preview-toolbar-right {
4731
4769
  display: flex;
4732
4770
  align-items: center;
4733
- gap: 6px;
4771
+ gap: 4px;
4734
4772
  flex-shrink: 0;
4735
4773
  white-space: nowrap;
4736
4774
  }
@@ -4752,7 +4790,7 @@
4752
4790
  border: none;
4753
4791
  color: var(--dsw-alias-label-secondary, #64748b);
4754
4792
  font-size: 11px;
4755
- padding: 4px 9px;
4793
+ padding: 3px 7px;
4756
4794
  border-radius: 4px;
4757
4795
  cursor: pointer;
4758
4796
  transition: all 0.15s;
@@ -4778,13 +4816,13 @@
4778
4816
  border: 1px solid var(--dsw-alias-border-l2, #cbd5e1);
4779
4817
  color: var(--dsw-alias-label-primary, #334155);
4780
4818
  font-size: 11px;
4781
- padding: 4px 9px;
4782
- border-radius: 5px;
4819
+ padding: 3px 7px;
4820
+ border-radius: 4px;
4783
4821
  cursor: pointer;
4784
4822
  transition: all 0.15s;
4785
4823
  display: flex;
4786
4824
  align-items: center;
4787
- gap: 4px;
4825
+ gap: 3px;
4788
4826
  white-space: nowrap;
4789
4827
  flex-shrink: 0;
4790
4828
  }
@@ -6399,6 +6437,59 @@
6399
6437
  injectStyles();
6400
6438
  setupFileInterceptors(ctx);
6401
6439
 
6440
+ // Subscribe to DSH Session & Workspace store changes (Left Sidebar updates)
6441
+ let lastTrackedSessionId = null;
6442
+ let lastTrackedWsPath = getCurrentWorkspaceRoot();
6443
+
6444
+ function handleDshSessionOrWorkspaceChange() {
6445
+ try {
6446
+ const sSnap = ctx.sessions?.list?.getSnapshot?.();
6447
+ const curSessionId = sSnap?.current;
6448
+
6449
+ // When user clicks a session on the left sidebar:
6450
+ if (curSessionId && curSessionId !== lastTrackedSessionId) {
6451
+ lastTrackedSessionId = curSessionId;
6452
+ // Clear manual override when user actively selects a session in the left sidebar
6453
+ if (globalManualWorkspace) {
6454
+ globalManualWorkspace = null;
6455
+ try { localStorage.removeItem(STORAGE_KEY_MANUAL_WS); } catch (e) {}
6456
+ }
6457
+ }
6458
+
6459
+ const activeWsInfo = resolveActiveWorkspace(ctx);
6460
+ const activeWsPath = activeWsInfo.path;
6461
+
6462
+ if (activeWsPath && activeWsPath !== lastTrackedWsPath) {
6463
+ lastTrackedWsPath = activeWsPath;
6464
+ syncCurrentWorkspaceTabs(activeWsPath);
6465
+ notifyStateChange();
6466
+ if (typeof window !== 'undefined') {
6467
+ window.dispatchEvent(new CustomEvent('dsh:workspace:switch', {
6468
+ detail: { path: activeWsPath, id: activeWsInfo.id, title: activeWsInfo.title }
6469
+ }));
6470
+ }
6471
+ }
6472
+ } catch (e) {
6473
+ console.warn('[Preview] Session/Workspace change handler error:', e);
6474
+ }
6475
+ }
6476
+
6477
+ if (ctx.sessions?.list?.subscribe) {
6478
+ ctx.sessions.list.subscribe(handleDshSessionOrWorkspaceChange);
6479
+ }
6480
+ if (ctx.workspaces?.list?.subscribe) {
6481
+ ctx.workspaces.list.subscribe(handleDshSessionOrWorkspaceChange);
6482
+ }
6483
+
6484
+ // Safety polling every 800ms for route or state changes
6485
+ setInterval(handleDshSessionOrWorkspaceChange, 800);
6486
+
6487
+ // Listen to popstate / hashchange
6488
+ if (typeof window !== 'undefined') {
6489
+ window.addEventListener('popstate', handleDshSessionOrWorkspaceChange);
6490
+ window.addEventListener('hashchange', handleDshSessionOrWorkspaceChange);
6491
+ }
6492
+
6402
6493
  console.log('[Preview] Markdown Preview Client Plugin loaded successfully!');
6403
6494
 
6404
6495
  // 1. Mount Unified Right Activity Rail Host into shell.overlay
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.17)');
32
+ console.log('[PreviewService] Registered /api/preview route on webServer (v0.1.19)');
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.17",
3
+ "version": "0.1.19",
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",