@dou-ui/editor 0.0.0-stage → 0.1.0-alpha.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 (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,311 @@
1
+ import { blockFromNode, contentAttrs, createContentNode, createId, nodeFromBlock } from "./model.js";
2
+ import { blockAround, findBlockById } from "./blocks.js";
3
+ import { blockSelectionRange } from "./block-selection.js";
4
+ import { originKey } from "./editor.js";
5
+ import { NodeSelection, Selection, TextSelection } from "prosemirror-state";
6
+ import { closeHistory } from "prosemirror-history";
7
+ // src/core/transaction.ts
8
+ /**
9
+ * Set on a transaction that is an undo step of its own, for history
10
+ * providers other than the built-in one (collaboration reads it).
11
+ */
12
+ var ownStepKey = "douOwnStep";
13
+ var warn = (message) => {
14
+ if (process.env.NODE_ENV !== "production") console.warn(`[@dou-ui/editor] ${message}`);
15
+ };
16
+ /**
17
+ * Changes made by blocks and ids, applied together as one undo step. Ids
18
+ * are looked up in the document as it is after the earlier calls.
19
+ */
20
+ var EditorTransaction = class {
21
+ before;
22
+ hasCursor;
23
+ /** The ProseMirror transaction underneath, for anything the methods don't cover. */
24
+ tr;
25
+ meta;
26
+ /** `focusBlock` was called: the editor takes focus once the changes are applied. */
27
+ focused = false;
28
+ constructor(before, meta = {}, hasCursor = true) {
29
+ this.before = before;
30
+ this.hasCursor = hasCursor;
31
+ this.tr = before.tr;
32
+ this.meta = { ...meta };
33
+ }
34
+ get changed() {
35
+ return this.tr.docChanged;
36
+ }
37
+ setMeta(meta) {
38
+ Object.assign(this.meta, meta);
39
+ return this;
40
+ }
41
+ /** A block as it is now, or `null`. */
42
+ getBlock(id) {
43
+ const info = findBlockById(this.tr.doc, id);
44
+ return info ? blockFromNode(info.node) : null;
45
+ }
46
+ /**
47
+ * Insert blocks, by default after the block with the cursor (at the end
48
+ * without one). Returns the new blocks' ids; an id already in the
49
+ * document is replaced with a fresh one.
50
+ */
51
+ insertBlocks(blocks, placement) {
52
+ if (!blocks.length) return [];
53
+ const tr = this.tr;
54
+ const schema = tr.doc.type.schema;
55
+ const taken = usedIds(tr.doc);
56
+ const ids = [];
57
+ const nodes = blocks.map((block) => {
58
+ const node = nodeFromBlock(schema, freshIds(block, taken));
59
+ ids.push(node.attrs.id);
60
+ return node;
61
+ });
62
+ if (insertNodes(tr, placement ?? this.defaultPlacement(), nodes) === null) return [];
63
+ return ids;
64
+ }
65
+ /** Change a block's type, props, content or children. Returns `false` when it isn't found. */
66
+ updateBlock(id, patch) {
67
+ const tr = this.tr;
68
+ const info = findBlockById(tr.doc, id);
69
+ if (!info) {
70
+ warn(`updateBlock: no block with the id "${id}"`);
71
+ return false;
72
+ }
73
+ const schema = tr.doc.type.schema;
74
+ const typeName = patch.type ?? info.content.type.name;
75
+ const nodeType = schema.nodes[typeName];
76
+ if (!nodeType?.isInGroup("blockContent")) {
77
+ warn(`updateBlock: unknown block type "${typeName}"`);
78
+ return false;
79
+ }
80
+ const rowsSpec = !!nodeType.spec.rows;
81
+ if (patch.rows !== void 0 && !rowsSpec) {
82
+ warn(`updateBlock: a "${typeName}" block has no rows`);
83
+ return false;
84
+ }
85
+ if (patch.content !== void 0 && (rowsSpec || !nodeType.inlineContent)) {
86
+ warn(`updateBlock: a "${typeName}" block has no text content`);
87
+ return false;
88
+ }
89
+ if (patch.children) replaceChildren(tr, info, patch.children);
90
+ if (patch.type === void 0 && patch.props === void 0 && patch.content === void 0 && patch.rows === void 0) return true;
91
+ const current = findBlockById(tr.doc, id);
92
+ const content = current.content;
93
+ const props = {
94
+ ...content.attrs,
95
+ ...patch.props
96
+ };
97
+ const keepsContent = patch.content === void 0 && patch.rows === void 0;
98
+ if (keepsContent && nodeType === content.type) {
99
+ tr.setNodeMarkup(current.contentPos, void 0, contentAttrs(nodeType, props));
100
+ return true;
101
+ }
102
+ if (keepsContent && nodeType.isTextblock && content.isTextblock) {
103
+ const from = current.contentPos;
104
+ tr.setBlockType(from + 1, from + content.nodeSize - 1, nodeType, contentAttrs(nodeType, props));
105
+ return true;
106
+ }
107
+ const block = blockFromNode(current.node);
108
+ const next = createContentNode(schema, typeName, props, patch.content ?? block.content, patch.rows ?? block.rows);
109
+ tr.replaceWith(current.contentPos, current.contentPos + content.nodeSize, next);
110
+ return true;
111
+ }
112
+ /** Remove blocks with their children. The document always keeps one block. */
113
+ removeBlocks(ids) {
114
+ const infos = outermost(this.tr.doc, ids, "removeBlocks");
115
+ if (!infos.length) return false;
116
+ deleteInfos(this.tr, infos);
117
+ const tr = this.tr;
118
+ if (tr.doc.content.size === 0) tr.insert(0, emptyBlock(tr.doc.type.schema));
119
+ return true;
120
+ }
121
+ /**
122
+ * Move blocks, in the order given, to `placement`. Fails when it is
123
+ * inside one of them.
124
+ */
125
+ moveBlocks(ids, placement) {
126
+ const tr = this.tr;
127
+ const infos = outermost(tr.doc, ids, "moveBlocks");
128
+ if (!infos.length) return false;
129
+ const anchor = "before" in placement ? placement.before : "after" in placement ? placement.after : placement.parent;
130
+ if (anchor !== null && infos.some((info) => info.node.attrs.id === anchor || contains(info, tr.doc, anchor))) {
131
+ warn("moveBlocks: blocks cannot be moved next to or into themselves");
132
+ return false;
133
+ }
134
+ const nodes = infos.map((info) => info.node);
135
+ const { anchor: anchorPos, head } = tr.selection;
136
+ const nodeSelected = tr.selection instanceof NodeSelection;
137
+ const carried = infos.findIndex((info) => anchorPos > info.pos && anchorPos < info.pos + info.node.nodeSize && head > info.pos && head < info.pos + info.node.nodeSize);
138
+ const base = tr.steps.length;
139
+ const at = insertNodes(tr, placement, nodes);
140
+ if (at === null) return false;
141
+ const inserted = tr.steps.length;
142
+ deleteInfos(tr, infos, base);
143
+ if (carried >= 0) {
144
+ const info = infos[carried];
145
+ let start = at;
146
+ for (let i = 0; i < carried; i++) start += nodes[i].nodeSize;
147
+ const moved = tr.mapping.slice(inserted).map(start, 1);
148
+ const shift = (pos) => moved + (pos - info.pos);
149
+ tr.setSelection(nodeSelected ? NodeSelection.create(tr.doc, shift(anchorPos)) : TextSelection.create(tr.doc, shift(anchorPos), shift(head)));
150
+ }
151
+ return true;
152
+ }
153
+ /** Put the cursor at the start or end of a block's text (or select a block without text). */
154
+ focusBlock(id, edge = "end") {
155
+ const tr = this.tr;
156
+ const info = findBlockById(tr.doc, id);
157
+ if (!info) {
158
+ warn(`focusBlock: no block with the id "${id}"`);
159
+ return false;
160
+ }
161
+ tr.setSelection(selectionIn(tr, info, edge)).scrollIntoView();
162
+ this.focused = true;
163
+ return true;
164
+ }
165
+ /** After the selection's block, or at the end. */
166
+ defaultPlacement() {
167
+ if (!this.hasCursor && !this.tr.selectionSet) return { parent: null };
168
+ const range = this.tr.docChanged ? null : blockSelectionRange(this.before);
169
+ const id = range ? range.parent.child(range.to).attrs.id : blockAround(this.tr.selection.$to)?.node.attrs.id;
170
+ return id ? { after: id } : { parent: null };
171
+ }
172
+ };
173
+ function emptyBlock(schema) {
174
+ return schema.nodes.block.create({ id: createId() }, schema.nodes.paragraph.create());
175
+ }
176
+ function usedIds(doc) {
177
+ const ids = /* @__PURE__ */ new Set();
178
+ doc.descendants((node) => {
179
+ if (node.type.name === "block") ids.add(node.attrs.id);
180
+ return node.type.name === "block" || node.type.name === "blockGroup";
181
+ });
182
+ return ids;
183
+ }
184
+ /** The block with ids that are free, recursively. */
185
+ function freshIds(block, taken) {
186
+ let id = block.id;
187
+ if (!id || taken.has(id)) id = createId();
188
+ taken.add(id);
189
+ return {
190
+ ...block,
191
+ id,
192
+ children: block.children?.map((child) => freshIds(child, taken))
193
+ };
194
+ }
195
+ /** Whether a block with `id` sits inside `info`. */
196
+ function contains(info, doc, id) {
197
+ const found = findBlockById(doc, id);
198
+ return !!found && found.pos > info.pos && found.pos < info.pos + info.node.nodeSize;
199
+ }
200
+ /** The blocks with these ids, without those inside another one of them, in the order given. */
201
+ function outermost(doc, ids, method) {
202
+ const infos = [];
203
+ const seen = /* @__PURE__ */ new Set();
204
+ for (const id of ids) {
205
+ if (seen.has(id)) continue;
206
+ seen.add(id);
207
+ const info = findBlockById(doc, id);
208
+ if (info) infos.push(info);
209
+ else warn(`${method}: no block with the id "${id}"`);
210
+ }
211
+ return infos.filter((info) => !infos.some((other) => other !== info && info.pos > other.pos && info.pos < other.pos + other.node.nodeSize));
212
+ }
213
+ /**
214
+ * Delete blocks found in the document as it was after step `base`; a
215
+ * group left without children goes too.
216
+ */
217
+ function deleteInfos(tr, infos, base = tr.steps.length) {
218
+ const sorted = [...infos].sort((a, b) => b.pos - a.pos);
219
+ for (const info of sorted) {
220
+ const from = tr.mapping.slice(base).map(info.pos, 1);
221
+ const to = from + info.node.nodeSize;
222
+ const $from = tr.doc.resolve(from);
223
+ if ($from.parent.type.name === "blockGroup" && $from.parent.childCount === 1) tr.delete($from.before(), $from.after());
224
+ else tr.delete(from, to);
225
+ }
226
+ }
227
+ /** Insert block nodes at a placement. Returns `false` when the placement can't be found. */
228
+ function insertNodes(tr, placement, nodes) {
229
+ const schema = tr.doc.type.schema;
230
+ let at;
231
+ if ("before" in placement || "after" in placement) {
232
+ const id = "before" in placement ? placement.before : placement.after;
233
+ const info = findBlockById(tr.doc, id);
234
+ if (!info) {
235
+ warn(`no block with the id "${id}" to insert next to`);
236
+ return null;
237
+ }
238
+ at = "before" in placement ? info.pos : info.pos + info.node.nodeSize;
239
+ } else if (placement.parent === null) at = childPos(0, tr.doc, placement.index);
240
+ else {
241
+ const info = findBlockById(tr.doc, placement.parent);
242
+ if (!info) {
243
+ warn(`no block with the id "${placement.parent}" to insert into`);
244
+ return null;
245
+ }
246
+ if (!info.group) {
247
+ at = info.contentPos + info.content.nodeSize;
248
+ tr.insert(at, schema.nodes.blockGroup.create(null, nodes));
249
+ return at + 1;
250
+ }
251
+ at = childPos(info.contentPos + info.content.nodeSize + 1, info.group, placement.index);
252
+ }
253
+ tr.insert(at, nodes);
254
+ return at;
255
+ }
256
+ /** The position before the child at `index` of a container whose content starts at `start`. */
257
+ function childPos(start, container, index = container.childCount) {
258
+ const at = Math.max(0, Math.min(index, container.childCount));
259
+ let pos = start;
260
+ for (let i = 0; i < at; i++) pos += container.child(i).nodeSize;
261
+ return pos;
262
+ }
263
+ function replaceChildren(tr, info, children) {
264
+ const schema = tr.doc.type.schema;
265
+ const groupPos = info.contentPos + info.content.nodeSize;
266
+ const taken = usedIds(tr.doc);
267
+ info.group?.descendants((node) => {
268
+ if (node.type.name === "block") taken.delete(node.attrs.id);
269
+ });
270
+ const nodes = children.map((child) => nodeFromBlock(schema, freshIds(child, taken)));
271
+ const group = nodes.length ? schema.nodes.blockGroup.create(null, nodes) : null;
272
+ const end = info.group ? groupPos + info.group.nodeSize : groupPos;
273
+ if (group) tr.replaceWith(groupPos, end, group);
274
+ else if (info.group) tr.delete(groupPos, end);
275
+ }
276
+ /** A cursor at the start or end of a block's text; a node selection for a block without text. */
277
+ function selectionIn(tr, info, edge) {
278
+ const content = info.content;
279
+ const doc = tr.doc;
280
+ if (content.isTextblock) {
281
+ const pos = edge === "start" ? info.contentPos + 1 : info.contentPos + content.nodeSize - 1;
282
+ return TextSelection.create(doc, pos);
283
+ }
284
+ if (content.isAtom) return NodeSelection.create(doc, info.contentPos);
285
+ const inside = edge === "start" ? info.contentPos + 1 : info.contentPos + content.nodeSize - 1;
286
+ return Selection.near(doc.resolve(inside), edge === "start" ? 1 : -1);
287
+ }
288
+ /**
289
+ * Apply a transaction made with `transact`: one undo step of its own,
290
+ * unless `history` is `false`. Returns whether anything happened.
291
+ */
292
+ function applyEditorTransaction(view, transaction) {
293
+ const { tr, meta } = transaction;
294
+ if (!tr.docChanged && !tr.selectionSet) return false;
295
+ if (meta.origin !== void 0) tr.setMeta(originKey, meta.origin);
296
+ if (!tr.docChanged) {
297
+ view.dispatch(tr);
298
+ return true;
299
+ }
300
+ if (meta.history === false) {
301
+ tr.setMeta("addToHistory", false);
302
+ view.dispatch(tr);
303
+ return true;
304
+ }
305
+ view.dispatch(closeHistory(tr).setMeta(ownStepKey, true));
306
+ view.dispatch(closeHistory(view.state.tr));
307
+ return true;
308
+ }
309
+ export { EditorTransaction, applyEditorTransaction, ownStepKey, selectionIn };
310
+
311
+ //# sourceMappingURL=transaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction.js","names":[],"sources":["../../src/core/transaction.ts"],"sourcesContent":["import type { Node as PMNode, Schema } from 'prosemirror-model'\nimport {\n NodeSelection,\n Selection,\n TextSelection,\n type EditorState,\n type Transaction,\n} from 'prosemirror-state'\nimport { blockAround, findBlockById, type BlockInfo } from './blocks'\nimport { blockSelectionRange } from './block-selection'\nimport {\n blockFromNode,\n contentAttrs,\n createContentNode,\n createId,\n nodeFromBlock,\n type Block,\n type BlockProps,\n type InlineContent,\n type PartialBlock,\n type PartialTableRow,\n} from './model'\nimport type { ContentType } from './schema'\nimport { closeHistory } from 'prosemirror-history'\nimport type { EditorView } from 'prosemirror-view'\nimport { originKey } from './editor'\n\n/**\n * Where blocks go: right before or after a block, or among a block's\n * children (`parent: null` is the top level). `index` counts from 0 and\n * defaults to the end.\n */\nexport type BlockPlacement =\n { before: string } | { after: string } | { parent: string | null; index?: number }\n\n/** The parts of a block to change. `props` are merged into the current ones. */\nexport interface BlockPatch {\n type?: ContentType\n props?: BlockProps\n /** Inline content, or plain text. */\n content?: InlineContent[] | string\n rows?: PartialTableRow[]\n /** Replaces all children; `[]` removes them. */\n children?: PartialBlock[]\n}\n\nexport interface EditorTransactionMeta {\n /** `false` keeps the change out of undo, e.g. a fix made by the program. */\n history?: boolean\n /** Who made the change; passed on to the `transaction` event. */\n origin?: string\n}\n\n/** Where a selection is put inside a block: at the start of its text or at the end. */\nexport type BlockEdge = 'start' | 'end'\n\n/**\n * Set on a transaction that is an undo step of its own, for history\n * providers other than the built-in one (collaboration reads it).\n */\nexport const ownStepKey = 'douOwnStep'\n\nconst warn = (message: string) => {\n if (process.env.NODE_ENV !== 'production') console.warn(`[@dou-ui/editor] ${message}`)\n}\n\n/**\n * Changes made by blocks and ids, applied together as one undo step. Ids\n * are looked up in the document as it is after the earlier calls.\n */\nexport class EditorTransaction {\n /** The ProseMirror transaction underneath, for anything the methods don't cover. */\n readonly tr: Transaction\n readonly meta: EditorTransactionMeta\n /** `focusBlock` was called: the editor takes focus once the changes are applied. */\n focused = false\n\n constructor(\n /** The state the changes start from. */\n readonly before: EditorState,\n meta: EditorTransactionMeta = {},\n /**\n * Whether the selection was placed by the user or the program. An editor\n * that never had focus has its selection at the start, which is no cursor.\n */\n private readonly hasCursor = true,\n ) {\n this.tr = before.tr\n this.meta = { ...meta }\n }\n\n get changed(): boolean {\n return this.tr.docChanged\n }\n\n setMeta(meta: EditorTransactionMeta): this {\n Object.assign(this.meta, meta)\n return this\n }\n\n /** A block as it is now, or `null`. */\n getBlock(id: string): Block | null {\n const info = findBlockById(this.tr.doc, id)\n return info ? blockFromNode(info.node) : null\n }\n\n /**\n * Insert blocks, by default after the block with the cursor (at the end\n * without one). Returns the new blocks' ids; an id already in the\n * document is replaced with a fresh one.\n */\n insertBlocks(blocks: PartialBlock[], placement?: BlockPlacement): string[] {\n if (!blocks.length) return []\n const tr = this.tr\n const schema = tr.doc.type.schema\n const taken = usedIds(tr.doc)\n const ids: string[] = []\n const nodes = blocks.map((block) => {\n const node = nodeFromBlock(schema, freshIds(block, taken))\n ids.push(node.attrs.id as string)\n return node\n })\n const target = placement ?? this.defaultPlacement()\n if (insertNodes(tr, target, nodes) === null) return []\n return ids\n }\n\n /** Change a block's type, props, content or children. Returns `false` when it isn't found. */\n updateBlock(id: string, patch: BlockPatch): boolean {\n const tr = this.tr\n const info = findBlockById(tr.doc, id)\n if (!info) {\n warn(`updateBlock: no block with the id \"${id}\"`)\n return false\n }\n const schema = tr.doc.type.schema\n // check everything before changing anything: a refused patch changes nothing\n const typeName = patch.type ?? info.content.type.name\n const nodeType = schema.nodes[typeName]\n if (!nodeType?.isInGroup('blockContent')) {\n warn(`updateBlock: unknown block type \"${typeName}\"`)\n return false\n }\n const rowsSpec = !!nodeType.spec.rows\n if (patch.rows !== undefined && !rowsSpec) {\n warn(`updateBlock: a \"${typeName}\" block has no rows`)\n return false\n }\n if (patch.content !== undefined && (rowsSpec || !nodeType.inlineContent)) {\n warn(`updateBlock: a \"${typeName}\" block has no text content`)\n return false\n }\n if (patch.children) replaceChildren(tr, info, patch.children)\n if (\n patch.type === undefined &&\n patch.props === undefined &&\n patch.content === undefined &&\n patch.rows === undefined\n )\n return true\n const current = findBlockById(tr.doc, id)!\n const content = current.content\n const props = { ...content.attrs, ...patch.props }\n const keepsContent = patch.content === undefined && patch.rows === undefined\n // keep the text node for node when it can stay: collaborators typing in it keep their place\n if (keepsContent && nodeType === content.type) {\n tr.setNodeMarkup(current.contentPos, undefined, contentAttrs(nodeType, props))\n return true\n }\n if (keepsContent && nodeType.isTextblock && content.isTextblock) {\n const from = current.contentPos\n tr.setBlockType(\n from + 1,\n from + content.nodeSize - 1,\n nodeType,\n contentAttrs(nodeType, props),\n )\n return true\n }\n const block = blockFromNode(current.node)\n const next = createContentNode(\n schema,\n typeName,\n props,\n patch.content ?? block.content,\n patch.rows ?? block.rows,\n )\n tr.replaceWith(current.contentPos, current.contentPos + content.nodeSize, next)\n return true\n }\n\n /** Remove blocks with their children. The document always keeps one block. */\n removeBlocks(ids: string[]): boolean {\n const infos = outermost(this.tr.doc, ids, 'removeBlocks')\n if (!infos.length) return false\n deleteInfos(this.tr, infos)\n const tr = this.tr\n if (tr.doc.content.size === 0) tr.insert(0, emptyBlock(tr.doc.type.schema))\n return true\n }\n\n /**\n * Move blocks, in the order given, to `placement`. Fails when it is\n * inside one of them.\n */\n moveBlocks(ids: string[], placement: BlockPlacement): boolean {\n const tr = this.tr\n const infos = outermost(tr.doc, ids, 'moveBlocks')\n if (!infos.length) return false\n const anchor =\n 'before' in placement\n ? placement.before\n : 'after' in placement\n ? placement.after\n : placement.parent\n if (\n anchor !== null &&\n infos.some((info) => info.node.attrs.id === anchor || contains(info, tr.doc, anchor))\n ) {\n warn('moveBlocks: blocks cannot be moved next to or into themselves')\n return false\n }\n // insert the moved blocks first, then delete the originals: the document never empties\n const nodes = infos.map((info) => info.node)\n // a selection inside a moved block goes along with it\n const { anchor: anchorPos, head } = tr.selection\n const nodeSelected = tr.selection instanceof NodeSelection\n const carried = infos.findIndex(\n (info) =>\n anchorPos > info.pos &&\n anchorPos < info.pos + info.node.nodeSize &&\n head > info.pos &&\n head < info.pos + info.node.nodeSize,\n )\n const base = tr.steps.length\n const at = insertNodes(tr, placement, nodes)\n if (at === null) return false\n const inserted = tr.steps.length\n deleteInfos(tr, infos, base)\n if (carried >= 0) {\n const info = infos[carried]!\n let start = at\n for (let i = 0; i < carried; i++) start += nodes[i]!.nodeSize\n const moved = tr.mapping.slice(inserted).map(start, 1)\n const shift = (pos: number) => moved + (pos - info.pos)\n tr.setSelection(\n nodeSelected\n ? NodeSelection.create(tr.doc, shift(anchorPos))\n : TextSelection.create(tr.doc, shift(anchorPos), shift(head)),\n )\n }\n return true\n }\n\n /** Put the cursor at the start or end of a block's text (or select a block without text). */\n focusBlock(id: string, edge: BlockEdge = 'end'): boolean {\n const tr = this.tr\n const info = findBlockById(tr.doc, id)\n if (!info) {\n warn(`focusBlock: no block with the id \"${id}\"`)\n return false\n }\n tr.setSelection(selectionIn(tr, info, edge)).scrollIntoView()\n this.focused = true\n return true\n }\n\n /** After the selection's block, or at the end. */\n private defaultPlacement(): BlockPlacement {\n if (!this.hasCursor && !this.tr.selectionSet) return { parent: null }\n // selected blocks are only known before the first change\n const range = this.tr.docChanged ? null : blockSelectionRange(this.before)\n const id = range\n ? (range.parent.child(range.to).attrs.id as string)\n : (blockAround(this.tr.selection.$to)?.node.attrs.id as string | undefined)\n return id ? { after: id } : { parent: null }\n }\n}\n\nfunction emptyBlock(schema: Schema): PMNode {\n return schema.nodes.block!.create({ id: createId() }, schema.nodes.paragraph!.create())\n}\n\nfunction usedIds(doc: PMNode): Set<string> {\n const ids = new Set<string>()\n doc.descendants((node) => {\n if (node.type.name === 'block') ids.add(node.attrs.id as string)\n return node.type.name === 'block' || node.type.name === 'blockGroup'\n })\n return ids\n}\n\n/** The block with ids that are free, recursively. */\nfunction freshIds(block: PartialBlock, taken: Set<string>): PartialBlock {\n let id = block.id\n if (!id || taken.has(id)) id = createId()\n taken.add(id)\n return {\n ...block,\n id,\n children: block.children?.map((child) => freshIds(child, taken)),\n }\n}\n\n/** Whether a block with `id` sits inside `info`. */\nfunction contains(info: BlockInfo, doc: PMNode, id: string): boolean {\n const found = findBlockById(doc, id)\n return !!found && found.pos > info.pos && found.pos < info.pos + info.node.nodeSize\n}\n\n/** The blocks with these ids, without those inside another one of them, in the order given. */\nfunction outermost(doc: PMNode, ids: string[], method: string): BlockInfo[] {\n const infos: BlockInfo[] = []\n const seen = new Set<string>()\n for (const id of ids) {\n if (seen.has(id)) continue\n seen.add(id)\n const info = findBlockById(doc, id)\n if (info) infos.push(info)\n else warn(`${method}: no block with the id \"${id}\"`)\n }\n return infos.filter(\n (info) =>\n !infos.some(\n (other) =>\n other !== info && info.pos > other.pos && info.pos < other.pos + other.node.nodeSize,\n ),\n )\n}\n\n/**\n * Delete blocks found in the document as it was after step `base`; a\n * group left without children goes too.\n */\nfunction deleteInfos(tr: Transaction, infos: BlockInfo[], base = tr.steps.length) {\n // last first, so the earlier positions stay valid\n // oxlint-disable-next-line unicorn/no-array-sort -- sorts a copy\n const sorted = [...infos].sort((a, b) => b.pos - a.pos)\n for (const info of sorted) {\n // a block moved right in front of this one pushes it on (`1`)\n const from = tr.mapping.slice(base).map(info.pos, 1)\n const to = from + info.node.nodeSize\n const $from = tr.doc.resolve(from)\n if ($from.parent.type.name === 'blockGroup' && $from.parent.childCount === 1)\n tr.delete($from.before(), $from.after())\n else tr.delete(from, to)\n }\n}\n\n/** Insert block nodes at a placement. Returns `false` when the placement can't be found. */\nfunction insertNodes(tr: Transaction, placement: BlockPlacement, nodes: PMNode[]): number | null {\n const schema = tr.doc.type.schema\n let at: number\n if ('before' in placement || 'after' in placement) {\n const id = 'before' in placement ? placement.before : placement.after\n const info = findBlockById(tr.doc, id)\n if (!info) {\n warn(`no block with the id \"${id}\" to insert next to`)\n return null\n }\n at = 'before' in placement ? info.pos : info.pos + info.node.nodeSize\n } else if (placement.parent === null) {\n at = childPos(0, tr.doc, placement.index)\n } else {\n const info = findBlockById(tr.doc, placement.parent)\n if (!info) {\n warn(`no block with the id \"${placement.parent}\" to insert into`)\n return null\n }\n if (!info.group) {\n at = info.contentPos + info.content.nodeSize\n tr.insert(at, schema.nodes.blockGroup!.create(null, nodes))\n return at + 1\n }\n at = childPos(info.contentPos + info.content.nodeSize + 1, info.group, placement.index)\n }\n tr.insert(at, nodes)\n return at\n}\n\n/** The position before the child at `index` of a container whose content starts at `start`. */\nfunction childPos(start: number, container: PMNode, index = container.childCount) {\n const at = Math.max(0, Math.min(index, container.childCount))\n let pos = start\n for (let i = 0; i < at; i++) pos += container.child(i).nodeSize\n return pos\n}\n\nfunction replaceChildren(tr: Transaction, info: BlockInfo, children: PartialBlock[]) {\n const schema = tr.doc.type.schema\n const groupPos = info.contentPos + info.content.nodeSize\n const taken = usedIds(tr.doc)\n // the old children's ids are free again\n info.group?.descendants((node) => {\n if (node.type.name === 'block') taken.delete(node.attrs.id as string)\n })\n const nodes = children.map((child) => nodeFromBlock(schema, freshIds(child, taken)))\n const group = nodes.length ? schema.nodes.blockGroup!.create(null, nodes) : null\n const end = info.group ? groupPos + info.group.nodeSize : groupPos\n if (group) tr.replaceWith(groupPos, end, group)\n else if (info.group) tr.delete(groupPos, end)\n}\n\n/** A cursor at the start or end of a block's text; a node selection for a block without text. */\nexport function selectionIn(tr: Transaction, info: BlockInfo, edge: BlockEdge): Selection {\n const content = info.content\n const doc = tr.doc\n if (content.isTextblock) {\n const pos = edge === 'start' ? info.contentPos + 1 : info.contentPos + content.nodeSize - 1\n return TextSelection.create(doc, pos)\n }\n if (content.isAtom) return NodeSelection.create(doc, info.contentPos)\n // a table: the first or last cell's text\n const inside = edge === 'start' ? info.contentPos + 1 : info.contentPos + content.nodeSize - 1\n return Selection.near(doc.resolve(inside), edge === 'start' ? 1 : -1)\n}\n\n/**\n * Apply a transaction made with `transact`: one undo step of its own,\n * unless `history` is `false`. Returns whether anything happened.\n */\nexport function applyEditorTransaction(view: EditorView, transaction: EditorTransaction): boolean {\n const { tr, meta } = transaction\n if (!tr.docChanged && !tr.selectionSet) return false\n if (meta.origin !== undefined) tr.setMeta(originKey, meta.origin)\n if (!tr.docChanged) {\n view.dispatch(tr)\n return true\n }\n if (meta.history === false) {\n tr.setMeta('addToHistory', false)\n view.dispatch(tr)\n return true\n }\n view.dispatch(closeHistory(tr).setMeta(ownStepKey, true))\n // and typing right after it starts another step\n view.dispatch(closeHistory(view.state.tr))\n return true\n}\n"],"mappings":"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"}
@@ -0,0 +1,177 @@
1
+ import { defineComponent, h, inject, onMounted, provide, render, shallowReactive, shallowRef } from "vue";
2
+ // src/define-block.ts
3
+ var contentKey = Symbol("DouEditorContent");
4
+ /** Where a block component's text goes. */
5
+ var EditorContent = defineComponent({
6
+ name: "DouEditorContent",
7
+ props: { as: {
8
+ type: String,
9
+ default: "div"
10
+ } },
11
+ setup(props) {
12
+ const content = inject(contentKey, null);
13
+ const host = shallowRef(null);
14
+ onMounted(() => {
15
+ if (content && host.value) host.value.append(content);
16
+ });
17
+ return () => h(props.as, {
18
+ ref: host,
19
+ class: "dou-editor__text"
20
+ });
21
+ }
22
+ });
23
+ /** Store props as `data-prop-*` attributes, so copy and paste keep them. */
24
+ var dataAttrs = (props) => Object.fromEntries(Object.entries(props).map(([name, value]) => [`data-prop-${name}`, JSON.stringify(value)]));
25
+ function readDataAttrs(dom, names) {
26
+ const attrs = {};
27
+ for (const name of names) {
28
+ const raw = dom.getAttribute(`data-prop-${name}`);
29
+ if (raw === null) continue;
30
+ try {
31
+ attrs[name] = JSON.parse(raw);
32
+ } catch {}
33
+ }
34
+ return attrs;
35
+ }
36
+ /** Renders components inside an app, with values provided to them. */
37
+ function componentMounter(appContext, provides = []) {
38
+ return (component, props, element) => {
39
+ const Root = defineComponent({ setup() {
40
+ for (const [key, value] of provides) provide(key, value);
41
+ return () => h(component, { ...props });
42
+ } });
43
+ const vnode = h(Root);
44
+ vnode.appContext = appContext;
45
+ render(vnode, element);
46
+ return () => render(null, element);
47
+ };
48
+ }
49
+ /**
50
+ * A node view that renders a Vue component. Everything the component shows
51
+ * besides the text is outside the editable content, so its buttons and
52
+ * inputs work as usual.
53
+ */
54
+ function vueNodeView(component, appContext, provides = []) {
55
+ return (node, view, getPos) => {
56
+ const idOf = () => {
57
+ const pos = getPos();
58
+ if (pos === void 0) return "";
59
+ return view.state.doc.resolve(pos).parent.attrs.id ?? "";
60
+ };
61
+ const type = node.type.name;
62
+ const dom = document.createElement("div");
63
+ dom.className = `dou-editor__content dou-editor__content--${type} dou-editor__content--custom`;
64
+ dom.dataset.contentType = type;
65
+ const contentElement = node.isTextblock ? document.createElement("div") : null;
66
+ const state = shallowReactive({
67
+ block: {
68
+ id: idOf(),
69
+ type,
70
+ props: { ...node.attrs }
71
+ },
72
+ selected: false,
73
+ editable: view.editable,
74
+ updateProps(props) {
75
+ const pos = getPos();
76
+ if (pos === void 0) return;
77
+ const current = view.state.doc.nodeAt(pos);
78
+ if (!current) return;
79
+ view.dispatch(view.state.tr.setNodeMarkup(pos, void 0, {
80
+ ...current.attrs,
81
+ ...props
82
+ }));
83
+ }
84
+ });
85
+ const Root = defineComponent({ setup() {
86
+ provide(contentKey, contentElement);
87
+ for (const [key, value] of provides) provide(key, value);
88
+ return () => h(component, { ...state });
89
+ } });
90
+ const vnode = h(Root);
91
+ vnode.appContext = appContext;
92
+ render(vnode, dom);
93
+ let current = node;
94
+ return {
95
+ dom,
96
+ contentDOM: contentElement,
97
+ update(next) {
98
+ if (next.type !== current.type) return false;
99
+ current = next;
100
+ state.block = {
101
+ id: idOf(),
102
+ type,
103
+ props: { ...next.attrs }
104
+ };
105
+ state.editable = view.editable;
106
+ return true;
107
+ },
108
+ selectNode() {
109
+ state.selected = true;
110
+ },
111
+ deselectNode() {
112
+ state.selected = false;
113
+ },
114
+ stopEvent: (event) => {
115
+ const target = event.target;
116
+ if (contentElement?.contains(target)) return false;
117
+ return !!target.closest?.("input, textarea, select, button, a, [contenteditable=\"true\"], [data-editor-control]");
118
+ },
119
+ ignoreMutation: (mutation) => !contentElement || !contentElement.contains(mutation.target),
120
+ destroy() {
121
+ render(null, dom);
122
+ }
123
+ };
124
+ };
125
+ }
126
+ /**
127
+ * Declare a block type rendered by a Vue component (or by plain DOM), with
128
+ * its props stored in the document. Returns an extension for `extensions`.
129
+ */
130
+ function defineBlock(options) {
131
+ const { type } = options;
132
+ const props = options.props ?? {};
133
+ const names = Object.keys(props);
134
+ const inline = (options.content ?? "inline") === "inline";
135
+ const spec = {
136
+ attrs: Object.fromEntries(names.map((name) => [name, { default: props[name] }])),
137
+ ...inline ? {
138
+ content: "inline*",
139
+ defining: true
140
+ } : {
141
+ atom: true,
142
+ selectable: true
143
+ },
144
+ continues: options.continues,
145
+ component: options.component,
146
+ parseDOM: [{
147
+ tag: `div[data-content-type="${type}"]`,
148
+ getAttrs: (dom) => readDataAttrs(dom, names)
149
+ }],
150
+ toDOM: (node) => {
151
+ const attrs = {
152
+ class: `dou-editor__content dou-editor__content--${type}`,
153
+ "data-content-type": type,
154
+ ...dataAttrs(node.attrs)
155
+ };
156
+ return inline ? [
157
+ "div",
158
+ attrs,
159
+ 0
160
+ ] : ["div", attrs];
161
+ },
162
+ ...options.spec
163
+ };
164
+ const menu = options.menu;
165
+ return {
166
+ name: `block:${type}`,
167
+ blocks: { [type]: spec },
168
+ blockOptions: menu ? [{
169
+ ...menu,
170
+ key: menu.key ?? type,
171
+ type
172
+ }] : []
173
+ };
174
+ }
175
+ export { EditorContent, componentMounter, defineBlock, vueNodeView };
176
+
177
+ //# sourceMappingURL=define-block.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-block.js","names":[],"sources":["../src/define-block.ts"],"sourcesContent":["import type { NodeSpec } from 'prosemirror-model'\nimport type { NodeView, NodeViewConstructor } from 'prosemirror-view'\nimport {\n defineComponent,\n h,\n inject,\n onMounted,\n provide,\n render,\n shallowReactive,\n shallowRef,\n type AppContext,\n type Component,\n type InjectionKey,\n type PropType,\n} from 'vue'\nimport type { BlockOption, EditorExtension, Label, MountComponent } from './core/extension'\nimport type { BlockProps } from './core/model'\n\n/** What a block's Vue component receives. */\nexport interface BlockViewProps {\n /** The block's id, type and current props. */\n block: { id: string; type: string; props: BlockProps }\n /** Change props; the change goes into the document, the undo history and collaboration. */\n updateProps: (props: BlockProps) => void\n /** The block is selected as a whole (a block without text was clicked). */\n selected: boolean\n editable: boolean\n}\n\nexport interface DefineBlockOptions {\n type: string\n /** The props and their defaults. They are stored in the document, as `Block.props`. */\n props?: Record<string, unknown>\n /** `inline` holds formatted text; `none` has no text (an image, an embed). Defaults to `inline`. */\n content?: 'inline' | 'none'\n /** Renders the block. Put `<EditorContent />` where the text goes. */\n component?: Component\n /** `Enter` at the end continues with the same type, like a list. */\n continues?: boolean\n /** Entries for the slash menu and the “turn into” menus; one is made from `menu` when given. */\n menu?: Omit<BlockOption, 'key' | 'type'> & { key?: string }\n /** Override the ProseMirror spec, e.g. `parseDOM` for pasting HTML from elsewhere. */\n spec?: Partial<NodeSpec>\n}\n\nconst contentKey: InjectionKey<HTMLElement | null> = Symbol('DouEditorContent')\n\n/** Where a block component's text goes. */\nexport const EditorContent = defineComponent({\n name: 'DouEditorContent',\n props: { as: { type: String as PropType<string>, default: 'div' } },\n setup(props) {\n const content = inject(contentKey, null)\n const host = shallowRef<HTMLElement | null>(null)\n onMounted(() => {\n if (content && host.value) host.value.append(content)\n })\n return () => h(props.as, { ref: host, class: 'dou-editor__text' })\n },\n})\n\n/** Store props as `data-prop-*` attributes, so copy and paste keep them. */\nconst dataAttrs = (props: Record<string, unknown>) =>\n Object.fromEntries(\n Object.entries(props).map(([name, value]) => [`data-prop-${name}`, JSON.stringify(value)]),\n )\n\nfunction readDataAttrs(dom: HTMLElement, names: string[]) {\n const attrs: Record<string, unknown> = {}\n for (const name of names) {\n const raw = dom.getAttribute(`data-prop-${name}`)\n if (raw === null) continue\n try {\n attrs[name] = JSON.parse(raw)\n } catch {\n // ignore attributes that are not ours\n }\n }\n return attrs\n}\n\n/** Renders components inside an app, with values provided to them. */\nexport function componentMounter(\n appContext: AppContext | null,\n provides: [InjectionKey<unknown>, unknown][] = [],\n): MountComponent {\n return (component, props, element) => {\n const Root = defineComponent({\n setup() {\n for (const [key, value] of provides) provide(key, value)\n return () => h(component, { ...props })\n },\n })\n const vnode = h(Root)\n vnode.appContext = appContext\n render(vnode, element)\n return () => render(null, element)\n }\n}\n\n/**\n * A node view that renders a Vue component. Everything the component shows\n * besides the text is outside the editable content, so its buttons and\n * inputs work as usual.\n */\nexport function vueNodeView(\n component: Component,\n appContext: AppContext | null,\n provides: [InjectionKey<unknown>, unknown][] = [],\n): NodeViewConstructor {\n return (node, view, getPos) => {\n // the id lives on the block around the content node\n const idOf = () => {\n const pos = getPos()\n if (pos === undefined) return ''\n return (view.state.doc.resolve(pos).parent.attrs.id as string | null) ?? ''\n }\n const type = node.type.name\n const dom = document.createElement('div')\n dom.className = `dou-editor__content dou-editor__content--${type} dou-editor__content--custom`\n dom.dataset.contentType = type\n const contentElement = node.isTextblock ? document.createElement('div') : null\n const state = shallowReactive<BlockViewProps>({\n block: { id: idOf(), type, props: { ...node.attrs } },\n selected: false,\n editable: view.editable,\n updateProps(props) {\n const pos = getPos()\n if (pos === undefined) return\n const current = view.state.doc.nodeAt(pos)\n if (!current) return\n view.dispatch(view.state.tr.setNodeMarkup(pos, undefined, { ...current.attrs, ...props }))\n },\n })\n const Root = defineComponent({\n setup() {\n provide(contentKey, contentElement)\n for (const [key, value] of provides) provide(key, value)\n return () => h(component, { ...state })\n },\n })\n const vnode = h(Root)\n vnode.appContext = appContext\n render(vnode, dom)\n let current = node\n const nodeView: NodeView = {\n dom,\n contentDOM: contentElement,\n update(next) {\n if (next.type !== current.type) return false\n current = next\n state.block = { id: idOf(), type, props: { ...next.attrs } }\n state.editable = view.editable\n return true\n },\n selectNode() {\n state.selected = true\n },\n deselectNode() {\n state.selected = false\n },\n // the component's own controls (and anything marked `data-editor-control`)\n // handle their events; the editor gets the rest, so a click on a block\n // without text still selects it\n stopEvent: (event) => {\n const target = event.target as HTMLElement\n if (contentElement?.contains(target)) return false\n return !!target.closest?.(\n 'input, textarea, select, button, a, [contenteditable=\"true\"], [data-editor-control]',\n )\n },\n ignoreMutation: (mutation) => !contentElement || !contentElement.contains(mutation.target),\n destroy() {\n render(null, dom)\n },\n }\n return nodeView\n }\n}\n\n/**\n * Declare a block type rendered by a Vue component (or by plain DOM), with\n * its props stored in the document. Returns an extension for `extensions`.\n */\nexport function defineBlock(options: DefineBlockOptions): EditorExtension {\n const { type } = options\n const props = options.props ?? {}\n const names = Object.keys(props)\n const inline = (options.content ?? 'inline') === 'inline'\n const spec: NodeSpec = {\n attrs: Object.fromEntries(names.map((name) => [name, { default: props[name] }])),\n ...(inline ? { content: 'inline*', defining: true } : { atom: true, selectable: true }),\n continues: options.continues,\n component: options.component,\n parseDOM: [\n {\n tag: `div[data-content-type=\"${type}\"]`,\n getAttrs: (dom) => readDataAttrs(dom as HTMLElement, names),\n },\n ],\n toDOM: (node) => {\n const attrs = {\n class: `dou-editor__content dou-editor__content--${type}`,\n 'data-content-type': type,\n ...dataAttrs(node.attrs),\n }\n return inline ? ['div', attrs, 0] : ['div', attrs]\n },\n ...options.spec,\n }\n const menu = options.menu\n return {\n name: `block:${type}`,\n blocks: { [type]: spec },\n blockOptions: menu ? [{ ...menu, key: menu.key ?? type, type }] : [],\n }\n}\n\nexport type { Label }\n"],"mappings":"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"}