@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,18 +1,12 @@
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";
1
+ import { settingsTabsOf } from "../core/define-editor.js";
7
2
  import { useEditorContext } from "./context.js";
8
- import { EMPTY_EDITOR_CONTENT } from "../core/schema/index.js";
3
+ import { createSurfaceSession } from "../core/surface-session.js";
9
4
  import { EditorSettingsModal } from "./EditorSettingsModal.js";
10
5
  import { EditorUiContext } from "./editor-ui.js";
11
6
  import { reactNotifier } from "./notify.js";
12
7
  import { reactSuggestionRenderer } from "./suggestion-renderer.js";
13
8
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
14
9
  import { jsx, jsxs } from "react/jsx-runtime";
15
- import { getSchema } from "@tiptap/core";
16
10
  import { EditorContent, useEditor } from "@tiptap/react";
17
11
  //#region src/react/EditorSurface.tsx
18
12
  /** A boolean that is the host's (`value` given) or the component's own (`value` undefined); the setter is stable. */
@@ -42,10 +36,12 @@ function useOpenState(value, onChange) {
42
36
  latest
43
37
  };
44
38
  }
45
- /** Everything fixed at mount: preset, context, extensions, content check; callbacks are read from the surface's props. */
39
+ /**
40
+ * Everything fixed at mount: the chrome handle and the framework-free session (preset, context, extensions, content
41
+ * check, setup lifecycle); callbacks are read from the surface's latest props.
42
+ */
46
43
  function createSurface(props, app, parts) {
47
- const { preset, collaboration } = props.current;
48
- if (collaboration && !preset.features.some((feature) => feature.id === "collab")) throw new Error("@ecsplico/editor: `collaboration` needs a preset with the collaboration feature");
44
+ const { preset, collaboration, value, editable, metadata, documentId, upload, abilities, slashCommands, extensions, blockActions } = props.current;
49
45
  const ui = {
50
46
  openSettings: parts.openSettings,
51
47
  toggleSidebar: () => parts.sidebar.set(!parts.sidebar.latest.current.open),
@@ -54,76 +50,31 @@ function createSurface(props, app, parts) {
54
50
  return parts.comments.latest.current.open;
55
51
  }
56
52
  };
57
- const registry = createMetadataRegistry({ onCreate: (list, item) => props.current.onMetadataCreate?.(list, item) });
58
- registry.replace({ ...props.current.metadata });
59
- const ctx = createFeatureContext({
60
- app,
61
- preset,
62
- ui,
63
- metadata: registry,
64
- notify: reactNotifier,
65
- getEditor: () => parts.editor.current ?? void 0,
66
- ...props.current.documentId !== void 0 && { documentId: props.current.documentId },
67
- ...props.current.upload && { upload: props.current.upload },
68
- ...collaboration && { collaboration },
69
- ...props.current.abilities && { abilities: props.current.abilities },
70
- ...props.current.slashCommands && { slashCommands: props.current.slashCommands }
71
- });
72
- const extensions = [
73
- presetExtension(preset),
74
- SuggestionPopup.configure({ renderer: reactSuggestionRenderer }),
75
- ...contributions(preset, "extensions").flatMap((factory) => factory(ctx)),
76
- ...props.current.extensions ?? []
77
- ];
78
- const schema = getSchema(extensions);
79
- const overlays = preset.features.flatMap((feature) => [...feature.overlays ?? []]);
80
- const blockActions = blockActionsOf(preset, ctx, props.current.blockActions);
81
- /** `json` migrated by the preset and checked against the installed schema: unknown nodes/marks or a broken structure are errors. */
82
- const validated = (json) => {
83
- try {
84
- const migrated = preset.pipeline.migrateContent(json);
85
- schema.nodeFromJSON(migrated).check();
86
- return { json: migrated };
87
- } catch (error) {
88
- return { error: error instanceof Error ? error : new Error(String(error)) };
89
- }
90
- };
91
- let lastEmitted;
92
- let content;
93
- let initialError;
94
- if (!collaboration) {
95
- const initial = props.current.value ? validated(props.current.value) : { json: EMPTY_EDITOR_CONTENT };
96
- if ("error" in initial) initialError = initial.error;
97
- content = "json" in initial ? initial.json : EMPTY_EDITOR_CONTENT;
98
- }
99
53
  return {
100
54
  preset,
101
- collaboration,
102
55
  ui,
103
- registry,
104
- ctx,
105
- extensions,
106
- overlays,
107
- blockActions,
108
- validated,
109
- content,
110
- initialError,
111
- get lastEmitted() {
112
- return lastEmitted;
113
- },
114
- set lastEmitted(json) {
115
- lastEmitted = json;
116
- },
117
- /** What the effects below already applied; they only react to a change. */
118
- applied: {
119
- value: props.current.value,
120
- editable: props.current.editable ?? true,
121
- metadata: props.current.metadata
122
- },
123
- /** Teardowns of the `setup` contributions that are currently active. */
124
- teardowns: [],
125
- errorReported: false,
126
- announced: null
56
+ session: createSurfaceSession({
57
+ app,
58
+ preset,
59
+ ui,
60
+ notify: reactNotifier,
61
+ suggestionRenderer: reactSuggestionRenderer,
62
+ getEditor: () => parts.editor.current ?? void 0,
63
+ collaboration,
64
+ value,
65
+ editable,
66
+ metadata,
67
+ documentId,
68
+ upload,
69
+ abilities,
70
+ slashCommands,
71
+ extensions,
72
+ blockActions,
73
+ onChange: (content) => props.current.onChange?.(content),
74
+ onMetadataCreate: (list, item) => props.current.onMetadataCreate?.(list, item),
75
+ onContentError: (error) => props.current.onContentError?.(error),
76
+ onReady: (editor) => props.current.onReady?.(editor)
77
+ })
127
78
  };
128
79
  }
129
80
  const EditorSurface = forwardRef(function EditorSurface(props, ref) {
@@ -149,91 +100,34 @@ const EditorSurface = forwardRef(function EditorSurface(props, ref) {
149
100
  tab: tab ?? state.tab
150
101
  }));
151
102
  }, []);
152
- const [surface] = useState(() => createSurface(latest, app, {
103
+ const [{ preset, ui, session }] = useState(() => createSurface(latest, app, {
153
104
  editor: editorRef,
154
105
  sidebar,
155
106
  comments,
156
107
  openSettings
157
108
  }));
158
- const { preset, collaboration } = surface;
159
109
  const editor = useEditor({
160
- extensions: surface.extensions,
161
- ...!collaboration && {
162
- content: surface.content,
163
- onUpdate: ({ editor: instance }) => {
164
- const json = instance.getJSON();
165
- surface.lastEmitted = json;
166
- latest.current.onChange?.(json);
167
- }
168
- },
169
- editable,
110
+ ...session.editorOptions,
170
111
  immediatelyRender: true,
171
- shouldRerenderOnTransaction: false,
172
- editorProps: { attributes: { class: "tiptap" } }
112
+ shouldRerenderOnTransaction: false
173
113
  });
174
114
  editorRef.current = editor;
175
115
  useEffect(() => {
176
- for (const run of contributions(preset, "setup")) {
177
- const teardown = run(surface.ctx);
178
- if (teardown) surface.teardowns.push(teardown);
179
- }
180
- return () => {
181
- for (const teardown of surface.teardowns.splice(0)) teardown();
182
- };
183
- }, [surface, preset]);
184
- useEffect(() => {
185
- if (surface.initialError && !surface.errorReported) {
186
- surface.errorReported = true;
187
- latest.current.onContentError?.(surface.initialError);
188
- }
189
- const unregister = registerEditor(editor, {
190
- ui: surface.ui,
191
- preset,
192
- ctx: surface.ctx,
193
- blockActions: surface.blockActions
194
- });
195
- if (surface.announced !== editor) {
196
- surface.announced = editor;
197
- latest.current.onReady?.(editor);
198
- }
199
- return unregister;
200
- }, [
201
- editor,
202
- surface,
203
- preset
204
- ]);
205
- useEffect(() => {
206
- if (value === surface.applied.value) return;
207
- surface.applied.value = value;
208
- if (collaboration || !value || editor.isDestroyed || value === surface.lastEmitted) return;
209
- const checked = surface.validated(value);
210
- if ("error" in checked) {
211
- latest.current.onContentError?.(checked.error);
212
- return;
213
- }
214
- editor.commands.setContent(checked.json, { emitUpdate: false });
215
- }, [
116
+ session.activate();
117
+ return () => session.deactivate();
118
+ }, [session]);
119
+ useEffect(() => session.register(editor), [editor, session]);
120
+ useEffect(() => session.setValue(value), [
216
121
  value,
217
122
  editor,
218
- surface,
219
- collaboration
123
+ session
220
124
  ]);
221
- useEffect(() => {
222
- if (editable === surface.applied.editable) return;
223
- surface.applied.editable = editable;
224
- if (editor.isDestroyed) return;
225
- editor.setEditable(editable, false);
226
- editor.view.dispatch(editor.state.tr.setMeta("addToHistory", false));
227
- }, [
125
+ useEffect(() => session.setEditable(editable), [
228
126
  editable,
229
127
  editor,
230
- surface
128
+ session
231
129
  ]);
232
- useEffect(() => {
233
- if (metadata === surface.applied.metadata) return;
234
- surface.applied.metadata = metadata;
235
- surface.registry.replace({ ...metadata });
236
- }, [metadata, surface]);
130
+ useEffect(() => session.setMetadata(metadata), [metadata, session]);
237
131
  useEffect(() => {
238
132
  if (sidebar.open) comments.set(false);
239
133
  }, [sidebar.open, comments.set]);
@@ -245,7 +139,7 @@ const EditorSurface = forwardRef(function EditorSurface(props, ref) {
245
139
  openSettings
246
140
  }), [editor, openSettings]);
247
141
  return /* @__PURE__ */ jsxs(EditorUiContext.Provider, {
248
- value: surface.ui,
142
+ value: ui,
249
143
  children: [
250
144
  /* @__PURE__ */ jsx("div", {
251
145
  className: "relative",
@@ -254,7 +148,7 @@ const EditorSurface = forwardRef(function EditorSurface(props, ref) {
254
148
  className: "relative"
255
149
  })
256
150
  }),
257
- surface.overlays.map((Overlay, index) => /* @__PURE__ */ jsx(Overlay, { editor }, index)),
151
+ session.overlays.map((Overlay, index) => /* @__PURE__ */ jsx(Overlay, { editor }, index)),
258
152
  (props.settingsUi ?? "internal") === "internal" && /* @__PURE__ */ jsx(EditorSettingsModal, {
259
153
  preset,
260
154
  open: settingsState.open,
@@ -1 +1 @@
1
- {"version":3,"file":"EditorSurface.js","names":[],"sources":["../../src/react/EditorSurface.tsx"],"sourcesContent":["import { type AnyExtension, type Editor, getSchema, type JSONContent } from '@tiptap/core'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport { type ComponentType, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'\nimport type { EditorContext } from '../core/context'\nimport { contributions, settingsTabsOf } from '../core/define-editor'\nimport { type EditorUi, registerEditor } from '../core/editor-ui'\nimport type { AbilitiesOptions, BlockAction, SlashCommandItem } from '../core/extension-points'\nimport type { CollaborationSession, EditorFeature, EditorPreset, 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 { useEditorContext } from './context'\nimport { EditorSettingsModal } from './EditorSettingsModal'\nimport { EditorUiContext } from './editor-ui'\nimport { reactNotifier } from './notify'\nimport { reactSuggestionRenderer } from './suggestion-renderer'\n\nexport type ReactFeature = EditorFeature<ComponentType<{ editor: Editor }>>\nexport type ReactPreset = EditorPreset<ComponentType<{ editor: Editor }>>\n\nexport interface EditorSurfaceProps {\n /** The features this editor is made of (`defineEditor(...)`); fixed at mount, remount (`key`) to change it. */\n preset: ReactPreset\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, with `onChange`; the editor keeps its own undo history. */\n value?: JSONContent\n onChange?: (content: JSONContent) => void\n /** Host lists (`mentions`, `hashtags`, `speakers`, custom) features' suggestions search. */\n metadata?: EditorMetadata\n /** Deep-merged into the `Abilities` defaults; read once at mount. */\n abilities?: Partial<AbilitiesOptions>\n /** Added after the features' extensions; read once at mount. */\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 mount. */\n slashCommands?: (defaults: SlashCommandItem[]) => SlashCommandItem[]\n /** Document whose package stores uploads (media feature). Read once at mount. */\n documentId?: string\n /** Stores an uploaded file and returns its URL (media feature). Read once at mount. */\n upload?: UploadFn\n editable?: boolean\n /** `internal`: the surface owns its settings UI; `external`: it calls `onSettingsOpen` and the host shows the panel. */\n settingsUi?: 'internal' | 'external'\n /** The editor exists and its chrome is registered (`useEditorUi(editor)`, a host toolbar). */\n onReady?: (editor: Editor) => void\n onMetadataCreate?: (list: string, item: MetadataItem) => void\n /** The JSON handed in (initially or later) does not fit this editor's schema; the last valid document stays. */\n onContentError?: (error: Error) => void\n /** `settingsUi: 'external'`: the editor asks the host to open its settings (on `tab` when given). */\n onSettingsOpen?: (tab?: string) => void\n /** Whether this editor's AI sidebar is open (`toggleSidebar`): controlled when given. */\n sidebarOpen?: boolean\n onSidebarOpenChange?: (open: boolean) => void\n /**\n * Whether this editor's comments margin is open (`toggleComments`): controlled when given. The AI sidebar and the\n * margin exclude each other: whichever opens last closes the other.\n */\n commentsOpen?: boolean\n onCommentsOpenChange?: (open: boolean) => void\n}\n\nexport interface EditorSurfaceHandle {\n editor: Editor | null\n openSettings(tab?: string): void\n}\n\n/** A boolean that is the host's (`value` given) or the component's own (`value` undefined); the setter is stable. */\nfunction useOpenState(value: boolean | undefined, onChange: ((open: boolean) => void) | undefined) {\n const [internal, setInternal] = useState(false)\n const controlled = value !== undefined\n const open = value ?? internal\n const latest = useRef({ open, controlled, onChange })\n latest.current = { open, controlled, onChange }\n const set = useCallback((next: boolean) => {\n const current = latest.current\n if (next === current.open) return\n current.open = next\n if (!current.controlled) setInternal(next)\n current.onChange?.(next)\n }, [])\n return { open, set, latest }\n}\n\n/** Everything fixed at mount: preset, context, extensions, content check; callbacks are read from the surface's props. */\nfunction createSurface(props: { current: EditorSurfaceProps }, app: EditorContext, parts: {\n editor: { current: Editor | null }\n sidebar: { set: (open: boolean) => void, latest: { current: { open: boolean } } }\n comments: { set: (open: boolean) => void, latest: { current: { open: boolean } } }\n openSettings: (tab?: string) => void\n}) {\n const { preset, collaboration } = props.current\n // The mode is fixed at mount; hosts switch it by remounting (`key`).\n if (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 const ui: EditorUi = {\n openSettings: parts.openSettings,\n toggleSidebar: () => parts.sidebar.set(!parts.sidebar.latest.current.open),\n toggleComments: () => parts.comments.set(!parts.comments.latest.current.open),\n get commentsOpen() {\n return parts.comments.latest.current.open\n },\n }\n\n const registry = createMetadataRegistry({ onCreate: (list, item) => props.current.onMetadataCreate?.(list, item) })\n registry.replace({ ...props.current.metadata })\n\n const ctx = createFeatureContext({\n app,\n preset,\n ui,\n metadata: registry,\n notify: reactNotifier,\n getEditor: () => parts.editor.current ?? undefined,\n ...(props.current.documentId !== undefined && { documentId: props.current.documentId }),\n ...(props.current.upload && { upload: props.current.upload }),\n ...(collaboration && { collaboration }),\n ...(props.current.abilities && { abilities: props.current.abilities }),\n ...(props.current.slashCommands && { slashCommands: props.current.slashCommands }),\n })\n\n const extensions: AnyExtension[] = [\n presetExtension(preset),\n SuggestionPopup.configure({ renderer: reactSuggestionRenderer }),\n ...contributions(preset, 'extensions').flatMap(factory => factory(ctx)),\n ...(props.current.extensions ?? []),\n ]\n const schema = getSchema(extensions)\n const overlays = preset.features.flatMap(feature => [...(feature.overlays ?? [])])\n const blockActions = blockActionsOf(preset, ctx, props.current.blockActions)\n\n /** `json` migrated by the preset and checked against the installed schema: unknown nodes/marks or a broken structure are errors. */\n const validated = (json: JSONContent): { json: JSONContent } | { error: Error } => {\n try {\n const migrated = preset.pipeline.migrateContent(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 `onChange`; the host handing it back is not an external change.\n let lastEmitted: JSONContent | undefined\n let content: JSONContent | undefined\n let initialError: Error | undefined\n if (!collaboration) {\n const initial = props.current.value ? validated(props.current.value) : { json: EMPTY_EDITOR_CONTENT }\n if ('error' in initial) initialError = initial.error\n content = 'json' in initial ? initial.json : EMPTY_EDITOR_CONTENT\n }\n\n return {\n preset,\n collaboration,\n ui,\n registry,\n ctx,\n extensions,\n overlays,\n blockActions,\n validated,\n content,\n initialError,\n get lastEmitted() {\n return lastEmitted\n },\n set lastEmitted(json: JSONContent | undefined) {\n lastEmitted = json\n },\n /** What the effects below already applied; they only react to a change. */\n applied: {\n value: props.current.value,\n editable: props.current.editable ?? true,\n metadata: props.current.metadata,\n },\n /** Teardowns of the `setup` contributions that are currently active. */\n teardowns: [] as (() => void)[],\n errorReported: false,\n announced: null as Editor | null,\n }\n}\n\nexport const EditorSurface = forwardRef<EditorSurfaceHandle, EditorSurfaceProps>(function EditorSurface(props, ref) {\n const { value, editable = true, metadata } = props\n const latest = useRef(props)\n latest.current = props\n\n const app = useEditorContext()\n const editorRef = useRef<Editor | null>(null)\n const sidebar = useOpenState(props.sidebarOpen, props.onSidebarOpenChange)\n const comments = useOpenState(props.commentsOpen, props.onCommentsOpenChange)\n // `settingsUi: 'internal'`: the surface's own settings modal (`openSettings` opens it).\n const [settingsState, setSettingsState] = useState<{ open: boolean, tab: string | undefined }>({ open: false, tab: undefined })\n\n const openSettings = useCallback((tab?: string) => {\n // Nothing to open without a contributed tab.\n if (!settingsTabsOf(latest.current.preset, app).length) return\n if ((latest.current.settingsUi ?? 'internal') === 'external') {\n latest.current.onSettingsOpen?.(tab)\n return\n }\n setSettingsState(state => ({ open: true, tab: tab ?? state.tab }))\n }, [])\n\n const [surface] = useState(() => createSurface(latest, app, { editor: editorRef, sidebar, comments, openSettings }))\n const { preset, collaboration } = surface\n\n const editor = useEditor({\n extensions: surface.extensions,\n ...(!collaboration && {\n content: surface.content,\n onUpdate: ({ editor: instance }: { editor: Editor }) => {\n const json = instance.getJSON()\n surface.lastEmitted = json\n latest.current.onChange?.(json)\n },\n }),\n editable,\n immediatelyRender: true,\n shouldRerenderOnTransaction: false,\n editorProps: { attributes: { class: 'tiptap' } },\n })\n editorRef.current = editor\n\n // Per-mount side effects of features (`setup`). Run and teardown are paired, so StrictMode's mount, cleanup, mount\n // leaves exactly one active setup, and unmounting tears it down.\n useEffect(() => {\n for (const run of contributions(preset, 'setup')) {\n const teardown = run(surface.ctx)\n if (teardown) surface.teardowns.push(teardown)\n }\n return () => {\n for (const teardown of surface.teardowns.splice(0)) teardown()\n }\n }, [surface, preset])\n\n // The editor exists from the first render; announce it once its chrome is registered first, so a host toolbar that\n // receives it through `onReady` finds the chrome.\n useEffect(() => {\n if (surface.initialError && !surface.errorReported) {\n surface.errorReported = true\n latest.current.onContentError?.(surface.initialError)\n }\n const unregister = registerEditor(editor, { ui: surface.ui, preset, ctx: surface.ctx, blockActions: surface.blockActions })\n if (surface.announced !== editor) {\n surface.announced = editor\n latest.current.onReady?.(editor)\n }\n return unregister\n }, [editor, surface, preset])\n\n // External replacement of the standalone document (the host loaded or reset it); not an edit, so no update event.\n useEffect(() => {\n if (value === surface.applied.value) return\n surface.applied.value = value\n if (collaboration || !value || editor.isDestroyed || value === surface.lastEmitted) return\n const checked = surface.validated(value)\n if ('error' in checked) {\n latest.current.onContentError?.(checked.error)\n return\n }\n editor.commands.setContent(checked.json, { emitUpdate: false })\n }, [value, editor, surface, collaboration])\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.\n useEffect(() => {\n if (editable === surface.applied.editable) return\n surface.applied.editable = editable\n if (editor.isDestroyed) return\n editor.setEditable(editable, false)\n editor.view.dispatch(editor.state.tr.setMeta('addToHistory', false))\n }, [editable, editor, surface])\n\n useEffect(() => {\n if (metadata === surface.applied.metadata) return\n surface.applied.metadata = metadata\n surface.registry.replace({ ...metadata })\n }, [metadata, surface])\n\n // The AI sidebar and the comments margin exclude each other: whichever opens last closes the other.\n useEffect(() => {\n if (sidebar.open) comments.set(false)\n }, [sidebar.open, comments.set])\n useEffect(() => {\n if (comments.open) sidebar.set(false)\n }, [comments.open, sidebar.set])\n\n useImperativeHandle(ref, () => ({ editor, openSettings }), [editor, openSettings])\n\n return (\n <EditorUiContext.Provider value={surface.ui}>\n <div className=\"relative\">\n <EditorContent editor={editor} className=\"relative\" />\n </div>\n {surface.overlays.map((Overlay, index) => <Overlay key={index} editor={editor} />)}\n {(props.settingsUi ?? 'internal') === 'internal' && (\n <EditorSettingsModal\n preset={preset}\n open={settingsState.open}\n onOpenChange={open => setSettingsState(state => ({ ...state, open }))}\n {...(settingsState.tab !== undefined && { tab: settingsState.tab })}\n onTabChange={tab => setSettingsState(state => ({ ...state, tab }))}\n />\n )}\n </EditorUiContext.Provider>\n )\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"EditorSurface.js","names":[],"sources":["../../src/react/EditorSurface.tsx"],"sourcesContent":["import type { AnyExtension, Editor, JSONContent } from '@tiptap/core'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport { type ComponentType, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'\nimport type { EditorContext } from '../core/context'\nimport { settingsTabsOf } from '../core/define-editor'\nimport type { EditorUi } from '../core/editor-ui'\nimport type { AbilitiesOptions, BlockAction, SlashCommandItem } from '../core/extension-points'\nimport type { CollaborationSession, EditorFeature, EditorPreset, UploadFn } from '../core/feature'\nimport type { EditorMetadata, MetadataItem } from '../core/metadata-types'\nimport { createSurfaceSession } from '../core/surface-session'\nimport { useEditorContext } from './context'\nimport { EditorSettingsModal } from './EditorSettingsModal'\nimport { EditorUiContext } from './editor-ui'\nimport { reactNotifier } from './notify'\nimport { reactSuggestionRenderer } from './suggestion-renderer'\n\nexport type ReactFeature = EditorFeature<ComponentType<{ editor: Editor }>>\nexport type ReactPreset = EditorPreset<ComponentType<{ editor: Editor }>>\n\nexport interface EditorSurfaceProps {\n /** The features this editor is made of (`defineEditor(...)`); fixed at mount, remount (`key`) to change it. */\n preset: ReactPreset\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, with `onChange`; the editor keeps its own undo history. */\n value?: JSONContent\n onChange?: (content: JSONContent) => void\n /** Host lists (`mentions`, `hashtags`, `speakers`, custom) features' suggestions search. */\n metadata?: EditorMetadata\n /** Deep-merged into the `Abilities` defaults; read once at mount. */\n abilities?: Partial<AbilitiesOptions>\n /** Added after the features' extensions; read once at mount. */\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 mount. */\n slashCommands?: (defaults: SlashCommandItem[]) => SlashCommandItem[]\n /** Document whose package stores uploads (media feature). Read once at mount. */\n documentId?: string\n /** Stores an uploaded file and returns its URL (media feature). Read once at mount. */\n upload?: UploadFn\n editable?: boolean\n /** `internal`: the surface owns its settings UI; `external`: it calls `onSettingsOpen` and the host shows the panel. */\n settingsUi?: 'internal' | 'external'\n /** The editor exists and its chrome is registered (`useEditorUi(editor)`, a host toolbar). */\n onReady?: (editor: Editor) => void\n onMetadataCreate?: (list: string, item: MetadataItem) => void\n /** The JSON handed in (initially or later) does not fit this editor's schema; the last valid document stays. */\n onContentError?: (error: Error) => void\n /** `settingsUi: 'external'`: the editor asks the host to open its settings (on `tab` when given). */\n onSettingsOpen?: (tab?: string) => void\n /** Whether this editor's AI sidebar is open (`toggleSidebar`): controlled when given. */\n sidebarOpen?: boolean\n onSidebarOpenChange?: (open: boolean) => void\n /**\n * Whether this editor's comments margin is open (`toggleComments`): controlled when given. The AI sidebar and the\n * margin exclude each other: whichever opens last closes the other.\n */\n commentsOpen?: boolean\n onCommentsOpenChange?: (open: boolean) => void\n}\n\nexport interface EditorSurfaceHandle {\n editor: Editor | null\n openSettings(tab?: string): void\n}\n\n/** A boolean that is the host's (`value` given) or the component's own (`value` undefined); the setter is stable. */\nfunction useOpenState(value: boolean | undefined, onChange: ((open: boolean) => void) | undefined) {\n const [internal, setInternal] = useState(false)\n const controlled = value !== undefined\n const open = value ?? internal\n const latest = useRef({ open, controlled, onChange })\n latest.current = { open, controlled, onChange }\n const set = useCallback((next: boolean) => {\n const current = latest.current\n if (next === current.open) return\n current.open = next\n if (!current.controlled) setInternal(next)\n current.onChange?.(next)\n }, [])\n return { open, set, latest }\n}\n\n/**\n * Everything fixed at mount: the chrome handle and the framework-free session (preset, context, extensions, content\n * check, setup lifecycle); callbacks are read from the surface's latest props.\n */\nfunction createSurface(props: { current: EditorSurfaceProps }, app: EditorContext, parts: {\n editor: { current: Editor | null }\n sidebar: { set: (open: boolean) => void, latest: { current: { open: boolean } } }\n comments: { set: (open: boolean) => void, latest: { current: { open: boolean } } }\n openSettings: (tab?: string) => void\n}) {\n const { preset, collaboration, value, editable, metadata, documentId, upload, abilities, slashCommands, extensions, blockActions } = props.current\n\n const ui: EditorUi = {\n openSettings: parts.openSettings,\n toggleSidebar: () => parts.sidebar.set(!parts.sidebar.latest.current.open),\n toggleComments: () => parts.comments.set(!parts.comments.latest.current.open),\n get commentsOpen() {\n return parts.comments.latest.current.open\n },\n }\n\n // The mode is fixed at mount; hosts switch it by remounting (`key`).\n const session = createSurfaceSession({\n app,\n preset,\n ui,\n notify: reactNotifier,\n suggestionRenderer: reactSuggestionRenderer,\n getEditor: () => parts.editor.current ?? undefined,\n collaboration,\n value,\n editable,\n metadata,\n documentId,\n upload,\n abilities,\n slashCommands,\n extensions,\n blockActions,\n onChange: content => props.current.onChange?.(content),\n onMetadataCreate: (list, item) => props.current.onMetadataCreate?.(list, item),\n onContentError: error => props.current.onContentError?.(error),\n onReady: editor => props.current.onReady?.(editor),\n })\n return { preset, ui, session }\n}\n\nexport const EditorSurface = forwardRef<EditorSurfaceHandle, EditorSurfaceProps>(function EditorSurface(props, ref) {\n const { value, editable = true, metadata } = props\n const latest = useRef(props)\n latest.current = props\n\n const app = useEditorContext()\n const editorRef = useRef<Editor | null>(null)\n const sidebar = useOpenState(props.sidebarOpen, props.onSidebarOpenChange)\n const comments = useOpenState(props.commentsOpen, props.onCommentsOpenChange)\n // `settingsUi: 'internal'`: the surface's own settings modal (`openSettings` opens it).\n const [settingsState, setSettingsState] = useState<{ open: boolean, tab: string | undefined }>({ open: false, tab: undefined })\n\n const openSettings = useCallback((tab?: string) => {\n // Nothing to open without a contributed tab.\n if (!settingsTabsOf(latest.current.preset, app).length) return\n if ((latest.current.settingsUi ?? 'internal') === 'external') {\n latest.current.onSettingsOpen?.(tab)\n return\n }\n setSettingsState(state => ({ open: true, tab: tab ?? state.tab }))\n }, [])\n\n const [{ preset, ui, session }] = useState(() => createSurface(latest, app, { editor: editorRef, sidebar, comments, openSettings }))\n\n const editor = useEditor({\n ...session.editorOptions,\n immediatelyRender: true,\n shouldRerenderOnTransaction: false,\n })\n editorRef.current = editor\n\n // Per-mount side effects of features (`setup`). Activation and deactivation are paired, so StrictMode's mount,\n // cleanup, mount leaves exactly one active setup, and unmounting tears it down.\n useEffect(() => {\n session.activate()\n return () => session.deactivate()\n }, [session])\n\n // The editor exists from the first render; its chrome is registered before the session announces it through\n // `onReady`, so a host toolbar that receives it finds the chrome.\n useEffect(() => session.register(editor), [editor, session])\n\n // External replacement of the standalone document (the host loaded or reset it); not an edit, so no update event.\n useEffect(() => session.setValue(value), [value, editor, session])\n\n useEffect(() => session.setEditable(editable), [editable, editor, session])\n\n useEffect(() => session.setMetadata(metadata), [metadata, session])\n\n // The AI sidebar and the comments margin exclude each other: whichever opens last closes the other.\n useEffect(() => {\n if (sidebar.open) comments.set(false)\n }, [sidebar.open, comments.set])\n useEffect(() => {\n if (comments.open) sidebar.set(false)\n }, [comments.open, sidebar.set])\n\n useImperativeHandle(ref, () => ({ editor, openSettings }), [editor, openSettings])\n\n return (\n <EditorUiContext.Provider value={ui}>\n <div className=\"relative\">\n <EditorContent editor={editor} className=\"relative\" />\n </div>\n {session.overlays.map((Overlay, index) => <Overlay key={index} editor={editor} />)}\n {(props.settingsUi ?? 'internal') === 'internal' && (\n <EditorSettingsModal\n preset={preset}\n open={settingsState.open}\n onOpenChange={open => setSettingsState(state => ({ ...state, open }))}\n {...(settingsState.tab !== undefined && { tab: settingsState.tab })}\n onTabChange={tab => setSettingsState(state => ({ ...state, tab }))}\n />\n )}\n </EditorUiContext.Provider>\n )\n})\n"],"mappings":";;;;;;;;;;;;AAoEA,SAAS,aAAa,OAA4B,UAAiD;CACjG,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAC9C,MAAM,aAAa,UAAU,KAAA;CAC7B,MAAM,OAAO,SAAS;CACtB,MAAM,SAAS,OAAO;EAAE;EAAM;EAAY;CAAS,CAAC;CACpD,OAAO,UAAU;EAAE;EAAM;EAAY;CAAS;CAQ9C,OAAO;EAAE;EAAM,KAPH,aAAa,SAAkB;GACzC,MAAM,UAAU,OAAO;GACvB,IAAI,SAAS,QAAQ,MAAM;GAC3B,QAAQ,OAAO;GACf,IAAI,CAAC,QAAQ,YAAY,YAAY,IAAI;GACzC,QAAQ,WAAW,IAAI;EACzB,GAAG,CAAC,CACa;EAAG;CAAO;AAC7B;;;;;AAMA,SAAS,cAAc,OAAwC,KAAoB,OAKhF;CACD,MAAM,EAAE,QAAQ,eAAe,OAAO,UAAU,UAAU,YAAY,QAAQ,WAAW,eAAe,YAAY,iBAAiB,MAAM;CAE3I,MAAM,KAAe;EACnB,cAAc,MAAM;EACpB,qBAAqB,MAAM,QAAQ,IAAI,CAAC,MAAM,QAAQ,OAAO,QAAQ,IAAI;EACzE,sBAAsB,MAAM,SAAS,IAAI,CAAC,MAAM,SAAS,OAAO,QAAQ,IAAI;EAC5E,IAAI,eAAe;GACjB,OAAO,MAAM,SAAS,OAAO,QAAQ;EACvC;CACF;CAyBA,OAAO;EAAE;EAAQ;EAAI,SAtBL,qBAAqB;GACnC;GACA;GACA;GACA,QAAQ;GACR,oBAAoB;GACpB,iBAAiB,MAAM,OAAO,WAAW,KAAA;GACzC;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,WAAU,YAAW,MAAM,QAAQ,WAAW,OAAO;GACrD,mBAAmB,MAAM,SAAS,MAAM,QAAQ,mBAAmB,MAAM,IAAI;GAC7E,iBAAgB,UAAS,MAAM,QAAQ,iBAAiB,KAAK;GAC7D,UAAS,WAAU,MAAM,QAAQ,UAAU,MAAM;EACnD,CAC2B;CAAE;AAC/B;AAEA,MAAa,gBAAgB,WAAoD,SAAS,cAAc,OAAO,KAAK;CAClH,MAAM,EAAE,OAAO,WAAW,MAAM,aAAa;CAC7C,MAAM,SAAS,OAAO,KAAK;CAC3B,OAAO,UAAU;CAEjB,MAAM,MAAM,iBAAiB;CAC7B,MAAM,YAAY,OAAsB,IAAI;CAC5C,MAAM,UAAU,aAAa,MAAM,aAAa,MAAM,mBAAmB;CACzE,MAAM,WAAW,aAAa,MAAM,cAAc,MAAM,oBAAoB;CAE5E,MAAM,CAAC,eAAe,oBAAoB,SAAqD;EAAE,MAAM;EAAO,KAAK,KAAA;CAAU,CAAC;CAE9H,MAAM,eAAe,aAAa,QAAiB;EAEjD,IAAI,CAAC,eAAe,OAAO,QAAQ,QAAQ,GAAG,CAAC,CAAC,QAAQ;EACxD,KAAK,OAAO,QAAQ,cAAc,gBAAgB,YAAY;GAC5D,OAAO,QAAQ,iBAAiB,GAAG;GACnC;EACF;EACA,kBAAiB,WAAU;GAAE,MAAM;GAAM,KAAK,OAAO,MAAM;EAAI,EAAE;CACnE,GAAG,CAAC,CAAC;CAEL,MAAM,CAAC,EAAE,QAAQ,IAAI,aAAa,eAAe,cAAc,QAAQ,KAAK;EAAE,QAAQ;EAAW;EAAS;EAAU;CAAa,CAAC,CAAC;CAEnI,MAAM,SAAS,UAAU;EACvB,GAAG,QAAQ;EACX,mBAAmB;EACnB,6BAA6B;CAC/B,CAAC;CACD,UAAU,UAAU;CAIpB,gBAAgB;EACd,QAAQ,SAAS;EACjB,aAAa,QAAQ,WAAW;CAClC,GAAG,CAAC,OAAO,CAAC;CAIZ,gBAAgB,QAAQ,SAAS,MAAM,GAAG,CAAC,QAAQ,OAAO,CAAC;CAG3D,gBAAgB,QAAQ,SAAS,KAAK,GAAG;EAAC;EAAO;EAAQ;CAAO,CAAC;CAEjE,gBAAgB,QAAQ,YAAY,QAAQ,GAAG;EAAC;EAAU;EAAQ;CAAO,CAAC;CAE1E,gBAAgB,QAAQ,YAAY,QAAQ,GAAG,CAAC,UAAU,OAAO,CAAC;CAGlE,gBAAgB;EACd,IAAI,QAAQ,MAAM,SAAS,IAAI,KAAK;CACtC,GAAG,CAAC,QAAQ,MAAM,SAAS,GAAG,CAAC;CAC/B,gBAAgB;EACd,IAAI,SAAS,MAAM,QAAQ,IAAI,KAAK;CACtC,GAAG,CAAC,SAAS,MAAM,QAAQ,GAAG,CAAC;CAE/B,oBAAoB,YAAY;EAAE;EAAQ;CAAa,IAAI,CAAC,QAAQ,YAAY,CAAC;CAEjF,OACE,qBAAC,gBAAgB,UAAjB;EAA0B,OAAO;EAAjC,UAAA;GACE,oBAAC,OAAD;IAAK,WAAU;IACb,UAAA,oBAAC,eAAD;KAAuB;KAAQ,WAAU;IAAY,CAAA;GAClD,CAAA;GACJ,QAAQ,SAAS,KAAK,SAAS,UAAU,oBAAC,SAAD,EAA6B,OAAS,GAAxB,KAAwB,CAAC;IAC/E,MAAM,cAAc,gBAAgB,cACpC,oBAAC,qBAAD;IACU;IACR,MAAM,cAAc;IACpB,eAAc,SAAQ,kBAAiB,WAAU;KAAE,GAAG;KAAO;IAAK,EAAE;IACpE,GAAK,cAAc,QAAQ,KAAA,KAAa,EAAE,KAAK,cAAc,IAAI;IACjE,cAAa,QAAO,kBAAiB,WAAU;KAAE,GAAG;KAAO;IAAI,EAAE;GAClE,CAAA;EAEqB;;AAE9B,CAAC"}
@@ -1,8 +1,7 @@
1
1
  import { abilitiesAtSelection } from "../core/abilities.js";
2
- import { createMetadataRegistry } from "../core/metadata.js";
3
2
  import { mergeToolbarGroups } from "../core/toolbar.js";
4
3
  import { contributions } from "../core/define-editor.js";
5
- import { editorEntryOf, notifierOf } from "../core/editor-ui.js";
4
+ import { borrowedFeatureContext, editorEntryOf } from "../core/editor-ui.js";
6
5
  import { useEditorContext } from "./context.js";
7
6
  import { useEditorI18n } from "./use-editor-i18n.js";
8
7
  import { cn } from "./ui/utils.js";
@@ -48,48 +47,12 @@ function EditorToolbar({ editor: given, preset }) {
48
47
  const { t } = useEditorI18n();
49
48
  const [state] = useState(() => {
50
49
  const ref = { current: editor };
51
- const fallbackMetadata = createMetadataRegistry();
52
- const entry = () => editorEntryOf(ref.current);
53
- const ui = () => entry()?.ui;
54
50
  return {
55
51
  ref,
56
- ctx: {
52
+ ctx: borrowedFeatureContext({
57
53
  app,
58
- locale: app.locale,
59
- t: app.t,
60
- settings: app.settings,
61
- notify: notifierOf(() => ref.current, app),
62
- fetch: app.fetch,
63
- ai: app.ai,
64
- get metadata() {
65
- return entry()?.ctx.metadata ?? fallbackMetadata;
66
- },
67
- ui: {
68
- openSettings: (tab) => ui()?.openSettings(tab),
69
- toggleSidebar: () => ui()?.toggleSidebar(),
70
- toggleComments: () => ui()?.toggleComments(),
71
- get commentsOpen() {
72
- return ui()?.commentsOpen ?? false;
73
- }
74
- },
75
- get documentId() {
76
- return entry()?.ctx.documentId;
77
- },
78
- get upload() {
79
- return entry()?.ctx.upload;
80
- },
81
- get collaboration() {
82
- return entry()?.ctx.collaboration;
83
- },
84
- get anchorResolver() {
85
- return entry()?.ctx.anchorResolver ?? null;
86
- },
87
- get abilities() {
88
- return entry()?.ctx.abilities ?? {};
89
- },
90
- slashCommands: () => entry()?.ctx.slashCommands() ?? [],
91
54
  getEditor: () => ref.current
92
- }
55
+ })
93
56
  };
94
57
  });
95
58
  state.ref.current = editor;
@@ -1 +1 @@
1
- {"version":3,"file":"EditorToolbar.js","names":[],"sources":["../../src/react/EditorToolbar.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useEditorState } from '@tiptap/react'\nimport { type ComponentType, type ReactNode, useState } from 'react'\nimport { abilitiesAtSelection } from '../core/abilities'\nimport { contributions } from '../core/define-editor'\nimport { editorEntryOf, notifierOf } from '../core/editor-ui'\nimport type { FeatureContext } from '../core/feature'\nimport { createMetadataRegistry } from '../core/metadata'\nimport type { Ability } from '../core/schema/abilities'\nimport { mergeToolbarGroups, type MenuItem, type ToolbarGroup, type ToolbarItem } from '../core/toolbar'\nimport { useEditorContext } from './context'\nimport type { ReactPreset } from './EditorSurface'\nimport { Icon } from './Icon'\nimport { Button } from './ui/button'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from './ui/dropdown-menu'\nimport { Separator } from './ui/separator'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip'\nimport { useEditorI18n } from './use-editor-i18n'\nimport { useStore } from './use-store'\nimport { cn } from './ui/utils'\n\ntype ItemComponent = ComponentType<{ editor: Editor }>\ntype Item = ToolbarItem<ItemComponent>\ntype Group = ToolbarGroup<ItemComponent>\n\nconst NO_ABILITIES: ReadonlySet<Ability> = new Set()\n\n// Factories are plain functions here (React features must not call hooks in them): built once per preset, app-wide,\n// keyed by the toolbar's `ctx` so each toolbar's items delegate to its own editor.\nconst built = new WeakMap<FeatureContext, WeakMap<ReactPreset, Group[]>>()\nfunction groupsOf(ctx: FeatureContext, preset: ReactPreset | undefined): Group[] {\n if (!preset) return []\n let byPreset = built.get(ctx)\n if (!byPreset) built.set(ctx, byPreset = new WeakMap())\n const cached = byPreset.get(preset)\n if (cached) return cached\n const groups = mergeToolbarGroups(contributions(preset, 'toolbar').flatMap(factory => factory(ctx)))\n byPreset.set(preset, groups)\n return groups\n}\n\nconst isMac = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform)\nconst KEY_LABELS: Record<string, string> = {\n meta: isMac ? '⌘' : 'Ctrl',\n ctrl: 'Ctrl',\n // i18n-ignore: keyboard key name, shown as on the keycap\n alt: isMac ? '⌥' : 'Alt',\n shift: '⇧',\n enter: '↵',\n}\n\n/**\n * The toolbar of an editor, placed anywhere on the page. `editor` is the instance it works on (from the surface's\n * `onReady`); the preset registered for that instance supplies the groups, `preset` renders them (disabled) while\n * there is no editor yet. A destroyed editor counts as none: a host still holds the old instance for the render in\n * which its keyed surface remounts. Never reads a global active editor.\n */\nexport function EditorToolbar({ editor: given, preset }: { editor?: Editor, preset?: ReactPreset }) {\n const editor = given?.isDestroyed ? undefined : given\n const app = useEditorContext()\n const { t } = useEditorI18n()\n\n // The context toolbar factories see: settings and fetch of the app, everything per-surface delegates to the editor's\n // registered context while there is one. Built once; `ref` keeps it pointing at the current editor.\n const [state] = useState(() => {\n const ref: { current: Editor | undefined } = { current: editor }\n const fallbackMetadata = createMetadataRegistry()\n const entry = () => editorEntryOf(ref.current)\n const ui = () => entry()?.ui\n const ctx: FeatureContext = {\n app,\n locale: app.locale,\n t: app.t,\n settings: app.settings,\n notify: notifierOf(() => ref.current, app),\n fetch: app.fetch,\n ai: app.ai,\n get metadata() { return entry()?.ctx.metadata ?? fallbackMetadata },\n ui: {\n openSettings: tab => ui()?.openSettings(tab),\n toggleSidebar: () => ui()?.toggleSidebar(),\n toggleComments: () => ui()?.toggleComments(),\n get commentsOpen() { return ui()?.commentsOpen ?? false },\n },\n get documentId() { return entry()?.ctx.documentId },\n get upload() { return entry()?.ctx.upload },\n get collaboration() { return entry()?.ctx.collaboration },\n get anchorResolver() { return entry()?.ctx.anchorResolver ?? null },\n get abilities() { return entry()?.ctx.abilities ?? {} },\n slashCommands: () => entry()?.ctx.slashCommands() ?? [],\n getEditor: () => ref.current,\n }\n return { ref, ctx }\n })\n state.ref.current = editor\n\n // `active`/`visible`/`menu` may read the settings: re-render when they change.\n useStore(app.settings)\n // `active`, `can()` and the abilities read the editor state: re-render on every transaction.\n const abilities = useEditorState({\n editor: editor ?? null,\n selector: ({ editor: current }) => current && !current.isDestroyed ? abilitiesAtSelection(current) : NO_ABILITIES,\n equalityFn: () => false,\n }) ?? NO_ABILITIES\n\n const groups = groupsOf(state.ctx, (editorEntryOf(editor)?.preset as ReactPreset | undefined) ?? preset)\n .filter(group => !editor || !group.visible || group.visible(editor))\n .map(group => ({ group, items: group.items.filter(item => !editor || !item.visible || item.visible(editor)) }))\n .filter(({ items }) => items.length > 0)\n\n const active = (item: Item) => !!editor && !!item.active?.(editor)\n const disabled = (item: Item) => {\n if (!editor) return true\n if (item.disabled?.(editor)) return true\n return !active(item) && !!item.ability && !abilities.has(item.ability)\n }\n\n return (\n <TooltipProvider>\n <div className=\"flex flex-wrap items-center gap-1.5\" role=\"toolbar\" aria-label={t('toolbar.label')}>\n {groups.map(({ group, items }, index) => (\n <div key={group.id} className=\"contents\">\n {index > 0 && <Separator orientation=\"vertical\" className=\"mx-2 h-6 data-[orientation=vertical]:h-6\" />}\n <div className=\"flex items-center gap-0.5\">\n {items.map(item => item.component\n ? <item.component key={item.id} editor={editor!} />\n : <ToolbarButton\n key={item.id}\n item={item}\n editor={editor}\n active={active(item)}\n disabled={disabled(item)}\n />)}\n </div>\n </div>\n ))}\n </div>\n </TooltipProvider>\n )\n}\n\nfunction ToolbarButton({ item, editor, active, disabled }: { item: Item, editor: Editor | undefined, active: boolean, disabled: boolean }) {\n const button = (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={item.label}\n aria-pressed={item.active ? active : undefined}\n data-state={item.active ? (active ? 'on' : 'off') : undefined}\n disabled={disabled}\n className={cn(active && 'bg-primary/10 text-primary hover:bg-primary/15 hover:text-primary')}\n onMouseDown={event => event.preventDefault()}\n onClick={item.run && editor ? () => item.run!(editor) : undefined}\n >\n <Icon name={item.icon} />\n </Button>\n )\n const tooltip = (trigger: ReactNode) => (\n <Tooltip>\n <TooltipTrigger asChild>{trigger}</TooltipTrigger>\n <TooltipContent>\n {item.label}\n {item.kbds && item.kbds.length > 0 && (\n <kbd data-slot=\"kbd\" className=\"rounded-sm bg-background/20 px-1 font-sans\">{item.kbds.map(key => KEY_LABELS[key] ?? key.toUpperCase()).join('+')}</kbd>\n )}\n </TooltipContent>\n </Tooltip>\n )\n if (!item.menu) return tooltip(button)\n return (\n <DropdownMenu>\n {tooltip(<DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>)}\n <DropdownMenuContent align=\"start\">\n {editor && item.menu(editor).map((entry, index) => <MenuRow key={index} entry={entry} />)}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction MenuRow({ entry }: { entry: MenuItem }) {\n if (entry.type === 'label') return <DropdownMenuLabel>{entry.label}</DropdownMenuLabel>\n if (entry.type === 'separator') return <DropdownMenuSeparator />\n return (\n <DropdownMenuItem disabled={entry.disabled} onSelect={() => entry.onSelect?.()}>\n {entry.icon && <Icon name={entry.icon} />}\n <span>{entry.label}</span>\n {entry.description && <span className=\"ml-auto text-xs text-muted-foreground\">{entry.description}</span>}\n </DropdownMenuItem>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAgCA,MAAM,+BAAqC,IAAI,IAAI;AAInD,MAAM,wBAAQ,IAAI,QAAuD;AACzE,SAAS,SAAS,KAAqB,QAA0C;CAC/E,IAAI,CAAC,QAAQ,OAAO,CAAC;CACrB,IAAI,WAAW,MAAM,IAAI,GAAG;CAC5B,IAAI,CAAC,UAAU,MAAM,IAAI,KAAK,2BAAW,IAAI,QAAQ,CAAC;CACtD,MAAM,SAAS,SAAS,IAAI,MAAM;CAClC,IAAI,QAAQ,OAAO;CACnB,MAAM,SAAS,mBAAmB,cAAc,QAAQ,SAAS,CAAC,CAAC,SAAQ,YAAW,QAAQ,GAAG,CAAC,CAAC;CACnG,SAAS,IAAI,QAAQ,MAAM;CAC3B,OAAO;AACT;AAEA,MAAM,QAAQ,OAAO,cAAc,eAAe,kBAAkB,KAAK,UAAU,QAAQ;AAC3F,MAAM,aAAqC;CACzC,MAAM,QAAQ,MAAM;CACpB,MAAM;CAEN,KAAK,QAAQ,MAAM;CACnB,OAAO;CACP,OAAO;AACT;;;;;;;AAQA,SAAgB,cAAc,EAAE,QAAQ,OAAO,UAAqD;CAClG,MAAM,SAAS,OAAO,cAAc,KAAA,IAAY;CAChD,MAAM,MAAM,iBAAiB;CAC7B,MAAM,EAAE,MAAM,cAAc;CAI5B,MAAM,CAAC,SAAS,eAAe;EAC7B,MAAM,MAAuC,EAAE,SAAS,OAAO;EAC/D,MAAM,mBAAmB,uBAAuB;EAChD,MAAM,cAAc,cAAc,IAAI,OAAO;EAC7C,MAAM,WAAW,MAAM,CAAC,EAAE;EAwB1B,OAAO;GAAE;GAAK,KAAA;IAtBZ;IACA,QAAQ,IAAI;IACZ,GAAG,IAAI;IACP,UAAU,IAAI;IACd,QAAQ,iBAAiB,IAAI,SAAS,GAAG;IACzC,OAAO,IAAI;IACX,IAAI,IAAI;IACR,IAAI,WAAW;KAAE,OAAO,MAAM,CAAC,EAAE,IAAI,YAAY;IAAiB;IAClE,IAAI;KACF,eAAc,QAAO,GAAG,CAAC,EAAE,aAAa,GAAG;KAC3C,qBAAqB,GAAG,CAAC,EAAE,cAAc;KACzC,sBAAsB,GAAG,CAAC,EAAE,eAAe;KAC3C,IAAI,eAAe;MAAE,OAAO,GAAG,CAAC,EAAE,gBAAgB;KAAM;IAC1D;IACA,IAAI,aAAa;KAAE,OAAO,MAAM,CAAC,EAAE,IAAI;IAAW;IAClD,IAAI,SAAS;KAAE,OAAO,MAAM,CAAC,EAAE,IAAI;IAAO;IAC1C,IAAI,gBAAgB;KAAE,OAAO,MAAM,CAAC,EAAE,IAAI;IAAc;IACxD,IAAI,iBAAiB;KAAE,OAAO,MAAM,CAAC,EAAE,IAAI,kBAAkB;IAAK;IAClE,IAAI,YAAY;KAAE,OAAO,MAAM,CAAC,EAAE,IAAI,aAAa,CAAC;IAAE;IACtD,qBAAqB,MAAM,CAAC,EAAE,IAAI,cAAc,KAAK,CAAC;IACtD,iBAAiB,IAAI;GAEP;EAAE;CACpB,CAAC;CACD,MAAM,IAAI,UAAU;CAGpB,SAAS,IAAI,QAAQ;CAErB,MAAM,YAAY,eAAe;EAC/B,QAAQ,UAAU;EAClB,WAAW,EAAE,QAAQ,cAAc,WAAW,CAAC,QAAQ,cAAc,qBAAqB,OAAO,IAAI;EACrG,kBAAkB;CACpB,CAAC,KAAK;CAEN,MAAM,SAAS,SAAS,MAAM,KAAM,cAAc,MAAM,CAAC,EAAE,UAAsC,MAAM,CAAC,CACrG,QAAO,UAAS,CAAC,UAAU,CAAC,MAAM,WAAW,MAAM,QAAQ,MAAM,CAAC,CAAC,CACnE,KAAI,WAAU;EAAE;EAAO,OAAO,MAAM,MAAM,QAAO,SAAQ,CAAC,UAAU,CAAC,KAAK,WAAW,KAAK,QAAQ,MAAM,CAAC;CAAE,EAAE,CAAC,CAC9G,QAAQ,EAAE,YAAY,MAAM,SAAS,CAAC;CAEzC,MAAM,UAAU,SAAe,CAAC,CAAC,UAAU,CAAC,CAAC,KAAK,SAAS,MAAM;CACjE,MAAM,YAAY,SAAe;EAC/B,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI,KAAK,WAAW,MAAM,GAAG,OAAO;EACpC,OAAO,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC,KAAK,WAAW,CAAC,UAAU,IAAI,KAAK,OAAO;CACvE;CAEA,OACE,oBAAC,iBAAD,EAAA,UACE,oBAAC,OAAD;EAAK,WAAU;EAAsC,MAAK;EAAU,cAAY,EAAE,eAAe;EAC9F,UAAA,OAAO,KAAK,EAAE,OAAO,SAAS,UAC7B,qBAAC,OAAD;GAAoB,WAAU;GAA9B,UAAA,CACG,QAAQ,KAAK,oBAAC,WAAD;IAAW,aAAY;IAAW,WAAU;GAA4C,CAAA,GACtG,oBAAC,OAAD;IAAK,WAAU;IACZ,UAAA,MAAM,KAAI,SAAQ,KAAK,YACpB,oBAAC,KAAK,WAAN,EAAsC,OAAU,GAA3B,KAAK,EAAsB,IAChD,oBAAC,eAAD;KAEQ;KACE;KACR,QAAQ,OAAO,IAAI;KACnB,UAAU,SAAS,IAAI;IACxB,GALM,KAAK,EAKX,CAAC;GACH,CAAA,CACF;EAbK,GAAA,MAAM,EAaX,CACN;CACE,CAAA,EACU,CAAA;AAErB;AAEA,SAAS,cAAc,EAAE,MAAM,QAAQ,QAAQ,YAA4F;CACzI,MAAM,SACJ,oBAAC,QAAD;EACE,SAAQ;EACR,MAAK;EACL,cAAY,KAAK;EACjB,gBAAc,KAAK,SAAS,SAAS,KAAA;EACrC,cAAY,KAAK,SAAU,SAAS,OAAO,QAAS,KAAA;EAC1C;EACV,WAAW,GAAG,UAAU,mEAAmE;EAC3F,cAAa,UAAS,MAAM,eAAe;EAC3C,SAAS,KAAK,OAAO,eAAe,KAAK,IAAK,MAAM,IAAI,KAAA;EAExD,UAAA,oBAAC,MAAD,EAAM,MAAM,KAAK,KAAO,CAAA;CAClB,CAAA;CAEV,MAAM,WAAW,YACf,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,gBAAD;EAAgB,SAAA;EAAS,UAAA;CAAwB,CAAA,GACjD,qBAAC,gBAAD,EAAA,UAAA,CACG,KAAK,OACL,KAAK,QAAQ,KAAK,KAAK,SAAS,KAC/B,oBAAC,OAAD;EAAK,aAAU;EAAM,WAAU;EAA8C,UAAA,KAAK,KAAK,KAAI,QAAO,WAAW,QAAQ,IAAI,YAAY,CAAC,CAAC,CAAC,KAAK,GAAG;CAAO,CAAA,CAE3I,EAAA,CAAA,CACT,EAAA,CAAA;CAEX,IAAI,CAAC,KAAK,MAAM,OAAO,QAAQ,MAAM;CACrC,OACE,qBAAC,cAAD,EAAA,UAAA,CACG,QAAQ,oBAAC,qBAAD;EAAqB,SAAA;EAAS,UAAA;CAA4B,CAAA,CAAC,GACpE,oBAAC,qBAAD;EAAqB,OAAM;EACxB,UAAA,UAAU,KAAK,KAAK,MAAM,CAAC,CAAC,KAAK,OAAO,UAAU,oBAAC,SAAD,EAA4B,MAAQ,GAAtB,KAAsB,CAAC;CACrE,CAAA,CACT,EAAA,CAAA;AAElB;AAEA,SAAS,QAAQ,EAAE,SAA8B;CAC/C,IAAI,MAAM,SAAS,SAAS,OAAO,oBAAC,mBAAD,EAAA,UAAoB,MAAM,MAAyB,CAAA;CACtF,IAAI,MAAM,SAAS,aAAa,OAAO,oBAAC,uBAAD,CAAwB,CAAA;CAC/D,OACE,qBAAC,kBAAD;EAAkB,UAAU,MAAM;EAAU,gBAAgB,MAAM,WAAW;EAA7E,UAAA;GACG,MAAM,QAAQ,oBAAC,MAAD,EAAM,MAAM,MAAM,KAAO,CAAA;GACxC,oBAAC,QAAD,EAAA,UAAO,MAAM,MAAY,CAAA;GACxB,MAAM,eAAe,oBAAC,QAAD;IAAM,WAAU;IAAyC,UAAA,MAAM;GAAkB,CAAA;EACvF;;AAEtB"}
1
+ {"version":3,"file":"EditorToolbar.js","names":[],"sources":["../../src/react/EditorToolbar.tsx"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport { useEditorState } from '@tiptap/react'\nimport { type ComponentType, type ReactNode, useState } from 'react'\nimport { abilitiesAtSelection } from '../core/abilities'\nimport { contributions } from '../core/define-editor'\nimport { borrowedFeatureContext, editorEntryOf } from '../core/editor-ui'\nimport type { FeatureContext } from '../core/feature'\nimport type { Ability } from '../core/schema/abilities'\nimport { mergeToolbarGroups, type MenuItem, type ToolbarGroup, type ToolbarItem } from '../core/toolbar'\nimport { useEditorContext } from './context'\nimport type { ReactPreset } from './EditorSurface'\nimport { Icon } from './Icon'\nimport { Button } from './ui/button'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from './ui/dropdown-menu'\nimport { Separator } from './ui/separator'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip'\nimport { useEditorI18n } from './use-editor-i18n'\nimport { useStore } from './use-store'\nimport { cn } from './ui/utils'\n\ntype ItemComponent = ComponentType<{ editor: Editor }>\ntype Item = ToolbarItem<ItemComponent>\ntype Group = ToolbarGroup<ItemComponent>\n\nconst NO_ABILITIES: ReadonlySet<Ability> = new Set()\n\n// Factories are plain functions here (React features must not call hooks in them): built once per preset, app-wide,\n// keyed by the toolbar's `ctx` so each toolbar's items delegate to its own editor.\nconst built = new WeakMap<FeatureContext, WeakMap<ReactPreset, Group[]>>()\nfunction groupsOf(ctx: FeatureContext, preset: ReactPreset | undefined): Group[] {\n if (!preset) return []\n let byPreset = built.get(ctx)\n if (!byPreset) built.set(ctx, byPreset = new WeakMap())\n const cached = byPreset.get(preset)\n if (cached) return cached\n const groups = mergeToolbarGroups(contributions(preset, 'toolbar').flatMap(factory => factory(ctx)))\n byPreset.set(preset, groups)\n return groups\n}\n\nconst isMac = typeof navigator !== 'undefined' && /Mac|iPhone|iPad/.test(navigator.platform)\nconst KEY_LABELS: Record<string, string> = {\n meta: isMac ? '⌘' : 'Ctrl',\n ctrl: 'Ctrl',\n // i18n-ignore: keyboard key name, shown as on the keycap\n alt: isMac ? '⌥' : 'Alt',\n shift: '⇧',\n enter: '↵',\n}\n\n/**\n * The toolbar of an editor, placed anywhere on the page. `editor` is the instance it works on (from the surface's\n * `onReady`); the preset registered for that instance supplies the groups, `preset` renders them (disabled) while\n * there is no editor yet. A destroyed editor counts as none: a host still holds the old instance for the render in\n * which its keyed surface remounts. Never reads a global active editor.\n */\nexport function EditorToolbar({ editor: given, preset }: { editor?: Editor, preset?: ReactPreset }) {\n const editor = given?.isDestroyed ? undefined : given\n const app = useEditorContext()\n const { t } = useEditorI18n()\n\n // The context toolbar factories see: settings and fetch of the app, everything per-surface delegates to the editor's\n // registered context while there is one. Built once; `ref` keeps it pointing at the current editor.\n const [state] = useState(() => {\n const ref: { current: Editor | undefined } = { current: editor }\n return { ref, ctx: borrowedFeatureContext({ app, getEditor: () => ref.current }) }\n })\n state.ref.current = editor\n\n // `active`/`visible`/`menu` may read the settings: re-render when they change.\n useStore(app.settings)\n // `active`, `can()` and the abilities read the editor state: re-render on every transaction.\n const abilities = useEditorState({\n editor: editor ?? null,\n selector: ({ editor: current }) => current && !current.isDestroyed ? abilitiesAtSelection(current) : NO_ABILITIES,\n equalityFn: () => false,\n }) ?? NO_ABILITIES\n\n const groups = groupsOf(state.ctx, (editorEntryOf(editor)?.preset as ReactPreset | undefined) ?? preset)\n .filter(group => !editor || !group.visible || group.visible(editor))\n .map(group => ({ group, items: group.items.filter(item => !editor || !item.visible || item.visible(editor)) }))\n .filter(({ items }) => items.length > 0)\n\n const active = (item: Item) => !!editor && !!item.active?.(editor)\n const disabled = (item: Item) => {\n if (!editor) return true\n if (item.disabled?.(editor)) return true\n return !active(item) && !!item.ability && !abilities.has(item.ability)\n }\n\n return (\n <TooltipProvider>\n <div className=\"flex flex-wrap items-center gap-1.5\" role=\"toolbar\" aria-label={t('toolbar.label')}>\n {groups.map(({ group, items }, index) => (\n <div key={group.id} className=\"contents\">\n {index > 0 && <Separator orientation=\"vertical\" className=\"mx-2 h-6 data-[orientation=vertical]:h-6\" />}\n <div className=\"flex items-center gap-0.5\">\n {items.map(item => item.component\n ? <item.component key={item.id} editor={editor!} />\n : <ToolbarButton\n key={item.id}\n item={item}\n editor={editor}\n active={active(item)}\n disabled={disabled(item)}\n />)}\n </div>\n </div>\n ))}\n </div>\n </TooltipProvider>\n )\n}\n\nfunction ToolbarButton({ item, editor, active, disabled }: { item: Item, editor: Editor | undefined, active: boolean, disabled: boolean }) {\n const button = (\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={item.label}\n aria-pressed={item.active ? active : undefined}\n data-state={item.active ? (active ? 'on' : 'off') : undefined}\n disabled={disabled}\n className={cn(active && 'bg-primary/10 text-primary hover:bg-primary/15 hover:text-primary')}\n onMouseDown={event => event.preventDefault()}\n onClick={item.run && editor ? () => item.run!(editor) : undefined}\n >\n <Icon name={item.icon} />\n </Button>\n )\n const tooltip = (trigger: ReactNode) => (\n <Tooltip>\n <TooltipTrigger asChild>{trigger}</TooltipTrigger>\n <TooltipContent>\n {item.label}\n {item.kbds && item.kbds.length > 0 && (\n <kbd data-slot=\"kbd\" className=\"rounded-sm bg-background/20 px-1 font-sans\">{item.kbds.map(key => KEY_LABELS[key] ?? key.toUpperCase()).join('+')}</kbd>\n )}\n </TooltipContent>\n </Tooltip>\n )\n if (!item.menu) return tooltip(button)\n return (\n <DropdownMenu>\n {tooltip(<DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>)}\n <DropdownMenuContent align=\"start\">\n {editor && item.menu(editor).map((entry, index) => <MenuRow key={index} entry={entry} />)}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction MenuRow({ entry }: { entry: MenuItem }) {\n if (entry.type === 'label') return <DropdownMenuLabel>{entry.label}</DropdownMenuLabel>\n if (entry.type === 'separator') return <DropdownMenuSeparator />\n return (\n <DropdownMenuItem disabled={entry.disabled} onSelect={() => entry.onSelect?.()}>\n {entry.icon && <Icon name={entry.icon} />}\n <span>{entry.label}</span>\n {entry.description && <span className=\"ml-auto text-xs text-muted-foreground\">{entry.description}</span>}\n </DropdownMenuItem>\n )\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /* The React surface's token map: the `--sk-*` variables the feature stylesheets (`styles/*.css`) read resolve to the
2
- shadcn/ui theme tokens (the counterpart of `vue/kit/nuxt-ui.css` and `vue/kit/primevue.css`). */
2
+ shadcn/ui theme tokens (the counterpart of `vue/kit/nuxt-ui.css`). */
3
3
  :root,
4
4
  .dark {
5
5
  --sk-bg: var(--background);
@@ -12,7 +12,7 @@
12
12
  --sk-border-accented: var(--border);
13
13
  --sk-primary: var(--primary);
14
14
  --sk-primary-fg: var(--primary-foreground);
15
- --sk-success: oklch(0.72 0.19 150);
15
+ --sk-success: var(--color-success, oklch(0.72 0.19 150));
16
16
  --sk-danger: var(--destructive);
17
17
  --sk-radius: var(--radius);
18
18
  --sk-bg-elevated: var(--card);
@@ -2,11 +2,11 @@ import { BlockId } from "../core/schema/block-ids.js";
2
2
  import { NodeFlags } from "../core/schema/node-flags.js";
3
3
  import { OpaqueFence } from "../core/schema/opaque-fence.js";
4
4
  import { StrikeX } from "../core/schema/strike.js";
5
- import { mentionsSchemaExtensions } from "../features/mentions/schema.js";
6
5
  import { compositeSchemaExtensions } from "../features/composites/schema.js";
7
- import { mediaSchemaExtensions } from "../features/media/schema.js";
8
6
  import { authorsSchemaExtensions } from "../features/authors/schema.js";
9
7
  import { commentsSchemaExtensions } from "../features/comments/schema.js";
8
+ import { mediaSchemaExtensions } from "../features/media/schema.js";
9
+ import { mentionsSchemaExtensions } from "../features/mentions/schema.js";
10
10
  import { tablesSchemaExtensions } from "../features/tables/schema.js";
11
11
  import { transcriptSchemaExtensions } from "../features/transcript/schema.js";
12
12
  import StarterKit from "@tiptap/starter-kit";
@@ -1,10 +1,10 @@
1
1
  import { EditorSettings } from "../settings/index.js";
2
- import { ModelProfile, ModelRef } from "../features/ai/protocol/models.js";
3
- import "../features/ai/protocol/index.js";
4
2
  import { AssetRecord } from "../features/media/assets.js";
5
3
  import "../features/media/schema.js";
6
4
  import { MentionUser } from "../features/mentions/mention.js";
7
5
  import "../features/mentions/schema.js";
6
+ import { ModelProfile, ModelRef } from "../features/ai/protocol/models.js";
7
+ import "../features/ai/protocol/index.js";
8
8
  import { ThemeRecord } from "../features/themes/schema.js";
9
9
  import { AnyExtension, JSONContent } from "@tiptap/core";
10
10
  import { LanguageModel } from "ai";
@@ -4,9 +4,9 @@ import { migrateLegacyContent } from "../features/composites/legacy.js";
4
4
  import { advancedSchemaExtensions } from "../schema/advanced.js";
5
5
  import { decodeCollabState, encodeCollabState, isStateStale } from "./collab-state.js";
6
6
  import { getSchema } from "@tiptap/core";
7
+ import { Hocuspocus } from "@hocuspocus/server";
7
8
  import { prosemirrorToYDoc, prosemirrorToYXmlFragment, yDocToProsemirrorJSON, yXmlFragmentToProsemirror } from "@tiptap/y-tiptap";
8
9
  import * as Y from "yjs";
9
- import { Hocuspocus } from "@hocuspocus/server";
10
10
  //#region src/server/collab.ts
11
11
  /** Y.XmlFragment the TipTap Collaboration extension binds to (its default `field`). */
12
12
  const COLLAB_FIELD = "default";
@@ -22,6 +22,7 @@ export declare const chatBodySchema: z.ZodObject<{
22
22
  pending: "pending";
23
23
  unchanged: "unchanged";
24
24
  missing: "missing";
25
+ stale: "stale";
25
26
  overlap: "overlap";
26
27
  empty: "empty";
27
28
  blocked: "blocked";
@@ -97,7 +98,8 @@ export declare const chatBodySchema: z.ZodObject<{
97
98
  }, z.core.$strip>;
98
99
  /**
99
100
  * Document chat: answers the last message of `messages` about `document`, or proposes edit operations through the
100
- * `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, or converting into a type not in
101
+ * `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, an `add` without `afterId`, an
102
+ * operation inside a block that another operation replaces or deletes, or converting into a type not in
101
103
  * `targets`, make the model retry (at most `AGENT_LIMITS.retries` times); what is still invalid then, or left out by a
102
104
  * corrected list, is dropped. The system prompt describes the editor's block model from `kinds`, `blocks` and `targets`. SSE like
103
105
  * `/api/ai/generate`: `delta` (text), `ops` (`{ ops: AgentOp[], dropped: AgentOpProblem[], retries: number }`, once,
@@ -120,8 +122,8 @@ export declare function completePost(ctx: AiHandlerContext, request: HandlerRequ
120
122
  /** Body schema of `generatePost`. */
121
123
  export declare const generateBodySchema: z.ZodObject<{
122
124
  task: z.ZodEnum<{
123
- generate: "generate";
124
125
  custom: "custom";
126
+ generate: "generate";
125
127
  continue: "continue";
126
128
  reformulate: "reformulate";
127
129
  "transcript-summary": "transcript-summary";
@@ -1,14 +1,15 @@
1
1
  import { AI_LANGUAGES, STRUCTURE_MODES } from "../../settings/values.js";
2
- import { AGENT_LIMITS, AGENT_OP_OUTCOMES, AGENT_OP_TYPES, AGENT_TOOL, agentOpTarget, agentRetryMessage, agentUnreadableProblem, checkAgentOps, documentBlockIds } from "../../features/ai/protocol/agent-ops.js";
2
+ import { HttpError } from "../errors.js";
3
+ import { ASSET_DOCUMENT_ID, ASSET_NAME } from "../../features/media/assets.js";
4
+ import { authenticated, enforceRateLimit, eventStream, json, readBody } from "./context.js";
5
+ import { readBlockLines } from "../../features/ai/protocol/block-lines.js";
6
+ import { AGENT_LIMITS, AGENT_OP_OUTCOMES, AGENT_OP_TYPES, AGENT_TOOL, agentOpTarget, agentRetryMessage, agentUnreadableProblem, checkAgentOps } from "../../features/ai/protocol/agent-ops.js";
3
7
  import { CHAT_LIMITS, documentChatMessages } from "../../features/ai/protocol/chat.js";
4
8
  import { AI_COMPLETE_LIMITS, cleanCompletion, completeMessages, completionReady } from "../../features/ai/protocol/complete.js";
5
9
  import { modelRefSchema } from "../../features/ai/protocol/models.js";
6
10
  import { AI_LIMITS, AI_TASKS, AI_TASK_IDS } from "../../features/ai/protocol/tasks.js";
7
11
  import { AI_ID_PATTERN, aiBlockInputSchema, aiBlockSchema } from "../../features/ai/protocol/schemas.js";
8
- import { ASSET_DOCUMENT_ID, ASSET_NAME } from "../../features/media/assets.js";
9
12
  import { TRANSCRIPT_CLEANUP_LIMITS, parseTranscriptCleanup, transcriptCleanupChunks, transcriptCleanupMessages, transcriptCleanupRequestSchema } from "../../integrations/transcript-ai/protocol.js";
10
- import { HttpError } from "../errors.js";
11
- import { authenticated, enforceRateLimit, eventStream, json, readBody } from "./context.js";
12
13
  import { z } from "zod";
13
14
  import { jsonSchema, tool } from "ai";
14
15
  //#region src/server/handlers/ai.ts
@@ -117,7 +118,7 @@ const agentTools = { [AGENT_TOOL]: tool({
117
118
  },
118
119
  afterId: {
119
120
  type: ["string", "null"],
120
- description: "add: ID des Blocks, nach dem eingefügt wird; null = Dokumentanfang."
121
+ description: "add (Pflicht): ID des Blocks, nach dem eingefügt wird; für den Dokumentanfang ausdrücklich null."
121
122
  },
122
123
  markdown: {
123
124
  type: "string",
@@ -136,7 +137,8 @@ const agentTools = { [AGENT_TOOL]: tool({
136
137
  }) };
137
138
  /**
138
139
  * Document chat: answers the last message of `messages` about `document`, or proposes edit operations through the
139
- * `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, or converting into a type not in
140
+ * `edit_document` tool. Operations pointing to IDs that are unknown or outside `scope`, an `add` without `afterId`, an
141
+ * operation inside a block that another operation replaces or deletes, or converting into a type not in
140
142
  * `targets`, make the model retry (at most `AGENT_LIMITS.retries` times); what is still invalid then, or left out by a
141
143
  * corrected list, is dropped. The system prompt describes the editor's block model from `kinds`, `blocks` and `targets`. SSE like
142
144
  * `/api/ai/generate`: `delta` (text), `ops` (`{ ops: AgentOp[], dropped: AgentOpProblem[], retries: number }`, once,
@@ -147,11 +149,11 @@ async function chatPost(ctx, request) {
147
149
  const { messages, document, scope, nextOp, model, kinds, blocks, targets, structure } = await readBody(request, chatBodySchema);
148
150
  const languageModel = await ctx.ai.resolveModel(model, user);
149
151
  if (!messages.at(-1).content.trim()) throw new HttpError(400, "Nachricht fehlt");
150
- const known = documentBlockIds(document);
151
- const allowed = scope ? new Set(scope.filter((id) => known.has(id))) : null;
152
+ const lines = readBlockLines(document);
153
+ const allowed = scope ? new Set(scope.filter((id) => lines.ids.has(id))) : null;
152
154
  if (allowed && !allowed.size) throw new HttpError(400, "Die Auswahl enthält keine Blöcke des Dokuments");
153
155
  const context = {
154
- known: (id) => known.has(id),
156
+ lines,
155
157
  allowed,
156
158
  targets: new Set(targets)
157
159
  };