quoin-editor 1.7.0 → 2.0.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 (260) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/MIGRATION.md +137 -23
  3. package/README.md +32 -11
  4. package/dist/ai-labels-BXX32H9a.d.cts +53 -0
  5. package/dist/ai-labels-BXX32H9a.d.ts +53 -0
  6. package/dist/ai.cjs +84 -86
  7. package/dist/ai.d.cts +4 -53
  8. package/dist/ai.d.ts +4 -53
  9. package/dist/ai.js +8 -10
  10. package/dist/ai.js.map +1 -1
  11. package/dist/catalogue.cjs +27 -26
  12. package/dist/catalogue.d.cts +10 -1
  13. package/dist/catalogue.d.ts +10 -1
  14. package/dist/catalogue.js +18 -17
  15. package/dist/catalogue.js.map +1 -1
  16. package/dist/{chunk-MNA7MBCH.js → chunk-2UKI26JA.js} +9 -11
  17. package/dist/chunk-2UKI26JA.js.map +1 -0
  18. package/dist/{chunk-MZXW2HXP.cjs → chunk-4PIJC4FD.cjs} +27 -29
  19. package/dist/{chunk-OS2O6IKX.js → chunk-5RBMZGKV.js} +4 -4
  20. package/dist/{chunk-OS2O6IKX.js.map → chunk-5RBMZGKV.js.map} +1 -1
  21. package/dist/{chunk-OJ4O6HWC.js → chunk-5X52BAZB.js} +10 -12
  22. package/dist/chunk-5X52BAZB.js.map +1 -0
  23. package/dist/{chunk-JY5VUHKN.cjs → chunk-5YAJZSTI.cjs} +28 -30
  24. package/dist/{chunk-YEFPH27F.cjs → chunk-66OPHDFA.cjs} +92 -94
  25. package/dist/{chunk-PWUFFHAQ.js → chunk-6BVE46K7.js} +8 -10
  26. package/dist/chunk-6BVE46K7.js.map +1 -0
  27. package/dist/{chunk-LKZJ2TDU.cjs → chunk-77CNOLHY.cjs} +10 -13
  28. package/dist/{chunk-4T4TJ2QS.cjs → chunk-7E43TURF.cjs} +7 -7
  29. package/dist/{chunk-BJTOLCRR.js → chunk-7Y4B7NHO.js} +2 -2
  30. package/dist/chunk-7Y4B7NHO.js.map +1 -0
  31. package/dist/{chunk-7GU25Z6L.js → chunk-B4VCDJSA.js} +9 -11
  32. package/dist/chunk-B4VCDJSA.js.map +1 -0
  33. package/dist/{chunk-QR2NIDUK.cjs → chunk-BYYCD54G.cjs} +2 -2
  34. package/dist/{chunk-6T5YGCZ5.cjs → chunk-CJVJNMOB.cjs} +302 -299
  35. package/dist/{chunk-CSKKSXCE.js → chunk-CSPCDVCT.js} +3 -3
  36. package/dist/{chunk-CSKKSXCE.js.map → chunk-CSPCDVCT.js.map} +1 -1
  37. package/dist/{chunk-BF7733XS.cjs → chunk-D6CIIWCU.cjs} +57 -59
  38. package/dist/{chunk-GHNQKJMD.js → chunk-DBOZFNWU.js} +3 -3
  39. package/dist/{chunk-GHNQKJMD.js.map → chunk-DBOZFNWU.js.map} +1 -1
  40. package/dist/{chunk-BM2GRZNM.cjs → chunk-DJU25PFC.cjs} +11 -11
  41. package/dist/chunk-DLQCTGC3.js +10 -0
  42. package/dist/chunk-DLQCTGC3.js.map +1 -0
  43. package/dist/{chunk-RQXJKJ5S.js → chunk-EU5IGKMP.js} +7 -10
  44. package/dist/chunk-EU5IGKMP.js.map +1 -0
  45. package/dist/{chunk-VZZPCYA3.cjs → chunk-FK7EBZPX.cjs} +226 -226
  46. package/dist/{chunk-BXSWZEA5.js → chunk-GPTJYWGS.js} +3 -3
  47. package/dist/{chunk-BXSWZEA5.js.map → chunk-GPTJYWGS.js.map} +1 -1
  48. package/dist/{chunk-I722LBD3.cjs → chunk-HOSDMHK6.cjs} +32 -34
  49. package/dist/{chunk-AZF7B2AE.cjs → chunk-IKZSCTRF.cjs} +11 -11
  50. package/dist/{chunk-R7MYG522.js → chunk-IW4TMECB.js} +3 -3
  51. package/dist/{chunk-R7MYG522.js.map → chunk-IW4TMECB.js.map} +1 -1
  52. package/dist/{chunk-PLUMKN4B.js → chunk-J2SVYBTC.js} +10 -12
  53. package/dist/chunk-J2SVYBTC.js.map +1 -0
  54. package/dist/{chunk-UZS3TW75.js → chunk-KQPZTSW3.js} +9 -11
  55. package/dist/chunk-KQPZTSW3.js.map +1 -0
  56. package/dist/{chunk-STKGRXSA.cjs → chunk-LEICOZYK.cjs} +15 -17
  57. package/dist/{chunk-H3C3MJW5.cjs → chunk-LM3DHROX.cjs} +2 -2
  58. package/dist/{chunk-AD3NY7NA.js → chunk-LSI4RMTY.js} +3 -3
  59. package/dist/{chunk-AD3NY7NA.js.map → chunk-LSI4RMTY.js.map} +1 -1
  60. package/dist/{chunk-QOIVLYMW.js → chunk-M77A3EGD.js} +5 -5
  61. package/dist/{chunk-QOIVLYMW.js.map → chunk-M77A3EGD.js.map} +1 -1
  62. package/dist/{chunk-5JEQZIRI.js → chunk-MAS4AEF4.js} +8 -10
  63. package/dist/chunk-MAS4AEF4.js.map +1 -0
  64. package/dist/{chunk-KDK2ATMB.cjs → chunk-NFO3W644.cjs} +51 -53
  65. package/dist/{chunk-XFEDJEJF.js → chunk-NL5J77VD.js} +204 -204
  66. package/dist/chunk-NL5J77VD.js.map +1 -0
  67. package/dist/{chunk-WN7BZDNH.cjs → chunk-PURYVDC3.cjs} +13 -13
  68. package/dist/{chunk-G25FMFEZ.js → chunk-UDBLZILE.js} +4 -4
  69. package/dist/{chunk-G25FMFEZ.js.map → chunk-UDBLZILE.js.map} +1 -1
  70. package/dist/{chunk-FNJOPGMN.cjs → chunk-V4WQG7FR.cjs} +116 -118
  71. package/dist/{chunk-IJKGF6SC.js → chunk-YABGWOAV.js} +8 -10
  72. package/dist/chunk-YABGWOAV.js.map +1 -0
  73. package/dist/{chunk-XZ4NC3Q7.cjs → chunk-YEDPBYO5.cjs} +4 -4
  74. package/dist/{chunk-QJ34MOFV.cjs → chunk-Z6LAARPB.cjs} +315 -320
  75. package/dist/{chunk-HSLOPJVT.js → chunk-ZN5DHNSL.js} +19 -16
  76. package/dist/chunk-ZN5DHNSL.js.map +1 -0
  77. package/dist/chunk-ZWTIRGY5.cjs +10 -0
  78. package/dist/comments-labels-DWqsSz8G.d.cts +30 -0
  79. package/dist/comments-labels-DWqsSz8G.d.ts +30 -0
  80. package/dist/comments.cjs +63 -65
  81. package/dist/comments.d.cts +4 -30
  82. package/dist/comments.d.ts +4 -30
  83. package/dist/comments.js +9 -11
  84. package/dist/comments.js.map +1 -1
  85. package/dist/{diagram-extension-Da7hp-tv.d.ts → diagram-extension-22x_njXB.d.ts} +4 -12
  86. package/dist/{diagram-extension-D573COJv.d.cts → diagram-extension-C_FSkoj9.d.cts} +4 -12
  87. package/dist/diagram-labels-Bi_jEZ8_.d.cts +11 -0
  88. package/dist/diagram-labels-Bi_jEZ8_.d.ts +11 -0
  89. package/dist/diagram.cjs +10 -9
  90. package/dist/diagram.d.cts +3 -2
  91. package/dist/diagram.d.ts +3 -2
  92. package/dist/diagram.js +7 -6
  93. package/dist/diff-labels-DWwg7N0n.d.cts +27 -0
  94. package/dist/diff-labels-DWwg7N0n.d.ts +27 -0
  95. package/dist/diff.cjs +27 -29
  96. package/dist/diff.d.cts +4 -27
  97. package/dist/diff.d.ts +4 -27
  98. package/dist/diff.js +5 -7
  99. package/dist/diff.js.map +1 -1
  100. package/dist/docx.cjs +30 -30
  101. package/dist/docx.js +3 -3
  102. package/dist/embed-labels-BiVa9HYl.d.cts +23 -0
  103. package/dist/embed-labels-BiVa9HYl.d.ts +23 -0
  104. package/dist/embed.cjs +10 -9
  105. package/dist/embed.d.cts +4 -23
  106. package/dist/embed.d.ts +4 -23
  107. package/dist/embed.js +8 -7
  108. package/dist/emoji-extension-C832oGeT.d.ts +16 -0
  109. package/dist/emoji-extension-Dk5IQLZf.d.cts +16 -0
  110. package/dist/{emoji-extension-gRKnbW8G.d.ts → emoji-labels-YwOms7ZJ.d.cts} +2 -15
  111. package/dist/{emoji-extension-C3o_Y5uC.d.cts → emoji-labels-YwOms7ZJ.d.ts} +2 -15
  112. package/dist/emoji.cjs +10 -9
  113. package/dist/emoji.d.cts +4 -3
  114. package/dist/emoji.d.ts +4 -3
  115. package/dist/emoji.js +7 -6
  116. package/dist/engine.cjs +58 -58
  117. package/dist/engine.d.cts +8 -7
  118. package/dist/engine.d.ts +8 -7
  119. package/dist/engine.js +3 -3
  120. package/dist/find-extension-BfruAPLi.d.cts +15 -0
  121. package/dist/find-extension-DmK9_N81.d.ts +15 -0
  122. package/dist/{find-extension-B_Ctrs6T.d.ts → find-labels-C4vs8Qe9.d.cts} +2 -14
  123. package/dist/{find-extension-BGGsJMu4.d.cts → find-labels-C4vs8Qe9.d.ts} +2 -14
  124. package/dist/find.cjs +11 -10
  125. package/dist/find.d.cts +2 -1
  126. package/dist/find.d.ts +2 -1
  127. package/dist/find.js +7 -6
  128. package/dist/headless.cjs +69 -89
  129. package/dist/headless.d.cts +21 -28
  130. package/dist/headless.d.ts +21 -28
  131. package/dist/headless.js +4 -4
  132. package/dist/highlight.cjs +7 -6
  133. package/dist/highlight.js +6 -5
  134. package/dist/highlight.js.map +1 -1
  135. package/dist/{index-CsG7OQoq.d.cts → index-3fYvwqUZ.d.cts} +1 -1
  136. package/dist/{index-h1llgZM-.d.ts → index-CgxnzCO5.d.ts} +1 -1
  137. package/dist/index.cjs +82 -101
  138. package/dist/index.d.cts +18 -185
  139. package/dist/index.d.ts +18 -185
  140. package/dist/index.js +6 -5
  141. package/dist/labels-BhRJYo46.d.ts +176 -0
  142. package/dist/labels-CiCxj2jP.d.cts +176 -0
  143. package/dist/locale-ar.cjs +565 -0
  144. package/dist/locale-ar.d.cts +63 -0
  145. package/dist/locale-ar.d.ts +63 -0
  146. package/dist/locale-ar.js +551 -0
  147. package/dist/locale-ar.js.map +1 -0
  148. package/dist/locale-de.cjs +510 -0
  149. package/dist/locale-de.d.cts +63 -0
  150. package/dist/locale-de.d.ts +63 -0
  151. package/dist/locale-de.js +496 -0
  152. package/dist/locale-de.js.map +1 -0
  153. package/dist/locale-es.cjs +510 -0
  154. package/dist/locale-es.d.cts +63 -0
  155. package/dist/locale-es.d.ts +63 -0
  156. package/dist/locale-es.js +496 -0
  157. package/dist/locale-es.js.map +1 -0
  158. package/dist/locale-fr.cjs +510 -0
  159. package/dist/locale-fr.d.cts +63 -0
  160. package/dist/locale-fr.d.ts +63 -0
  161. package/dist/locale-fr.js +496 -0
  162. package/dist/locale-fr.js.map +1 -0
  163. package/dist/locale-ja.cjs +505 -0
  164. package/dist/locale-ja.d.cts +63 -0
  165. package/dist/locale-ja.d.ts +63 -0
  166. package/dist/locale-ja.js +491 -0
  167. package/dist/locale-ja.js.map +1 -0
  168. package/dist/locale-pt.cjs +511 -0
  169. package/dist/locale-pt.d.cts +63 -0
  170. package/dist/locale-pt.d.ts +63 -0
  171. package/dist/locale-pt.js +497 -0
  172. package/dist/locale-pt.js.map +1 -0
  173. package/dist/locale-zh.cjs +505 -0
  174. package/dist/locale-zh.d.cts +63 -0
  175. package/dist/locale-zh.d.ts +63 -0
  176. package/dist/locale-zh.js +491 -0
  177. package/dist/locale-zh.js.map +1 -0
  178. package/dist/math-labels-DOf5KFxp.d.cts +24 -0
  179. package/dist/math-labels-DOf5KFxp.d.ts +24 -0
  180. package/dist/math.cjs +11 -10
  181. package/dist/math.d.cts +4 -24
  182. package/dist/math.d.ts +4 -24
  183. package/dist/math.js +9 -8
  184. package/dist/mention-labels-DIQy8LRT.d.cts +15 -0
  185. package/dist/mention-labels-DIQy8LRT.d.ts +15 -0
  186. package/dist/mention.cjs +11 -10
  187. package/dist/mention.d.cts +4 -15
  188. package/dist/mention.d.ts +4 -15
  189. package/dist/mention.js +9 -8
  190. package/dist/{view-controller-p-2x3J7Y.d.ts → node-reveal-BnFcnHhz.d.ts} +14 -14
  191. package/dist/{view-controller-BfVSNZYZ.d.cts → node-reveal-DY6bimv3.d.cts} +14 -14
  192. package/dist/palette-BrX-hEd-.d.cts +4 -0
  193. package/dist/palette-BrX-hEd-.d.ts +4 -0
  194. package/dist/presence-labels-DXVJLifl.d.cts +15 -0
  195. package/dist/presence-labels-DXVJLifl.d.ts +15 -0
  196. package/dist/presence.cjs +40 -42
  197. package/dist/presence.d.cts +4 -15
  198. package/dist/presence.d.ts +4 -15
  199. package/dist/presence.js +9 -11
  200. package/dist/presence.js.map +1 -1
  201. package/dist/source-labels-CeQDXsYZ.d.cts +9 -0
  202. package/dist/source-labels-CeQDXsYZ.d.ts +9 -0
  203. package/dist/source.cjs +11 -14
  204. package/dist/source.d.cts +5 -10
  205. package/dist/source.d.ts +5 -10
  206. package/dist/source.js +7 -10
  207. package/dist/source.js.map +1 -1
  208. package/dist/{stats-extension-oD6D1cYy.d.ts → stats-extension-D5HySF0a.d.cts} +4 -16
  209. package/dist/{stats-extension-XqOTvRaq.d.cts → stats-extension-Dmw0Ujxe.d.ts} +4 -16
  210. package/dist/stats-labels-W8gep51s.d.cts +15 -0
  211. package/dist/stats-labels-W8gep51s.d.ts +15 -0
  212. package/dist/stats.cjs +11 -10
  213. package/dist/stats.d.cts +3 -2
  214. package/dist/stats.d.ts +3 -2
  215. package/dist/stats.js +8 -7
  216. package/dist/styles.css +2 -2
  217. package/dist/table-extension-7LkLNK6Q.d.cts +15 -0
  218. package/dist/table-extension-DEpV0DnP.d.ts +15 -0
  219. package/dist/{table-extension-v6VB59bF.d.ts → table-labels-Dc-MzU3j.d.cts} +2 -14
  220. package/dist/{table-extension-BP47hHoz.d.cts → table-labels-Dc-MzU3j.d.ts} +2 -14
  221. package/dist/table.cjs +26 -25
  222. package/dist/table.d.cts +2 -1
  223. package/dist/table.d.ts +2 -1
  224. package/dist/table.js +7 -6
  225. package/dist/{to-html-1oD-CDle.d.cts → to-html-C19PbIzh.d.cts} +1 -0
  226. package/dist/{to-html-CLRBCX0I.d.ts → to-html-CogwrQer.d.ts} +1 -0
  227. package/dist/{to-markdown-CFPMbbiX.d.cts → to-markdown-C2w7Dzg8.d.cts} +1 -0
  228. package/dist/{to-markdown-BVOzhgVM.d.ts → to-markdown-DC1EUye5.d.ts} +1 -0
  229. package/dist/toc-extension-Dvh37bWQ.d.cts +15 -0
  230. package/dist/toc-extension-SVckrFmb.d.ts +15 -0
  231. package/dist/toc-labels-B2apPfXG.d.cts +14 -0
  232. package/dist/toc-labels-B2apPfXG.d.ts +14 -0
  233. package/dist/toc.cjs +10 -9
  234. package/dist/toc.d.cts +3 -2
  235. package/dist/toc.d.ts +3 -2
  236. package/dist/toc.js +7 -6
  237. package/dist/{types-DXF9p4qV.d.cts → types-Bzhq5AXj.d.cts} +1 -0
  238. package/dist/{types-BjLcmV00.d.ts → types-DMuuDBVh.d.ts} +1 -0
  239. package/dist/view.cjs +4 -4
  240. package/dist/view.js +2 -2
  241. package/dist/yjs.cjs +121 -121
  242. package/dist/yjs.d.cts +2 -2
  243. package/dist/yjs.d.ts +2 -2
  244. package/dist/yjs.js +5 -5
  245. package/package.json +71 -1
  246. package/dist/chunk-5JEQZIRI.js.map +0 -1
  247. package/dist/chunk-7GU25Z6L.js.map +0 -1
  248. package/dist/chunk-BJTOLCRR.js.map +0 -1
  249. package/dist/chunk-HSLOPJVT.js.map +0 -1
  250. package/dist/chunk-IJKGF6SC.js.map +0 -1
  251. package/dist/chunk-MNA7MBCH.js.map +0 -1
  252. package/dist/chunk-OJ4O6HWC.js.map +0 -1
  253. package/dist/chunk-PLUMKN4B.js.map +0 -1
  254. package/dist/chunk-PWUFFHAQ.js.map +0 -1
  255. package/dist/chunk-RQXJKJ5S.js.map +0 -1
  256. package/dist/chunk-UZS3TW75.js.map +0 -1
  257. package/dist/chunk-XFEDJEJF.js.map +0 -1
  258. package/dist/toc-extension-CI6jNOYl.d.cts +0 -26
  259. package/dist/toc-extension-CTHWjNJC.d.ts +0 -26
  260. /package/dist/{chunk-WZA5S5EJ.cjs → chunk-I7X7B3WD.cjs} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,6 +1,26 @@
1
1
  # Changelog
2
2
 
3
- Every release of `quoin-editor` is listed here, newest first, in the spirit of [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). From 1.0.0 the package follows [semantic versioning](https://semver.org/): a breaking change waits for the next major version. Until then, Quoin was 0.x and a minor version could contain breaking changes; those known at release were marked "Breaking". The 0.x sections are kept as they were released, "Docs" headings included. Moving from 0.4 to 1.0 is covered step by step in [MIGRATION.md](MIGRATION.md). Review finding ids such as F-17 are those of the 0.4.0 review in [#73](https://github.com/AmineSeboui/quoin-editor/issues/73).
3
+ Every release of `quoin-editor` is listed here, newest first, in the spirit of [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). From 1.0.0 the package follows [semantic versioning](https://semver.org/): a breaking change waits for the next major version. Until then, Quoin was 0.x and a minor version could contain breaking changes; those known at release were marked "Breaking". The 0.x sections are kept as they were released, "Docs" headings included. Moving from 0.4 to 1.0, and from 1.x to 2.0, is covered step by step in [MIGRATION.md](MIGRATION.md). Review finding ids such as F-17 are those of the 0.4.0 review in [#73](https://github.com/AmineSeboui/quoin-editor/issues/73).
4
+
5
+ ## 2.0.0 (2026-11-06)
6
+
7
+ ### Removed
8
+ - Seven names the view layer only ever calls itself leave `quoin-editor/view`, and with it `quoin-editor/headless` and `quoin-editor`. `BlockShell` and `SelectionSink`, with the `BlockShellProps` and `SelectionSinkProps` that typed them, are drawn by `EditorView` itself and register themselves with the controller, so a second copy of either takes the registration from the first. `VoidBlockFallback` is what a block shell already falls back to when no renderer draws a block type, so a renderer table that names it changes nothing. `defaultRenderers` held the three renderers a canvas already reaches for on its own; a renderer of your own draws its text with `EditableRegion`, which is what those three do. `isCrossSelection` answers whether a text selection spans blocks or regions, which is what painting and the arrow keys ask; a host that wants the answer reads `anchor` and `head` of the public `Selection`, and were it to earn a place it belongs beside `textSelection` in `quoin-editor/engine` rather than in the React entry. `BlockRenderProps` and `BlockRenderer` stay, so a renderer table still types. The five values among the seven were marked deprecated in 1.8.0. Removing a published name is breaking, so this lands in 2.0.0; [MIGRATION.md](MIGRATION.md) covers the move.
9
+
10
+ ### Fixed
11
+ - A menu no longer throws where `Element.prototype.scrollIntoView` is absent, which is jsdom with no stub and any webview that omits it. Ten calls guarded the element they had queried but not the method on it, so an element that existed without the method was called anyway and threw a `TypeError` that left Quoin for the host's tree, replacing the page wherever an error boundary caught it. The calls are in the slash and language menus, the code block's caret reveal, a block link's target, a table cell, a heading jumped to from the table of contents and vertical caret movement. Keeping the highlighted entry in view is the whole of what a host without the method now loses. Two `scrollTo` calls that restore a scroll position after a change carried the same shape and are guarded with them.
12
+ - An editable canvas warns in development when a definition of `CODE`, `IMAGE` or `FILE` replaces the preview that edits it and brings no editor of its own. Quoin edits those three in their preview rather than through an `editor`, so such a definition left a block the canvas drew and nobody could write in: no editable node, no language picker, and for an image or a file no toolbar and no way to fill the empty state. Nothing was logged, and the warning that would have caught it covered `MARKDOWN`, `CALLOUT` and `TOGGLE` only, whose consequence is milder since the canvas keeps drawing and typing into those. The migration guide said as much by naming only that first set, and now names both. Nothing about what the canvas draws changes.
13
+
14
+ ## 1.8.0 (2026-11-05)
15
+
16
+ ### Added
17
+ - Seven locale packs, one entry each, carrying every string the editor shows or speaks in that language: `quoin-editor/locale-ar` (Arabic), `quoin-editor/locale-de` (German), `quoin-editor/locale-es` (Spanish), `quoin-editor/locale-fr` (French), `quoin-editor/locale-ja` (Japanese), `quoin-editor/locale-pt` (Portuguese) and `quoin-editor/locale-zh` (Simplified Chinese). A pack covers the canvas and all fourteen feature label sets, so `frLabels` goes to the canvas and `frTableLabels` to the table, and it is data alone: no React, no side effects, about 5.5 KB gzip each, downloaded only by a host that imports one. A label that carries a count, a date, a number or a file size is written as its language writes it, through `Intl`, so Arabic gets its six plural forms and Japanese its one. Nothing is detected: the host picks the language, and a key a pack leaves out still falls back to English.
18
+ - `quoin-editor/headless` exports the types its own signatures already named, so a host can write them down rather than reach them through `Parameters` and `ReturnType`: `Announcer` with `Spoken`, `Engagement`, `FocusTarget`, `HostRegistry` with `HostEntry`, and `NodeReveal`, all reached through `ViewController`; `CaretPoint`, which `caretFromPoint` returns; and `BlockShellProps` and `SelectionSinkProps`, taken by the two components named after them. They reach `quoin-editor` too, which re-exports the headless entry. Nothing new runs and no type changes shape.
19
+
20
+ ### Changed
21
+ - The exports a host meets first carry a documentation comment, so an editor explains them on hover: `BlockCanvas`, `EditorView`, `useQuoinEditor`, `createEditor`, `toMarkdown`, `toHTML`, `toPlainText`, `fromMarkdown`, `PALETTE` and `englishLabels`. Nothing about their behaviour changes.
22
+ - `BlockShell`, `SelectionSink`, `defaultRenderers`, `VoidBlockFallback` and `isCrossSelection` are marked deprecated, in an editor and on the headless reference, and they go in 2.0.0. All five are internal to the view layer and a host has no use for them: `EditorView` draws the first two itself and a second copy of either takes the registration with the controller from the first, a renderer of your own draws a block's text with `EditableRegion` rather than reaching for `defaultRenderers`, a block shell already falls back to `VoidBlockFallback` when no renderer draws a block's type, and a host reads `anchor` and `head` of the public `Selection` to tell a selection that spans blocks or regions from one held inside a single region. `BlockRenderProps` and `BlockRenderer` stay public, so a renderer table still types. Every name is still exported and still does what it did.
23
+ - The comparison table weighs Quoin against Lexical and Plate as well, rechecked against each project's own pages and dated 2026-11-05. Lexical keeps an editor state it writes as JSON, draws no interface of its own, and ships neither comments nor translations: none of its forty packages covers either, and the comment plugin a reader finds is playground code. Plate keeps Slate JSON, converts markdown both ways, collaborates over Yjs and marks comments on text, and is MIT with a paid set of components behind its Notion-like template. A localization row goes in beside them: BlockNote ships 24 locales, Editor.js takes a dictionary the host writes, and Tiptap, Lexical and Plate ship none, since their interface strings live in the code a host copies into its own project. The comparison page reads in a wider column than the guide, because seven columns in a line of prose left each one too narrow to read.
4
24
 
5
25
  ## 1.7.0 (2026-11-04)
6
26
 
package/MIGRATION.md CHANGED
@@ -1,4 +1,8 @@
1
- # Migrating from 0.4 to 1.0
1
+ # Migrating Quoin
2
+
3
+ Two upgrades are written up here. [0.4 to 1.0](#migrating-from-04-to-10) is the rewrite; [1.x to 2.0](#migrating-from-1x-to-20) takes seven names the view layer only ever called itself out of the public entries.
4
+
5
+ ## Migrating from 0.4 to 1.0
2
6
 
3
7
  Quoin 1.0 keeps the 0.4 document format and the `BlockCanvas` contract, and replaces everything under them: the card and textarea of each block give way to an editor of Quoin's own that types into the page in place. Most hosts upgrade by changing the version and reading the list below; the [checklist](#checklist) at the end sums it up. Every change is listed in the [changelog](CHANGELOG.md).
4
8
 
@@ -8,13 +12,13 @@ npm install quoin-editor@^1
8
12
 
9
13
  React 18.2 or 19 and TypeScript 5.4 or later, as in 0.4.
10
14
 
11
- ## Data
15
+ ### Data
12
16
 
13
- ### No migration is needed
17
+ #### No migration is needed
14
18
 
15
19
  A document is still a `QuoinBlock[]` of `{ id, type, data }`, and every key of the five 0.4 block types keeps its name and meaning (`MarkdownBlockData`, `CalloutBlockData`, `CodeBlockData`, `ImageBlockData` and `FileBlockData`). Store and load the same arrays. 1.0 adds optional keys, listed [below](#new-optional-keys). Blocks nobody edits are handed back by `onChange` as the very objects you supplied, so a stored 0.4 document is never rewritten by being opened. The one exception is a block whose id repeats another's: it gets a fresh id.
16
20
 
17
- ### Multi-paragraph markdown blocks split on their first edit
21
+ #### Multi-paragraph markdown blocks split on their first edit
18
22
 
19
23
  A 0.4 `MARKDOWN` block could hold a whole page: several paragraphs, a list, a table. A 1.0 text block holds one line: a paragraph, a heading, a list item, a task or a quote. A 0.4 block of several lines is drawn line by line and kept as it is until someone edits it. On that first edit it is split: the first line keeps the block's id, the others take ids derived from it (`<id>-2`, `<id>-3` and on, skipping any id the document already has), a table becomes a `TABLE` block and an image alone on its line an `IMAGE` block. Each piece is written back as the canonical markdown of its line, so a host that diffs stored strings sees those blocks change once.
20
24
 
@@ -33,11 +37,11 @@ export const edited: QuoinBlock[] = [
33
37
  ];
34
38
  ```
35
39
 
36
- ### The `indent` field
40
+ #### The `indent` field
37
41
 
38
42
  `QuoinBlock` gains an optional `indent`, the nesting depth shared by every block type: a nested list item, or a block inside a toggle. It is written only when it is above 0. Code that copies, validates or stores blocks should keep it.
39
43
 
40
- ### New optional keys
44
+ #### New optional keys
41
45
 
42
46
  1.0 writes keys that 0.4 never wrote, each only once a writer uses what it holds:
43
47
 
@@ -50,21 +54,21 @@ export const edited: QuoinBlock[] = [
50
54
 
51
55
  The block itself may carry `indent`, and documents may hold the new block types `DIVIDER`, `TOGGLE`, `TABLE`, `EMBED`, `BOOKMARK`, `MATH` and `TOC`. A host whose schema rejects unknown keys or types would reject a paragraph that was aligned, or an image that was resized. Add the keys above as optional, or let unknown keys through: in zod, `.passthrough()` (zod 3) or `.loose()` (zod 4) on the `data` schema; in JSON Schema, `"additionalProperties": true`.
52
56
 
53
- ### Captions and callout bodies are inline markdown
57
+ #### Captions and callout bodies are inline markdown
54
58
 
55
59
  An image's `caption` and a callout's `body` were plain text in 0.4. In 1.0 they are typed in place and stored as inline markdown, like a paragraph. A 0.4 caption such as `A **sleepy** cat` now shows "sleepy" in bold, and `&amp;` shows as `&`. To keep a character literal in stored data, escape it with a backslash (`\*\*`); text typed in the canvas is escaped for you.
56
60
 
57
- ### Named HTML entities
61
+ #### Named HTML entities
58
62
 
59
63
  Numeric character references (`&#233;`, `&#xE9;`) are always decoded. Of the named entities, only the 23 most common are: `&amp;`, `&lt;`, `&gt;`, `&quot;`, `&apos;`, `&nbsp;`, `&copy;`, `&reg;`, `&trade;`, `&hellip;`, `&mdash;`, `&ndash;`, `&lsquo;`, `&rsquo;`, `&ldquo;`, `&rdquo;`, `&laquo;`, `&raquo;`, `&times;`, `&divide;`, `&deg;`, `&middot;` and `&bull;`. Any other, such as `&eacute;`, stays literal text, and is written back escaped (`\&eacute;`) once its block is edited. The full HTML5 table would add about 10 KB gzip to the engine. Workaround: replace other named entities with the character itself or its numeric reference before you import the markdown.
60
64
 
61
- ### Inline images in imported markdown
65
+ #### Inline images in imported markdown
62
66
 
63
67
  An image alone on its line becomes an `IMAGE` block. An image inside a paragraph, such as `see ![chart](chart.png) below`, stays literal text, since 1.0 has no inline image. Workaround: put each image on a line of its own before you import the markdown, or write an extension that adds an inline image atom.
64
68
 
65
- ## API
69
+ ### API
66
70
 
67
- ### `BlockCanvas`
71
+ #### `BlockCanvas`
68
72
 
69
73
  Every 0.4 prop keeps its name, and its meaning except as the changes below say: `blocks`, `onChange`, `onDelete`, `blockTypes`, `upload`, `resolveAssetUrl`, `onError`, `readOnly`, `className`, `id` and `aria-label`. New ones are optional:
70
74
 
@@ -83,11 +87,11 @@ Two behaviours changed:
83
87
  - **`onDelete` reports more.** Besides the block menu's Delete, it reports whole blocks removed by typing over a selected block or by pasting over selected blocks. A line that Backspace or Delete merges into its neighbour is still not reported. To restore a block, splice it back into `blocks` once they no longer hold it, as on a later click of an Undo toast, not inside the callback.
84
88
  - **A collaborative canvas is uncontrolled.** A canvas given `collaborationExtension` from `quoin-editor/yjs` takes its content from the shared document. Pass the starting blocks once and keep echoing `onChange`; while the shared document is connected, any other array handed to `blocks` is ignored, with a warning in development.
85
89
 
86
- ### `defineBlock`
90
+ #### `defineBlock`
87
91
 
88
92
  `defineBlock` takes the same definition. `icon` now accepts any component that takes a `className` and `aria-hidden`, not only a lucide one. A custom block shows its `preview` at rest, and its `editor` opens on a double click, its Edit button or Enter on the selected block; Escape closes it.
89
93
 
90
- A definition of a type the canvas types into in place, `MARKDOWN`, `CALLOUT` or `TOGGLE`, now draws only on a read-only canvas, and an editable canvas warns in development. To change how one of them looks while it is edited, give an extension a renderer for the type in `ui.renderers`:
94
+ A definition of a type the canvas types into in place, `MARKDOWN`, `CALLOUT` or `TOGGLE`, now draws only on a read-only canvas, and an editable canvas warns in development. `CODE`, `IMAGE` and `FILE` are edited in their `preview` rather than through an `editor`, so a definition that replaces the preview and brings no editor of its own leaves a block an editable canvas draws and nobody can write in; that warns in development too. To draw one of those three your own way on a read-only canvas and keep Quoin's editing where the canvas is editable, register your definition only while `readOnly` is set. To change how one of them looks while it is edited, give an extension a renderer for the type in `ui.renderers`:
91
95
 
92
96
  ```tsx
93
97
  import { BlockCanvas, EditableRegion, type BlockRenderer, type ExtensionUI, type QuoinBlock } from 'quoin-editor';
@@ -107,17 +111,17 @@ export function Editor({ blocks, onChange }: { blocks: QuoinBlock[]; onChange: (
107
111
  }
108
112
  ```
109
113
 
110
- ### `useQuoin`
114
+ #### `useQuoin`
111
115
 
112
116
  `QuoinConfig`, which `useQuoin()` returns to a custom block, gains `highlight` and the required `controller` and `labels`. `QuoinProvider` fills them in, so only a `QuoinConfig` built by hand, such as a test double for a custom block, needs `controller: null` and `labels: englishLabels` added.
113
117
 
114
- ### Entries
118
+ #### Entries
115
119
 
116
120
  - `quoin-editor/headless` now exports the view and the engine (`useQuoinEditor`, `EditorView`, `createEditor` and the rest), and none of the 0.4 helpers. The main entry re-exports all of it.
117
121
  - `formatShortcut` is exported from `quoin-editor` only. Import it from there instead of `quoin-editor/headless`.
118
122
  - `quoin-editor/engine` is the engine alone, with no React and no `'use client'` banner: import `toMarkdown`, `toHTML` and the other pure functions from it in a server component.
119
123
 
120
- ### Removed helpers
124
+ #### Removed helpers
121
125
 
122
126
  The 0.4 helpers worked on the text of a textarea. In 1.0 each is an engine command, run through the canvas's ref (`handle.current?.editor.commands.<name>(...)`) or a headless editor, or is no longer needed.
123
127
 
@@ -163,11 +167,11 @@ export function Editor({ blocks, onChange }: { blocks: QuoinBlock[]; onChange: (
163
167
  }
164
168
  ```
165
169
 
166
- ### Command names of the design spec
170
+ #### Command names of the design spec
167
171
 
168
172
  The 1.0 design spec named `mergeBackward`, `mergeForward` and `insertBlocks` as commands. They are not commands: `deleteBackward` at the start of a block joins it to the one before, `deleteForward` at its end joins the next one to it, and `insertBlockAfter` and `insertSlice` insert blocks. Use those names.
169
173
 
170
- ### Labels
174
+ #### Labels
171
175
 
172
176
  Strings come from `labels`. Two keys are functions rather than strings: `blockDeleted(n)` and `undoHint(shortcut)`. English callout tone names are written in lower case in the tone chip and its menu; give `calloutTones` to write them otherwise:
173
177
 
@@ -185,7 +189,7 @@ export function Editor({ blocks, onChange }: { blocks: QuoinBlock[]; onChange: (
185
189
  }
186
190
  ```
187
191
 
188
- ## Features that now need an import
192
+ ### Features that now need an import
189
193
 
190
194
  In 0.4 the slash palette's Table command wrote a markdown table into the block. In 1.0 a table is a `TABLE` block, drawn and edited by `quoin-editor/table`, which a page loads only when it imports it. Without it, a table, including one split out of a 0.4 block, is drawn as an unsupported block and its data is kept as it was. The one-line change is to pass the extension:
191
195
 
@@ -202,22 +206,22 @@ export function Editor({ blocks, onChange }: { blocks: QuoinBlock[]; onChange: (
202
206
 
203
207
  `catalogue()` from `quoin-editor/catalogue` adds every opt-in feature at once: tables, embeds, math, a table of contents, mentions, emoji, find and replace and stats.
204
208
 
205
- ## Styling
209
+ ### Styling
206
210
 
207
211
  - Every 0.4 `--quoin-` token keeps its name, and the stylesheet still sits in the one `quoin` cascade layer.
208
212
  - New tokens: `--quoin-selection`, the writer's text and background colours `--quoin-color-*` and `--quoin-bg-*`, `--quoin-comment`, `--quoin-comment-active` and `--quoin-comment-edge`, `--quoin-diff-inserted` and `--quoin-diff-deleted`, and `--quoin-link`. Each has a light and a dark default; a host that maps its own tokens onto Quoin's can map these too.
209
213
  - `color-scheme` is declared for light and dark.
210
214
  - The DOM and the class names inside the canvas are new. CSS that targeted 0.4 internals, such as a block card or its textarea, no longer matches; style through the tokens.
211
215
 
212
- ## React 18
216
+ ### React 18
213
217
 
214
218
  React 18.2 and 19 are both supported, as 0.4.1 made them. CI typechecks the packed package against both majors and renders the server entries in plain Node on each, and `BlockCanvas` forwards its ref, so a `QuoinEditorHandle` reaches you on React 18 too.
215
219
 
216
- ## The package
220
+ ### The package
217
221
 
218
222
  The tarball ships ESM and CommonJS builds of every entry, with their sourcemaps. The sourcemaps leave out `sourcesContent`: a debugger maps to the original file names and lines, but shows their text only where it can read the sources, such as from the repository at the release tag.
219
223
 
220
- ## Checklist
224
+ ### Checklist
221
225
 
222
226
  - [ ] Install `quoin-editor@^1`.
223
227
  - [ ] Keep `indent` and the new optional keys when you copy, validate or store blocks, and let a strict schema accept them.
@@ -231,3 +235,113 @@ The tarball ships ESM and CommonJS builds of every entry, with their sourcemaps.
231
235
  - [ ] Pass `tableExtension()` (or `catalogue()`) if your documents hold tables.
232
236
  - [ ] Map the new tokens if you map Quoin's tokens to your own, and drop CSS that targeted 0.4 internals.
233
237
  - [ ] Treat a canvas with a collaboration extension as uncontrolled.
238
+
239
+ ## Migrating from 1.x to 2.0
240
+
241
+ Quoin 2.0 keeps the document format, the props of every component and every command of 1.x. What changes is the published surface: seven names that only the view layer ever called itself stop being exported from `quoin-editor/view`, and so from `quoin-editor/headless` and `quoin-editor` with it. Five of them have carried a deprecation notice since 1.8.0, shown by an editor on hover and on the [headless reference](https://amineseboui.github.io/quoin-editor/docs/headless-reference/); the other two are the prop types of the two components, and go with them. Nothing else is removed and nothing behaves differently, so a host that imports none of the seven upgrades by changing the version.
242
+
243
+ ```bash
244
+ npm install quoin-editor@^2
245
+ ```
246
+
247
+ React 18.2 or 19 and TypeScript 5.4 or later, as in 1.x.
248
+
249
+ ### Exports that are gone
250
+
251
+ | 1.x | 2.0 | Notes |
252
+ | --- | --- | --- |
253
+ | `defaultRenderers` | `EditableRegion` | The one removal with a cost, [below](#defaultrenderers). |
254
+ | `BlockShell`, `BlockShellProps` | Nothing | [`EditorView` draws a shell for every block already.](#blockshell-and-selectionsink) |
255
+ | `SelectionSink`, `SelectionSinkProps` | Nothing | [`EditorView` draws one already.](#blockshell-and-selectionsink) |
256
+ | `VoidBlockFallback` | Nothing | [A block shell falls back to it on its own.](#voidblockfallback) |
257
+ | `isCrossSelection` | `Selection` | [Four lines over `anchor` and `head`.](#iscrossselection) |
258
+
259
+ `BlockRenderProps` and `BlockRenderer` stay public, so a renderer table still types.
260
+
261
+ #### `defaultRenderers`
262
+
263
+ A paragraph, a heading, a bullet, a numbered item, a task and a quote are all `MARKDOWN` blocks drawn by one renderer. A host that wanted its own quote therefore had to name `MARKDOWN` in its table, and could hand every other kind back to the default:
264
+
265
+ ```jsx
266
+ import { defaultRenderers, EditableRegion } from 'quoin-editor';
267
+
268
+ const Markdown = defaultRenderers.MARKDOWN;
269
+
270
+ export const renderers = {
271
+ MARKDOWN: (props) =>
272
+ props.block.attrs.kind === 'quote' ? (
273
+ <figure className="pull-quote">
274
+ <EditableRegion block={props.block.id} region="text" label="Quote" />
275
+ </figure>
276
+ ) : (
277
+ <Markdown {...props} />
278
+ ),
279
+ };
280
+ ```
281
+
282
+ In 2.0 that renderer owns every kind, and draws each one's text with `EditableRegion`, which is what the default renderer does inside:
283
+
284
+ ```tsx
285
+ import { EditableRegion, type BlockRenderer } from 'quoin-editor';
286
+
287
+ const LABELS: Record<string, string> = {
288
+ heading: 'Heading',
289
+ bullet: 'Bullet list item',
290
+ ordered: 'Numbered list item',
291
+ task: 'Task',
292
+ quote: 'Quote',
293
+ };
294
+
295
+ export const renderers: Record<string, BlockRenderer> = {
296
+ MARKDOWN: ({ block }) => {
297
+ const kind = typeof block.attrs.kind === 'string' ? block.attrs.kind : '';
298
+ const text = <EditableRegion block={block.id} region="text" label={LABELS[kind] ?? 'Paragraph'} />;
299
+ if (kind === 'quote') return <figure className="pull-quote">{text}</figure>;
300
+ if (kind === 'heading') {
301
+ const level = typeof block.attrs.level === 'number' ? block.attrs.level : 1;
302
+ return <div role="heading" aria-level={level} className="quoin-heading">{text}</div>;
303
+ }
304
+ if (kind === 'bullet') {
305
+ return (
306
+ <div className="quoin-list-item" data-list="bullet">
307
+ <span className="quoin-marker" aria-hidden="true">{'•'}</span>
308
+ {text}
309
+ </div>
310
+ );
311
+ }
312
+ return text;
313
+ },
314
+ };
315
+ ```
316
+
317
+ `EditableRegion` takes the block's id, the name of the region to draw (`text` for every markdown kind) and an accessible name, and is the same editable host the default renderer mounts, so the caret, the selection and the decorations keep working.
318
+
319
+ Two kinds ask for more than markup, and the sample above leaves both drawn as plain text. A numbered item shows its running number, which counts the ordered blocks above it at the same indent; a task shows a checkbox that runs the `toggleTask` command against its own block. Neither the counter nor the checkbox is public, so a host that overrides `MARKDOWN` and still wants those two drawn the way Quoin draws them writes them itself. A host that only wants to restyle a kind has a cheaper way out: leave `MARKDOWN` out of the renderer table and style the markup the default renderer already writes, which carries `data-kind` on the block and the `quoin-heading`, `quoin-list-item`, `quoin-task` and `quoin-quote` class names.
320
+
321
+ #### `BlockShell` and `SelectionSink`
322
+
323
+ Neither was ever something to render. `EditorView` draws one `SelectionSink` and one `BlockShell` per block, and each registers itself with the `ViewController` on mount (`controller.registerSink`, `controller.registerBlockElement`). A second copy takes that registration from the first, so a host that rendered either lost the selection or the block it meant to help. Drop the import; the editor already draws both. `BlockShellProps` and `SelectionSinkProps` named their props and go with them.
324
+
325
+ #### `VoidBlockFallback`
326
+
327
+ A block shell falls back to it already when no renderer in the table draws a block's type, so a table that named it only restated the default. Drop the entry and the import.
328
+
329
+ #### `isCrossSelection`
330
+
331
+ It asked whether a text selection spans blocks or regions, which is what painting and the arrow keys ask. It reads `anchor` and `head` of the public `Selection`, so a host that wants the answer writes it:
332
+
333
+ ```ts
334
+ import type { Selection } from 'quoin-editor/engine';
335
+
336
+ export const spansBlocks = (selection: Selection | null): boolean =>
337
+ selection?.kind === 'text' &&
338
+ (selection.anchor.block !== selection.head.block || selection.anchor.region !== selection.head.region);
339
+ ```
340
+
341
+ ### 2.0 checklist
342
+
343
+ - [ ] Install `quoin-editor@^2`.
344
+ - [ ] Drop any `BlockShell` or `SelectionSink` you render, and the `BlockShellProps` and `SelectionSinkProps` that typed them.
345
+ - [ ] Drop any `VoidBlockFallback` entry from a renderer table.
346
+ - [ ] Replace `isCrossSelection` with the predicate above.
347
+ - [ ] Draw every `MARKDOWN` kind yourself if your table names `MARKDOWN`, or leave `MARKDOWN` out and style the default markup instead.
package/README.md CHANGED
@@ -69,23 +69,25 @@ What `onChange` hands you, and what you store:
69
69
  | `quoin-editor/comments` | Comment threads anchored to text, kept in your store |
70
70
  | `quoin-editor/ai` | Your model's answers streamed into the document; Quoin never calls a model |
71
71
  | `quoin-editor/view`, `/diff`, `/docx` | Server rendering with no hooks, a diff of two versions, a Word export |
72
+ | `quoin-editor/locale-ar`, `/locale-de`, `/locale-es`, `/locale-fr`, `/locale-ja`, `/locale-pt`, `/locale-zh` | Every string the editor shows, in that language |
72
73
  | `quoin-editor/headless`, `/engine` | The editor without the canvas UI, and the engine alone with no React |
73
74
 
74
75
  ## Why Quoin
75
76
 
76
77
  Each competitor cell links to the page that shows it; the Tiptap UI cell also draws on the review in [#73](https://github.com/AmineSeboui/quoin-editor/issues/73) (2026-10-12).
77
78
 
78
- | Feature | Quoin 1.7 | Tiptap 3 | BlockNote | Editor.js |
79
- | --- | --- | --- | --- | --- |
80
- | Stored document | Block array, markdown per text block | [JSON, or HTML][tt-store] | [Block JSON][bn-formats] | [JSON][ej-repo] |
81
- | Markdown | The storage format | [`@tiptap/markdown`, an early release][tt-md] | [Export and import, documented as lossy][bn-formats] | [Community plugin][ej-awesome] |
82
- | Notion-style UI | Included, MIT | [MIT menus and drag handle][tt-review]; [Notion-like template on a paid plan][tt-notion] | [Included][bn-home] | [Toolbox and inline toolbar][ej-repo] |
83
- | Licence | MIT | [MIT core, paid Pro extensions][tt-repo] | [MPL-2.0, `xl-*` packages GPL-3.0 or commercial][bn-repo] | [Apache-2.0][ej-repo] |
84
- | Real-time collaboration | Yjs, opt-in entry | [Yjs][tt-collab] | [Yjs][bn-collab] | [On its roadmap, in progress][ej-repo] |
85
- | Comments | Opt-in, your store | [Paid plan][tt-comments] | [Built in, on Yjs or a thread store][bn-comments] | [Community plugin][ej-awesome] |
86
- | Framework | React | [React, Vue, Svelte, plain JavaScript and more][tt-install] | [React, or vanilla JS with your own UI][bn-vanilla] | [Plain JavaScript][ej-start] |
79
+ | Feature | Quoin 2.0 | Tiptap 3 | BlockNote | Editor.js | Lexical | Plate |
80
+ | --- | --- | --- | --- | --- | --- | --- |
81
+ | Stored document | Block array, markdown per text block | [JSON, or HTML][tt-store] | [Block JSON][bn-formats] | [JSON][ej-repo] | [Editor state, as JSON][lx-intro] | [Slate JSON][pl-docs] |
82
+ | Markdown | The storage format | [`@tiptap/markdown`, an early release][tt-md] | [Export and import, documented as lossy][bn-formats] | [Community plugin][ej-awesome] | [`@lexical/markdown`, in and out][lx-md] | [`@platejs/markdown`, in and out][pl-md] |
83
+ | Notion-style UI | Included, MIT | [MIT menus and drag handle][tt-review]; [Notion-like template on a paid plan][tt-notion] | [Included][bn-home] | [Toolbox and inline toolbar][ej-repo] | [None; you draw it][lx-intro] | [MIT components][pl-repo]; [Notion-like set on a paid licence][pl-pro] |
84
+ | Licence | MIT | [MIT core, paid Pro extensions][tt-repo] | [MPL-2.0, `xl-*` packages GPL-3.0 or commercial][bn-repo] | [Apache-2.0][ej-repo] | [MIT][lx-repo] | [MIT, with paid Plus components][pl-pro] |
85
+ | Real-time collaboration | Yjs, opt-in entry | [Yjs][tt-collab] | [Yjs][bn-collab] | [On its roadmap, in progress][ej-repo] | [Yjs][lx-collab] | [Yjs][pl-yjs] |
86
+ | Comments | Opt-in, your store | [Paid plan][tt-comments] | [Built in, on Yjs or a thread store][bn-comments] | [Community plugin][ej-awesome] | [None shipped; a playground example][lx-repo] | [`@platejs/comment`, as text marks][pl-comment] |
87
+ | Localization | Seven packs, every string | [None shipped; the UI is yours][tt-ui] | [24 locales built in][bn-i18n] | [An i18n API; you bring the words][ej-i18n] | [None shipped; it draws no UI][lx-repo] | [None shipped; the UI is yours][pl-docs] |
88
+ | Framework | React | [React, Vue, Svelte, plain JavaScript and more][tt-install] | [React, or vanilla JS with your own UI][bn-vanilla] | [Plain JavaScript][ej-start] | [Any, with React bindings][lx-repo] | [React][pl-repo] |
87
89
 
88
- Sources, checked on 2026-11-03: Tiptap [persistence][tt-store], [markdown][tt-md], [Notion-like template][tt-notion], [repository][tt-repo], [collaboration][tt-collab], [comments][tt-comments], [install][tt-install]; BlockNote [home][bn-home], [formats][bn-formats], [repository][bn-repo], [collaboration][bn-collab], [comments][bn-comments], [getting started][bn-start], [vanilla JS][bn-vanilla]; Editor.js [home][ej-home], [repository][ej-repo], [getting started][ej-start], [awesome-editorjs][ej-awesome].
90
+ Sources, checked on 2026-11-05: Tiptap [persistence][tt-store], [markdown][tt-md], [Notion-like template][tt-notion], [repository][tt-repo], [collaboration][tt-collab], [comments][tt-comments], [install][tt-install], [UI components][tt-ui]; BlockNote [home][bn-home], [formats][bn-formats], [repository][bn-repo], [collaboration][bn-collab], [comments][bn-comments], [getting started][bn-start], [vanilla JS][bn-vanilla], [localization][bn-i18n]; Editor.js [home][ej-home], [repository][ej-repo], [getting started][ej-start], [awesome-editorjs][ej-awesome], [i18n][ej-i18n]; Lexical [docs][lx-intro], [repository][lx-repo], [markdown][lx-md], [collaboration][lx-collab]; Plate [docs][pl-docs], [repository][pl-repo], [markdown][pl-md], [collaboration][pl-yjs], [comments][pl-comment], [Plate Plus][pl-pro].
89
91
 
90
92
  [tt-store]: https://tiptap.dev/docs/editor/core-concepts/persistence
91
93
  [tt-md]: https://tiptap.dev/docs/editor/markdown
@@ -106,6 +108,19 @@ Sources, checked on 2026-11-03: Tiptap [persistence][tt-store], [markdown][tt-md
106
108
  [ej-repo]: https://github.com/codex-team/editor.js
107
109
  [ej-start]: https://editorjs.io/getting-started
108
110
  [ej-awesome]: https://github.com/editor-js/awesome-editorjs
111
+ [tt-ui]: https://tiptap.dev/docs/ui-components/components/overview
112
+ [bn-i18n]: https://www.blocknotejs.org/docs/features/localization
113
+ [ej-i18n]: https://editorjs.io/i18n/
114
+ [lx-intro]: https://lexical.dev/docs/intro
115
+ [lx-repo]: https://github.com/facebook/lexical
116
+ [lx-md]: https://lexical.dev/docs/packages/lexical-markdown
117
+ [lx-collab]: https://lexical.dev/docs/collaboration/react
118
+ [pl-docs]: https://platejs.org/docs
119
+ [pl-repo]: https://github.com/udecode/plate
120
+ [pl-md]: https://platejs.org/docs/markdown
121
+ [pl-yjs]: https://platejs.org/docs/yjs
122
+ [pl-comment]: https://platejs.org/docs/comment
123
+ [pl-pro]: https://pro.platejs.org
109
124
 
110
125
  ## Learn more
111
126
 
@@ -116,7 +131,13 @@ Sources, checked on 2026-11-03: Tiptap [persistence][tt-store], [markdown][tt-md
116
131
  - [Collaboration](https://amineseboui.github.io/quoin-editor/docs/collaboration/), [comments](https://amineseboui.github.io/quoin-editor/docs/comments/) and [AI](https://amineseboui.github.io/quoin-editor/docs/ai/)
117
132
  - [Server rendering](https://amineseboui.github.io/quoin-editor/docs/server-rendering/): read-only, `QuoinView`, `QuoinDiff`, `toDocx`
118
133
  - [Accessibility](https://amineseboui.github.io/quoin-editor/docs/accessibility/): what each test suite checks, and what it does not yet
119
- - [FAQ](https://amineseboui.github.io/quoin-editor/docs/faq/): the package name, stability, React versions, and [upgrading from 0.4](MIGRATION.md)
134
+ - [FAQ](https://amineseboui.github.io/quoin-editor/docs/faq/): the package name, stability, React versions, and [upgrading to 2.0](MIGRATION.md#migrating-from-1x-to-20)
135
+
136
+ ## Sponsors
137
+
138
+ Quoin is MIT, with no paid tier and nothing held back behind a licence. It is built and maintained by one person. [Sponsoring it](https://github.com/sponsors/AmineSeboui) pays for the time that keeps issues answered and releases going out.
139
+
140
+ No sponsors yet. Yours would be the first name here.
120
141
 
121
142
  ## The name
122
143
 
@@ -0,0 +1,53 @@
1
+ type AiAction = 'continue' | 'improve' | 'fix' | 'shorten' | 'lengthen' | 'simplify' | 'tone' | 'translate' | 'summarize' | 'explain' | 'custom';
2
+ /** What the host is asked: `selection` and `context` are markdown, `language` the canvas's `lang` when it has one. */
3
+ type AiRequest = {
4
+ action: AiAction;
5
+ prompt?: string;
6
+ selection: string;
7
+ context: string;
8
+ language?: string;
9
+ };
10
+ /** The host's model, answering a request as markdown in chunks, and stopping when `signal` aborts. */
11
+ type AiStream = (request: AiRequest, signal: AbortSignal) => AsyncIterable<string>;
12
+
13
+ /** Every string AI actions show or speak. */
14
+ type AiLabels = {
15
+ /** The toolbar button, block menu item and slash command that open the AI menu. */
16
+ askAi: string;
17
+ /** What the slash command for the AI menu does. */
18
+ askAiDescription: string;
19
+ /** The name of each action in the AI menu; `custom` names the slash command group's own entry. */
20
+ actions: Record<AiAction, string>;
21
+ /** The name of the list of actions. */
22
+ menu: string;
23
+ /** The field the writer types a request in. */
24
+ prompt: string;
25
+ /** The field once Change tone is chosen. */
26
+ tonePrompt: string;
27
+ /** The field once Translate is chosen. */
28
+ translatePrompt: string;
29
+ /** The choice that sends what was typed as a request of its own. */
30
+ ask(prompt: string): string;
31
+ /** The name of the bar under the answer. */
32
+ response: string;
33
+ writing: string;
34
+ stopped: string;
35
+ stop: string;
36
+ accept: string;
37
+ discard: string;
38
+ retry: string;
39
+ /** The field for a further request about the answer. */
40
+ followUp: string;
41
+ /** Shown when the host's answer failed. */
42
+ failed: string;
43
+ /** Said once an answer is complete, given its text. */
44
+ written(text: string): string;
45
+ /** Said once an answer is stopped, given what it wrote. */
46
+ halted(text: string): string;
47
+ /** Shown when the text the answer was for was deleted meanwhile. */
48
+ targetGone: string;
49
+ };
50
+ declare const englishAiLabels: AiLabels;
51
+
52
+ export { englishAiLabels as e };
53
+ export type { AiStream as A, AiLabels as a, AiAction as b, AiRequest as c };
@@ -0,0 +1,53 @@
1
+ type AiAction = 'continue' | 'improve' | 'fix' | 'shorten' | 'lengthen' | 'simplify' | 'tone' | 'translate' | 'summarize' | 'explain' | 'custom';
2
+ /** What the host is asked: `selection` and `context` are markdown, `language` the canvas's `lang` when it has one. */
3
+ type AiRequest = {
4
+ action: AiAction;
5
+ prompt?: string;
6
+ selection: string;
7
+ context: string;
8
+ language?: string;
9
+ };
10
+ /** The host's model, answering a request as markdown in chunks, and stopping when `signal` aborts. */
11
+ type AiStream = (request: AiRequest, signal: AbortSignal) => AsyncIterable<string>;
12
+
13
+ /** Every string AI actions show or speak. */
14
+ type AiLabels = {
15
+ /** The toolbar button, block menu item and slash command that open the AI menu. */
16
+ askAi: string;
17
+ /** What the slash command for the AI menu does. */
18
+ askAiDescription: string;
19
+ /** The name of each action in the AI menu; `custom` names the slash command group's own entry. */
20
+ actions: Record<AiAction, string>;
21
+ /** The name of the list of actions. */
22
+ menu: string;
23
+ /** The field the writer types a request in. */
24
+ prompt: string;
25
+ /** The field once Change tone is chosen. */
26
+ tonePrompt: string;
27
+ /** The field once Translate is chosen. */
28
+ translatePrompt: string;
29
+ /** The choice that sends what was typed as a request of its own. */
30
+ ask(prompt: string): string;
31
+ /** The name of the bar under the answer. */
32
+ response: string;
33
+ writing: string;
34
+ stopped: string;
35
+ stop: string;
36
+ accept: string;
37
+ discard: string;
38
+ retry: string;
39
+ /** The field for a further request about the answer. */
40
+ followUp: string;
41
+ /** Shown when the host's answer failed. */
42
+ failed: string;
43
+ /** Said once an answer is complete, given its text. */
44
+ written(text: string): string;
45
+ /** Said once an answer is stopped, given what it wrote. */
46
+ halted(text: string): string;
47
+ /** Shown when the text the answer was for was deleted meanwhile. */
48
+ targetGone: string;
49
+ };
50
+ declare const englishAiLabels: AiLabels;
51
+
52
+ export { englishAiLabels as e };
53
+ export type { AiStream as A, AiLabels as a, AiAction as b, AiRequest as c };