@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # Changelog
2
+
3
+ ## 2.1.0
4
+
5
+ ### Added
6
+
7
+ - The storage and connection layer of the former `@ecsplico/editor-adapters`, as 11 subpath entries (guide: `docs/integration.md` §14): `@ecsplico/editor/adapters` (types only), `/adapters/json`, `/adapters/drizzle`, `/adapters/forge`, `/adapters/vue` (`useEditorDocument`), `/anchors`, `/comments/server`, `/comments/json`, `/comments/drizzle`, `/comments/client` and `/server/nitro` (`installEditorServer`, the Nuxt-free Nitro host). Exported names are unchanged.
8
+ - Optional peer dependency `drizzle-orm` (`^0.45.0`), needed only by `/adapters/drizzle` and `/comments/drizzle`.
9
+ - Bundle-isolation fixtures `adapters-node`, `adapters-drizzle`, `adapters-nitro`, `adapters-vue` and `headless-comments-client`: Vue is reachable only from `/adapters/vue`, Nitro only from `/server/nitro`, drizzle only from the two drizzle entries, React from none.
10
+ - Example `examples/vue-nitro-json` (`bun run proto:vue-nitro-json`): a Vue editor on a bare Nitro server with JSON-file storage.
11
+
12
+ ### Notes
13
+
14
+ - `@ecsplico/editor-adapters` is deprecated and will receive no further releases. `@ecsplico/editor-adapters/vue` is now `@ecsplico/editor/adapters/vue`; every other subpath keeps its name under `@ecsplico/editor` (for example `@ecsplico/editor-adapters/adapters/json` → `@ecsplico/editor/adapters/json`, `@ecsplico/editor-adapters/server/nitro` → `@ecsplico/editor/server/nitro`, `@ecsplico/editor-adapters/anchors` → `@ecsplico/editor/anchors`). The root entry of the old package (types) is `@ecsplico/editor/adapters`.
15
+ - `./adapters/react`, `./server/nest` and `./adapters/prisma` are reserved names and not exported.
16
+
17
+ ### Fixed
18
+
19
+ - `createEditor()` without arguments threw a `TypeError` (destructuring `undefined`); `app.use(createEditor())` now installs with the default `nuxtUiKit()` as documented.
20
+
21
+ ## 2.0.0
22
+
23
+ ### Removed (breaking)
24
+
25
+ - The PrimeVue kit: `@ecsplico/editor/vue/kit/primevue` (`primeVueKit()`) and the stylesheet `@ecsplico/editor/vue/kit/primevue.css`. The Vue binding renders through Nuxt UI only; hosts that used `primeVueKit()` move to `nuxtUiKit()` (`@ecsplico/editor/vue/kit/nuxt-ui`) and import `@ecsplico/editor/vue/kit/nuxt-ui.css`.
26
+ - The Nuxt module option `kit` (`tiptapEditor: { kit: 'nuxt-ui' | 'primevue' }`). The module always adds `vue/kit/nuxt-ui.css`, patches Nuxt UI's `ui.css` so Tailwind scans the package and registers the Nuxt UI context plugin; `@nuxt/ui` must be registered before it.
27
+ - The peer dependency `primevue` (and its optional peer metadata).
28
+ - The `advanced-primevue` bundle-isolation fixture and the `examples/vue-primevue` example.
29
+
30
+ ### Changed
31
+
32
+ - `createEditor({ kit })`: `kit` is optional and defaults to `nuxtUiKit()`. `app.use(createEditor())` is enough; pass a `VueUiKit` only to render the chrome with another library.
33
+ - React: `react/styles/ui-vars.css` sets `--sk-success` from `--color-success` (the `@skaile/brand` token), falling back to the former `oklch(0.72 0.19 150)`.
34
+
35
+ ## 1.0.0
36
+
37
+ First release on npmjs as `@ecsplico/editor` (Vue: Nuxt UI or PrimeVue kit; React: shadcn/ui; optional React and Vue peers).
package/README.md CHANGED
@@ -123,10 +123,10 @@ Every entry is an explicit subpath of `package.json#exports`; there is no wildca
123
123
  | `@ecsplico/editor/transcript`, `/transcript/vue`, `/transcript/react`, `/transcript/schema` | `/vue` and `/react`: `transcriptFeature`, `TranscriptSegmentView`; framework-free: `transcriptFeatureCore`, `transcriptSegment`, `speakerConversions`, `segmentSpeakerLines`, `transcriptSchemaExtensions()`, `speakerParagraphsToSegments`, `lastSpeaker`, `speakersIn` |
124
124
  | `@ecsplico/editor/authors`, `/authors/vue`, `/authors/react`, `/authors/schema` | `/vue` and `/react`: `authorsFeature`, `AuthorColorsPanel`, `AuthorColorsButton`, `SettingsAuthorsTab`; framework-free: `authorsFeatureCore`, `currentAuthor`, `AuthorAttribution`, `authorsSchemaExtensions()`, `AuthorMark`, `documentAuthors`, `AuthorIdentity`, `AUTHOR_COLOR_STYLES`, `AuthorColorStyle` |
125
125
  | `@ecsplico/editor/themes`, `/themes/vue`, `/themes/react`, `/themes/schema` | `/vue` and `/react`: `themesFeature()` (site-wide Obsidian themes: its `setup` hook starts the theme runtime and returns its teardown, its `appearance` settings tab picks the theme), `SettingsAppearanceTab`; framework-free: `themesFeatureCore`, `startObsidianTheme(ctx)` (reference-counted per app, returns the release), `scopeThemeCss`, `THEME_SCOPE_CLASS`; Vue-free schema: `ThemeRecord`, `ThemeManifest`, `themeManifestSchema`, `THEME_ID`, `MAX_THEME_BYTES`. Needs an adapter with `listThemes`/`loadTheme`; see the [usage guide](./docs/usage.md#themes-obsidian) |
126
- | `@ecsplico/editor/comments`, `/comments/vue`, `/comments/react`, `/comments/schema` | `/vue` and `/react`: `commentsFeature` (requires `collab`), `useComments`, `CommentsMargin`, `CommentsPopover`, `CommentsListMenu`, `CommentThreadView`, `CommentComposer`, `CommentBubble`, `SettingsCommentsTab` (`/vue` also the type `CommentsStore`); framework-free: `commentsFeatureCore`, `createCommentsModel`, `Comments` plugin helpers, `commentsSchemaExtensions()`, `CommentMark`, `CommentThread`, `CommentReply`, `COMMENT_LIMITS`, `COMMENTS_LAYOUTS`, `CommentsLayout` |
126
+ | `@ecsplico/editor/comments`, `/comments/vue`, `/comments/react`, `/comments/schema` | `/vue` and `/react`: `commentsFeature` (requires `collab`), `useComments`, `CommentsMargin`, `CommentsPopover`, `CommentsListMenu`, `CommentThreadView`, `CommentComposer`, `CommentBubble`, `SettingsCommentsTab` (`/vue` also the type `CommentsStore`); framework-free: `commentsFeatureCore`, `createCommentsModel`, `Comments` plugin helpers, `commentsSchemaExtensions()`, `CommentMark`, `CommentThread`, `CommentReply`, `COMMENT_LIMITS`, `COMMENTS_LAYOUTS`, `CommentsLayout`, and the margin's layout (the active thread pinned to its text's line, the others one-line cards out of its way): `layoutMargin(entries, activeId, { gap, minTop })` (with `MarginEntry`, `MarginLayout`, `MARGIN_GAP`), `marginCards` (with `MarginCard`, `DRAFT_CARD`; an orphaned thread sits at its deletion point, `CommentsSnapshot.orphanAt`, which host models may omit), `createMarginController` (with `MarginController`, `MarginSource`, `MarginState`: measuring and scheduling for both `CommentsMargin`s) |
127
127
  | `@ecsplico/editor/composites/vue`, `/composites/react` | `compositeFeature()` (Vue: Nuxt UI, React: shadcn), the host-placed `CompositeTable` and `CompositeInspector`, `SettingsEditingTab`, `CompositeChrome`, `CompositeDetails`, `CompositeView` and editors, `useCompositeRows`, `useCompositeAnchors` |
128
- | `@ecsplico/editor/composites`, `/composites/schema` | Framework-free: `compositeFeatureCore`, `Composite`, `defaultCompositeKinds`, `compositeKind`, `compositeOutline`, `moveBlockInto`, `canMoveBlockInto`, `headingsMarkdown`, `documentCompositeMarkdown`, `Structure`, `createCompositeRows`, `observeCompositeAnchors`, `compositeRows`, `setCompositeMetaField`, `setCompositeTags`, `setCompositeDescription`, `compositeDescriptionText`, `compositeSchemaExtensions()`, `compositeJSON`, `blocksFromMarkdown`, `splitCompositeFences`, `migrateLegacyContent`, conversions and heading↔composite transforms |
129
- | `@ecsplico/editor/ai`, `/ai/protocol` | Framework-free: `aiFeatureCore`, `modelProfiles(app)`, `createAiAssistant`, `createDocumentChat`, `createAgentOpReview({ notify, t })` (host-driven agent edit review), `notifyAiRateLimited(notify, t)`, review/apply helpers (`startAiReview`, `applyAiResult`, …) and the protocol (`AI_TASKS`, `ModelRef`, `AGENT_OP_TYPES`, `CHAT_LIMITS`, …), importable alone from `/ai/protocol`; the model-facing agent texts (`checkAgentOps`, `agentShownMessage`, `agentFeedbackText`, `agentRetryMessage`) take a `locale: 'de' \| 'en'` option (default `de`), and `agentRetryMessage(problems, scoped, { locale, tool })` names the `tool` the model must call again (default `edit_document`) |
128
+ | `@ecsplico/editor/composites`, `/composites/schema` | Framework-free: `compositeFeatureCore`, `Composite`, `defaultCompositeKinds`, `compositeKind`, `kindAccepts` (the kinds' accepted children, `CompositeKind.accepts`), `AcceptedChildren` (the editor extension that enforces them; registered by `compositeFeatureCore`), `compositeOutline`, `moveBlockInto` (returns whether the document changed), `moveIntoVerdict(editor, id, targetId, t)` (with `MoveVerdict`: `{ ok: true }` or `{ ok: false, reason }` in the language of `t`), `canMoveBlockInto` (`moveIntoVerdict(...).ok`), `headingsMarkdown`, `documentCompositeMarkdown`, `Structure`, `createCompositeRows`, `createCompositeInspector`, `compositeFieldText`, `observeCompositeAnchors`, `compositeRows`, `setCompositeMetaField`, `setCompositeTags`, `setCompositeDescription`, `compositeDescriptionText`, `compositeSchemaExtensions()`, `compositeJSON`, `blocksFromMarkdown`, `splitCompositeFences`, `migrateLegacyContent`, conversions and heading↔composite transforms |
129
+ | `@ecsplico/editor/ai`, `/ai/protocol` | Framework-free: `aiFeatureCore`, `modelProfiles(app)`, `createAiAssistant`, `createDocumentChat`, `createAiSelectionFlow` (with `AI_SELECTION_MENU_KEY`), `createAgentOpReview({ notify, t })` (host-driven agent edit review), `notifyAiRateLimited(notify, t)`, review/apply helpers (`startAiReview`, `applyAiResult`, …) and the protocol (`AI_TASKS`, `ModelRef`, `AGENT_OP_TYPES`, `AGENT_OP_OUTCOMES`, `CHAT_LIMITS`, …), importable alone from `/ai/protocol`; the model-facing agent texts (`checkAgentOps`, `agentShownMessage`, `agentFeedbackText`, `agentRetryMessage`) take a `locale: 'de' \| 'en'` option (default `de`), and `agentRetryMessage(problems, scoped, { locale, tool })` names the `tool` the model must call again (default `edit_document`). The `[id]` line format of the chat's document text has one owner there, `block-lines`: writer helpers (`blockLine`, `singleBlockLine`, `nestedLinePrefix`, `quoteLinePrefix`, `listLinePrefix`, `continuedLinePrefix`, `compositeChromePart`, `compositeLineHeader`, `compositeLineClose`, `TRUNCATED_LINE`), the reader `readBlockLines` (the block ids and which blocks lie inside which; lines in fenced code are not block lines; type `BlockLines`), the echo strippers `stripBlockLineEcho` and `stripListMarkerEcho`, and the (German, model-facing) prompt texts `BLOCK_LINES_LABEL`, `BLOCK_LINE_INTRO`, `BLOCK_LINE_MARKERS`, `BLOCK_LINE_LIST_TEXT`, `BLOCK_LINE_HOST_TEXT`, `BLOCK_LINE_COMPOSITE_TEXT`, `BLOCK_LINE_ECHO_TEXT`, `BLOCK_LINE_IDS_HINT` with the example (`BLOCK_LINE_EXAMPLE`, `BLOCK_LINE_EXAMPLE_ID`, `BLOCK_LINE_EXAMPLE_TEXT`) |
130
130
  | `@ecsplico/editor/ai/vue`, `/ai/react` | `aiFeature()`, `AiSidebar`, `ModelQuickSwitch`, `AiReviewBar`, `AiSelectionMenu`, `SettingsAiTab`, `SettingsModelProfiles`; `/ai/vue` also `useAiAssistant`, `useDocumentChat`, `useModelProfiles` (thin hooks over the models) |
131
131
  | `@ecsplico/editor/integrations/composite-ai` | Framework-free: `compositeAiFeature` (requires `composites`, `ai`, `media`), `fillComposite`, `summarizeComposite`, `transcribeDocument`, `compositeSourceText`, `mediaAssetOf` |
132
132
  | `@ecsplico/editor/integrations/composite-media` | Framework-free: `compositeMediaFeature` (requires `composites`, `media`): imported files become `document` composites |
@@ -137,7 +137,7 @@ Every entry is an explicit subpath of `package.json#exports`; there is no wildca
137
137
  | `@ecsplico/editor/server`, `/server/handlers`, `/server/node`, `/nuxt` | Server side without a web framework: `createCollabServer`, `createAiModels`, `createRateLimiter`, `EditorAdapter`, one request handler per route (`/server/handlers`), `listenCollab` for a plain Node script (`/server/node`); the Nitro registry `setEditorAdapter`, `forgetCollabDocument`, `liveContent`, `collabStats`; the Nuxt module (config key `tiptapEditor`) wraps the same core. See the [integration guide](./docs/integration.md) |
138
138
  | `@ecsplico/editor/styles/core.css` (required), `/composites.css`, `/media.css`, `/mentions.css`, `/tables.css`, `/authors.css`, `/comments.css`, `/transcript.css`, `/ai.css`, `/collab.css`, `/block-ui.css`, `/themes.css` | Feature CSS (import after Tailwind, Nuxt UI (Vue) or `ui-vars.css` (React) and `core.css`; `themes.css` after the feature sheets). The Nuxt module adds all of them itself |
139
139
 
140
- Nuxt apps use the same entries; the Nuxt client plugin (`src/nuxt/runtime/plugins/editor-context-<kit>.client.ts`, one file per `kit` option so an app never bundles the UI library it does not use) installs the editor context with account-synced settings.
140
+ Nuxt apps use the same entries; the Nuxt client plugin (`src/nuxt/runtime/plugins/editor-context-nuxt-ui.client.ts`) installs the editor context with account-synced settings.
141
141
 
142
142
  ## Presets and features
143
143
 
@@ -167,9 +167,9 @@ Settings are stored as one inert, versioned JSON object shared by all editors of
167
167
 
168
168
  ## CSS and UI-provider requirements
169
169
 
170
- - Design tokens: `@ecsplico/editor/styles/tokens.css` declares the shared `--sk-*` variables (`--sk-bg`, `--sk-fg`, `--sk-muted`, `--sk-border`, `--sk-primary`, `--sk-primary-fg`, `--sk-success`, `--sk-warning`, `--sk-danger`, `--sk-radius`, `--sk-font`) with light defaults under `:where(:root)` and dark values under `:where(.dark)`. Every feature stylesheet reads them; a UI kit maps them onto its own variables (Nuxt UI: `vue/kit/nuxt-ui.css`, PrimeVue: `vue/kit/primevue.css`, React/shadcn: `react/styles/ui-vars.css`), a host may override them once.
170
+ - Design tokens: `@ecsplico/editor/styles/tokens.css` declares the shared `--sk-*` variables (`--sk-bg`, `--sk-fg`, `--sk-muted`, `--sk-border`, `--sk-primary`, `--sk-primary-fg`, `--sk-success`, `--sk-warning`, `--sk-danger`, `--sk-radius`, `--sk-font`) with light defaults under `:where(:root)` and dark values under `:where(.dark)`. Every feature stylesheet reads them; a UI kit maps them onto its own variables (Nuxt UI: `vue/kit/nuxt-ui.css`, React/shadcn: `react/styles/ui-vars.css`), a host may override them once.
171
171
 
172
- - Vue UI kit: the Vue chrome renders through the kit handed to `createEditor({ kit })`. `nuxtUiKit()` (`@ecsplico/editor/vue/kit/nuxt-ui`, needs `@nuxt/ui`, stylesheet `@ecsplico/editor/vue/kit/nuxt-ui.css`) is the demo default; `primeVueKit()` (`@ecsplico/editor/vue/kit/primevue`, needs `primevue` + `@iconify/vue`, stylesheet `@ecsplico/editor/vue/kit/primevue.css`) works with the host's `app.use(PrimeVue)`, `ToastService` and a mounted `<Toast />`. `@ecsplico/editor/vue/kit` holds the `VueUiKit` contract and `useUiKit()`. Only those two entries import their UI library. The Nuxt module option `kit: 'nuxt-ui' | 'primevue'` (default `'nuxt-ui'`) selects the kit and the stylesheet; the `ui.css` patch runs only for `'nuxt-ui'`. `examples/vue-primevue` mounts the editor with PrimeVue.
172
+ - Vue UI kit: the Vue binding is built on Nuxt UI. The chrome renders through the kit handed to `createEditor({ kit })`; `kit` is optional and defaults to `nuxtUiKit()` (`@ecsplico/editor/vue/kit/nuxt-ui`, needs `@nuxt/ui`, stylesheet `@ecsplico/editor/vue/kit/nuxt-ui.css`). `@ecsplico/editor/vue/kit` holds the `VueUiKit` contract and `useUiKit()`; pass your own `VueUiKit` only to render the chrome with another library. Only the `nuxt-ui` kit entry imports `@nuxt/ui`. The Nuxt module has no `kit` option: it always adds `vue/kit/nuxt-ui.css`, patches Nuxt UI's `ui.css` so Tailwind scans the package (`@nuxt/ui` must be registered before the module) and installs the Nuxt UI context plugin.
173
173
 
174
174
  - Import the core CSS **after** Tailwind and Nuxt UI, and let Tailwind see the library's classes:
175
175
 
@@ -208,12 +208,12 @@ bun run --cwd examples/react-simple dev # http://localhost:3041
208
208
  bun run --cwd examples/react-simple typecheck
209
209
  bun run --cwd examples/react-advanced dev # http://localhost:3043, needs `bun run dev` (Nuxt host, port 3031)
210
210
  bun run --cwd examples/react-advanced typecheck
211
- bun run --cwd examples/vue-primevue dev # http://localhost:3044, the Vue surface on primeVueKit() (no Nuxt UI)
212
211
  bun run --cwd examples/vue-markdown dev # http://localhost:3045, MarkdownSurface with frontmatter, tables and source mode
213
212
  bun run typecheck:editor # vue-tsc --noEmit -p packages/editor/tsconfig.json (no Nuxt globals)
214
213
  bun run check:editor-entries # production build per fixture, module-graph isolation rules
215
214
  bun run check:editor-markdown-clipboard # MarkdownClipboard paste/copy cases (HTML, Markdown, plain text, data-URL opt-in) in happy-dom
216
215
  bun run check:editor-composition # defineEditor rejects missing `requires`; accepted compositions; one formatAiResult contributor
216
+ bun run test:editor # bun test over packages/editor/test: framework-free models on a mounted editor (happy-dom)
217
217
  ```
218
218
 
219
219
  On a clean checkout run the example build once before `typecheck:editor`.
@@ -0,0 +1,80 @@
1
+ import { Node } from "@tiptap/pm/model";
2
+ //#region src/adapters/anchors.d.ts
3
+ /** A half-open range of ProseMirror positions; `from === to` is a cursor position. */
4
+ export interface DocRange {
5
+ from: number;
6
+ to: number;
7
+ }
8
+ /** Selected text. */
9
+ export interface RangeAnchor {
10
+ kind: 'range';
11
+ from: number;
12
+ to: number;
13
+ quote: string;
14
+ contextBefore?: string;
15
+ contextAfter?: string;
16
+ }
17
+ /** A zero-width cursor position. */
18
+ export interface PointAnchor {
19
+ kind: 'point';
20
+ position: number;
21
+ contextBefore?: string;
22
+ contextAfter?: string;
23
+ }
24
+ /** What remains of an anchor whose target was deleted or cannot be found: the last good anchor plus its quote and context. */
25
+ export interface OrphanedAnchor {
26
+ kind: 'orphan';
27
+ previous: RangeAnchor | PointAnchor;
28
+ quote?: string;
29
+ contextBefore?: string;
30
+ contextAfter?: string;
31
+ reason: 'target-changed' | 'target-deleted' | 'unresolvable';
32
+ }
33
+ /** The stored form of an anchor; the layout of skaile-forge's `concept-discussions.json`. */
34
+ export type PortableAnchor = RangeAnchor | PointAnchor | OrphanedAnchor;
35
+ /** Characters of quote a range anchor keeps. */
36
+ export declare const QUOTE_LENGTH = 200;
37
+ /** Characters of surrounding text kept on each side. */
38
+ export declare const CONTEXT_LENGTH = 80;
39
+ /** What `captureAnchor` and `capturePoint` read: a tiptap `Editor`, or anything with `state.doc`. */
40
+ export interface AnchorSource {
41
+ state: {
42
+ doc: Node;
43
+ };
44
+ }
45
+ /** What `mapAnchor` reads of a ProseMirror transaction. */
46
+ export interface AnchorTransaction {
47
+ /** The document after the transaction. */
48
+ doc: Node;
49
+ mapping: {
50
+ mapResult(pos: number, assoc?: number): {
51
+ pos: number;
52
+ deleted: boolean;
53
+ };
54
+ };
55
+ }
56
+ /** The anchor of the text in `range` (clamped to the document); its `quote` is the trimmed text, cut to `QUOTE_LENGTH`. */
57
+ export declare function captureAnchor(source: AnchorSource, range: DocRange): RangeAnchor;
58
+ /** The anchor of the cursor position `position` (clamped to the document). */
59
+ export declare function capturePoint(source: AnchorSource, position: number): PointAnchor;
60
+ /**
61
+ * The range `anchor` points at in `doc`, or `null` when it cannot be found (an orphan). Orphaned anchors resolve to
62
+ * `null` too: a deleted target is never silently re-attached to other text. A point resolves to `{ from, to }` with
63
+ * `from === to`.
64
+ *
65
+ * 1. The stored positions, if they lie in the document and the text there still reads as the quote (a point: the
66
+ * text around still reads as its context).
67
+ * 2. A search for the quote (a point: for its context); among repeats the one whose surroundings agree most with the
68
+ * stored context wins, ties go to the one nearest the stored position.
69
+ */
70
+ export declare function resolveAnchor(doc: Node, anchor: PortableAnchor): DocRange | null;
71
+ /**
72
+ * `anchor` carried through a transaction: positions follow the edit, quote and context are read again from the new
73
+ * document (so an edit inside the range changes the quote). The same object when nothing changed. A range collapsed by
74
+ * the edit, or a point inside deleted text, becomes an `orphan` that keeps the last good anchor; orphans stay as they are.
75
+ */
76
+ export declare function mapAnchor(anchor: PortableAnchor, transaction: AnchorTransaction): PortableAnchor;
77
+ /** Whether `value` has the shape of a stored anchor (range, point or orphan); for input from the network. */
78
+ export declare function isPortableAnchor(value: unknown): value is PortableAnchor;
79
+ //#endregion
80
+ //# sourceMappingURL=anchors.d.ts.map
@@ -0,0 +1,270 @@
1
+ //#region src/adapters/anchors.ts
2
+ /** Characters of quote a range anchor keeps. */
3
+ const QUOTE_LENGTH = 200;
4
+ /** Characters of surrounding text kept on each side. */
5
+ const CONTEXT_LENGTH = 80;
6
+ /** Characters of context compared to accept an exact position and to search a moved one. */
7
+ const MATCH_LENGTH = 24;
8
+ const projections = /* @__PURE__ */ new WeakMap();
9
+ const isSpace = (char) => /\s/.test(char);
10
+ function projectionOf(doc) {
11
+ const cached = projections.get(doc);
12
+ if (cached) return cached;
13
+ const chars = [];
14
+ const starts = [];
15
+ const ends = [];
16
+ const push = (char, start, end) => {
17
+ if (isSpace(char)) {
18
+ if (chars.length === 0 || chars[chars.length - 1] === " ") return;
19
+ char = " ";
20
+ }
21
+ chars.push(char);
22
+ starts.push(start);
23
+ ends.push(end);
24
+ };
25
+ doc.descendants((node, pos) => {
26
+ if (node.isText) {
27
+ const text = node.text ?? "";
28
+ for (let i = 0; i < text.length; i++) push(text[i], pos + i, pos + i + 1);
29
+ return;
30
+ }
31
+ if (node.isTextblock || node.isBlock && node.isLeaf) push(" ", pos, pos);
32
+ if (node.isLeaf) {
33
+ const leaf = node.type.spec.leafText?.(node) ?? "";
34
+ for (let i = 0; i < leaf.length; i++) push(leaf[i], pos, pos + node.nodeSize);
35
+ }
36
+ });
37
+ const projection = {
38
+ text: chars.join(""),
39
+ starts,
40
+ ends
41
+ };
42
+ projections.set(doc, projection);
43
+ return projection;
44
+ }
45
+ /** First index in the non-decreasing `values` whose value satisfies `ok` (monotone: false… then true…). */
46
+ function firstIndex(values, ok) {
47
+ let low = 0;
48
+ let high = values.length;
49
+ while (low < high) {
50
+ const middle = low + high >> 1;
51
+ if (ok(values[middle])) high = middle;
52
+ else low = middle + 1;
53
+ }
54
+ return low;
55
+ }
56
+ /** The characters of `projection` inside `[from, to]`: `[first, last)` indexes. */
57
+ function span(projection, from, to) {
58
+ const first = firstIndex(projection.starts, (start) => start >= from);
59
+ const last = firstIndex(projection.ends, (end) => end > to);
60
+ return [first, Math.max(first, last)];
61
+ }
62
+ /** `value` with whitespace collapsed, as the projection holds text. */
63
+ const normalize = (value) => value.replace(/\s+/g, " ");
64
+ const clampPosition = (doc, position) => Math.max(0, Math.min(doc.content.size, Math.floor(position)));
65
+ /** The anchor of the text in `range` (clamped to the document); its `quote` is the trimmed text, cut to `QUOTE_LENGTH`. */
66
+ function captureAnchor(source, range) {
67
+ const { doc } = source.state;
68
+ const from = clampPosition(doc, Math.min(range.from, range.to));
69
+ const to = clampPosition(doc, Math.max(range.from, range.to));
70
+ const projection = projectionOf(doc);
71
+ const [first, last] = span(projection, from, to);
72
+ return {
73
+ kind: "range",
74
+ from,
75
+ to,
76
+ quote: projection.text.slice(first, last).trim().slice(0, 200),
77
+ contextBefore: projection.text.slice(Math.max(0, first - 80), first),
78
+ contextAfter: projection.text.slice(last, last + 80)
79
+ };
80
+ }
81
+ /** The anchor of the cursor position `position` (clamped to the document). */
82
+ function capturePoint(source, position) {
83
+ const { doc } = source.state;
84
+ const at = clampPosition(doc, position);
85
+ const projection = projectionOf(doc);
86
+ const [first, last] = span(projection, at, at);
87
+ return {
88
+ kind: "point",
89
+ position: at,
90
+ contextBefore: projection.text.slice(Math.max(0, last - 80), last),
91
+ contextAfter: projection.text.slice(first, first + 80)
92
+ };
93
+ }
94
+ const commonSuffix = (text, end, context) => {
95
+ let count = 0;
96
+ while (count < context.length && count < end && text[end - 1 - count] === context[context.length - 1 - count]) count++;
97
+ return count;
98
+ };
99
+ const commonPrefix = (text, start, context) => {
100
+ let count = 0;
101
+ while (count < context.length && start + count < text.length && text[start + count] === context[count]) count++;
102
+ return count;
103
+ };
104
+ /** Every index where `needle` occurs in `text` (overlaps included). */
105
+ function occurrences(text, needle) {
106
+ const found = [];
107
+ if (!needle) return found;
108
+ for (let index = text.indexOf(needle); index >= 0; index = text.indexOf(needle, index + 1)) found.push(index);
109
+ return found;
110
+ }
111
+ const matchTail = (context) => context.slice(-24);
112
+ const matchHead = (context) => context.slice(0, MATCH_LENGTH);
113
+ /**
114
+ * The range `anchor` points at in `doc`, or `null` when it cannot be found (an orphan). Orphaned anchors resolve to
115
+ * `null` too: a deleted target is never silently re-attached to other text. A point resolves to `{ from, to }` with
116
+ * `from === to`.
117
+ *
118
+ * 1. The stored positions, if they lie in the document and the text there still reads as the quote (a point: the
119
+ * text around still reads as its context).
120
+ * 2. A search for the quote (a point: for its context); among repeats the one whose surroundings agree most with the
121
+ * stored context wins, ties go to the one nearest the stored position.
122
+ */
123
+ function resolveAnchor(doc, anchor) {
124
+ if (anchor.kind === "orphan") return null;
125
+ const projection = projectionOf(doc);
126
+ const size = doc.content.size;
127
+ const contextBefore = normalize(anchor.contextBefore ?? "");
128
+ const contextAfter = normalize(anchor.contextAfter ?? "");
129
+ if (anchor.kind === "point") {
130
+ const stored = Number.isInteger(anchor.position) && anchor.position >= 0 && anchor.position <= size ? anchor.position : null;
131
+ if (stored !== null) {
132
+ const [first, last] = span(projection, stored, stored);
133
+ const beforeOk = !contextBefore || projection.text.slice(0, last).endsWith(matchTail(contextBefore));
134
+ const afterOk = !contextAfter || projection.text.startsWith(matchHead(contextAfter), first);
135
+ if (beforeOk && afterOk) return {
136
+ from: stored,
137
+ to: stored
138
+ };
139
+ }
140
+ const [firstIndex, ...otherIndexes] = pointCandidates(projection, contextBefore, contextAfter);
141
+ if (firstIndex === void 0) return null;
142
+ const near = anchor.position;
143
+ const entryOf = (index) => ({
144
+ index,
145
+ position: boundaryPosition(projection, index),
146
+ score: commonSuffix(projection.text, index, contextBefore) + commonPrefix(projection.text, index, contextAfter)
147
+ });
148
+ const best = pickBest(entryOf(firstIndex), otherIndexes.map(entryOf), (entry) => Math.abs(entry.position - near));
149
+ return {
150
+ from: best.position,
151
+ to: best.position
152
+ };
153
+ }
154
+ const quote = normalize(anchor.quote).trim();
155
+ if (Number.isInteger(anchor.from) && Number.isInteger(anchor.to) && anchor.from >= 0 && anchor.from <= anchor.to && anchor.to <= size) {
156
+ if (!quote) return {
157
+ from: anchor.from,
158
+ to: anchor.to
159
+ };
160
+ const [first, last] = span(projection, anchor.from, anchor.to);
161
+ if (projection.text.slice(first, last).trim().slice(0, 200) === quote) return {
162
+ from: anchor.from,
163
+ to: anchor.to
164
+ };
165
+ }
166
+ if (!quote) return null;
167
+ const [firstMatch, ...otherMatches] = occurrences(projection.text, quote);
168
+ if (firstMatch === void 0) return null;
169
+ const truncated = quote.length >= 200;
170
+ const entryOf = (index) => {
171
+ const from = projection.starts[index];
172
+ return {
173
+ index,
174
+ from,
175
+ to: truncated ? Math.min(size, from + Math.max(0, anchor.to - anchor.from)) : projection.ends[index + quote.length - 1],
176
+ score: commonSuffix(projection.text, index, contextBefore) + commonPrefix(projection.text, index + quote.length, contextAfter)
177
+ };
178
+ };
179
+ const best = pickBest(entryOf(firstMatch), otherMatches.map(entryOf), (entry) => Math.abs(entry.from - anchor.from));
180
+ return {
181
+ from: best.from,
182
+ to: best.to
183
+ };
184
+ }
185
+ /** The boundaries (between characters) where the context of a point reads: both sides, else either side alone. */
186
+ function pointCandidates(projection, contextBefore, contextAfter) {
187
+ const before = matchTail(contextBefore);
188
+ const after = matchHead(contextAfter);
189
+ if (before && after) {
190
+ const both = occurrences(projection.text, before + after).map((index) => index + before.length);
191
+ if (both.length) return both;
192
+ }
193
+ if (before) {
194
+ const found = occurrences(projection.text, before).map((index) => index + before.length);
195
+ if (found.length) return found;
196
+ }
197
+ if (after) return occurrences(projection.text, after);
198
+ return [];
199
+ }
200
+ /** The cursor position of the boundary before character `index`. */
201
+ function boundaryPosition(projection, index) {
202
+ const { text, starts, ends } = projection;
203
+ if (index >= text.length) return ends[text.length - 1] ?? 0;
204
+ if (text[index] === " " && starts[index] === ends[index] && index > 0) return ends[index - 1];
205
+ return starts[index];
206
+ }
207
+ /** The entry with the highest `score` among `first` and `rest`; among equals the one with the lowest `distance`. */
208
+ function pickBest(first, rest, distance) {
209
+ let best = first;
210
+ for (const entry of rest) if (entry.score > best.score || entry.score === best.score && distance(entry) < distance(best)) best = entry;
211
+ return best;
212
+ }
213
+ const sameAnchor = (a, b) => JSON.stringify(a) === JSON.stringify(b);
214
+ /**
215
+ * `anchor` carried through a transaction: positions follow the edit, quote and context are read again from the new
216
+ * document (so an edit inside the range changes the quote). The same object when nothing changed. A range collapsed by
217
+ * the edit, or a point inside deleted text, becomes an `orphan` that keeps the last good anchor; orphans stay as they are.
218
+ */
219
+ function mapAnchor(anchor, transaction) {
220
+ if (anchor.kind === "orphan") return anchor;
221
+ const source = { state: { doc: transaction.doc } };
222
+ let next;
223
+ if (anchor.kind === "range") {
224
+ const from = transaction.mapping.mapResult(anchor.from, 1).pos;
225
+ const to = transaction.mapping.mapResult(anchor.to, -1).pos;
226
+ if (from >= to) next = {
227
+ kind: "orphan",
228
+ previous: anchor,
229
+ quote: anchor.quote,
230
+ contextBefore: anchor.contextBefore,
231
+ contextAfter: anchor.contextAfter,
232
+ reason: from === to ? "target-deleted" : "target-changed"
233
+ };
234
+ else next = captureAnchor(source, {
235
+ from,
236
+ to
237
+ });
238
+ } else {
239
+ const after = transaction.mapping.mapResult(anchor.position, 1);
240
+ const before = transaction.mapping.mapResult(anchor.position, -1);
241
+ next = after.deleted && before.deleted ? {
242
+ kind: "orphan",
243
+ previous: anchor,
244
+ contextBefore: anchor.contextBefore,
245
+ contextAfter: anchor.contextAfter,
246
+ reason: "target-deleted"
247
+ } : capturePoint(source, after.pos);
248
+ }
249
+ return sameAnchor(anchor, next) ? anchor : next;
250
+ }
251
+ const isPosition = (value) => typeof value === "number" && Number.isInteger(value) && value >= 0;
252
+ const isOptionalText = (value) => value === void 0 || typeof value === "string";
253
+ function isLiveAnchor(value) {
254
+ if (typeof value !== "object" || value === null) return false;
255
+ const anchor = value;
256
+ if (!isOptionalText(anchor.contextBefore) || !isOptionalText(anchor.contextAfter)) return false;
257
+ if (anchor.kind === "range") return isPosition(anchor.from) && isPosition(anchor.to) && anchor.from <= anchor.to && typeof anchor.quote === "string";
258
+ return anchor.kind === "point" && isPosition(anchor.position);
259
+ }
260
+ /** Whether `value` has the shape of a stored anchor (range, point or orphan); for input from the network. */
261
+ function isPortableAnchor(value) {
262
+ if (isLiveAnchor(value)) return true;
263
+ if (typeof value !== "object" || value === null) return false;
264
+ const anchor = value;
265
+ return anchor.kind === "orphan" && isLiveAnchor(anchor.previous) && isOptionalText(anchor.quote) && isOptionalText(anchor.contextBefore) && isOptionalText(anchor.contextAfter) && (anchor.reason === "target-changed" || anchor.reason === "target-deleted" || anchor.reason === "unresolvable");
266
+ }
267
+ //#endregion
268
+ export { CONTEXT_LENGTH, QUOTE_LENGTH, captureAnchor, capturePoint, isPortableAnchor, mapAnchor, resolveAnchor };
269
+
270
+ //# sourceMappingURL=anchors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anchors.js","names":[],"sources":["../../src/adapters/anchors.ts"],"sourcesContent":["import type { Node as PMNode } from '@tiptap/pm/model'\n\n// Portable text anchors: a durable reference from a comment to a place in a document that survives edits, re-parses\n// and content swaps. Pure and framework-free: no runtime import, so the entry loads in a browser, a worker or Node.\n//\n// An anchor stores ProseMirror positions (fast path) plus the quoted text and its surroundings (fallback). Resolving\n// trusts the positions while the text there still reads as the quote, otherwise searches the document for the quote\n// and lets the context decide between repeats; no match at all is an orphan (`null`), never a guess.\n\n/** A half-open range of ProseMirror positions; `from === to` is a cursor position. */\nexport interface DocRange {\n from: number\n to: number\n}\n\n/** Selected text. */\nexport interface RangeAnchor {\n kind: 'range'\n from: number\n to: number\n quote: string\n contextBefore?: string\n contextAfter?: string\n}\n\n/** A zero-width cursor position. */\nexport interface PointAnchor {\n kind: 'point'\n position: number\n contextBefore?: string\n contextAfter?: string\n}\n\n/** What remains of an anchor whose target was deleted or cannot be found: the last good anchor plus its quote and context. */\nexport interface OrphanedAnchor {\n kind: 'orphan'\n previous: RangeAnchor | PointAnchor\n quote?: string\n contextBefore?: string\n contextAfter?: string\n reason: 'target-changed' | 'target-deleted' | 'unresolvable'\n}\n\n/** The stored form of an anchor; the layout of skaile-forge's `concept-discussions.json`. */\nexport type PortableAnchor = RangeAnchor | PointAnchor | OrphanedAnchor\n\n/** Characters of quote a range anchor keeps. */\nexport const QUOTE_LENGTH = 200\n/** Characters of surrounding text kept on each side. */\nexport const CONTEXT_LENGTH = 80\n/** Characters of context compared to accept an exact position and to search a moved one. */\nconst MATCH_LENGTH = 24\n\n/** What `captureAnchor` and `capturePoint` read: a tiptap `Editor`, or anything with `state.doc`. */\nexport interface AnchorSource {\n state: { doc: PMNode }\n}\n\n/** What `mapAnchor` reads of a ProseMirror transaction. */\nexport interface AnchorTransaction {\n /** The document after the transaction. */\n doc: PMNode\n mapping: { mapResult(pos: number, assoc?: number): { pos: number, deleted: boolean } }\n}\n\n// -- Text projection ------------------------------------------------------------------------------------------------\n\n/**\n * The document as one string with its positions: runs of whitespace collapse to one space, a text block boundary is a\n * space (`starts`/`ends` = the boundary position), a non-text leaf is its `leafText` or U+FFFC. `starts[i]`/`ends[i]`\n * are the document positions before and after character `i`; both are non-decreasing.\n */\ninterface Projection {\n text: string\n starts: number[]\n ends: number[]\n}\n\nconst projections = new WeakMap<PMNode, Projection>()\nconst isSpace = (char: string) => /\\s/.test(char)\n\nfunction projectionOf(doc: PMNode): Projection {\n const cached = projections.get(doc)\n if (cached) return cached\n const chars: string[] = []\n const starts: number[] = []\n const ends: number[] = []\n const push = (char: string, start: number, end: number) => {\n if (isSpace(char)) {\n if (chars.length === 0 || chars[chars.length - 1] === ' ') return\n char = ' '\n }\n chars.push(char)\n starts.push(start)\n ends.push(end)\n }\n doc.descendants((node, pos) => {\n if (node.isText) {\n const text = node.text ?? ''\n for (let i = 0; i < text.length; i++) push(text[i]!, pos + i, pos + i + 1)\n return\n }\n if (node.isTextblock || (node.isBlock && node.isLeaf)) push(' ', pos, pos)\n if (node.isLeaf) {\n const leaf = node.type.spec.leafText?.(node) ?? '\\ufffc'\n for (let i = 0; i < leaf.length; i++) push(leaf[i]!, pos, pos + node.nodeSize)\n }\n })\n const projection = { text: chars.join(''), starts, ends }\n projections.set(doc, projection)\n return projection\n}\n\n/** First index in the non-decreasing `values` whose value satisfies `ok` (monotone: false… then true…). */\nfunction firstIndex(values: readonly number[], ok: (value: number) => boolean): number {\n let low = 0\n let high = values.length\n while (low < high) {\n const middle = (low + high) >> 1\n if (ok(values[middle]!)) high = middle\n else low = middle + 1\n }\n return low\n}\n\n/** The characters of `projection` inside `[from, to]`: `[first, last)` indexes. */\nfunction span(projection: Projection, from: number, to: number): [number, number] {\n const first = firstIndex(projection.starts, start => start >= from)\n const last = firstIndex(projection.ends, end => end > to)\n return [first, Math.max(first, last)]\n}\n\n/** `value` with whitespace collapsed, as the projection holds text. */\nconst normalize = (value: string) => value.replace(/\\s+/g, ' ')\n\nconst clampPosition = (doc: PMNode, position: number) => Math.max(0, Math.min(doc.content.size, Math.floor(position)))\n\n// -- Capture ----------------------------------------------------------------------------------------------------------\n\n/** The anchor of the text in `range` (clamped to the document); its `quote` is the trimmed text, cut to `QUOTE_LENGTH`. */\nexport function captureAnchor(source: AnchorSource, range: DocRange): RangeAnchor {\n const { doc } = source.state\n const from = clampPosition(doc, Math.min(range.from, range.to))\n const to = clampPosition(doc, Math.max(range.from, range.to))\n const projection = projectionOf(doc)\n const [first, last] = span(projection, from, to)\n return {\n kind: 'range',\n from,\n to,\n quote: projection.text.slice(first, last).trim().slice(0, QUOTE_LENGTH),\n contextBefore: projection.text.slice(Math.max(0, first - CONTEXT_LENGTH), first),\n contextAfter: projection.text.slice(last, last + CONTEXT_LENGTH),\n }\n}\n\n/** The anchor of the cursor position `position` (clamped to the document). */\nexport function capturePoint(source: AnchorSource, position: number): PointAnchor {\n const { doc } = source.state\n const at = clampPosition(doc, position)\n const projection = projectionOf(doc)\n const [first, last] = span(projection, at, at)\n return {\n kind: 'point',\n position: at,\n contextBefore: projection.text.slice(Math.max(0, last - CONTEXT_LENGTH), last),\n contextAfter: projection.text.slice(first, first + CONTEXT_LENGTH),\n }\n}\n\n// -- Resolve ----------------------------------------------------------------------------------------------------------\n\nconst commonSuffix = (text: string, end: number, context: string): number => {\n let count = 0\n while (count < context.length && count < end && text[end - 1 - count] === context[context.length - 1 - count]) count++\n return count\n}\n\nconst commonPrefix = (text: string, start: number, context: string): number => {\n let count = 0\n while (count < context.length && start + count < text.length && text[start + count] === context[count]) count++\n return count\n}\n\n/** Every index where `needle` occurs in `text` (overlaps included). */\nfunction occurrences(text: string, needle: string): number[] {\n const found: number[] = []\n if (!needle) return found\n for (let index = text.indexOf(needle); index >= 0; index = text.indexOf(needle, index + 1)) found.push(index)\n return found\n}\n\nconst matchTail = (context: string) => context.slice(-MATCH_LENGTH)\nconst matchHead = (context: string) => context.slice(0, MATCH_LENGTH)\n\n/**\n * The range `anchor` points at in `doc`, or `null` when it cannot be found (an orphan). Orphaned anchors resolve to\n * `null` too: a deleted target is never silently re-attached to other text. A point resolves to `{ from, to }` with\n * `from === to`.\n *\n * 1. The stored positions, if they lie in the document and the text there still reads as the quote (a point: the\n * text around still reads as its context).\n * 2. A search for the quote (a point: for its context); among repeats the one whose surroundings agree most with the\n * stored context wins, ties go to the one nearest the stored position.\n */\nexport function resolveAnchor(doc: PMNode, anchor: PortableAnchor): DocRange | null {\n if (anchor.kind === 'orphan') return null\n const projection = projectionOf(doc)\n const size = doc.content.size\n const contextBefore = normalize(anchor.contextBefore ?? '')\n const contextAfter = normalize(anchor.contextAfter ?? '')\n\n if (anchor.kind === 'point') {\n const stored = Number.isInteger(anchor.position) && anchor.position >= 0 && anchor.position <= size ? anchor.position : null\n if (stored !== null) {\n const [first, last] = span(projection, stored, stored)\n const beforeOk = !contextBefore || projection.text.slice(0, last).endsWith(matchTail(contextBefore))\n const afterOk = !contextAfter || projection.text.startsWith(matchHead(contextAfter), first)\n if (beforeOk && afterOk) return { from: stored, to: stored }\n }\n const [firstIndex, ...otherIndexes] = pointCandidates(projection, contextBefore, contextAfter)\n if (firstIndex === undefined) return null\n const near = anchor.position\n const entryOf = (index: number) => ({\n index,\n position: boundaryPosition(projection, index),\n score: commonSuffix(projection.text, index, contextBefore) + commonPrefix(projection.text, index, contextAfter),\n })\n const best = pickBest(entryOf(firstIndex), otherIndexes.map(entryOf), entry => Math.abs(entry.position - near))\n return { from: best.position, to: best.position }\n }\n\n const quote = normalize(anchor.quote).trim()\n if (Number.isInteger(anchor.from) && Number.isInteger(anchor.to) && anchor.from >= 0 && anchor.from <= anchor.to && anchor.to <= size) {\n if (!quote) return { from: anchor.from, to: anchor.to }\n const [first, last] = span(projection, anchor.from, anchor.to)\n if (projection.text.slice(first, last).trim().slice(0, QUOTE_LENGTH) === quote) return { from: anchor.from, to: anchor.to }\n }\n if (!quote) return null\n const [firstMatch, ...otherMatches] = occurrences(projection.text, quote)\n if (firstMatch === undefined) return null\n // A quote cut at `QUOTE_LENGTH` says where the text starts, not where it ends: keep the stored length.\n const truncated = quote.length >= QUOTE_LENGTH\n const entryOf = (index: number) => {\n const from = projection.starts[index]!\n const to = truncated ? Math.min(size, from + Math.max(0, anchor.to - anchor.from)) : projection.ends[index + quote.length - 1]!\n return {\n index,\n from,\n to,\n score: commonSuffix(projection.text, index, contextBefore) + commonPrefix(projection.text, index + quote.length, contextAfter),\n }\n }\n const best = pickBest(entryOf(firstMatch), otherMatches.map(entryOf), entry => Math.abs(entry.from - anchor.from))\n return { from: best.from, to: best.to }\n}\n\n/** The boundaries (between characters) where the context of a point reads: both sides, else either side alone. */\nfunction pointCandidates(projection: Projection, contextBefore: string, contextAfter: string): number[] {\n const before = matchTail(contextBefore)\n const after = matchHead(contextAfter)\n if (before && after) {\n const both = occurrences(projection.text, before + after).map(index => index + before.length)\n if (both.length) return both\n }\n if (before) {\n const found = occurrences(projection.text, before).map(index => index + before.length)\n if (found.length) return found\n }\n if (after) return occurrences(projection.text, after)\n return []\n}\n\n/** The cursor position of the boundary before character `index`. */\nfunction boundaryPosition(projection: Projection, index: number): number {\n const { text, starts, ends } = projection\n if (index >= text.length) return ends[text.length - 1] ?? 0\n // Before a block boundary the cursor belongs at the end of the text before it, not in the gap between blocks.\n if (text[index] === ' ' && starts[index] === ends[index] && index > 0) return ends[index - 1]!\n return starts[index]!\n}\n\n/** The entry with the highest `score` among `first` and `rest`; among equals the one with the lowest `distance`. */\nfunction pickBest<T extends { score: number }>(first: T, rest: readonly T[], distance: (entry: T) => number): T {\n let best = first\n for (const entry of rest) {\n if (entry.score > best.score || (entry.score === best.score && distance(entry) < distance(best))) best = entry\n }\n return best\n}\n\n// -- Map --------------------------------------------------------------------------------------------------------------\n\nconst sameAnchor = (a: PortableAnchor, b: PortableAnchor) => JSON.stringify(a) === JSON.stringify(b)\n\n/**\n * `anchor` carried through a transaction: positions follow the edit, quote and context are read again from the new\n * document (so an edit inside the range changes the quote). The same object when nothing changed. A range collapsed by\n * the edit, or a point inside deleted text, becomes an `orphan` that keeps the last good anchor; orphans stay as they are.\n */\nexport function mapAnchor(anchor: PortableAnchor, transaction: AnchorTransaction): PortableAnchor {\n if (anchor.kind === 'orphan') return anchor\n const source = { state: { doc: transaction.doc } }\n let next: PortableAnchor\n if (anchor.kind === 'range') {\n const from = transaction.mapping.mapResult(anchor.from, 1).pos\n const to = transaction.mapping.mapResult(anchor.to, -1).pos\n if (from >= to) {\n next = { kind: 'orphan', previous: anchor, quote: anchor.quote, contextBefore: anchor.contextBefore, contextAfter: anchor.contextAfter, reason: from === to ? 'target-deleted' : 'target-changed' }\n }\n else {\n next = captureAnchor(source, { from, to })\n }\n }\n else {\n const after = transaction.mapping.mapResult(anchor.position, 1)\n const before = transaction.mapping.mapResult(anchor.position, -1)\n next = after.deleted && before.deleted\n ? { kind: 'orphan', previous: anchor, contextBefore: anchor.contextBefore, contextAfter: anchor.contextAfter, reason: 'target-deleted' }\n : capturePoint(source, after.pos)\n }\n return sameAnchor(anchor, next) ? anchor : next\n}\n\n// -- Validation -------------------------------------------------------------------------------------------------------\n\nconst isPosition = (value: unknown): value is number => typeof value === 'number' && Number.isInteger(value) && value >= 0\nconst isOptionalText = (value: unknown) => value === undefined || typeof value === 'string'\n\nfunction isLiveAnchor(value: unknown): value is RangeAnchor | PointAnchor {\n if (typeof value !== 'object' || value === null) return false\n const anchor = value as Record<string, unknown>\n if (!isOptionalText(anchor.contextBefore) || !isOptionalText(anchor.contextAfter)) return false\n if (anchor.kind === 'range') return isPosition(anchor.from) && isPosition(anchor.to) && anchor.from <= anchor.to && typeof anchor.quote === 'string'\n return anchor.kind === 'point' && isPosition(anchor.position)\n}\n\n/** Whether `value` has the shape of a stored anchor (range, point or orphan); for input from the network. */\nexport function isPortableAnchor(value: unknown): value is PortableAnchor {\n if (isLiveAnchor(value)) return true\n if (typeof value !== 'object' || value === null) return false\n const anchor = value as Record<string, unknown>\n return anchor.kind === 'orphan'\n && isLiveAnchor(anchor.previous)\n && isOptionalText(anchor.quote) && isOptionalText(anchor.contextBefore) && isOptionalText(anchor.contextAfter)\n && (anchor.reason === 'target-changed' || anchor.reason === 'target-deleted' || anchor.reason === 'unresolvable')\n}\n"],"mappings":";;AA+CA,MAAa,eAAe;;AAE5B,MAAa,iBAAiB;;AAE9B,MAAM,eAAe;AA2BrB,MAAM,8BAAc,IAAI,QAA4B;AACpD,MAAM,WAAW,SAAiB,KAAK,KAAK,IAAI;AAEhD,SAAS,aAAa,KAAyB;CAC7C,MAAM,SAAS,YAAY,IAAI,GAAG;CAClC,IAAI,QAAQ,OAAO;CACnB,MAAM,QAAkB,CAAC;CACzB,MAAM,SAAmB,CAAC;CAC1B,MAAM,OAAiB,CAAC;CACxB,MAAM,QAAQ,MAAc,OAAe,QAAgB;EACzD,IAAI,QAAQ,IAAI,GAAG;GACjB,IAAI,MAAM,WAAW,KAAK,MAAM,MAAM,SAAS,OAAO,KAAK;GAC3D,OAAO;EACT;EACA,MAAM,KAAK,IAAI;EACf,OAAO,KAAK,KAAK;EACjB,KAAK,KAAK,GAAG;CACf;CACA,IAAI,aAAa,MAAM,QAAQ;EAC7B,IAAI,KAAK,QAAQ;GACf,MAAM,OAAO,KAAK,QAAQ;GAC1B,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,KAAK,KAAK,IAAK,MAAM,GAAG,MAAM,IAAI,CAAC;GACzE;EACF;EACA,IAAI,KAAK,eAAgB,KAAK,WAAW,KAAK,QAAS,KAAK,KAAK,KAAK,GAAG;EACzE,IAAI,KAAK,QAAQ;GACf,MAAM,OAAO,KAAK,KAAK,KAAK,WAAW,IAAI,KAAK;GAChD,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,KAAK,KAAK,IAAK,KAAK,MAAM,KAAK,QAAQ;EAC/E;CACF,CAAC;CACD,MAAM,aAAa;EAAE,MAAM,MAAM,KAAK,EAAE;EAAG;EAAQ;CAAK;CACxD,YAAY,IAAI,KAAK,UAAU;CAC/B,OAAO;AACT;;AAGA,SAAS,WAAW,QAA2B,IAAwC;CACrF,IAAI,MAAM;CACV,IAAI,OAAO,OAAO;CAClB,OAAO,MAAM,MAAM;EACjB,MAAM,SAAU,MAAM,QAAS;EAC/B,IAAI,GAAG,OAAO,OAAQ,GAAG,OAAO;OAC3B,MAAM,SAAS;CACtB;CACA,OAAO;AACT;;AAGA,SAAS,KAAK,YAAwB,MAAc,IAA8B;CAChF,MAAM,QAAQ,WAAW,WAAW,SAAQ,UAAS,SAAS,IAAI;CAClE,MAAM,OAAO,WAAW,WAAW,OAAM,QAAO,MAAM,EAAE;CACxD,OAAO,CAAC,OAAO,KAAK,IAAI,OAAO,IAAI,CAAC;AACtC;;AAGA,MAAM,aAAa,UAAkB,MAAM,QAAQ,QAAQ,GAAG;AAE9D,MAAM,iBAAiB,KAAa,aAAqB,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,QAAQ,MAAM,KAAK,MAAM,QAAQ,CAAC,CAAC;;AAKrH,SAAgB,cAAc,QAAsB,OAA8B;CAChF,MAAM,EAAE,QAAQ,OAAO;CACvB,MAAM,OAAO,cAAc,KAAK,KAAK,IAAI,MAAM,MAAM,MAAM,EAAE,CAAC;CAC9D,MAAM,KAAK,cAAc,KAAK,KAAK,IAAI,MAAM,MAAM,MAAM,EAAE,CAAC;CAC5D,MAAM,aAAa,aAAa,GAAG;CACnC,MAAM,CAAC,OAAO,QAAQ,KAAK,YAAY,MAAM,EAAE;CAC/C,OAAO;EACL,MAAM;EACN;EACA;EACA,OAAO,WAAW,KAAK,MAAM,OAAO,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,GAAA,GAAe;EACtE,eAAe,WAAW,KAAK,MAAM,KAAK,IAAI,GAAG,QAAA,EAAsB,GAAG,KAAK;EAC/E,cAAc,WAAW,KAAK,MAAM,MAAM,OAAA,EAAqB;CACjE;AACF;;AAGA,SAAgB,aAAa,QAAsB,UAA+B;CAChF,MAAM,EAAE,QAAQ,OAAO;CACvB,MAAM,KAAK,cAAc,KAAK,QAAQ;CACtC,MAAM,aAAa,aAAa,GAAG;CACnC,MAAM,CAAC,OAAO,QAAQ,KAAK,YAAY,IAAI,EAAE;CAC7C,OAAO;EACL,MAAM;EACN,UAAU;EACV,eAAe,WAAW,KAAK,MAAM,KAAK,IAAI,GAAG,OAAA,EAAqB,GAAG,IAAI;EAC7E,cAAc,WAAW,KAAK,MAAM,OAAO,QAAA,EAAsB;CACnE;AACF;AAIA,MAAM,gBAAgB,MAAc,KAAa,YAA4B;CAC3E,IAAI,QAAQ;CACZ,OAAO,QAAQ,QAAQ,UAAU,QAAQ,OAAO,KAAK,MAAM,IAAI,WAAW,QAAQ,QAAQ,SAAS,IAAI,QAAQ;CAC/G,OAAO;AACT;AAEA,MAAM,gBAAgB,MAAc,OAAe,YAA4B;CAC7E,IAAI,QAAQ;CACZ,OAAO,QAAQ,QAAQ,UAAU,QAAQ,QAAQ,KAAK,UAAU,KAAK,QAAQ,WAAW,QAAQ,QAAQ;CACxG,OAAO;AACT;;AAGA,SAAS,YAAY,MAAc,QAA0B;CAC3D,MAAM,QAAkB,CAAC;CACzB,IAAI,CAAC,QAAQ,OAAO;CACpB,KAAK,IAAI,QAAQ,KAAK,QAAQ,MAAM,GAAG,SAAS,GAAG,QAAQ,KAAK,QAAQ,QAAQ,QAAQ,CAAC,GAAG,MAAM,KAAK,KAAK;CAC5G,OAAO;AACT;AAEA,MAAM,aAAa,YAAoB,QAAQ,MAAM,GAAa;AAClE,MAAM,aAAa,YAAoB,QAAQ,MAAM,GAAG,YAAY;;;;;;;;;;;AAYpE,SAAgB,cAAc,KAAa,QAAyC;CAClF,IAAI,OAAO,SAAS,UAAU,OAAO;CACrC,MAAM,aAAa,aAAa,GAAG;CACnC,MAAM,OAAO,IAAI,QAAQ;CACzB,MAAM,gBAAgB,UAAU,OAAO,iBAAiB,EAAE;CAC1D,MAAM,eAAe,UAAU,OAAO,gBAAgB,EAAE;CAExD,IAAI,OAAO,SAAS,SAAS;EAC3B,MAAM,SAAS,OAAO,UAAU,OAAO,QAAQ,KAAK,OAAO,YAAY,KAAK,OAAO,YAAY,OAAO,OAAO,WAAW;EACxH,IAAI,WAAW,MAAM;GACnB,MAAM,CAAC,OAAO,QAAQ,KAAK,YAAY,QAAQ,MAAM;GACrD,MAAM,WAAW,CAAC,iBAAiB,WAAW,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,SAAS,UAAU,aAAa,CAAC;GACnG,MAAM,UAAU,CAAC,gBAAgB,WAAW,KAAK,WAAW,UAAU,YAAY,GAAG,KAAK;GAC1F,IAAI,YAAY,SAAS,OAAO;IAAE,MAAM;IAAQ,IAAI;GAAO;EAC7D;EACA,MAAM,CAAC,YAAY,GAAG,gBAAgB,gBAAgB,YAAY,eAAe,YAAY;EAC7F,IAAI,eAAe,KAAA,GAAW,OAAO;EACrC,MAAM,OAAO,OAAO;EACpB,MAAM,WAAW,WAAmB;GAClC;GACA,UAAU,iBAAiB,YAAY,KAAK;GAC5C,OAAO,aAAa,WAAW,MAAM,OAAO,aAAa,IAAI,aAAa,WAAW,MAAM,OAAO,YAAY;EAChH;EACA,MAAM,OAAO,SAAS,QAAQ,UAAU,GAAG,aAAa,IAAI,OAAO,IAAG,UAAS,KAAK,IAAI,MAAM,WAAW,IAAI,CAAC;EAC9G,OAAO;GAAE,MAAM,KAAK;GAAU,IAAI,KAAK;EAAS;CAClD;CAEA,MAAM,QAAQ,UAAU,OAAO,KAAK,CAAC,CAAC,KAAK;CAC3C,IAAI,OAAO,UAAU,OAAO,IAAI,KAAK,OAAO,UAAU,OAAO,EAAE,KAAK,OAAO,QAAQ,KAAK,OAAO,QAAQ,OAAO,MAAM,OAAO,MAAM,MAAM;EACrI,IAAI,CAAC,OAAO,OAAO;GAAE,MAAM,OAAO;GAAM,IAAI,OAAO;EAAG;EACtD,MAAM,CAAC,OAAO,QAAQ,KAAK,YAAY,OAAO,MAAM,OAAO,EAAE;EAC7D,IAAI,WAAW,KAAK,MAAM,OAAO,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,GAAA,GAAe,MAAM,OAAO,OAAO;GAAE,MAAM,OAAO;GAAM,IAAI,OAAO;EAAG;CAC5H;CACA,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,CAAC,YAAY,GAAG,gBAAgB,YAAY,WAAW,MAAM,KAAK;CACxE,IAAI,eAAe,KAAA,GAAW,OAAO;CAErC,MAAM,YAAY,MAAM,UAAA;CACxB,MAAM,WAAW,UAAkB;EACjC,MAAM,OAAO,WAAW,OAAO;EAE/B,OAAO;GACL;GACA;GACA,IAJS,YAAY,KAAK,IAAI,MAAM,OAAO,KAAK,IAAI,GAAG,OAAO,KAAK,OAAO,IAAI,CAAC,IAAI,WAAW,KAAK,QAAQ,MAAM,SAAS;GAK1H,OAAO,aAAa,WAAW,MAAM,OAAO,aAAa,IAAI,aAAa,WAAW,MAAM,QAAQ,MAAM,QAAQ,YAAY;EAC/H;CACF;CACA,MAAM,OAAO,SAAS,QAAQ,UAAU,GAAG,aAAa,IAAI,OAAO,IAAG,UAAS,KAAK,IAAI,MAAM,OAAO,OAAO,IAAI,CAAC;CACjH,OAAO;EAAE,MAAM,KAAK;EAAM,IAAI,KAAK;CAAG;AACxC;;AAGA,SAAS,gBAAgB,YAAwB,eAAuB,cAAgC;CACtG,MAAM,SAAS,UAAU,aAAa;CACtC,MAAM,QAAQ,UAAU,YAAY;CACpC,IAAI,UAAU,OAAO;EACnB,MAAM,OAAO,YAAY,WAAW,MAAM,SAAS,KAAK,CAAC,CAAC,KAAI,UAAS,QAAQ,OAAO,MAAM;EAC5F,IAAI,KAAK,QAAQ,OAAO;CAC1B;CACA,IAAI,QAAQ;EACV,MAAM,QAAQ,YAAY,WAAW,MAAM,MAAM,CAAC,CAAC,KAAI,UAAS,QAAQ,OAAO,MAAM;EACrF,IAAI,MAAM,QAAQ,OAAO;CAC3B;CACA,IAAI,OAAO,OAAO,YAAY,WAAW,MAAM,KAAK;CACpD,OAAO,CAAC;AACV;;AAGA,SAAS,iBAAiB,YAAwB,OAAuB;CACvE,MAAM,EAAE,MAAM,QAAQ,SAAS;CAC/B,IAAI,SAAS,KAAK,QAAQ,OAAO,KAAK,KAAK,SAAS,MAAM;CAE1D,IAAI,KAAK,WAAW,OAAO,OAAO,WAAW,KAAK,UAAU,QAAQ,GAAG,OAAO,KAAK,QAAQ;CAC3F,OAAO,OAAO;AAChB;;AAGA,SAAS,SAAsC,OAAU,MAAoB,UAAmC;CAC9G,IAAI,OAAO;CACX,KAAK,MAAM,SAAS,MAClB,IAAI,MAAM,QAAQ,KAAK,SAAU,MAAM,UAAU,KAAK,SAAS,SAAS,KAAK,IAAI,SAAS,IAAI,GAAI,OAAO;CAE3G,OAAO;AACT;AAIA,MAAM,cAAc,GAAmB,MAAsB,KAAK,UAAU,CAAC,MAAM,KAAK,UAAU,CAAC;;;;;;AAOnG,SAAgB,UAAU,QAAwB,aAAgD;CAChG,IAAI,OAAO,SAAS,UAAU,OAAO;CACrC,MAAM,SAAS,EAAE,OAAO,EAAE,KAAK,YAAY,IAAI,EAAE;CACjD,IAAI;CACJ,IAAI,OAAO,SAAS,SAAS;EAC3B,MAAM,OAAO,YAAY,QAAQ,UAAU,OAAO,MAAM,CAAC,CAAC,CAAC;EAC3D,MAAM,KAAK,YAAY,QAAQ,UAAU,OAAO,IAAI,EAAE,CAAC,CAAC;EACxD,IAAI,QAAQ,IACV,OAAO;GAAE,MAAM;GAAU,UAAU;GAAQ,OAAO,OAAO;GAAO,eAAe,OAAO;GAAe,cAAc,OAAO;GAAc,QAAQ,SAAS,KAAK,mBAAmB;EAAiB;OAGlM,OAAO,cAAc,QAAQ;GAAE;GAAM;EAAG,CAAC;CAE7C,OACK;EACH,MAAM,QAAQ,YAAY,QAAQ,UAAU,OAAO,UAAU,CAAC;EAC9D,MAAM,SAAS,YAAY,QAAQ,UAAU,OAAO,UAAU,EAAE;EAChE,OAAO,MAAM,WAAW,OAAO,UAC3B;GAAE,MAAM;GAAU,UAAU;GAAQ,eAAe,OAAO;GAAe,cAAc,OAAO;GAAc,QAAQ;EAAiB,IACrI,aAAa,QAAQ,MAAM,GAAG;CACpC;CACA,OAAO,WAAW,QAAQ,IAAI,IAAI,SAAS;AAC7C;AAIA,MAAM,cAAc,UAAoC,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,SAAS;AACzH,MAAM,kBAAkB,UAAmB,UAAU,KAAA,KAAa,OAAO,UAAU;AAEnF,SAAS,aAAa,OAAoD;CACxE,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,MAAM,SAAS;CACf,IAAI,CAAC,eAAe,OAAO,aAAa,KAAK,CAAC,eAAe,OAAO,YAAY,GAAG,OAAO;CAC1F,IAAI,OAAO,SAAS,SAAS,OAAO,WAAW,OAAO,IAAI,KAAK,WAAW,OAAO,EAAE,KAAK,OAAO,QAAQ,OAAO,MAAM,OAAO,OAAO,UAAU;CAC5I,OAAO,OAAO,SAAS,WAAW,WAAW,OAAO,QAAQ;AAC9D;;AAGA,SAAgB,iBAAiB,OAAyC;CACxE,IAAI,aAAa,KAAK,GAAG,OAAO;CAChC,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,MAAM,SAAS;CACf,OAAO,OAAO,SAAS,YAClB,aAAa,OAAO,QAAQ,KAC5B,eAAe,OAAO,KAAK,KAAK,eAAe,OAAO,aAAa,KAAK,eAAe,OAAO,YAAY,MACzG,OAAO,WAAW,oBAAoB,OAAO,WAAW,oBAAoB,OAAO,WAAW;AACtG"}
@@ -0,0 +1,64 @@
1
+ import { AuthorIdentity } from "../../core/feature.js";
2
+ import "../../core/index.js";
3
+ import { CommentsModel } from "../../features/comments/model.js";
4
+ import "../../features/comments/index.js";
5
+ import { Editor } from "@tiptap/core";
6
+ //#region src/adapters/comments/client.d.ts
7
+ /** The part of a `Response` the model reads; `globalThis.fetch` fits. */
8
+ export interface CommentsFetchResponse {
9
+ ok: boolean;
10
+ status: number;
11
+ json(): Promise<unknown>;
12
+ }
13
+ export type CommentsFetch = (url: string, init?: {
14
+ method?: string;
15
+ headers?: Record<string, string>;
16
+ body?: string;
17
+ }) => Promise<CommentsFetchResponse>;
18
+ export interface RestCommentsModelOptions {
19
+ /** How requests are sent: `globalThis.fetch`, or a wrapper that adds the base URL and credentials. */
20
+ fetch: CommentsFetch;
21
+ /** The document the threads belong to. */
22
+ documentId: string;
23
+ /** The document's comments endpoint (`GET` → `Discussion`, `POST` → `CommentAction`); default `/comments/<encoded documentId>`. */
24
+ endpoint?: string;
25
+ /** Who writes: the author of new comments and the only one who may edit or delete them; `null` while nobody is signed in (no comments then). */
26
+ user: AuthorIdentity | null | (() => AuthorIdentity | null);
27
+ /** Whether the user may compose and change comments now; readers of a read-only document may be allowed. */
28
+ canComment: boolean | (() => boolean);
29
+ /** The editor the threads belong to; may return no editor yet or a destroyed one. */
30
+ getEditor: () => Editor | undefined;
31
+ /** Milliseconds a changed anchor waits before it is written back (typing moves many); default 500. `idle()` writes at once. */
32
+ persistDelay?: number;
33
+ /** Called with every failed request; the model reloads the threads afterwards, so the local state falls back to the server's. */
34
+ onError?: (error: Error) => void;
35
+ }
36
+ export interface RestCommentsModel extends CommentsModel {
37
+ /** Fetches the threads again and resolves their anchors against the current document. */
38
+ reload(): Promise<void>;
39
+ /** Resolves once every change made so far has been sent (and the threads loaded, if a load was requested). */
40
+ idle(): Promise<void>;
41
+ }
42
+ /**
43
+ * The comments of one editor over a REST endpoint, as a `CommentsModel` (the host passes `() => model` as the
44
+ * `model` factory of `commentsFeature`/`commentsFeatureCore`; one instance serves the plugin and every hook).
45
+ *
46
+ * - **Load.** `refresh()` (called by the feature once the editor exists) fetches `GET endpoint`; each annotation's
47
+ * anchor is resolved against the document (exact offsets, else quote and context search). An anchor that moved is
48
+ * re-captured and written back; an anchor whose text is gone becomes an `orphan` (the thread stays, without a range)
49
+ * and an orphan whose text is back with the same surroundings is re-attached. While the document is still empty the
50
+ * resolving waits for its content.
51
+ * - **Edits.** Every transaction carries all anchors with it (`mapAnchor`), so ranges, quotes and contexts follow the
52
+ * text; changed anchors are written back after `persistDelay`. Text deleted under an anchor orphans it for good (it is
53
+ * never silently re-attached to other text during editing; `reload()` re-attaches it when its text and context
54
+ * are found again, e.g. after the content was replaced).
55
+ * - **Writes** are applied locally at once and sent in order; a failed request is reported to `onError` and the threads
56
+ * are reloaded. Without a `user` or while `canComment` is false nothing is written. Only the author may edit or
57
+ * delete a comment.
58
+ * - **Lifecycle.** The model follows the editor `getEditor` names until that editor is destroyed; `dispose()` writes
59
+ * pending anchor changes and does not stop it (the feature and its hooks share the instance and dispose it on their
60
+ * own schedule).
61
+ */
62
+ export declare function createRestCommentsModel(options: RestCommentsModelOptions): RestCommentsModel;
63
+ //#endregion
64
+ //# sourceMappingURL=client.d.ts.map