@ecsplico/editor 2.0.0 → 2.1.1

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 (278) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +4 -3
  3. package/dist/adapters/anchors.d.ts +80 -0
  4. package/dist/adapters/anchors.js +270 -0
  5. package/dist/adapters/anchors.js.map +1 -0
  6. package/dist/adapters/comments/client.d.ts +64 -0
  7. package/dist/adapters/comments/client.js +536 -0
  8. package/dist/adapters/comments/client.js.map +1 -0
  9. package/dist/adapters/comments/drizzle.d.ts +430 -0
  10. package/dist/adapters/comments/drizzle.js +236 -0
  11. package/dist/adapters/comments/drizzle.js.map +1 -0
  12. package/dist/adapters/comments/errors.d.ts +9 -0
  13. package/dist/adapters/comments/errors.js +19 -0
  14. package/dist/adapters/comments/errors.js.map +1 -0
  15. package/dist/adapters/comments/json.d.ts +19 -0
  16. package/dist/adapters/comments/json.js +94 -0
  17. package/dist/adapters/comments/json.js.map +1 -0
  18. package/dist/adapters/comments/record.js +78 -0
  19. package/dist/adapters/comments/record.js.map +1 -0
  20. package/dist/adapters/comments/server.d.ts +23 -0
  21. package/dist/adapters/comments/server.js +102 -0
  22. package/dist/adapters/comments/server.js.map +1 -0
  23. package/dist/adapters/comments/types.d.ts +109 -0
  24. package/dist/adapters/documents.js +108 -0
  25. package/dist/adapters/documents.js.map +1 -0
  26. package/dist/adapters/drizzle.d.ts +293 -0
  27. package/dist/adapters/drizzle.js +157 -0
  28. package/dist/adapters/drizzle.js.map +1 -0
  29. package/dist/adapters/files.js +30 -0
  30. package/dist/adapters/files.js.map +1 -0
  31. package/dist/adapters/forge.d.ts +58 -0
  32. package/dist/adapters/forge.js +91 -0
  33. package/dist/adapters/forge.js.map +1 -0
  34. package/dist/adapters/index.d.ts +13 -0
  35. package/dist/adapters/index.js +0 -0
  36. package/dist/adapters/json.d.ts +24 -0
  37. package/dist/adapters/json.js +128 -0
  38. package/dist/adapters/json.js.map +1 -0
  39. package/dist/adapters/nitro.d.ts +61 -0
  40. package/dist/adapters/nitro.js +212 -0
  41. package/dist/adapters/nitro.js.map +1 -0
  42. package/dist/adapters/routes.js +16 -0
  43. package/dist/adapters/routes.js.map +1 -0
  44. package/dist/adapters/shared.d.ts +37 -0
  45. package/dist/adapters/shared.js +32 -0
  46. package/dist/adapters/shared.js.map +1 -0
  47. package/dist/adapters/vue.d.ts +67 -0
  48. package/dist/adapters/vue.js +187 -0
  49. package/dist/adapters/vue.js.map +1 -0
  50. package/dist/code/CodeSurface.vue.d.ts +3 -3
  51. package/dist/core/doc-anchors.d.ts +5 -0
  52. package/dist/core/doc-anchors.js +4 -3
  53. package/dist/core/doc-anchors.js.map +1 -1
  54. package/dist/core/editor-ui.js +57 -1
  55. package/dist/core/editor-ui.js.map +1 -1
  56. package/dist/core/surface-session.js +139 -0
  57. package/dist/core/surface-session.js.map +1 -0
  58. package/dist/document/DocumentSurface.d.ts +1 -1
  59. package/dist/document/DocumentSurface.vue.d.ts +3 -3
  60. package/dist/features/ai/agent-op-review.d.ts +2 -2
  61. package/dist/features/ai/agent-op-review.js.map +1 -1
  62. package/dist/features/ai/agent-ops.d.ts +1 -1
  63. package/dist/features/ai/agent-ops.js +9 -6
  64. package/dist/features/ai/agent-ops.js.map +1 -1
  65. package/dist/features/ai/ai-apply.d.ts +1 -1
  66. package/dist/features/ai/ai-review.js +10 -1
  67. package/dist/features/ai/ai-review.js.map +1 -1
  68. package/dist/features/ai/components/AiSelectionMenu.js +78 -252
  69. package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
  70. package/dist/features/ai/components/AiSelectionMenu.vue.d.ts +2 -1
  71. package/dist/features/ai/components/AiSidebar.js +2 -2
  72. package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
  73. package/dist/features/ai/components/DocumentChatOps.js +1 -0
  74. package/dist/features/ai/components/DocumentChatOps.js.map +1 -1
  75. package/dist/features/ai/components/ModelQuickSwitch.js +1 -1
  76. package/dist/features/ai/components/SettingsAiTab.js +1 -1
  77. package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
  78. package/dist/features/ai/document-chat.d.ts +3 -2
  79. package/dist/features/ai/document-chat.js +16 -19
  80. package/dist/features/ai/document-chat.js.map +1 -1
  81. package/dist/features/ai/index.d.ts +5 -3
  82. package/dist/features/ai/index.js +4 -2
  83. package/dist/features/ai/protocol/agent-ops.d.ts +22 -17
  84. package/dist/features/ai/protocol/agent-ops.js +65 -39
  85. package/dist/features/ai/protocol/agent-ops.js.map +1 -1
  86. package/dist/features/ai/protocol/block-lines.d.ts +89 -0
  87. package/dist/features/ai/protocol/block-lines.js +177 -0
  88. package/dist/features/ai/protocol/block-lines.js.map +1 -0
  89. package/dist/features/ai/protocol/chat.js +10 -10
  90. package/dist/features/ai/protocol/chat.js.map +1 -1
  91. package/dist/features/ai/protocol/index.d.ts +3 -2
  92. package/dist/features/ai/protocol/index.js +3 -2
  93. package/dist/features/ai/react/AiSelectionMenu.d.ts +3 -2
  94. package/dist/features/ai/react/AiSelectionMenu.js +64 -255
  95. package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
  96. package/dist/features/ai/react/AiSidebar.js +2 -2
  97. package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
  98. package/dist/features/ai/react/DocumentChatOps.js +1 -0
  99. package/dist/features/ai/react/DocumentChatOps.js.map +1 -1
  100. package/dist/features/ai/react/ModelQuickSwitch.js +1 -1
  101. package/dist/features/ai/react/SettingsAiTab.js +1 -1
  102. package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
  103. package/dist/features/ai/react/use-ai-selection-flow.js +34 -0
  104. package/dist/features/ai/react/use-ai-selection-flow.js.map +1 -0
  105. package/dist/features/ai/selection-flow.d.ts +133 -0
  106. package/dist/features/ai/selection-flow.js +283 -0
  107. package/dist/features/ai/selection-flow.js.map +1 -0
  108. package/dist/features/ai/use-ai-assistant.d.ts +1 -1
  109. package/dist/features/ai/use-document-chat.d.ts +1 -1
  110. package/dist/features/authors/components/AuthorColorsPanel.js +1 -1
  111. package/dist/features/authors/feature.js +1 -1
  112. package/dist/features/authors/index.js +1 -1
  113. package/dist/features/authors/react/AuthorColorsPanel.js +1 -1
  114. package/dist/features/comments/components/CommentBubble.js +1 -1
  115. package/dist/features/comments/components/CommentComposer.js +1 -1
  116. package/dist/features/comments/components/CommentThreadView.js +5 -2
  117. package/dist/features/comments/components/CommentThreadView.js.map +1 -1
  118. package/dist/features/comments/components/CommentsMargin.js +108 -230
  119. package/dist/features/comments/components/CommentsMargin.js.map +1 -1
  120. package/dist/features/comments/components/CommentsOverlay.js +1 -1
  121. package/dist/features/comments/feature.js +1 -1
  122. package/dist/features/comments/index.d.ts +4 -2
  123. package/dist/features/comments/index.js +4 -2
  124. package/dist/features/comments/margin-controller.d.ts +44 -0
  125. package/dist/features/comments/margin-controller.js +132 -0
  126. package/dist/features/comments/margin-controller.js.map +1 -0
  127. package/dist/features/comments/margin-layout.d.ts +55 -0
  128. package/dist/features/comments/margin-layout.js +79 -0
  129. package/dist/features/comments/margin-layout.js.map +1 -0
  130. package/dist/features/comments/model.d.ts +8 -0
  131. package/dist/features/comments/model.js +60 -34
  132. package/dist/features/comments/model.js.map +1 -1
  133. package/dist/features/comments/react/CommentBubble.js +1 -1
  134. package/dist/features/comments/react/CommentComposer.js +1 -1
  135. package/dist/features/comments/react/CommentThreadView.js +2 -2
  136. package/dist/features/comments/react/CommentThreadView.js.map +1 -1
  137. package/dist/features/comments/react/CommentsMargin.d.ts +0 -5
  138. package/dist/features/comments/react/CommentsMargin.js +85 -251
  139. package/dist/features/comments/react/CommentsMargin.js.map +1 -1
  140. package/dist/features/comments/react/CommentsOverlay.js +1 -1
  141. package/dist/features/comments/react/use-comments.js +1 -1
  142. package/dist/features/comments/react/use-margin-layout.js +45 -0
  143. package/dist/features/comments/react/use-margin-layout.js.map +1 -0
  144. package/dist/features/comments/react.d.ts +1 -1
  145. package/dist/features/comments/schema.d.ts +23 -0
  146. package/dist/features/comments/schema.js.map +1 -1
  147. package/dist/features/comments/snapshot-equality.js +40 -0
  148. package/dist/features/comments/snapshot-equality.js.map +1 -0
  149. package/dist/features/comments/thread-anchor.js +33 -0
  150. package/dist/features/comments/thread-anchor.js.map +1 -0
  151. package/dist/features/comments/use-comments.d.ts +3 -1
  152. package/dist/features/comments/use-comments.js +1 -0
  153. package/dist/features/comments/use-comments.js.map +1 -1
  154. package/dist/features/comments/use-margin-layout.js +29 -0
  155. package/dist/features/comments/use-margin-layout.js.map +1 -0
  156. package/dist/features/comments/vue.d.ts +1 -1
  157. package/dist/features/composites/accepted-children.d.ts +21 -0
  158. package/dist/features/composites/accepted-children.js +126 -0
  159. package/dist/features/composites/accepted-children.js.map +1 -0
  160. package/dist/features/composites/components/CompositeDetails.js +1 -1
  161. package/dist/features/composites/components/CompositeInspector.js +38 -164
  162. package/dist/features/composites/components/CompositeInspector.js.map +1 -1
  163. package/dist/features/composites/feature.d.ts +2 -1
  164. package/dist/features/composites/feature.js +8 -2
  165. package/dist/features/composites/feature.js.map +1 -1
  166. package/dist/features/composites/index.d.ts +5 -3
  167. package/dist/features/composites/index.js +8 -6
  168. package/dist/features/composites/inspector-model.d.ts +97 -0
  169. package/dist/features/composites/inspector-model.js +255 -0
  170. package/dist/features/composites/inspector-model.js.map +1 -0
  171. package/dist/features/composites/kinds.d.ts +11 -0
  172. package/dist/features/composites/kinds.js +17 -2
  173. package/dist/features/composites/kinds.js.map +1 -1
  174. package/dist/features/composites/outline.d.ts +18 -5
  175. package/dist/features/composites/outline.js +44 -12
  176. package/dist/features/composites/outline.js.map +1 -1
  177. package/dist/features/composites/react/CompositeDetails.js +1 -1
  178. package/dist/features/composites/react/CompositeInspector.d.ts +1 -1
  179. package/dist/features/composites/react/CompositeInspector.js +21 -180
  180. package/dist/features/composites/react/CompositeInspector.js.map +1 -1
  181. package/dist/features/composites/react/CompositeStatusMenu.d.ts +1 -1
  182. package/dist/features/composites/react/CompositeView.js +1 -1
  183. package/dist/features/composites/react/use-composite-inspector.js +29 -0
  184. package/dist/features/composites/react/use-composite-inspector.js.map +1 -0
  185. package/dist/features/composites/schema.d.ts +2 -2
  186. package/dist/features/composites/schema.js +2 -2
  187. package/dist/features/composites/schema.js.map +1 -1
  188. package/dist/features/composites/use-composite-anchors.d.ts +1 -1
  189. package/dist/features/composites/use-composite-inspector.js +41 -0
  190. package/dist/features/composites/use-composite-inspector.js.map +1 -0
  191. package/dist/features/composites/use-composite-rows.d.ts +1 -1
  192. package/dist/features/media/index.d.ts +1 -1
  193. package/dist/features/media/vue.d.ts +1 -1
  194. package/dist/features/mentions/index.d.ts +3 -3
  195. package/dist/features/mentions/index.js +3 -3
  196. package/dist/features/mentions/schema.d.ts +1 -1
  197. package/dist/features/mentions/schema.js +1 -1
  198. package/dist/features/themes/index.d.ts +1 -1
  199. package/dist/features/themes/index.js +1 -1
  200. package/dist/features/transcript/feature.js +1 -1
  201. package/dist/features/transcript/index.js +2 -2
  202. package/dist/i18n/de/ai.js +2 -0
  203. package/dist/i18n/de/ai.js.map +1 -1
  204. package/dist/i18n/de/composites.js +8 -1
  205. package/dist/i18n/de/composites.js.map +1 -1
  206. package/dist/i18n/de/tree.js +14 -1
  207. package/dist/i18n/de/tree.js.map +1 -1
  208. package/dist/i18n/de.d.ts +22 -0
  209. package/dist/i18n/en/ai.js +2 -0
  210. package/dist/i18n/en/ai.js.map +1 -1
  211. package/dist/i18n/en/composites.js +8 -1
  212. package/dist/i18n/en/composites.js.map +1 -1
  213. package/dist/i18n/en/tree.js +14 -1
  214. package/dist/i18n/en/tree.js.map +1 -1
  215. package/dist/integrations/composite-ai/composite-fill.js +2 -2
  216. package/dist/integrations/composite-ai/composite-summary.js +2 -2
  217. package/dist/integrations/composite-ai/index.js +2 -2
  218. package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
  219. package/dist/markdown/MarkdownSurface.d.ts +1 -1
  220. package/dist/markdown/MarkdownSurface.js +2 -2
  221. package/dist/markdown/MarkdownSurface.vue.d.ts +9 -9
  222. package/dist/markdown/index.d.ts +2 -2
  223. package/dist/nuxt/runtime/server/routes/collab/[id].js +1 -1
  224. package/dist/react/EditorSurface.d.ts +1 -1
  225. package/dist/react/EditorSurface.js +43 -149
  226. package/dist/react/EditorSurface.js.map +1 -1
  227. package/dist/react/EditorToolbar.js +3 -40
  228. package/dist/react/EditorToolbar.js.map +1 -1
  229. package/dist/schema/advanced.js +2 -2
  230. package/dist/server/adapter.d.ts +2 -2
  231. package/dist/server/collab.js +1 -1
  232. package/dist/server/handlers/ai.d.ts +4 -2
  233. package/dist/server/handlers/ai.js +11 -9
  234. package/dist/server/handlers/ai.js.map +1 -1
  235. package/dist/server/handlers/context.d.ts +1 -1
  236. package/dist/server/handlers/editor.d.ts +1 -1
  237. package/dist/server/handlers/editor.js +2 -2
  238. package/dist/server/host.d.ts +1 -1
  239. package/dist/server/index.d.ts +2 -2
  240. package/dist/settings/index.d.ts +1 -1
  241. package/dist/styles/tokens.css +1 -1
  242. package/dist/tree/DocumentTree.js +159 -387
  243. package/dist/tree/DocumentTree.js.map +1 -1
  244. package/dist/tree/drop-rules.js +37 -0
  245. package/dist/tree/drop-rules.js.map +1 -0
  246. package/dist/tree/react/DocumentTree.d.ts +2 -2
  247. package/dist/tree/react/DocumentTree.js +135 -397
  248. package/dist/tree/react/DocumentTree.js.map +1 -1
  249. package/dist/tree/react/use-tree-model.js +129 -0
  250. package/dist/tree/react/use-tree-model.js.map +1 -0
  251. package/dist/tree/react.js +1 -1
  252. package/dist/tree/tree-host.js +100 -0
  253. package/dist/tree/tree-host.js.map +1 -0
  254. package/dist/tree/tree-items.js +22 -43
  255. package/dist/tree/tree-items.js.map +1 -1
  256. package/dist/tree/tree-model.js +580 -0
  257. package/dist/tree/tree-model.js.map +1 -0
  258. package/dist/tree/tree-rows.js +77 -0
  259. package/dist/tree/tree-rows.js.map +1 -0
  260. package/dist/tree/use-tree-model.js +94 -0
  261. package/dist/tree/use-tree-model.js.map +1 -0
  262. package/dist/tree/vue.js +1 -1
  263. package/dist/vue/AdvancedEditor.vue.d.ts +6 -6
  264. package/dist/vue/SimpleEditor.vue.d.ts +2 -2
  265. package/dist/vue/components/EditorSurface.js +27 -87
  266. package/dist/vue/components/EditorSurface.js.map +1 -1
  267. package/dist/vue/components/EditorSurface.vue.d.ts +6 -6
  268. package/dist/vue/components/EditorToolbar.js +7 -44
  269. package/dist/vue/components/EditorToolbar.js.map +1 -1
  270. package/dist/vue/context.d.ts +1 -1
  271. package/dist/vue/context.js +1 -1
  272. package/dist/vue/context.js.map +1 -1
  273. package/dist/vue/use-editor-ui.d.ts +1 -1
  274. package/dist/vue/use-notifier.d.ts +1 -1
  275. package/docs/integration.md +97 -4
  276. package/docs/llms.md +9 -1
  277. package/docs/usage.md +10 -5
  278. package/package.json +67 -5
@@ -1,453 +1,162 @@
1
1
  import { useEditorI18n } from "../../react/use-editor-i18n.js";
2
2
  import { cn } from "../../react/ui/utils.js";
3
3
  import { Icon } from "../../react/Icon.js";
4
- import { useStore } from "../../react/use-store.js";
5
- import { useEditorSettings } from "../../react/settings.js";
6
4
  import { Button } from "../../react/ui/button.js";
7
- import { revealBlockInEditor } from "../../outline/heading-outline.js";
8
- import { compositeJSON, compositeKindsOf } from "../../features/composites/nodes.js";
9
- import { moveBlockInto } from "../../features/composites/outline.js";
10
- import { pickFiles } from "../../features/media/upload.js";
11
- import { useNotifier } from "../../react/use-notifier.js";
12
5
  import { Input } from "../../react/ui/input.js";
13
6
  import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "../../react/ui/dropdown-menu.js";
14
- import { useFileImport } from "../../features/media/react.js";
15
- import { remapFolderPath } from "../folder-paths.js";
16
7
  import { Collapsible, CollapsibleContent } from "../../react/ui/collapsible.js";
17
- import { DRAG_MIME, EMPTY_OUTLINE, VIRTUAL_ROOT, ancestorKeys, applyNodeFilters, canDrop, collectFolderPaths, collectKeys, compositeInsertPos, dragPayloadOf, filterKinds, folderItem, folderNamesIn, holdsComposites, isDraggable, joinPath, outlineItems, parentPath, readOutline, virtualRootItem } from "../tree-items.js";
18
- import { useTreeView } from "./use-tree-view.js";
19
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
8
+ import { DRAG_MIME } from "../tree-items.js";
9
+ import { rowOf } from "../tree-model.js";
10
+ import { useTreeModel } from "./use-tree-model.js";
11
+ import { useRef } from "react";
20
12
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
21
- import { toast } from "sonner";
22
13
  //#region src/tree/react/DocumentTree.tsx
23
- const OUTLINE_DEBOUNCE_MS = 150;
24
- const EMPTY_DOCUMENTS = [];
25
- const EMPTY_KINDS = [];
26
14
  const REVEAL_ON_HOVER = "opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 data-[state=open]:opacity-100";
27
15
  const isOutlineItem = (item) => item.type === "composite" || item.type === "heading";
16
+ /** The label with the part the title filter matched highlighted. */
17
+ function Label({ label, match }) {
18
+ if (!match) return /* @__PURE__ */ jsx(Fragment$1, { children: label });
19
+ const [at, length] = match;
20
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
21
+ label.slice(0, at),
22
+ /* @__PURE__ */ jsx("mark", {
23
+ className: "rounded-sm bg-primary/20 text-inherit",
24
+ children: label.slice(at, at + length)
25
+ }),
26
+ label.slice(at + length)
27
+ ] });
28
+ }
28
29
  /** The file browser of a document store: folders and documents with their drag and drop, the open document's outline below it. */
29
- function DocumentTree({ store, editor, selectedFolder: controlledFolder, onSelectedFolderChange, documentActions, className }) {
30
+ function DocumentTree({ store, editor, selectedFolder, onSelectedFolderChange, documentActions, className }) {
30
31
  const { t } = useEditorI18n();
31
- const { nodeFilters, expanded, model } = useTreeView();
32
- const { settings } = useEditorSettings();
33
- const { importFiles } = useFileImport(editor);
34
- const notify = useNotifier(editor);
35
- const tree = useStore(store.tree);
36
- const documents = useStore(store.documents) ?? EMPTY_DOCUMENTS;
37
- const activeId = useStore(store.activeId);
38
- const structure = settings.blocks.structure;
39
- const virtual = settings.blocks.treeRoot === "file" && structure === "composite" && !!editor;
40
- const treeElement = useRef(null);
41
- const [ownFolder, setOwnFolder] = useState("");
42
- const selectedFolder = controlledFolder ?? ownFolder;
43
- const selectedFolderRef = useRef(selectedFolder);
44
- selectedFolderRef.current = selectedFolder;
45
- const onSelectedFolderChangeRef = useRef(onSelectedFolderChange);
46
- onSelectedFolderChangeRef.current = onSelectedFolderChange;
47
- const setSelectedFolder = useCallback((folder) => {
48
- selectedFolderRef.current = folder;
49
- setOwnFolder(folder);
50
- onSelectedFolderChangeRef.current?.(folder);
51
- }, []);
52
- /** Virtual mode: block ID of the selected `folder`/`document` composite; `null` = the document itself. */
53
- const [virtualTarget, setVirtualTarget] = useState(null);
54
- const [collapsedOutline, setCollapsedOutline] = useState([]);
55
- const [dropKey, setDropKey] = useState(null);
56
- const dragging = useRef(null);
57
- const [removing, setRemoving] = useState(null);
58
- const [renaming, setRenamingState] = useState(null);
59
- const renamingRef = useRef(null);
60
- const setRenaming = (next) => {
61
- renamingRef.current = next;
62
- setRenamingState(next);
63
- };
64
- const [creatingFolder, setCreatingFolderState] = useState(null);
65
- const creatingFolderRef = useRef(null);
66
- const setCreatingFolder = (next) => {
67
- creatingFolderRef.current = next;
68
- setCreatingFolderState(next);
69
- };
70
- const kinds = useMemo(() => editor ? compositeKindsOf(editor) : EMPTY_KINDS, [editor]);
71
- const [outline, setOutline] = useState(EMPTY_OUTLINE);
72
- useEffect(() => {
73
- setOutline(readOutline(editor, structure, t));
74
- if (!editor) return;
75
- let timer;
76
- const onUpdate = () => {
77
- clearTimeout(timer);
78
- timer = setTimeout(() => setOutline(readOutline(editor, structure, t)), OUTLINE_DEBOUNCE_MS);
79
- };
80
- editor.on("update", onUpdate);
81
- return () => {
82
- clearTimeout(timer);
83
- editor.off("update", onUpdate);
84
- };
85
- }, [
32
+ const binding = useTreeModel({
33
+ store,
86
34
  editor,
87
- structure,
88
- t
89
- ]);
90
- const activeFolder = documents.find((d) => d.id === activeId)?.folder;
91
- useEffect(() => {
92
- if (virtual) model.expand([VIRTUAL_ROOT]);
93
- }, [virtual, model]);
94
- useEffect(() => {
95
- model.expand(ancestorKeys(activeFolder ?? ""));
96
- if (activeFolder !== void 0) setSelectedFolder(activeFolder);
97
- }, [
98
- activeFolder,
99
- model,
100
- setSelectedFolder
101
- ]);
102
- useEffect(() => {
103
- if (activeId) model.expand([`doc:${activeId}`]);
104
- }, [activeId, model]);
105
- useEffect(() => {
106
- if (!tree) return;
107
- const paths = /* @__PURE__ */ new Set();
108
- collectFolderPaths(tree, paths);
109
- if (!paths.has(selectedFolderRef.current)) setSelectedFolder("");
110
- }, [tree, setSelectedFolder]);
111
- /** A folder moved from `from` to `to`: its open/selected state follows it. */
112
- function followFolderMove(from, to) {
113
- model.followFolderMove(from, to);
114
- setSelectedFolder(remapFolderPath(selectedFolderRef.current, from, to));
115
- model.expand(ancestorKeys(parentPath(to)));
116
- }
117
- /** Makes a composite's children visible in the outline again. */
118
- function openOutline(blockId) {
119
- setCollapsedOutline((current) => current.filter((key) => key !== `outline:${blockId}`));
120
- }
121
- function selectItem(item) {
122
- if (renamingRef.current?.item.key === item.key) return;
123
- if (item.key === "vroot") {
124
- setVirtualTarget(null);
125
- return;
126
- }
127
- if (item.type === "document") {
128
- setSelectedFolder(item.folder ?? "");
129
- store.open(item.id).catch(() => void 0);
130
- } else setSelectedFolder(item.path ?? "");
131
- }
132
- function selectOutline(item) {
133
- if (editor && !editor.isDestroyed) revealBlockInEditor(editor, {
134
- id: item.blockId ?? null,
135
- pos: item.pos
136
- });
137
- if (virtual && item.blockId && (item.kind === "folder" || item.kind === "document")) setVirtualTarget(item.blockId);
138
- }
139
- const outlineList = outlineItems(outline.nodes, kinds, selectOutline);
140
- const unfilteredItems = virtual ? [virtualRootItem(documents.find((d) => d.id === activeId)?.title ?? t("tree.rootFallback"), outlineList, selectItem)] : tree ? [folderItem(tree, activeId, outlineList, selectItem)] : [];
141
- const items = applyNodeFilters(unfilteredItems, nodeFilters);
142
- const isEmpty = !virtual && !!tree && !tree.folders.length && !tree.documents.length;
143
- const isOpen = (item) => isOutlineItem(item) ? !collapsedOutline.includes(item.key) : expanded.includes(item.key);
144
- function setOpen(item, open) {
145
- if (isOutlineItem(item)) setCollapsedOutline((current) => open ? current.filter((key) => key !== item.key) : current.includes(item.key) ? current : [...current, item.key]);
146
- else if (open) model.expand([item.key]);
147
- else model.expanded.set(model.expanded.get().filter((key) => key !== item.key));
148
- }
35
+ selectedFolder,
36
+ onSelectedFolderChange
37
+ });
38
+ const treeElement = useRef(null);
39
+ if (!binding) return null;
40
+ const { model, state } = binding;
41
+ const { view, creatingFolder } = state;
149
42
  function onChevronClick(event, item) {
150
43
  if (item.type === "root" || item.type === "folder") return;
151
44
  event.stopPropagation();
152
- setOpen(item, !isOpen(item));
153
- }
154
- function activate(item) {
155
- item.onSelect?.();
156
- if (item.type === "root" || item.type === "folder") setOpen(item, !isOpen(item));
45
+ model.toggle(item.key);
157
46
  }
158
47
  function onRowKeyDown(event, item) {
159
48
  if (event.target !== event.currentTarget) return;
160
- const expandable = item.type === "root" || item.type === "folder" || !!item.children?.length;
161
- if (event.key === "Enter" || event.key === " ") activate(item);
162
- else if (event.key === "ArrowRight" && expandable && !isOpen(item)) setOpen(item, true);
163
- else if (event.key === "ArrowLeft" && expandable && isOpen(item)) setOpen(item, false);
49
+ const { expandable, open } = rowOf(state, item, t);
50
+ if (event.key === "Enter" || event.key === " ") model.activate(item);
51
+ else if (event.key === "ArrowRight" && expandable && !open) model.setOpen(item.key, true);
52
+ else if (event.key === "ArrowLeft" && expandable && open) model.setOpen(item.key, false);
164
53
  else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
165
54
  const rows = [...treeElement.current?.querySelectorAll("[role=\"treeitem\"]") ?? []];
166
55
  rows[rows.indexOf(event.currentTarget) + (event.key === "ArrowDown" ? 1 : -1)]?.focus();
167
56
  } else return;
168
57
  event.preventDefault();
169
58
  }
170
- const kindsOf = (item) => filterKinds(item, {
171
- structure,
172
- activeId,
173
- kinds,
174
- t
175
- });
176
- const hiddenBelow = (item) => nodeFilters[item.key] ?? [];
177
- const isFiltered = (item) => hiddenBelow(item).length > 0;
178
- const showFilter = (item) => kindsOf(item).length > 0 && (item.type === "root" || item.type === "folder" || !!item.children?.length || isFiltered(item));
179
- const visibleKeys = collectKeys(unfilteredItems, /* @__PURE__ */ new Set());
180
- const hasFilters = Object.entries(nodeFilters).some(([key, hidden]) => hidden.length > 0 && visibleKeys.has(key));
181
- async function importIntoComposite(blockId) {
182
- const files = await pickFiles("*/*");
183
- if (!files.length || !editor || editor.isDestroyed) return;
184
- await importFiles(editor, files, compositeInsertPos(editor, blockId));
185
- if (blockId) openOutline(blockId);
186
- }
187
- function createCompositeFolder(blockId) {
188
- if (!editor || editor.isDestroyed) return;
189
- const pos = compositeInsertPos(editor, blockId);
190
- editor.chain().insertContentAt(pos, compositeJSON("folder")).setTextSelection(pos + 2).focus().scrollIntoView().run();
191
- if (blockId) openOutline(blockId);
192
- }
193
- async function createDocumentIn(folder) {
194
- setSelectedFolder(folder);
195
- const doc = await store.create(void 0, folder).catch(() => void 0);
196
- if (!doc) return;
197
- model.expand(ancestorKeys(folder));
198
- await store.open(doc.id).catch(() => void 0);
199
- }
200
- const folderError = creatingFolder?.draft.trim() ? store.validateName(creatingFolder.draft, folderNamesIn(tree, creatingFolder.parent)) : null;
201
- function startCreateFolder(parent) {
202
- setSelectedFolder(parent);
203
- setCreatingFolder({
204
- parent,
205
- draft: ""
206
- });
207
- }
208
- async function commitCreateFolder() {
209
- const current = creatingFolderRef.current;
210
- if (!current) return;
211
- if (!current.draft.trim()) {
212
- setCreatingFolder(null);
213
- return;
214
- }
215
- if (folderError) return;
216
- setCreatingFolder(null);
217
- if (await store.createFolder(joinPath(current.parent, current.draft.trim())).then(() => true, () => false)) model.expand(ancestorKeys(current.parent));
218
- }
219
- const renameErrorOf = (current) => !current || current.draft.trim() === current.item.label ? null : store.validateName(current.draft, folderNamesIn(tree, parentPath(current.item.path), current.item.path));
220
- const renameError = renameErrorOf(renaming);
221
- async function commitRename() {
222
- const current = renamingRef.current;
223
- if (!current) return;
224
- if (renameErrorOf(current)) return;
225
- const name = current.draft.trim();
226
- setRenaming(null);
227
- if (name === current.item.label) return;
228
- const to = joinPath(parentPath(current.item.path), name);
229
- if (await store.moveFolder(current.item.path, to).then(() => true, () => false)) followFolderMove(current.item.path, to);
230
- }
231
- function onRenameBlur() {
232
- const current = renamingRef.current;
233
- if (!current) return;
234
- const error = renameErrorOf(current);
235
- if (error) {
236
- notify({
237
- kind: "warning",
238
- title: t("tree.folder.renameFailed"),
239
- description: error
240
- });
241
- setRenaming(null);
242
- return;
243
- }
244
- return commitRename();
245
- }
246
- async function restore(removed) {
247
- const restored = await removed.restore().catch(() => void 0);
248
- if (restored) await store.open(restored.id).catch(() => void 0);
249
- }
250
- async function removeWithUndo(id, folder) {
251
- const removed = await store.remove(id, folder).catch(() => void 0);
252
- if (!removed) return;
253
- toast(t("tree.removed"), {
254
- description: removed.title,
255
- icon: /* @__PURE__ */ jsx(Icon, { name: "i-lucide-trash-2" }),
256
- action: {
257
- label: t("tree.undo"),
258
- onClick: () => {
259
- restore(removed);
260
- }
261
- }
262
- });
263
- }
264
- async function removeItem(item) {
265
- setRemoving(item.key);
266
- try {
267
- if (item.type === "document") await removeWithUndo(item.id, item.folder ?? "");
268
- else await store.removeFolder(item.path).catch(() => void 0);
269
- } finally {
270
- setRemoving(null);
271
- }
272
- }
273
- /**
274
- * What can be created below `item`: folders of the store take documents and sub-folders, the open file (as the root or
275
- * as a document row) and its Ordner/Dokument composites take Ordner and imported Dokument Bausteine. Other rows take
276
- * nothing. Folders also list „Umbenennen“ here: renaming a document happens in the editor's title field.
277
- */
278
- function addMenu(item) {
279
- const groups = [];
280
- if (item.key !== "vroot" && (item.type === "root" || item.type === "folder")) {
281
- const path = item.path;
282
- groups.push([{
283
- label: t("tree.menu.newDocument"),
284
- icon: "i-lucide-file-plus",
285
- onSelect: () => void createDocumentIn(path)
286
- }, {
287
- label: item.type === "root" ? t("tree.menu.newFolder") : t("tree.menu.newSubfolder"),
288
- icon: "i-lucide-folder-plus",
289
- onSelect: () => startCreateFolder(path)
290
- }]);
291
- } else if (holdsComposites(item, {
292
- structure,
293
- editor,
294
- activeId
295
- })) {
296
- const blockId = item.blockId ?? null;
297
- groups.push([{
298
- label: t("tree.menu.newCompositeFolder"),
299
- icon: "i-lucide-folder-plus",
300
- onSelect: () => {
301
- createCompositeFolder(blockId);
302
- if (!blockId) model.expand([item.key]);
303
- }
304
- }, {
305
- label: t("tree.menu.importFile"),
306
- icon: "i-lucide-file-up",
307
- onSelect: () => {
308
- importIntoComposite(blockId);
309
- if (!blockId) model.expand([item.key]);
310
- }
311
- }]);
312
- }
313
- if (item.type === "folder") groups.push([{
314
- label: t("tree.menu.rename"),
315
- icon: "i-lucide-pencil",
316
- onSelect: () => setRenaming({
317
- item,
318
- draft: item.label
319
- })
320
- }]);
321
- return groups;
322
- }
323
- const isDraggable$1 = (item) => renaming?.item.key !== item.key && isDraggable(item, virtual);
324
59
  function onDragStart(event, item) {
325
- if (!isDraggable$1(item)) return event.preventDefault();
326
- const payload = dragPayloadOf(item);
327
- dragging.current = payload;
60
+ const payload = model.dragStart(item);
61
+ if (!payload) return event.preventDefault();
328
62
  event.dataTransfer.setData(DRAG_MIME, JSON.stringify(payload));
329
63
  event.dataTransfer.effectAllowed = "move";
330
64
  }
331
65
  function onDragOver(event, item) {
332
- if (!canDrop(dragging.current, item, {
333
- virtual,
334
- editor
335
- })) return;
336
- event.preventDefault();
337
- event.dataTransfer.dropEffect = "move";
338
- setDropKey(item.key);
66
+ const verdict = model.dragOver(item);
67
+ if (!verdict) return;
68
+ if (verdict.ok) event.preventDefault();
69
+ event.dataTransfer.dropEffect = verdict.ok ? "move" : "none";
339
70
  }
340
71
  function onDragLeave(event, item) {
341
- if (dropKey === item.key && !event.currentTarget.contains(event.relatedTarget)) setDropKey(null);
72
+ if (!event.currentTarget.contains(event.relatedTarget)) model.dragLeave(item);
342
73
  }
343
- function endDrag() {
344
- dragging.current = null;
345
- setDropKey(null);
346
- }
347
- async function onDrop(event, target) {
348
- const raw = event.dataTransfer.getData(DRAG_MIME);
349
- endDrag();
350
- if (!raw) return;
74
+ function onDrop(event, item) {
75
+ if (!state.drag) return;
351
76
  event.preventDefault();
352
- const source = JSON.parse(raw);
353
- if (!canDrop(source, target, {
354
- virtual,
355
- editor
356
- })) return;
357
- if (source.type === "composite") {
358
- const targetId = target.key === "vroot" ? null : target.blockId;
359
- if (editor && moveBlockInto(editor, source.blockId, targetId) && targetId) openOutline(targetId);
360
- return;
361
- }
362
- const targetPath = target.path;
363
- if (source.type === "document") {
364
- if (await store.move(source.id, targetPath).then(() => true, () => false)) model.expand(ancestorKeys(targetPath));
365
- return;
366
- }
367
- const to = joinPath(targetPath, source.path.slice(source.path.lastIndexOf("/") + 1));
368
- if (await store.moveFolder(source.path, to).then(() => true, () => false)) followFolderMove(source.path, to);
77
+ model.drop(item);
369
78
  }
370
79
  const filterMenu = (item) => {
371
- const hidden = hiddenBelow(item);
80
+ const { filterKinds, hiddenKinds } = rowOf(state, item, t);
372
81
  return /* @__PURE__ */ jsxs(DropdownMenuContent, {
373
82
  align: "start",
374
83
  className: "w-auto",
375
84
  children: [
376
85
  /* @__PURE__ */ jsx(DropdownMenuLabel, { children: t("tree.filter.show") }),
377
- kindsOf(item).map((kind) => /* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
378
- checked: !hidden.includes(kind.id),
86
+ filterKinds.map((kind) => /* @__PURE__ */ jsxs(DropdownMenuCheckboxItem, {
87
+ checked: !hiddenKinds.includes(kind.id),
379
88
  onCheckedChange: (visible) => model.setKindHidden(item.key, kind.id, !visible),
380
89
  onSelect: (event) => event.preventDefault(),
381
90
  children: [/* @__PURE__ */ jsx(Icon, { name: kind.icon }), kind.label]
382
91
  }, kind.id)),
383
- hidden.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(DropdownMenuSeparator, {}), /* @__PURE__ */ jsxs(DropdownMenuItem, {
92
+ hiddenKinds.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(DropdownMenuSeparator, {}), /* @__PURE__ */ jsxs(DropdownMenuItem, {
384
93
  onSelect: () => model.clearFilter(item.key),
385
94
  children: [/* @__PURE__ */ jsx(Icon, { name: "i-lucide-filter-x" }), t("tree.filter.reset")]
386
95
  })] })
387
96
  ]
388
97
  });
389
98
  };
390
- const renderRow = (item, open, level) => {
391
- const chevron = item.type === "root" || item.type === "folder" || !!item.children?.length;
392
- const selectedInVirtual = virtual && (item.key === "vroot" ? virtualTarget === null : !!item.blockId && item.blockId === virtualTarget);
393
- const add = addMenu(item);
394
- const filtered = isFiltered(item);
395
- return /* @__PURE__ */ jsxs("div", {
99
+ const renderRow = (item, level) => {
100
+ const row = rowOf(state, item, t);
101
+ const add = model.addMenu(item);
102
+ const filtered = row.hiddenKinds.length > 0;
103
+ const refused = row.drop && !row.drop.ok ? row.drop.reason : null;
104
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
396
105
  role: "treeitem",
397
106
  tabIndex: 0,
398
107
  "aria-level": level + 1,
399
- "aria-expanded": chevron ? open : void 0,
400
- "aria-selected": item.id !== void 0 && item.id === activeId ? true : void 0,
401
- className: cn("group flex w-full cursor-pointer items-center gap-1.5 rounded-md px-2 py-1 text-sm select-none outline-none hover:bg-muted/60 focus-visible:ring-1 focus-visible:ring-ring", item.id !== void 0 && item.id === activeId && "bg-primary/10 font-medium text-primary", item.type !== "document" && item.path === selectedFolder && "bg-muted", selectedInVirtual && "bg-muted", dropKey === item.key && "ring-1 ring-primary"),
402
- draggable: isDraggable$1(item),
403
- onClick: () => activate(item),
108
+ "aria-expanded": row.expandable ? row.open : void 0,
109
+ "aria-selected": row.active ? true : void 0,
110
+ className: cn("group flex w-full cursor-pointer items-center gap-1.5 rounded-md px-2 py-1 text-sm select-none outline-none hover:bg-muted/60 focus-visible:ring-1 focus-visible:ring-ring", row.active && "bg-primary/10 font-medium text-primary", row.selected && "bg-muted", row.drop?.ok && "ring-1 ring-primary", refused && "bg-destructive/10 ring-1 ring-destructive"),
111
+ draggable: model.isDraggable(item),
112
+ onClick: () => model.activate(item),
404
113
  onKeyDown: (event) => onRowKeyDown(event, item),
405
114
  onDragStart: (event) => onDragStart(event, item),
406
115
  onDragOver: (event) => onDragOver(event, item),
407
116
  onDragLeave: (event) => onDragLeave(event, item),
408
- onDrop: (event) => void onDrop(event, item),
409
- onDragEnd: endDrag,
117
+ onDrop: (event) => onDrop(event, item),
118
+ onDragEnd: () => model.dragEnd(),
410
119
  children: [
411
- chevron ? /* @__PURE__ */ jsx("span", {
120
+ row.expandable ? /* @__PURE__ */ jsx("span", {
412
121
  className: "flex shrink-0",
413
122
  onClick: (event) => onChevronClick(event, item),
414
123
  children: /* @__PURE__ */ jsx(Icon, {
415
124
  name: "i-lucide-chevron-right",
416
- className: cn("size-3.5 text-muted-foreground transition-transform", open && "rotate-90")
125
+ className: cn("size-3.5 text-muted-foreground transition-transform", row.open && "rotate-90")
417
126
  })
418
127
  }) : isOutlineItem(item) && /* @__PURE__ */ jsx("span", { className: "size-3.5 shrink-0" }),
419
128
  /* @__PURE__ */ jsx(Icon, { name: item.icon }),
420
- renaming?.item.key === item.key ? /* @__PURE__ */ jsxs("div", {
129
+ row.renaming ? /* @__PURE__ */ jsxs("div", {
421
130
  className: "flex min-w-0 flex-1 flex-col gap-0.5",
422
131
  children: [/* @__PURE__ */ jsx(Input, {
423
- value: renaming.draft,
132
+ value: row.renaming.draft,
424
133
  className: "h-6 min-w-0 px-2 text-xs md:text-xs",
425
134
  autoFocus: true,
426
135
  "aria-label": t("tree.folder.name"),
427
- "aria-invalid": !!renameError,
428
- onChange: (event) => setRenaming({
429
- item: renaming.item,
430
- draft: event.target.value
431
- }),
136
+ "aria-invalid": !!row.renaming.error,
137
+ onChange: (event) => model.setRenameDraft(event.target.value),
432
138
  onFocus: (event) => event.currentTarget.select(),
433
139
  onClick: (event) => event.stopPropagation(),
434
140
  onKeyDown: (event) => {
435
141
  event.stopPropagation();
436
- if (event.key === "Enter") commitRename();
437
- else if (event.key === "Escape") setRenaming(null);
142
+ if (event.key === "Enter") model.commitRename();
143
+ else if (event.key === "Escape") model.cancelRename();
438
144
  },
439
- onBlur: () => void onRenameBlur()
440
- }), renameError && /* @__PURE__ */ jsx("p", {
145
+ onBlur: () => void model.blurRename()
146
+ }), row.renaming.error && /* @__PURE__ */ jsx("p", {
441
147
  className: "text-xs text-destructive",
442
148
  role: "alert",
443
149
  onClick: (event) => event.stopPropagation(),
444
- children: renameError
150
+ children: row.renaming.error
445
151
  })]
446
152
  }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
447
153
  /* @__PURE__ */ jsx("span", {
448
154
  className: "min-w-0 flex-1 truncate",
449
155
  title: item.label,
450
- children: item.label
156
+ children: /* @__PURE__ */ jsx(Label, {
157
+ label: item.label,
158
+ match: row.match
159
+ })
451
160
  }),
452
161
  item.hasSummary && /* @__PURE__ */ jsx(Icon, {
453
162
  name: "i-lucide-text-quote",
@@ -456,7 +165,7 @@ function DocumentTree({ store, editor, selectedFolder: controlledFolder, onSelec
456
165
  /* @__PURE__ */ jsxs("div", {
457
166
  className: "flex shrink-0",
458
167
  onClick: (event) => event.stopPropagation(),
459
- children: [showFilter(item) && /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
168
+ children: [row.showFilter && /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
460
169
  asChild: true,
461
170
  children: /* @__PURE__ */ jsx(Button, {
462
171
  size: "icon-xs",
@@ -483,7 +192,7 @@ function DocumentTree({ store, editor, selectedFolder: controlledFolder, onSelec
483
192
  className: "w-auto",
484
193
  onCloseAutoFocus: (event) => event.preventDefault(),
485
194
  children: add.map((group, index) => /* @__PURE__ */ jsxs("div", { children: [index > 0 && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}), group.map((entry) => /* @__PURE__ */ jsxs(DropdownMenuItem, {
486
- onSelect: entry.onSelect,
195
+ onSelect: entry.run,
487
196
  children: [/* @__PURE__ */ jsx(Icon, { name: entry.icon }), entry.label]
488
197
  }, entry.label))] }, index))
489
198
  })] }),
@@ -496,11 +205,11 @@ function DocumentTree({ store, editor, selectedFolder: controlledFolder, onSelec
496
205
  variant: "ghost",
497
206
  className: "text-destructive hover:text-destructive",
498
207
  "aria-label": t("tree.delete"),
499
- disabled: removing === item.key,
500
- onClick: () => void removeItem(item),
208
+ disabled: row.removing,
209
+ onClick: () => void model.remove(item),
501
210
  children: /* @__PURE__ */ jsx(Icon, {
502
- name: removing === item.key ? "i-lucide-loader-circle" : "i-lucide-trash-2",
503
- className: cn(removing === item.key && "animate-spin")
211
+ name: row.removing ? "i-lucide-loader-circle" : "i-lucide-trash-2",
212
+ className: cn(row.removing && "animate-spin")
504
213
  })
505
214
  })
506
215
  ]
@@ -508,28 +217,56 @@ function DocumentTree({ store, editor, selectedFolder: controlledFolder, onSelec
508
217
  })
509
218
  ] })
510
219
  ]
511
- });
220
+ }), refused && /* @__PURE__ */ jsx("p", {
221
+ className: "px-2 pt-0.5 text-xs text-destructive",
222
+ role: "status",
223
+ children: refused
224
+ })] });
512
225
  };
513
226
  const renderItems = (list, level) => /* @__PURE__ */ jsx("ul", {
514
227
  role: level === 0 ? "tree" : "group",
515
228
  "aria-label": level === 0 ? t("tree.aria") : void 0,
516
229
  ref: level === 0 ? treeElement : void 0,
517
230
  className: cn("m-0 list-none p-0", level > 0 && "ml-3 border-l border-border pl-1"),
518
- children: list.map((item) => {
519
- const open = isOpen(item);
520
- return /* @__PURE__ */ jsx("li", {
521
- role: "none",
522
- children: /* @__PURE__ */ jsxs(Collapsible, {
523
- open,
524
- children: [renderRow(item, open, level), item.children?.length ? /* @__PURE__ */ jsx(CollapsibleContent, { children: renderItems(item.children, level + 1) }) : null]
525
- })
526
- }, item.key);
527
- })
231
+ children: list.map((item) => /* @__PURE__ */ jsx("li", {
232
+ role: "none",
233
+ children: /* @__PURE__ */ jsxs(Collapsible, {
234
+ open: rowOf(state, item, t).open,
235
+ children: [renderRow(item, level), item.children?.length ? /* @__PURE__ */ jsx(CollapsibleContent, { children: renderItems(item.children, level + 1) }) : null]
236
+ })
237
+ }, item.key))
528
238
  });
529
239
  return /* @__PURE__ */ jsxs("div", {
530
240
  className: cn("flex flex-col gap-2", className),
531
241
  children: [
532
- hasFilters && /* @__PURE__ */ jsx("div", {
242
+ /* @__PURE__ */ jsxs("div", {
243
+ className: "relative",
244
+ children: [
245
+ /* @__PURE__ */ jsx(Icon, {
246
+ name: "i-lucide-search",
247
+ className: "pointer-events-none absolute top-1/2 left-2 size-3.5 -translate-y-1/2 text-muted-foreground"
248
+ }),
249
+ /* @__PURE__ */ jsx(Input, {
250
+ value: state.filterText,
251
+ className: "h-7 pr-7 pl-7 text-sm md:text-sm",
252
+ placeholder: t("tree.filterText.placeholder"),
253
+ "aria-label": t("tree.filterText.placeholder"),
254
+ onChange: (event) => model.setFilterText(event.target.value),
255
+ onKeyDown: (event) => {
256
+ if (event.key === "Escape") model.setFilterText("");
257
+ }
258
+ }),
259
+ state.filterText && /* @__PURE__ */ jsx(Button, {
260
+ size: "icon-xs",
261
+ variant: "ghost",
262
+ className: "absolute top-1/2 right-1 -translate-y-1/2",
263
+ "aria-label": t("tree.filterText.clear"),
264
+ onClick: () => model.setFilterText(""),
265
+ children: /* @__PURE__ */ jsx(Icon, { name: "i-lucide-x" })
266
+ })
267
+ ]
268
+ }),
269
+ view.hasKindFilters && /* @__PURE__ */ jsx("div", {
533
270
  className: "flex items-center",
534
271
  children: /* @__PURE__ */ jsxs(Button, {
535
272
  size: "xs",
@@ -546,27 +283,28 @@ function DocumentTree({ store, editor, selectedFolder: controlledFolder, onSelec
546
283
  autoFocus: true,
547
284
  placeholder: creatingFolder.parent ? t("tree.folder.placeholderIn", { parent: creatingFolder.parent }) : t("tree.folder.placeholder"),
548
285
  "aria-label": t("tree.folder.name"),
549
- "aria-invalid": !!folderError,
550
- onChange: (event) => setCreatingFolder({
551
- parent: creatingFolder.parent,
552
- draft: event.target.value
553
- }),
286
+ "aria-invalid": !!creatingFolder.error,
287
+ onChange: (event) => model.setFolderDraft(event.target.value),
554
288
  onKeyDown: (event) => {
555
- if (event.key === "Enter") commitCreateFolder();
556
- else if (event.key === "Escape") setCreatingFolder(null);
289
+ if (event.key === "Enter") model.commitCreateFolder();
290
+ else if (event.key === "Escape") model.cancelCreateFolder();
557
291
  },
558
- onBlur: () => setCreatingFolder(null)
559
- }), folderError && /* @__PURE__ */ jsx("p", {
292
+ onBlur: () => model.cancelCreateFolder()
293
+ }), creatingFolder.error && /* @__PURE__ */ jsx("p", {
560
294
  className: "px-0.5 text-xs text-destructive",
561
295
  role: "alert",
562
- children: folderError
296
+ children: creatingFolder.error
563
297
  })]
564
298
  }),
565
- !tree && !virtual ? /* @__PURE__ */ jsx("p", {
299
+ !state.tree && !state.virtual ? /* @__PURE__ */ jsx("p", {
566
300
  className: "px-2.5 py-1.5 text-sm text-muted-foreground",
567
- children: tree === null ? t("tree.loadFailed") : t("tree.loading")
568
- }) : renderItems(items, 0),
569
- isEmpty && /* @__PURE__ */ jsx("p", {
301
+ children: state.tree === null ? t("tree.loadFailed") : t("tree.loading")
302
+ }) : renderItems(view.items, 0),
303
+ view.noMatches && /* @__PURE__ */ jsx("p", {
304
+ className: "px-2.5 py-1.5 text-sm text-muted-foreground",
305
+ children: t("tree.noMatches")
306
+ }),
307
+ view.isEmpty && /* @__PURE__ */ jsx("p", {
570
308
  className: "px-2.5 py-1.5 text-sm text-muted-foreground",
571
309
  children: t("tree.empty")
572
310
  })