@dou-ui/editor 0.0.0-stage → 0.1.0-alpha.1

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 (324) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -2
  3. package/dist/catalog.js +18 -0
  4. package/dist/catalog.js.map +1 -0
  5. package/dist/code/code-editor-block.js +6 -0
  6. package/dist/code/code-editor-block.js.map +1 -0
  7. package/dist/code/code-editor-block.vue_vue_type_script_setup_true_lang.js +116 -0
  8. package/dist/code/code-editor-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
  9. package/dist/code/index.js +69 -0
  10. package/dist/code/index.js.map +1 -0
  11. package/dist/code/languages.js +51 -0
  12. package/dist/code/languages.js.map +1 -0
  13. package/dist/code/view.js +255 -0
  14. package/dist/code/view.js.map +1 -0
  15. package/dist/collaboration/binding.js +381 -0
  16. package/dist/collaboration/binding.js.map +1 -0
  17. package/dist/collaboration/cursors.js +150 -0
  18. package/dist/collaboration/cursors.js.map +1 -0
  19. package/dist/collaboration/index.js +108 -0
  20. package/dist/collaboration/index.js.map +1 -0
  21. package/dist/collaboration/order.js +88 -0
  22. package/dist/collaboration/order.js.map +1 -0
  23. package/dist/collaboration/table.js +234 -0
  24. package/dist/collaboration/table.js.map +1 -0
  25. package/dist/collaboration/text.js +91 -0
  26. package/dist/collaboration/text.js.map +1 -0
  27. package/dist/components/block-handle.js +6 -0
  28. package/dist/components/block-handle.js.map +1 -0
  29. package/dist/components/block-handle.vue_vue_type_script_setup_true_lang.js +474 -0
  30. package/dist/components/block-handle.vue_vue_type_script_setup_true_lang.js.map +1 -0
  31. package/dist/components/bubble-toolbar.js +6 -0
  32. package/dist/components/bubble-toolbar.js.map +1 -0
  33. package/dist/components/bubble-toolbar.vue_vue_type_script_setup_true_lang.js +381 -0
  34. package/dist/components/bubble-toolbar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  35. package/dist/components/comment-button.js +6 -0
  36. package/dist/components/comment-button.js.map +1 -0
  37. package/dist/components/comment-button.vue_vue_type_script_setup_true_lang.js +83 -0
  38. package/dist/components/comment-button.vue_vue_type_script_setup_true_lang.js.map +1 -0
  39. package/dist/components/comment-card.js +6 -0
  40. package/dist/components/comment-card.js.map +1 -0
  41. package/dist/components/comment-card.vue_vue_type_script_setup_true_lang.js +104 -0
  42. package/dist/components/comment-card.vue_vue_type_script_setup_true_lang.js.map +1 -0
  43. package/dist/components/menu-escape.js +25 -0
  44. package/dist/components/menu-escape.js.map +1 -0
  45. package/dist/components/search-bar.js +6 -0
  46. package/dist/components/search-bar.js.map +1 -0
  47. package/dist/components/search-bar.vue_vue_type_script_setup_true_lang.js +192 -0
  48. package/dist/components/search-bar.vue_vue_type_script_setup_true_lang.js.map +1 -0
  49. package/dist/components/slash-menu.js +6 -0
  50. package/dist/components/slash-menu.js.map +1 -0
  51. package/dist/components/slash-menu.vue_vue_type_script_setup_true_lang.js +164 -0
  52. package/dist/components/slash-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  53. package/dist/core/align.js +94 -0
  54. package/dist/core/align.js.map +1 -0
  55. package/dist/core/block-selection.js +155 -0
  56. package/dist/core/block-selection.js.map +1 -0
  57. package/dist/core/blocks.js +132 -0
  58. package/dist/core/blocks.js.map +1 -0
  59. package/dist/core/changes.js +85 -0
  60. package/dist/core/changes.js.map +1 -0
  61. package/dist/core/colors.js +16 -0
  62. package/dist/core/colors.js.map +1 -0
  63. package/dist/core/commands.js +379 -0
  64. package/dist/core/commands.js.map +1 -0
  65. package/dist/core/comments.js +186 -0
  66. package/dist/core/comments.js.map +1 -0
  67. package/dist/core/compare.js +106 -0
  68. package/dist/core/compare.js.map +1 -0
  69. package/dist/core/convert.js +28 -0
  70. package/dist/core/convert.js.map +1 -0
  71. package/dist/core/diff.js +141 -0
  72. package/dist/core/diff.js.map +1 -0
  73. package/dist/core/editor.js +138 -0
  74. package/dist/core/editor.js.map +1 -0
  75. package/dist/core/extension.js +99 -0
  76. package/dist/core/extension.js.map +1 -0
  77. package/dist/core/file-drop.js +56 -0
  78. package/dist/core/file-drop.js.map +1 -0
  79. package/dist/core/highlight.js +120 -0
  80. package/dist/core/highlight.js.map +1 -0
  81. package/dist/core/input-rules.js +61 -0
  82. package/dist/core/input-rules.js.map +1 -0
  83. package/dist/core/keymap.js +38 -0
  84. package/dist/core/keymap.js.map +1 -0
  85. package/dist/core/model.js +196 -0
  86. package/dist/core/model.js.map +1 -0
  87. package/dist/core/output.js +263 -0
  88. package/dist/core/output.js.map +1 -0
  89. package/dist/core/plugins.js +102 -0
  90. package/dist/core/plugins.js.map +1 -0
  91. package/dist/core/prepare.js +102 -0
  92. package/dist/core/prepare.js.map +1 -0
  93. package/dist/core/schema.js +158 -0
  94. package/dist/core/schema.js.map +1 -0
  95. package/dist/core/search.js +197 -0
  96. package/dist/core/search.js.map +1 -0
  97. package/dist/core/selection.js +16 -0
  98. package/dist/core/selection.js.map +1 -0
  99. package/dist/core/slash.js +83 -0
  100. package/dist/core/slash.js.map +1 -0
  101. package/dist/core/transaction.js +311 -0
  102. package/dist/core/transaction.js.map +1 -0
  103. package/dist/define-block.js +177 -0
  104. package/dist/define-block.js.map +1 -0
  105. package/dist/document.js +157 -0
  106. package/dist/document.js.map +1 -0
  107. package/dist/dom.js +48 -0
  108. package/dist/dom.js.map +1 -0
  109. package/dist/editor.js +33 -0
  110. package/dist/editor.js.map +1 -0
  111. package/dist/editor.vue_vue_type_script_setup_true_lang.js +564 -0
  112. package/dist/editor.vue_vue_type_script_setup_true_lang.js.map +1 -0
  113. package/dist/editor2.js +6 -0
  114. package/dist/editor2.js.map +1 -0
  115. package/dist/extensions/align/align-menu.js +6 -0
  116. package/dist/extensions/align/align-menu.js.map +1 -0
  117. package/dist/extensions/align/align-menu.vue_vue_type_script_setup_true_lang.js +82 -0
  118. package/dist/extensions/align/align-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  119. package/dist/extensions/align/align-sub.js +6 -0
  120. package/dist/extensions/align/align-sub.js.map +1 -0
  121. package/dist/extensions/align/align-sub.vue_vue_type_script_setup_true_lang.js +57 -0
  122. package/dist/extensions/align/align-sub.vue_vue_type_script_setup_true_lang.js.map +1 -0
  123. package/dist/extensions/align/commands.js +40 -0
  124. package/dist/extensions/align/commands.js.map +1 -0
  125. package/dist/extensions/align/index.js +27 -0
  126. package/dist/extensions/align/index.js.map +1 -0
  127. package/dist/extensions/attachment/attachment-block.js +6 -0
  128. package/dist/extensions/attachment/attachment-block.js.map +1 -0
  129. package/dist/extensions/attachment/attachment-block.vue_vue_type_script_setup_true_lang.js +119 -0
  130. package/dist/extensions/attachment/attachment-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
  131. package/dist/extensions/attachment/files.js +66 -0
  132. package/dist/extensions/attachment/files.js.map +1 -0
  133. package/dist/extensions/attachment/index.js +132 -0
  134. package/dist/extensions/attachment/index.js.map +1 -0
  135. package/dist/extensions/blocks.js +170 -0
  136. package/dist/extensions/blocks.js.map +1 -0
  137. package/dist/extensions/callout/callout-block.js +6 -0
  138. package/dist/extensions/callout/callout-block.js.map +1 -0
  139. package/dist/extensions/callout/callout-block.vue_vue_type_script_setup_true_lang.js +71 -0
  140. package/dist/extensions/callout/callout-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
  141. package/dist/extensions/callout/html.js +61 -0
  142. package/dist/extensions/callout/html.js.map +1 -0
  143. package/dist/extensions/callout/index.js +52 -0
  144. package/dist/extensions/callout/index.js.map +1 -0
  145. package/dist/extensions/callout/tones.js +12 -0
  146. package/dist/extensions/callout/tones.js.map +1 -0
  147. package/dist/extensions/code-block/code-block-view.js +6 -0
  148. package/dist/extensions/code-block/code-block-view.js.map +1 -0
  149. package/dist/extensions/code-block/code-block-view.vue_vue_type_script_setup_true_lang.js +87 -0
  150. package/dist/extensions/code-block/code-block-view.vue_vue_type_script_setup_true_lang.js.map +1 -0
  151. package/dist/extensions/code-block/index.js +98 -0
  152. package/dist/extensions/code-block/index.js.map +1 -0
  153. package/dist/extensions/code-block/languages.js +116 -0
  154. package/dist/extensions/code-block/languages.js.map +1 -0
  155. package/dist/extensions/colors/color-menu.js +6 -0
  156. package/dist/extensions/colors/color-menu.js.map +1 -0
  157. package/dist/extensions/colors/color-menu.vue_vue_type_script_setup_true_lang.js +113 -0
  158. package/dist/extensions/colors/color-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  159. package/dist/extensions/colors/index.js +49 -0
  160. package/dist/extensions/colors/index.js.map +1 -0
  161. package/dist/extensions/colors/palette.js +26 -0
  162. package/dist/extensions/colors/palette.js.map +1 -0
  163. package/dist/extensions/editor-kit.js +35 -0
  164. package/dist/extensions/editor-kit.js.map +1 -0
  165. package/dist/extensions/embed/embed-block.js +6 -0
  166. package/dist/extensions/embed/embed-block.js.map +1 -0
  167. package/dist/extensions/embed/embed-block.vue_vue_type_script_setup_true_lang.js +210 -0
  168. package/dist/extensions/embed/embed-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
  169. package/dist/extensions/embed/index.js +118 -0
  170. package/dist/extensions/embed/index.js.map +1 -0
  171. package/dist/extensions/embed/targets.js +79 -0
  172. package/dist/extensions/embed/targets.js.map +1 -0
  173. package/dist/extensions/formatting.js +153 -0
  174. package/dist/extensions/formatting.js.map +1 -0
  175. package/dist/extensions/image/image-block.js +6 -0
  176. package/dist/extensions/image/image-block.js.map +1 -0
  177. package/dist/extensions/image/image-block.vue_vue_type_script_setup_true_lang.js +206 -0
  178. package/dist/extensions/image/image-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
  179. package/dist/extensions/image/index.js +125 -0
  180. package/dist/extensions/image/index.js.map +1 -0
  181. package/dist/extensions/image/uploads.js +96 -0
  182. package/dist/extensions/image/uploads.js.map +1 -0
  183. package/dist/extensions/list-html.js +58 -0
  184. package/dist/extensions/list-html.js.map +1 -0
  185. package/dist/extensions/lists.js +182 -0
  186. package/dist/extensions/lists.js.map +1 -0
  187. package/dist/extensions/mention/index.js +56 -0
  188. package/dist/extensions/mention/index.js.map +1 -0
  189. package/dist/extensions/mention/mention-menu.js +6 -0
  190. package/dist/extensions/mention/mention-menu.js.map +1 -0
  191. package/dist/extensions/mention/mention-menu.vue_vue_type_script_setup_true_lang.js +132 -0
  192. package/dist/extensions/mention/mention-menu.vue_vue_type_script_setup_true_lang.js.map +1 -0
  193. package/dist/extensions/mention/state.js +113 -0
  194. package/dist/extensions/mention/state.js.map +1 -0
  195. package/dist/extensions/table/commands.js +356 -0
  196. package/dist/extensions/table/commands.js.map +1 -0
  197. package/dist/extensions/table/html.js +134 -0
  198. package/dist/extensions/table/html.js.map +1 -0
  199. package/dist/extensions/table/index.js +56 -0
  200. package/dist/extensions/table/index.js.map +1 -0
  201. package/dist/extensions/table/plugin.js +81 -0
  202. package/dist/extensions/table/plugin.js.map +1 -0
  203. package/dist/extensions/table/schema.js +258 -0
  204. package/dist/extensions/table/schema.js.map +1 -0
  205. package/dist/extensions/table/table-controls.js +6 -0
  206. package/dist/extensions/table/table-controls.js.map +1 -0
  207. package/dist/extensions/table/table-controls.vue_vue_type_script_setup_true_lang.js +698 -0
  208. package/dist/extensions/table/table-controls.vue_vue_type_script_setup_true_lang.js.map +1 -0
  209. package/dist/extensions/table/view.js +19 -0
  210. package/dist/extensions/table/view.js.map +1 -0
  211. package/dist/extensions/toggle/index.js +87 -0
  212. package/dist/extensions/toggle/index.js.map +1 -0
  213. package/dist/extensions/toggle/state.js +120 -0
  214. package/dist/extensions/toggle/state.js.map +1 -0
  215. package/dist/extensions/toggle/toggle-block.js +6 -0
  216. package/dist/extensions/toggle/toggle-block.js.map +1 -0
  217. package/dist/extensions/toggle/toggle-block.vue_vue_type_script_setup_true_lang.js +42 -0
  218. package/dist/extensions/toggle/toggle-block.vue_vue_type_script_setup_true_lang.js.map +1 -0
  219. package/dist/index.js +49 -0
  220. package/dist/index.js.map +1 -0
  221. package/dist/locale.js +522 -0
  222. package/dist/locale.js.map +1 -0
  223. package/dist/markdown/index.js +24 -0
  224. package/dist/markdown/index.js.map +1 -0
  225. package/dist/style.css +1 -0
  226. package/dist/style.d.ts +1 -0
  227. package/dist/style.js +8 -0
  228. package/dist/types/catalog.d.ts +8 -0
  229. package/dist/types/code/code-editor-block.vue.d.ts +17 -0
  230. package/dist/types/code/index.d.ts +18 -0
  231. package/dist/types/code/languages.d.ts +7 -0
  232. package/dist/types/code/types.d.ts +37 -0
  233. package/dist/types/code/view.d.ts +12 -0
  234. package/dist/types/collaboration/binding.d.ts +32 -0
  235. package/dist/types/collaboration/cursors.d.ts +26 -0
  236. package/dist/types/collaboration/index.d.ts +47 -0
  237. package/dist/types/collaboration/order.d.ts +20 -0
  238. package/dist/types/collaboration/table.d.ts +25 -0
  239. package/dist/types/collaboration/text.d.ts +9 -0
  240. package/dist/types/components/block-handle.vue.d.ts +3 -0
  241. package/dist/types/components/bubble-toolbar.vue.d.ts +7 -0
  242. package/dist/types/components/comment-button.vue.d.ts +3 -0
  243. package/dist/types/components/comment-card.vue.d.ts +3 -0
  244. package/dist/types/components/menu-escape.d.ts +7 -0
  245. package/dist/types/components/search-bar.vue.d.ts +22 -0
  246. package/dist/types/components/slash-menu.vue.d.ts +11 -0
  247. package/dist/types/core/align.d.ts +16 -0
  248. package/dist/types/core/block-selection.d.ts +22 -0
  249. package/dist/types/core/blocks.d.ts +52 -0
  250. package/dist/types/core/changes.d.ts +14 -0
  251. package/dist/types/core/colors.d.ts +4 -0
  252. package/dist/types/core/commands.d.ts +61 -0
  253. package/dist/types/core/comments.d.ts +44 -0
  254. package/dist/types/core/compare.d.ts +10 -0
  255. package/dist/types/core/convert.d.ts +12 -0
  256. package/dist/types/core/diff.d.ts +49 -0
  257. package/dist/types/core/editor.d.ts +64 -0
  258. package/dist/types/core/extension.d.ts +167 -0
  259. package/dist/types/core/file-drop.d.ts +19 -0
  260. package/dist/types/core/highlight.d.ts +32 -0
  261. package/dist/types/core/input-rules.d.ts +21 -0
  262. package/dist/types/core/keymap.d.ts +8 -0
  263. package/dist/types/core/model.d.ts +158 -0
  264. package/dist/types/core/output.d.ts +64 -0
  265. package/dist/types/core/plugins.d.ts +24 -0
  266. package/dist/types/core/prepare.d.ts +23 -0
  267. package/dist/types/core/schema.d.ts +38 -0
  268. package/dist/types/core/search.d.ts +83 -0
  269. package/dist/types/core/selection.d.ts +8 -0
  270. package/dist/types/core/slash.d.ts +21 -0
  271. package/dist/types/core/transaction.d.ts +97 -0
  272. package/dist/types/define-block.d.ts +66 -0
  273. package/dist/types/document.d.ts +46 -0
  274. package/dist/types/dom.d.ts +17 -0
  275. package/dist/types/editor.d.ts +252 -0
  276. package/dist/types/editor.vue.d.ts +210 -0
  277. package/dist/types/extensions/align/align-menu.vue.d.ts +3 -0
  278. package/dist/types/extensions/align/align-sub.vue.d.ts +7 -0
  279. package/dist/types/extensions/align/commands.d.ts +13 -0
  280. package/dist/types/extensions/align/index.d.ts +7 -0
  281. package/dist/types/extensions/attachment/attachment-block.vue.d.ts +17 -0
  282. package/dist/types/extensions/attachment/files.d.ts +6 -0
  283. package/dist/types/extensions/attachment/index.d.ts +20 -0
  284. package/dist/types/extensions/blocks.d.ts +8 -0
  285. package/dist/types/extensions/callout/callout-block.vue.d.ts +14 -0
  286. package/dist/types/extensions/callout/html.d.ts +4 -0
  287. package/dist/types/extensions/callout/index.d.ts +3 -0
  288. package/dist/types/extensions/callout/tones.d.ts +8 -0
  289. package/dist/types/extensions/code-block/code-block-view.vue.d.ts +16 -0
  290. package/dist/types/extensions/code-block/index.d.ts +19 -0
  291. package/dist/types/extensions/code-block/languages.d.ts +9 -0
  292. package/dist/types/extensions/colors/color-menu.vue.d.ts +3 -0
  293. package/dist/types/extensions/colors/index.d.ts +7 -0
  294. package/dist/types/extensions/colors/palette.d.ts +7 -0
  295. package/dist/types/extensions/editor-kit.d.ts +37 -0
  296. package/dist/types/extensions/embed/embed-block.vue.d.ts +16 -0
  297. package/dist/types/extensions/embed/index.d.ts +18 -0
  298. package/dist/types/extensions/embed/targets.d.ts +32 -0
  299. package/dist/types/extensions/formatting.d.ts +8 -0
  300. package/dist/types/extensions/image/image-block.vue.d.ts +16 -0
  301. package/dist/types/extensions/image/index.d.ts +16 -0
  302. package/dist/types/extensions/image/uploads.d.ts +35 -0
  303. package/dist/types/extensions/list-html.d.ts +11 -0
  304. package/dist/types/extensions/lists.d.ts +6 -0
  305. package/dist/types/extensions/mention/index.d.ts +11 -0
  306. package/dist/types/extensions/mention/mention-menu.vue.d.ts +7 -0
  307. package/dist/types/extensions/mention/state.d.ts +28 -0
  308. package/dist/types/extensions/table/commands.d.ts +69 -0
  309. package/dist/types/extensions/table/html.d.ts +4 -0
  310. package/dist/types/extensions/table/index.d.ts +8 -0
  311. package/dist/types/extensions/table/plugin.d.ts +4 -0
  312. package/dist/types/extensions/table/schema.d.ts +23 -0
  313. package/dist/types/extensions/table/table-controls.vue.d.ts +3 -0
  314. package/dist/types/extensions/table/view.d.ts +13 -0
  315. package/dist/types/extensions/toggle/index.d.ts +7 -0
  316. package/dist/types/extensions/toggle/state.d.ts +17 -0
  317. package/dist/types/extensions/toggle/toggle-block.vue.d.ts +14 -0
  318. package/dist/types/index.d.ts +266 -0
  319. package/dist/types/locale.d.ts +195 -0
  320. package/dist/types/markdown/index.d.ts +14 -0
  321. package/dist/types/use-comments.d.ts +17 -0
  322. package/dist/use-comments.js +141 -0
  323. package/dist/use-comments.js.map +1 -0
  324. package/package.json +98 -4
@@ -0,0 +1,381 @@
1
+ import { createId } from "../core/model.js";
2
+ import { blockAround, findBlockById } from "../core/blocks.js";
3
+ import { originKey } from "../core/editor.js";
4
+ import { ownStepKey } from "../core/transaction.js";
5
+ import { keysBetween, orderKeys } from "./order.js";
6
+ import { inlineFrom, insertInline, updateText } from "./text.js";
7
+ import { cellAt, cellStart, clearTable, readTable, writeTable } from "./table.js";
8
+ import { placeOf } from "./cursors.js";
9
+ import { Plugin, PluginKey, TextSelection } from "prosemirror-state";
10
+ import { Fragment } from "prosemirror-model";
11
+ import * as Y from "yjs";
12
+ // src/collaboration/binding.ts
13
+ var ROOT = "\0root";
14
+ var parentKey = (parent) => parent ?? ROOT;
15
+ function indexDoc(doc) {
16
+ const blocks = /* @__PURE__ */ new Map();
17
+ const children = /* @__PURE__ */ new Map();
18
+ const visit = (container, parent) => {
19
+ const ids = [];
20
+ container.forEach((block) => {
21
+ const id = block.attrs.id;
22
+ ids.push(id);
23
+ blocks.set(id, {
24
+ content: block.firstChild,
25
+ parent
26
+ });
27
+ if (block.childCount > 1) visit(block.child(1), id);
28
+ });
29
+ children.set(parentKey(parent), ids);
30
+ };
31
+ visit(doc, null);
32
+ return {
33
+ blocks,
34
+ children
35
+ };
36
+ }
37
+ /** The content node a stored block stands for. Unknown types become paragraphs. */
38
+ function contentFrom(yblock, schema) {
39
+ const name = yblock.get("type");
40
+ const known = schema.nodes[name];
41
+ const type = known?.isInGroup("blockContent") ? known : schema.nodes.paragraph;
42
+ const props = yblock.get("props")?.toJSON() ?? {};
43
+ const attrs = {};
44
+ for (const attr of Object.keys(type.spec.attrs ?? {})) if (attr in props) attrs[attr] = props[attr];
45
+ if (type.spec.rows) return readTable(yblock, type, attrs);
46
+ if (!type.inlineContent) return type.create(attrs);
47
+ const text = yblock.get("text");
48
+ const inline = text ? inlineFrom(text, schema, !!type.spec.code) : [];
49
+ return type.createChecked(attrs, inline.length ? Fragment.from(inline) : null);
50
+ }
51
+ function writeContent(yblock, content) {
52
+ yblock.set("type", content.type.name);
53
+ const text = new Y.Text();
54
+ yblock.set("text", text);
55
+ if (content.type.spec.rows) {
56
+ writeTable(yblock, null, content);
57
+ return;
58
+ }
59
+ const props = new Y.Map();
60
+ for (const [name, value] of Object.entries(content.attrs)) props.set(name, value);
61
+ yblock.set("props", props);
62
+ if (content.inlineContent) insertInline(text, 0, content, 0, content.content.size);
63
+ }
64
+ /**
65
+ * Bring a stored block's type, props and text from `before` to `after`.
66
+ * Returns whether the stored block holds more than `after` shows (see `writeTable`).
67
+ */
68
+ function updateContent(yblock, before, after) {
69
+ if (before.type !== after.type) yblock.set("type", after.type.name);
70
+ if (after.type.spec.rows) return writeTable(yblock, before.type === after.type ? before : null, after);
71
+ clearTable(yblock);
72
+ const props = yblock.get("props");
73
+ for (const [name, value] of Object.entries(after.attrs)) if (JSON.stringify(props.get(name)) !== JSON.stringify(value)) props.set(name, value);
74
+ for (const name of Array.from(props.keys())) if (!(name in after.attrs)) props.delete(name);
75
+ const text = yblock.get("text");
76
+ if (!after.inlineContent) {
77
+ if (text.length) text.delete(0, text.length);
78
+ return false;
79
+ }
80
+ updateText(text, before, after);
81
+ return false;
82
+ }
83
+ /**
84
+ * Where every stored block goes. A parent that no longer exists (deleted
85
+ * while someone moved a block into it) leaves the block at the top level;
86
+ * a cycle (two blocks moved into each other at the same time) is broken at
87
+ * its smallest id. Every client reads the same data the same way, so all
88
+ * end up with the same tree.
89
+ */
90
+ function placeAll(map) {
91
+ const parentOf = /* @__PURE__ */ new Map();
92
+ map.forEach((yblock, id) => {
93
+ const parent = yblock.get("parent");
94
+ parentOf.set(id, parent && parent !== id && map.has(parent) ? parent : null);
95
+ });
96
+ const settled = /* @__PURE__ */ new Set();
97
+ for (const id of parentOf.keys()) {
98
+ const path = [];
99
+ const onPath = /* @__PURE__ */ new Set();
100
+ let current = id;
101
+ while (current !== null && !settled.has(current)) {
102
+ if (onPath.has(current)) {
103
+ const breakAt = [...path.slice(path.indexOf(current))].sort()[0];
104
+ parentOf.set(breakAt, null);
105
+ break;
106
+ }
107
+ path.push(current);
108
+ onPath.add(current);
109
+ current = parentOf.get(current) ?? null;
110
+ }
111
+ for (const visited of path) settled.add(visited);
112
+ }
113
+ const children = /* @__PURE__ */ new Map();
114
+ for (const [id, parent] of parentOf) {
115
+ const key = parentKey(parent);
116
+ const list = children.get(key);
117
+ if (list) list.push(id);
118
+ else children.set(key, [id]);
119
+ }
120
+ const orderOf = (id) => map.get(id).get("order") ?? "";
121
+ for (const list of children.values()) list.sort((a, b) => {
122
+ const x = orderOf(a);
123
+ const y = orderOf(b);
124
+ return x < y ? -1 : x > y ? 1 : a < b ? -1 : a > b ? 1 : 0;
125
+ });
126
+ return children;
127
+ }
128
+ /** Whether `$pos` is inside table structure below `depth`. */
129
+ function inTable($pos, depth) {
130
+ for (let d = $pos.depth; d > depth; d--) if ($pos.node(d).type.spec.tableRole) return true;
131
+ return false;
132
+ }
133
+ /** A position as its table cell and offset in it, if it is in one. */
134
+ function cellPlace(doc, pos) {
135
+ const info = blockAround(doc.resolve(pos));
136
+ if (!info?.content.type.spec.rows) return null;
137
+ const cell = cellAt(info.content, pos - info.contentPos - 1);
138
+ return cell && {
139
+ block: info.node.attrs.id,
140
+ ...cell
141
+ };
142
+ }
143
+ /** Put a cursor that was in a replaced table back into its cell. */
144
+ function keepCellSelection(state, tr) {
145
+ const { selection } = state;
146
+ if (!(selection instanceof TextSelection)) return;
147
+ const back = (pos) => {
148
+ const place = cellPlace(state.doc, pos);
149
+ if (!place) return null;
150
+ const info = findBlockById(tr.doc, place.block);
151
+ const cell = info && cellStart(info.content, place.key);
152
+ return cell ? info.contentPos + 1 + cell.offset + Math.min(place.index, cell.size) : null;
153
+ };
154
+ const anchor = back(selection.anchor);
155
+ const head = back(selection.head);
156
+ if (anchor !== null && head !== null) tr.setSelection(TextSelection.create(tr.doc, anchor, head));
157
+ }
158
+ var collaborationKey = new PluginKey("dou-editor-collaboration");
159
+ function createBinding(map) {
160
+ return {
161
+ map,
162
+ origin: { binding: "tracked" },
163
+ untracked: { binding: "untracked" },
164
+ view: null
165
+ };
166
+ }
167
+ /** Write the blocks of `doc` into an empty map. */
168
+ function writeDoc(map, doc) {
169
+ const index = indexDoc(doc);
170
+ map.doc.transact(() => {
171
+ for (const [key, ids] of index.children) {
172
+ const keys = keysBetween(null, null, ids.length);
173
+ ids.forEach((id, i) => {
174
+ const yblock = new Y.Map();
175
+ map.set(id, yblock);
176
+ yblock.set("parent", key === ROOT ? null : key);
177
+ yblock.set("order", keys[i]);
178
+ writeContent(yblock, index.blocks.get(id).content);
179
+ });
180
+ }
181
+ });
182
+ }
183
+ /** The document a map holds, read without an editor; `null` while the map is empty. */
184
+ function readDoc(map, schema) {
185
+ const children = placeAll(map);
186
+ const build = (id) => {
187
+ const content = contentFrom(map.get(id), schema);
188
+ const kids = children.get(id);
189
+ const parts = kids?.length ? [content, schema.nodes.blockGroup.create(null, kids.map(build))] : [content];
190
+ return schema.nodes.block.create({ id }, parts);
191
+ };
192
+ const top = children.get(ROOT) ?? [];
193
+ return top.length ? schema.nodes.doc.create(null, top.map(build)) : null;
194
+ }
195
+ function syncPlugin(binding) {
196
+ const { map } = binding;
197
+ const ydoc = map.doc;
198
+ let schema;
199
+ let synced = null;
200
+ const cache = /* @__PURE__ */ new Map();
201
+ let placeholderId = createId();
202
+ function buildDoc() {
203
+ const children = placeAll(map);
204
+ const build = (id) => {
205
+ let content = cache.get(id);
206
+ if (!content) {
207
+ content = contentFrom(map.get(id), schema);
208
+ cache.set(id, content);
209
+ }
210
+ const kids = children.get(id);
211
+ const parts = kids?.length ? [content, schema.nodes.blockGroup.create(null, kids.map(build))] : [content];
212
+ return schema.nodes.block.create({ id }, parts);
213
+ };
214
+ const top = children.get(ROOT) ?? [];
215
+ const blocks = top.length ? top.map(build) : [schema.nodes.block.create({ id: placeholderId }, schema.nodes.paragraph.create())];
216
+ return schema.nodes.doc.create(null, blocks);
217
+ }
218
+ /** Show the map's current state in the editor. */
219
+ function readMap(view) {
220
+ const doc = view.state.doc;
221
+ const next = buildDoc();
222
+ const start = doc.content.findDiffStart(next.content);
223
+ if (start !== null) {
224
+ const end = doc.content.findDiffEnd(next.content);
225
+ const overlap = start - Math.min(end.a, end.b);
226
+ if (overlap > 0) {
227
+ end.a += overlap;
228
+ end.b += overlap;
229
+ }
230
+ let from = start;
231
+ let to = end.a;
232
+ const $from = doc.resolve(from);
233
+ const $to = doc.resolve(to);
234
+ const shared = $from.sharedDepth(to);
235
+ if (!$from.node(shared).inlineContent && (inTable($from, shared) || inTable($to, shared))) {
236
+ if ($from.depth > shared) from = $from.before(shared + 1);
237
+ if ($to.depth > shared) to = $to.after(shared + 1);
238
+ }
239
+ const tail = doc.content.size - to;
240
+ const tr = view.state.tr.replace(from, to, next.slice(from, next.content.size - tail));
241
+ if (to !== end.a || from !== start) keepCellSelection(view.state, tr);
242
+ tr.setMeta(collaborationKey, { remote: true });
243
+ tr.setMeta("remote", true);
244
+ if (binding.undoing) tr.setMeta(originKey, binding.undoing);
245
+ tr.setMeta("addToHistory", false);
246
+ view.dispatch(tr);
247
+ }
248
+ synced = indexDoc(view.state.doc);
249
+ }
250
+ /** Write the editor's changes since the last sync into the map. */
251
+ function writeMap(view, tracked) {
252
+ const doc = view.state.doc;
253
+ const before = synced ?? indexDoc(doc);
254
+ const after = indexDoc(doc);
255
+ let structural = false;
256
+ for (const [key, ids] of after.children) {
257
+ const old = before.children.get(key);
258
+ if (!old || old.length !== ids.length || old.some((id, i) => ids[i] !== id)) structural = true;
259
+ }
260
+ if (structural) binding.stopCapturing?.();
261
+ const stale = [];
262
+ ydoc.transact(() => {
263
+ for (const [id, placed] of after.blocks) {
264
+ const yblock = map.get(id);
265
+ const old = before.blocks.get(id);
266
+ if (!yblock) {
267
+ const created = new Y.Map();
268
+ map.set(id, created);
269
+ writeContent(created, placed.content);
270
+ } else if (old && old.content !== placed.content) {
271
+ if (updateContent(yblock, old.content, placed.content)) stale.push(id);
272
+ } else if (!old) {
273
+ if (updateContent(yblock, contentFrom(yblock, schema), placed.content)) stale.push(id);
274
+ }
275
+ cache.set(id, placed.content);
276
+ }
277
+ for (const id of before.blocks.keys()) if (!after.blocks.has(id)) {
278
+ map.delete(id);
279
+ cache.delete(id);
280
+ }
281
+ for (const [key, ids] of after.children) {
282
+ const old = before.children.get(key);
283
+ if (old && old.length === ids.length && old.every((id, i) => ids[i] === id)) {
284
+ if (ids.every((id) => map.get(id).get("order") !== void 0)) continue;
285
+ }
286
+ const parent = key === ROOT ? null : key;
287
+ const keys = ids.map((id) => {
288
+ const yblock = map.get(id);
289
+ const order = yblock.get("order");
290
+ return yblock.get("parent") === parent && order !== void 0 ? order : null;
291
+ });
292
+ const next = orderKeys(keys);
293
+ ids.forEach((id, index) => {
294
+ if (keys[index] === next[index]) return;
295
+ const yblock = map.get(id);
296
+ if (yblock.get("parent") !== parent) yblock.set("parent", parent);
297
+ yblock.set("order", next[index]);
298
+ });
299
+ }
300
+ }, tracked ? binding.origin : binding.untracked);
301
+ if (structural) binding.stopCapturing?.();
302
+ synced = after;
303
+ if (stale.length) {
304
+ for (const id of stale) cache.delete(id);
305
+ queueMicrotask(() => {
306
+ if (binding.view === view) readMap(view);
307
+ });
308
+ }
309
+ }
310
+ return new Plugin({
311
+ key: collaborationKey,
312
+ state: {
313
+ init: () => ({
314
+ remote: false,
315
+ tracked: false,
316
+ own: false
317
+ }),
318
+ apply(tr, value) {
319
+ const meta = tr.getMeta(collaborationKey);
320
+ if (!tr.docChanged) return value;
321
+ if (meta?.remote) return {
322
+ remote: true,
323
+ tracked: false,
324
+ own: false
325
+ };
326
+ const tracked = tr.getMeta("addToHistory") !== false;
327
+ const own = tr.getMeta(ownStepKey) === true;
328
+ return !!tr.getMeta("appendedTransaction") && !value.remote ? {
329
+ remote: false,
330
+ tracked: value.tracked || tracked,
331
+ own: value.own || own
332
+ } : {
333
+ remote: false,
334
+ tracked,
335
+ own
336
+ };
337
+ }
338
+ },
339
+ view(view) {
340
+ schema = view.state.schema;
341
+ binding.view = view;
342
+ const observer = (events, transaction) => {
343
+ if (transaction.origin === binding.origin || transaction.origin === binding.untracked) return;
344
+ for (const event of events) {
345
+ if (event.target === map) {
346
+ for (const id of event.keysChanged) cache.delete(id);
347
+ continue;
348
+ }
349
+ const id = event.path[0];
350
+ if (typeof id === "string") cache.delete(id);
351
+ }
352
+ readMap(view);
353
+ };
354
+ map.observeDeep(observer);
355
+ if (map.size) readMap(view);
356
+ else synced = indexDoc(view.state.doc);
357
+ placeholderId = view.state.doc.firstChild?.attrs.id ?? placeholderId;
358
+ return {
359
+ update(current, previous) {
360
+ if (current.state.doc === previous.doc) return;
361
+ const state = collaborationKey.getState(current.state);
362
+ if (state.remote) {
363
+ synced = indexDoc(current.state.doc);
364
+ return;
365
+ }
366
+ binding.selectionBefore = placeOf(binding, previous.doc, previous.selection.head);
367
+ if (state.own) binding.stopCapturing?.();
368
+ writeMap(current, state.tracked);
369
+ if (state.own) binding.stopCapturing?.();
370
+ },
371
+ destroy() {
372
+ map.unobserveDeep(observer);
373
+ binding.view = null;
374
+ }
375
+ };
376
+ }
377
+ });
378
+ }
379
+ export { collaborationKey, createBinding, readDoc, syncPlugin, writeDoc };
380
+
381
+ //# sourceMappingURL=binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding.js","names":[],"sources":["../../src/collaboration/binding.ts"],"sourcesContent":["import { Fragment, type Node as PMNode, type ResolvedPos, type Schema } from 'prosemirror-model'\nimport {\n Plugin,\n PluginKey,\n TextSelection,\n type EditorState,\n type Transaction,\n} from 'prosemirror-state'\nimport type { EditorView } from 'prosemirror-view'\nimport * as Y from 'yjs'\nimport { blockAround, findBlockById } from '../core/blocks'\nimport { createId } from '../core/model'\nimport { originKey } from '../core/editor'\nimport { ownStepKey } from '../core/transaction'\nimport { placeOf } from './cursors'\nimport { keysBetween, orderKeys } from './order'\nimport { cellAt, cellStart, clearTable, readTable, writeTable } from './table'\nimport { inlineFrom, insertInline, updateText, type Attributes } from './text'\n\n/*\n * How a document is stored in Yjs. Every block is one entry of a `Y.Map`,\n * by id, holding its type, its props, its text and its place: the id of its\n * parent and an order key among its siblings. Moving a block only changes\n * its place, so text typed into it meanwhile by someone else stays in it.\n *\n * map: Y.Map<id, Y.Map {\n * type: string\n * props: Y.Map<name, value>\n * text: Y.Text (marks as formatting attributes, inline nodes as embeds)\n * parent: string | null\n * order: string\n * }>\n *\n * Tables keep their rows, columns and cells in more entries; see `./table`.\n */\n\nexport type YBlock = Y.Map<unknown>\n\nconst ROOT = '\\u0000root'\n\nconst parentKey = (parent: string | null) => parent ?? ROOT\n\n/** The place of every block as the editor shows it, read from the document. */\ninterface Placed {\n /** The content node (paragraph, heading, …). */\n content: PMNode\n parent: string | null\n}\n\ninterface Index {\n blocks: Map<string, Placed>\n /** Child ids in order, by parent (`ROOT` for the top level). */\n children: Map<string, string[]>\n}\n\nfunction indexDoc(doc: PMNode): Index {\n const blocks = new Map<string, Placed>()\n const children = new Map<string, string[]>()\n const visit = (container: PMNode, parent: string | null) => {\n const ids: string[] = []\n container.forEach((block) => {\n const id = block.attrs.id as string\n ids.push(id)\n blocks.set(id, { content: block.firstChild!, parent })\n if (block.childCount > 1) visit(block.child(1), id)\n })\n children.set(parentKey(parent), ids)\n }\n visit(doc, null)\n return { blocks, children }\n}\n\n/** The content node a stored block stands for. Unknown types become paragraphs. */\nfunction contentFrom(yblock: YBlock, schema: Schema): PMNode {\n const name = yblock.get('type') as string\n const known = schema.nodes[name]\n const type = known?.isInGroup('blockContent') ? known : schema.nodes.paragraph!\n const props = (yblock.get('props') as Y.Map<unknown> | undefined)?.toJSON() ?? {}\n const attrs: Attributes = {}\n for (const attr of Object.keys(type.spec.attrs ?? {}))\n if (attr in props) attrs[attr] = props[attr]\n if (type.spec.rows) return readTable(yblock, type, attrs)\n if (!type.inlineContent) return type.create(attrs)\n const text = yblock.get('text') as Y.Text | undefined\n const inline = text ? inlineFrom(text, schema, !!type.spec.code) : []\n return type.createChecked(attrs, inline.length ? Fragment.from(inline) : null)\n}\n\nfunction writeContent(yblock: YBlock, content: PMNode) {\n yblock.set('type', content.type.name)\n const text = new Y.Text()\n yblock.set('text', text)\n if (content.type.spec.rows) {\n writeTable(yblock, null, content)\n return\n }\n const props = new Y.Map<unknown>()\n for (const [name, value] of Object.entries(content.attrs)) props.set(name, value)\n yblock.set('props', props)\n if (content.inlineContent) insertInline(text, 0, content, 0, content.content.size)\n}\n\n/**\n * Bring a stored block's type, props and text from `before` to `after`.\n * Returns whether the stored block holds more than `after` shows (see `writeTable`).\n */\nfunction updateContent(yblock: YBlock, before: PMNode, after: PMNode): boolean {\n if (before.type !== after.type) yblock.set('type', after.type.name)\n if (after.type.spec.rows)\n return writeTable(yblock, before.type === after.type ? before : null, after)\n clearTable(yblock)\n const props = yblock.get('props') as Y.Map<unknown>\n for (const [name, value] of Object.entries(after.attrs)) {\n if (JSON.stringify(props.get(name)) !== JSON.stringify(value)) props.set(name, value)\n }\n for (const name of Array.from(props.keys())) if (!(name in after.attrs)) props.delete(name)\n\n const text = yblock.get('text') as Y.Text\n if (!after.inlineContent) {\n if (text.length) text.delete(0, text.length)\n return false\n }\n updateText(text, before, after)\n return false\n}\n\n/* the stored tree → document */\n\n/**\n * Where every stored block goes. A parent that no longer exists (deleted\n * while someone moved a block into it) leaves the block at the top level;\n * a cycle (two blocks moved into each other at the same time) is broken at\n * its smallest id. Every client reads the same data the same way, so all\n * end up with the same tree.\n */\nfunction placeAll(map: Y.Map<YBlock>): Map<string, string[]> {\n const parentOf = new Map<string, string | null>()\n map.forEach((yblock, id) => {\n const parent = yblock.get('parent') as string | null | undefined\n parentOf.set(id, parent && parent !== id && map.has(parent) ? parent : null)\n })\n const settled = new Set<string>()\n for (const id of parentOf.keys()) {\n const path: string[] = []\n const onPath = new Set<string>()\n let current: string | null = id\n while (current !== null && !settled.has(current)) {\n if (onPath.has(current)) {\n const cycle = path.slice(path.indexOf(current))\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts a fresh copy\n const breakAt = [...cycle].sort()[0]!\n parentOf.set(breakAt, null)\n break\n }\n path.push(current)\n onPath.add(current)\n current = parentOf.get(current) ?? null\n }\n for (const visited of path) settled.add(visited)\n }\n const children = new Map<string, string[]>()\n for (const [id, parent] of parentOf) {\n const key = parentKey(parent)\n const list = children.get(key)\n if (list) list.push(id)\n else children.set(key, [id])\n }\n const orderOf = (id: string) => (map.get(id)!.get('order') as string | undefined) ?? ''\n for (const list of children.values()) {\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts our own list\n list.sort((a, b) => {\n const x = orderOf(a)\n const y = orderOf(b)\n return x < y ? -1 : x > y ? 1 : a < b ? -1 : a > b ? 1 : 0\n })\n }\n return children\n}\n\n/** Whether `$pos` is inside table structure below `depth`. */\nfunction inTable($pos: ResolvedPos, depth: number) {\n for (let d = $pos.depth; d > depth; d--) if ($pos.node(d).type.spec.tableRole) return true\n return false\n}\n\n/** A position as its table cell and offset in it, if it is in one. */\nfunction cellPlace(doc: PMNode, pos: number) {\n const info = blockAround(doc.resolve(pos))\n if (!info?.content.type.spec.rows) return null\n const cell = cellAt(info.content, pos - info.contentPos - 1)\n return cell && { block: info.node.attrs.id as string, ...cell }\n}\n\n/** Put a cursor that was in a replaced table back into its cell. */\nfunction keepCellSelection(state: EditorState, tr: Transaction) {\n const { selection } = state\n if (!(selection instanceof TextSelection)) return\n const back = (pos: number) => {\n const place = cellPlace(state.doc, pos)\n if (!place) return null\n const info = findBlockById(tr.doc, place.block)\n const cell = info && cellStart(info.content, place.key)\n return cell ? info.contentPos + 1 + cell.offset + Math.min(place.index, cell.size) : null\n }\n const anchor = back(selection.anchor)\n const head = back(selection.head)\n if (anchor !== null && head !== null) tr.setSelection(TextSelection.create(tr.doc, anchor, head))\n}\n\n/* the binding */\n\nexport const collaborationKey = new PluginKey<{\n remote: boolean\n tracked: boolean\n /** An undo step of its own (`transact`). */\n own: boolean\n}>('dou-editor-collaboration')\n\nexport interface Binding {\n map: Y.Map<YBlock>\n /** The origin of this editor's own changes; the undo manager tracks it. */\n origin: object\n /** The origin of changes that are not undone (`setBlocks`, fixes). */\n untracked: object\n view: EditorView | null\n /** Ends the current undo step, so a structural change is undone on its own. */\n stopCapturing?: () => void\n /** Where the cursor was before the change being written, for undo to return to. */\n selectionBefore?: unknown\n /** Set while the undo manager takes a step back or forward. */\n undoing?: 'undo' | 'redo'\n}\n\nexport function createBinding(map: Y.Map<YBlock>): Binding {\n return { map, origin: { binding: 'tracked' }, untracked: { binding: 'untracked' }, view: null }\n}\n\n/** Write the blocks of `doc` into an empty map. */\nexport function writeDoc(map: Y.Map<YBlock>, doc: PMNode) {\n const index = indexDoc(doc)\n map.doc!.transact(() => {\n for (const [key, ids] of index.children) {\n const keys = keysBetween(null, null, ids.length)\n ids.forEach((id, i) => {\n const yblock = new Y.Map<unknown>()\n map.set(id, yblock)\n yblock.set('parent', key === ROOT ? null : key)\n yblock.set('order', keys[i])\n writeContent(yblock, index.blocks.get(id)!.content)\n })\n }\n })\n}\n\n/** The document a map holds, read without an editor; `null` while the map is empty. */\nexport function readDoc(map: Y.Map<YBlock>, schema: Schema): PMNode | null {\n const children = placeAll(map)\n const build = (id: string): PMNode => {\n const content = contentFrom(map.get(id)!, schema)\n const kids = children.get(id)\n const parts = kids?.length\n ? [content, schema.nodes.blockGroup!.create(null, kids.map(build))]\n : [content]\n return schema.nodes.block!.create({ id }, parts)\n }\n const top = children.get(ROOT) ?? []\n return top.length ? schema.nodes.doc!.create(null, top.map(build)) : null\n}\n\nexport function syncPlugin(binding: Binding): Plugin {\n const { map } = binding\n const ydoc = map.doc!\n let schema: Schema\n // the document as last written to or read from the map\n let synced: Index | null = null\n // content nodes read from the map, by id, until the block changes there\n const cache = new Map<string, PMNode>()\n let placeholderId = createId()\n\n function buildDoc(): PMNode {\n const children = placeAll(map)\n const build = (id: string): PMNode => {\n let content = cache.get(id)\n if (!content) {\n content = contentFrom(map.get(id)!, schema)\n cache.set(id, content)\n }\n const kids = children.get(id)\n const parts = kids?.length\n ? [content, schema.nodes.blockGroup!.create(null, kids.map(build))]\n : [content]\n return schema.nodes.block!.create({ id }, parts)\n }\n const top = children.get(ROOT) ?? []\n // an empty map still shows one empty paragraph; it is stored once typed in\n const blocks = top.length\n ? top.map(build)\n : [schema.nodes.block!.create({ id: placeholderId }, schema.nodes.paragraph!.create())]\n return schema.nodes.doc!.create(null, blocks)\n }\n\n /** Show the map's current state in the editor. */\n function readMap(view: EditorView) {\n const doc = view.state.doc\n const next = buildDoc()\n const start = doc.content.findDiffStart(next.content)\n if (start !== null) {\n const end = doc.content.findDiffEnd(next.content)!\n const overlap = start - Math.min(end.a, end.b)\n if (overlap > 0) {\n end.a += overlap\n end.b += overlap\n }\n let from = start\n let to = end.a\n const $from = doc.resolve(from)\n const $to = doc.resolve(to)\n const shared = $from.sharedDepth(to)\n // a change across table cells or rows: swap them whole, a partial cut breaks the grid\n if (!$from.node(shared).inlineContent && (inTable($from, shared) || inTable($to, shared))) {\n if ($from.depth > shared) from = $from.before(shared + 1)\n if ($to.depth > shared) to = $to.after(shared + 1)\n }\n const tail = doc.content.size - to\n const tr = view.state.tr.replace(from, to, next.slice(from, next.content.size - tail))\n if (to !== end.a || from !== start) keepCellSelection(view.state, tr)\n tr.setMeta(collaborationKey, { remote: true })\n tr.setMeta('remote', true)\n // this user's undo comes back through the shared document too\n if (binding.undoing) tr.setMeta(originKey, binding.undoing)\n tr.setMeta('addToHistory', false)\n view.dispatch(tr)\n }\n synced = indexDoc(view.state.doc)\n }\n\n /** Write the editor's changes since the last sync into the map. */\n function writeMap(view: EditorView, tracked: boolean) {\n const doc = view.state.doc\n const before = synced ?? indexDoc(doc)\n const after = indexDoc(doc)\n // moving, indenting or adding blocks is an undo step of its own\n let structural = false\n for (const [key, ids] of after.children) {\n const old = before.children.get(key)\n if (!old || old.length !== ids.length || old.some((id, i) => ids[i] !== id)) structural = true\n }\n if (structural) binding.stopCapturing?.()\n // blocks that hold more than the editor shows, to be read back\n const stale: string[] = []\n ydoc.transact(\n () => {\n for (const [id, placed] of after.blocks) {\n const yblock = map.get(id)\n const old = before.blocks.get(id)\n if (!yblock) {\n const created = new Y.Map<unknown>()\n map.set(id, created)\n writeContent(created, placed.content)\n } else if (old && old.content !== placed.content) {\n if (updateContent(yblock, old.content, placed.content)) stale.push(id)\n } else if (!old) {\n // stored, but not shown before (e.g. the empty placeholder came back)\n if (updateContent(yblock, contentFrom(yblock, schema), placed.content)) stale.push(id)\n }\n cache.set(id, placed.content)\n }\n for (const id of before.blocks.keys()) {\n if (!after.blocks.has(id)) {\n map.delete(id)\n cache.delete(id)\n }\n }\n // places: only lists that changed, and only the blocks that moved in them\n for (const [key, ids] of after.children) {\n const old = before.children.get(key)\n if (old && old.length === ids.length && old.every((id, i) => ids[i] === id)) {\n if (ids.every((id) => map.get(id)!.get('order') !== undefined)) continue\n }\n const parent = key === ROOT ? null : key\n const keys = ids.map((id) => {\n const yblock = map.get(id)!\n const order = yblock.get('order') as string | undefined\n return yblock.get('parent') === parent && order !== undefined ? order : null\n })\n const next = orderKeys(keys)\n ids.forEach((id, index) => {\n if (keys[index] === next[index]) return\n const yblock = map.get(id)!\n if (yblock.get('parent') !== parent) yblock.set('parent', parent)\n yblock.set('order', next[index])\n })\n }\n },\n tracked ? binding.origin : binding.untracked,\n )\n if (structural) binding.stopCapturing?.()\n synced = after\n if (stale.length) {\n for (const id of stale) cache.delete(id)\n // not from inside this update: show the rest once it is done\n queueMicrotask(() => {\n if (binding.view === view) readMap(view)\n })\n }\n }\n\n return new Plugin({\n key: collaborationKey,\n state: {\n init: () => ({ remote: false, tracked: false, own: false }),\n apply(tr, value) {\n const meta = tr.getMeta(collaborationKey) as { remote?: boolean } | undefined\n if (!tr.docChanged) return value\n if (meta?.remote) return { remote: true, tracked: false, own: false }\n // a transaction can come with appended ones (e.g. id fixes), written\n // together: undone together if any of them is\n const tracked = tr.getMeta('addToHistory') !== false\n const own = tr.getMeta(ownStepKey) === true\n const appended = !!tr.getMeta('appendedTransaction') && !value.remote\n return appended\n ? { remote: false, tracked: value.tracked || tracked, own: value.own || own }\n : { remote: false, tracked, own }\n },\n },\n view(view) {\n schema = view.state.schema\n binding.view = view\n const observer = (\n events: Y.YEvent<Y.AbstractType<unknown>>[],\n transaction: Y.Transaction,\n ) => {\n if (transaction.origin === binding.origin || transaction.origin === binding.untracked)\n return\n // forget the cached content of every block that changed\n for (const event of events) {\n if (event.target === map) {\n for (const id of (event as Y.YMapEvent<unknown>).keysChanged) cache.delete(id)\n continue\n }\n // the path from the map starts with the block's id\n const id = event.path[0]\n if (typeof id === 'string') cache.delete(id)\n }\n readMap(view)\n }\n map.observeDeep(observer)\n if (map.size) readMap(view)\n else synced = indexDoc(view.state.doc)\n placeholderId = (view.state.doc.firstChild?.attrs.id as string | undefined) ?? placeholderId\n return {\n update(current, previous: EditorState) {\n if (current.state.doc === previous.doc) return\n const state = collaborationKey.getState(current.state)!\n if (state.remote) {\n synced = indexDoc(current.state.doc)\n return\n }\n // read before writing: the map still holds the previous text\n binding.selectionBefore = placeOf(binding, previous.doc, previous.selection.head)\n if (state.own) binding.stopCapturing?.()\n writeMap(current, state.tracked)\n if (state.own) binding.stopCapturing?.()\n },\n destroy() {\n map.unobserveDeep(observer)\n binding.view = null\n },\n }\n },\n })\n}\n\nexport type { Transaction }\n"],"mappings":"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"}
@@ -0,0 +1,150 @@
1
+ import { blockAround, findBlockById } from "../core/blocks.js";
2
+ import { cellAt, cellStart } from "./table.js";
3
+ import { Plugin, PluginKey } from "prosemirror-state";
4
+ import { Decoration, DecorationSet } from "prosemirror-view";
5
+ import * as Y from "yjs";
6
+ // src/collaboration/cursors.ts
7
+ /** The text a place points into: the block's own, or one of its table cells. */
8
+ function textOf(binding, block, cell) {
9
+ const yblock = binding.map.get(block);
10
+ if (!cell) return yblock?.get("text");
11
+ return (yblock?.get("cells"))?.get(cell);
12
+ }
13
+ /** A document position as a place that survives other people's edits. */
14
+ function placeOf(binding, doc, pos) {
15
+ const info = blockAround(doc.resolve(pos));
16
+ if (!info) return null;
17
+ const id = info.node.attrs.id;
18
+ let index = pos - info.contentPos - 1;
19
+ let cell;
20
+ if (info.content.type.spec.rows) {
21
+ const found = cellAt(info.content, index);
22
+ if (!found) return null;
23
+ cell = found.key;
24
+ index = found.index;
25
+ }
26
+ const text = textOf(binding, id, cell);
27
+ if (!text) return null;
28
+ const offset = Math.max(0, Math.min(index, text.length));
29
+ const position = Y.relativePositionToJSON(Y.createRelativePositionFromTypeIndex(text, offset));
30
+ return cell ? {
31
+ block: id,
32
+ cell,
33
+ position
34
+ } : {
35
+ block: id,
36
+ position
37
+ };
38
+ }
39
+ /** Back from a place to a document position, or `null` if its block is gone. */
40
+ function positionOf(binding, doc, place) {
41
+ const info = findBlockById(doc, place.block);
42
+ if (!info) return null;
43
+ const absolute = Y.createAbsolutePositionFromRelativePosition(Y.createRelativePositionFromJSON(place.position), binding.map.doc);
44
+ let start = info.contentPos + 1;
45
+ let size = info.content.content.size;
46
+ if (place.cell) {
47
+ const cell = info.content.type.spec.rows ? cellStart(info.content, place.cell) : null;
48
+ if (!cell) return null;
49
+ start += cell.offset;
50
+ size = cell.size;
51
+ }
52
+ const offset = absolute && textOf(binding, place.block, place.cell) === absolute.type ? Math.min(absolute.index, size) : 0;
53
+ return start + offset;
54
+ }
55
+ function cursorElement(user) {
56
+ const cursor = document.createElement("span");
57
+ cursor.className = "dou-editor__cursor";
58
+ cursor.style.setProperty("--dou-editor-cursor-color", user.color);
59
+ const label = document.createElement("span");
60
+ label.className = "dou-editor__cursor-label";
61
+ label.textContent = user.name;
62
+ cursor.append("⁠", label, "⁠");
63
+ return cursor;
64
+ }
65
+ var cursorsKey = new PluginKey("dou-editor-cursors");
66
+ function decorations(binding, awareness, state) {
67
+ const found = [];
68
+ awareness.getStates().forEach((value, client) => {
69
+ if (client === awareness.clientID) return;
70
+ const cursor = value.cursor;
71
+ const user = value.user;
72
+ if (!cursor) return;
73
+ const anchor = positionOf(binding, state.doc, cursor.anchor);
74
+ const head = positionOf(binding, state.doc, cursor.head);
75
+ if (anchor === null || head === null) return;
76
+ const shown = {
77
+ name: user?.name ?? "",
78
+ color: user?.color ?? "#6b7280"
79
+ };
80
+ found.push(Decoration.widget(head, () => cursorElement(shown), {
81
+ key: `cursor-${client}-${shown.name}-${shown.color}`,
82
+ side: 10
83
+ }));
84
+ if (anchor !== head) found.push(Decoration.inline(Math.min(anchor, head), Math.max(anchor, head), {
85
+ class: "dou-editor__remote-selection",
86
+ style: `--dou-editor-cursor-color: ${shown.color}`
87
+ }));
88
+ });
89
+ return DecorationSet.create(state.doc, found);
90
+ }
91
+ /** Shares this user's cursor and shows everyone else's. */
92
+ function cursorsPlugin(binding, awareness) {
93
+ return new Plugin({
94
+ key: cursorsKey,
95
+ state: {
96
+ init: (_config, state) => decorations(binding, awareness, state),
97
+ apply(tr, value, _old, state) {
98
+ if (tr.docChanged || tr.getMeta(cursorsKey)) return decorations(binding, awareness, state);
99
+ return value;
100
+ }
101
+ },
102
+ props: { decorations(state) {
103
+ return cursorsKey.getState(state);
104
+ } },
105
+ view(view) {
106
+ let scheduled = false;
107
+ const onChange = () => {
108
+ if (scheduled) return;
109
+ scheduled = true;
110
+ queueMicrotask(() => {
111
+ scheduled = false;
112
+ if (binding.view === view) view.dispatch(view.state.tr.setMeta(cursorsKey, true));
113
+ });
114
+ };
115
+ awareness.on("change", onChange);
116
+ let last = "";
117
+ const share = () => {
118
+ let cursor = null;
119
+ if (view.hasFocus()) {
120
+ const { anchor, head } = view.state.selection;
121
+ const a = placeOf(binding, view.state.doc, anchor);
122
+ const h = placeOf(binding, view.state.doc, head);
123
+ if (a && h) cursor = {
124
+ anchor: a,
125
+ head: h
126
+ };
127
+ }
128
+ const json = JSON.stringify(cursor);
129
+ if (json === last) return;
130
+ last = json;
131
+ awareness.setLocalStateField("cursor", cursor);
132
+ };
133
+ const onFocus = () => share();
134
+ view.dom.addEventListener("focus", onFocus);
135
+ view.dom.addEventListener("blur", onFocus);
136
+ return {
137
+ update: share,
138
+ destroy() {
139
+ awareness.off("change", onChange);
140
+ view.dom.removeEventListener("focus", onFocus);
141
+ view.dom.removeEventListener("blur", onFocus);
142
+ awareness.setLocalStateField("cursor", null);
143
+ }
144
+ };
145
+ }
146
+ });
147
+ }
148
+ export { cursorsPlugin, placeOf, positionOf };
149
+
150
+ //# sourceMappingURL=cursors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursors.js","names":[],"sources":["../../src/collaboration/cursors.ts"],"sourcesContent":["import type { Node as PMNode } from 'prosemirror-model'\nimport { Plugin, PluginKey, type EditorState } from 'prosemirror-state'\nimport { Decoration, DecorationSet } from 'prosemirror-view'\nimport * as Y from 'yjs'\nimport { blockAround, findBlockById } from '../core/blocks'\nimport type { Binding, YBlock } from './binding'\nimport { cellAt, cellStart } from './table'\n\n/** What the editor needs of a Yjs awareness instance (`y-protocols/awareness`). */\nexport interface AwarenessLike {\n clientID: number\n getLocalState(): Record<string, unknown> | null\n setLocalStateField(field: string, value: unknown): void\n getStates(): Map<number, Record<string, unknown>>\n on(event: 'change', listener: (...args: unknown[]) => void): void\n off(event: 'change', listener: (...args: unknown[]) => void): void\n}\n\n/** A place in the text: the block and a position in its text that follows edits. */\ninterface Place {\n block: string\n /** In a table: the cell, `row id:column id`. */\n cell?: string\n position: unknown\n}\n\n/** The text a place points into: the block's own, or one of its table cells. */\nfunction textOf(binding: Binding, block: string, cell?: string): Y.Text | undefined {\n const yblock = binding.map.get(block) as YBlock | undefined\n if (!cell) return yblock?.get('text') as Y.Text | undefined\n return (yblock?.get('cells') as Y.Map<Y.Text> | undefined)?.get(cell)\n}\n\ninterface Cursor {\n anchor: Place\n head: Place\n}\n\n/** A document position as a place that survives other people's edits. */\nexport function placeOf(binding: Binding, doc: PMNode, pos: number): Place | null {\n const info = blockAround(doc.resolve(pos))\n if (!info) return null\n const id = info.node.attrs.id as string\n let index = pos - info.contentPos - 1\n let cell: string | undefined\n if (info.content.type.spec.rows) {\n const found = cellAt(info.content, index)\n if (!found) return null\n cell = found.key\n index = found.index\n }\n const text = textOf(binding, id, cell)\n if (!text) return null\n const offset = Math.max(0, Math.min(index, text.length))\n const position = Y.relativePositionToJSON(Y.createRelativePositionFromTypeIndex(text, offset))\n return cell ? { block: id, cell, position } : { block: id, position }\n}\n\n/** Back from a place to a document position, or `null` if its block is gone. */\nexport function positionOf(binding: Binding, doc: PMNode, place: Place): number | null {\n const info = findBlockById(doc, place.block)\n if (!info) return null\n const absolute = Y.createAbsolutePositionFromRelativePosition(\n Y.createRelativePositionFromJSON(place.position),\n binding.map.doc!,\n )\n let start = info.contentPos + 1\n let size = info.content.content.size\n if (place.cell) {\n const cell = info.content.type.spec.rows ? cellStart(info.content, place.cell) : null\n if (!cell) return null\n start += cell.offset\n size = cell.size\n }\n const offset =\n absolute && textOf(binding, place.block, place.cell) === absolute.type\n ? Math.min(absolute.index, size)\n : 0\n return start + offset\n}\n\nfunction cursorElement(user: { name: string; color: string }) {\n const cursor = document.createElement('span')\n cursor.className = 'dou-editor__cursor'\n cursor.style.setProperty('--dou-editor-cursor-color', user.color)\n const label = document.createElement('span')\n label.className = 'dou-editor__cursor-label'\n label.textContent = user.name\n // word joiners keep the caret from breaking the line around it\n cursor.append('⁠', label, '⁠')\n return cursor\n}\n\nconst cursorsKey = new PluginKey<DecorationSet>('dou-editor-cursors')\n\nfunction decorations(binding: Binding, awareness: AwarenessLike, state: EditorState) {\n const found: Decoration[] = []\n awareness.getStates().forEach((value, client) => {\n if (client === awareness.clientID) return\n const cursor = value.cursor as Cursor | null | undefined\n const user = value.user as { name?: string; color?: string } | undefined\n if (!cursor) return\n const anchor = positionOf(binding, state.doc, cursor.anchor)\n const head = positionOf(binding, state.doc, cursor.head)\n if (anchor === null || head === null) return\n const shown = { name: user?.name ?? '', color: user?.color ?? '#6b7280' }\n found.push(\n Decoration.widget(head, () => cursorElement(shown), {\n key: `cursor-${client}-${shown.name}-${shown.color}`,\n side: 10,\n }),\n )\n if (anchor !== head) {\n found.push(\n Decoration.inline(Math.min(anchor, head), Math.max(anchor, head), {\n class: 'dou-editor__remote-selection',\n style: `--dou-editor-cursor-color: ${shown.color}`,\n }),\n )\n }\n })\n return DecorationSet.create(state.doc, found)\n}\n\n/** Shares this user's cursor and shows everyone else's. */\nexport function cursorsPlugin(binding: Binding, awareness: AwarenessLike): Plugin {\n return new Plugin<DecorationSet>({\n key: cursorsKey,\n state: {\n init: (_config, state) => decorations(binding, awareness, state),\n apply(tr, value, _old, state) {\n if (tr.docChanged || tr.getMeta(cursorsKey)) return decorations(binding, awareness, state)\n return value\n },\n },\n props: {\n decorations(state) {\n return cursorsKey.getState(state)\n },\n },\n view(view) {\n let scheduled = false\n // others' cursors changed: redraw on the next frame, never inside a dispatch\n const onChange = () => {\n if (scheduled) return\n scheduled = true\n queueMicrotask(() => {\n scheduled = false\n if (binding.view === view) view.dispatch(view.state.tr.setMeta(cursorsKey, true))\n })\n }\n awareness.on('change', onChange)\n let last = ''\n const share = () => {\n let cursor: Cursor | null = null\n if (view.hasFocus()) {\n const { anchor, head } = view.state.selection\n const a = placeOf(binding, view.state.doc, anchor)\n const h = placeOf(binding, view.state.doc, head)\n if (a && h) cursor = { anchor: a, head: h }\n }\n const json = JSON.stringify(cursor)\n if (json === last) return\n last = json\n awareness.setLocalStateField('cursor', cursor)\n }\n const onFocus = () => share()\n view.dom.addEventListener('focus', onFocus)\n view.dom.addEventListener('blur', onFocus)\n return {\n update: share,\n destroy() {\n awareness.off('change', onChange)\n view.dom.removeEventListener('focus', onFocus)\n view.dom.removeEventListener('blur', onFocus)\n awareness.setLocalStateField('cursor', null)\n },\n }\n },\n })\n}\n"],"mappings":"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"}