@keith9681/dsh-sidebar 0.15.2

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 (220) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/README_EN.md +0 -0
  4. package/cordis.patch.yml +49 -0
  5. package/lib/client-editor.js +34651 -0
  6. package/lib/client-mermaid.js +200629 -0
  7. package/lib/client-registry.js +13388 -0
  8. package/lib/client-terminal.js +8904 -0
  9. package/lib/client.js +13388 -0
  10. package/lib/index.js +3959 -0
  11. package/lib/invariant.js +22 -0
  12. package/lib/types/agent-pty.d.ts +226 -0
  13. package/lib/types/browser-probe.d.ts +15 -0
  14. package/lib/types/bundle-route.d.ts +12 -0
  15. package/lib/types/client/BrowserView.d.ts +22 -0
  16. package/lib/types/client/DiffTab.d.ts +6 -0
  17. package/lib/types/client/DiffView.d.ts +51 -0
  18. package/lib/types/client/EditorHost.d.ts +12 -0
  19. package/lib/types/client/FileTree.d.ts +36 -0
  20. package/lib/types/client/GitView.d.ts +8 -0
  21. package/lib/types/client/OrphanedTab.d.ts +11 -0
  22. package/lib/types/client/PdfView.d.ts +6 -0
  23. package/lib/types/client/RenderBoundary.d.ts +32 -0
  24. package/lib/types/client/SandboxStatusBar.d.ts +10 -0
  25. package/lib/types/client/SideCardSection.d.ts +74 -0
  26. package/lib/types/client/SideChatView.d.ts +16 -0
  27. package/lib/types/client/Sidebar.d.ts +6 -0
  28. package/lib/types/client/SubagentView.d.ts +16 -0
  29. package/lib/types/client/TabBar.d.ts +43 -0
  30. package/lib/types/client/TerminalView.d.ts +24 -0
  31. package/lib/types/client/TextEditor.d.ts +10 -0
  32. package/lib/types/client/TreePanel.d.ts +23 -0
  33. package/lib/types/client/UploadOverlay.d.ts +20 -0
  34. package/lib/types/client/add-plugin-modal.d.ts +15 -0
  35. package/lib/types/client/api.d.ts +268 -0
  36. package/lib/types/client/binary-download.d.ts +12 -0
  37. package/lib/types/client/breakpoints.d.ts +12 -0
  38. package/lib/types/client/browser.d.ts +50 -0
  39. package/lib/types/client/builtins/index.d.ts +18 -0
  40. package/lib/types/client/builtins/tabs.d.ts +11 -0
  41. package/lib/types/client/builtins/viewers.d.ts +3 -0
  42. package/lib/types/client/chunk-loader.d.ts +124 -0
  43. package/lib/types/client/chunks/editor.d.ts +9 -0
  44. package/lib/types/client/chunks/mermaid.d.ts +10 -0
  45. package/lib/types/client/chunks/terminal.d.ts +9 -0
  46. package/lib/types/client/cm-themes.d.ts +24 -0
  47. package/lib/types/client/conversation-draft.d.ts +14 -0
  48. package/lib/types/client/desktop-env.d.ts +35 -0
  49. package/lib/types/client/editable-state.d.ts +27 -0
  50. package/lib/types/client/editor-load.d.ts +66 -0
  51. package/lib/types/client/frame-batcher.d.ts +24 -0
  52. package/lib/types/client/icons.d.ts +57 -0
  53. package/lib/types/client/image-types.d.ts +3 -0
  54. package/lib/types/client/ime-guard.d.ts +36 -0
  55. package/lib/types/client/index.d.ts +19 -0
  56. package/lib/types/client/intercept.d.ts +33 -0
  57. package/lib/types/client/lang.d.ts +12 -0
  58. package/lib/types/client/lazy-chunk.d.ts +25 -0
  59. package/lib/types/client/link-intercept.d.ts +42 -0
  60. package/lib/types/client/locales.d.ts +366 -0
  61. package/lib/types/client/mermaid-blocks.d.ts +41 -0
  62. package/lib/types/client/mermaid-sanitize.d.ts +2 -0
  63. package/lib/types/client/mermaid.d.ts +12 -0
  64. package/lib/types/client/open-when-sized.d.ts +20 -0
  65. package/lib/types/client/open-with-settings.d.ts +4 -0
  66. package/lib/types/client/open-with.d.ts +87 -0
  67. package/lib/types/client/openpath-intercept.d.ts +41 -0
  68. package/lib/types/client/paths.d.ts +35 -0
  69. package/lib/types/client/pdf-types.d.ts +2 -0
  70. package/lib/types/client/plugin-settings.d.ts +8 -0
  71. package/lib/types/client/plugins-shared.d.ts +27 -0
  72. package/lib/types/client/plugins-tabs.d.ts +3 -0
  73. package/lib/types/client/plugins-viewers.d.ts +3 -0
  74. package/lib/types/client/prefs.d.ts +41 -0
  75. package/lib/types/client/produced-files.d.ts +23 -0
  76. package/lib/types/client/selection-payload.d.ts +27 -0
  77. package/lib/types/client/service.d.ts +440 -0
  78. package/lib/types/client/settings-nav-icon.d.ts +19 -0
  79. package/lib/types/client/shell-presets.d.ts +48 -0
  80. package/lib/types/client/sidechat-transcript.d.ts +103 -0
  81. package/lib/types/client/split-pane.d.ts +30 -0
  82. package/lib/types/client/state.d.ts +359 -0
  83. package/lib/types/client/subagent-detect.d.ts +54 -0
  84. package/lib/types/client/subagent-jobs.d.ts +63 -0
  85. package/lib/types/client/tab-content-memo.d.ts +33 -0
  86. package/lib/types/client/terminal-font.d.ts +20 -0
  87. package/lib/types/client/theme.d.ts +45 -0
  88. package/lib/types/client/titlebar-strip.d.ts +23 -0
  89. package/lib/types/client/upload.d.ts +69 -0
  90. package/lib/types/client/wco.d.ts +47 -0
  91. package/lib/types/config.d.ts +64 -0
  92. package/lib/types/context-types.d.ts +603 -0
  93. package/lib/types/fs-operations.d.ts +28 -0
  94. package/lib/types/fs-search.d.ts +24 -0
  95. package/lib/types/fs-tree.d.ts +51 -0
  96. package/lib/types/git.d.ts +82 -0
  97. package/lib/types/html-route.d.ts +59 -0
  98. package/lib/types/index.d.ts +47 -0
  99. package/lib/types/invariant.d.ts +15 -0
  100. package/lib/types/jobs-routes.d.ts +45 -0
  101. package/lib/types/open-external.d.ts +24 -0
  102. package/lib/types/prefs-shared.d.ts +225 -0
  103. package/lib/types/pty-deps.d.ts +78 -0
  104. package/lib/types/pty-manager.d.ts +168 -0
  105. package/lib/types/sidechat-core.d.ts +172 -0
  106. package/lib/types/sidechat-routes.d.ts +30 -0
  107. package/lib/types/subagent-activity.d.ts +44 -0
  108. package/lib/types/subagent-live-route.d.ts +44 -0
  109. package/lib/types/tools.d.ts +31 -0
  110. package/lib/types/trust-fence.d.ts +24 -0
  111. package/lib/types/wire.d.ts +38 -0
  112. package/package.json +173 -0
  113. package/scripts/install.ps1 +289 -0
  114. package/scripts/install.sh +287 -0
  115. package/src/agent-pty.ts +527 -0
  116. package/src/browser-probe.ts +26 -0
  117. package/src/bundle-route.ts +129 -0
  118. package/src/client/BrowserView.tsx +253 -0
  119. package/src/client/DiffTab.tsx +110 -0
  120. package/src/client/DiffView.tsx +305 -0
  121. package/src/client/EditorHost.tsx +525 -0
  122. package/src/client/FileTree.tsx +653 -0
  123. package/src/client/GitView.tsx +558 -0
  124. package/src/client/OrphanedTab.tsx +27 -0
  125. package/src/client/PdfView.tsx +110 -0
  126. package/src/client/RenderBoundary.tsx +49 -0
  127. package/src/client/SandboxStatusBar.tsx +60 -0
  128. package/src/client/SideCardSection.module.css +960 -0
  129. package/src/client/SideCardSection.tsx +1152 -0
  130. package/src/client/SideChatView.module.css +437 -0
  131. package/src/client/SideChatView.tsx +644 -0
  132. package/src/client/Sidebar.tsx +1148 -0
  133. package/src/client/SubagentView.module.css +547 -0
  134. package/src/client/SubagentView.tsx +883 -0
  135. package/src/client/TabBar.tsx +305 -0
  136. package/src/client/TerminalView.tsx +383 -0
  137. package/src/client/TextEditor.tsx +500 -0
  138. package/src/client/TreePanel.tsx +273 -0
  139. package/src/client/UploadOverlay.tsx +62 -0
  140. package/src/client/add-plugin-modal.tsx +199 -0
  141. package/src/client/api.ts +335 -0
  142. package/src/client/binary-download.tsx +23 -0
  143. package/src/client/breakpoints.ts +51 -0
  144. package/src/client/browser.ts +119 -0
  145. package/src/client/builtins/index.ts +37 -0
  146. package/src/client/builtins/tabs.tsx +331 -0
  147. package/src/client/builtins/viewers.tsx +125 -0
  148. package/src/client/chunk-loader.ts +351 -0
  149. package/src/client/chunks/editor.tsx +9 -0
  150. package/src/client/chunks/mermaid.tsx +10 -0
  151. package/src/client/chunks/terminal.tsx +9 -0
  152. package/src/client/cm-themes.ts +129 -0
  153. package/src/client/conversation-draft.ts +29 -0
  154. package/src/client/css-modules.d.ts +5 -0
  155. package/src/client/desktop-env.ts +72 -0
  156. package/src/client/editable-state.ts +70 -0
  157. package/src/client/editor-load.ts +92 -0
  158. package/src/client/frame-batcher.ts +56 -0
  159. package/src/client/icons.tsx +161 -0
  160. package/src/client/image-types.ts +8 -0
  161. package/src/client/ime-guard.ts +47 -0
  162. package/src/client/index.tsx +369 -0
  163. package/src/client/intercept.tsx +110 -0
  164. package/src/client/lang.ts +103 -0
  165. package/src/client/layout.css +94 -0
  166. package/src/client/lazy-chunk.tsx +89 -0
  167. package/src/client/link-intercept.ts +73 -0
  168. package/src/client/locales.ts +734 -0
  169. package/src/client/mermaid-blocks.ts +110 -0
  170. package/src/client/mermaid-sanitize.ts +93 -0
  171. package/src/client/mermaid.tsx +401 -0
  172. package/src/client/open-when-sized.ts +43 -0
  173. package/src/client/open-with-settings.tsx +135 -0
  174. package/src/client/open-with.ts +221 -0
  175. package/src/client/openpath-intercept.ts +62 -0
  176. package/src/client/paths.ts +47 -0
  177. package/src/client/pdf-types.ts +4 -0
  178. package/src/client/plugin-settings.ts +43 -0
  179. package/src/client/plugins-shared.ts +29 -0
  180. package/src/client/plugins-tabs.ts +87 -0
  181. package/src/client/plugins-viewers.ts +30 -0
  182. package/src/client/prefs.ts +207 -0
  183. package/src/client/produced-files.ts +91 -0
  184. package/src/client/selection-payload.ts +86 -0
  185. package/src/client/service.ts +838 -0
  186. package/src/client/settings-nav-icon.ts +45 -0
  187. package/src/client/shell-presets.ts +83 -0
  188. package/src/client/sidebar.module.css +2577 -0
  189. package/src/client/sidechat-transcript.ts +321 -0
  190. package/src/client/split-pane.tsx +322 -0
  191. package/src/client/state.ts +1175 -0
  192. package/src/client/subagent-detect.ts +137 -0
  193. package/src/client/subagent-jobs.ts +161 -0
  194. package/src/client/tab-content-memo.ts +46 -0
  195. package/src/client/terminal-font.ts +28 -0
  196. package/src/client/theme.ts +110 -0
  197. package/src/client/titlebar-strip.ts +37 -0
  198. package/src/client/upload.ts +187 -0
  199. package/src/client/wco.ts +120 -0
  200. package/src/config.ts +155 -0
  201. package/src/context-types.ts +610 -0
  202. package/src/fs-operations.ts +99 -0
  203. package/src/fs-search.ts +86 -0
  204. package/src/fs-tree.ts +158 -0
  205. package/src/git.ts +244 -0
  206. package/src/html-route.ts +106 -0
  207. package/src/index.ts +1132 -0
  208. package/src/invariant.ts +32 -0
  209. package/src/jobs-routes.ts +257 -0
  210. package/src/open-external.ts +90 -0
  211. package/src/prefs-shared.ts +268 -0
  212. package/src/pty-deps.ts +240 -0
  213. package/src/pty-manager.ts +376 -0
  214. package/src/sidechat-core.ts +468 -0
  215. package/src/sidechat-routes.ts +344 -0
  216. package/src/subagent-activity.ts +91 -0
  217. package/src/subagent-live-route.ts +93 -0
  218. package/src/tools.ts +479 -0
  219. package/src/trust-fence.ts +77 -0
  220. package/src/wire.ts +100 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Per-session write-permission state shared between the main client bundle
3
+ * and the lazy editor chunk.
4
+ *
5
+ * The sidebar is READ-ONLY by default: the host refuses every fs.write and
6
+ * git mutation unless the session has been explicitly switched to
7
+ * "editable" from the editor toolbar toggle (see TextEditor.tsx). The
8
+ * AUTHORITATIVE flag lives on the host (config.get / config.set-editable);
9
+ * this module is only the client mirror that keeps the main bundle and the
10
+ * editor chunk in lockstep — chunks bypass the module loader (see
11
+ * chunk-loader.ts), so they cannot import state from the main bundle and
12
+ * must rendezvous on a shared global instead, exactly like
13
+ * `globalThis.__dshChunks__`.
14
+ */
15
+
16
+ /** The shared global slot (one per page; survives chunk re-activation). */
17
+ const GLOBAL_KEY = '__dshSidebarEditable__'
18
+
19
+ interface EditableGlobal {
20
+ /** Per-session editable cache; an absent key means read-only. */
21
+ bySession: Map<string, boolean>
22
+ /** Change listeners per session (component subscriptions). */
23
+ listeners: Map<string, Set<(editable: boolean) => void>>
24
+ }
25
+
26
+ function globalState(): EditableGlobal {
27
+ const g = globalThis as unknown as Record<string, EditableGlobal | undefined>
28
+ let state = g[GLOBAL_KEY]
29
+ if (state === undefined) {
30
+ state = { bySession: new Map(), listeners: new Map() }
31
+ g[GLOBAL_KEY] = state
32
+ }
33
+ return state
34
+ }
35
+
36
+ /**
37
+ * Whether the session may write files. Defaults to false — every session
38
+ * starts read-only and only becomes writable through the toolbar toggle.
39
+ */
40
+ export function isSessionEditable(sessionId: string): boolean {
41
+ return globalState().bySession.get(sessionId) === true
42
+ }
43
+
44
+ /**
45
+ * Update the local editable flag and notify subscribers. The caller issues
46
+ * the host call (config.set-editable) separately; a host failure should
47
+ * flip the flag back through this same setter.
48
+ */
49
+ export function setSessionEditable(sessionId: string, editable: boolean): void {
50
+ const state = globalState()
51
+ state.bySession.set(sessionId, editable)
52
+ const set = state.listeners.get(sessionId)
53
+ if (set === undefined) return
54
+ for (const callback of set) callback(editable)
55
+ }
56
+
57
+ /** Subscribe to editable changes of one session; returns an unsubscribe fn. */
58
+ export function subscribeSessionEditable(sessionId: string, callback: (editable: boolean) => void): () => void {
59
+ const state = globalState()
60
+ let set = state.listeners.get(sessionId)
61
+ if (set === undefined) {
62
+ set = new Set()
63
+ state.listeners.set(sessionId, set)
64
+ }
65
+ set.add(callback)
66
+ return () => {
67
+ set!.delete(callback)
68
+ if (set!.size === 0) state.listeners.delete(sessionId)
69
+ }
70
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Pure editor-load planning: the decision logic the editor host needs to
3
+ * turn a matched file viewer + a host fs.read result into a render action.
4
+ * Kept dependency-free (no React, no fetch) so the strategy dispatch is
5
+ * unit-testable and the wire contract (head bytes, binary flag) is pinned.
6
+ *
7
+ * The host flow this module drives:
8
+ * 1. `matchFileViewer(path)` picks a viewer by extension/priority.
9
+ * 2. `planFirstMatch` dispatches its fetchStrategy.
10
+ * 3. An fsRead viewer fetches through the host; `planFsReadOutcome`
11
+ * decides what to do with the result — including the head-based
12
+ * re-match that lets a `detect` viewer claim a binary the extension
13
+ * match could not see (the builtin NUL probe on `binary-download`).
14
+ */
15
+ import type { FileViewerDescriptor } from './service.ts'
16
+
17
+ /** One host fs.read result (mirror of the wire; `head` present when binary). */
18
+ export interface FsReadOutcome {
19
+ binary: boolean
20
+ content: string
21
+ truncated: boolean
22
+ /** base64 of the first bytes (present on binary reads; sniffing material). */
23
+ head?: string
24
+ }
25
+
26
+ /** What the editor host should do next. */
27
+ export type EditorLoadAction =
28
+ /** No renderer: show the download UI. */
29
+ | { kind: 'binary' }
30
+ /** Render `viewer`'s component with the carried payload. */
31
+ | { kind: 'render'; viewer: FileViewerDescriptor; content?: string; truncated?: boolean; mediaUrl?: string; customData?: unknown }
32
+ /** Fetch the file through the host (fsRead strategy). */
33
+ | { kind: 'fetchFsRead'; viewer: FileViewerDescriptor }
34
+ /** Call the viewer's load() and render with its return value. */
35
+ | { kind: 'customLoad'; viewer: FileViewerDescriptor }
36
+
37
+ /** Decode the host's base64 head bytes into the sniffing buffer. */
38
+ export function decodeHead(headBase64: string): Uint8Array {
39
+ const binary = atob(headBase64)
40
+ const bytes = new Uint8Array(binary.length)
41
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i)
42
+ return bytes
43
+ }
44
+
45
+ /**
46
+ * Dispatch one matched viewer's fetchStrategy. A missing viewer or a
47
+ * `binary-download` strategy both mean "no client-side renderer" → the
48
+ * download UI. `mediaUrlOf` builds the media URL for `mediaUrl`/`none`
49
+ * strategies (pure, but scope-bound — injected by the host).
50
+ */
51
+ export function planFirstMatch(
52
+ viewer: FileViewerDescriptor | undefined,
53
+ mediaUrlOf: () => string,
54
+ ): EditorLoadAction {
55
+ if (viewer === undefined || viewer.fetchStrategy === 'binary-download') return { kind: 'binary' }
56
+ switch (viewer.fetchStrategy) {
57
+ case 'mediaUrl':
58
+ case 'none':
59
+ return { kind: 'render', viewer, mediaUrl: mediaUrlOf() }
60
+ case 'custom':
61
+ return { kind: 'customLoad', viewer }
62
+ case 'fsRead':
63
+ return { kind: 'fetchFsRead', viewer }
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Decide what an fsRead result means for the editor.
69
+ * - Text: the first match stands (content is valid for any fsRead viewer).
70
+ * - Binary: the host head bytes enable a re-match — a `detect` viewer (e.g.
71
+ * a plugin sniffing a binary format) may claim the file. `custom` viewers
72
+ * load their own bytes; `mediaUrl`/`none` viewers render the media route;
73
+ * an fsRead viewer or nothing cannot render binary → download UI.
74
+ */
75
+ export function planFsReadOutcome(
76
+ viewer: FileViewerDescriptor,
77
+ result: FsReadOutcome,
78
+ rematch: (head: Uint8Array) => FileViewerDescriptor | undefined,
79
+ mediaUrlOf: () => string,
80
+ ): EditorLoadAction {
81
+ if (!result.binary) {
82
+ return { kind: 'render', viewer, content: result.content, truncated: result.truncated }
83
+ }
84
+ const claimed = result.head === undefined ? undefined : rematch(decodeHead(result.head))
85
+ if (claimed !== undefined && claimed.fetchStrategy === 'custom') {
86
+ return { kind: 'customLoad', viewer: claimed }
87
+ }
88
+ if (claimed !== undefined && (claimed.fetchStrategy === 'mediaUrl' || claimed.fetchStrategy === 'none')) {
89
+ return { kind: 'render', viewer: claimed, mediaUrl: mediaUrlOf() }
90
+ }
91
+ return { kind: 'binary' }
92
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Per-frame task coalescing for pointer-driven interactions.
3
+ *
4
+ * Pointer streams fire several times faster than the display refresh, and
5
+ * applying every event (a React setState, a store reduce) re-renders whole
6
+ * trees at event cadence — the visible drag lag on slower CPUs (#315). This
7
+ * batcher keeps the LATEST scheduled task, runs it at most once per
8
+ * animation frame, and lets the caller flush synchronously on release (so
9
+ * the final pointer position is never lost to a cancelled frame).
10
+ *
11
+ * Callers compose the pending value in a ref before scheduling; the
12
+ * scheduled task reads that ref, so replacing the task on every event is
13
+ * cheap (one ref write + one rAF check).
14
+ */
15
+ export interface FrameBatcher {
16
+ /** Schedule (or replace) the per-frame task. Safe to call on every event. */
17
+ schedule(task: () => void): void
18
+ /** Cancel any pending frame and run the scheduled task synchronously
19
+ * (no-op when nothing is scheduled). */
20
+ flushNow(): void
21
+ /** Cancel any pending frame and drop the task without running it. */
22
+ dispose(): void
23
+ }
24
+
25
+ export function createFrameBatcher(): FrameBatcher {
26
+ let frame: number | null = null
27
+ let task: (() => void) | null = null
28
+
29
+ const run = (): void => {
30
+ frame = null
31
+ const current = task
32
+ task = null
33
+ current?.()
34
+ }
35
+
36
+ return {
37
+ schedule(next) {
38
+ task = next
39
+ if (frame === null) frame = requestAnimationFrame(run)
40
+ },
41
+ flushNow() {
42
+ if (frame !== null) {
43
+ cancelAnimationFrame(frame)
44
+ frame = null
45
+ }
46
+ run()
47
+ },
48
+ dispose() {
49
+ if (frame !== null) {
50
+ cancelAnimationFrame(frame)
51
+ frame = null
52
+ }
53
+ task = null
54
+ },
55
+ }
56
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Icons the sidebar needs beyond the primitives set: a terminal glyph (the
3
+ * icon library has none), a diff glyph, and the two panel-toggle glyphs for
4
+ * the top-right cluster. Per-tab icons live on the tab descriptors
5
+ * (`descriptor.icon`), not in a type-keyed switch — the icon mapping was
6
+ * registry-ized with the tab types.
7
+ */
8
+ import type { IconProps } from '@deepseek-ai/dsh-client-ui-primitives'
9
+
10
+ /**
11
+ * Right-panel toggle glyph (the "侧拉" button): a frame with a filled strip
12
+ * along its RIGHT edge, in the app's outline style (1.5px stroke,
13
+ * currentColor).
14
+ */
15
+ export const IconPanelRightOutline16 = ({ size = 16, className }: IconProps) => (
16
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
17
+ <rect x="1.5" y="2" width="13" height="12" rx="2.5" stroke="currentColor" strokeWidth="1.5" />
18
+ <rect x="10.5" y="3.25" width="2.75" height="9.5" rx="1" fill="currentColor" stroke="none" />
19
+ </svg>
20
+ )
21
+
22
+ /**
23
+ * Terminal glyph in the app's outline style (1.5px stroke, currentColor):
24
+ * a rounded frame with a prompt chevron and underscore cursor.
25
+ */
26
+ export const IconTerminalOutline16 = ({ size = 16, className }: IconProps) => (
27
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
28
+ <rect x="1.5" y="2.5" width="13" height="11" rx="2" stroke="currentColor" strokeWidth="1.5" />
29
+ <path d="M4.5 6.25 6.75 8 4.5 9.75" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
30
+ <path d="M8.5 10.4h3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
31
+ </svg>
32
+ )
33
+
34
+ /** Diff glyph in the app's outline style: a file frame with a plus and a minus row. */
35
+ export const IconDiffOutline16 = ({ size = 16, className }: IconProps) => (
36
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
37
+ <rect x="1.5" y="1.5" width="13" height="13" rx="2.5" stroke="currentColor" strokeWidth="1.5" />
38
+ <path d="M4 5h3M5.5 3.5v3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
39
+ <path d="M9.5 12.5h2.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
40
+ </svg>
41
+ )
42
+
43
+ /**
44
+ * Stop glyph for the background-job kill button: a filled square in the
45
+ * app's outline scale (16), the universal "halt this work" mark.
46
+ */
47
+ export const IconStopOutline16 = ({ size = 16, className }: IconProps) => (
48
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
49
+ <rect x="4" y="4" width="8" height="8" rx="1.5" fill="currentColor" stroke="none" />
50
+ </svg>
51
+ )
52
+
53
+ /** Upload glyph in the app's outline style: an arrow rising into a tray
54
+ * (the file-manager "upload into the workspace" action). */
55
+ export const IconUploadOutline16 = ({ size = 16, className }: IconProps) => (
56
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
57
+ <path d="M8 10V2.75M4.75 5.5 8 2.25 11.25 5.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
58
+ <path d="M2.75 10.5v2.25A1.25 1.25 0 0 0 4 14h8a1.25 1.25 0 0 0 1.25-1.25V10.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
59
+ </svg>
60
+ )
61
+
62
+ // ── File-viewer inventory glyphs (Side card settings page) ────────────────
63
+
64
+ /** Image viewer glyph: a picture frame with a sun and a mountain. */
65
+ export const IconImageOutline16 = ({ size = 16, className }: IconProps) => (
66
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
67
+ <rect x="1.5" y="2.5" width="13" height="11" rx="2" stroke="currentColor" strokeWidth="1.5" />
68
+ <circle cx="5.5" cy="6" r="1.2" stroke="currentColor" strokeWidth="1.5" />
69
+ <path d="m3.5 12 3-3 2.25 2.25L11.5 8.5 13 10.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
70
+ </svg>
71
+ )
72
+
73
+ /** PDF viewer glyph: a document frame with the "PDF" label. */
74
+ export const IconPdfOutline16 = ({ size = 16, className }: IconProps) => (
75
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
76
+ <path d="M3.5 1.5h6.5L13.5 5v9.5h-10z" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
77
+ <path d="M9.5 1.5V5h4" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
78
+ <path d="M5 13.5v-3h1.4c.75 0 1.1.32 1.1.85 0 .54-.35.85-1.1.85H5.3" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
79
+ <path d="M8.3 13.5v-3h1.05c.8 0 1.35.5 1.35 1.5s-.55 1.5-1.35 1.5z" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
80
+ <path d="M11.6 13.5v-3h1.3" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" />
81
+ </svg>
82
+ )
83
+
84
+ /** Markdown viewer glyph: the classic "M with a down arrow" badge. */
85
+ export const IconMarkdownOutline16 = ({ size = 16, className }: IconProps) => (
86
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
87
+ <rect x="1.5" y="2.5" width="13" height="11" rx="2" stroke="currentColor" strokeWidth="1.5" />
88
+ <path d="M4 10.5V5.5l2 2.5 2-2.5v5M9.5 10.5v-5l2 2.5 2-2.5v5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
89
+ </svg>
90
+ )
91
+
92
+ /** HTML viewer glyph: a document frame with a "‹/›" tag pair. */
93
+ export const IconHtmlOutline16 = ({ size = 16, className }: IconProps) => (
94
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
95
+ <path d="M3.5 1.5h6.5L13.5 5v9.5h-10z" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
96
+ <path d="M9.5 1.5V5h4" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
97
+ <path d="M5.6 13.2 4.2 10l1.4-3.2M7.4 6.8 8.8 10l-1.4 3.2" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
98
+ </svg>
99
+ )
100
+
101
+ /** Browser tab glyph: a globe with meridians. */
102
+ export const IconGlobeOutline16 = ({ size = 16, className }: IconProps) => (
103
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
104
+ <circle cx="8" cy="8" r="6.5" stroke="currentColor" strokeWidth="1.5" />
105
+ <ellipse cx="8" cy="8" rx="2.8" ry="6.5" stroke="currentColor" strokeWidth="1.5" />
106
+ <path d="M1.5 8h13M8 1.5c-2.4 1.8-2.4 11.2 0 13M8 1.5c2.4 1.8 2.4 11.2 0 13" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
107
+ </svg>
108
+ )
109
+
110
+ /** History glyph (thread switcher): a clock with a counterclockwise arrow,
111
+ * in the app's outline style — the "past conversations" mark. */
112
+ export const IconHistoryOutline16 = ({ size = 16, className }: IconProps) => (
113
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
114
+ <path d="M2.4 6.8A5.6 5.6 0 1 1 2.4 9.2" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
115
+ <path d="M2.2 3.4v3.4h3.4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
116
+ <path d="M8 5.4V8l1.9 1.2" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
117
+ </svg>
118
+ )
119
+
120
+ /** Save glyph (save-as-new-session): the classic floppy disk, in the app's
121
+ * outline style. */
122
+ export const IconSaveOutline16 = ({ size = 16, className }: IconProps) => (
123
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
124
+ <path d="M4.2 14.5h7.6a1.2 1.2 0 0 0 1.2-1.2V4.9L10.6 2.5H4.2A1.2 1.2 0 0 0 3 3.7v9.6a1.2 1.2 0 0 0 1.2 1.2z" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
125
+ <path d="M10 2.5v2.6H5.6V2.5" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
126
+ <path d="M5.4 14.5v-4.2h5.2v4.2" stroke="currentColor" strokeWidth="1.5" strokeLinejoin="round" />
127
+ </svg>
128
+ )
129
+
130
+ /**
131
+ * Visual Studio Code brand mark for the file-tree "open with" menu. The
132
+ * path is the Simple Icons `visualstudiocode` glyph (CC0 1.0,
133
+ * simple-icons@11.0.0 — later releases dropped it over Microsoft's brand
134
+ * policy, so it is inlined here rather than pulled from react-icons),
135
+ * rendered monochrome via currentColor to follow the active skin.
136
+ */
137
+ export const IconVscode16 = ({ size = 16, className }: IconProps) => (
138
+ <svg width={size} height={size} className={className} viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
139
+ <path d="M23.15 2.587L18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z" />
140
+ </svg>
141
+ )
142
+
143
+ // ── Write-permission toggle glyphs (read-only by default) ──────────────────
144
+
145
+ /** Closed padlock: the session is READ-ONLY — click to enable editing. */
146
+ export const IconLockClosedOutline16 = ({ size = 16, className }: IconProps) => (
147
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
148
+ <rect x="3" y="7" width="10" height="6.5" rx="1.5" stroke="currentColor" strokeWidth="1.5" />
149
+ <path d="M5.25 7V5.25a2.75 2.75 0 0 1 5.5 0V7" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
150
+ <circle cx="8" cy="10" r="1.1" fill="currentColor" stroke="none" />
151
+ </svg>
152
+ )
153
+
154
+ /** Open padlock: the session is EDITABLE — click to go back to read-only. */
155
+ export const IconLockOpenOutline16 = ({ size = 16, className }: IconProps) => (
156
+ <svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
157
+ <rect x="3" y="7" width="10" height="6.5" rx="1.5" stroke="currentColor" strokeWidth="1.5" />
158
+ <path d="M5.25 7V5.25a2.75 2.75 0 0 1 4.95-1.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
159
+ <circle cx="8" cy="10" r="1.1" fill="currentColor" stroke="none" />
160
+ </svg>
161
+ )
@@ -0,0 +1,8 @@
1
+ /** Image extension dispatch, separate from React for unit testing. */
2
+ export const IMAGE_EXTENSIONS = [
3
+ '.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.bmp', '.ico', '.avif',
4
+ ] as readonly string[]
5
+
6
+ export function isImageExt(ext: string): boolean {
7
+ return IMAGE_EXTENSIONS.includes(ext)
8
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * IME-composition key guard.
3
+ *
4
+ * While a Chinese/Japanese/Korean input method is composing (the user is
5
+ * picking a candidate from the IME window), every pressed key BELONGS to the
6
+ * input method: arrows move the candidate highlight, Enter/Space confirm the
7
+ * composition, Escape cancels it. Page code must not process those keys —
8
+ * a component that does (a number stepper calling preventDefault() on
9
+ * ArrowUp/ArrowDown, a submit handler reacting to Enter, ...) silently
10
+ * breaks the IME: candidates stop responding, the composition gets torn
11
+ * apart, and only bare letters come out.
12
+ *
13
+ * This guard enforces that rule at the document boundary: a capture-phase
14
+ * keydown/keyup listener that stops the event from propagating further
15
+ * whenever a composition is in progress. Because it runs in the capture
16
+ * phase on `document` — the outermost node — it fires BEFORE React's
17
+ * delegated handlers (attached at the root container) and before any native
18
+ * target/bubble listener, so an inlined third-party component (e.g. the
19
+ * Univer office UI bundled into this plugin) can never intercept
20
+ * composition keys. The browser's native IME processing is untouched:
21
+ * stopPropagation only silences page JS, not the default action.
22
+ *
23
+ * The composition signal follows the DSH core convention (InputBar's IME
24
+ * guard, issue #535): `isComposing` for modern engines, keyCode 229 as the
25
+ * legacy signal engines emit without isComposing.
26
+ */
27
+
28
+ /** The pure decision: is this keyboard event part of an IME composition? */
29
+ export function isImeComposition(event: { isComposing: boolean; keyCode: number }): boolean {
30
+ return event.isComposing || event.keyCode === 229
31
+ }
32
+
33
+ /**
34
+ * Register the document-level capture guard. Returns the disposer
35
+ * (HMR-safe; call through `ctx.effect`).
36
+ */
37
+ export function registerImeGuard(): () => void {
38
+ const onKey = (event: KeyboardEvent): void => {
39
+ if (isImeComposition(event)) event.stopPropagation()
40
+ }
41
+ document.addEventListener('keydown', onKey, true)
42
+ document.addEventListener('keyup', onKey, true)
43
+ return () => {
44
+ document.removeEventListener('keydown', onKey, true)
45
+ document.removeEventListener('keyup', onKey, true)
46
+ }
47
+ }