@lijian-ui/dsh-file-manager 0.2.2 → 0.2.4
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/LICENSE +38 -38
- package/README.i18n.yaml +6 -6
- package/README.md +66 -66
- package/README.zh.md +66 -66
- package/cordis.patch.yml +13 -13
- package/lib/client.js +27354 -355
- package/lib/client.js.map +1 -1
- package/lib/index.js +474 -42
- package/lib/tsconfig.client.tsbuildinfo +1 -1
- package/lib/tsconfig.host.tsbuildinfo +1 -1
- package/lib/types/client/file-source.d.ts +10 -0
- package/lib/types/client/file-source.d.ts.map +1 -1
- package/lib/types/client/fileType.d.ts +19 -4
- package/lib/types/client/fileType.d.ts.map +1 -1
- package/lib/types/client/index.d.ts +0 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +3 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/mount.d.ts +1 -1
- package/lib/types/client/mount.d.ts.map +1 -1
- package/lib/types/client/picker/FilePickerModal.d.ts +8 -6
- package/lib/types/client/picker/FilePickerModal.d.ts.map +1 -1
- package/lib/types/client/preview/PreviewPanel.d.ts +2 -1
- package/lib/types/client/preview/PreviewPanel.d.ts.map +1 -1
- package/lib/types/client/preview/PreviewToolbar.d.ts +3 -1
- package/lib/types/client/preview/PreviewToolbar.d.ts.map +1 -1
- package/lib/types/client/preview/content.d.ts +3 -1
- package/lib/types/client/preview/content.d.ts.map +1 -1
- package/lib/types/client/preview/office.d.ts +23 -0
- package/lib/types/client/preview/office.d.ts.map +1 -0
- package/lib/types/client/reference.d.ts +23 -10
- package/lib/types/client/reference.d.ts.map +1 -1
- package/lib/types/client/store.d.ts.map +1 -1
- package/lib/types/host/fs-service.d.ts.map +1 -1
- package/lib/types/host/office-preview.d.ts +13 -0
- package/lib/types/host/office-preview.d.ts.map +1 -0
- package/lib/types/host/routes.d.ts.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +19 -4
- package/src/client/DockItem.tsx +1 -1
- package/src/client/FileManagerSettingsCard.tsx +117 -117
- package/src/client/PluginSettingsCard.tsx +337 -337
- package/src/client/chat/file-ref.ts +144 -144
- package/src/client/chat/mermaid-chat.tsx +102 -102
- package/src/client/chat/placeholder-hint.tsx +46 -46
- package/src/client/components/ExplorerPanel.tsx +534 -534
- package/src/client/components/FileIcon.tsx +46 -46
- package/src/client/components/ScmPanel.tsx +480 -480
- package/src/client/components/a11y.ts +20 -20
- package/src/client/components/icons.tsx +274 -274
- package/src/client/components/overlay.tsx +225 -225
- package/src/client/drag/DragFileInlay.tsx +89 -89
- package/src/client/drag/file-drag.ts +67 -67
- package/src/client/drag.ts +154 -154
- package/src/client/file-source.ts +34 -0
- package/src/client/fileType.ts +32 -5
- package/src/client/hooks/useResizableSplit.ts +91 -91
- package/src/client/hooks/useStore.ts +15 -15
- package/src/client/index.ts +45 -17
- package/src/client/layout.ts +523 -523
- package/src/client/locales.ts +6 -0
- package/src/client/maximize.ts +34 -34
- package/src/client/mount.tsx +2 -1
- package/src/client/persist.ts +194 -194
- package/src/client/picker/FilePickerModal.tsx +526 -507
- package/src/client/picker/file-picker.ts +86 -86
- package/src/client/preview/PreviewPanel.tsx +8 -1
- package/src/client/preview/PreviewTabs.tsx +159 -159
- package/src/client/preview/PreviewToolbar.tsx +3 -2
- package/src/client/preview/content.tsx +7 -2
- package/src/client/preview/markdown.ts +351 -351
- package/src/client/preview/mermaid.ts +274 -274
- package/src/client/preview/office.tsx +321 -0
- package/src/client/reference.ts +62 -8
- package/src/client/settings-card.module.css +316 -316
- package/src/client/settings-form.ts +451 -451
- package/src/client/store.ts +17 -12
- package/src/client/styles/drag.module.css +30 -30
- package/src/client/styles/explorer.module.css +371 -371
- package/src/client/styles/picker.module.css +344 -344
- package/src/client/styles/preview.module.css +18 -11
- package/src/client/styles/scm.module.css +380 -380
- package/src/client/styles/tokens.module.css +376 -367
- package/src/core/types.ts +162 -162
- package/src/host/fs-service.ts +7 -1
- package/src/host/gate.ts +73 -73
- package/src/host/git-runner.ts +116 -116
- package/src/host/git-service.ts +395 -395
- package/src/host/loopback.ts +63 -63
- package/src/host/office-preview.ts +399 -0
- package/src/host/poll-guard.ts +109 -109
- package/src/host/routes.ts +72 -1
- package/src/index.ts +3 -1
- package/src/mount-once.ts +48 -48
- package/lib/types/client/chat/AttachedFilesDock.d.ts +0 -51
- package/lib/types/client/chat/AttachedFilesDock.d.ts.map +0 -1
- package/lib/types/client/floating.d.ts +0 -27
- package/lib/types/client/floating.d.ts.map +0 -1
- package/lib/types/client/mention.d.ts +0 -44
- package/lib/types/client/mention.d.ts.map +0 -1
- package/src/client/styles/chip.module.css +0 -38
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The panel system's single drag engine hook — a thin React wrapper over the
|
|
3
|
-
* framework-free machinery in drag.ts (FileManager's useResizableSplit
|
|
4
|
-
* architecture, re-implemented): px or ratio units, range-validated
|
|
5
|
-
* localStorage persistence, double-click reset to the default width.
|
|
6
|
-
* @module dsh-filemgr/client/hooks/useResizableSplit
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
10
|
-
import type { PointerEvent as ReactPointerEvent } from 'react'
|
|
11
|
-
import { handlePointerDragStart } from '../drag.ts'
|
|
12
|
-
import { readStoredNumber, writeStoredNumber } from '../persist.ts'
|
|
13
|
-
|
|
14
|
-
export interface UseResizableSplitOptions {
|
|
15
|
-
/** Default width (px or percent). */
|
|
16
|
-
defaultWidth?: number
|
|
17
|
-
/** Minimum (same unit). */
|
|
18
|
-
minWidth?: number
|
|
19
|
-
/** Maximum (same unit). */
|
|
20
|
-
maxWidth?: number
|
|
21
|
-
/** localStorage key (preference persistence). */
|
|
22
|
-
storageKey?: string
|
|
23
|
-
/** 'px' for fixed pixel widths, 'ratio' for percents (default 'ratio'). */
|
|
24
|
-
unit?: 'px' | 'ratio'
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface DragHandleProps {
|
|
28
|
-
onPointerDown: (event: ReactPointerEvent<HTMLDivElement>) => void
|
|
29
|
-
onDoubleClick: () => void
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Resizable-split engine.
|
|
34
|
-
* @param options - width contract + persistence key.
|
|
35
|
-
* @returns current width, the committed setter, handle props, and the clamp.
|
|
36
|
-
*/
|
|
37
|
-
export function useResizableSplit(options: UseResizableSplitOptions = {}) {
|
|
38
|
-
const {
|
|
39
|
-
defaultWidth = 50,
|
|
40
|
-
minWidth = 20,
|
|
41
|
-
maxWidth = 80,
|
|
42
|
-
storageKey,
|
|
43
|
-
unit = 'ratio',
|
|
44
|
-
} = options
|
|
45
|
-
const isPx = unit === 'px'
|
|
46
|
-
|
|
47
|
-
const [width, setWidthState] = useState(() =>
|
|
48
|
-
storageKey === undefined
|
|
49
|
-
? defaultWidth
|
|
50
|
-
: readStoredNumber(storageKey, minWidth, maxWidth, defaultWidth))
|
|
51
|
-
|
|
52
|
-
// The pointer-down closure reads the width at drag START without rebinding.
|
|
53
|
-
const widthRef = useRef(width)
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
widthRef.current = width
|
|
56
|
-
}, [width])
|
|
57
|
-
|
|
58
|
-
/** The committed setter: state + storage (validated) + resize event. */
|
|
59
|
-
const setWidth = useCallback((value: number) => {
|
|
60
|
-
setWidthState(value)
|
|
61
|
-
if (storageKey !== undefined) writeStoredNumber(storageKey, value)
|
|
62
|
-
try {
|
|
63
|
-
window.dispatchEvent(new CustomEvent('preview-panel-resize', { detail: { width: value } }))
|
|
64
|
-
} catch {
|
|
65
|
-
// event dispatch is best-effort
|
|
66
|
-
}
|
|
67
|
-
}, [storageKey])
|
|
68
|
-
|
|
69
|
-
const clamp = useCallback((value: number): number => {
|
|
70
|
-
return Math.min(maxWidth, Math.max(minWidth, value))
|
|
71
|
-
}, [minWidth, maxWidth])
|
|
72
|
-
|
|
73
|
-
const handlePointerDown = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
|
|
74
|
-
const el = event.currentTarget as HTMLDivElement
|
|
75
|
-
handlePointerDragStart(event.nativeEvent, el, {
|
|
76
|
-
reverse: el.dataset.reverse === 'true',
|
|
77
|
-
getStartWidth: () => widthRef.current,
|
|
78
|
-
compute: (startWidth, deltaX) => clamp(startWidth + deltaX),
|
|
79
|
-
onFrame: (value) => setWidthState(value),
|
|
80
|
-
onEnd: (value) => setWidth(value),
|
|
81
|
-
})
|
|
82
|
-
}, [clamp, setWidth])
|
|
83
|
-
|
|
84
|
-
const handleDoubleClick = useCallback(() => {
|
|
85
|
-
setWidth(defaultWidth)
|
|
86
|
-
}, [defaultWidth, setWidth])
|
|
87
|
-
|
|
88
|
-
const handleProps: DragHandleProps = { onPointerDown: handlePointerDown, onDoubleClick: handleDoubleClick }
|
|
89
|
-
|
|
90
|
-
return { width, setWidth, handleProps, clamp, isPx }
|
|
91
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The panel system's single drag engine hook — a thin React wrapper over the
|
|
3
|
+
* framework-free machinery in drag.ts (FileManager's useResizableSplit
|
|
4
|
+
* architecture, re-implemented): px or ratio units, range-validated
|
|
5
|
+
* localStorage persistence, double-click reset to the default width.
|
|
6
|
+
* @module dsh-filemgr/client/hooks/useResizableSplit
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
10
|
+
import type { PointerEvent as ReactPointerEvent } from 'react'
|
|
11
|
+
import { handlePointerDragStart } from '../drag.ts'
|
|
12
|
+
import { readStoredNumber, writeStoredNumber } from '../persist.ts'
|
|
13
|
+
|
|
14
|
+
export interface UseResizableSplitOptions {
|
|
15
|
+
/** Default width (px or percent). */
|
|
16
|
+
defaultWidth?: number
|
|
17
|
+
/** Minimum (same unit). */
|
|
18
|
+
minWidth?: number
|
|
19
|
+
/** Maximum (same unit). */
|
|
20
|
+
maxWidth?: number
|
|
21
|
+
/** localStorage key (preference persistence). */
|
|
22
|
+
storageKey?: string
|
|
23
|
+
/** 'px' for fixed pixel widths, 'ratio' for percents (default 'ratio'). */
|
|
24
|
+
unit?: 'px' | 'ratio'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface DragHandleProps {
|
|
28
|
+
onPointerDown: (event: ReactPointerEvent<HTMLDivElement>) => void
|
|
29
|
+
onDoubleClick: () => void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resizable-split engine.
|
|
34
|
+
* @param options - width contract + persistence key.
|
|
35
|
+
* @returns current width, the committed setter, handle props, and the clamp.
|
|
36
|
+
*/
|
|
37
|
+
export function useResizableSplit(options: UseResizableSplitOptions = {}) {
|
|
38
|
+
const {
|
|
39
|
+
defaultWidth = 50,
|
|
40
|
+
minWidth = 20,
|
|
41
|
+
maxWidth = 80,
|
|
42
|
+
storageKey,
|
|
43
|
+
unit = 'ratio',
|
|
44
|
+
} = options
|
|
45
|
+
const isPx = unit === 'px'
|
|
46
|
+
|
|
47
|
+
const [width, setWidthState] = useState(() =>
|
|
48
|
+
storageKey === undefined
|
|
49
|
+
? defaultWidth
|
|
50
|
+
: readStoredNumber(storageKey, minWidth, maxWidth, defaultWidth))
|
|
51
|
+
|
|
52
|
+
// The pointer-down closure reads the width at drag START without rebinding.
|
|
53
|
+
const widthRef = useRef(width)
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
widthRef.current = width
|
|
56
|
+
}, [width])
|
|
57
|
+
|
|
58
|
+
/** The committed setter: state + storage (validated) + resize event. */
|
|
59
|
+
const setWidth = useCallback((value: number) => {
|
|
60
|
+
setWidthState(value)
|
|
61
|
+
if (storageKey !== undefined) writeStoredNumber(storageKey, value)
|
|
62
|
+
try {
|
|
63
|
+
window.dispatchEvent(new CustomEvent('preview-panel-resize', { detail: { width: value } }))
|
|
64
|
+
} catch {
|
|
65
|
+
// event dispatch is best-effort
|
|
66
|
+
}
|
|
67
|
+
}, [storageKey])
|
|
68
|
+
|
|
69
|
+
const clamp = useCallback((value: number): number => {
|
|
70
|
+
return Math.min(maxWidth, Math.max(minWidth, value))
|
|
71
|
+
}, [minWidth, maxWidth])
|
|
72
|
+
|
|
73
|
+
const handlePointerDown = useCallback((event: ReactPointerEvent<HTMLDivElement>) => {
|
|
74
|
+
const el = event.currentTarget as HTMLDivElement
|
|
75
|
+
handlePointerDragStart(event.nativeEvent, el, {
|
|
76
|
+
reverse: el.dataset.reverse === 'true',
|
|
77
|
+
getStartWidth: () => widthRef.current,
|
|
78
|
+
compute: (startWidth, deltaX) => clamp(startWidth + deltaX),
|
|
79
|
+
onFrame: (value) => setWidthState(value),
|
|
80
|
+
onEnd: (value) => setWidth(value),
|
|
81
|
+
})
|
|
82
|
+
}, [clamp, setWidth])
|
|
83
|
+
|
|
84
|
+
const handleDoubleClick = useCallback(() => {
|
|
85
|
+
setWidth(defaultWidth)
|
|
86
|
+
}, [defaultWidth, setWidth])
|
|
87
|
+
|
|
88
|
+
const handleProps: DragHandleProps = { onPointerDown: handlePointerDown, onDoubleClick: handleDoubleClick }
|
|
89
|
+
|
|
90
|
+
return { width, setWidth, handleProps, clamp, isPx }
|
|
91
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* React bindings for the framework-free stores: useSyncExternalStore with a
|
|
3
|
-
* stable snapshot (the stores return immutable snapshots, so selector-free
|
|
4
|
-
* subscription is safe), plus a stable-callback helper for event handlers.
|
|
5
|
-
* @module dsh-filemgr/client/hooks/useStore
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useSyncExternalStore } from 'react'
|
|
9
|
-
import type { StateHandle } from '../store.ts'
|
|
10
|
-
|
|
11
|
-
/** Subscribe a component to one store (full snapshot). */
|
|
12
|
-
export function useStore<S>(store: StateHandle<S>): S {
|
|
13
|
-
return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)
|
|
14
|
-
}
|
|
15
|
-
|
|
1
|
+
/**
|
|
2
|
+
* React bindings for the framework-free stores: useSyncExternalStore with a
|
|
3
|
+
* stable snapshot (the stores return immutable snapshots, so selector-free
|
|
4
|
+
* subscription is safe), plus a stable-callback helper for event handlers.
|
|
5
|
+
* @module dsh-filemgr/client/hooks/useStore
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useSyncExternalStore } from 'react'
|
|
9
|
+
import type { StateHandle } from '../store.ts'
|
|
10
|
+
|
|
11
|
+
/** Subscribe a component to one store (full snapshot). */
|
|
12
|
+
export function useStore<S>(store: StateHandle<S>): S {
|
|
13
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)
|
|
14
|
+
}
|
|
15
|
+
|
package/src/client/index.ts
CHANGED
|
@@ -34,10 +34,9 @@ import { MermaidChatEnhancer } from './chat/mermaid-chat.tsx'
|
|
|
34
34
|
import { PlaceholderHint } from './chat/placeholder-hint.tsx'
|
|
35
35
|
import { FilePickerHost } from './picker/FilePickerModal.tsx'
|
|
36
36
|
import { handleFileRefClick } from './chat/file-ref.ts'
|
|
37
|
-
import { appendToDraft, appendFileReferenceChip } from './reference.ts'
|
|
38
|
-
import { registerFileReferenceSource } from './file-source.ts'
|
|
37
|
+
import { appendToDraft, appendFileReferenceChip, appendSheetChip } from './reference.ts'
|
|
38
|
+
import { registerFileReferenceSource, registerSheetReferenceSource } from './file-source.ts'
|
|
39
39
|
import type { FileReferenceSelection } from './preview/selection.ts'
|
|
40
|
-
import './styles/chip.module.css'
|
|
41
40
|
import { FilePanelDockItem } from './FilePanelDockItem.tsx'
|
|
42
41
|
|
|
43
42
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
@@ -108,6 +107,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
108
107
|
// `slash/input-insert-reference` chip can send. Inert as a menu
|
|
109
108
|
// participant — the '@' tree modal is our own picker, not the shell menu.
|
|
110
109
|
ctx.effect(() => registerFileReferenceSource(ctx), 'dsh-filemgr: file reference source')
|
|
110
|
+
ctx.effect(() => registerSheetReferenceSource(ctx), 'dsh-filemgr: sheet reference source')
|
|
111
111
|
|
|
112
112
|
// The '@' picker is our own tree modal (pi-desktop style), NOT the shell's
|
|
113
113
|
// flat trigger menu: a bare `@` in the composer opens FilePickerHost
|
|
@@ -219,13 +219,13 @@ export function apply(ctx: ClientContext): void {
|
|
|
219
219
|
const layout = new PanelLayoutController(stores.layout)
|
|
220
220
|
const disposers: Array<() => void> = []
|
|
221
221
|
let disposeGitEvents: (() => void) | undefined
|
|
222
|
+
let disposeFsEvents: (() => void) | undefined
|
|
222
223
|
let currentRoot = ''
|
|
223
224
|
let lastPreviewOpen = false
|
|
224
225
|
|
|
225
226
|
// The Explorer row '@' button: insert a file-reference chip for files
|
|
226
|
-
// (path
|
|
227
|
-
//
|
|
228
|
-
// range; failures fall back to plain text.
|
|
227
|
+
// (plain `@<rel-path>`, no line range) or plain `@<rel-path>/` text for
|
|
228
|
+
// folders.
|
|
229
229
|
const onReference = (path: string, isDir: boolean): void => {
|
|
230
230
|
const sessionId = ctx.sessions.list.getSnapshot().current as SessionId | undefined
|
|
231
231
|
if (sessionId === undefined) {
|
|
@@ -236,16 +236,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
236
236
|
appendToDraft(ctx, sessionId, `@${path}/ `)
|
|
237
237
|
return
|
|
238
238
|
}
|
|
239
|
-
|
|
240
|
-
if (!result.ok) {
|
|
241
|
-
appendToDraft(ctx, sessionId, `@${path} `)
|
|
242
|
-
return
|
|
243
|
-
}
|
|
244
|
-
const text = result.value.content
|
|
245
|
-
const trimmed = text.endsWith('\n') ? text.slice(0, -1) : text
|
|
246
|
-
const lines = trimmed === '' ? 1 : trimmed.split('\n').length
|
|
247
|
-
appendFileReferenceChip(ctx, sessionId, { path, startLine: 1, endLine: lines })
|
|
248
|
-
})
|
|
239
|
+
appendFileReferenceChip(ctx, sessionId, { path })
|
|
249
240
|
}
|
|
250
241
|
|
|
251
242
|
// The preview "添加到对话" action: insert a file-reference chip
|
|
@@ -255,6 +246,14 @@ export function apply(ctx: ClientContext): void {
|
|
|
255
246
|
return appendFileReferenceChip(ctx, sessionId, selection)
|
|
256
247
|
}
|
|
257
248
|
|
|
249
|
+
// The preview Excel "引用到对话" action: mint a `sheet` chip (appearance
|
|
250
|
+
// only) appended at the draft end, serializing the
|
|
251
|
+
// `相对路径!Sheet名!Range` token on submit.
|
|
252
|
+
const onInsertSheetReference = (path: string, sheetName: string, range: string): boolean => {
|
|
253
|
+
const sessionId = ctx.sessions.list.getSnapshot().current as SessionId | undefined
|
|
254
|
+
return appendSheetChip(ctx, sessionId, path, { sheetName, range })
|
|
255
|
+
}
|
|
256
|
+
|
|
258
257
|
// The project root follows the active session's cwd; switching sessions
|
|
259
258
|
// re-binds every store (widths, collapse, tree, tabs persist per root).
|
|
260
259
|
const bindRoot = (): void => {
|
|
@@ -271,6 +270,8 @@ export function apply(ctx: ClientContext): void {
|
|
|
271
270
|
// switches; syncGitSubscription re-evaluates it below.
|
|
272
271
|
disposeGitEvents?.()
|
|
273
272
|
disposeGitEvents = undefined
|
|
273
|
+
disposeFsEvents?.()
|
|
274
|
+
disposeFsEvents = undefined
|
|
274
275
|
const previewOpen = stores.preview.getSnapshot().open
|
|
275
276
|
lastPreviewOpen = previewOpen
|
|
276
277
|
layoutSetRoot(stores.layout, root, previewOpen)
|
|
@@ -279,6 +280,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
279
280
|
stores.preview.setRoot(root)
|
|
280
281
|
|
|
281
282
|
syncGitSubscription()
|
|
283
|
+
syncFsSubscription()
|
|
282
284
|
}
|
|
283
285
|
|
|
284
286
|
/**
|
|
@@ -312,8 +314,33 @@ export function apply(ctx: ClientContext): void {
|
|
|
312
314
|
disposeGitEvents = undefined
|
|
313
315
|
}
|
|
314
316
|
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Live-filesystem notifications: the host watches the workspace root
|
|
320
|
+
* while any SSE stream is open (recursive fs.watch + debounce) and emits
|
|
321
|
+
* `{kind:'fs'}` on out-of-band writes — chiefly an agent/LLM subprocess
|
|
322
|
+
* creating or editing a work product directly on disk (which never
|
|
323
|
+
* touches the /filemgr/* write APIs). Refresh the explorer so the new
|
|
324
|
+
* file shows up without a manual UI action. The subscription mirrors the
|
|
325
|
+
* panel lifecycle: only while a session root exists AND the explorer
|
|
326
|
+
* panel is visible (CollapsiblePanel/折叠 keeps the dock icon hot).
|
|
327
|
+
*/
|
|
328
|
+
const syncFsSubscription = (): void => {
|
|
329
|
+
const root = currentRoot
|
|
330
|
+
const visible = !stores.layout.getSnapshot().explorerCollapsed
|
|
331
|
+
const want = root !== '' && visible
|
|
332
|
+
if (want && disposeFsEvents === undefined) {
|
|
333
|
+
disposeFsEvents = subscribePanelEvents(root, (event) => {
|
|
334
|
+
if (event.kind === 'fs') void stores.explorer.handleFsChange()
|
|
335
|
+
})
|
|
336
|
+
} else if (!want && disposeFsEvents !== undefined) {
|
|
337
|
+
disposeFsEvents()
|
|
338
|
+
disposeFsEvents = undefined
|
|
339
|
+
}
|
|
340
|
+
}
|
|
315
341
|
disposers.push(ctx.sessions.list.subscribe(bindRoot))
|
|
316
342
|
disposers.push(stores.explorer.subscribe(syncGitSubscription))
|
|
343
|
+
disposers.push(stores.layout.subscribe(syncFsSubscription))
|
|
317
344
|
bindRoot()
|
|
318
345
|
|
|
319
346
|
// Header tool-dock bridge (dsh-term's AnimatedDock): the "file panel"
|
|
@@ -358,7 +385,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
358
385
|
// Mount everything. DOM failures degrade the panels, never the GUI.
|
|
359
386
|
try {
|
|
360
387
|
layout.mount()
|
|
361
|
-
mountPanels(stores, () => layout.toggleExplorer(), onReference, onAddFileReference, ctx.locale)
|
|
388
|
+
mountPanels(stores, () => layout.toggleExplorer(), onReference, onAddFileReference, onInsertSheetReference, ctx.locale)
|
|
362
389
|
} catch (error) {
|
|
363
390
|
console.error('[dsh-filemgr] mount failed:', error)
|
|
364
391
|
}
|
|
@@ -399,6 +426,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
399
426
|
document.removeEventListener('visibilitychange', onVisibilityChange)
|
|
400
427
|
document.removeEventListener('click', onFileRefClick)
|
|
401
428
|
disposeGitEvents?.()
|
|
429
|
+
disposeFsEvents?.()
|
|
402
430
|
langObserver?.disconnect()
|
|
403
431
|
pickerRoot.unmount()
|
|
404
432
|
pickerHostEl.remove()
|