@lijian-ui/dsh-file-manager 0.2.3 → 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 (96) 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 +27305 -270
  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.map +1 -1
  16. package/lib/types/client/locales.d.ts +3 -0
  17. package/lib/types/client/locales.d.ts.map +1 -1
  18. package/lib/types/client/mount.d.ts +1 -1
  19. package/lib/types/client/mount.d.ts.map +1 -1
  20. package/lib/types/client/preview/PreviewPanel.d.ts +2 -1
  21. package/lib/types/client/preview/PreviewPanel.d.ts.map +1 -1
  22. package/lib/types/client/preview/PreviewToolbar.d.ts +3 -1
  23. package/lib/types/client/preview/PreviewToolbar.d.ts.map +1 -1
  24. package/lib/types/client/preview/content.d.ts +3 -1
  25. package/lib/types/client/preview/content.d.ts.map +1 -1
  26. package/lib/types/client/preview/office.d.ts +23 -0
  27. package/lib/types/client/preview/office.d.ts.map +1 -0
  28. package/lib/types/client/reference.d.ts +17 -0
  29. package/lib/types/client/reference.d.ts.map +1 -1
  30. package/lib/types/client/store.d.ts.map +1 -1
  31. package/lib/types/host/fs-service.d.ts.map +1 -1
  32. package/lib/types/host/office-preview.d.ts +13 -0
  33. package/lib/types/host/office-preview.d.ts.map +1 -0
  34. package/lib/types/host/routes.d.ts.map +1 -1
  35. package/lib/types/index.d.ts.map +1 -1
  36. package/package.json +19 -4
  37. package/src/client/FileManagerSettingsCard.tsx +117 -117
  38. package/src/client/PluginSettingsCard.tsx +337 -337
  39. package/src/client/chat/file-ref.ts +144 -144
  40. package/src/client/chat/mermaid-chat.tsx +102 -102
  41. package/src/client/chat/placeholder-hint.tsx +46 -46
  42. package/src/client/components/ExplorerPanel.tsx +534 -534
  43. package/src/client/components/FileIcon.tsx +46 -46
  44. package/src/client/components/ScmPanel.tsx +480 -480
  45. package/src/client/components/a11y.ts +20 -20
  46. package/src/client/components/icons.tsx +274 -274
  47. package/src/client/components/overlay.tsx +225 -225
  48. package/src/client/drag/DragFileInlay.tsx +89 -89
  49. package/src/client/drag/file-drag.ts +67 -67
  50. package/src/client/drag.ts +154 -154
  51. package/src/client/file-source.ts +34 -0
  52. package/src/client/fileType.ts +32 -5
  53. package/src/client/hooks/useResizableSplit.ts +91 -91
  54. package/src/client/hooks/useStore.ts +15 -15
  55. package/src/client/index.ts +42 -3
  56. package/src/client/layout.ts +523 -523
  57. package/src/client/locales.ts +6 -0
  58. package/src/client/maximize.ts +34 -34
  59. package/src/client/mount.tsx +2 -1
  60. package/src/client/persist.ts +194 -194
  61. package/src/client/picker/FilePickerModal.tsx +526 -526
  62. package/src/client/picker/file-picker.ts +86 -86
  63. package/src/client/preview/PreviewPanel.tsx +7 -1
  64. package/src/client/preview/PreviewTabs.tsx +159 -159
  65. package/src/client/preview/PreviewToolbar.tsx +3 -2
  66. package/src/client/preview/content.tsx +7 -2
  67. package/src/client/preview/markdown.ts +351 -351
  68. package/src/client/preview/mermaid.ts +274 -274
  69. package/src/client/preview/office.tsx +321 -0
  70. package/src/client/reference.ts +49 -0
  71. package/src/client/settings-card.module.css +316 -316
  72. package/src/client/settings-form.ts +451 -451
  73. package/src/client/store.ts +17 -12
  74. package/src/client/styles/drag.module.css +30 -30
  75. package/src/client/styles/explorer.module.css +371 -371
  76. package/src/client/styles/picker.module.css +344 -344
  77. package/src/client/styles/preview.module.css +3 -1
  78. package/src/client/styles/scm.module.css +380 -380
  79. package/src/client/styles/tokens.module.css +376 -371
  80. package/src/core/types.ts +162 -162
  81. package/src/host/fs-service.ts +7 -1
  82. package/src/host/gate.ts +73 -73
  83. package/src/host/git-runner.ts +116 -116
  84. package/src/host/git-service.ts +395 -395
  85. package/src/host/loopback.ts +63 -63
  86. package/src/host/office-preview.ts +399 -0
  87. package/src/host/poll-guard.ts +109 -109
  88. package/src/host/routes.ts +72 -1
  89. package/src/index.ts +3 -1
  90. package/src/mount-once.ts +48 -48
  91. package/lib/types/client/chat/AttachedFilesDock.d.ts +0 -51
  92. package/lib/types/client/chat/AttachedFilesDock.d.ts.map +0 -1
  93. package/lib/types/client/floating.d.ts +0 -27
  94. package/lib/types/client/floating.d.ts.map +0 -1
  95. package/lib/types/client/mention.d.ts +0 -44
  96. package/lib/types/client/mention.d.ts.map +0 -1
@@ -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,8 +34,8 @@ 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
40
  import { FilePanelDockItem } from './FilePanelDockItem.tsx'
41
41
 
@@ -107,6 +107,7 @@ export function apply(ctx: ClientContext): void {
107
107
  // `slash/input-insert-reference` chip can send. Inert as a menu
108
108
  // participant — the '@' tree modal is our own picker, not the shell menu.
109
109
  ctx.effect(() => registerFileReferenceSource(ctx), 'dsh-filemgr: file reference source')
110
+ ctx.effect(() => registerSheetReferenceSource(ctx), 'dsh-filemgr: sheet reference source')
110
111
 
111
112
  // The '@' picker is our own tree modal (pi-desktop style), NOT the shell's
112
113
  // flat trigger menu: a bare `@` in the composer opens FilePickerHost
@@ -218,6 +219,7 @@ export function apply(ctx: ClientContext): void {
218
219
  const layout = new PanelLayoutController(stores.layout)
219
220
  const disposers: Array<() => void> = []
220
221
  let disposeGitEvents: (() => void) | undefined
222
+ let disposeFsEvents: (() => void) | undefined
221
223
  let currentRoot = ''
222
224
  let lastPreviewOpen = false
223
225
 
@@ -244,6 +246,14 @@ export function apply(ctx: ClientContext): void {
244
246
  return appendFileReferenceChip(ctx, sessionId, selection)
245
247
  }
246
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
+
247
257
  // The project root follows the active session's cwd; switching sessions
248
258
  // re-binds every store (widths, collapse, tree, tabs persist per root).
249
259
  const bindRoot = (): void => {
@@ -260,6 +270,8 @@ export function apply(ctx: ClientContext): void {
260
270
  // switches; syncGitSubscription re-evaluates it below.
261
271
  disposeGitEvents?.()
262
272
  disposeGitEvents = undefined
273
+ disposeFsEvents?.()
274
+ disposeFsEvents = undefined
263
275
  const previewOpen = stores.preview.getSnapshot().open
264
276
  lastPreviewOpen = previewOpen
265
277
  layoutSetRoot(stores.layout, root, previewOpen)
@@ -268,6 +280,7 @@ export function apply(ctx: ClientContext): void {
268
280
  stores.preview.setRoot(root)
269
281
 
270
282
  syncGitSubscription()
283
+ syncFsSubscription()
271
284
  }
272
285
 
273
286
  /**
@@ -301,8 +314,33 @@ export function apply(ctx: ClientContext): void {
301
314
  disposeGitEvents = undefined
302
315
  }
303
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
+ }
304
341
  disposers.push(ctx.sessions.list.subscribe(bindRoot))
305
342
  disposers.push(stores.explorer.subscribe(syncGitSubscription))
343
+ disposers.push(stores.layout.subscribe(syncFsSubscription))
306
344
  bindRoot()
307
345
 
308
346
  // Header tool-dock bridge (dsh-term's AnimatedDock): the "file panel"
@@ -347,7 +385,7 @@ export function apply(ctx: ClientContext): void {
347
385
  // Mount everything. DOM failures degrade the panels, never the GUI.
348
386
  try {
349
387
  layout.mount()
350
- mountPanels(stores, () => layout.toggleExplorer(), onReference, onAddFileReference, ctx.locale)
388
+ mountPanels(stores, () => layout.toggleExplorer(), onReference, onAddFileReference, onInsertSheetReference, ctx.locale)
351
389
  } catch (error) {
352
390
  console.error('[dsh-filemgr] mount failed:', error)
353
391
  }
@@ -388,6 +426,7 @@ export function apply(ctx: ClientContext): void {
388
426
  document.removeEventListener('visibilitychange', onVisibilityChange)
389
427
  document.removeEventListener('click', onFileRefClick)
390
428
  disposeGitEvents?.()
429
+ disposeFsEvents?.()
391
430
  langObserver?.disconnect()
392
431
  pickerRoot.unmount()
393
432
  pickerHostEl.remove()