@ecsplico/editor 2.0.0 → 2.1.0

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 +18 -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 +7 -7
  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 +4 -4
  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 +4 -4
  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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-model.js","names":[],"sources":["../../src/tree/tree-model.ts"],"sourcesContent":["import { createStore, type ReadonlyStore } from '../core/store'\nimport { type CompositeKind, kindAccepts } from '../features/composites'\nimport type { Translate } from '../i18n'\nimport { type DropEnv, dropVerdict, type DropVerdict } from './drop-rules'\nimport { remapFolderPath } from './folder-paths'\nimport type { DocumentStore } from './store'\nimport {\n ancestorKeys,\n collectFolderPaths,\n creatableBelow,\n type DragPayload,\n dragPayloadOf,\n EMPTY_OUTLINE,\n filterKinds,\n folderNamesIn,\n isDraggable,\n joinPath,\n type Outline,\n parentPath,\n type TreeItem,\n VIRTUAL_ROOT,\n} from './tree-items'\nimport { deriveTree, filterQuery, type TreeRow, type TreeView } from './tree-rows'\nimport type { NodeFilters, TreeViewModel } from './tree-view-model'\nimport type { DocumentSummary, TreeFolder } from './types'\n\n// One framework-free model per `DocumentTree`, rendered by the Vue and the React component alike. It owns what the\n// tree remembers (open rows, kind filters, the title filter, the selection, a drag, a folder being renamed or created)\n// and checks all of it against the tree after every change of the store, the outline or the view state, so a key of\n// something that moved or vanished never lingers. It never calls the store or the editor to change something: it emits\n// `TreeCommand`s to the `execute` it was given and follows their result.\n\n/** What the model asks its host to do; the host reports whether it happened. */\nexport type TreeCommand\n = | { type: 'openDocument', id: string }\n | { type: 'createDocument', folder: string }\n | { type: 'moveDocument', id: string, folder: string }\n | { type: 'removeDocument', id: string, folder: string }\n | { type: 'createFolder', path: string }\n | { type: 'moveFolder', from: string, to: string }\n | { type: 'removeFolder', path: string }\n /** Moves the block into the Baustein `targetId` (`null` = the end of the document). */\n | { type: 'moveBlock', id: string, targetId: string | null }\n | { type: 'revealBlock', id: string | null, pos: number }\n /** Adds an Ordner-Baustein to the Baustein `targetId` (`null` = the end of the document). */\n | { type: 'createBausteinFolder', targetId: string | null }\n /** Lets the user pick files and imports them into the Baustein `targetId` (`null` = the end of the document); `false` when none were imported. */\n | { type: 'importFiles', targetId: string | null }\n | { type: 'warn', title: string, description: string }\n\n/** Runs a command and resolves whether it happened. Never rejects: the host reports its own failures. */\nexport type TreeExecutor = (command: TreeCommand) => boolean | Promise<boolean>\n\n/** What the model needs from the open document's editor. */\nexport interface TreeEditor {\n kinds: readonly CompositeKind[]\n /** The outline in `structure`: Bausteine in `'composite'` structure, else the headings. Empty once the editor is gone. */\n outline: (structure: string) => Outline\n /** Whether a block may move into a Baustein, judged on the live document. */\n moveVerdict: NonNullable<DropEnv['moveVerdict']>\n /** `listener` runs after the document changed; returns the unsubscribe function. */\n onChange: (listener: () => void) => () => void\n}\n\n/** What the surrounding app decides: the open editor and the `blocks.structure` / `blocks.treeRoot` settings. */\nexport interface TreeContext {\n editor: TreeEditor | undefined\n structure: string\n treeRoot: string\n}\n\nexport interface TreeModelOptions {\n /** Words the menus, the drop refusals and the label of an untitled open document; the framework layer passes the context's `t`. */\n t: Translate\n store: DocumentStore\n /** The view state shared by the trees of one app: open store items and kind filters (persisted). */\n view: TreeViewModel\n execute: TreeExecutor\n context: TreeContext\n /** The folder new documents and folders go to; default the root. */\n selectedFolder?: string\n /** How long the outline waits for the document to settle before it is read again; default 150 ms. */\n outlineDebounceMs?: number\n}\n\n/** A drag in progress. */\nexport interface TreeDrag {\n source: DragPayload\n /** Key of the dragged row */\n sourceKey: string\n /** Key of the row under the pointer */\n hover: string | null\n /** What dropping on `hover` would do; `null` without a `hover` */\n verdict: DropVerdict | null\n}\n\n/** A folder being renamed. */\nexport interface TreeRename {\n /** Key of the folder's row */\n key: string\n path: string\n /** Its name when renaming started */\n base: string\n draft: string\n error: string | null\n}\n\n/** A folder being named. */\nexport interface TreeCreateFolder {\n /** Folder path the folder is created in */\n parent: string\n draft: string\n error: string | null\n}\n\n/** Everything a renderer needs. Fields below `view` are what the derived `view` was computed from and what `rowOf` reads. */\nexport interface TreeState {\n /** The loaded folder tree: `undefined` = not loaded yet, `null` = loading failed. */\n tree: TreeFolder | null | undefined\n documents: readonly DocumentSummary[]\n activeId: string | undefined\n /** The open document is the root (virtual mode) instead of the store's folders. */\n virtual: boolean\n structure: string\n kinds: readonly CompositeKind[]\n hasEditor: boolean\n outline: Outline\n nodeFilters: NodeFilters\n expanded: readonly string[]\n collapsedOutline: readonly string[]\n /** What the title filter input holds. */\n filterText: string\n filterOverrides: Readonly<Record<string, boolean>>\n /** The folder new documents, folders and host imports go to. */\n selectedFolder: string\n /** Virtual mode: block ID of the selected Baustein that takes Bausteine; `null` = the document itself. */\n virtualTarget: string | null\n drag: TreeDrag | null\n renaming: TreeRename | null\n creatingFolder: TreeCreateFolder | null\n /** Key of the row whose deletion is running. */\n removing: string | null\n view: TreeView\n}\n\n/** One row of a row's menu. */\nexport interface TreeMenuEntry {\n label: string\n icon: string\n run: () => void\n}\n\n/** How a renderer draws one row. */\nexport interface TreeRowState extends TreeRow {\n /** The row's document is the open one. */\n active: boolean\n /** The row is the selected folder, or (virtual mode) the selected Baustein. */\n selected: boolean\n /** What dropping the drag on this row would do, while the pointer is over it. */\n drop: DropVerdict | null\n renaming: TreeRename | null\n removing: boolean\n /** The kinds the row's kind filter can hide below it, and the ones it hides. */\n filterKinds: readonly CompositeKind[]\n hiddenKinds: readonly string[]\n /** The row offers a kind filter. */\n showFilter: boolean\n}\n\nexport interface TreeModel {\n readonly state: ReadonlyStore<TreeState>\n /** The open editor or a setting changed. */\n setContext: (context: TreeContext) => void\n /** The host selected another folder (it controls the value). */\n setSelectedFolder: (path: string) => void\n\n // --- what is open, filtered, typed\n /** Opens or closes a row; while the title filter is set the choice is discarded when it is cleared. */\n setOpen: (key: string, open: boolean) => void\n toggle: (key: string) => void\n /** Opens the rows (also during a title filter); closed rows are left alone otherwise. */\n expand: (keys: readonly string[]) => void\n /** Filters the rows by title (case-insensitive substring); `''` clears it and restores the opened rows from before. */\n setFilterText: (text: string) => void\n setKindHidden: (key: string, kind: string, hidden: boolean) => void\n clearFilter: (key: string) => void\n clearAllFilters: () => void\n\n // --- rows\n /** A row was chosen (click, Enter). */\n select: (item: TreeItem) => void\n /** `select`, and a folder or the root toggles. */\n activate: (item: TreeItem) => void\n addMenu: (item: TreeItem) => TreeMenuEntry[][]\n remove: (item: TreeItem) => Promise<void>\n\n // --- drag and drop\n isDraggable: (item: TreeItem) => boolean\n /** Starts dragging `item`; returns what the drag carries, `null` when `item` cannot be dragged. */\n dragStart: (item: TreeItem) => DragPayload | null\n /** The pointer is over `item`; returns the verdict (`null` without a drag). Only an `ok` one is a drop target. */\n dragOver: (item: TreeItem) => DropVerdict | null\n /** The pointer left `item` for good (not into one of its parts). */\n dragLeave: (item: TreeItem) => void\n dragEnd: () => void\n drop: (item: TreeItem) => Promise<void>\n\n // --- renaming and creating folders\n startRename: (item: TreeItem) => void\n setRenameDraft: (text: string) => void\n cancelRename: () => void\n commitRename: () => Promise<void>\n /** The rename field lost focus: commit, or give up with a warning when the name is invalid. */\n blurRename: () => Promise<void>\n startCreateFolder: (parent: string) => void\n setFolderDraft: (text: string) => void\n cancelCreateFolder: () => void\n commitCreateFolder: () => Promise<void>\n\n dispose: () => void\n}\n\nconst NO_DOCUMENTS: readonly DocumentSummary[] = []\nconst NO_KINDS: readonly CompositeKind[] = []\nconst NO_FILTERS: Readonly<Record<string, boolean>> = {}\nconst UNSET = Symbol('unset')\n\nconst isOutlineKey = (key: string) => key.startsWith('outline:')\n\n/** The state `core` holds: everything of `TreeState` but the derived `view`. */\ntype TreeCore = Omit<TreeState, 'view'>\n\nfunction sameVerdict(a: DropVerdict | null, b: DropVerdict | null): boolean {\n if (a === b) return true\n if (!a || !b) return false\n return a.ok ? b.ok : !b.ok && a.reason === b.reason\n}\n\n/** A row's state in `state` (`item` is one of `state.view.items`). */\nexport function rowOf(state: TreeState, item: TreeItem, t: Translate): TreeRowState {\n const hidden = state.nodeFilters[item.key] ?? []\n const kinds = filterKinds(item, { structure: state.structure, activeId: state.activeId, kinds: state.kinds, t })\n const virtualTarget = state.virtual && (item.key === VIRTUAL_ROOT ? state.virtualTarget === null : !!item.blockId && item.blockId === state.virtualTarget)\n return {\n ...state.view.rows.get(item.key)!,\n active: item.id !== undefined && item.id === state.activeId,\n selected: (item.type !== 'document' && item.path === state.selectedFolder) || virtualTarget,\n drop: state.drag?.hover === item.key ? state.drag.verdict : null,\n renaming: state.renaming?.key === item.key ? state.renaming : null,\n removing: state.removing === item.key,\n filterKinds: kinds,\n hiddenKinds: hidden,\n showFilter: kinds.length > 0 && (item.type === 'root' || item.type === 'folder' || !!item.children?.length || hidden.length > 0),\n }\n}\n\n/** A tree model over `options.store`; call `dispose` when its tree goes away. */\nexport function createTreeModel(options: TreeModelOptions): TreeModel {\n const { store, view: shared, execute, t } = options\n const debounceMs = options.outlineDebounceMs ?? 150\n\n let editor = options.context.editor\n let structure = options.context.structure\n let treeRoot = options.context.treeRoot\n let unwatchEditor = editor?.onChange(onEditorChange)\n let outlineTimer: ReturnType<typeof setTimeout> | undefined\n /** A folder move the host is running: until it is done, a selected folder at the old path follows to the new one. */\n let pendingMove: { from: string, to: string } | undefined\n let disposed = false\n\n const isVirtual = () => treeRoot === 'file' && structure === 'composite' && !!editor\n\n let core: TreeCore = {\n tree: store.tree.get(),\n documents: store.documents.get() ?? NO_DOCUMENTS,\n activeId: store.activeId.get(),\n virtual: isVirtual(),\n structure,\n kinds: editor?.kinds ?? NO_KINDS,\n hasEditor: !!editor,\n outline: editor ? editor.outline(structure) : EMPTY_OUTLINE,\n nodeFilters: shared.nodeFilters.get(),\n expanded: shared.expanded.get(),\n collapsedOutline: [],\n filterText: '',\n filterOverrides: NO_FILTERS,\n selectedFolder: options.selectedFolder ?? '',\n virtualTarget: null,\n drag: null,\n renaming: null,\n creatingFolder: null,\n removing: null,\n }\n let lastView: { deps: readonly unknown[], view: TreeView } | undefined\n let lastTree: TreeFolder | null | undefined | typeof UNSET = UNSET\n let folderPaths = new Set<string>()\n let lastActiveFolder: string | undefined | typeof UNSET = UNSET\n let lastActiveId: string | undefined | typeof UNSET = UNSET\n\n const dropEnv = (): DropEnv => ({ virtual: core.virtual, moveVerdict: editor?.moveVerdict, t })\n\n function viewOf(c: TreeCore): TreeView {\n const deps = [c.tree, c.documents, c.activeId, c.virtual, c.outline, c.kinds, c.nodeFilters, c.expanded, c.collapsedOutline, c.filterText, c.filterOverrides]\n if (lastView && deps.every((dep, i) => Object.is(dep, lastView!.deps[i]))) return lastView.view\n const view = deriveTree({ ...c, t, select: selectItem })\n lastView = { deps, view }\n return view\n }\n\n const renameError = (draft: string, path: string, base: string, tree: TreeFolder | null | undefined) =>\n draft.trim() === base ? null : store.validateName(draft, folderNamesIn(tree, parentPath(path), path))\n const folderError = (draft: string, parent: string, tree: TreeFolder | null | undefined) =>\n draft.trim() ? store.validateName(draft, folderNamesIn(tree, parent)) : null\n\n /** Measures everything the tree remembers against the current tree: what is gone is dropped, what depends on the tree is recomputed. */\n function reconcile(c: TreeCore, view: TreeView): TreeCore {\n let next = c\n if (c.tree !== lastTree) {\n lastTree = c.tree\n folderPaths = new Set<string>()\n if (c.tree) collectFolderPaths(c.tree, folderPaths)\n if (c.tree && !folderPaths.has(next.selectedFolder)) {\n const followed = pendingMove ? remapFolderPath(next.selectedFolder, pendingMove.from, pendingMove.to) : ''\n next = { ...next, selectedFolder: folderPaths.has(followed) ? followed : '' }\n }\n }\n if (next.virtualTarget !== null && (!next.virtual || !view.keys.has(`outline:${next.virtualTarget}`))) next = { ...next, virtualTarget: null }\n if (next.renaming) {\n const r = next.renaming\n if (!view.keys.has(r.key)) next = { ...next, renaming: null }\n else {\n const error = renameError(r.draft, r.path, r.base, next.tree)\n if (error !== r.error) next = { ...next, renaming: { ...r, error } }\n }\n }\n if (next.creatingFolder) {\n const f = next.creatingFolder\n if (next.tree && !folderPaths.has(f.parent)) next = { ...next, creatingFolder: null }\n else {\n const error = folderError(f.draft, f.parent, next.tree)\n if (error !== f.error) next = { ...next, creatingFolder: { ...f, error } }\n }\n }\n if (next.drag) {\n const d = next.drag\n if (!view.keys.has(d.sourceKey) || (d.hover !== null && !view.keys.has(d.hover))) next = { ...next, drag: null }\n else if (d.hover !== null) {\n const target = view.rows.get(d.hover)?.item\n const verdict = target ? dropVerdict(d.source, target, dropEnv()) : d.verdict\n if (!sameVerdict(verdict, d.verdict)) next = { ...next, drag: { ...d, verdict } }\n }\n }\n return next\n }\n\n const state = createStore<TreeState>({ ...core, view: viewOf(core) })\n\n function publish(c: TreeCore) {\n if (disposed) return\n const view = viewOf(c)\n core = reconcile(c, view)\n const next: TreeState = { ...core, view }\n const current = state.get()\n if ((Object.keys(next) as (keyof TreeState)[]).every(key => Object.is(next[key], current[key]))) return\n state.set(next)\n }\n\n const patch = (changes: Partial<TreeCore>) => publish({ ...core, ...changes })\n\n // --- sources ---------------------------------------------------------------------------------------------------\n\n /** Reads the store and the shared view state; what opening a document shows is opened. */\n function sync() {\n if (disposed) return\n const documents = store.documents.get() ?? NO_DOCUMENTS\n const activeId = store.activeId.get()\n publish({ ...core, tree: store.tree.get(), documents, activeId, nodeFilters: shared.nodeFilters.get(), expanded: shared.expanded.get() })\n // Opening a document (also by URL, or after a move) shows it: its folders open and its folder becomes the selected one.\n const activeFolder = documents.find(d => d.id === activeId)?.folder\n if (activeFolder !== lastActiveFolder) {\n lastActiveFolder = activeFolder\n expand(ancestorKeys(activeFolder ?? ''))\n if (activeFolder !== undefined) setSelectedFolder(activeFolder)\n }\n if (activeId !== lastActiveId) {\n lastActiveId = activeId\n if (activeId) expand([`doc:${activeId}`])\n }\n }\n\n const unsubscribe = [\n store.tree.subscribe(sync),\n store.documents.subscribe(sync),\n store.activeId.subscribe(sync),\n shared.nodeFilters.subscribe(sync),\n shared.expanded.subscribe(sync),\n ]\n\n function readOutline() {\n patch({ outline: editor ? editor.outline(structure) : EMPTY_OUTLINE })\n }\n\n // The outline is read when the document changed and has been quiet for a moment; a drag in progress is checked at once.\n function onEditorChange() {\n clearTimeout(outlineTimer)\n outlineTimer = setTimeout(readOutline, debounceMs)\n if (core.drag) publish(core)\n }\n\n function setContext(context: TreeContext) {\n if (context.editor === editor && context.structure === structure && context.treeRoot === treeRoot) return\n const editorChanged = context.editor !== editor\n const wasVirtual = core.virtual\n editor = context.editor\n structure = context.structure\n treeRoot = context.treeRoot\n clearTimeout(outlineTimer)\n if (editorChanged) {\n unwatchEditor?.()\n unwatchEditor = editor?.onChange(onEditorChange)\n }\n patch({\n virtual: isVirtual(),\n structure,\n kinds: editor?.kinds ?? NO_KINDS,\n hasEditor: !!editor,\n outline: editor ? editor.outline(structure) : EMPTY_OUTLINE,\n })\n if (core.virtual && !wasVirtual) expand([VIRTUAL_ROOT])\n }\n\n // --- open, filtered, typed -------------------------------------------------------------------------------------\n\n const filtering = () => filterQuery(core.filterText) !== ''\n\n function setOpen(key: string, open: boolean) {\n if (filtering()) patch({ filterOverrides: { ...core.filterOverrides, [key]: open } })\n else if (isOutlineKey(key)) patch({ collapsedOutline: open ? core.collapsedOutline.filter(k => k !== key) : [...core.collapsedOutline.filter(k => k !== key), key] })\n else if (open) shared.expand([key])\n else shared.expanded.set(shared.expanded.get().filter(k => k !== key))\n }\n\n function expand(keys: readonly string[]) {\n const outlineKeys = keys.filter(isOutlineKey)\n const { filterOverrides } = core\n const overridden = keys.some(key => key in filterOverrides)\n if (overridden || outlineKeys.some(key => core.collapsedOutline.includes(key))) {\n patch({\n collapsedOutline: core.collapsedOutline.filter(key => !outlineKeys.includes(key)),\n filterOverrides: overridden ? Object.fromEntries(Object.entries(filterOverrides).filter(([key]) => !keys.includes(key))) : filterOverrides,\n })\n }\n shared.expand(keys.filter(key => !isOutlineKey(key)))\n }\n\n function setFilterText(text: string) {\n patch({ filterText: text, filterOverrides: filterQuery(text) ? core.filterOverrides : NO_FILTERS })\n }\n\n // --- selection -------------------------------------------------------------------------------------------------\n\n function setSelectedFolder(path: string) {\n patch({ selectedFolder: path })\n }\n\n function selectItem(item: TreeItem) {\n if (core.renaming?.key === item.key) return\n if (item.key === VIRTUAL_ROOT) return patch({ virtualTarget: null })\n if (item.type === 'document') {\n setSelectedFolder(item.folder ?? '')\n void execute({ type: 'openDocument', id: item.id! })\n }\n else if (item.type === 'composite' || item.type === 'heading') {\n void execute({ type: 'revealBlock', id: item.blockId ?? null, pos: item.pos! })\n const takesBausteine = !!item.kind && core.kinds.some(kind => kindAccepts(core.kinds, item.kind!, kind.id))\n if (core.virtual && item.blockId && takesBausteine) patch({ virtualTarget: item.blockId })\n }\n else {\n setSelectedFolder(item.path ?? '')\n }\n }\n\n function toggle(key: string) {\n setOpen(key, !state.get().view.rows.get(key)?.open)\n }\n\n /** A folder moved from `from` to `to`: its open/selected state follows it. */\n function followFolderMove(from: string, to: string) {\n shared.followFolderMove(from, to)\n patch({\n selectedFolder: remapFolderPath(core.selectedFolder, from, to),\n filterOverrides: Object.fromEntries(Object.entries(core.filterOverrides).map(([key, open]) => [key.startsWith('folder:') ? `folder:${remapFolderPath(key.slice(7), from, to)}` : key, open])),\n })\n expand(ancestorKeys(parentPath(to)))\n }\n\n async function moveFolder(from: string, to: string) {\n pendingMove = { from, to }\n const moved = await execute({ type: 'moveFolder', from, to })\n pendingMove = undefined\n if (moved) followFolderMove(from, to)\n }\n\n // --- drag and drop ---------------------------------------------------------------------------------------------\n\n function dragStart(item: TreeItem) {\n if (core.renaming?.key === item.key || !isDraggable(item, core.virtual)) return null\n const source = dragPayloadOf(item)\n patch({ drag: { source, sourceKey: item.key, hover: null, verdict: null } })\n return source\n }\n\n function dragOver(item: TreeItem) {\n const drag = core.drag\n if (!drag) return null\n const verdict = dropVerdict(drag.source, item, dropEnv())\n if (drag.hover !== item.key || !sameVerdict(drag.verdict, verdict)) patch({ drag: { ...drag, hover: item.key, verdict } })\n return verdict\n }\n\n async function drop(item: TreeItem) {\n const drag = core.drag\n patch({ drag: null })\n if (!drag || !dropVerdict(drag.source, item, dropEnv()).ok) return\n const { source } = drag\n if (source.type === 'composite') {\n const targetId = item.key === VIRTUAL_ROOT ? null : item.blockId!\n if (await execute({ type: 'moveBlock', id: source.blockId, targetId }) && targetId) expand([`outline:${targetId}`])\n return\n }\n const targetPath = item.path!\n if (source.type === 'document') {\n if (await execute({ type: 'moveDocument', id: source.id!, folder: targetPath })) expand(ancestorKeys(targetPath))\n return\n }\n await moveFolder(source.path, joinPath(targetPath, source.path.slice(source.path.lastIndexOf('/') + 1)))\n }\n\n // --- renaming, creating ----------------------------------------------------------------------------------------\n\n function startRename(item: TreeItem) {\n if (item.type !== 'folder') return\n patch({ renaming: { key: item.key, path: item.path!, base: item.label, draft: item.label, error: null } })\n }\n\n async function commitRename() {\n const r = core.renaming\n // An invalid name keeps the field open with its inline error.\n if (!r || r.error) return\n const name = r.draft.trim()\n patch({ renaming: null })\n if (name !== r.base) await moveFolder(r.path, joinPath(parentPath(r.path), name))\n }\n\n async function blurRename() {\n const r = core.renaming\n if (!r) return\n if (r.error) {\n void execute({ type: 'warn', title: t('tree.folder.renameFailed'), description: r.error })\n patch({ renaming: null })\n return\n }\n await commitRename()\n }\n\n function startCreateFolder(parent: string) {\n patch({ selectedFolder: parent, creatingFolder: { parent, draft: '', error: null } })\n }\n\n async function commitCreateFolder() {\n const f = core.creatingFolder\n if (!f) return\n if (!f.draft.trim()) return patch({ creatingFolder: null })\n if (f.error) return\n patch({ creatingFolder: null })\n if (await execute({ type: 'createFolder', path: joinPath(f.parent, f.draft.trim()) })) expand(ancestorKeys(f.parent))\n }\n\n // --- menus, deleting -------------------------------------------------------------------------------------------\n\n async function createDocumentIn(folder: string) {\n setSelectedFolder(folder)\n if (await execute({ type: 'createDocument', folder })) expand(ancestorKeys(folder))\n }\n\n /** Runs `command` for the Baustein of `item` (the file itself when it has none) and shows the result. */\n async function inComposite(item: TreeItem, command: TreeCommand) {\n if (await execute(command)) expand([item.blockId ? `outline:${item.blockId}` : item.key])\n }\n\n /**\n * What can be created below `item`: folders of the store take documents and sub-folders, the open file (as the root or\n * as a document row) and its Bausteine take what their kind accepts. Other rows take nothing. Folders also list\n * „Umbenennen“ here: renaming a document happens in the editor's title field.\n */\n function addMenu(item: TreeItem): TreeMenuEntry[][] {\n const groups: TreeMenuEntry[][] = []\n if (item.key !== VIRTUAL_ROOT && (item.type === 'root' || item.type === 'folder')) {\n const path = item.path!\n groups.push([\n { label: t('tree.menu.newDocument'), icon: 'i-lucide-file-plus', run: () => void createDocumentIn(path) },\n { label: item.type === 'root' ? t('tree.menu.newFolder') : t('tree.menu.newSubfolder'), icon: 'i-lucide-folder-plus', run: () => startCreateFolder(path) },\n ])\n }\n else {\n const can = creatableBelow(item, { structure: core.structure, hasEditor: core.hasEditor, activeId: core.activeId, kinds: core.kinds })\n const targetId = item.blockId ?? null\n const entries: TreeMenuEntry[] = []\n if (can.folder) entries.push({ label: t('tree.menu.newCompositeFolder'), icon: 'i-lucide-folder-plus', run: () => void inComposite(item, { type: 'createBausteinFolder', targetId }) })\n if (can.document) entries.push({ label: t('tree.menu.importFile'), icon: 'i-lucide-file-up', run: () => void inComposite(item, { type: 'importFiles', targetId }) })\n if (entries.length) groups.push(entries)\n }\n if (item.type === 'folder') groups.push([{ label: t('tree.menu.rename'), icon: 'i-lucide-pencil', run: () => startRename(item) }])\n return groups\n }\n\n async function remove(item: TreeItem) {\n patch({ removing: item.key })\n try {\n await execute(item.type === 'document' ? { type: 'removeDocument', id: item.id!, folder: item.folder ?? '' } : { type: 'removeFolder', path: item.path! })\n }\n finally {\n patch({ removing: null })\n }\n }\n\n sync()\n\n return {\n state,\n setContext,\n setSelectedFolder,\n setOpen,\n toggle,\n expand,\n setFilterText,\n setKindHidden: (key, kind, hidden) => shared.setKindHidden(key, kind, hidden),\n clearFilter: key => shared.clearFilter(key),\n clearAllFilters: () => shared.clearAllFilters(),\n select: selectItem,\n activate(item) {\n selectItem(item)\n if (item.type === 'root' || item.type === 'folder') toggle(item.key)\n },\n addMenu,\n remove,\n isDraggable: item => core.renaming?.key !== item.key && isDraggable(item, core.virtual),\n dragStart,\n dragOver,\n dragLeave(item) {\n if (core.drag?.hover === item.key) patch({ drag: { ...core.drag, hover: null, verdict: null } })\n },\n dragEnd: () => patch({ drag: null }),\n drop,\n startRename,\n setRenameDraft(text) {\n const r = core.renaming\n if (r) patch({ renaming: { ...r, draft: text, error: renameError(text, r.path, r.base, core.tree) } })\n },\n cancelRename: () => patch({ renaming: null }),\n commitRename,\n blurRename,\n startCreateFolder,\n setFolderDraft(text) {\n const f = core.creatingFolder\n if (f) patch({ creatingFolder: { ...f, draft: text, error: folderError(text, f.parent, core.tree) } })\n },\n cancelCreateFolder: () => patch({ creatingFolder: null }),\n commitCreateFolder,\n dispose() {\n disposed = true\n clearTimeout(outlineTimer)\n unwatchEditor?.()\n for (const stop of unsubscribe) stop()\n },\n }\n}\n"],"mappings":";;;;;;;AA8NA,MAAM,eAA2C,CAAC;AAClD,MAAM,WAAqC,CAAC;AAC5C,MAAM,aAAgD,CAAC;AACvD,MAAM,QAAQ,OAAO,OAAO;AAE5B,MAAM,gBAAgB,QAAgB,IAAI,WAAW,UAAU;AAK/D,SAAS,YAAY,GAAuB,GAAgC;CAC1E,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,CAAC,KAAK,CAAC,GAAG,OAAO;CACrB,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE;AAC/C;;AAGA,SAAgB,MAAM,OAAkB,MAAgB,GAA4B;CAClF,MAAM,SAAS,MAAM,YAAY,KAAK,QAAQ,CAAC;CAC/C,MAAM,QAAQ,YAAY,MAAM;EAAE,WAAW,MAAM;EAAW,UAAU,MAAM;EAAU,OAAO,MAAM;EAAO;CAAE,CAAC;CAC/G,MAAM,gBAAgB,MAAM,YAAY,KAAK,QAAA,UAAuB,MAAM,kBAAkB,OAAO,CAAC,CAAC,KAAK,WAAW,KAAK,YAAY,MAAM;CAC5I,OAAO;EACL,GAAG,MAAM,KAAK,KAAK,IAAI,KAAK,GAAG;EAC/B,QAAQ,KAAK,OAAO,KAAA,KAAa,KAAK,OAAO,MAAM;EACnD,UAAW,KAAK,SAAS,cAAc,KAAK,SAAS,MAAM,kBAAmB;EAC9E,MAAM,MAAM,MAAM,UAAU,KAAK,MAAM,MAAM,KAAK,UAAU;EAC5D,UAAU,MAAM,UAAU,QAAQ,KAAK,MAAM,MAAM,WAAW;EAC9D,UAAU,MAAM,aAAa,KAAK;EAClC,aAAa;EACb,aAAa;EACb,YAAY,MAAM,SAAS,MAAM,KAAK,SAAS,UAAU,KAAK,SAAS,YAAY,CAAC,CAAC,KAAK,UAAU,UAAU,OAAO,SAAS;CAChI;AACF;;AAGA,SAAgB,gBAAgB,SAAsC;CACpE,MAAM,EAAE,OAAO,MAAM,QAAQ,SAAS,MAAM;CAC5C,MAAM,aAAa,QAAQ,qBAAqB;CAEhD,IAAI,SAAS,QAAQ,QAAQ;CAC7B,IAAI,YAAY,QAAQ,QAAQ;CAChC,IAAI,WAAW,QAAQ,QAAQ;CAC/B,IAAI,gBAAgB,QAAQ,SAAS,cAAc;CACnD,IAAI;;CAEJ,IAAI;CACJ,IAAI,WAAW;CAEf,MAAM,kBAAkB,aAAa,UAAU,cAAc,eAAe,CAAC,CAAC;CAE9E,IAAI,OAAiB;EACnB,MAAM,MAAM,KAAK,IAAI;EACrB,WAAW,MAAM,UAAU,IAAI,KAAK;EACpC,UAAU,MAAM,SAAS,IAAI;EAC7B,SAAS,UAAU;EACnB;EACA,OAAO,QAAQ,SAAS;EACxB,WAAW,CAAC,CAAC;EACb,SAAS,SAAS,OAAO,QAAQ,SAAS,IAAI;EAC9C,aAAa,OAAO,YAAY,IAAI;EACpC,UAAU,OAAO,SAAS,IAAI;EAC9B,kBAAkB,CAAC;EACnB,YAAY;EACZ,iBAAiB;EACjB,gBAAgB,QAAQ,kBAAkB;EAC1C,eAAe;EACf,MAAM;EACN,UAAU;EACV,gBAAgB;EAChB,UAAU;CACZ;CACA,IAAI;CACJ,IAAI,WAAyD;CAC7D,IAAI,8BAAc,IAAI,IAAY;CAClC,IAAI,mBAAsD;CAC1D,IAAI,eAAkD;CAEtD,MAAM,iBAA0B;EAAE,SAAS,KAAK;EAAS,aAAa,QAAQ;EAAa;CAAE;CAE7F,SAAS,OAAO,GAAuB;EACrC,MAAM,OAAO;GAAC,EAAE;GAAM,EAAE;GAAW,EAAE;GAAU,EAAE;GAAS,EAAE;GAAS,EAAE;GAAO,EAAE;GAAa,EAAE;GAAU,EAAE;GAAkB,EAAE;GAAY,EAAE;EAAe;EAC5J,IAAI,YAAY,KAAK,OAAO,KAAK,MAAM,OAAO,GAAG,KAAK,SAAU,KAAK,EAAE,CAAC,GAAG,OAAO,SAAS;EAC3F,MAAM,OAAO,WAAW;GAAE,GAAG;GAAG;GAAG,QAAQ;EAAW,CAAC;EACvD,WAAW;GAAE;GAAM;EAAK;EACxB,OAAO;CACT;CAEA,MAAM,eAAe,OAAe,MAAc,MAAc,SAC9D,MAAM,KAAK,MAAM,OAAO,OAAO,MAAM,aAAa,OAAO,cAAc,MAAM,WAAW,IAAI,GAAG,IAAI,CAAC;CACtG,MAAM,eAAe,OAAe,QAAgB,SAClD,MAAM,KAAK,IAAI,MAAM,aAAa,OAAO,cAAc,MAAM,MAAM,CAAC,IAAI;;CAG1E,SAAS,UAAU,GAAa,MAA0B;EACxD,IAAI,OAAO;EACX,IAAI,EAAE,SAAS,UAAU;GACvB,WAAW,EAAE;GACb,8BAAc,IAAI,IAAY;GAC9B,IAAI,EAAE,MAAM,mBAAmB,EAAE,MAAM,WAAW;GAClD,IAAI,EAAE,QAAQ,CAAC,YAAY,IAAI,KAAK,cAAc,GAAG;IACnD,MAAM,WAAW,cAAc,gBAAgB,KAAK,gBAAgB,YAAY,MAAM,YAAY,EAAE,IAAI;IACxG,OAAO;KAAE,GAAG;KAAM,gBAAgB,YAAY,IAAI,QAAQ,IAAI,WAAW;IAAG;GAC9E;EACF;EACA,IAAI,KAAK,kBAAkB,SAAS,CAAC,KAAK,WAAW,CAAC,KAAK,KAAK,IAAI,WAAW,KAAK,eAAe,IAAI,OAAO;GAAE,GAAG;GAAM,eAAe;EAAK;EAC7I,IAAI,KAAK,UAAU;GACjB,MAAM,IAAI,KAAK;GACf,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,GAAG,GAAG,OAAO;IAAE,GAAG;IAAM,UAAU;GAAK;QACvD;IACH,MAAM,QAAQ,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI;IAC5D,IAAI,UAAU,EAAE,OAAO,OAAO;KAAE,GAAG;KAAM,UAAU;MAAE,GAAG;MAAG;KAAM;IAAE;GACrE;EACF;EACA,IAAI,KAAK,gBAAgB;GACvB,MAAM,IAAI,KAAK;GACf,IAAI,KAAK,QAAQ,CAAC,YAAY,IAAI,EAAE,MAAM,GAAG,OAAO;IAAE,GAAG;IAAM,gBAAgB;GAAK;QAC/E;IACH,MAAM,QAAQ,YAAY,EAAE,OAAO,EAAE,QAAQ,KAAK,IAAI;IACtD,IAAI,UAAU,EAAE,OAAO,OAAO;KAAE,GAAG;KAAM,gBAAgB;MAAE,GAAG;MAAG;KAAM;IAAE;GAC3E;EACF;EACA,IAAI,KAAK,MAAM;GACb,MAAM,IAAI,KAAK;GACf,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,SAAS,KAAM,EAAE,UAAU,QAAQ,CAAC,KAAK,KAAK,IAAI,EAAE,KAAK,GAAI,OAAO;IAAE,GAAG;IAAM,MAAM;GAAK;QAC1G,IAAI,EAAE,UAAU,MAAM;IACzB,MAAM,SAAS,KAAK,KAAK,IAAI,EAAE,KAAK,CAAC,EAAE;IACvC,MAAM,UAAU,SAAS,YAAY,EAAE,QAAQ,QAAQ,QAAQ,CAAC,IAAI,EAAE;IACtE,IAAI,CAAC,YAAY,SAAS,EAAE,OAAO,GAAG,OAAO;KAAE,GAAG;KAAM,MAAM;MAAE,GAAG;MAAG;KAAQ;IAAE;GAClF;EACF;EACA,OAAO;CACT;CAEA,MAAM,QAAQ,YAAuB;EAAE,GAAG;EAAM,MAAM,OAAO,IAAI;CAAE,CAAC;CAEpE,SAAS,QAAQ,GAAa;EAC5B,IAAI,UAAU;EACd,MAAM,OAAO,OAAO,CAAC;EACrB,OAAO,UAAU,GAAG,IAAI;EACxB,MAAM,OAAkB;GAAE,GAAG;GAAM;EAAK;EACxC,MAAM,UAAU,MAAM,IAAI;EAC1B,IAAK,OAAO,KAAK,IAAI,CAAC,CAAyB,OAAM,QAAO,OAAO,GAAG,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG;EACjG,MAAM,IAAI,IAAI;CAChB;CAEA,MAAM,SAAS,YAA+B,QAAQ;EAAE,GAAG;EAAM,GAAG;CAAQ,CAAC;;CAK7E,SAAS,OAAO;EACd,IAAI,UAAU;EACd,MAAM,YAAY,MAAM,UAAU,IAAI,KAAK;EAC3C,MAAM,WAAW,MAAM,SAAS,IAAI;EACpC,QAAQ;GAAE,GAAG;GAAM,MAAM,MAAM,KAAK,IAAI;GAAG;GAAW;GAAU,aAAa,OAAO,YAAY,IAAI;GAAG,UAAU,OAAO,SAAS,IAAI;EAAE,CAAC;EAExI,MAAM,eAAe,UAAU,MAAK,MAAK,EAAE,OAAO,QAAQ,CAAC,EAAE;EAC7D,IAAI,iBAAiB,kBAAkB;GACrC,mBAAmB;GACnB,OAAO,aAAa,gBAAgB,EAAE,CAAC;GACvC,IAAI,iBAAiB,KAAA,GAAW,kBAAkB,YAAY;EAChE;EACA,IAAI,aAAa,cAAc;GAC7B,eAAe;GACf,IAAI,UAAU,OAAO,CAAC,OAAO,UAAU,CAAC;EAC1C;CACF;CAEA,MAAM,cAAc;EAClB,MAAM,KAAK,UAAU,IAAI;EACzB,MAAM,UAAU,UAAU,IAAI;EAC9B,MAAM,SAAS,UAAU,IAAI;EAC7B,OAAO,YAAY,UAAU,IAAI;EACjC,OAAO,SAAS,UAAU,IAAI;CAChC;CAEA,SAAS,cAAc;EACrB,MAAM,EAAE,SAAS,SAAS,OAAO,QAAQ,SAAS,IAAI,cAAc,CAAC;CACvE;CAGA,SAAS,iBAAiB;EACxB,aAAa,YAAY;EACzB,eAAe,WAAW,aAAa,UAAU;EACjD,IAAI,KAAK,MAAM,QAAQ,IAAI;CAC7B;CAEA,SAAS,WAAW,SAAsB;EACxC,IAAI,QAAQ,WAAW,UAAU,QAAQ,cAAc,aAAa,QAAQ,aAAa,UAAU;EACnG,MAAM,gBAAgB,QAAQ,WAAW;EACzC,MAAM,aAAa,KAAK;EACxB,SAAS,QAAQ;EACjB,YAAY,QAAQ;EACpB,WAAW,QAAQ;EACnB,aAAa,YAAY;EACzB,IAAI,eAAe;GACjB,gBAAgB;GAChB,gBAAgB,QAAQ,SAAS,cAAc;EACjD;EACA,MAAM;GACJ,SAAS,UAAU;GACnB;GACA,OAAO,QAAQ,SAAS;GACxB,WAAW,CAAC,CAAC;GACb,SAAS,SAAS,OAAO,QAAQ,SAAS,IAAI;EAChD,CAAC;EACD,IAAI,KAAK,WAAW,CAAC,YAAY,OAAO,CAAC,YAAY,CAAC;CACxD;CAIA,MAAM,kBAAkB,YAAY,KAAK,UAAU,MAAM;CAEzD,SAAS,QAAQ,KAAa,MAAe;EAC3C,IAAI,UAAU,GAAG,MAAM,EAAE,iBAAiB;GAAE,GAAG,KAAK;IAAkB,MAAM;EAAK,EAAE,CAAC;OAC/E,IAAI,aAAa,GAAG,GAAG,MAAM,EAAE,kBAAkB,OAAO,KAAK,iBAAiB,QAAO,MAAK,MAAM,GAAG,IAAI,CAAC,GAAG,KAAK,iBAAiB,QAAO,MAAK,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC;OAC/J,IAAI,MAAM,OAAO,OAAO,CAAC,GAAG,CAAC;OAC7B,OAAO,SAAS,IAAI,OAAO,SAAS,IAAI,CAAC,CAAC,QAAO,MAAK,MAAM,GAAG,CAAC;CACvE;CAEA,SAAS,OAAO,MAAyB;EACvC,MAAM,cAAc,KAAK,OAAO,YAAY;EAC5C,MAAM,EAAE,oBAAoB;EAC5B,MAAM,aAAa,KAAK,MAAK,QAAO,OAAO,eAAe;EAC1D,IAAI,cAAc,YAAY,MAAK,QAAO,KAAK,iBAAiB,SAAS,GAAG,CAAC,GAC3E,MAAM;GACJ,kBAAkB,KAAK,iBAAiB,QAAO,QAAO,CAAC,YAAY,SAAS,GAAG,CAAC;GAChF,iBAAiB,aAAa,OAAO,YAAY,OAAO,QAAQ,eAAe,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,SAAS,GAAG,CAAC,CAAC,IAAI;EAC7H,CAAC;EAEH,OAAO,OAAO,KAAK,QAAO,QAAO,CAAC,aAAa,GAAG,CAAC,CAAC;CACtD;CAEA,SAAS,cAAc,MAAc;EACnC,MAAM;GAAE,YAAY;GAAM,iBAAiB,YAAY,IAAI,IAAI,KAAK,kBAAkB;EAAW,CAAC;CACpG;CAIA,SAAS,kBAAkB,MAAc;EACvC,MAAM,EAAE,gBAAgB,KAAK,CAAC;CAChC;CAEA,SAAS,WAAW,MAAgB;EAClC,IAAI,KAAK,UAAU,QAAQ,KAAK,KAAK;EACrC,IAAI,KAAK,QAAA,SAAsB,OAAO,MAAM,EAAE,eAAe,KAAK,CAAC;EACnE,IAAI,KAAK,SAAS,YAAY;GAC5B,kBAAkB,KAAK,UAAU,EAAE;GACnC,QAAa;IAAE,MAAM;IAAgB,IAAI,KAAK;GAAI,CAAC;EACrD,OACK,IAAI,KAAK,SAAS,eAAe,KAAK,SAAS,WAAW;GAC7D,QAAa;IAAE,MAAM;IAAe,IAAI,KAAK,WAAW;IAAM,KAAK,KAAK;GAAK,CAAC;GAC9E,MAAM,iBAAiB,CAAC,CAAC,KAAK,QAAQ,KAAK,MAAM,MAAK,SAAQ,YAAY,KAAK,OAAO,KAAK,MAAO,KAAK,EAAE,CAAC;GAC1G,IAAI,KAAK,WAAW,KAAK,WAAW,gBAAgB,MAAM,EAAE,eAAe,KAAK,QAAQ,CAAC;EAC3F,OAEE,kBAAkB,KAAK,QAAQ,EAAE;CAErC;CAEA,SAAS,OAAO,KAAa;EAC3B,QAAQ,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,GAAG,CAAC,EAAE,IAAI;CACpD;;CAGA,SAAS,iBAAiB,MAAc,IAAY;EAClD,OAAO,iBAAiB,MAAM,EAAE;EAChC,MAAM;GACJ,gBAAgB,gBAAgB,KAAK,gBAAgB,MAAM,EAAE;GAC7D,iBAAiB,OAAO,YAAY,OAAO,QAAQ,KAAK,eAAe,CAAC,CAAC,KAAK,CAAC,KAAK,UAAU,CAAC,IAAI,WAAW,SAAS,IAAI,UAAU,gBAAgB,IAAI,MAAM,CAAC,GAAG,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC,CAAC;EAC9L,CAAC;EACD,OAAO,aAAa,WAAW,EAAE,CAAC,CAAC;CACrC;CAEA,eAAe,WAAW,MAAc,IAAY;EAClD,cAAc;GAAE;GAAM;EAAG;EACzB,MAAM,QAAQ,MAAM,QAAQ;GAAE,MAAM;GAAc;GAAM;EAAG,CAAC;EAC5D,cAAc,KAAA;EACd,IAAI,OAAO,iBAAiB,MAAM,EAAE;CACtC;CAIA,SAAS,UAAU,MAAgB;EACjC,IAAI,KAAK,UAAU,QAAQ,KAAK,OAAO,CAAC,YAAY,MAAM,KAAK,OAAO,GAAG,OAAO;EAChF,MAAM,SAAS,cAAc,IAAI;EACjC,MAAM,EAAE,MAAM;GAAE;GAAQ,WAAW,KAAK;GAAK,OAAO;GAAM,SAAS;EAAK,EAAE,CAAC;EAC3E,OAAO;CACT;CAEA,SAAS,SAAS,MAAgB;EAChC,MAAM,OAAO,KAAK;EAClB,IAAI,CAAC,MAAM,OAAO;EAClB,MAAM,UAAU,YAAY,KAAK,QAAQ,MAAM,QAAQ,CAAC;EACxD,IAAI,KAAK,UAAU,KAAK,OAAO,CAAC,YAAY,KAAK,SAAS,OAAO,GAAG,MAAM,EAAE,MAAM;GAAE,GAAG;GAAM,OAAO,KAAK;GAAK;EAAQ,EAAE,CAAC;EACzH,OAAO;CACT;CAEA,eAAe,KAAK,MAAgB;EAClC,MAAM,OAAO,KAAK;EAClB,MAAM,EAAE,MAAM,KAAK,CAAC;EACpB,IAAI,CAAC,QAAQ,CAAC,YAAY,KAAK,QAAQ,MAAM,QAAQ,CAAC,CAAC,CAAC,IAAI;EAC5D,MAAM,EAAE,WAAW;EACnB,IAAI,OAAO,SAAS,aAAa;GAC/B,MAAM,WAAW,KAAK,QAAA,UAAuB,OAAO,KAAK;GACzD,IAAI,MAAM,QAAQ;IAAE,MAAM;IAAa,IAAI,OAAO;IAAS;GAAS,CAAC,KAAK,UAAU,OAAO,CAAC,WAAW,UAAU,CAAC;GAClH;EACF;EACA,MAAM,aAAa,KAAK;EACxB,IAAI,OAAO,SAAS,YAAY;GAC9B,IAAI,MAAM,QAAQ;IAAE,MAAM;IAAgB,IAAI,OAAO;IAAK,QAAQ;GAAW,CAAC,GAAG,OAAO,aAAa,UAAU,CAAC;GAChH;EACF;EACA,MAAM,WAAW,OAAO,MAAM,SAAS,YAAY,OAAO,KAAK,MAAM,OAAO,KAAK,YAAY,GAAG,IAAI,CAAC,CAAC,CAAC;CACzG;CAIA,SAAS,YAAY,MAAgB;EACnC,IAAI,KAAK,SAAS,UAAU;EAC5B,MAAM,EAAE,UAAU;GAAE,KAAK,KAAK;GAAK,MAAM,KAAK;GAAO,MAAM,KAAK;GAAO,OAAO,KAAK;GAAO,OAAO;EAAK,EAAE,CAAC;CAC3G;CAEA,eAAe,eAAe;EAC5B,MAAM,IAAI,KAAK;EAEf,IAAI,CAAC,KAAK,EAAE,OAAO;EACnB,MAAM,OAAO,EAAE,MAAM,KAAK;EAC1B,MAAM,EAAE,UAAU,KAAK,CAAC;EACxB,IAAI,SAAS,EAAE,MAAM,MAAM,WAAW,EAAE,MAAM,SAAS,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;CAClF;CAEA,eAAe,aAAa;EAC1B,MAAM,IAAI,KAAK;EACf,IAAI,CAAC,GAAG;EACR,IAAI,EAAE,OAAO;GACX,QAAa;IAAE,MAAM;IAAQ,OAAO,EAAE,0BAA0B;IAAG,aAAa,EAAE;GAAM,CAAC;GACzF,MAAM,EAAE,UAAU,KAAK,CAAC;GACxB;EACF;EACA,MAAM,aAAa;CACrB;CAEA,SAAS,kBAAkB,QAAgB;EACzC,MAAM;GAAE,gBAAgB;GAAQ,gBAAgB;IAAE;IAAQ,OAAO;IAAI,OAAO;GAAK;EAAE,CAAC;CACtF;CAEA,eAAe,qBAAqB;EAClC,MAAM,IAAI,KAAK;EACf,IAAI,CAAC,GAAG;EACR,IAAI,CAAC,EAAE,MAAM,KAAK,GAAG,OAAO,MAAM,EAAE,gBAAgB,KAAK,CAAC;EAC1D,IAAI,EAAE,OAAO;EACb,MAAM,EAAE,gBAAgB,KAAK,CAAC;EAC9B,IAAI,MAAM,QAAQ;GAAE,MAAM;GAAgB,MAAM,SAAS,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;EAAE,CAAC,GAAG,OAAO,aAAa,EAAE,MAAM,CAAC;CACtH;CAIA,eAAe,iBAAiB,QAAgB;EAC9C,kBAAkB,MAAM;EACxB,IAAI,MAAM,QAAQ;GAAE,MAAM;GAAkB;EAAO,CAAC,GAAG,OAAO,aAAa,MAAM,CAAC;CACpF;;CAGA,eAAe,YAAY,MAAgB,SAAsB;EAC/D,IAAI,MAAM,QAAQ,OAAO,GAAG,OAAO,CAAC,KAAK,UAAU,WAAW,KAAK,YAAY,KAAK,GAAG,CAAC;CAC1F;;;;;;CAOA,SAAS,QAAQ,MAAmC;EAClD,MAAM,SAA4B,CAAC;EACnC,IAAI,KAAK,QAAA,YAAyB,KAAK,SAAS,UAAU,KAAK,SAAS,WAAW;GACjF,MAAM,OAAO,KAAK;GAClB,OAAO,KAAK,CACV;IAAE,OAAO,EAAE,uBAAuB;IAAG,MAAM;IAAsB,WAAW,KAAK,iBAAiB,IAAI;GAAE,GACxG;IAAE,OAAO,KAAK,SAAS,SAAS,EAAE,qBAAqB,IAAI,EAAE,wBAAwB;IAAG,MAAM;IAAwB,WAAW,kBAAkB,IAAI;GAAE,CAC3J,CAAC;EACH,OACK;GACH,MAAM,MAAM,eAAe,MAAM;IAAE,WAAW,KAAK;IAAW,WAAW,KAAK;IAAW,UAAU,KAAK;IAAU,OAAO,KAAK;GAAM,CAAC;GACrI,MAAM,WAAW,KAAK,WAAW;GACjC,MAAM,UAA2B,CAAC;GAClC,IAAI,IAAI,QAAQ,QAAQ,KAAK;IAAE,OAAO,EAAE,8BAA8B;IAAG,MAAM;IAAwB,WAAW,KAAK,YAAY,MAAM;KAAE,MAAM;KAAwB;IAAS,CAAC;GAAE,CAAC;GACtL,IAAI,IAAI,UAAU,QAAQ,KAAK;IAAE,OAAO,EAAE,sBAAsB;IAAG,MAAM;IAAoB,WAAW,KAAK,YAAY,MAAM;KAAE,MAAM;KAAe;IAAS,CAAC;GAAE,CAAC;GACnK,IAAI,QAAQ,QAAQ,OAAO,KAAK,OAAO;EACzC;EACA,IAAI,KAAK,SAAS,UAAU,OAAO,KAAK,CAAC;GAAE,OAAO,EAAE,kBAAkB;GAAG,MAAM;GAAmB,WAAW,YAAY,IAAI;EAAE,CAAC,CAAC;EACjI,OAAO;CACT;CAEA,eAAe,OAAO,MAAgB;EACpC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;EAC5B,IAAI;GACF,MAAM,QAAQ,KAAK,SAAS,aAAa;IAAE,MAAM;IAAkB,IAAI,KAAK;IAAK,QAAQ,KAAK,UAAU;GAAG,IAAI;IAAE,MAAM;IAAgB,MAAM,KAAK;GAAM,CAAC;EAC3J,UACQ;GACN,MAAM,EAAE,UAAU,KAAK,CAAC;EAC1B;CACF;CAEA,KAAK;CAEL,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA,gBAAgB,KAAK,MAAM,WAAW,OAAO,cAAc,KAAK,MAAM,MAAM;EAC5E,cAAa,QAAO,OAAO,YAAY,GAAG;EAC1C,uBAAuB,OAAO,gBAAgB;EAC9C,QAAQ;EACR,SAAS,MAAM;GACb,WAAW,IAAI;GACf,IAAI,KAAK,SAAS,UAAU,KAAK,SAAS,UAAU,OAAO,KAAK,GAAG;EACrE;EACA;EACA;EACA,cAAa,SAAQ,KAAK,UAAU,QAAQ,KAAK,OAAO,YAAY,MAAM,KAAK,OAAO;EACtF;EACA;EACA,UAAU,MAAM;GACd,IAAI,KAAK,MAAM,UAAU,KAAK,KAAK,MAAM,EAAE,MAAM;IAAE,GAAG,KAAK;IAAM,OAAO;IAAM,SAAS;GAAK,EAAE,CAAC;EACjG;EACA,eAAe,MAAM,EAAE,MAAM,KAAK,CAAC;EACnC;EACA;EACA,eAAe,MAAM;GACnB,MAAM,IAAI,KAAK;GACf,IAAI,GAAG,MAAM,EAAE,UAAU;IAAE,GAAG;IAAG,OAAO;IAAM,OAAO,YAAY,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI;GAAE,EAAE,CAAC;EACvG;EACA,oBAAoB,MAAM,EAAE,UAAU,KAAK,CAAC;EAC5C;EACA;EACA;EACA,eAAe,MAAM;GACnB,MAAM,IAAI,KAAK;GACf,IAAI,GAAG,MAAM,EAAE,gBAAgB;IAAE,GAAG;IAAG,OAAO;IAAM,OAAO,YAAY,MAAM,EAAE,QAAQ,KAAK,IAAI;GAAE,EAAE,CAAC;EACvG;EACA,0BAA0B,MAAM,EAAE,gBAAgB,KAAK,CAAC;EACxD;EACA,UAAU;GACR,WAAW;GACX,aAAa,YAAY;GACzB,gBAAgB;GAChB,KAAK,MAAM,QAAQ,aAAa,KAAK;EACvC;CACF;AACF"}
@@ -0,0 +1,77 @@
1
+ import { applyNodeFilters, collectKeys, folderItem, outlineItems, virtualRootItem } from "./tree-items.js";
2
+ //#region src/tree/tree-rows.ts
3
+ const isOutlineItem = (item) => item.type === "composite" || item.type === "heading";
4
+ /** The normalized filter text: trimmed and lower-cased; empty = no filter. */
5
+ const filterQuery = (filterText) => filterText.trim().toLowerCase();
6
+ /**
7
+ * Keeps the items whose label contains `query` and the ancestors of such items; everything else goes, also below a
8
+ * match. `matched` collects the keys of the matches, `below` those that have a match among their descendants.
9
+ */
10
+ function pruneByText(list, query, matched, below) {
11
+ return list.flatMap((item) => {
12
+ const hit = item.label.toLowerCase().includes(query);
13
+ const children = item.children ? pruneByText(item.children, query, matched, below) : [];
14
+ if (hit) matched.add(item.key);
15
+ if (children.length) below.add(item.key);
16
+ if (!hit && !children.length) return [];
17
+ return [{
18
+ ...item,
19
+ children: children.length ? children : void 0
20
+ }];
21
+ });
22
+ }
23
+ /**
24
+ * The tree to show. In the store's folders: the `Dokumente` root with folders and documents, the open document listing
25
+ * its outline; in virtual mode: the open document as the root with its Bausteine. The kind filters (hidden kinds per
26
+ * item) apply first, then the filter text: only matching rows and their ancestors stay, and the ancestors of a match
27
+ * show open (without being written to `expanded`) until the filter text is cleared. A choice to open or close a row
28
+ * made meanwhile lives in `filterOverrides`.
29
+ */
30
+ function deriveTree(input) {
31
+ const { tree, virtual, activeId, nodeFilters, select } = input;
32
+ const outline = outlineItems(input.outline.nodes, input.kinds, select);
33
+ const unfiltered = virtual ? [virtualRootItem(input.documents.find((d) => d.id === activeId)?.title ?? input.t("tree.rootFallback"), outline, select)] : tree ? [folderItem(tree, activeId, outline, select)] : [];
34
+ const keys = collectKeys(unfiltered, /* @__PURE__ */ new Set());
35
+ let items = applyNodeFilters(unfiltered, nodeFilters);
36
+ const query = filterQuery(input.filterText);
37
+ const matched = /* @__PURE__ */ new Set();
38
+ const below = /* @__PURE__ */ new Set();
39
+ if (query) items = pruneByText(items, query, matched, below);
40
+ const expanded = new Set(input.expanded);
41
+ const collapsed = new Set(input.collapsedOutline);
42
+ const rows = /* @__PURE__ */ new Map();
43
+ const open = [];
44
+ const walk = (list, depth) => {
45
+ for (const item of list) {
46
+ const expandable = item.type === "root" || item.type === "folder" || !!item.children?.length;
47
+ const stored = isOutlineItem(item) ? !collapsed.has(item.key) : expanded.has(item.key);
48
+ const forced = below.has(item.key);
49
+ const isOpen = expandable && ((query ? input.filterOverrides[item.key] : void 0) ?? (forced || stored));
50
+ const at = matched.has(item.key) ? item.label.toLowerCase().indexOf(query) : -1;
51
+ rows.set(item.key, {
52
+ item,
53
+ depth,
54
+ expandable,
55
+ open: isOpen,
56
+ forcedOpen: isOpen && forced && !stored,
57
+ match: at >= 0 ? [at, query.length] : null
58
+ });
59
+ if (isOpen) open.push(item.key);
60
+ if (item.children) walk(item.children, depth + 1);
61
+ }
62
+ };
63
+ walk(items, 0);
64
+ return {
65
+ items,
66
+ rows,
67
+ open,
68
+ keys,
69
+ hasKindFilters: Object.entries(nodeFilters).some(([key, hidden]) => hidden.length > 0 && keys.has(key)),
70
+ isEmpty: !virtual && !!tree && !tree.folders.length && !tree.documents.length,
71
+ noMatches: !!query && items.length === 0 && (virtual || !!tree)
72
+ };
73
+ }
74
+ //#endregion
75
+ export { deriveTree, filterQuery };
76
+
77
+ //# sourceMappingURL=tree-rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-rows.js","names":[],"sources":["../../src/tree/tree-rows.ts"],"sourcesContent":["import type { CompositeKind } from '../features/composites'\nimport type { Translate } from '../i18n'\nimport {\n applyNodeFilters,\n collectKeys,\n folderItem,\n type Outline,\n outlineItems,\n type TreeItem,\n virtualRootItem,\n} from './tree-items'\nimport type { NodeFilters } from './tree-view-model'\nimport type { DocumentSummary, TreeFolder } from './types'\n\n// The visible rows of the document tree as one pure function of what the tree has loaded and what the user opened,\n// filtered and typed. No framework, no editor, no store.\n\n/** Everything `deriveTree` reads. */\nexport interface TreeInput {\n /** Words the root's label while the open document has no title in `documents`. */\n t: Translate\n /** `null` = loading failed, `undefined` = not loaded yet */\n tree: TreeFolder | null | undefined\n documents: readonly DocumentSummary[]\n activeId: string | undefined\n /** The open document is the root (virtual mode) instead of the store's folders. */\n virtual: boolean\n outline: Outline\n kinds: readonly CompositeKind[]\n /** Hidden kinds per item key */\n nodeFilters: NodeFilters\n /** Keys of the open store items (folders, documents, the roots) */\n expanded: readonly string[]\n /** Outline entries start open: only the closed ones are listed. */\n collapsedOutline: readonly string[]\n /** What the title filter shows; blank = no filter */\n filterText: string\n /** While a filter text is set: open (`true`) or closed (`false`) choices made meanwhile, by item key. Ignored without a filter text. */\n filterOverrides: Readonly<Record<string, boolean>>\n /** Chosen on an item (a row click, Enter). */\n select: (item: TreeItem) => void\n}\n\n/** What the tree knows about one visible row. */\nexport interface TreeRow {\n /** The (filtered) item of the row. */\n item: TreeItem\n depth: number\n /** The item has (or may get) children and shows a chevron. */\n expandable: boolean\n open: boolean\n /** Open only because the filter text matches below it. */\n forcedOpen: boolean\n /** `[start, length]` of the filter text in the label when this row's title matches. */\n match: readonly [number, number] | null\n}\n\nexport interface TreeView {\n /** The items to render: the kind filters and the filter text applied. */\n items: TreeItem[]\n /** Per key of the items in `items`. */\n rows: ReadonlyMap<string, TreeRow>\n /** Keys of the open rows, for a host tree that wants a list. */\n open: string[]\n /** Every key the unfiltered tree has: what a stale key is measured against. */\n keys: ReadonlySet<string>\n /** A kind filter of an existing item hides something. */\n hasKindFilters: boolean\n /** The store has no folder and no document. */\n isEmpty: boolean\n /** A filter text is set and nothing matches it. */\n noMatches: boolean\n}\n\nconst isOutlineItem = (item: TreeItem) => item.type === 'composite' || item.type === 'heading'\n\n/** The normalized filter text: trimmed and lower-cased; empty = no filter. */\nexport const filterQuery = (filterText: string) => filterText.trim().toLowerCase()\n\n/**\n * Keeps the items whose label contains `query` and the ancestors of such items; everything else goes, also below a\n * match. `matched` collects the keys of the matches, `below` those that have a match among their descendants.\n */\nfunction pruneByText(list: readonly TreeItem[], query: string, matched: Set<string>, below: Set<string>): TreeItem[] {\n return list.flatMap((item) => {\n const hit = item.label.toLowerCase().includes(query)\n const children = item.children ? pruneByText(item.children, query, matched, below) : []\n if (hit) matched.add(item.key)\n if (children.length) below.add(item.key)\n if (!hit && !children.length) return []\n return [{ ...item, children: children.length ? children : undefined }]\n })\n}\n\n/**\n * The tree to show. In the store's folders: the `Dokumente` root with folders and documents, the open document listing\n * its outline; in virtual mode: the open document as the root with its Bausteine. The kind filters (hidden kinds per\n * item) apply first, then the filter text: only matching rows and their ancestors stay, and the ancestors of a match\n * show open (without being written to `expanded`) until the filter text is cleared. A choice to open or close a row\n * made meanwhile lives in `filterOverrides`.\n */\nexport function deriveTree(input: TreeInput): TreeView {\n const { tree, virtual, activeId, nodeFilters, select } = input\n const outline = outlineItems(input.outline.nodes, input.kinds, select)\n const unfiltered: TreeItem[] = virtual\n ? [virtualRootItem(input.documents.find(d => d.id === activeId)?.title ?? input.t('tree.rootFallback'), outline, select)]\n : tree ? [folderItem(tree, activeId, outline, select)] : []\n const keys = collectKeys(unfiltered, new Set())\n\n let items = applyNodeFilters(unfiltered, nodeFilters)\n const query = filterQuery(input.filterText)\n const matched = new Set<string>()\n const below = new Set<string>()\n if (query) items = pruneByText(items, query, matched, below)\n\n const expanded = new Set(input.expanded)\n const collapsed = new Set(input.collapsedOutline)\n const rows = new Map<string, TreeRow>()\n const open: string[] = []\n const walk = (list: readonly TreeItem[], depth: number) => {\n for (const item of list) {\n const expandable = item.type === 'root' || item.type === 'folder' || !!item.children?.length\n const stored = isOutlineItem(item) ? !collapsed.has(item.key) : expanded.has(item.key)\n const forced = below.has(item.key)\n const isOpen = expandable && ((query ? input.filterOverrides[item.key] : undefined) ?? (forced || stored))\n const at = matched.has(item.key) ? item.label.toLowerCase().indexOf(query) : -1\n rows.set(item.key, { item, depth, expandable, open: isOpen, forcedOpen: isOpen && forced && !stored, match: at >= 0 ? [at, query.length] : null })\n if (isOpen) open.push(item.key)\n if (item.children) walk(item.children, depth + 1)\n }\n }\n walk(items, 0)\n\n return {\n items,\n rows,\n open,\n keys,\n hasKindFilters: Object.entries(nodeFilters).some(([key, hidden]) => hidden.length > 0 && keys.has(key)),\n isEmpty: !virtual && !!tree && !tree.folders.length && !tree.documents.length,\n noMatches: !!query && items.length === 0 && (virtual || !!tree),\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ import { useUiKit } from "../vue/kit/use-ui-kit.js";
2
+ import { useEditorSettings } from "../vue/settings.js";
3
+ import { useEditorContext } from "../vue/context.js";
4
+ import { useEditorI18n } from "../vue/use-editor-i18n.js";
5
+ import { treeView } from "./tree-view-model.js";
6
+ import { createTreeModel } from "./tree-model.js";
7
+ import { createTreeExecutor, treeEditorOf } from "./tree-host.js";
8
+ import { useStoreRef } from "../vue/store-ref.js";
9
+ import { useFileImport } from "../features/media/vue.js";
10
+ import { computed, onScopeDispose, shallowRef, watch } from "vue";
11
+ //#region src/tree/use-tree-model.ts
12
+ /**
13
+ * The tree model (`createTreeModel`) of one Vue `DocumentTree`: over the app's shared view state, with the UI kit's toasts and
14
+ * the Vue file import, following the open editor and the `blocks` settings. `selectedFolder` is two-way: the model's
15
+ * selection (including its fall back to the root when the folder is gone) is written to it, and a change of it selects
16
+ * that folder. The model is replaced when `store` changes and disposed with the scope; call during `setup()`.
17
+ */
18
+ function useTreeModel(options) {
19
+ const view = treeView(useEditorContext());
20
+ const { t } = useEditorI18n();
21
+ const { settings } = useEditorSettings();
22
+ const notify = useUiKit().notifier();
23
+ const { importFiles } = useFileImport(options.editor);
24
+ const treeEditor = computed(() => {
25
+ const editor = options.editor();
26
+ return editor ? treeEditorOf(editor, t) : void 0;
27
+ });
28
+ const context = computed(() => ({
29
+ editor: treeEditor.value,
30
+ structure: settings.value.blocks.structure,
31
+ treeRoot: settings.value.blocks.treeRoot
32
+ }));
33
+ const execute = createTreeExecutor({
34
+ store: options.store,
35
+ editor: options.editor,
36
+ importFiles,
37
+ warn: (title, description) => {
38
+ notify({
39
+ title,
40
+ description,
41
+ kind: "warning",
42
+ icon: "i-lucide-circle-alert"
43
+ });
44
+ },
45
+ deleted: (title, undo) => {
46
+ notify({
47
+ title: t("tree.removed"),
48
+ description: title,
49
+ icon: "i-lucide-trash-2",
50
+ action: {
51
+ label: t("tree.undo"),
52
+ onClick: undo
53
+ }
54
+ });
55
+ }
56
+ });
57
+ const model = shallowRef(createTreeModel({
58
+ t,
59
+ store: options.store(),
60
+ view,
61
+ execute,
62
+ context: context.value,
63
+ selectedFolder: options.selectedFolder.value
64
+ }));
65
+ const state = useStoreRef(() => model.value.state);
66
+ watch(options.store, (store) => {
67
+ model.value.dispose();
68
+ model.value = createTreeModel({
69
+ t,
70
+ store,
71
+ view,
72
+ execute,
73
+ context: context.value,
74
+ selectedFolder: options.selectedFolder.value
75
+ });
76
+ });
77
+ watch(context, (next) => model.value.setContext(next), { flush: "sync" });
78
+ watch(options.selectedFolder, (folder) => model.value.setSelectedFolder(folder), { flush: "sync" });
79
+ watch(() => state.value.selectedFolder, (folder) => {
80
+ options.selectedFolder.value = folder;
81
+ }, {
82
+ flush: "sync",
83
+ immediate: true
84
+ });
85
+ onScopeDispose(() => model.value.dispose());
86
+ return {
87
+ model,
88
+ state
89
+ };
90
+ }
91
+ //#endregion
92
+ export { useTreeModel };
93
+
94
+ //# sourceMappingURL=use-tree-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-tree-model.js","names":[],"sources":["../../src/tree/use-tree-model.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { computed, onScopeDispose, type Ref, shallowRef, watch } from 'vue'\nimport { useFileImport } from '../features/media/vue'\nimport { useUiKit } from '../vue/kit/use-ui-kit'\nimport { useEditorContext } from '../vue/context'\nimport { useEditorSettings } from '../vue/settings'\nimport { useEditorI18n } from '../vue/use-editor-i18n'\nimport { useStoreRef } from '../vue/store-ref'\nimport type { DocumentStore } from './store'\nimport { createTreeExecutor, treeEditorOf } from './tree-host'\nimport { createTreeModel, type TreeModel, type TreeState } from './tree-model'\nimport { treeView } from './tree-view-model'\n\n/**\n * The tree model (`createTreeModel`) of one Vue `DocumentTree`: over the app's shared view state, with the UI kit's toasts and\n * the Vue file import, following the open editor and the `blocks` settings. `selectedFolder` is two-way: the model's\n * selection (including its fall back to the root when the folder is gone) is written to it, and a change of it selects\n * that folder. The model is replaced when `store` changes and disposed with the scope; call during `setup()`.\n */\nexport function useTreeModel(options: {\n store: () => DocumentStore\n editor: () => Editor | undefined\n selectedFolder: Ref<string>\n}) {\n const view = treeView(useEditorContext())\n const { t } = useEditorI18n()\n const { settings } = useEditorSettings()\n const notify = useUiKit().notifier()\n const { importFiles } = useFileImport(options.editor)\n\n const treeEditor = computed(() => {\n const editor = options.editor()\n return editor ? treeEditorOf(editor, t) : undefined\n })\n const context = computed(() => ({\n editor: treeEditor.value,\n structure: settings.value.blocks.structure,\n treeRoot: settings.value.blocks.treeRoot,\n }))\n\n const execute = createTreeExecutor({\n store: options.store,\n editor: options.editor,\n importFiles,\n warn: (title, description) => {\n notify({ title, description, kind: 'warning', icon: 'i-lucide-circle-alert' })\n },\n deleted: (title, undo) => {\n notify({\n title: t('tree.removed'),\n description: title,\n icon: 'i-lucide-trash-2',\n action: { label: t('tree.undo'), onClick: undo },\n })\n },\n })\n\n const model = shallowRef<TreeModel>(createTreeModel({ t, store: options.store(), view, execute, context: context.value, selectedFolder: options.selectedFolder.value }))\n const state = useStoreRef<TreeState>(() => model.value.state)\n\n watch(options.store, (store) => {\n model.value.dispose()\n model.value = createTreeModel({ t, store, view, execute, context: context.value, selectedFolder: options.selectedFolder.value })\n })\n watch(context, next => model.value.setContext(next), { flush: 'sync' })\n watch(options.selectedFolder, folder => model.value.setSelectedFolder(folder), { flush: 'sync' })\n watch(() => state.value.selectedFolder, (folder) => { options.selectedFolder.value = folder }, { flush: 'sync', immediate: true })\n onScopeDispose(() => model.value.dispose())\n\n return { model, state }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAmBA,SAAgB,aAAa,SAI1B;CACD,MAAM,OAAO,SAAS,iBAAiB,CAAC;CACxC,MAAM,EAAE,MAAM,cAAc;CAC5B,MAAM,EAAE,aAAa,kBAAkB;CACvC,MAAM,SAAS,SAAS,CAAC,CAAC,SAAS;CACnC,MAAM,EAAE,gBAAgB,cAAc,QAAQ,MAAM;CAEpD,MAAM,aAAa,eAAe;EAChC,MAAM,SAAS,QAAQ,OAAO;EAC9B,OAAO,SAAS,aAAa,QAAQ,CAAC,IAAI,KAAA;CAC5C,CAAC;CACD,MAAM,UAAU,gBAAgB;EAC9B,QAAQ,WAAW;EACnB,WAAW,SAAS,MAAM,OAAO;EACjC,UAAU,SAAS,MAAM,OAAO;CAClC,EAAE;CAEF,MAAM,UAAU,mBAAmB;EACjC,OAAO,QAAQ;EACf,QAAQ,QAAQ;EAChB;EACA,OAAO,OAAO,gBAAgB;GAC5B,OAAO;IAAE;IAAO;IAAa,MAAM;IAAW,MAAM;GAAwB,CAAC;EAC/E;EACA,UAAU,OAAO,SAAS;GACxB,OAAO;IACL,OAAO,EAAE,cAAc;IACvB,aAAa;IACb,MAAM;IACN,QAAQ;KAAE,OAAO,EAAE,WAAW;KAAG,SAAS;IAAK;GACjD,CAAC;EACH;CACF,CAAC;CAED,MAAM,QAAQ,WAAsB,gBAAgB;EAAE;EAAG,OAAO,QAAQ,MAAM;EAAG;EAAM;EAAS,SAAS,QAAQ;EAAO,gBAAgB,QAAQ,eAAe;CAAM,CAAC,CAAC;CACvK,MAAM,QAAQ,kBAA6B,MAAM,MAAM,KAAK;CAE5D,MAAM,QAAQ,QAAQ,UAAU;EAC9B,MAAM,MAAM,QAAQ;EACpB,MAAM,QAAQ,gBAAgB;GAAE;GAAG;GAAO;GAAM;GAAS,SAAS,QAAQ;GAAO,gBAAgB,QAAQ,eAAe;EAAM,CAAC;CACjI,CAAC;CACD,MAAM,UAAS,SAAQ,MAAM,MAAM,WAAW,IAAI,GAAG,EAAE,OAAO,OAAO,CAAC;CACtE,MAAM,QAAQ,iBAAgB,WAAU,MAAM,MAAM,kBAAkB,MAAM,GAAG,EAAE,OAAO,OAAO,CAAC;CAChG,YAAY,MAAM,MAAM,iBAAiB,WAAW;EAAE,QAAQ,eAAe,QAAQ;CAAO,GAAG;EAAE,OAAO;EAAQ,WAAW;CAAK,CAAC;CACjI,qBAAqB,MAAM,MAAM,QAAQ,CAAC;CAE1C,OAAO;EAAE;EAAO;CAAM;AACxB"}
package/dist/tree/vue.js CHANGED
@@ -1,3 +1,3 @@
1
- import { useTreeView } from "./tree-view.js";
2
1
  import _sfc_main from "./DocumentTree.js";
2
+ import { useTreeView } from "./tree-view.js";
3
3
  export { _sfc_main as DocumentTree, useTreeView };
@@ -46,21 +46,21 @@ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
46
46
  declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
47
47
  editor: import("vue").ComputedRef<Editor | undefined>;
48
48
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
49
+ settingsOpen: (tab?: string | undefined) => any;
49
50
  "update:sidebarOpen": (value: boolean) => any;
50
51
  "update:commentsOpen": (value: boolean) => any;
51
- ready: (editor: Editor) => any;
52
52
  "update:modelValue": (content: JSONContent) => any;
53
+ ready: (editor: Editor) => any;
53
54
  metadataCreate: (list: string, item: MetadataItem) => any;
54
55
  contentError: (error: Error) => any;
55
- settingsOpen: (tab?: string | undefined) => any;
56
56
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
57
+ onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
57
58
  "onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
58
59
  "onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
59
- onReady?: ((editor: Editor) => any) | undefined;
60
60
  "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
61
+ onReady?: ((editor: Editor) => any) | undefined;
61
62
  onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
62
63
  onContentError?: ((error: Error) => any) | undefined;
63
- onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
64
64
  }>, {
65
65
  editable: boolean;
66
66
  settingsUi: "internal" | "external";
@@ -18,13 +18,13 @@ type __VLS_Props = {
18
18
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
19
19
  editor: import("vue").ComputedRef<Editor | undefined>;
20
20
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
21
- ready: (editor: Editor) => any;
22
21
  "update:modelValue": (content: JSONContent) => any;
22
+ ready: (editor: Editor) => any;
23
23
  metadataCreate: (list: string, item: MetadataItem) => any;
24
24
  contentError: (error: Error) => any;
25
25
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
26
- onReady?: ((editor: Editor) => any) | undefined;
27
26
  "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
27
+ onReady?: ((editor: Editor) => any) | undefined;
28
28
  onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
29
29
  onContentError?: ((error: Error) => any) | undefined;
30
30
  }>, {
@@ -1,10 +1,5 @@
1
- import { presetExtension } from "../../core/pipeline.js";
2
- import { createMetadataRegistry } from "../../core/metadata.js";
3
- import { SuggestionPopup } from "../../core/suggestion-popup.js";
4
- import { contributions, settingsTabsOf } from "../../core/define-editor.js";
5
- import { registerEditor } from "../../core/editor-ui.js";
6
- import { blockActionsOf, createFeatureContext } from "../../core/feature-context.js";
7
- import { EMPTY_EDITOR_CONTENT } from "../../core/schema/index.js";
1
+ import { settingsTabsOf } from "../../core/define-editor.js";
2
+ import { createSurfaceSession } from "../../core/surface-session.js";
8
3
  import { useUiKit } from "../kit/use-ui-kit.js";
9
4
  import { attachNotifier } from "../notify.js";
10
5
  import { useEditorContext } from "../context.js";
@@ -12,7 +7,7 @@ import { EDITOR_UI } from "../use-editor-ui.js";
12
7
  import { vueSuggestionRenderer } from "../suggestion-renderer.js";
13
8
  import _sfc_main$1 from "./EditorSettingsModal.js";
14
9
  import { Fragment, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, mergeModels, onBeforeUnmount, openBlock, provide, ref, renderList, resolveDynamicComponent, toRaw, unref, useModel, watch } from "vue";
15
- import { EditorContent, getSchema, useEditor } from "@tiptap/vue-3";
10
+ import { EditorContent, useEditor } from "@tiptap/vue-3";
16
11
  //#region src/vue/components/EditorSurface.vue
17
12
  const _hoisted_1 = { class: "relative" };
18
13
  const _sfc_main = /*@__PURE__*/ defineComponent({
@@ -71,8 +66,6 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
71
66
  });
72
67
  const { preset } = props;
73
68
  const app = useEditorContext();
74
- const collaboration = props.collaboration;
75
- if (collaboration && !preset.features.some((feature) => feature.id === "collab")) throw new Error("@ecsplico/editor: `collaboration` needs a preset with the collaboration feature");
76
69
  const settingsOpen = ref(false);
77
70
  const settingsTab = ref();
78
71
  const ui = {
@@ -96,98 +89,45 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
96
89
  }
97
90
  };
98
91
  provide(EDITOR_UI, ui);
99
- let unregister;
100
- const registry = createMetadataRegistry({ onCreate: (list, item) => emit("metadataCreate", list, item) });
101
- registry.replace({ ...props.metadata });
102
- watch(() => props.metadata, (metadata) => registry.replace({ ...metadata }));
103
92
  const kitNotify = useUiKit().notifier();
104
93
  attachNotifier(app.notify, kitNotify);
105
- const ctx = createFeatureContext({
94
+ const session = createSurfaceSession({
106
95
  app,
107
96
  preset,
108
97
  ui,
109
- metadata: registry,
110
98
  notify: kitNotify,
99
+ suggestionRenderer: vueSuggestionRenderer,
111
100
  getEditor: () => editor.value,
112
- ...props.documentId !== void 0 && { documentId: props.documentId },
113
- ...props.upload && { upload: props.upload },
114
- ...collaboration && { collaboration },
115
- ...props.abilities && { abilities: props.abilities },
116
- ...props.slashCommands && { slashCommands: props.slashCommands }
117
- });
118
- const extensions = [
119
- presetExtension(preset),
120
- SuggestionPopup.configure({ renderer: vueSuggestionRenderer }),
121
- ...contributions(preset, "extensions").flatMap((factory) => factory(ctx)),
122
- ...props.extensions ?? []
123
- ];
124
- const schema = getSchema(extensions);
125
- const overlays = preset.features.flatMap((feature) => [...feature.overlays ?? []]);
126
- const blockActions = blockActionsOf(preset, ctx, props.blockActions);
127
- const teardowns = [];
128
- for (const run of contributions(preset, "setup")) {
129
- const teardown = run(ctx);
130
- if (teardown) teardowns.push(teardown);
131
- }
132
- /** `json` migrated by the preset and checked against the installed schema: unknown nodes/marks or a broken structure are errors. */
133
- function validated(json) {
134
- try {
135
- const migrated = preset.pipeline.migrateContent(toRaw(json));
136
- schema.nodeFromJSON(migrated).check();
137
- return { json: migrated };
138
- } catch (error) {
139
- return { error: error instanceof Error ? error : new Error(String(error)) };
140
- }
141
- }
142
- let lastEmitted;
143
- let standaloneOptions = {};
144
- if (!collaboration) {
145
- const initial = props.modelValue ? validated(props.modelValue) : { json: EMPTY_EDITOR_CONTENT };
146
- if ("error" in initial) emit("contentError", initial.error);
147
- standaloneOptions = {
148
- content: "json" in initial ? initial.json : EMPTY_EDITOR_CONTENT,
149
- onUpdate: ({ editor: instance }) => {
150
- lastEmitted = instance.getJSON();
151
- emit("update:modelValue", lastEmitted);
152
- }
153
- };
154
- }
155
- const editor = useEditor({
156
- extensions,
157
- ...standaloneOptions,
101
+ collaboration: props.collaboration,
102
+ value: props.modelValue && toRaw(props.modelValue),
158
103
  editable: props.editable,
159
- editorProps: { attributes: { class: "tiptap" } }
160
- });
161
- if (!collaboration) watch(() => props.modelValue, (value) => {
162
- const instance = editor.value;
163
- if (!value || !instance || instance.isDestroyed || toRaw(value) === lastEmitted) return;
164
- const checked = validated(value);
165
- if ("error" in checked) {
166
- emit("contentError", checked.error);
167
- return;
168
- }
169
- instance.commands.setContent(checked.json, { emitUpdate: false });
170
- });
171
- watch(() => props.editable, (editable) => {
172
- const instance = editor.value;
173
- if (!instance || instance.isDestroyed) return;
174
- instance.setEditable(editable, false);
175
- instance.view.dispatch(instance.state.tr.setMeta("addToHistory", false));
104
+ metadata: props.metadata,
105
+ documentId: props.documentId,
106
+ upload: props.upload,
107
+ abilities: props.abilities,
108
+ slashCommands: props.slashCommands,
109
+ extensions: props.extensions,
110
+ blockActions: props.blockActions,
111
+ onChange: (content) => emit("update:modelValue", content),
112
+ onMetadataCreate: (list, item) => emit("metadataCreate", list, item),
113
+ onContentError: (error) => emit("contentError", error),
114
+ onReady: (instance) => emit("ready", instance)
176
115
  });
116
+ const overlays = session.overlays;
117
+ session.activate();
118
+ const editor = useEditor(session.editorOptions);
119
+ watch(() => props.modelValue, (value) => session.setValue(value && toRaw(value)));
120
+ watch(() => props.editable, (editable) => session.setEditable(editable));
121
+ watch(() => props.metadata, (metadata) => session.setMetadata(metadata));
122
+ let unregister;
177
123
  const stopReady = watch(editor, (instance) => {
178
124
  if (!instance) return;
179
- unregister = registerEditor(toRaw(instance), {
180
- ui,
181
- preset,
182
- ctx,
183
- blockActions
184
- });
185
- emit("ready", instance);
125
+ unregister = session.register(toRaw(instance));
186
126
  stopReady();
187
127
  });
188
128
  onBeforeUnmount(() => {
189
129
  unregister?.();
190
- for (const teardown of teardowns.splice(0)) teardown();
130
+ session.deactivate();
191
131
  editor.value?.destroy();
192
132
  });
193
133
  __expose({
@@ -1 +1 @@
1
- {"version":3,"file":"EditorSurface.js","names":["_useModel","_unref","_openBlock","_createElementBlock","_Fragment","_createElementVNode","_createVNode","_renderList","_createBlock","_resolveDynamicComponent","EditorSettingsModal","_createCommentVNode"],"sources":["../../../src/vue/components/EditorSurface.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { AnyExtension, Editor, EditorOptions, JSONContent } from '@tiptap/vue-3'\nimport { EditorContent, getSchema, useEditor } from '@tiptap/vue-3'\nimport { type Component, onBeforeUnmount, provide, ref, toRaw, watch } from 'vue'\nimport { contributions, settingsTabsOf } from '../../core/define-editor'\nimport { type EditorUi, registerEditor } from '../../core/editor-ui'\nimport { useEditorContext } from '../context'\nimport { attachNotifier } from '../notify'\nimport { useUiKit } from '../kit/use-ui-kit'\nimport { EDITOR_UI } from '../use-editor-ui'\nimport type { AbilitiesOptions, BlockAction, SlashCommandItem } from '../../core/extension-points'\nimport { type CollaborationSession, type EditorPreset, type UploadFn } from '../../core/feature'\nimport { blockActionsOf, createFeatureContext } from '../../core/feature-context'\nimport { createMetadataRegistry } from '../../core/metadata'\nimport type { EditorMetadata, MetadataItem } from '../../core/metadata-types'\nimport { presetExtension } from '../../core/pipeline'\nimport { EMPTY_EDITOR_CONTENT } from '../../core/schema'\nimport { SuggestionPopup } from '../../core/suggestion-popup'\nimport { vueSuggestionRenderer } from '../suggestion-renderer'\nimport EditorSettingsModal from './EditorSettingsModal.vue'\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<{\n /** The features this editor is made of (`simpleEditor()`, `defineEditor(...)`); fixed at mount, remount (`key`) to change it. */\n preset: EditorPreset\n /** Collaborative mode: the Y.Doc is the document. Needs a preset with the collaboration feature; fixed at mount. */\n collaboration?: CollaborationSession\n /** Standalone mode (without `collaboration`): TipTap JSON via `v-model`; the editor keeps its own undo history. */\n modelValue?: JSONContent\n /** Host lists (`mentions`, `hashtags`, `speakers`, custom) features' suggestions search. */\n metadata?: EditorMetadata\n /** Deep-merged into the `Abilities` defaults; read once at setup. */\n abilities?: Partial<AbilitiesOptions>\n /** Added after the features' extensions; read once at setup. */\n extensions?: AnyExtension[]\n /** Hover actions shown after the features' own; shown by an installed block-UI feature. */\n blockActions?: BlockAction[]\n /** Builds the `/` menu from the features' commands; absent = the features' commands. Read once at setup. */\n slashCommands?: (defaults: SlashCommandItem[]) => SlashCommandItem[]\n /** Document whose package stores uploads (media feature). Read once at setup. */\n documentId?: string\n /** Stores an uploaded file and returns its URL (media feature). Read once at setup. */\n upload?: UploadFn\n editable?: boolean\n /** `internal`: the surface owns its settings UI; `external`: it emits `settingsOpen` and the host shows the panel. */\n settingsUi?: 'internal' | 'external'\n}>(), { editable: true, settingsUi: 'internal' })\n\nconst emit = defineEmits<{\n 'ready': [editor: Editor]\n 'update:modelValue': [content: JSONContent]\n 'metadataCreate': [list: string, item: MetadataItem]\n /** The JSON handed in (initially or later) does not fit this editor's schema; the last valid document stays. */\n 'contentError': [error: Error]\n /** `settingsUi: 'external'`: the editor asks the host to open its settings (on `tab` when given). */\n 'settingsOpen': [tab?: string]\n}>()\n\n/** Whether this editor's AI sidebar is open (`toggleSidebar`): hosts bind it (`v-model:sidebar-open`). */\nconst sidebarOpen = defineModel<boolean>('sidebarOpen', { default: false })\n/**\n * Whether this editor's comments margin is open (`toggleComments`, Mod-Shift-m of the comments feature): hosts bind it\n * (`v-model:comments-open`). The AI sidebar and the margin exclude each other: whichever opens last closes the other.\n */\nconst commentsOpen = defineModel<boolean>('commentsOpen', { default: false })\nwatch(sidebarOpen, (open) => {\n if (open) commentsOpen.value = false\n})\nwatch(commentsOpen, (open) => {\n if (open) sidebarOpen.value = false\n})\n\nconst { preset } = props\nconst app = useEditorContext()\n\n// The mode is fixed at setup; hosts switch it by remounting (`key`).\nconst collaboration = props.collaboration\nif (collaboration && !preset.features.some(feature => feature.id === 'collab')) {\n throw new Error('@ecsplico/editor: `collaboration` needs a preset with the collaboration feature')\n}\n\n// The editor's chrome for its subtree (EDITOR_UI) and, registered by editor instance once it exists, for components\n// a host places beside it (EditorToolbar, outline, sidebar).\nconst settingsOpen = ref(false)\nconst settingsTab = ref<string | undefined>()\nconst ui: EditorUi = {\n openSettings: (tab) => {\n // Nothing to open without a contributed tab.\n if (!settingsTabsOf(preset, app).length) return\n if (props.settingsUi === 'external') {\n emit('settingsOpen', tab)\n return\n }\n if (tab) settingsTab.value = tab\n settingsOpen.value = true\n },\n toggleSidebar: () => {\n sidebarOpen.value = !sidebarOpen.value\n },\n toggleComments: () => {\n commentsOpen.value = !commentsOpen.value\n },\n get commentsOpen() {\n return commentsOpen.value\n },\n}\nprovide(EDITOR_UI, ui)\nlet unregister: (() => void) | undefined\n\nconst registry = createMetadataRegistry({ onCreate: (list, item) => emit('metadataCreate', list, item) })\nregistry.replace({ ...props.metadata })\nwatch(() => props.metadata, metadata => registry.replace({ ...metadata }))\n\n// Captured in setup: features raise notices later, outside it.\nconst kitNotify = useUiKit().notifier()\nattachNotifier(app.notify, kitNotify)\n\nconst ctx = createFeatureContext({\n app,\n preset,\n ui,\n metadata: registry,\n notify: kitNotify,\n getEditor: () => editor.value,\n ...(props.documentId !== undefined && { documentId: props.documentId }),\n ...(props.upload && { upload: props.upload }),\n ...(collaboration && { collaboration }),\n ...(props.abilities && { abilities: props.abilities }),\n ...(props.slashCommands && { slashCommands: props.slashCommands }),\n})\n\nconst extensions: AnyExtension[] = [\n presetExtension(preset),\n SuggestionPopup.configure({ renderer: vueSuggestionRenderer }),\n ...contributions(preset, 'extensions').flatMap(factory => factory(ctx)),\n ...(props.extensions ?? []),\n]\nconst schema = getSchema(extensions)\n// Descriptors are framework-agnostic (`overlays: unknown[]`); in this Vue surface they are Vue components.\nconst overlays = preset.features.flatMap(feature => [...(feature.overlays ?? [])]) as Component[]\nconst blockActions = blockActionsOf(preset, ctx, props.blockActions)\n// A setup contribution may return its teardown; they run when the surface unmounts.\nconst teardowns: (() => void)[] = []\nfor (const run of contributions(preset, 'setup')) {\n const teardown = run(ctx)\n if (teardown) teardowns.push(teardown)\n}\n\n/** `json` migrated by the preset and checked against the installed schema: unknown nodes/marks or a broken structure are errors. */\nfunction validated(json: JSONContent): { json: JSONContent } | { error: Error } {\n try {\n const migrated = preset.pipeline.migrateContent(toRaw(json))\n schema.nodeFromJSON(migrated).check()\n return { json: migrated }\n }\n catch (error) {\n return { error: error instanceof Error ? error : new Error(String(error)) }\n }\n}\n\n// Standalone: the JSON last sent through `update:modelValue`; the host handing it back is not an external change.\nlet lastEmitted: JSONContent | undefined\nlet standaloneOptions: Partial<EditorOptions> = {}\nif (!collaboration) {\n // Raw: a reactive host value would put Vue proxies into node attributes.\n const initial = props.modelValue ? validated(props.modelValue) : { json: EMPTY_EDITOR_CONTENT }\n if ('error' in initial) emit('contentError', initial.error)\n standaloneOptions = {\n content: 'json' in initial ? initial.json : EMPTY_EDITOR_CONTENT,\n onUpdate: ({ editor: instance }) => {\n lastEmitted = instance.getJSON()\n emit('update:modelValue', lastEmitted)\n },\n }\n}\n\nconst editor = useEditor({\n extensions,\n ...standaloneOptions,\n editable: props.editable,\n editorProps: { attributes: { class: 'tiptap' } },\n})\n\n// External replacement of the standalone document (the host loaded or reset it); not an edit, so no update event.\nif (!collaboration) {\n watch(() => props.modelValue, (value) => {\n const instance = editor.value\n if (!value || !instance || instance.isDestroyed || toRaw(value) === lastEmitted) return\n const checked = validated(value)\n if ('error' in checked) {\n emit('contentError', checked.error)\n return\n }\n instance.commands.setContent(checked.json, { emitUpdate: false })\n })\n}\n\n// `setEditable(…, false)` changes the flag without any event; an empty, history-less transaction lets components that\n// follow `isEditable` on `transaction` (block UI, the composite inspector) notice. It has no steps, so nothing syncs.\nwatch(() => props.editable, (editable) => {\n const instance = editor.value\n if (!instance || instance.isDestroyed) return\n instance.setEditable(editable, false)\n instance.view.dispatch(instance.state.tr.setMeta('addToHistory', false))\n})\n\n// `useEditor` creates the instance in onMounted; announce it once it exists (its chrome registered first, so a host\n// toolbar that receives it through `ready` finds the chrome).\nconst stopReady = watch(editor, (instance) => {\n if (!instance) return\n unregister = registerEditor(toRaw(instance), { ui, preset, ctx, blockActions })\n emit('ready', instance)\n stopReady()\n})\n\nonBeforeUnmount(() => {\n unregister?.()\n for (const teardown of teardowns.splice(0)) teardown()\n editor.value?.destroy()\n})\n\ndefineExpose({ editor, settingsOpen, settingsTab })\n</script>\n\n<template>\n <template v-if=\"editor\">\n <div class=\"relative\">\n <EditorContent :editor=\"editor\" class=\"relative\" />\n </div>\n <component :is=\"overlay\" v-for=\"(overlay, index) in overlays\" :key=\"index\" :editor=\"editor\" />\n <EditorSettingsModal v-if=\"settingsUi === 'internal'\" v-model:open=\"settingsOpen\" v-model:tab=\"settingsTab\" :preset=\"preset\" />\n </template>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"EditorSurface.js","names":["_useModel","_unref","_openBlock","_createElementBlock","_Fragment","_createElementVNode","_createVNode","_renderList","_createBlock","_resolveDynamicComponent","EditorSettingsModal","_createCommentVNode"],"sources":["../../../src/vue/components/EditorSurface.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { AnyExtension, Editor, JSONContent } from '@tiptap/vue-3'\nimport { EditorContent, useEditor } from '@tiptap/vue-3'\nimport { type Component, onBeforeUnmount, provide, ref, toRaw, watch } from 'vue'\nimport { settingsTabsOf } from '../../core/define-editor'\nimport type { EditorUi } from '../../core/editor-ui'\nimport { useEditorContext } from '../context'\nimport { attachNotifier } from '../notify'\nimport { useUiKit } from '../kit/use-ui-kit'\nimport { EDITOR_UI } from '../use-editor-ui'\nimport type { AbilitiesOptions, BlockAction, SlashCommandItem } from '../../core/extension-points'\nimport type { CollaborationSession, EditorPreset, UploadFn } from '../../core/feature'\nimport type { EditorMetadata, MetadataItem } from '../../core/metadata-types'\nimport { createSurfaceSession } from '../../core/surface-session'\nimport { vueSuggestionRenderer } from '../suggestion-renderer'\nimport EditorSettingsModal from './EditorSettingsModal.vue'\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<{\n /** The features this editor is made of (`simpleEditor()`, `defineEditor(...)`); fixed at mount, remount (`key`) to change it. */\n preset: EditorPreset\n /** Collaborative mode: the Y.Doc is the document. Needs a preset with the collaboration feature; fixed at mount. */\n collaboration?: CollaborationSession\n /** Standalone mode (without `collaboration`): TipTap JSON via `v-model`; the editor keeps its own undo history. */\n modelValue?: JSONContent\n /** Host lists (`mentions`, `hashtags`, `speakers`, custom) features' suggestions search. */\n metadata?: EditorMetadata\n /** Deep-merged into the `Abilities` defaults; read once at setup. */\n abilities?: Partial<AbilitiesOptions>\n /** Added after the features' extensions; read once at setup. */\n extensions?: AnyExtension[]\n /** Hover actions shown after the features' own; shown by an installed block-UI feature. */\n blockActions?: BlockAction[]\n /** Builds the `/` menu from the features' commands; absent = the features' commands. Read once at setup. */\n slashCommands?: (defaults: SlashCommandItem[]) => SlashCommandItem[]\n /** Document whose package stores uploads (media feature). Read once at setup. */\n documentId?: string\n /** Stores an uploaded file and returns its URL (media feature). Read once at setup. */\n upload?: UploadFn\n editable?: boolean\n /** `internal`: the surface owns its settings UI; `external`: it emits `settingsOpen` and the host shows the panel. */\n settingsUi?: 'internal' | 'external'\n}>(), { editable: true, settingsUi: 'internal' })\n\nconst emit = defineEmits<{\n 'ready': [editor: Editor]\n 'update:modelValue': [content: JSONContent]\n 'metadataCreate': [list: string, item: MetadataItem]\n /** The JSON handed in (initially or later) does not fit this editor's schema; the last valid document stays. */\n 'contentError': [error: Error]\n /** `settingsUi: 'external'`: the editor asks the host to open its settings (on `tab` when given). */\n 'settingsOpen': [tab?: string]\n}>()\n\n/** Whether this editor's AI sidebar is open (`toggleSidebar`): hosts bind it (`v-model:sidebar-open`). */\nconst sidebarOpen = defineModel<boolean>('sidebarOpen', { default: false })\n/**\n * Whether this editor's comments margin is open (`toggleComments`, Mod-Shift-m of the comments feature): hosts bind it\n * (`v-model:comments-open`). The AI sidebar and the margin exclude each other: whichever opens last closes the other.\n */\nconst commentsOpen = defineModel<boolean>('commentsOpen', { default: false })\nwatch(sidebarOpen, (open) => {\n if (open) commentsOpen.value = false\n})\nwatch(commentsOpen, (open) => {\n if (open) sidebarOpen.value = false\n})\n\nconst { preset } = props\nconst app = useEditorContext()\n\n\n// The editor's chrome for its subtree (EDITOR_UI) and, registered by editor instance once it exists, for components\n// a host places beside it (EditorToolbar, outline, sidebar).\nconst settingsOpen = ref(false)\nconst settingsTab = ref<string | undefined>()\nconst ui: EditorUi = {\n openSettings: (tab) => {\n // Nothing to open without a contributed tab.\n if (!settingsTabsOf(preset, app).length) return\n if (props.settingsUi === 'external') {\n emit('settingsOpen', tab)\n return\n }\n if (tab) settingsTab.value = tab\n settingsOpen.value = true\n },\n toggleSidebar: () => {\n sidebarOpen.value = !sidebarOpen.value\n },\n toggleComments: () => {\n commentsOpen.value = !commentsOpen.value\n },\n get commentsOpen() {\n return commentsOpen.value\n },\n}\nprovide(EDITOR_UI, ui)\n\n// Captured in setup: features raise notices later, outside it.\nconst kitNotify = useUiKit().notifier()\nattachNotifier(app.notify, kitNotify)\n\n// Everything framework-free: mode, context, extensions, content validation, setup lifecycle. The mode is fixed at\n// setup; hosts switch it by remounting (`key`).\nconst session = createSurfaceSession({\n app,\n preset,\n ui,\n notify: kitNotify,\n suggestionRenderer: vueSuggestionRenderer,\n getEditor: () => editor.value,\n collaboration: props.collaboration,\n // Raw: a reactive host value would put Vue proxies into node attributes.\n value: props.modelValue && toRaw(props.modelValue),\n editable: props.editable,\n metadata: props.metadata,\n documentId: props.documentId,\n upload: props.upload,\n abilities: props.abilities,\n slashCommands: props.slashCommands,\n extensions: props.extensions,\n blockActions: props.blockActions,\n onChange: content => emit('update:modelValue', content),\n onMetadataCreate: (list, item) => emit('metadataCreate', list, item),\n onContentError: error => emit('contentError', error),\n // The registered instance is the one `useEditor` created: the vue-3 `Editor` subclass.\n onReady: instance => emit('ready', instance as Editor),\n})\n// Descriptors are framework-agnostic (`overlays: unknown[]`); in this Vue surface they are Vue components.\nconst overlays = session.overlays as readonly Component[]\n\n// Setup contributions run during setup (also on the server) and are torn down when the surface unmounts.\nsession.activate()\n\nconst editor = useEditor(session.editorOptions)\n\nwatch(() => props.modelValue, value => session.setValue(value && toRaw(value)))\nwatch(() => props.editable, editable => session.setEditable(editable))\nwatch(() => props.metadata, metadata => session.setMetadata(metadata))\n\n// `useEditor` creates the instance in onMounted; announce it once it exists (its chrome registered first, so a host\n// toolbar that receives it through `ready` finds the chrome).\nlet unregister: (() => void) | undefined\nconst stopReady = watch(editor, (instance) => {\n if (!instance) return\n unregister = session.register(toRaw(instance))\n stopReady()\n})\n\nonBeforeUnmount(() => {\n unregister?.()\n session.deactivate()\n editor.value?.destroy()\n})\n\ndefineExpose({ editor, settingsOpen, settingsTab })\n</script>\n\n<template>\n <template v-if=\"editor\">\n <div class=\"relative\">\n <EditorContent :editor=\"editor\" class=\"relative\" />\n </div>\n <component :is=\"overlay\" v-for=\"(overlay, index) in overlays\" :key=\"index\" :editor=\"editor\" />\n <EditorSettingsModal v-if=\"settingsUi === 'internal'\" v-model:open=\"settingsOpen\" v-model:tab=\"settingsTab\" :preset=\"preset\" />\n </template>\n</template>\n"],"mappings":"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"}
@@ -43,21 +43,21 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
43
43
  settingsOpen: import("vue").Ref<boolean, boolean>;
44
44
  settingsTab: import("vue").Ref<string | undefined, string | undefined>;
45
45
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
46
+ settingsOpen: (tab?: string | undefined) => any;
46
47
  "update:sidebarOpen": (value: boolean) => any;
47
48
  "update:commentsOpen": (value: boolean) => any;
48
- ready: (editor: Editor) => any;
49
49
  "update:modelValue": (content: JSONContent) => any;
50
+ ready: (editor: Editor) => any;
50
51
  metadataCreate: (list: string, item: MetadataItem) => any;
51
52
  contentError: (error: Error) => any;
52
- settingsOpen: (tab?: string | undefined) => any;
53
53
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
54
+ onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
54
55
  "onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
55
56
  "onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
56
- onReady?: ((editor: Editor) => any) | undefined;
57
57
  "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
58
+ onReady?: ((editor: Editor) => any) | undefined;
58
59
  onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
59
60
  onContentError?: ((error: Error) => any) | undefined;
60
- onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
61
61
  }>, {
62
62
  editable: boolean;
63
63
  settingsUi: "internal" | "external";