@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.
Files changed (101) hide show
  1. package/LICENSE +38 -38
  2. package/README.i18n.yaml +6 -6
  3. package/README.md +66 -66
  4. package/README.zh.md +66 -66
  5. package/cordis.patch.yml +13 -13
  6. package/lib/client.js +27354 -355
  7. package/lib/client.js.map +1 -1
  8. package/lib/index.js +474 -42
  9. package/lib/tsconfig.client.tsbuildinfo +1 -1
  10. package/lib/tsconfig.host.tsbuildinfo +1 -1
  11. package/lib/types/client/file-source.d.ts +10 -0
  12. package/lib/types/client/file-source.d.ts.map +1 -1
  13. package/lib/types/client/fileType.d.ts +19 -4
  14. package/lib/types/client/fileType.d.ts.map +1 -1
  15. package/lib/types/client/index.d.ts +0 -1
  16. package/lib/types/client/index.d.ts.map +1 -1
  17. package/lib/types/client/locales.d.ts +3 -0
  18. package/lib/types/client/locales.d.ts.map +1 -1
  19. package/lib/types/client/mount.d.ts +1 -1
  20. package/lib/types/client/mount.d.ts.map +1 -1
  21. package/lib/types/client/picker/FilePickerModal.d.ts +8 -6
  22. package/lib/types/client/picker/FilePickerModal.d.ts.map +1 -1
  23. package/lib/types/client/preview/PreviewPanel.d.ts +2 -1
  24. package/lib/types/client/preview/PreviewPanel.d.ts.map +1 -1
  25. package/lib/types/client/preview/PreviewToolbar.d.ts +3 -1
  26. package/lib/types/client/preview/PreviewToolbar.d.ts.map +1 -1
  27. package/lib/types/client/preview/content.d.ts +3 -1
  28. package/lib/types/client/preview/content.d.ts.map +1 -1
  29. package/lib/types/client/preview/office.d.ts +23 -0
  30. package/lib/types/client/preview/office.d.ts.map +1 -0
  31. package/lib/types/client/reference.d.ts +23 -10
  32. package/lib/types/client/reference.d.ts.map +1 -1
  33. package/lib/types/client/store.d.ts.map +1 -1
  34. package/lib/types/host/fs-service.d.ts.map +1 -1
  35. package/lib/types/host/office-preview.d.ts +13 -0
  36. package/lib/types/host/office-preview.d.ts.map +1 -0
  37. package/lib/types/host/routes.d.ts.map +1 -1
  38. package/lib/types/index.d.ts.map +1 -1
  39. package/package.json +19 -4
  40. package/src/client/DockItem.tsx +1 -1
  41. package/src/client/FileManagerSettingsCard.tsx +117 -117
  42. package/src/client/PluginSettingsCard.tsx +337 -337
  43. package/src/client/chat/file-ref.ts +144 -144
  44. package/src/client/chat/mermaid-chat.tsx +102 -102
  45. package/src/client/chat/placeholder-hint.tsx +46 -46
  46. package/src/client/components/ExplorerPanel.tsx +534 -534
  47. package/src/client/components/FileIcon.tsx +46 -46
  48. package/src/client/components/ScmPanel.tsx +480 -480
  49. package/src/client/components/a11y.ts +20 -20
  50. package/src/client/components/icons.tsx +274 -274
  51. package/src/client/components/overlay.tsx +225 -225
  52. package/src/client/drag/DragFileInlay.tsx +89 -89
  53. package/src/client/drag/file-drag.ts +67 -67
  54. package/src/client/drag.ts +154 -154
  55. package/src/client/file-source.ts +34 -0
  56. package/src/client/fileType.ts +32 -5
  57. package/src/client/hooks/useResizableSplit.ts +91 -91
  58. package/src/client/hooks/useStore.ts +15 -15
  59. package/src/client/index.ts +45 -17
  60. package/src/client/layout.ts +523 -523
  61. package/src/client/locales.ts +6 -0
  62. package/src/client/maximize.ts +34 -34
  63. package/src/client/mount.tsx +2 -1
  64. package/src/client/persist.ts +194 -194
  65. package/src/client/picker/FilePickerModal.tsx +526 -507
  66. package/src/client/picker/file-picker.ts +86 -86
  67. package/src/client/preview/PreviewPanel.tsx +8 -1
  68. package/src/client/preview/PreviewTabs.tsx +159 -159
  69. package/src/client/preview/PreviewToolbar.tsx +3 -2
  70. package/src/client/preview/content.tsx +7 -2
  71. package/src/client/preview/markdown.ts +351 -351
  72. package/src/client/preview/mermaid.ts +274 -274
  73. package/src/client/preview/office.tsx +321 -0
  74. package/src/client/reference.ts +62 -8
  75. package/src/client/settings-card.module.css +316 -316
  76. package/src/client/settings-form.ts +451 -451
  77. package/src/client/store.ts +17 -12
  78. package/src/client/styles/drag.module.css +30 -30
  79. package/src/client/styles/explorer.module.css +371 -371
  80. package/src/client/styles/picker.module.css +344 -344
  81. package/src/client/styles/preview.module.css +18 -11
  82. package/src/client/styles/scm.module.css +380 -380
  83. package/src/client/styles/tokens.module.css +376 -367
  84. package/src/core/types.ts +162 -162
  85. package/src/host/fs-service.ts +7 -1
  86. package/src/host/gate.ts +73 -73
  87. package/src/host/git-runner.ts +116 -116
  88. package/src/host/git-service.ts +395 -395
  89. package/src/host/loopback.ts +63 -63
  90. package/src/host/office-preview.ts +399 -0
  91. package/src/host/poll-guard.ts +109 -109
  92. package/src/host/routes.ts +72 -1
  93. package/src/index.ts +3 -1
  94. package/src/mount-once.ts +48 -48
  95. package/lib/types/client/chat/AttachedFilesDock.d.ts +0 -51
  96. package/lib/types/client/chat/AttachedFilesDock.d.ts.map +0 -1
  97. package/lib/types/client/floating.d.ts +0 -27
  98. package/lib/types/client/floating.d.ts.map +0 -1
  99. package/lib/types/client/mention.d.ts +0 -44
  100. package/lib/types/client/mention.d.ts.map +0 -1
  101. 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
+
@@ -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 + 1..N) or plain `@<rel-path>/` text for folders. File lines
227
- // are fetched from the host so the chip can carry an accurate line
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
- void panelApi.read(currentRoot, path, false).then((result) => {
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()