@ecsplico/editor 1.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 (363) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +7 -7
  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/notify.d.ts +1 -1
  57. package/dist/core/notify.js.map +1 -1
  58. package/dist/core/surface-session.js +139 -0
  59. package/dist/core/surface-session.js.map +1 -0
  60. package/dist/document/DocumentSurface.d.ts +1 -1
  61. package/dist/document/DocumentSurface.vue.d.ts +3 -3
  62. package/dist/features/ai/agent-op-review.d.ts +2 -2
  63. package/dist/features/ai/agent-op-review.js.map +1 -1
  64. package/dist/features/ai/agent-ops.d.ts +1 -1
  65. package/dist/features/ai/agent-ops.js +9 -6
  66. package/dist/features/ai/agent-ops.js.map +1 -1
  67. package/dist/features/ai/ai-apply.d.ts +1 -1
  68. package/dist/features/ai/ai-review.js +10 -1
  69. package/dist/features/ai/ai-review.js.map +1 -1
  70. package/dist/features/ai/components/AiSelectionMenu.js +78 -252
  71. package/dist/features/ai/components/AiSelectionMenu.js.map +1 -1
  72. package/dist/features/ai/components/AiSelectionMenu.vue.d.ts +2 -1
  73. package/dist/features/ai/components/AiSidebar.js +2 -2
  74. package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
  75. package/dist/features/ai/components/DocumentChatOps.js +1 -0
  76. package/dist/features/ai/components/DocumentChatOps.js.map +1 -1
  77. package/dist/features/ai/components/ModelQuickSwitch.js +1 -1
  78. package/dist/features/ai/components/SettingsAiTab.js +1 -1
  79. package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
  80. package/dist/features/ai/document-chat.d.ts +3 -2
  81. package/dist/features/ai/document-chat.js +16 -19
  82. package/dist/features/ai/document-chat.js.map +1 -1
  83. package/dist/features/ai/index.d.ts +5 -3
  84. package/dist/features/ai/index.js +4 -2
  85. package/dist/features/ai/protocol/agent-ops.d.ts +22 -17
  86. package/dist/features/ai/protocol/agent-ops.js +65 -39
  87. package/dist/features/ai/protocol/agent-ops.js.map +1 -1
  88. package/dist/features/ai/protocol/block-lines.d.ts +89 -0
  89. package/dist/features/ai/protocol/block-lines.js +177 -0
  90. package/dist/features/ai/protocol/block-lines.js.map +1 -0
  91. package/dist/features/ai/protocol/chat.js +10 -10
  92. package/dist/features/ai/protocol/chat.js.map +1 -1
  93. package/dist/features/ai/protocol/index.d.ts +3 -2
  94. package/dist/features/ai/protocol/index.js +3 -2
  95. package/dist/features/ai/react/AiSelectionMenu.d.ts +3 -2
  96. package/dist/features/ai/react/AiSelectionMenu.js +64 -255
  97. package/dist/features/ai/react/AiSelectionMenu.js.map +1 -1
  98. package/dist/features/ai/react/AiSidebar.js +2 -2
  99. package/dist/features/ai/react/DocumentChatAnswer.js +1 -1
  100. package/dist/features/ai/react/DocumentChatOps.js +1 -0
  101. package/dist/features/ai/react/DocumentChatOps.js.map +1 -1
  102. package/dist/features/ai/react/ModelQuickSwitch.js +1 -1
  103. package/dist/features/ai/react/SettingsAiTab.js +1 -1
  104. package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
  105. package/dist/features/ai/react/use-ai-selection-flow.js +34 -0
  106. package/dist/features/ai/react/use-ai-selection-flow.js.map +1 -0
  107. package/dist/features/ai/selection-flow.d.ts +133 -0
  108. package/dist/features/ai/selection-flow.js +283 -0
  109. package/dist/features/ai/selection-flow.js.map +1 -0
  110. package/dist/features/ai/use-ai-assistant.d.ts +1 -1
  111. package/dist/features/ai/use-document-chat.d.ts +1 -1
  112. package/dist/features/authors/components/AuthorColorsPanel.js +1 -1
  113. package/dist/features/authors/feature.js +1 -1
  114. package/dist/features/authors/index.js +1 -1
  115. package/dist/features/authors/react/AuthorColorsPanel.js +1 -1
  116. package/dist/features/comments/components/CommentBubble.js +1 -1
  117. package/dist/features/comments/components/CommentComposer.js +1 -1
  118. package/dist/features/comments/components/CommentThreadView.js +5 -2
  119. package/dist/features/comments/components/CommentThreadView.js.map +1 -1
  120. package/dist/features/comments/components/CommentsMargin.js +108 -230
  121. package/dist/features/comments/components/CommentsMargin.js.map +1 -1
  122. package/dist/features/comments/components/CommentsOverlay.js +1 -1
  123. package/dist/features/comments/feature.js +1 -1
  124. package/dist/features/comments/index.d.ts +4 -2
  125. package/dist/features/comments/index.js +4 -2
  126. package/dist/features/comments/margin-controller.d.ts +44 -0
  127. package/dist/features/comments/margin-controller.js +132 -0
  128. package/dist/features/comments/margin-controller.js.map +1 -0
  129. package/dist/features/comments/margin-layout.d.ts +55 -0
  130. package/dist/features/comments/margin-layout.js +79 -0
  131. package/dist/features/comments/margin-layout.js.map +1 -0
  132. package/dist/features/comments/model.d.ts +8 -0
  133. package/dist/features/comments/model.js +60 -34
  134. package/dist/features/comments/model.js.map +1 -1
  135. package/dist/features/comments/react/CommentBubble.js +1 -1
  136. package/dist/features/comments/react/CommentComposer.js +1 -1
  137. package/dist/features/comments/react/CommentThreadView.js +2 -2
  138. package/dist/features/comments/react/CommentThreadView.js.map +1 -1
  139. package/dist/features/comments/react/CommentsMargin.d.ts +0 -5
  140. package/dist/features/comments/react/CommentsMargin.js +85 -251
  141. package/dist/features/comments/react/CommentsMargin.js.map +1 -1
  142. package/dist/features/comments/react/CommentsOverlay.js +1 -1
  143. package/dist/features/comments/react/use-comments.js +1 -1
  144. package/dist/features/comments/react/use-margin-layout.js +45 -0
  145. package/dist/features/comments/react/use-margin-layout.js.map +1 -0
  146. package/dist/features/comments/react.d.ts +1 -1
  147. package/dist/features/comments/schema.d.ts +23 -0
  148. package/dist/features/comments/schema.js.map +1 -1
  149. package/dist/features/comments/snapshot-equality.js +40 -0
  150. package/dist/features/comments/snapshot-equality.js.map +1 -0
  151. package/dist/features/comments/thread-anchor.js +33 -0
  152. package/dist/features/comments/thread-anchor.js.map +1 -0
  153. package/dist/features/comments/use-comments.d.ts +3 -1
  154. package/dist/features/comments/use-comments.js +1 -0
  155. package/dist/features/comments/use-comments.js.map +1 -1
  156. package/dist/features/comments/use-margin-layout.js +29 -0
  157. package/dist/features/comments/use-margin-layout.js.map +1 -0
  158. package/dist/features/comments/vue.d.ts +1 -1
  159. package/dist/features/composites/accepted-children.d.ts +21 -0
  160. package/dist/features/composites/accepted-children.js +126 -0
  161. package/dist/features/composites/accepted-children.js.map +1 -0
  162. package/dist/features/composites/components/CompositeDetails.js +1 -1
  163. package/dist/features/composites/components/CompositeInspector.js +38 -164
  164. package/dist/features/composites/components/CompositeInspector.js.map +1 -1
  165. package/dist/features/composites/feature.d.ts +2 -1
  166. package/dist/features/composites/feature.js +8 -2
  167. package/dist/features/composites/feature.js.map +1 -1
  168. package/dist/features/composites/index.d.ts +5 -3
  169. package/dist/features/composites/index.js +8 -6
  170. package/dist/features/composites/inspector-model.d.ts +97 -0
  171. package/dist/features/composites/inspector-model.js +255 -0
  172. package/dist/features/composites/inspector-model.js.map +1 -0
  173. package/dist/features/composites/kinds.d.ts +11 -0
  174. package/dist/features/composites/kinds.js +17 -2
  175. package/dist/features/composites/kinds.js.map +1 -1
  176. package/dist/features/composites/outline.d.ts +18 -5
  177. package/dist/features/composites/outline.js +44 -12
  178. package/dist/features/composites/outline.js.map +1 -1
  179. package/dist/features/composites/react/CompositeDetails.js +1 -1
  180. package/dist/features/composites/react/CompositeInspector.d.ts +1 -1
  181. package/dist/features/composites/react/CompositeInspector.js +21 -180
  182. package/dist/features/composites/react/CompositeInspector.js.map +1 -1
  183. package/dist/features/composites/react/CompositeStatusMenu.d.ts +1 -1
  184. package/dist/features/composites/react/CompositeView.js +1 -1
  185. package/dist/features/composites/react/use-composite-inspector.js +29 -0
  186. package/dist/features/composites/react/use-composite-inspector.js.map +1 -0
  187. package/dist/features/composites/schema.d.ts +2 -2
  188. package/dist/features/composites/schema.js +2 -2
  189. package/dist/features/composites/schema.js.map +1 -1
  190. package/dist/features/composites/use-composite-anchors.d.ts +1 -1
  191. package/dist/features/composites/use-composite-inspector.js +41 -0
  192. package/dist/features/composites/use-composite-inspector.js.map +1 -0
  193. package/dist/features/composites/use-composite-rows.d.ts +1 -1
  194. package/dist/features/media/index.d.ts +1 -1
  195. package/dist/features/media/vue.d.ts +1 -1
  196. package/dist/features/mentions/index.d.ts +3 -3
  197. package/dist/features/mentions/index.js +3 -3
  198. package/dist/features/mentions/schema.d.ts +1 -1
  199. package/dist/features/mentions/schema.js +1 -1
  200. package/dist/features/themes/index.d.ts +1 -1
  201. package/dist/features/themes/index.js +1 -1
  202. package/dist/features/transcript/feature.js +1 -1
  203. package/dist/features/transcript/index.js +2 -2
  204. package/dist/i18n/de/ai.js +2 -0
  205. package/dist/i18n/de/ai.js.map +1 -1
  206. package/dist/i18n/de/common.js +1 -2
  207. package/dist/i18n/de/common.js.map +1 -1
  208. package/dist/i18n/de/composites.js +8 -1
  209. package/dist/i18n/de/composites.js.map +1 -1
  210. package/dist/i18n/de/tree.js +14 -1
  211. package/dist/i18n/de/tree.js.map +1 -1
  212. package/dist/i18n/de.d.ts +22 -1
  213. package/dist/i18n/en/ai.js +2 -0
  214. package/dist/i18n/en/ai.js.map +1 -1
  215. package/dist/i18n/en/common.js +1 -2
  216. package/dist/i18n/en/common.js.map +1 -1
  217. package/dist/i18n/en/composites.js +8 -1
  218. package/dist/i18n/en/composites.js.map +1 -1
  219. package/dist/i18n/en/tree.js +14 -1
  220. package/dist/i18n/en/tree.js.map +1 -1
  221. package/dist/integrations/composite-ai/composite-fill.js +2 -2
  222. package/dist/integrations/composite-ai/composite-summary.js +2 -2
  223. package/dist/integrations/composite-ai/index.js +2 -2
  224. package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
  225. package/dist/markdown/MarkdownSurface.d.ts +1 -1
  226. package/dist/markdown/MarkdownSurface.js +2 -2
  227. package/dist/markdown/MarkdownSurface.vue.d.ts +7 -7
  228. package/dist/markdown/index.d.ts +2 -2
  229. package/dist/nuxt/module.d.ts +0 -6
  230. package/dist/nuxt/module.js +3 -5
  231. package/dist/nuxt/module.js.map +1 -1
  232. package/dist/nuxt/runtime/install-context.d.ts +2 -3
  233. package/dist/nuxt/runtime/install-context.js +2 -3
  234. package/dist/nuxt/runtime/install-context.js.map +1 -1
  235. package/dist/nuxt/runtime/plugins/editor-context-nuxt-ui.client.ts +1 -1
  236. package/dist/nuxt/runtime/server/routes/collab/[id].js +1 -1
  237. package/dist/react/EditorSurface.d.ts +1 -1
  238. package/dist/react/EditorSurface.js +43 -149
  239. package/dist/react/EditorSurface.js.map +1 -1
  240. package/dist/react/EditorToolbar.js +3 -40
  241. package/dist/react/EditorToolbar.js.map +1 -1
  242. package/dist/react/styles/ui-vars.css +2 -2
  243. package/dist/schema/advanced.js +2 -2
  244. package/dist/server/adapter.d.ts +2 -2
  245. package/dist/server/collab.js +1 -1
  246. package/dist/server/handlers/ai.d.ts +4 -2
  247. package/dist/server/handlers/ai.js +11 -9
  248. package/dist/server/handlers/ai.js.map +1 -1
  249. package/dist/server/handlers/context.d.ts +1 -1
  250. package/dist/server/handlers/editor.d.ts +1 -1
  251. package/dist/server/handlers/editor.js +2 -2
  252. package/dist/server/host.d.ts +1 -1
  253. package/dist/server/index.d.ts +2 -2
  254. package/dist/settings/index.d.ts +1 -1
  255. package/dist/styles/ai.css +1 -1
  256. package/dist/styles/authors.css +1 -1
  257. package/dist/styles/block-ui.css +1 -1
  258. package/dist/styles/collab.css +1 -1
  259. package/dist/styles/comments.css +1 -1
  260. package/dist/styles/core.css +2 -2
  261. package/dist/styles/media.css +1 -1
  262. package/dist/styles/mentions.css +1 -1
  263. package/dist/styles/themes.css +1 -1
  264. package/dist/styles/tokens.css +1 -1
  265. package/dist/styles/transcript.css +1 -1
  266. package/dist/tree/DocumentTree.js +159 -387
  267. package/dist/tree/DocumentTree.js.map +1 -1
  268. package/dist/tree/drop-rules.js +37 -0
  269. package/dist/tree/drop-rules.js.map +1 -0
  270. package/dist/tree/react/DocumentTree.d.ts +2 -2
  271. package/dist/tree/react/DocumentTree.js +135 -397
  272. package/dist/tree/react/DocumentTree.js.map +1 -1
  273. package/dist/tree/react/use-tree-model.js +129 -0
  274. package/dist/tree/react/use-tree-model.js.map +1 -0
  275. package/dist/tree/react.js +1 -1
  276. package/dist/tree/tree-host.js +100 -0
  277. package/dist/tree/tree-host.js.map +1 -0
  278. package/dist/tree/tree-items.js +22 -43
  279. package/dist/tree/tree-items.js.map +1 -1
  280. package/dist/tree/tree-model.js +580 -0
  281. package/dist/tree/tree-model.js.map +1 -0
  282. package/dist/tree/tree-rows.js +77 -0
  283. package/dist/tree/tree-rows.js.map +1 -0
  284. package/dist/tree/use-tree-model.js +94 -0
  285. package/dist/tree/use-tree-model.js.map +1 -0
  286. package/dist/tree/vue.js +1 -1
  287. package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
  288. package/dist/vue/SimpleEditor.vue.d.ts +2 -2
  289. package/dist/vue/components/EditorSurface.js +27 -87
  290. package/dist/vue/components/EditorSurface.js.map +1 -1
  291. package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
  292. package/dist/vue/components/EditorToolbar.js +7 -44
  293. package/dist/vue/components/EditorToolbar.js.map +1 -1
  294. package/dist/vue/context.d.ts +4 -4
  295. package/dist/vue/context.js +3 -2
  296. package/dist/vue/context.js.map +1 -1
  297. package/dist/vue/kit/types.d.ts +1 -1
  298. package/dist/vue/kit/use-ui-kit.d.ts +2 -2
  299. package/dist/vue/kit/use-ui-kit.js +3 -3
  300. package/dist/vue/kit/use-ui-kit.js.map +1 -1
  301. package/dist/vue/use-editor-ui.d.ts +1 -1
  302. package/dist/vue/use-notifier.d.ts +1 -1
  303. package/docs/integration.md +98 -5
  304. package/docs/llms.md +10 -2
  305. package/docs/usage.md +10 -5
  306. package/package.json +70 -23
  307. package/dist/nuxt/runtime/plugins/editor-context-primevue.client.ts +0 -8
  308. package/dist/vue/kit/primevue/Alert.js +0 -49
  309. package/dist/vue/kit/primevue/Alert.js.map +0 -1
  310. package/dist/vue/kit/primevue/Avatar.js +0 -54
  311. package/dist/vue/kit/primevue/Avatar.js.map +0 -1
  312. package/dist/vue/kit/primevue/Badge.js +0 -41
  313. package/dist/vue/kit/primevue/Badge.js.map +0 -1
  314. package/dist/vue/kit/primevue/Button.js +0 -112
  315. package/dist/vue/kit/primevue/Button.js.map +0 -1
  316. package/dist/vue/kit/primevue/Chip.js +0 -38
  317. package/dist/vue/kit/primevue/Chip.js.map +0 -1
  318. package/dist/vue/kit/primevue/DropdownMenu.js +0 -98
  319. package/dist/vue/kit/primevue/DropdownMenu.js.map +0 -1
  320. package/dist/vue/kit/primevue/FieldGroup.js +0 -22
  321. package/dist/vue/kit/primevue/FieldGroup.js.map +0 -1
  322. package/dist/vue/kit/primevue/FormField.js +0 -56
  323. package/dist/vue/kit/primevue/FormField.js.map +0 -1
  324. package/dist/vue/kit/primevue/Icon.js +0 -23
  325. package/dist/vue/kit/primevue/Icon.js.map +0 -1
  326. package/dist/vue/kit/primevue/Input.js +0 -96
  327. package/dist/vue/kit/primevue/Input.js.map +0 -1
  328. package/dist/vue/kit/primevue/InputMenu.js +0 -122
  329. package/dist/vue/kit/primevue/InputMenu.js.map +0 -1
  330. package/dist/vue/kit/primevue/Modal.js +0 -59
  331. package/dist/vue/kit/primevue/Modal.js.map +0 -1
  332. package/dist/vue/kit/primevue/Popover.js +0 -135
  333. package/dist/vue/kit/primevue/Popover.js.map +0 -1
  334. package/dist/vue/kit/primevue/RadioGroup.js +0 -58
  335. package/dist/vue/kit/primevue/RadioGroup.js.map +0 -1
  336. package/dist/vue/kit/primevue/Select.js +0 -68
  337. package/dist/vue/kit/primevue/Select.js.map +0 -1
  338. package/dist/vue/kit/primevue/SelectMenu.js +0 -94
  339. package/dist/vue/kit/primevue/SelectMenu.js.map +0 -1
  340. package/dist/vue/kit/primevue/Separator.js +0 -16
  341. package/dist/vue/kit/primevue/Separator.js.map +0 -1
  342. package/dist/vue/kit/primevue/Switch.js +0 -46
  343. package/dist/vue/kit/primevue/Switch.js.map +0 -1
  344. package/dist/vue/kit/primevue/Table.js +0 -54
  345. package/dist/vue/kit/primevue/Table.js.map +0 -1
  346. package/dist/vue/kit/primevue/Tabs.js +0 -49
  347. package/dist/vue/kit/primevue/Tabs.js.map +0 -1
  348. package/dist/vue/kit/primevue/Textarea.js +0 -55
  349. package/dist/vue/kit/primevue/Textarea.js.map +0 -1
  350. package/dist/vue/kit/primevue/Tooltip.js +0 -43
  351. package/dist/vue/kit/primevue/Tooltip.js.map +0 -1
  352. package/dist/vue/kit/primevue/Tree.js +0 -75
  353. package/dist/vue/kit/primevue/Tree.js.map +0 -1
  354. package/dist/vue/kit/primevue/select-options.js +0 -55
  355. package/dist/vue/kit/primevue/select-options.js.map +0 -1
  356. package/dist/vue/kit/primevue/shared.js +0 -49
  357. package/dist/vue/kit/primevue/shared.js.map +0 -1
  358. package/dist/vue/kit/primevue/trigger.js +0 -25
  359. package/dist/vue/kit/primevue/trigger.js.map +0 -1
  360. package/dist/vue/kit/primevue.css +0 -86
  361. package/dist/vue/kit/primevue.d.ts +0 -33
  362. package/dist/vue/kit/primevue.js +0 -96
  363. package/dist/vue/kit/primevue.js.map +0 -1
@@ -1,20 +1,22 @@
1
1
  import { splitCompositeFences } from "../../core/fences.js";
2
- import { compositeKind, defaultCompositeKinds } from "./kinds.js";
2
+ import { compositeKind, defaultCompositeKinds, kindAccepts } from "./kinds.js";
3
3
  import { blocksFromMarkdown, compositeFence, compositeFromMarkdown } from "./fences.js";
4
4
  import { compositeChromeBlocks, flattenCompositesToHeadings, groupHeadingsIntoComposites, headingGroupRange, structureBlocks } from "./heading-composites.js";
5
5
  import { COMPOSITE_CHROME_COUNT, CompositeDescriptionNode, CompositeNode, CompositeTagsNode, CompositeTitleNode, compositeJSON, compositeKindsOf } from "./nodes.js";
6
6
  import { compositeAiBrief } from "./ai-brief.js";
7
- import { compositeDescriptionText, compositeMetaWith, compositeOfRow, compositeRowAt, compositeRows, setCompositeDescription, setCompositeMetaField, setCompositeTags } from "./rows.js";
8
- import { compositeSectionOf, observeCompositeAnchors } from "./anchors.js";
9
7
  import { compositeConversions, convertCompositeBlocks } from "./convert.js";
10
8
  import { LEGACY_COMPOSITE_NODE, migrateLegacyContent } from "./legacy.js";
9
+ import { compositeSchemaExtensions } from "./schema.js";
10
+ import { compositeDescriptionText, compositeMetaWith, compositeOfRow, compositeRowAt, compositeRows, setCompositeDescription, setCompositeMetaField, setCompositeTags } from "./rows.js";
11
+ import { compositeSectionOf, observeCompositeAnchors } from "./anchors.js";
12
+ import { AcceptedChildren } from "./accepted-children.js";
11
13
  import { documentCompositeMarkdown, headingsMarkdown, markdownFileName } from "./markdown-export.js";
12
14
  import { compositeNodes } from "./node-views.js";
13
15
  import { Structure, structureOf } from "./structure.js";
14
16
  import { STRUCTURE_MODE_KEYS } from "./structure-mode.js";
15
17
  import { compositeFeatureCore } from "./feature.js";
16
- import { canMoveBlockInto, compositeOutline, moveBlockInto } from "./outline.js";
18
+ import { canMoveBlockInto, compositeOutline, moveBlockInto, moveIntoVerdict } from "./outline.js";
17
19
  import { createCompositeRows } from "./rows-model.js";
20
+ import { compositeFieldText, createCompositeInspector } from "./inspector-model.js";
18
21
  import { TREE_ROOT_KEYS } from "./tree-root.js";
19
- import { compositeSchemaExtensions } from "./schema.js";
20
- export { COMPOSITE_CHROME_COUNT, CompositeDescriptionNode, CompositeNode, CompositeTagsNode, CompositeTitleNode, LEGACY_COMPOSITE_NODE, STRUCTURE_MODE_KEYS, Structure, TREE_ROOT_KEYS, blocksFromMarkdown, canMoveBlockInto, compositeAiBrief, compositeChromeBlocks, compositeConversions, compositeDescriptionText, compositeFeatureCore, compositeFence, compositeFromMarkdown, compositeJSON, compositeKind, compositeKindsOf, compositeMetaWith, compositeNodes, compositeOfRow, compositeOutline, compositeRowAt, compositeRows, compositeSchemaExtensions, compositeSectionOf, convertCompositeBlocks, createCompositeRows, defaultCompositeKinds, documentCompositeMarkdown, flattenCompositesToHeadings, groupHeadingsIntoComposites, headingGroupRange, headingsMarkdown, markdownFileName, migrateLegacyContent, moveBlockInto, observeCompositeAnchors, setCompositeDescription, setCompositeMetaField, setCompositeTags, splitCompositeFences, structureBlocks as structureCompositeBlocks, structureOf };
22
+ export { AcceptedChildren, COMPOSITE_CHROME_COUNT, CompositeDescriptionNode, CompositeNode, CompositeTagsNode, CompositeTitleNode, LEGACY_COMPOSITE_NODE, STRUCTURE_MODE_KEYS, Structure, TREE_ROOT_KEYS, blocksFromMarkdown, canMoveBlockInto, compositeAiBrief, compositeChromeBlocks, compositeConversions, compositeDescriptionText, compositeFeatureCore, compositeFence, compositeFieldText, compositeFromMarkdown, compositeJSON, compositeKind, compositeKindsOf, compositeMetaWith, compositeNodes, compositeOfRow, compositeOutline, compositeRowAt, compositeRows, compositeSchemaExtensions, compositeSectionOf, convertCompositeBlocks, createCompositeInspector, createCompositeRows, defaultCompositeKinds, documentCompositeMarkdown, flattenCompositesToHeadings, groupHeadingsIntoComposites, headingGroupRange, headingsMarkdown, kindAccepts, markdownFileName, migrateLegacyContent, moveBlockInto, moveIntoVerdict, observeCompositeAnchors, setCompositeDescription, setCompositeMetaField, setCompositeTags, splitCompositeFences, structureBlocks as structureCompositeBlocks, structureOf };
@@ -0,0 +1,97 @@
1
+ import { ReadonlyStore } from "../../core/store.js";
2
+ import { CompositeMetaField } from "./kinds.js";
3
+ import { CompositeRow } from "./rows.js";
4
+ import { CompositeRowsState } from "./rows-model.js";
5
+ import { Editor } from "@tiptap/core";
6
+ //#region src/features/composites/inspector-model.d.ts
7
+ /** Which section of the inspector shows: the document's facts, or the properties of the Baustein under the caret. */
8
+ export type CompositeInspectorSection = 'document' | 'composite';
9
+ /** A kind the Baustein can be switched to, as the kind select lists it. */
10
+ export interface CompositeKindOption {
11
+ value: string;
12
+ label: string;
13
+ icon: string;
14
+ }
15
+ /** Everything the inspector renders (see `createCompositeInspector`): the live rows plus the inspector's own state. */
16
+ export interface CompositeInspectorState extends CompositeRowsState {
17
+ /** `'composite'` only while the caret last was in the editor and a Baustein is focused, else `'document'`. */
18
+ section: CompositeInspectorSection;
19
+ /** Whether the editor accepts edits; follows `setEditable` (see `createCompositeInspector`). */
20
+ editable: boolean;
21
+ /** The description text as the user sees it in the focused Baustein's textarea; may be ahead of the document until it is committed. */
22
+ draft: string;
23
+ /** The text typed into the tag search. */
24
+ searchTerm: string;
25
+ /** Tag names the metadata source suggested for the latest search that was not superseded. */
26
+ tagItems: string[];
27
+ /** The kinds the editor was configured with, in configured order; empty without an editor. */
28
+ kindOptions: CompositeKindOption[];
29
+ /** How many Bausteine have a description. */
30
+ summarized: number;
31
+ /** The focused Baustein has a description. */
32
+ hasSummary: boolean;
33
+ }
34
+ /** The inspector model of an editor: observable `state`, the actions of the panel, and `dispose()`. */
35
+ export interface CompositeInspector {
36
+ readonly state: ReadonlyStore<CompositeInspectorState>;
37
+ /**
38
+ * Reports whether the host's document-level input (its title field) has the focus. Only the rising edge switches
39
+ * the section to `'document'`; the section stays there after the input blurs, until the editor takes the focus.
40
+ */
41
+ setDocumentFocused: (focused: boolean) => void;
42
+ /** Switches the focused Baustein to `kind`; ignored when it already has it or no Baustein is focused. */
43
+ setKind: (kind: string) => void;
44
+ /**
45
+ * The user typed `text` into the description. The draft is written to the document 600 ms after the last call and
46
+ * on `blurDescription`; the Baustein focused at the first call of a pending draft is the one that is written.
47
+ */
48
+ inputDescription: (text: string) => void;
49
+ /**
50
+ * The description textarea lost focus: writes the pending draft, then shows what the document holds. The draft
51
+ * shows the normalized text after a write; after a refused write (a peer changed the description since the draft
52
+ * started) it shows the peer's text.
53
+ */
54
+ blurDescription: () => void;
55
+ /** Replaces the tags of the focused Baustein. */
56
+ setTags: (tags: readonly string[]) => void;
57
+ /** Creates the tag `raw` (normalized) on the focused Baustein and registers it with the metadata source; clears the search term. */
58
+ createTag: (raw: string) => void;
59
+ /** Sets the tag search term and schedules a search for it. */
60
+ setSearchTerm: (term: string) => void;
61
+ /** Schedules a tag search for the current term (the picker opened). */
62
+ searchTags: () => void;
63
+ /** Writes `value` to the meta `field` of the focused Baustein; ignored while the editor is not editable. */
64
+ setField: (field: CompositeMetaField, value: unknown) => void;
65
+ /** Commits a pending description draft and detaches from the editor. Idempotent; the model is inert afterwards. */
66
+ dispose: () => void;
67
+ }
68
+ /** The text of `field` in `row`'s meta as an input shows it: strings and numbers, otherwise empty. */
69
+ export declare function compositeFieldText(row: Pick<CompositeRow, 'meta'> | null, field: Pick<CompositeMetaField, 'key'>): string;
70
+ /**
71
+ * The properties panel of `editor`: which section shows, the focused Baustein's description draft, the tag search
72
+ * and the writes. Owns its own `createCompositeRows(editor)`, so a focus change and the rows it refers to arrive in
73
+ * one notification and the draft is committed before it is replaced. Create one model per editor (a framework hook
74
+ * follows an editor that changes); without an editor the state is empty and every action is ignored.
75
+ *
76
+ * Section: the caret is either in the host's input (`setDocumentFocused`) or in the editor. Only the editor's `focus`
77
+ * event moves it to the editor (not `selectionUpdate`: collaboration sync moves the selection while the host's input
78
+ * has the focus). A deleted Baustein falls back to the document section.
79
+ *
80
+ * `isEditable` is not reactive: `editable` follows it on every editor `transaction`, so whoever calls `setEditable`
81
+ * must dispatch a transaction afterwards (`EditorSurface` does).
82
+ *
83
+ * Description: edits go to a local `draft` so the asynchronous re-read of the document never overwrites the text
84
+ * (and moves the caret) while it is typed. `base` is the document text the draft started from; the write lands only
85
+ * while the description still holds it, so a peer's change made meanwhile is never replaced. The draft is committed
86
+ * 600 ms after typing pauses, on blur, when another Baustein takes the focus (before the draft is replaced), when
87
+ * the panel leaves the Baustein section, and on `dispose()`. A commit is skipped when the editor is destroyed or not
88
+ * editable, and refused when a peer changed the description; the draft then reloads the live text. A draft that is
89
+ * not dirty follows every change of the focused description.
90
+ *
91
+ * Tag search: each term change restarts a 150 ms debounce; when it fires, the previous search is aborted and the
92
+ * source is asked with a fresh `AbortSignal`. Results of aborted searches are dropped, errors of aborted searches
93
+ * are silent, other errors are logged and keep the old suggestions.
94
+ */
95
+ export declare function createCompositeInspector(editor: Editor | undefined): CompositeInspector;
96
+ //#endregion
97
+ //# sourceMappingURL=inspector-model.d.ts.map
@@ -0,0 +1,255 @@
1
+ import { createStore } from "../../core/store.js";
2
+ import { metadataOf } from "../../core/metadata.js";
3
+ import { METADATA_LIST } from "../../core/metadata-types.js";
4
+ import { compositeKindsOf } from "./nodes.js";
5
+ import { compositeDescriptionText, compositeOfRow, setCompositeDescription, setCompositeMetaField, setCompositeTags } from "./rows.js";
6
+ import { normalizeTagName } from "../../core/tag-name.js";
7
+ import { createCompositeRows } from "./rows-model.js";
8
+ //#region src/features/composites/inspector-model.ts
9
+ /** Pause after the last keystroke in the description before the draft is written to the document. */
10
+ const COMMIT_DELAY_MS = 600;
11
+ /** Pause after the last change of the tag search term before the metadata source is asked. */
12
+ const SEARCH_DELAY_MS = 150;
13
+ /** The text of `field` in `row`'s meta as an input shows it: strings and numbers, otherwise empty. */
14
+ function compositeFieldText(row, field) {
15
+ const value = row?.meta[field.key];
16
+ return typeof value === "string" || typeof value === "number" ? String(value) : "";
17
+ }
18
+ function sameState(a, b) {
19
+ return Object.keys(a).every((key) => Object.is(a[key], b[key]));
20
+ }
21
+ /**
22
+ * The properties panel of `editor`: which section shows, the focused Baustein's description draft, the tag search
23
+ * and the writes. Owns its own `createCompositeRows(editor)`, so a focus change and the rows it refers to arrive in
24
+ * one notification and the draft is committed before it is replaced. Create one model per editor (a framework hook
25
+ * follows an editor that changes); without an editor the state is empty and every action is ignored.
26
+ *
27
+ * Section: the caret is either in the host's input (`setDocumentFocused`) or in the editor. Only the editor's `focus`
28
+ * event moves it to the editor (not `selectionUpdate`: collaboration sync moves the selection while the host's input
29
+ * has the focus). A deleted Baustein falls back to the document section.
30
+ *
31
+ * `isEditable` is not reactive: `editable` follows it on every editor `transaction`, so whoever calls `setEditable`
32
+ * must dispatch a transaction afterwards (`EditorSurface` does).
33
+ *
34
+ * Description: edits go to a local `draft` so the asynchronous re-read of the document never overwrites the text
35
+ * (and moves the caret) while it is typed. `base` is the document text the draft started from; the write lands only
36
+ * while the description still holds it, so a peer's change made meanwhile is never replaced. The draft is committed
37
+ * 600 ms after typing pauses, on blur, when another Baustein takes the focus (before the draft is replaced), when
38
+ * the panel leaves the Baustein section, and on `dispose()`. A commit is skipped when the editor is destroyed or not
39
+ * editable, and refused when a peer changed the description; the draft then reloads the live text. A draft that is
40
+ * not dirty follows every change of the focused description.
41
+ *
42
+ * Tag search: each term change restarts a 150 ms debounce; when it fires, the previous search is aborted and the
43
+ * source is asked with a fresh `AbortSignal`. Results of aborted searches are dropped, errors of aborted searches
44
+ * are silent, other errors are logged and keep the old suggestions.
45
+ */
46
+ function createCompositeInspector(editor) {
47
+ const rows = createCompositeRows(editor);
48
+ const kindOptions = editor && !editor.isDestroyed ? compositeKindsOf(editor).map((kind) => ({
49
+ value: kind.id,
50
+ label: kind.label,
51
+ icon: kind.icon
52
+ })) : [];
53
+ const isEditable = () => !!editor && !editor.isDestroyed && editor.isEditable;
54
+ const focusedRow = () => rows.state.get().focused;
55
+ let disposed = false;
56
+ let caretIn = "document";
57
+ let documentFocused = false;
58
+ let section = "document";
59
+ let editable = isEditable();
60
+ let draft = "";
61
+ let base = "";
62
+ let dirty = false;
63
+ let pendingRow = null;
64
+ let commitTimer;
65
+ let focusKey = focusedRow()?.key;
66
+ let focusDescription = focusedRow()?.description;
67
+ let searchTerm = "";
68
+ let tagItems = [];
69
+ let searchTimer;
70
+ let pendingSearch;
71
+ let summarizedRows;
72
+ let summarized = 0;
73
+ const store = createStore(snapshot());
74
+ function snapshot() {
75
+ const read = rows.state.get();
76
+ if (read.rows !== summarizedRows) {
77
+ summarizedRows = read.rows;
78
+ summarized = read.rows.filter((row) => row.description.trim()).length;
79
+ }
80
+ return {
81
+ ...read,
82
+ section,
83
+ editable,
84
+ draft,
85
+ searchTerm,
86
+ tagItems,
87
+ kindOptions,
88
+ summarized,
89
+ hasSummary: !!read.focused?.description.trim()
90
+ };
91
+ }
92
+ function publish() {
93
+ const next = snapshot();
94
+ if (!sameState(store.get(), next)) store.set(next);
95
+ }
96
+ function load() {
97
+ dirty = false;
98
+ pendingRow = null;
99
+ base = focusedRow()?.description ?? "";
100
+ draft = base;
101
+ }
102
+ /** Writes the pending draft; `false` when there was nothing to write or the write was refused. Does not touch `draft`. */
103
+ function commit() {
104
+ clearTimeout(commitTimer);
105
+ if (!dirty || !pendingRow) return false;
106
+ dirty = false;
107
+ const row = pendingRow;
108
+ pendingRow = null;
109
+ if (!editor || editor.isDestroyed || !editor.isEditable) return false;
110
+ if (!setCompositeDescription(editor, row, draft, base)) return false;
111
+ base = compositeDescriptionText(draft);
112
+ return true;
113
+ }
114
+ function refreshSection() {
115
+ const next = caretIn === "editor" && focusedRow() ? "composite" : "document";
116
+ if (section === "composite" && next !== "composite") commit();
117
+ section = next;
118
+ }
119
+ function followRows() {
120
+ const focused = focusedRow();
121
+ const key = focused?.key;
122
+ const description = focused?.description;
123
+ if (key !== focusKey) {
124
+ focusKey = key;
125
+ focusDescription = description;
126
+ commit();
127
+ load();
128
+ } else if (description !== focusDescription) {
129
+ focusDescription = description;
130
+ if (dirty) {
131
+ if (description !== base && !commit()) load();
132
+ } else if (description !== base) load();
133
+ }
134
+ refreshSection();
135
+ publish();
136
+ }
137
+ function scheduleSearch() {
138
+ clearTimeout(searchTimer);
139
+ searchTimer = setTimeout(async () => {
140
+ if (!editor || editor.isDestroyed) return;
141
+ pendingSearch?.abort();
142
+ const controller = pendingSearch = new AbortController();
143
+ try {
144
+ const found = await metadataOf(editor).search(METADATA_LIST.hashtags, {
145
+ query: searchTerm,
146
+ editor,
147
+ signal: controller.signal
148
+ });
149
+ if (controller.signal.aborted) return;
150
+ tagItems = found.map((item) => item.label);
151
+ publish();
152
+ } catch (error) {
153
+ if (!controller.signal.aborted) console.warn("Tag-Vorschläge konnten nicht geladen werden", error);
154
+ }
155
+ }, SEARCH_DELAY_MS);
156
+ }
157
+ function setSearchTerm(term) {
158
+ if (disposed || term === searchTerm) return;
159
+ searchTerm = term;
160
+ scheduleSearch();
161
+ publish();
162
+ }
163
+ const syncEditable = () => {
164
+ const next = isEditable();
165
+ if (next === editable) return;
166
+ editable = next;
167
+ publish();
168
+ };
169
+ const toEditor = () => {
170
+ caretIn = "editor";
171
+ refreshSection();
172
+ publish();
173
+ };
174
+ load();
175
+ refreshSection();
176
+ const stopRows = rows.state.subscribe(followRows);
177
+ if (editor && !editor.isDestroyed) {
178
+ editor.on("transaction", syncEditable);
179
+ editor.on("focus", toEditor);
180
+ }
181
+ publish();
182
+ return {
183
+ state: store,
184
+ setDocumentFocused(focused) {
185
+ if (disposed) return;
186
+ const rising = focused && !documentFocused;
187
+ documentFocused = focused;
188
+ if (!rising) return;
189
+ caretIn = "document";
190
+ refreshSection();
191
+ publish();
192
+ },
193
+ setKind(kind) {
194
+ const row = focusedRow();
195
+ const found = editor && row ? compositeOfRow(editor.state.doc, row) : null;
196
+ if (!editor || !found || kind === row?.kind) return;
197
+ editor.commands.setCompositeKind(kind, found.pos);
198
+ },
199
+ inputDescription(text) {
200
+ if (disposed) return;
201
+ draft = text;
202
+ dirty = true;
203
+ pendingRow ??= focusedRow();
204
+ clearTimeout(commitTimer);
205
+ commitTimer = setTimeout(commit, COMMIT_DELAY_MS);
206
+ publish();
207
+ },
208
+ blurDescription() {
209
+ if (disposed) return;
210
+ if (commit()) draft = base;
211
+ else load();
212
+ publish();
213
+ },
214
+ setTags(tags) {
215
+ const row = focusedRow();
216
+ if (editor && row) setCompositeTags(editor, row, tags);
217
+ },
218
+ createTag(raw) {
219
+ const row = focusedRow();
220
+ const name = normalizeTagName(raw);
221
+ setSearchTerm("");
222
+ if (!editor || !row || !name || row.tags.includes(name)) return;
223
+ setCompositeTags(editor, row, [...row.tags, name]);
224
+ if (!tagItems.includes(name)) metadataOf(editor).create(METADATA_LIST.hashtags, {
225
+ id: name,
226
+ label: name
227
+ });
228
+ },
229
+ setSearchTerm,
230
+ searchTags() {
231
+ if (!disposed) scheduleSearch();
232
+ },
233
+ setField(field, value) {
234
+ const row = focusedRow();
235
+ if (editor && isEditable() && row) setCompositeMetaField(editor, row, field.key, value);
236
+ },
237
+ dispose() {
238
+ if (disposed) return;
239
+ commit();
240
+ disposed = true;
241
+ clearTimeout(searchTimer);
242
+ pendingSearch?.abort();
243
+ stopRows();
244
+ if (editor && !editor.isDestroyed) {
245
+ editor.off("transaction", syncEditable);
246
+ editor.off("focus", toEditor);
247
+ }
248
+ rows.dispose();
249
+ }
250
+ };
251
+ }
252
+ //#endregion
253
+ export { compositeFieldText, createCompositeInspector };
254
+
255
+ //# sourceMappingURL=inspector-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspector-model.js","names":[],"sources":["../../../src/features/composites/inspector-model.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { metadataOf } from '../../core/metadata'\nimport { METADATA_LIST } from '../../core/metadata-types'\nimport { createStore, type ReadonlyStore } from '../../core/store'\nimport { normalizeTagName } from '../../core/tag-name'\nimport type { CompositeMetaField } from './kinds'\nimport { compositeKindsOf } from './nodes'\nimport { compositeDescriptionText, compositeOfRow, type CompositeRow, setCompositeDescription, setCompositeMetaField, setCompositeTags } from './rows'\nimport { type CompositeRowsState, createCompositeRows } from './rows-model'\n\n/** Pause after the last keystroke in the description before the draft is written to the document. */\nconst COMMIT_DELAY_MS = 600\n/** Pause after the last change of the tag search term before the metadata source is asked. */\nconst SEARCH_DELAY_MS = 150\n\n/** Which section of the inspector shows: the document's facts, or the properties of the Baustein under the caret. */\nexport type CompositeInspectorSection = 'document' | 'composite'\n\n/** A kind the Baustein can be switched to, as the kind select lists it. */\nexport interface CompositeKindOption {\n value: string\n label: string\n icon: string\n}\n\n/** Everything the inspector renders (see `createCompositeInspector`): the live rows plus the inspector's own state. */\nexport interface CompositeInspectorState extends CompositeRowsState {\n /** `'composite'` only while the caret last was in the editor and a Baustein is focused, else `'document'`. */\n section: CompositeInspectorSection\n /** Whether the editor accepts edits; follows `setEditable` (see `createCompositeInspector`). */\n editable: boolean\n /** The description text as the user sees it in the focused Baustein's textarea; may be ahead of the document until it is committed. */\n draft: string\n /** The text typed into the tag search. */\n searchTerm: string\n /** Tag names the metadata source suggested for the latest search that was not superseded. */\n tagItems: string[]\n /** The kinds the editor was configured with, in configured order; empty without an editor. */\n kindOptions: CompositeKindOption[]\n /** How many Bausteine have a description. */\n summarized: number\n /** The focused Baustein has a description. */\n hasSummary: boolean\n}\n\n/** The inspector model of an editor: observable `state`, the actions of the panel, and `dispose()`. */\nexport interface CompositeInspector {\n readonly state: ReadonlyStore<CompositeInspectorState>\n /**\n * Reports whether the host's document-level input (its title field) has the focus. Only the rising edge switches\n * the section to `'document'`; the section stays there after the input blurs, until the editor takes the focus.\n */\n setDocumentFocused: (focused: boolean) => void\n /** Switches the focused Baustein to `kind`; ignored when it already has it or no Baustein is focused. */\n setKind: (kind: string) => void\n /**\n * The user typed `text` into the description. The draft is written to the document 600 ms after the last call and\n * on `blurDescription`; the Baustein focused at the first call of a pending draft is the one that is written.\n */\n inputDescription: (text: string) => void\n /**\n * The description textarea lost focus: writes the pending draft, then shows what the document holds. The draft\n * shows the normalized text after a write; after a refused write (a peer changed the description since the draft\n * started) it shows the peer's text.\n */\n blurDescription: () => void\n /** Replaces the tags of the focused Baustein. */\n setTags: (tags: readonly string[]) => void\n /** Creates the tag `raw` (normalized) on the focused Baustein and registers it with the metadata source; clears the search term. */\n createTag: (raw: string) => void\n /** Sets the tag search term and schedules a search for it. */\n setSearchTerm: (term: string) => void\n /** Schedules a tag search for the current term (the picker opened). */\n searchTags: () => void\n /** Writes `value` to the meta `field` of the focused Baustein; ignored while the editor is not editable. */\n setField: (field: CompositeMetaField, value: unknown) => void\n /** Commits a pending description draft and detaches from the editor. Idempotent; the model is inert afterwards. */\n dispose: () => void\n}\n\n/** The text of `field` in `row`'s meta as an input shows it: strings and numbers, otherwise empty. */\nexport function compositeFieldText(row: Pick<CompositeRow, 'meta'> | null, field: Pick<CompositeMetaField, 'key'>): string {\n const value = row?.meta[field.key]\n return typeof value === 'string' || typeof value === 'number' ? String(value) : ''\n}\n\nfunction sameState(a: CompositeInspectorState, b: CompositeInspectorState): boolean {\n return (Object.keys(a) as (keyof CompositeInspectorState)[]).every(key => Object.is(a[key], b[key]))\n}\n\n/**\n * The properties panel of `editor`: which section shows, the focused Baustein's description draft, the tag search\n * and the writes. Owns its own `createCompositeRows(editor)`, so a focus change and the rows it refers to arrive in\n * one notification and the draft is committed before it is replaced. Create one model per editor (a framework hook\n * follows an editor that changes); without an editor the state is empty and every action is ignored.\n *\n * Section: the caret is either in the host's input (`setDocumentFocused`) or in the editor. Only the editor's `focus`\n * event moves it to the editor (not `selectionUpdate`: collaboration sync moves the selection while the host's input\n * has the focus). A deleted Baustein falls back to the document section.\n *\n * `isEditable` is not reactive: `editable` follows it on every editor `transaction`, so whoever calls `setEditable`\n * must dispatch a transaction afterwards (`EditorSurface` does).\n *\n * Description: edits go to a local `draft` so the asynchronous re-read of the document never overwrites the text\n * (and moves the caret) while it is typed. `base` is the document text the draft started from; the write lands only\n * while the description still holds it, so a peer's change made meanwhile is never replaced. The draft is committed\n * 600 ms after typing pauses, on blur, when another Baustein takes the focus (before the draft is replaced), when\n * the panel leaves the Baustein section, and on `dispose()`. A commit is skipped when the editor is destroyed or not\n * editable, and refused when a peer changed the description; the draft then reloads the live text. A draft that is\n * not dirty follows every change of the focused description.\n *\n * Tag search: each term change restarts a 150 ms debounce; when it fires, the previous search is aborted and the\n * source is asked with a fresh `AbortSignal`. Results of aborted searches are dropped, errors of aborted searches\n * are silent, other errors are logged and keep the old suggestions.\n */\nexport function createCompositeInspector(editor: Editor | undefined): CompositeInspector {\n const rows = createCompositeRows(editor)\n const kindOptions: CompositeKindOption[] = editor && !editor.isDestroyed\n ? compositeKindsOf(editor).map(kind => ({ value: kind.id, label: kind.label, icon: kind.icon }))\n : []\n const isEditable = () => !!editor && !editor.isDestroyed && editor.isEditable\n const focusedRow = () => rows.state.get().focused\n\n let disposed = false\n\n // --- Section ---\n let caretIn: 'document' | 'editor' = 'document'\n let documentFocused = false\n let section: CompositeInspectorSection = 'document'\n let editable = isEditable()\n\n // --- Description draft ---\n let draft = ''\n let base = ''\n let dirty = false\n let pendingRow: CompositeRow | null = null\n let commitTimer: ReturnType<typeof setTimeout> | undefined\n // What the focused Baustein looked like at the last rows notification: a change of the key is a switch, a change of the description a peer's (or our own) write.\n let focusKey = focusedRow()?.key\n let focusDescription = focusedRow()?.description\n\n // --- Tag search ---\n let searchTerm = ''\n let tagItems: string[] = []\n let searchTimer: ReturnType<typeof setTimeout> | undefined\n let pendingSearch: AbortController | undefined\n\n let summarizedRows: CompositeRowsState['rows'] | undefined\n let summarized = 0\n const store = createStore<CompositeInspectorState>(snapshot())\n\n function snapshot(): CompositeInspectorState {\n const read = rows.state.get()\n if (read.rows !== summarizedRows) {\n summarizedRows = read.rows\n summarized = read.rows.filter(row => row.description.trim()).length\n }\n return {\n ...read,\n section,\n editable,\n draft,\n searchTerm,\n tagItems,\n kindOptions,\n summarized,\n hasSummary: !!read.focused?.description.trim(),\n }\n }\n\n function publish() {\n const next = snapshot()\n if (!sameState(store.get(), next)) store.set(next)\n }\n\n function load() {\n dirty = false\n pendingRow = null\n base = focusedRow()?.description ?? ''\n draft = base\n }\n\n /** Writes the pending draft; `false` when there was nothing to write or the write was refused. Does not touch `draft`. */\n function commit(): boolean {\n clearTimeout(commitTimer)\n if (!dirty || !pendingRow) return false\n dirty = false\n const row = pendingRow\n pendingRow = null\n if (!editor || editor.isDestroyed || !editor.isEditable) return false\n if (!setCompositeDescription(editor, row, draft, base)) return false\n base = compositeDescriptionText(draft)\n return true\n }\n\n function refreshSection() {\n const next = caretIn === 'editor' && focusedRow() ? 'composite' : 'document'\n if (section === 'composite' && next !== 'composite') commit()\n section = next\n }\n\n // A switch commits the draft of the previous Baustein first (`draft` still holds its text), then loads the new one.\n // A change of the description reaches an untouched draft, also while the textarea has focus. A draft with unsaved\n // typing keeps it; if a peer changed the description meanwhile, its commit is refused and the peer's text is\n // loaded instead.\n function followRows() {\n const focused = focusedRow()\n const key = focused?.key\n const description = focused?.description\n if (key !== focusKey) {\n focusKey = key\n focusDescription = description\n commit()\n load()\n }\n else if (description !== focusDescription) {\n focusDescription = description\n if (dirty) {\n if (description !== base && !commit()) load()\n }\n else if (description !== base) {\n load()\n }\n }\n refreshSection()\n publish()\n }\n\n function scheduleSearch() {\n clearTimeout(searchTimer)\n searchTimer = setTimeout(async () => {\n if (!editor || editor.isDestroyed) return\n pendingSearch?.abort()\n const controller = pendingSearch = new AbortController()\n try {\n const found = await metadataOf(editor).search(METADATA_LIST.hashtags, { query: searchTerm, editor, signal: controller.signal })\n if (controller.signal.aborted) return\n tagItems = found.map(item => item.label)\n publish()\n }\n catch (error) {\n if (!controller.signal.aborted) console.warn('Tag-Vorschläge konnten nicht geladen werden', error)\n }\n }, SEARCH_DELAY_MS)\n }\n\n function setSearchTerm(term: string) {\n if (disposed || term === searchTerm) return\n searchTerm = term\n scheduleSearch()\n publish()\n }\n\n const syncEditable = () => {\n const next = isEditable()\n if (next === editable) return\n editable = next\n publish()\n }\n const toEditor = () => {\n caretIn = 'editor'\n refreshSection()\n publish()\n }\n\n load()\n refreshSection()\n const stopRows = rows.state.subscribe(followRows)\n if (editor && !editor.isDestroyed) {\n editor.on('transaction', syncEditable)\n // Only `focus`, not `selectionUpdate`: a collaboration sync can move the selection while the host's input has the focus.\n editor.on('focus', toEditor)\n }\n publish()\n\n return {\n state: store,\n\n setDocumentFocused(focused) {\n if (disposed) return\n const rising = focused && !documentFocused\n documentFocused = focused\n if (!rising) return\n caretIn = 'document'\n refreshSection()\n publish()\n },\n\n setKind(kind) {\n const row = focusedRow()\n const found = editor && row ? compositeOfRow(editor.state.doc, row) : null\n if (!editor || !found || kind === row?.kind) return\n editor.commands.setCompositeKind(kind, found.pos)\n },\n\n inputDescription(text) {\n if (disposed) return\n draft = text\n dirty = true\n pendingRow ??= focusedRow()\n clearTimeout(commitTimer)\n commitTimer = setTimeout(commit, COMMIT_DELAY_MS)\n publish()\n },\n\n blurDescription() {\n if (disposed) return\n if (commit()) draft = base\n else load()\n publish()\n },\n\n setTags(tags) {\n const row = focusedRow()\n if (editor && row) setCompositeTags(editor, row, tags)\n },\n\n // „Neu Tag“ → `neu-tag`. A name that normalizes to an already chosen tag is ignored; only unknown names are reported as created.\n createTag(raw) {\n const row = focusedRow()\n const name = normalizeTagName(raw)\n setSearchTerm('')\n if (!editor || !row || !name || row.tags.includes(name)) return\n setCompositeTags(editor, row, [...row.tags, name])\n if (!tagItems.includes(name)) metadataOf(editor).create(METADATA_LIST.hashtags, { id: name, label: name })\n },\n\n setSearchTerm,\n\n searchTags() {\n if (!disposed) scheduleSearch()\n },\n\n setField(field, value) {\n const row = focusedRow()\n if (editor && isEditable() && row) setCompositeMetaField(editor, row, field.key, value)\n },\n\n dispose() {\n if (disposed) return\n commit()\n disposed = true\n clearTimeout(searchTimer)\n pendingSearch?.abort()\n stopRows()\n if (editor && !editor.isDestroyed) {\n editor.off('transaction', syncEditable)\n editor.off('focus', toEditor)\n }\n rows.dispose()\n },\n }\n}\n"],"mappings":"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"}
@@ -35,6 +35,12 @@ export interface CompositeKind {
35
35
  fields: CompositeMetaField[];
36
36
  /** Absent: the kind has no AI brief; „Baustein füllen (KI)“ is not offered for it. */
37
37
  ai?: CompositeKindAi;
38
+ /**
39
+ * Baustein kinds this kind accepts as nested Bausteine: a list of kind ids, or `'all'`. Absent: `folder` and `document`
40
+ * accept `'all'`, every other kind accepts none. Non-Baustein content (paragraphs, lists, ...) is always allowed.
41
+ * Only new moves are checked; documents that already nest against it stay as they are.
42
+ */
43
+ accepts?: readonly string[] | 'all';
38
44
  }
39
45
  /**
40
46
  * The built-in kinds in the language of `t`. The default set is produced from the translator (labels, placeholders, field
@@ -45,5 +51,10 @@ export interface CompositeKind {
45
51
  export declare function defaultCompositeKinds(t: Translate): readonly CompositeKind[];
46
52
  /** Kind `id` from `kinds`; an unknown id gets the `section` kind's presentation (else the first kind's) under its own id and label. */
47
53
  export declare function compositeKind(kinds: readonly CompositeKind[], id: string): CompositeKind;
54
+ /**
55
+ * Whether a Baustein of kind `parentKind` accepts one of kind `childKind` as nested Baustein. The `accepts` of the kind
56
+ * in `kinds` decides; without one (or for a kind `kinds` does not list) only `folder` and `document` accept all.
57
+ */
58
+ export declare function kindAccepts(kinds: readonly CompositeKind[], parentKind: string, childKind: string): boolean;
48
59
  //#endregion
49
60
  //# sourceMappingURL=kinds.d.ts.map
@@ -20,7 +20,8 @@ function buildDefaultKinds(t) {
20
20
  icon: "i-lucide-layers",
21
21
  titlePlaceholder: t("composites.kind.section.title"),
22
22
  descriptionPlaceholder: t("composites.kind.section.description"),
23
- fields: []
23
+ fields: [],
24
+ accepts: "all"
24
25
  },
25
26
  {
26
27
  id: "task",
@@ -125,7 +126,21 @@ function compositeKind(kinds, id) {
125
126
  label: id
126
127
  };
127
128
  }
129
+ /** Kinds that accept every Baustein kind when their definition sets no `accepts`. */
130
+ const ACCEPTS_ALL_BY_DEFAULT = {
131
+ folder: true,
132
+ document: true
133
+ };
134
+ /**
135
+ * Whether a Baustein of kind `parentKind` accepts one of kind `childKind` as nested Baustein. The `accepts` of the kind
136
+ * in `kinds` decides; without one (or for a kind `kinds` does not list) only `folder` and `document` accept all.
137
+ */
138
+ function kindAccepts(kinds, parentKind, childKind) {
139
+ const accepts = kinds.find((kind) => kind.id === parentKind)?.accepts;
140
+ if (accepts === void 0) return Object.hasOwn(ACCEPTS_ALL_BY_DEFAULT, parentKind);
141
+ return accepts === "all" || accepts.includes(childKind);
142
+ }
128
143
  //#endregion
129
- export { compositeKind, defaultCompositeKinds, fallbackCompositeKinds };
144
+ export { compositeKind, defaultCompositeKinds, fallbackCompositeKinds, kindAccepts };
130
145
 
131
146
  //# sourceMappingURL=kinds.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"kinds.js","names":[],"sources":["../../../src/features/composites/kinds.ts"],"sourcesContent":["// Vue-free and server-importable: the kind registry of the composite block family („Bausteine“).\nimport { createTranslator, type Translate } from '../../i18n'\n\n/** One metadata field of a composite kind, stored under `key` in the composite's `meta` attribute. */\nexport interface CompositeMetaField {\n key: string\n label: string\n type: 'text' | 'textarea' | 'date' | 'number' | 'select'\n /** Choices of a `select` field. */\n options?: { value: string, label: string }[]\n}\n\n/**\n * What a kind tells the model beyond its fields. `purpose` goes into every AI request's block model (chat, generate\n * tasks); `instructions`, `instructionsField` and `skeleton` are read only by „Baustein füllen (KI)“ (`fillComposite`).\n */\nexport interface CompositeKindAi {\n /** One or two German sentences: what a Baustein of this kind is for and what its body holds. */\n purpose: string\n /** Default instruction for filling the body of a Baustein of this kind (what to write, structure, tone). */\n instructions?: string\n /** Key of a `textarea` field of this kind; a Baustein whose value is non-empty replaces `instructions` with it. */\n instructionsField?: string\n /** Target text (Vorlage) as Markdown; `{key}` placeholders are replaced by the Baustein's `meta[key]` before it goes to the model, unknown ones stay. */\n skeleton?: string\n}\n\n/** A composite kind: what its `kind` attribute selects (label, icon, chrome placeholders, meta fields). */\nexport interface CompositeKind {\n id: string\n label: string\n icon: string\n titlePlaceholder: string\n descriptionPlaceholder: string\n fields: CompositeMetaField[]\n /** Absent: the kind has no AI brief; „Baustein füllen (KI)“ is not offered for it. */\n ai?: CompositeKindAi\n}\n\n/**\n * The built-in kinds in the language of `t`. The default set is produced from the translator (labels, placeholders, field\n * labels and status choices are catalog messages), so it is built per translator and memoized; hosts that pass their own\n * kinds (`compositeFeature({ kinds })`, `Composite.configure({ kinds })`) keep their labels untouched. Ids, field keys and\n * stored values (`open`, `doing`, `done`) do not depend on the language.\n */\nexport function defaultCompositeKinds(t: Translate): readonly CompositeKind[] {\n let kinds = DEFAULT_KINDS.get(t)\n if (!kinds) DEFAULT_KINDS.set(t, kinds = buildDefaultKinds(t))\n return kinds\n}\n\nconst DEFAULT_KINDS = new WeakMap<Translate, readonly CompositeKind[]>()\n\nfunction buildDefaultKinds(t: Translate): readonly CompositeKind[] {\n return [\n { id: 'section', label: t('composites.kind.section.label'), icon: 'i-lucide-layers', titlePlaceholder: t('composites.kind.section.title'), descriptionPlaceholder: t('composites.kind.section.description'), fields: [] },\n { id: 'task', label: t('composites.kind.task.label'), icon: 'i-lucide-square-check', titlePlaceholder: t('composites.kind.task.title'), descriptionPlaceholder: t('composites.kind.task.description'), fields: [\n { key: 'status', label: t('composites.kind.task.field.status'), type: 'select', options: [\n { value: 'open', label: t('composites.kind.task.status.open') },\n { value: 'doing', label: t('composites.kind.task.status.doing') },\n { value: 'done', label: t('composites.kind.task.status.done') },\n ] },\n { key: 'due', label: t('composites.kind.task.field.due'), type: 'date' },\n { key: 'assignee', label: t('composites.kind.task.field.assignee'), type: 'text' },\n ] },\n { id: 'note', label: t('composites.kind.note.label'), icon: 'i-lucide-sticky-note', titlePlaceholder: t('composites.kind.note.title'), descriptionPlaceholder: t('composites.kind.note.description'), fields: [] },\n { id: 'document', label: t('composites.kind.document.label'), icon: 'i-lucide-file-text', titlePlaceholder: t('composites.kind.document.title'), descriptionPlaceholder: t('composites.kind.document.description'), fields: [\n { key: 'filename', label: t('composites.kind.document.field.filename'), type: 'text' },\n { key: 'mimeType', label: t('composites.kind.document.field.mimeType'), type: 'text' },\n { key: 'size', label: t('composites.kind.document.field.size'), type: 'number' },\n { key: 'importedAt', label: t('composites.kind.document.field.importedAt'), type: 'date' },\n { key: 'transcript', label: t('composites.kind.document.field.transcript'), type: 'textarea' },\n ] },\n { id: 'folder', label: t('composites.kind.folder.label'), icon: 'i-lucide-folder', titlePlaceholder: t('composites.kind.folder.title'), descriptionPlaceholder: t('composites.kind.folder.description'), fields: [] },\n ]\n}\n\n/** The built-in kinds in the default language, for server-side and context-free code (`Composite`'s default option, an editor without a context). */\nexport const fallbackCompositeKinds = (): readonly CompositeKind[] => defaultCompositeKinds(DEFAULT_TRANSLATE)\n\nconst DEFAULT_TRANSLATE = createTranslator()\n\n/** Kind `id` from `kinds`; an unknown id gets the `section` kind's presentation (else the first kind's) under its own id and label. */\nexport function compositeKind(kinds: readonly CompositeKind[], id: string): CompositeKind {\n const known = kinds.find(kind => kind.id === id)\n if (known) return known\n const base = kinds.find(kind => kind.id === 'section') ?? kinds[0] ?? fallbackCompositeKinds()[0]!\n return { ...base, id, label: id }\n}\n"],"mappings":";;;;;;;;AA6CA,SAAgB,sBAAsB,GAAwC;CAC5E,IAAI,QAAQ,cAAc,IAAI,CAAC;CAC/B,IAAI,CAAC,OAAO,cAAc,IAAI,GAAG,QAAQ,kBAAkB,CAAC,CAAC;CAC7D,OAAO;AACT;AAEA,MAAM,gCAAgB,IAAI,QAA6C;AAEvE,SAAS,kBAAkB,GAAwC;CACjE,OAAO;EACL;GAAE,IAAI;GAAW,OAAO,EAAE,+BAA+B;GAAG,MAAM;GAAmB,kBAAkB,EAAE,+BAA+B;GAAG,wBAAwB,EAAE,qCAAqC;GAAG,QAAQ,CAAC;EAAE;EACxN;GAAE,IAAI;GAAQ,OAAO,EAAE,4BAA4B;GAAG,MAAM;GAAyB,kBAAkB,EAAE,4BAA4B;GAAG,wBAAwB,EAAE,kCAAkC;GAAG,QAAQ;IAC7M;KAAE,KAAK;KAAU,OAAO,EAAE,mCAAmC;KAAG,MAAM;KAAU,SAAS;MACvF;OAAE,OAAO;OAAQ,OAAO,EAAE,kCAAkC;MAAE;MAC9D;OAAE,OAAO;OAAS,OAAO,EAAE,mCAAmC;MAAE;MAChE;OAAE,OAAO;OAAQ,OAAO,EAAE,kCAAkC;MAAE;KAChE;IAAE;IACF;KAAE,KAAK;KAAO,OAAO,EAAE,gCAAgC;KAAG,MAAM;IAAO;IACvE;KAAE,KAAK;KAAY,OAAO,EAAE,qCAAqC;KAAG,MAAM;IAAO;GACnF;EAAE;EACF;GAAE,IAAI;GAAQ,OAAO,EAAE,4BAA4B;GAAG,MAAM;GAAwB,kBAAkB,EAAE,4BAA4B;GAAG,wBAAwB,EAAE,kCAAkC;GAAG,QAAQ,CAAC;EAAE;EACjN;GAAE,IAAI;GAAY,OAAO,EAAE,gCAAgC;GAAG,MAAM;GAAsB,kBAAkB,EAAE,gCAAgC;GAAG,wBAAwB,EAAE,sCAAsC;GAAG,QAAQ;IAC1N;KAAE,KAAK;KAAY,OAAO,EAAE,yCAAyC;KAAG,MAAM;IAAO;IACrF;KAAE,KAAK;KAAY,OAAO,EAAE,yCAAyC;KAAG,MAAM;IAAO;IACrF;KAAE,KAAK;KAAQ,OAAO,EAAE,qCAAqC;KAAG,MAAM;IAAS;IAC/E;KAAE,KAAK;KAAc,OAAO,EAAE,2CAA2C;KAAG,MAAM;IAAO;IACzF;KAAE,KAAK;KAAc,OAAO,EAAE,2CAA2C;KAAG,MAAM;IAAW;GAC/F;EAAE;EACF;GAAE,IAAI;GAAU,OAAO,EAAE,8BAA8B;GAAG,MAAM;GAAmB,kBAAkB,EAAE,8BAA8B;GAAG,wBAAwB,EAAE,oCAAoC;GAAG,QAAQ,CAAC;EAAE;CACtN;AACF;;AAGA,MAAa,+BAAyD,sBAAsB,iBAAiB;AAE7G,MAAM,oBAAoB,iBAAiB;;AAG3C,SAAgB,cAAc,OAAiC,IAA2B;CACxF,MAAM,QAAQ,MAAM,MAAK,SAAQ,KAAK,OAAO,EAAE;CAC/C,IAAI,OAAO,OAAO;CAElB,OAAO;EAAE,GADI,MAAM,MAAK,SAAQ,KAAK,OAAO,SAAS,KAAK,MAAM,MAAM,uBAAuB,CAAC,CAAC;EAC7E;EAAI,OAAO;CAAG;AAClC"}
1
+ {"version":3,"file":"kinds.js","names":[],"sources":["../../../src/features/composites/kinds.ts"],"sourcesContent":["// Vue-free and server-importable: the kind registry of the composite block family („Bausteine“).\nimport { createTranslator, type Translate } from '../../i18n'\n\n/** One metadata field of a composite kind, stored under `key` in the composite's `meta` attribute. */\nexport interface CompositeMetaField {\n key: string\n label: string\n type: 'text' | 'textarea' | 'date' | 'number' | 'select'\n /** Choices of a `select` field. */\n options?: { value: string, label: string }[]\n}\n\n/**\n * What a kind tells the model beyond its fields. `purpose` goes into every AI request's block model (chat, generate\n * tasks); `instructions`, `instructionsField` and `skeleton` are read only by „Baustein füllen (KI)“ (`fillComposite`).\n */\nexport interface CompositeKindAi {\n /** One or two German sentences: what a Baustein of this kind is for and what its body holds. */\n purpose: string\n /** Default instruction for filling the body of a Baustein of this kind (what to write, structure, tone). */\n instructions?: string\n /** Key of a `textarea` field of this kind; a Baustein whose value is non-empty replaces `instructions` with it. */\n instructionsField?: string\n /** Target text (Vorlage) as Markdown; `{key}` placeholders are replaced by the Baustein's `meta[key]` before it goes to the model, unknown ones stay. */\n skeleton?: string\n}\n\n/** A composite kind: what its `kind` attribute selects (label, icon, chrome placeholders, meta fields). */\nexport interface CompositeKind {\n id: string\n label: string\n icon: string\n titlePlaceholder: string\n descriptionPlaceholder: string\n fields: CompositeMetaField[]\n /** Absent: the kind has no AI brief; „Baustein füllen (KI)“ is not offered for it. */\n ai?: CompositeKindAi\n /**\n * Baustein kinds this kind accepts as nested Bausteine: a list of kind ids, or `'all'`. Absent: `folder` and `document`\n * accept `'all'`, every other kind accepts none. Non-Baustein content (paragraphs, lists, ...) is always allowed.\n * Only new moves are checked; documents that already nest against it stay as they are.\n */\n accepts?: readonly string[] | 'all'\n}\n\n/**\n * The built-in kinds in the language of `t`. The default set is produced from the translator (labels, placeholders, field\n * labels and status choices are catalog messages), so it is built per translator and memoized; hosts that pass their own\n * kinds (`compositeFeature({ kinds })`, `Composite.configure({ kinds })`) keep their labels untouched. Ids, field keys and\n * stored values (`open`, `doing`, `done`) do not depend on the language.\n */\nexport function defaultCompositeKinds(t: Translate): readonly CompositeKind[] {\n let kinds = DEFAULT_KINDS.get(t)\n if (!kinds) DEFAULT_KINDS.set(t, kinds = buildDefaultKinds(t))\n return kinds\n}\n\nconst DEFAULT_KINDS = new WeakMap<Translate, readonly CompositeKind[]>()\n\nfunction buildDefaultKinds(t: Translate): readonly CompositeKind[] {\n return [\n { id: 'section', label: t('composites.kind.section.label'), icon: 'i-lucide-layers', titlePlaceholder: t('composites.kind.section.title'), descriptionPlaceholder: t('composites.kind.section.description'), fields: [], accepts: 'all' },\n { id: 'task', label: t('composites.kind.task.label'), icon: 'i-lucide-square-check', titlePlaceholder: t('composites.kind.task.title'), descriptionPlaceholder: t('composites.kind.task.description'), fields: [\n { key: 'status', label: t('composites.kind.task.field.status'), type: 'select', options: [\n { value: 'open', label: t('composites.kind.task.status.open') },\n { value: 'doing', label: t('composites.kind.task.status.doing') },\n { value: 'done', label: t('composites.kind.task.status.done') },\n ] },\n { key: 'due', label: t('composites.kind.task.field.due'), type: 'date' },\n { key: 'assignee', label: t('composites.kind.task.field.assignee'), type: 'text' },\n ] },\n { id: 'note', label: t('composites.kind.note.label'), icon: 'i-lucide-sticky-note', titlePlaceholder: t('composites.kind.note.title'), descriptionPlaceholder: t('composites.kind.note.description'), fields: [] },\n { id: 'document', label: t('composites.kind.document.label'), icon: 'i-lucide-file-text', titlePlaceholder: t('composites.kind.document.title'), descriptionPlaceholder: t('composites.kind.document.description'), fields: [\n { key: 'filename', label: t('composites.kind.document.field.filename'), type: 'text' },\n { key: 'mimeType', label: t('composites.kind.document.field.mimeType'), type: 'text' },\n { key: 'size', label: t('composites.kind.document.field.size'), type: 'number' },\n { key: 'importedAt', label: t('composites.kind.document.field.importedAt'), type: 'date' },\n { key: 'transcript', label: t('composites.kind.document.field.transcript'), type: 'textarea' },\n ] },\n { id: 'folder', label: t('composites.kind.folder.label'), icon: 'i-lucide-folder', titlePlaceholder: t('composites.kind.folder.title'), descriptionPlaceholder: t('composites.kind.folder.description'), fields: [] },\n ]\n}\n\n/** The built-in kinds in the default language, for server-side and context-free code (`Composite`'s default option, an editor without a context). */\nexport const fallbackCompositeKinds = (): readonly CompositeKind[] => defaultCompositeKinds(DEFAULT_TRANSLATE)\n\nconst DEFAULT_TRANSLATE = createTranslator()\n\n/** Kind `id` from `kinds`; an unknown id gets the `section` kind's presentation (else the first kind's) under its own id and label. */\nexport function compositeKind(kinds: readonly CompositeKind[], id: string): CompositeKind {\n const known = kinds.find(kind => kind.id === id)\n if (known) return known\n const base = kinds.find(kind => kind.id === 'section') ?? kinds[0] ?? fallbackCompositeKinds()[0]!\n return { ...base, id, label: id }\n}\n\n/** Kinds that accept every Baustein kind when their definition sets no `accepts`. */\nconst ACCEPTS_ALL_BY_DEFAULT: Record<string, true> = { folder: true, document: true }\n\n/**\n * Whether a Baustein of kind `parentKind` accepts one of kind `childKind` as nested Baustein. The `accepts` of the kind\n * in `kinds` decides; without one (or for a kind `kinds` does not list) only `folder` and `document` accept all.\n */\nexport function kindAccepts(kinds: readonly CompositeKind[], parentKind: string, childKind: string): boolean {\n const accepts = kinds.find(kind => kind.id === parentKind)?.accepts\n if (accepts === undefined) return Object.hasOwn(ACCEPTS_ALL_BY_DEFAULT, parentKind)\n return accepts === 'all' || accepts.includes(childKind)\n}\n"],"mappings":";;;;;;;;AAmDA,SAAgB,sBAAsB,GAAwC;CAC5E,IAAI,QAAQ,cAAc,IAAI,CAAC;CAC/B,IAAI,CAAC,OAAO,cAAc,IAAI,GAAG,QAAQ,kBAAkB,CAAC,CAAC;CAC7D,OAAO;AACT;AAEA,MAAM,gCAAgB,IAAI,QAA6C;AAEvE,SAAS,kBAAkB,GAAwC;CACjE,OAAO;EACL;GAAE,IAAI;GAAW,OAAO,EAAE,+BAA+B;GAAG,MAAM;GAAmB,kBAAkB,EAAE,+BAA+B;GAAG,wBAAwB,EAAE,qCAAqC;GAAG,QAAQ,CAAC;GAAG,SAAS;EAAM;EACxO;GAAE,IAAI;GAAQ,OAAO,EAAE,4BAA4B;GAAG,MAAM;GAAyB,kBAAkB,EAAE,4BAA4B;GAAG,wBAAwB,EAAE,kCAAkC;GAAG,QAAQ;IAC7M;KAAE,KAAK;KAAU,OAAO,EAAE,mCAAmC;KAAG,MAAM;KAAU,SAAS;MACvF;OAAE,OAAO;OAAQ,OAAO,EAAE,kCAAkC;MAAE;MAC9D;OAAE,OAAO;OAAS,OAAO,EAAE,mCAAmC;MAAE;MAChE;OAAE,OAAO;OAAQ,OAAO,EAAE,kCAAkC;MAAE;KAChE;IAAE;IACF;KAAE,KAAK;KAAO,OAAO,EAAE,gCAAgC;KAAG,MAAM;IAAO;IACvE;KAAE,KAAK;KAAY,OAAO,EAAE,qCAAqC;KAAG,MAAM;IAAO;GACnF;EAAE;EACF;GAAE,IAAI;GAAQ,OAAO,EAAE,4BAA4B;GAAG,MAAM;GAAwB,kBAAkB,EAAE,4BAA4B;GAAG,wBAAwB,EAAE,kCAAkC;GAAG,QAAQ,CAAC;EAAE;EACjN;GAAE,IAAI;GAAY,OAAO,EAAE,gCAAgC;GAAG,MAAM;GAAsB,kBAAkB,EAAE,gCAAgC;GAAG,wBAAwB,EAAE,sCAAsC;GAAG,QAAQ;IAC1N;KAAE,KAAK;KAAY,OAAO,EAAE,yCAAyC;KAAG,MAAM;IAAO;IACrF;KAAE,KAAK;KAAY,OAAO,EAAE,yCAAyC;KAAG,MAAM;IAAO;IACrF;KAAE,KAAK;KAAQ,OAAO,EAAE,qCAAqC;KAAG,MAAM;IAAS;IAC/E;KAAE,KAAK;KAAc,OAAO,EAAE,2CAA2C;KAAG,MAAM;IAAO;IACzF;KAAE,KAAK;KAAc,OAAO,EAAE,2CAA2C;KAAG,MAAM;IAAW;GAC/F;EAAE;EACF;GAAE,IAAI;GAAU,OAAO,EAAE,8BAA8B;GAAG,MAAM;GAAmB,kBAAkB,EAAE,8BAA8B;GAAG,wBAAwB,EAAE,oCAAoC;GAAG,QAAQ,CAAC;EAAE;CACtN;AACF;;AAGA,MAAa,+BAAyD,sBAAsB,iBAAiB;AAE7G,MAAM,oBAAoB,iBAAiB;;AAG3C,SAAgB,cAAc,OAAiC,IAA2B;CACxF,MAAM,QAAQ,MAAM,MAAK,SAAQ,KAAK,OAAO,EAAE;CAC/C,IAAI,OAAO,OAAO;CAElB,OAAO;EAAE,GADI,MAAM,MAAK,SAAQ,KAAK,OAAO,SAAS,KAAK,MAAM,MAAM,uBAAuB,CAAC,CAAC;EAC7E;EAAI,OAAO;CAAG;AAClC;;AAGA,MAAM,yBAA+C;CAAE,QAAQ;CAAM,UAAU;AAAK;;;;;AAMpF,SAAgB,YAAY,OAAiC,YAAoB,WAA4B;CAC3G,MAAM,UAAU,MAAM,MAAK,SAAQ,KAAK,OAAO,UAAU,CAAC,EAAE;CAC5D,IAAI,YAAY,KAAA,GAAW,OAAO,OAAO,OAAO,wBAAwB,UAAU;CAClF,OAAO,YAAY,SAAS,QAAQ,SAAS,SAAS;AACxD"}
@@ -1,3 +1,4 @@
1
+ import { Translate } from "../../i18n/index.js";
1
2
  import { Editor } from "@tiptap/core";
2
3
  import { Node as Node$1 } from "@tiptap/pm/model";
3
4
  //#region src/features/composites/outline.d.ts
@@ -20,15 +21,27 @@ export interface OutlineNode {
20
21
  * take their place in the parent's list.
21
22
  */
22
23
  export declare function compositeOutline(doc: Node$1, hiddenKinds: ReadonlySet<string>, untitled: string): OutlineNode[];
24
+ /** Whether a block may move into a Baustein; `reason` is a short sentence for the UI (in the language of the `t` it was judged with) when it may not. */
25
+ export type MoveVerdict = {
26
+ ok: true;
27
+ } | {
28
+ ok: false;
29
+ reason: string;
30
+ };
23
31
  /**
24
- * Whether block `id` can move into the composite `targetId` (`null` = the end of the document): both exist, the target
25
- * is a composite and neither the block itself nor inside it.
32
+ * Whether block `id` may move into the Baustein `targetId` (`null` = the end of the document), with the reason when
33
+ * not: a block is missing, the target is no Baustein, the target is the block itself or inside it, or the target's kind
34
+ * does not accept the block's kind (`CompositeKind.accepts`). A block that is no Baustein may move into any Baustein,
35
+ * unless it holds Bausteine of a refused kind; Bausteine nested inside a moved Baustein stay with it and are not
36
+ * checked. Moving a block to the end of the Baustein it already sits in adds no nesting and is never refused for it.
26
37
  */
38
+ export declare function moveIntoVerdict(editor: Editor, id: string, targetId: string | null, t: Translate): MoveVerdict;
39
+ /** Whether block `id` can move into the Baustein `targetId` (`null` = the end of the document); see `moveIntoVerdict`. */
27
40
  export declare function canMoveBlockInto(editor: Editor, id: string, targetId: string | null): boolean;
28
41
  /**
29
- * Moves block `id` to the end of the body of the composite `targetId` (`null` = the end of the document) in a single
30
- * transaction, i.e. one undo step. `false` when nothing moved: a block is missing, the target is not a composite, or it
31
- * is the block itself or lies inside it.
42
+ * Moves block `id` to the end of the body of the Baustein `targetId` (`null` = the end of the document) in a single
43
+ * transaction, i.e. one undo step. `false` when nothing moved: `moveIntoVerdict` refuses the move (or the editor
44
+ * refused the transaction).
32
45
  */
33
46
  export declare function moveBlockInto(editor: Editor, id: string, targetId: string | null): boolean;
34
47
  //#endregion