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.
- package/README.md +14 -4
- package/lib/client.js +1127 -265
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +2 -0
- package/src/client/monaco/diffRender.ts +234 -174
- package/src/client/monaco/theme.ts +26 -15
- package/src/client/officialSidebar.ts +6 -1
- package/src/client/openReceipt.ts +13 -3
- package/src/client/planBridge.ts +178 -0
- package/src/client/planState.ts +129 -0
- package/src/client/receiptState.ts +1 -0
- package/src/client/state/editorFindState.ts +102 -0
- package/src/client/state/editorViewState.ts +56 -0
- package/src/client/state/regions.ts +83 -3
- package/src/client/styles/editor.css +8 -7
- package/src/client/ui/ClaimRouter.ts +1 -1
- package/src/client/ui/EditorView.ts +123 -76
- package/src/client/ui/OfficialSideTab.ts +2 -2
|
@@ -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 {
|
|
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,
|
|
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
|
-
//
|
|
324
|
-
const
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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.
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
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
|
|
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 (
|
|
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
|
|