dsh-vscode-mode 0.14.4 → 0.14.6

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.
@@ -18,6 +18,9 @@ import { clampZoom, clearZoomMem, dataUrlOf, isImagePath, isSvgPath, recallZoom,
18
18
  import { isMarkdownPath } from '../markdownPreview.js'
19
19
  import { setActiveEditorPath } from '../activePathStore.js'
20
20
  import { MarkdownPanel } from '../md/mdPanel.js'
21
+ import { isPlanPath, planDocument } from '../planState.js'
22
+ import { MarkdownText, MarkdownDelegateProvider } from '@deepseek-ai/dsh-client-ui-primitives'
23
+ import { isComponentType } from '../md/componentType.js'
21
24
  import { base64ToBytes, bytesToBase64, isPdfPath } from '../pdfPreview.js'
22
25
  import { inNativePath, tryNativeOpen } from '../nativeOpenStore.js'
23
26
  import { clearPendingNav, putPendingNav, readPendingNav } from '../pendingNav.js'
@@ -50,7 +53,7 @@ import { displayDiffTotal, editorDockMode } from '../diffDock.js'
50
53
  import { editorHeight } from '../editorLayout.js'
51
54
  import { revealInExplorer as revealPathInExplorer } from '../fileReveal.js'
52
55
  import { setSidePending, SIDEBAR_INSTALL_CMD } from '../sidebarBridge.js'
53
- import { upsertViewState, viewStatesLoad, viewStatesSave } from '../state/viewStateCache.js'
56
+ import { createViewKeeper } from '../state/editorViewState.js'
54
57
  import { mdPreviewLoad, mdPreviewSave } from '../state/mdPreviewCache.js'
55
58
  import { onUiStateReady, reportUiStateEvent, uiStateSettled } from '../state/uiStatePersist.js'
56
59
  import { migrateScopedKeys, workspaceScopeOf } from '../state/scopeStore.js'
@@ -126,6 +129,8 @@ function syncTypeSuggest(ed, monaco) {
126
129
 
127
130
  /** 日志弹窗每次加载条数(「加载更多」按此步长递增;上限由 host 的 SVN_LOG_SHOW_ALL_CAP 约束)。 */
128
131
  const SVN_LOG_PAGE = 100
132
+ /** Stable Markdown chrome for virtual plans; content stays in session-local memory. */
133
+ const PLAN_MD_LABELS = { code: { copyLabel: '复制', copiedLabel: '已复制' }, footnotes: '脚注' }
129
134
 
130
135
  /**
131
136
  * 日志弹窗状态 → 取数选项(P1-4/P1-5/P1-6:soc/mrg/range 与状态层键保持同源)。
@@ -238,7 +243,15 @@ export function EditorView(props) {
238
243
  const [monacoErr, setMonacoErr] = React.useState(null)
239
244
  const [records, setRecords] = React.useState({})
240
245
  const [tabs, setTabs] = React.useState([])
241
- const [active, setActive] = React.useState(null)
246
+ const [active, setDiskActive] = React.useState(null)
247
+ const [planTabs, setPlanTabs] = React.useState([])
248
+ const [planActive, setPlanActive] = React.useState(null)
249
+ const planActiveRef = React.useRef(null)
250
+ planActiveRef.current = planActive
251
+ const planHostRef = React.useRef(null)
252
+ React.useEffect(() => { setPlanTabs([]); setPlanActive(null) }, [sessionId, cwd])
253
+ /** @private @author ddj 2026年10月08号 @param path Disk tab selection. Leave read-only plan mode before any disk navigation. */
254
+ function setActive(path) { setPlanActive(null); setDiskActive(path) }
242
255
  const [dirtyMap, setDirtyMap] = React.useState({})
243
256
  const [content, setContent] = React.useState(null)
244
257
  const [contentPath, setContentPath] = React.useState(null)
@@ -320,8 +333,8 @@ export function EditorView(props) {
320
333
  const modelsRef = React.useRef(null)
321
334
  // model 跨挂载缓存(按工作区作用域):重挂载秒显内容,消除切换对话闪烁
322
335
  if (!modelsRef.current) modelsRef.current = modelsForScope(scope)
323
- // 视图状态缓存(path → Monaco viewState;重启后恢复光标/滚动/折叠位置)
324
- const viewStatesRef = React.useRef({})
336
+ // 视图/查找生命周期跟随实际绑定的 model,不能从新一帧 active/scope 猜旧模型归属。
337
+ const viewKeeperRef = React.useRef(null)
325
338
  // 防抖保存槽(arm/flush/cancel;见 saveDebounce.ts 的缺陷说明,勿退回单一取消句柄)
326
339
  const saveTimerRef = React.useRef(createSaveTimer())
327
340
  const savingRef = React.useRef(new Set()) // 在途保存的路径(关闭前落盘去重,防同内容双发)
@@ -512,9 +525,13 @@ export function EditorView(props) {
512
525
  lineRegionMapRef.current = lineRegionMap
513
526
  const sum = React.useMemo(() => summarize(Object.values(records)), [records])
514
527
 
515
- const addTab = (path, select) => {
528
+ /** @private @author ddj 2026年10月08号 @param path Disk path. @param select Select tab. @param preview Optional explicit Markdown mode. */
529
+ function addTab(path, select, preview) {
516
530
  setTabs((prev) => insertTab(prev, path))
517
- if (select) setActive(path)
531
+ if (select) {
532
+ if (typeof preview === 'boolean' || isPlanPath(path, cwd, sessionId)) setMdPreview(path, typeof preview === 'boolean' ? preview : true)
533
+ setActive(path)
534
+ }
518
535
  }
519
536
 
520
537
  /**
@@ -536,47 +553,24 @@ export function EditorView(props) {
536
553
  * @param select 是否设为活动页签
537
554
  * @returns 归一后的路径(空值返回 null)
538
555
  */
539
- const addTabNorm = (path, select) => {
556
+ const addTabNorm = (path, select, preview) => {
540
557
  if (!path) return null
541
558
  const normalized = tabPath(path)
542
559
  if (!normalized) return null
543
- addTab(normalized, select)
560
+ addTab(normalized, select, preview)
544
561
  return normalized
545
562
  }
546
563
 
547
- /**
548
- * 保存当前活动文件的视图状态(光标/滚动/折叠)到工作区作用域缓存。
549
- * @author ddj 2026年08月28号
550
- * @param path 要保存的文件路径(缺省 = 当前 active)
551
- */
552
- const saveViewState = (path) => {
553
- const ed = editorRef.current
554
- const target = path ?? active
555
- if (!ed || !target) return
556
- try {
557
- const state = ed.saveViewState()
558
- if (!state) return
559
- viewStatesRef.current = upsertViewState(viewStatesRef.current, target, state)
560
- viewStatesSave(scope, viewStatesRef.current)
561
- } catch (e) { /* 视图状态保存失败忽略 */ }
562
- }
564
+ /** @private @author ddj 2026年10月09号 @param path 可选预期文件路径。按已绑定模型归属保存,避免新 active 覆盖旧文件状态。 */
565
+ const saveViewState = (path) => viewKeeperRef.current?.save(path)
563
566
  saveViewStateRef.current = saveViewState
564
567
 
565
- /**
566
- * 恢复指定文件的视图状态(内容就绪、setModel 后调用;恢复后即消费删除)。
567
- * @author ddj 2026年08月28号
568
- * @param path 文件路径
569
- */
570
- const restoreViewState = (path) => {
571
- const ed = editorRef.current
572
- if (!ed || !path) return
573
- const saved = viewStatesRef.current[path]
574
- if (!saved) return
575
- const next = Object.assign({}, viewStatesRef.current)
576
- delete next[path]
577
- viewStatesRef.current = next
578
- try { ed.restoreViewState(saved) } catch (e) { /* 恢复失败忽略(行号越界自动兜底) */ }
579
- }
568
+ /** @private @author ddj 2026年10月09号 ref 脱离与 effect 卸载共用:保存视图/查找后销毁,重复调用无副作用。 */
569
+ const disposeEditor = React.useCallback(() => {
570
+ viewKeeperRef.current?.dispose()
571
+ viewKeeperRef.current = null
572
+ editorRef.current = null
573
+ }, [])
580
574
 
581
575
  /**
582
576
  * 采集当前焦点条目:路径从编辑器 model 实时读取(避免空依赖闭包读到过期 active),
@@ -1238,28 +1232,32 @@ export function EditorView(props) {
1238
1232
  return () => window.removeEventListener('edrv:snippets-changed', onChanged)
1239
1233
  }, [])
1240
1234
 
1241
- React.useEffect(() => {
1242
- const onOpen = (e) => {
1243
- const p = e?.detail?.path
1244
- if (!p) return
1245
- // G9:入口统一归一为工作区相对路径(差异栏/对话链接/LSP 跳转等都经此事件)
1246
- const normalized = tabPath(p)
1247
- if (!normalized) return
1248
- if (e?.detail?.focusDiff === true) {
1249
- recordNav()
1250
- pendingFocusRef.current = { path: normalized, region: null }
1251
- setFocusRequest((value) => value + 1)
1252
- addTab(normalized, true)
1253
- return
1254
- }
1255
- if (e?.detail?.line != null) {
1256
- // LSP/搜索跳转:打开并定位到行列(endLine/endColumn 为目标区间,供落地高亮)
1257
- openFileAt(normalized, e?.detail?.line, e?.detail?.column, e?.detail?.endLine, e?.detail?.endColumn)
1258
- return
1259
- }
1235
+ const fileOpenRef = React.useRef(null)
1236
+ /** @private @author ddj 2026年10月08号 @param e Routed file request. Always uses the current workspace and save/preview callbacks. */
1237
+ fileOpenRef.current = (e) => {
1238
+ const p = e?.detail?.path
1239
+ if (!p) return
1240
+ // G9:入口统一归一为工作区相对路径(差异栏/对话链接/LSP 跳转等都经此事件)
1241
+ const normalized = tabPath(p)
1242
+ if (!normalized) return
1243
+ if (e?.detail?.focusDiff === true) {
1260
1244
  recordNav()
1261
- addTab(normalized, true)
1245
+ pendingFocusRef.current = { path: normalized, region: null }
1246
+ setFocusRequest((value) => value + 1)
1247
+ addTab(normalized, true, false)
1248
+ return
1262
1249
  }
1250
+ if (e?.detail?.line != null) {
1251
+ // LSP/搜索跳转:打开并定位到行列(endLine/endColumn 为目标区间,供落地高亮)
1252
+ openFileAt(normalized, e?.detail?.line, e?.detail?.column, e?.detail?.endLine, e?.detail?.endColumn)
1253
+ return
1254
+ }
1255
+ recordNav()
1256
+ addTab(normalized, true, e?.detail?.preview)
1257
+ }
1258
+ React.useEffect(() => {
1259
+ /** @private @author ddj 2026年10月08号 @param event File navigation event. Dispatch to the latest render closure. */
1260
+ const onOpen = (event) => fileOpenRef.current?.(event)
1263
1261
  const onShowLauncher = (event) => {
1264
1262
  const tab = event?.detail?.tab
1265
1263
  if (tab === 'pending' || tab === 'archive') setLauncherTab(tab)
@@ -1345,7 +1343,6 @@ export function EditorView(props) {
1345
1343
  restoredScopeRef.current = scope
1346
1344
  // model 缓存跟随作用域(切工作区释放旧作用域模型;同工作区跨挂载复用)
1347
1345
  modelsRef.current = modelsForScope(scope)
1348
- viewStatesRef.current = viewStatesLoad(scope)
1349
1346
  // Markdown 预览态同样按作用域恢复(重启/换端口后由界面状态镜像回填,见 lateHydrateRef 分支)
1350
1347
  mdPreviewRef.current = mdPreviewLoad(String(scope))
1351
1348
  try {
@@ -1807,7 +1804,11 @@ export function EditorView(props) {
1807
1804
  React.useEffect(() => {
1808
1805
  if (!monaco) return
1809
1806
  registerThemes(monaco)
1810
- const apply = () => applyOfficial(monaco)
1807
+ /** @private @author ddj 2026年10月09号 同步 Monaco 主题与行内删除代码着色。 */
1808
+ const apply = () => {
1809
+ const themeId = applyOfficial(monaco)
1810
+ diffRendererRef.current.refresh(themeId)
1811
+ }
1811
1812
  apply()
1812
1813
  window.addEventListener('edrv:theme-change', apply)
1813
1814
  const query = typeof window.matchMedia === 'function' ? window.matchMedia('(prefers-color-scheme: dark)') : null
@@ -2052,11 +2053,10 @@ export function EditorView(props) {
2052
2053
  if (!monaco || !editorRef.current || !active || !contentReady) return
2053
2054
  const ed = editorRef.current
2054
2055
  const model = getModel(active, content)
2055
- if (ed.getModel() !== model) ed.setModel(model)
2056
+ viewKeeperRef.current?.bind(String(scope), active, model)
2056
2057
  syncTypeSuggest(ed, monaco)
2057
- restoreViewState(active)
2058
2058
  setLoadStage((prev) => ({ progress: Math.max(96, prev.progress), message: '创建编辑器视图…' }))
2059
- }, [monaco, active, content, contentReady, mdPreviewing, svnDiff])
2059
+ }, [monaco, scope, active, content, contentReady, mdPreviewing, svnDiff])
2060
2060
 
2061
2061
  // PDF 面板外壳 ref 回调(div 仅在 PDF 分支渲染,refs 先于 effect 就绪)
2062
2062
  const ensurePdfHost = (node) => { pdfHostRef.current = node }
@@ -2115,7 +2115,7 @@ export function EditorView(props) {
2115
2115
  // 跳转目标高亮的计时器随卸载清理(装饰随编辑器 dispose 一并消失)
2116
2116
  if (navFlashTimerRef.current) { clearTimeout(navFlashTimerRef.current); navFlashTimerRef.current = null }
2117
2117
  navFlashRef.current = []
2118
- if (editorRef.current) { editorRef.current.dispose(); editorRef.current = null }
2118
+ disposeEditor()
2119
2119
  // model 不在此销毁:跨挂载缓存按作用域存活(modelCache 切作用域时统一释放)
2120
2120
  for (const ctl of pdfCtlRef.current.values()) ctl.destroy()
2121
2121
  pdfCtlRef.current.clear()
@@ -2130,7 +2130,7 @@ export function EditorView(props) {
2130
2130
  // 普通 Monaco 编辑器(可编辑、单列行号)+ 自绘行内差异(decoration 绿底+ / view zone 删除块)
2131
2131
  const ensureEditor = React.useCallback((node) => {
2132
2132
  if (!node) {
2133
- if (editorRef.current) { editorRef.current.dispose(); editorRef.current = null }
2133
+ disposeEditor()
2134
2134
  return
2135
2135
  }
2136
2136
  if (editorRef.current || !monacoRef.current) return
@@ -2342,6 +2342,7 @@ export function EditorView(props) {
2342
2342
  }
2343
2343
  node.addEventListener('contextmenu', onBpCtxCapture, true)
2344
2344
  editorRef.current = ed
2345
+ viewKeeperRef.current = createViewKeeper(ed)
2345
2346
  }, [])
2346
2347
 
2347
2348
  // 调试断点装饰:断点表/活动文件变化时全量替换行首圆点(独立 collection,与差异/下划线互不干扰);
@@ -2619,13 +2620,19 @@ export function EditorView(props) {
2619
2620
  * @author ddj 2026年09月18号
2620
2621
  * @param path Markdown 文件路径
2621
2622
  */
2623
+ /** @private @author ddj 2026年10月08号 @param path Markdown disk path. @param preview Desired mode; explicit line opens always select source. */
2624
+ function setMdPreview(path, preview) {
2625
+ if (!isMarkdownPath(path) || mdPreviewRef.current.has(path) === preview) return
2626
+ toggleMdPreview(path)
2627
+ }
2628
+ /** @private @author ddj 2026年10月08号 @param path Markdown file. Toggle preview after flushing the active file's save and view state. */
2622
2629
  const toggleMdPreview = (path) => {
2623
2630
  if (!path) return
2624
2631
  const next = new Set(mdPreviewRef.current)
2625
2632
  const entering = !next.has(path)
2626
2633
  if (entering) {
2627
2634
  flushSave()
2628
- saveViewState(path)
2635
+ saveViewState(active)
2629
2636
  next.add(path)
2630
2637
  } else {
2631
2638
  next.delete(path)
@@ -3220,6 +3227,7 @@ export function EditorView(props) {
3220
3227
 
3221
3228
  /** 关闭当前活动页签(Ctrl+F4 与菜单动作共用;无页签时提示)。 */
3222
3229
  const closeActiveTab = () => {
3230
+ if (planActiveRef.current) { closePlan(planActiveRef.current); return }
3223
3231
  if (!activeRef.current) { setStatus('无打开的文件'); return }
3224
3232
  closeTab(activeRef.current)
3225
3233
  }
@@ -3346,7 +3354,7 @@ export function EditorView(props) {
3346
3354
  dapTrace('open-file-at', { input: path, normalized: tracePath, line, cwd })
3347
3355
  recordNav()
3348
3356
  // G9:同 openFile,先归一再进页签,保证待跳转路径与 active 同形态
3349
- const normalized = addTabNorm(path, true)
3357
+ const normalized = addTabNorm(path, true, false)
3350
3358
  if (!normalized) return
3351
3359
  stageFileAt(normalized, line, column, endLine, endColumn)
3352
3360
  }
@@ -3366,12 +3374,28 @@ export function EditorView(props) {
3366
3374
  setFocusRequest((value) => value + 1)
3367
3375
  }
3368
3376
 
3369
- /**
3370
- * Open a receipt target without losing edits or leaving the requested source hidden.
3371
- * @private @author ddj 2026年09月28号
3372
- * @param request Claimed external open request.
3373
- */
3377
+ /** @private @author ddj 2026年10月08号 @param plan Complete virtual document. Keeps virtual addresses out of all disk/save/LSP/SVN flows. */
3378
+ function openPlanDoc(plan) {
3379
+ const document = planDocument(plan?.address, plan)
3380
+ if (!document) return
3381
+ flushSave()
3382
+ saveViewState(active)
3383
+ setSvnDiff(null)
3384
+ setDiskActive(null)
3385
+ setPlanTabs((prev) => [...prev.filter((item) => item.address !== document.address), document].slice(-32))
3386
+ setPlanActive(document.address)
3387
+ setError(null)
3388
+ setStatus('计划预览 · 只读')
3389
+ }
3390
+ /** @private @author ddj 2026年10月08号 @param address Virtual tab. @param event Optional close click. Removes only in-memory plan content. */
3391
+ function closePlan(address, event) {
3392
+ event?.stopPropagation()
3393
+ setPlanTabs((prev) => prev.filter((item) => item.address !== address))
3394
+ if (planActiveRef.current === address) setActive(tabsRef.current[0]?.path ?? null)
3395
+ }
3396
+ /** @private @author ddj 2026年10月08号 @param request Claimed disk or virtual open. Flushes saves without replacing dirty models. */
3374
3397
  function openReceiptFile(request) {
3398
+ if (request.plan) { openPlanDoc(request.plan); return }
3375
3399
  if (!request.path) return
3376
3400
  let path = receiptTabPath(request.path, cwd)
3377
3401
  const existing = tabsRef.current.find((tab) => receiptPath(tab.path, cwd) === receiptPath(path, cwd))
@@ -3384,7 +3408,7 @@ export function EditorView(props) {
3384
3408
  if (positioned && mdPreviewRef.current.has(path)) toggleMdPreview(path)
3385
3409
  setError(null)
3386
3410
  if (monacoErr) setMonacoErr(null)
3387
- addTab(path, true)
3411
+ addTab(path, true, positioned ? false : request.preview)
3388
3412
  if (positioned) stageFileAt(path, request.line ?? 1, request.column ?? 1)
3389
3413
  const moved = receiptLoad && receiptPath(receiptLoad.path, receiptLoad.cwd) !== receiptPath(path, cwd)
3390
3414
  if (path !== active || !(loadError || receiptLoad?.status === 'error' || moved)) return
@@ -3423,10 +3447,14 @@ export function EditorView(props) {
3423
3447
 
3424
3448
  /** @private @author ddj 2026年09月28号 @param request Claimed target. @returns Correlated actual readiness. */
3425
3449
  function receiptReady(request) {
3450
+ if (request.plan) return request.sessionId === sessionId && planActive === request.plan.address
3451
+ && planTabs.some((item) => item.address === request.plan.address && item.markdown === request.plan.markdown)
3452
+ && planHostRef.current?.getAttribute('data-plan-address') === request.plan.address && !svnDiff
3426
3453
  return receiptReadyFor(request, receiptView(request))
3427
3454
  }
3428
3455
  /** @private @author ddj 2026年09月28号 @param request Claimed target. @returns Only its current load/render error. */
3429
3456
  function receiptError(request) {
3457
+ if (request.plan) return planDocument(request.plan.address, request.plan) ? null : '计划内容或资源地址无效'
3430
3458
  return receiptErrorFor(request, receiptView(request))
3431
3459
  }
3432
3460
  useOpenReceipt({ sessionId, open: openReceiptFile, ready: receiptReady, error: receiptError })
@@ -3514,11 +3542,20 @@ export function EditorView(props) {
3514
3542
  // @author ddj 2026年09月09号
3515
3543
  (dirtyMap[t.path] ? React.createElement('span', { className: 'edrv-tab-star', title: '未保存修改' }, '*') : null),
3516
3544
  (t.pinned ? null : React.createElement('span', { className: 'edrv-tab-x', title: '关闭', onClick: (e) => { e.stopPropagation(); closeTab(t.path) } }, '×')))),
3545
+ planTabs.map((plan) => React.createElement('div', {
3546
+ key: plan.address, className: 'edrv-tab' + (plan.address === planActive ? ' edrv-tab-active' : ''),
3547
+ title: plan.title + '(计划预览 · 只读)', onClick: () => openPlanDoc(plan),
3548
+ }, React.createElement('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: 180 } }, plan.title),
3549
+ React.createElement('span', { className: 'edrv-tab-x', title: '关闭', onClick: (event) => closePlan(plan.address, event) }, '×'))),
3517
3550
  (openInput
3518
3551
  ? React.createElement('input', { className: 'edrv-path-input', autoFocus: true, placeholder: '输入工作区相对/绝对路径,回车打开', value: pathDraft, onChange: (e) => setPathDraft(e.target.value), onKeyDown: (e) => { if (e.key === 'Enter') openPath(); if (e.key === 'Escape') setOpenInput(false) } })
3519
3552
  : React.createElement('button', { className: 'edrv-tab-add', title: '打开文件(输入路径)', onClick: () => setOpenInput(true) }, '+')))
3520
3553
 
3521
- const pathBar = React.createElement('div', { className: 'edrv-pathbar', title: active || '' },
3554
+ const activePlan = planTabs.find((plan) => plan.address === planActive)
3555
+ const pathBar = activePlan ? React.createElement('div', { className: 'edrv-pathbar', title: activePlan.address },
3556
+ React.createElement('span', { className: 'edrv-pb-name' }, activePlan.title),
3557
+ React.createElement('span', { className: 'edrv-pb-meta' }, '计划预览 · 只读 · 审批请使用对话中的官方控件'))
3558
+ : React.createElement('div', { className: 'edrv-pathbar', title: active || '' },
3522
3559
  React.createElement('span', { className: 'edrv-pb-name' }, active ? String(active).split(/[\\/]/).pop() : '未打开文件'),
3523
3560
  React.createElement('span', { className: 'edrv-pb-full' }, active || '使用右上搜索框 (' + (chordOf('edrv.quickOpen') ?? 'Ctrl+P') + ') 打开文件'),
3524
3561
  (recentUpdate && !svnUpdateDialog && sameUpdateOwner(recentUpdate, sessionId, scope)
@@ -3681,7 +3718,17 @@ export function EditorView(props) {
3681
3718
  retry ? React.createElement('button', { className: 'edrv-pill edrv-pill-ghost', onClick: retry }, '重试') : null)
3682
3719
 
3683
3720
  let body
3684
- if (svnDiff) {
3721
+ if (activePlan) {
3722
+ const labels = PLAN_MD_LABELS
3723
+ const text = activePlan.markdown
3724
+ const render = isComponentType(MarkdownText)
3725
+ ? React.createElement(MarkdownDelegateProvider, {
3726
+ openFile: (path, options) => options?.line != null ? openFileAt(path, options.line) : openFile(path, false),
3727
+ }, React.createElement(MarkdownText, { text, streaming: false, labels, variant: 'body' }))
3728
+ : React.createElement('pre', { className: 'edrv-mdview-pre' }, text)
3729
+ body = React.createElement('div', { className: 'edrv-mdview', ref: planHostRef, 'data-plan-address': activePlan.address },
3730
+ React.createElement('div', { className: 'edrv-mdview-stage' }, React.createElement('div', { className: 'edrv-mdview-doc' }, render)))
3731
+ } else if (svnDiff) {
3685
3732
  // 差异视图优先占位(覆盖编辑器主体):只读审阅态,关闭后回到原编辑器
3686
3733
  body = React.createElement(SvnDiffPanel, {
3687
3734
  key: 'edrv-svndiff:' + svnDiff.scheme + ':' + svnDiff.path,
@@ -69,14 +69,14 @@ export function OfficialSideTab(props) {
69
69
  if (fileHit) {
70
70
  const line = navigation?.params?.line
71
71
  window.dispatchEvent(new CustomEvent('edrv:open-editor', {
72
- detail: { path: fileHit.path, line: typeof line === 'number' ? line : undefined },
72
+ detail: { path: fileHit.path, line: typeof line === 'number' ? line : undefined, preview: navigation?.params?.preview },
73
73
  }))
74
74
  return
75
75
  }
76
76
  // 页类型 Tab:params 携带打开请求(line = 行引用/工具行跳转的行号定位)
77
77
  const request = resolveNavOpen(navigation?.params)
78
78
  window.dispatchEvent(new CustomEvent('edrv:open-editor', {
79
- detail: { path: request.path, focusDiff: request.focusDiff, line: resolveNavLine(navigation?.params) },
79
+ detail: { path: request.path, focusDiff: request.focusDiff, line: resolveNavLine(navigation?.params), preview: navigation?.params?.preview },
80
80
  }))
81
81
  })
82
82