smartrte-core 0.2.1 → 1.0.0-beta.2

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 (329) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +23 -0
  3. package/dist/command.d.ts +8 -8
  4. package/dist/editor.d.ts +32 -0
  5. package/dist/editor.js +121 -0
  6. package/dist/foundation/annotations/index.d.ts +2 -0
  7. package/dist/foundation/annotations/index.js +2 -0
  8. package/dist/foundation/annotations/range.d.ts +54 -0
  9. package/dist/foundation/annotations/range.js +158 -0
  10. package/dist/foundation/annotations/types.d.ts +33 -0
  11. package/dist/foundation/annotations/types.js +1 -0
  12. package/dist/foundation/atom/commands.d.ts +11 -0
  13. package/dist/foundation/atom/commands.js +118 -0
  14. package/dist/foundation/atom/composition.d.ts +8 -0
  15. package/dist/foundation/atom/composition.js +22 -0
  16. package/dist/foundation/atom/declarations.d.ts +2 -0
  17. package/dist/foundation/atom/declarations.js +17 -0
  18. package/dist/foundation/atom/formats.d.ts +24 -0
  19. package/dist/foundation/atom/formats.js +105 -0
  20. package/dist/foundation/atom/index.d.ts +10 -0
  21. package/dist/foundation/atom/index.js +10 -0
  22. package/dist/foundation/atom/lifecycle.d.ts +12 -0
  23. package/dist/foundation/atom/lifecycle.js +41 -0
  24. package/dist/foundation/atom/persistence.d.ts +3 -0
  25. package/dist/foundation/atom/persistence.js +20 -0
  26. package/dist/foundation/atom/plugin.d.ts +20 -0
  27. package/dist/foundation/atom/plugin.js +55 -0
  28. package/dist/foundation/atom/schema.d.ts +3 -0
  29. package/dist/foundation/atom/schema.js +29 -0
  30. package/dist/foundation/atom/security.d.ts +10 -0
  31. package/dist/foundation/atom/security.js +20 -0
  32. package/dist/foundation/atom/selection.d.ts +3 -0
  33. package/dist/foundation/atom/selection.js +4 -0
  34. package/dist/foundation/atom/types.d.ts +47 -0
  35. package/dist/foundation/atom/types.js +1 -0
  36. package/dist/foundation/block/commands.d.ts +17 -0
  37. package/dist/foundation/block/commands.js +197 -0
  38. package/dist/foundation/block/formats.d.ts +33 -0
  39. package/dist/foundation/block/formats.js +57 -0
  40. package/dist/foundation/block/index.d.ts +5 -0
  41. package/dist/foundation/block/index.js +5 -0
  42. package/dist/foundation/block/input.d.ts +19 -0
  43. package/dist/foundation/block/input.js +67 -0
  44. package/dist/foundation/block/plugin.d.ts +26 -0
  45. package/dist/foundation/block/plugin.js +79 -0
  46. package/dist/foundation/block/schema.d.ts +49 -0
  47. package/dist/foundation/block/schema.js +27 -0
  48. package/dist/foundation/block/types.d.ts +40 -0
  49. package/dist/foundation/block/types.js +1 -0
  50. package/dist/foundation/boundaries.d.ts +11 -0
  51. package/dist/foundation/boundaries.js +117 -0
  52. package/dist/foundation/clipboard/detection.d.ts +3 -0
  53. package/dist/foundation/clipboard/detection.js +29 -0
  54. package/dist/foundation/clipboard/diagnostics.d.ts +24 -0
  55. package/dist/foundation/clipboard/diagnostics.js +59 -0
  56. package/dist/foundation/clipboard/index.d.ts +8 -0
  57. package/dist/foundation/clipboard/index.js +8 -0
  58. package/dist/foundation/clipboard/insertion.d.ts +22 -0
  59. package/dist/foundation/clipboard/insertion.js +210 -0
  60. package/dist/foundation/clipboard/normalizers.d.ts +8 -0
  61. package/dist/foundation/clipboard/normalizers.js +229 -0
  62. package/dist/foundation/clipboard/pipeline.d.ts +13 -0
  63. package/dist/foundation/clipboard/pipeline.js +58 -0
  64. package/dist/foundation/clipboard/sanitizer.d.ts +3 -0
  65. package/dist/foundation/clipboard/sanitizer.js +58 -0
  66. package/dist/foundation/clipboard/serialization.d.ts +5 -0
  67. package/dist/foundation/clipboard/serialization.js +33 -0
  68. package/dist/foundation/clipboard/transfer.d.ts +11 -0
  69. package/dist/foundation/clipboard/transfer.js +108 -0
  70. package/dist/foundation/clipboard/types.d.ts +65 -0
  71. package/dist/foundation/clipboard/types.js +1 -0
  72. package/dist/foundation/collab/index.d.ts +3 -0
  73. package/dist/foundation/collab/index.js +3 -0
  74. package/dist/foundation/collab/rebase.d.ts +28 -0
  75. package/dist/foundation/collab/rebase.js +82 -0
  76. package/dist/foundation/collab/transform.d.ts +73 -0
  77. package/dist/foundation/collab/transform.js +251 -0
  78. package/dist/foundation/collab/types.d.ts +52 -0
  79. package/dist/foundation/collab/types.js +1 -0
  80. package/dist/foundation/comments/fromSelection.d.ts +16 -0
  81. package/dist/foundation/comments/fromSelection.js +24 -0
  82. package/dist/foundation/comments/index.d.ts +3 -0
  83. package/dist/foundation/comments/index.js +3 -0
  84. package/dist/foundation/comments/rebase.d.ts +31 -0
  85. package/dist/foundation/comments/rebase.js +43 -0
  86. package/dist/foundation/comments/types.d.ts +37 -0
  87. package/dist/foundation/comments/types.js +1 -0
  88. package/dist/foundation/diff/documentDiff.d.ts +12 -0
  89. package/dist/foundation/diff/documentDiff.js +148 -0
  90. package/dist/foundation/diff/index.d.ts +3 -0
  91. package/dist/foundation/diff/index.js +3 -0
  92. package/dist/foundation/diff/textDiff.d.ts +12 -0
  93. package/dist/foundation/diff/textDiff.js +114 -0
  94. package/dist/foundation/diff/types.d.ts +74 -0
  95. package/dist/foundation/diff/types.js +1 -0
  96. package/dist/foundation/editor.d.ts +138 -0
  97. package/dist/foundation/editor.js +398 -0
  98. package/dist/foundation/formats/codec.d.ts +45 -0
  99. package/dist/foundation/formats/codec.js +1 -0
  100. package/dist/foundation/formats/docx/export.d.ts +4 -0
  101. package/dist/foundation/formats/docx/export.js +285 -0
  102. package/dist/foundation/formats/docx/import.d.ts +9 -0
  103. package/dist/foundation/formats/docx/import.js +37 -0
  104. package/dist/foundation/formats/docx/nestingGuard.d.ts +40 -0
  105. package/dist/foundation/formats/docx/nestingGuard.js +66 -0
  106. package/dist/foundation/formats/docx/portableAtoms.d.ts +4 -0
  107. package/dist/foundation/formats/docx/portableAtoms.js +66 -0
  108. package/dist/foundation/formats/docx/styledImport.d.ts +10 -0
  109. package/dist/foundation/formats/docx/styledImport.js +205 -0
  110. package/dist/foundation/formats/featureCodecs.d.ts +37 -0
  111. package/dist/foundation/formats/featureCodecs.js +69 -0
  112. package/dist/foundation/formats/fidelity.d.ts +31 -0
  113. package/dist/foundation/formats/fidelity.js +123 -0
  114. package/dist/foundation/formats/index.d.ts +7 -0
  115. package/dist/foundation/formats/index.js +7 -0
  116. package/dist/foundation/formats/pdf/format.d.ts +29 -0
  117. package/dist/foundation/formats/pdf/format.js +190 -0
  118. package/dist/foundation/history.d.ts +20 -0
  119. package/dist/foundation/history.js +121 -0
  120. package/dist/foundation/identity.d.ts +8 -0
  121. package/dist/foundation/identity.js +30 -0
  122. package/dist/foundation/index.d.ts +30 -0
  123. package/dist/foundation/index.js +30 -0
  124. package/dist/foundation/list/commands.d.ts +28 -0
  125. package/dist/foundation/list/commands.js +422 -0
  126. package/dist/foundation/list/formats.d.ts +21 -0
  127. package/dist/foundation/list/formats.js +813 -0
  128. package/dist/foundation/list/index.d.ts +8 -0
  129. package/dist/foundation/list/index.js +8 -0
  130. package/dist/foundation/list/input.d.ts +35 -0
  131. package/dist/foundation/list/input.js +320 -0
  132. package/dist/foundation/list/plugin.d.ts +21 -0
  133. package/dist/foundation/list/plugin.js +71 -0
  134. package/dist/foundation/list/presets.d.ts +16 -0
  135. package/dist/foundation/list/presets.js +21 -0
  136. package/dist/foundation/list/schema.d.ts +35 -0
  137. package/dist/foundation/list/schema.js +29 -0
  138. package/dist/foundation/list/shadow.d.ts +54 -0
  139. package/dist/foundation/list/shadow.js +75 -0
  140. package/dist/foundation/list/shadowCorpus.d.ts +10 -0
  141. package/dist/foundation/list/shadowCorpus.js +41 -0
  142. package/dist/foundation/list/types.d.ts +50 -0
  143. package/dist/foundation/list/types.js +1 -0
  144. package/dist/foundation/mapping.d.ts +11 -0
  145. package/dist/foundation/mapping.js +40 -0
  146. package/dist/foundation/marks/canonical.d.ts +9 -0
  147. package/dist/foundation/marks/canonical.js +72 -0
  148. package/dist/foundation/marks/commands.d.ts +21 -0
  149. package/dist/foundation/marks/commands.js +136 -0
  150. package/dist/foundation/marks/dom.d.ts +2 -0
  151. package/dist/foundation/marks/dom.js +46 -0
  152. package/dist/foundation/marks/editor.d.ts +8 -0
  153. package/dist/foundation/marks/editor.js +50 -0
  154. package/dist/foundation/marks/formats.d.ts +37 -0
  155. package/dist/foundation/marks/formats.js +49 -0
  156. package/dist/foundation/marks/hardBreak.d.ts +8 -0
  157. package/dist/foundation/marks/hardBreak.js +80 -0
  158. package/dist/foundation/marks/index.d.ts +11 -0
  159. package/dist/foundation/marks/index.js +11 -0
  160. package/dist/foundation/marks/normalization.d.ts +2 -0
  161. package/dist/foundation/marks/normalization.js +49 -0
  162. package/dist/foundation/marks/plugin.d.ts +14 -0
  163. package/dist/foundation/marks/plugin.js +63 -0
  164. package/dist/foundation/marks/resolveMarkRun.d.ts +8 -0
  165. package/dist/foundation/marks/resolveMarkRun.js +47 -0
  166. package/dist/foundation/marks/schema.d.ts +167 -0
  167. package/dist/foundation/marks/schema.js +50 -0
  168. package/dist/foundation/marks/stored.d.ts +2 -0
  169. package/dist/foundation/marks/stored.js +31 -0
  170. package/dist/foundation/marks/types.d.ts +40 -0
  171. package/dist/foundation/marks/types.js +1 -0
  172. package/dist/foundation/modelDom.d.ts +38 -0
  173. package/dist/foundation/modelDom.js +327 -0
  174. package/dist/foundation/normalization.d.ts +22 -0
  175. package/dist/foundation/normalization.js +132 -0
  176. package/dist/foundation/operations.d.ts +28 -0
  177. package/dist/foundation/operations.js +568 -0
  178. package/dist/foundation/plugin/builtins.d.ts +10 -0
  179. package/dist/foundation/plugin/builtins.js +27 -0
  180. package/dist/foundation/plugin/dispatch.d.ts +33 -0
  181. package/dist/foundation/plugin/dispatch.js +34 -0
  182. package/dist/foundation/plugin/index.d.ts +4 -0
  183. package/dist/foundation/plugin/index.js +4 -0
  184. package/dist/foundation/plugin/registry.d.ts +36 -0
  185. package/dist/foundation/plugin/registry.js +115 -0
  186. package/dist/foundation/plugin/types.d.ts +141 -0
  187. package/dist/foundation/plugin/types.js +13 -0
  188. package/dist/foundation/positions.d.ts +20 -0
  189. package/dist/foundation/positions.js +121 -0
  190. package/dist/foundation/schema.d.ts +57 -0
  191. package/dist/foundation/schema.js +371 -0
  192. package/dist/foundation/schemaBuilder.d.ts +41 -0
  193. package/dist/foundation/schemaBuilder.js +96 -0
  194. package/dist/foundation/scope/index.d.ts +2 -0
  195. package/dist/foundation/scope/index.js +2 -0
  196. package/dist/foundation/scope/resolveScope.d.ts +13 -0
  197. package/dist/foundation/scope/resolveScope.js +860 -0
  198. package/dist/foundation/scope/types.d.ts +122 -0
  199. package/dist/foundation/scope/types.js +1 -0
  200. package/dist/foundation/security/urlPolicy.d.ts +21 -0
  201. package/dist/foundation/security/urlPolicy.js +75 -0
  202. package/dist/foundation/structural/contentTarget.d.ts +21 -0
  203. package/dist/foundation/structural/contentTarget.js +51 -0
  204. package/dist/foundation/structural/index.d.ts +2 -0
  205. package/dist/foundation/structural/index.js +2 -0
  206. package/dist/foundation/structural/move.d.ts +7 -0
  207. package/dist/foundation/structural/move.js +35 -0
  208. package/dist/foundation/suggestions/index.d.ts +3 -0
  209. package/dist/foundation/suggestions/index.js +3 -0
  210. package/dist/foundation/suggestions/inline.d.ts +64 -0
  211. package/dist/foundation/suggestions/inline.js +116 -0
  212. package/dist/foundation/suggestions/structural.d.ts +40 -0
  213. package/dist/foundation/suggestions/structural.js +47 -0
  214. package/dist/foundation/suggestions/types.d.ts +19 -0
  215. package/dist/foundation/suggestions/types.js +1 -0
  216. package/dist/foundation/surface/index.d.ts +3 -0
  217. package/dist/foundation/surface/index.js +3 -0
  218. package/dist/foundation/surface/input.d.ts +127 -0
  219. package/dist/foundation/surface/input.js +1761 -0
  220. package/dist/foundation/surface/renderer.d.ts +84 -0
  221. package/dist/foundation/surface/renderer.js +974 -0
  222. package/dist/foundation/surface/types.d.ts +48 -0
  223. package/dist/foundation/surface/types.js +1 -0
  224. package/dist/foundation/table/commands.d.ts +31 -0
  225. package/dist/foundation/table/commands.js +470 -0
  226. package/dist/foundation/table/formats.d.ts +27 -0
  227. package/dist/foundation/table/formats.js +40 -0
  228. package/dist/foundation/table/grid.d.ts +8 -0
  229. package/dist/foundation/table/grid.js +178 -0
  230. package/dist/foundation/table/index.d.ts +6 -0
  231. package/dist/foundation/table/index.js +6 -0
  232. package/dist/foundation/table/plugin.d.ts +23 -0
  233. package/dist/foundation/table/plugin.js +119 -0
  234. package/dist/foundation/table/schema.d.ts +2 -0
  235. package/dist/foundation/table/schema.js +38 -0
  236. package/dist/foundation/table/selection.d.ts +7 -0
  237. package/dist/foundation/table/selection.js +35 -0
  238. package/dist/foundation/table/types.d.ts +109 -0
  239. package/dist/foundation/table/types.js +1 -0
  240. package/dist/foundation/transactions.d.ts +41 -0
  241. package/dist/foundation/transactions.js +90 -0
  242. package/dist/foundation/types.d.ts +261 -0
  243. package/dist/foundation/types.js +1 -0
  244. package/dist/foundation/versioning/diffVersions.d.ts +5 -0
  245. package/dist/foundation/versioning/diffVersions.js +3 -0
  246. package/dist/foundation/versioning/index.d.ts +2 -0
  247. package/dist/foundation/versioning/index.js +2 -0
  248. package/dist/foundation/versioning/types.d.ts +17 -0
  249. package/dist/foundation/versioning/types.js +1 -0
  250. package/dist/history.d.ts +20 -0
  251. package/dist/history.js +165 -0
  252. package/dist/html/compatibility.js +7 -1
  253. package/dist/index.d.ts +1 -14
  254. package/dist/index.js +9 -14
  255. package/dist/legacy/index.d.ts +45 -0
  256. package/dist/legacy/index.js +45 -0
  257. package/dist/legacyCommands/alignment.d.ts +20 -0
  258. package/dist/{commands → legacyCommands}/alignment.js +17 -0
  259. package/dist/legacyCommands/blocks.d.ts +14 -0
  260. package/dist/legacyCommands/blocks.js +105 -0
  261. package/dist/legacyCommands/checklist.d.ts +11 -0
  262. package/dist/legacyCommands/checklist.js +45 -0
  263. package/dist/legacyCommands/code.d.ts +15 -0
  264. package/dist/{commands → legacyCommands}/code.js +25 -1
  265. package/dist/legacyCommands/formula.d.ts +7 -0
  266. package/dist/legacyCommands/formula.js +67 -0
  267. package/dist/legacyCommands/inlineAtoms.d.ts +7 -0
  268. package/dist/legacyCommands/inlineAtoms.js +58 -0
  269. package/dist/legacyCommands/list.d.ts +38 -0
  270. package/dist/legacyCommands/list.js +477 -0
  271. package/dist/{commands → legacyCommands}/marks.d.ts +3 -0
  272. package/dist/{commands → legacyCommands}/marks.js +28 -11
  273. package/dist/legacyCommands/media.d.ts +31 -0
  274. package/dist/legacyCommands/media.js +205 -0
  275. package/dist/legacyCommands/move.d.ts +14 -0
  276. package/dist/legacyCommands/move.js +91 -0
  277. package/dist/legacyCommands/table.d.ts +91 -0
  278. package/dist/legacyCommands/table.js +547 -0
  279. package/dist/listPresets.d.ts +12 -0
  280. package/dist/listPresets.js +6 -0
  281. package/dist/listScope.d.ts +27 -0
  282. package/dist/listScope.js +104 -0
  283. package/dist/markdown/compatibility.d.ts +2 -0
  284. package/dist/markdown/compatibility.js +94 -2
  285. package/dist/marks.d.ts +5 -5
  286. package/dist/model.d.ts +65 -9
  287. package/dist/plugin.d.ts +15 -0
  288. package/dist/plugin.js +31 -0
  289. package/dist/plugins/alignment.d.ts +3 -0
  290. package/dist/plugins/alignment.js +6 -0
  291. package/dist/plugins/basicFormatting.d.ts +9 -0
  292. package/dist/plugins/basicFormatting.js +24 -0
  293. package/dist/plugins/blockType.d.ts +3 -0
  294. package/dist/plugins/blockType.js +6 -0
  295. package/dist/plugins/blockquote.d.ts +3 -0
  296. package/dist/plugins/blockquote.js +6 -0
  297. package/dist/plugins/checklist.d.ts +3 -0
  298. package/dist/plugins/checklist.js +10 -0
  299. package/dist/plugins/codeBlock.d.ts +3 -0
  300. package/dist/plugins/codeBlock.js +6 -0
  301. package/dist/plugins/formula.d.ts +3 -0
  302. package/dist/plugins/formula.js +9 -0
  303. package/dist/plugins/list.d.ts +3 -0
  304. package/dist/plugins/list.js +10 -0
  305. package/dist/plugins/media.d.ts +3 -0
  306. package/dist/plugins/media.js +12 -0
  307. package/dist/plugins/move.d.ts +3 -0
  308. package/dist/plugins/move.js +9 -0
  309. package/dist/plugins/table.d.ts +3 -0
  310. package/dist/plugins/table.js +22 -0
  311. package/dist/preset.d.ts +12 -0
  312. package/dist/preset.js +40 -0
  313. package/dist/schema.d.ts +22 -0
  314. package/dist/schema.js +333 -0
  315. package/dist/security/urlPolicy.d.ts +1 -17
  316. package/dist/security/urlPolicy.js +3 -69
  317. package/dist/selection.d.ts +1 -1
  318. package/dist/selectionMapping.d.ts +4 -0
  319. package/dist/selectionMapping.js +222 -0
  320. package/dist/table.d.ts +3 -3
  321. package/dist/transaction.d.ts +35 -12
  322. package/dist/transaction.js +174 -28
  323. package/dist/tree.d.ts +8 -2
  324. package/dist/tree.js +44 -0
  325. package/package.json +27 -8
  326. package/dist/commands/alignment.d.ts +0 -18
  327. package/dist/commands/code.d.ts +0 -13
  328. package/dist/commands/list.d.ts +0 -20
  329. package/dist/commands/list.js +0 -52
@@ -0,0 +1,148 @@
1
+ import { isTextNode } from "../identity.js";
2
+ import { diffTextRuns } from "./textDiff.js";
3
+ const indexById = (document) => {
4
+ const index = new Map();
5
+ const visit = (node, parentId, path) => {
6
+ index.set(node.id, { node, parentId, path });
7
+ (node.children || []).forEach((child, childIndex) => {
8
+ if (!isTextNode(child))
9
+ visit(child, node.id, [...path, childIndex]);
10
+ });
11
+ };
12
+ visit(document, null, []);
13
+ return index;
14
+ };
15
+ /**
16
+ * Every `SmartElementNode` keeps a stable id across split/merge/move/type-
17
+ * change/undo (Phase 8c collab-readiness gate, assertion 1). That turns the
18
+ * structural half of a document diff into a hash-map join - id present in
19
+ * both/only-before/only-after - instead of a generic tree-alignment problem
20
+ * a Myers/patience-style algorithm would otherwise be needed to solve.
21
+ * `SmartTextNode` has no id, so text content is the one place a real
22
+ * (word-level, see textDiff.ts) diff algorithm is still needed.
23
+ */
24
+ export const diffDocuments = (before, after, schema) => {
25
+ const beforeIndex = indexById(before);
26
+ const afterIndex = indexById(after);
27
+ const removed = [];
28
+ beforeIndex.forEach((entry, id) => {
29
+ if (afterIndex.has(id))
30
+ return;
31
+ // Only the topmost removed root is reported - a removed id whose
32
+ // parent id is itself removed is part of that parent's own subtree.
33
+ if (entry.parentId && !afterIndex.has(entry.parentId))
34
+ return;
35
+ removed.push({ kind: "removed", nodeId: id, node: entry.node, parentId: entry.parentId, path: [...entry.path] });
36
+ });
37
+ const added = [];
38
+ afterIndex.forEach((entry, id) => {
39
+ if (beforeIndex.has(id))
40
+ return;
41
+ if (entry.parentId && !beforeIndex.has(entry.parentId))
42
+ return;
43
+ added.push({ kind: "added", nodeId: id, node: entry.node, parentId: entry.parentId, path: [...entry.path] });
44
+ });
45
+ const changed = [];
46
+ beforeIndex.forEach((beforeEntry, id) => {
47
+ const afterEntry = afterIndex.get(id);
48
+ if (!afterEntry)
49
+ return;
50
+ const entry = {};
51
+ if (beforeEntry.parentId !== afterEntry.parentId) {
52
+ entry.move = {
53
+ fromParentId: beforeEntry.parentId, toParentId: afterEntry.parentId,
54
+ fromPath: [...beforeEntry.path], toPath: [...afterEntry.path],
55
+ };
56
+ }
57
+ else if (beforeEntry.parentId) {
58
+ const move = sameParentMove(beforeEntry.parentId, beforeIndex, afterIndex, id);
59
+ if (move)
60
+ entry.move = move;
61
+ }
62
+ if (beforeEntry.node.type !== afterEntry.node.type) {
63
+ entry.typeChange = { before: beforeEntry.node.type, after: afterEntry.node.type };
64
+ }
65
+ const attrsDiff = diffAttrs(beforeEntry.node.attrs || {}, afterEntry.node.attrs || {});
66
+ if (attrsDiff.length) {
67
+ entry.attrsChange = { before: beforeEntry.node.attrs || {}, after: afterEntry.node.attrs || {}, changedKeys: attrsDiff };
68
+ }
69
+ if (isInlineOwner(beforeEntry.node, schema) && isInlineOwner(afterEntry.node, schema)) {
70
+ const segments = diffTextRuns(textRunsOf(beforeEntry.node), textRunsOf(afterEntry.node));
71
+ if (segments.some((segment) => segment.op !== "equal"))
72
+ entry.contentChange = segments;
73
+ }
74
+ if (entry.move || entry.typeChange || entry.attrsChange || entry.contentChange) {
75
+ changed.push({ kind: "changed", nodeId: id, ...entry });
76
+ }
77
+ });
78
+ return { added, removed, changed };
79
+ };
80
+ /**
81
+ * Detects a same-parent reorder without false-positives from mere sibling
82
+ * insertion/removal: only ids that were - and still are - direct children
83
+ * of this exact parent on both sides are considered, and their retained-
84
+ * only order is compared via LCS. A retained id inside that LCS kept its
85
+ * relative order (even if its numeric index shifted because siblings were
86
+ * added/removed around it) and is not reported as moved.
87
+ */
88
+ const sameParentMove = (parentId, beforeIndex, afterIndex, nodeId) => {
89
+ const beforeParent = beforeIndex.get(parentId);
90
+ const afterParent = afterIndex.get(parentId);
91
+ if (!beforeParent || !afterParent)
92
+ return undefined;
93
+ const beforeChildIds = (beforeParent.node.children || []).filter((child) => !isTextNode(child)).map((child) => child.id);
94
+ const afterChildIds = (afterParent.node.children || []).filter((child) => !isTextNode(child)).map((child) => child.id);
95
+ const oldOrder = beforeChildIds.filter((id) => afterIndex.get(id)?.parentId === parentId);
96
+ const newOrder = afterChildIds.filter((id) => beforeIndex.get(id)?.parentId === parentId);
97
+ const lcs = lcsIds(oldOrder, newOrder);
98
+ if (lcs.has(nodeId))
99
+ return undefined;
100
+ const before = beforeIndex.get(nodeId);
101
+ const after = afterIndex.get(nodeId);
102
+ return { fromParentId: parentId, toParentId: parentId, fromPath: [...before.path], toPath: [...after.path] };
103
+ };
104
+ const lcsIds = (before, after) => {
105
+ const n = before.length;
106
+ const m = after.length;
107
+ const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
108
+ for (let i = n - 1; i >= 0; i -= 1) {
109
+ for (let j = m - 1; j >= 0; j -= 1) {
110
+ dp[i][j] = before[i] === after[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
111
+ }
112
+ }
113
+ const kept = new Set();
114
+ let i = 0;
115
+ let j = 0;
116
+ while (i < n && j < m) {
117
+ if (before[i] === after[j] && dp[i][j] === dp[i + 1][j + 1] + 1) {
118
+ kept.add(before[i]);
119
+ i += 1;
120
+ j += 1;
121
+ }
122
+ else if (dp[i + 1][j] >= dp[i][j + 1])
123
+ i += 1;
124
+ else
125
+ j += 1;
126
+ }
127
+ return kept;
128
+ };
129
+ const diffAttrs = (before, after) => {
130
+ const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
131
+ return [...keys].filter((key) => JSON.stringify(before[key]) !== JSON.stringify(after[key]));
132
+ };
133
+ /** Mirrors the inline-content-owner predicate in canonicalEditorRuntime.ts's firstTextSelection, reused here for consistency rather than a second definition of the same concept. */
134
+ const isInlineOwner = (node, schema) => {
135
+ const children = node.children || [];
136
+ return children.every((child) => isTextNode(child) || schema.nodes[child.type]?.group === "inline");
137
+ };
138
+ /**
139
+ * Flattens an inline owner's children into `{text, marks}` runs for word-
140
+ * level diffing. Inline atomic children (e.g. inline images/formulas) are
141
+ * intentionally not represented here - only text is word-diffed; a change
142
+ * limited entirely to an inline atom within otherwise-unchanged text will
143
+ * not surface as a `contentChange`. Documented scope limit, not a silent
144
+ * gap: such a change is still visible via the atom's own `added`/`removed`
145
+ * entry if it's inserted/removed outright.
146
+ */
147
+ const textRunsOf = (node) => (node.children || []).filter((child) => isTextNode(child))
148
+ .map((child) => ({ text: child.text, marks: (child.marks || []) }));
@@ -0,0 +1,3 @@
1
+ export * from "./types.js";
2
+ export * from "./documentDiff.js";
3
+ export * from "./textDiff.js";
@@ -0,0 +1,3 @@
1
+ export * from "./types.js";
2
+ export * from "./documentDiff.js";
3
+ export * from "./textDiff.js";
@@ -0,0 +1,12 @@
1
+ import type { SmartMark } from "../types.js";
2
+ import type { TextDiffSegment } from "./types.js";
3
+ export interface TextRun {
4
+ readonly text: string;
5
+ readonly marks: readonly SmartMark[];
6
+ }
7
+ /**
8
+ * Diffs two sequences of `{text, marks}` runs (the flattened inline content
9
+ * of one node) at word granularity. A token never spans a run boundary, so
10
+ * it can never straddle two different mark sets by construction.
11
+ */
12
+ export declare const diffTextRuns: (before: readonly TextRun[], after: readonly TextRun[]) => TextDiffSegment[];
@@ -0,0 +1,114 @@
1
+ import { canonicalMarkOrder, markKey } from "../marks/canonical.js";
2
+ /**
3
+ * Word-boundary tokenizer: each token is a run of non-whitespace plus its
4
+ * own trailing whitespace (leading whitespace with no preceding word, e.g.
5
+ * at the very start of a run, becomes its own token). Deliberately not
6
+ * further split on punctuation - this project's diff is word-granularity,
7
+ * not a linguistic tokenizer. Gluing whitespace onto its word rather than
8
+ * treating every space as an independent token matters for LCS matching
9
+ * below: an isolated whitespace token can spuriously "match" between two
10
+ * otherwise-unrelated words (e.g. "alpha beta" -> "gamma delta" sharing
11
+ * nothing but a space), fragmenting what should read as one clean
12
+ * delete+insert into delete/insert/equal(" ")/delete/insert.
13
+ */
14
+ const tokenize = (text) => text.length ? text.match(/\S+\s*|^\s+/g) || [] : [];
15
+ const tokensOf = (runs) => runs.flatMap((run) => tokenize(run.text).map((text) => ({ text, marks: run.marks })));
16
+ const marksSignature = (marks) => canonicalMarkOrder(marks).map(markKey).join("|");
17
+ const sameMarks = (left, right) => marksSignature(left) === marksSignature(right);
18
+ /**
19
+ * A token's trailing whitespace is glued on for *display* (see `tokenize`
20
+ * above) but must not affect whether two tokens count as "the same word"
21
+ * for matching - the very same word carries different trailing whitespace
22
+ * depending only on whether another word happens to follow it in that
23
+ * particular text (a word at the end of "before" has no trailing space;
24
+ * the identical word gains one the moment "after" adds a word behind it).
25
+ * Comparing raw `.text` would make that purely incidental difference
26
+ * block an otherwise-correct match.
27
+ */
28
+ const matchKey = (token) => token.text.trimEnd();
29
+ /**
30
+ * Longest common subsequence over token match-keys (marks are compared
31
+ * separately, after alignment - see `sameMarks` below) via plain O(n*m)
32
+ * dynamic programming. Inputs here are bounded to one inline-content
33
+ * owner's word count (a paragraph/heading/cell), not a whole document, so
34
+ * Myers' O(ND) is unneeded complexity for this project's actual scale;
35
+ * revisit only if a real profile shows otherwise.
36
+ */
37
+ const lcsSteps = (before, after) => {
38
+ const n = before.length;
39
+ const m = after.length;
40
+ const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
41
+ for (let i = n - 1; i >= 0; i -= 1) {
42
+ for (let j = m - 1; j >= 0; j -= 1) {
43
+ dp[i][j] = matchKey(before[i]) === matchKey(after[j])
44
+ ? dp[i + 1][j + 1] + 1
45
+ : Math.max(dp[i + 1][j], dp[i][j + 1]);
46
+ }
47
+ }
48
+ const steps = [];
49
+ let i = 0;
50
+ let j = 0;
51
+ while (i < n && j < m) {
52
+ if (matchKey(before[i]) === matchKey(after[j]) && dp[i][j] === dp[i + 1][j + 1] + 1) {
53
+ steps.push({ op: "match", before: before[i], after: after[j] });
54
+ i += 1;
55
+ j += 1;
56
+ }
57
+ else if (dp[i + 1][j] >= dp[i][j + 1]) {
58
+ steps.push({ op: "delete", token: before[i] });
59
+ i += 1;
60
+ }
61
+ else {
62
+ steps.push({ op: "insert", token: after[j] });
63
+ j += 1;
64
+ }
65
+ }
66
+ while (i < n) {
67
+ steps.push({ op: "delete", token: before[i] });
68
+ i += 1;
69
+ }
70
+ while (j < m) {
71
+ steps.push({ op: "insert", token: after[j] });
72
+ j += 1;
73
+ }
74
+ return steps;
75
+ };
76
+ /** Coalesces adjacent steps of the same kind (and, for match/markChange, the same mark signature) into one segment - one token per LCS step would be needlessly fragmented for a UI. */
77
+ const coalesce = (steps) => {
78
+ const segments = [];
79
+ for (const step of steps) {
80
+ if (step.op === "match") {
81
+ const changed = !sameMarks(step.before.marks, step.after.marks);
82
+ const last = segments[segments.length - 1];
83
+ if (changed) {
84
+ if (last?.op === "markChange" && sameMarks(last.before, step.before.marks) && sameMarks(last.after, step.after.marks)) {
85
+ segments[segments.length - 1] = { ...last, text: last.text + step.after.text };
86
+ }
87
+ else {
88
+ segments.push({ op: "markChange", text: step.after.text, before: step.before.marks, after: step.after.marks });
89
+ }
90
+ }
91
+ else if (last?.op === "equal" && sameMarks(last.marks, step.after.marks)) {
92
+ segments[segments.length - 1] = { ...last, text: last.text + step.after.text };
93
+ }
94
+ else {
95
+ segments.push({ op: "equal", text: step.after.text, marks: step.after.marks });
96
+ }
97
+ continue;
98
+ }
99
+ const last = segments[segments.length - 1];
100
+ if (last?.op === step.op && sameMarks(last.marks, step.token.marks)) {
101
+ segments[segments.length - 1] = { ...last, text: last.text + step.token.text };
102
+ }
103
+ else {
104
+ segments.push({ op: step.op, text: step.token.text, marks: step.token.marks });
105
+ }
106
+ }
107
+ return segments;
108
+ };
109
+ /**
110
+ * Diffs two sequences of `{text, marks}` runs (the flattened inline content
111
+ * of one node) at word granularity. A token never spans a run boundary, so
112
+ * it can never straddle two different mark sets by construction.
113
+ */
114
+ export const diffTextRuns = (before, after) => coalesce(lcsSteps(tokensOf(before), tokensOf(after)));
@@ -0,0 +1,74 @@
1
+ import type { Attrs, SmartMark, SmartNode } from "../types.js";
2
+ /**
3
+ * A word-granularity diff segment within one inline-content owner's flattened
4
+ * text. Word, not character: character-level diffing fragments on every
5
+ * keystroke-sized edit and is visually noisy for review UI; word-level is
6
+ * what every comparable tool (Word, Google Docs) renders track-changes at.
7
+ * A run whose text matches on both sides but whose marks don't is reported
8
+ * as `markChange`, not a delete+insert of identical text.
9
+ */
10
+ export type TextDiffSegment = {
11
+ readonly op: "equal";
12
+ readonly text: string;
13
+ readonly marks: readonly SmartMark[];
14
+ } | {
15
+ readonly op: "insert";
16
+ readonly text: string;
17
+ readonly marks: readonly SmartMark[];
18
+ } | {
19
+ readonly op: "delete";
20
+ readonly text: string;
21
+ readonly marks: readonly SmartMark[];
22
+ } | {
23
+ readonly op: "markChange";
24
+ readonly text: string;
25
+ readonly before: readonly SmartMark[];
26
+ readonly after: readonly SmartMark[];
27
+ };
28
+ /** A node id present in `after` but not `before`. Only topmost added roots are reported - a node whose parent id is itself newly-added is part of that parent's own subtree, not a separate entry. */
29
+ export interface AddedNodeChange {
30
+ readonly kind: "added";
31
+ readonly nodeId: string;
32
+ readonly node: SmartNode;
33
+ readonly parentId: string | null;
34
+ readonly path: number[];
35
+ }
36
+ /** A node id present in `before` but not `after`. Only topmost removed roots are reported, mirroring `AddedNodeChange`. */
37
+ export interface RemovedNodeChange {
38
+ readonly kind: "removed";
39
+ readonly nodeId: string;
40
+ readonly node: SmartNode;
41
+ readonly parentId: string | null;
42
+ readonly path: number[];
43
+ }
44
+ /**
45
+ * A node id present in both trees with at least one difference. Carries
46
+ * whichever of `move`/`typeChange`/`attrsChange`/`contentChange` actually
47
+ * differ on one entry per id, rather than separate flat entries per change
48
+ * kind - a renderer needs one lookup per node id, not a correlation step.
49
+ */
50
+ export interface ChangedNodeChange {
51
+ readonly kind: "changed";
52
+ readonly nodeId: string;
53
+ readonly move?: {
54
+ readonly fromParentId: string | null;
55
+ readonly toParentId: string | null;
56
+ readonly fromPath: number[];
57
+ readonly toPath: number[];
58
+ };
59
+ readonly typeChange?: {
60
+ readonly before: string;
61
+ readonly after: string;
62
+ };
63
+ readonly attrsChange?: {
64
+ readonly before: Attrs;
65
+ readonly after: Attrs;
66
+ readonly changedKeys: readonly string[];
67
+ };
68
+ readonly contentChange?: readonly TextDiffSegment[];
69
+ }
70
+ export interface DocumentDiff {
71
+ readonly added: readonly AddedNodeChange[];
72
+ readonly removed: readonly RemovedNodeChange[];
73
+ readonly changed: readonly ChangedNodeChange[];
74
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,138 @@
1
+ import { type NormalizationRun } from "./normalization.js";
2
+ import type { Attrs, NormalizerRegistration, PersistedEditorDocument, ResolvedPos, SmartHistory, SmartMark, SmartNode, SmartOperation, SmartRange, SmartSchema, SmartSelection, SmartTransaction } from "./types.js";
3
+ import type { PositionLookup, ScopeRequest, ScopeResult } from "./scope/types.js";
4
+ import type { ContextMenuContribution, KeyboardShortcutContribution, PluginCommand } from "./plugin/types.js";
5
+ export interface FoundationEditorState extends PersistedEditorDocument {
6
+ selection: SmartSelection;
7
+ storedMarks?: SmartMark[];
8
+ }
9
+ export interface TransactOptions {
10
+ source?: SmartTransaction["metadata"]["source"];
11
+ timestamp?: number;
12
+ historyGroup?: string;
13
+ addToHistory?: boolean;
14
+ compositionId?: string;
15
+ authorId?: string;
16
+ }
17
+ export declare class TransactionBuilder {
18
+ readonly selectionBefore: SmartSelection;
19
+ readonly operations: SmartOperation[];
20
+ selectionAfter: SmartSelection;
21
+ storedMarksAfter?: SmartMark[];
22
+ /** True when the caller intentionally set the post-transaction marks. */
23
+ /** @internal Transaction assembly uses this to distinguish an explicit clear from the default. */
24
+ storedMarksExplicit: boolean;
25
+ constructor(selectionBefore: SmartSelection, storedMarks?: SmartMark[]);
26
+ insertNode(position: ResolvedPos, node: SmartNode): void;
27
+ removeNode(position: ResolvedPos, node: SmartNode): void;
28
+ replaceNode(position: ResolvedPos, before: SmartNode, after: SmartNode): void;
29
+ moveNode(from: ResolvedPos, to: ResolvedPos, nodeId: string): void;
30
+ splitNode(position: ResolvedPos, depth: number, newId?: string): void;
31
+ mergeNode(position: ResolvedPos, depth: number, retiredId: string, splitOffset: number): void;
32
+ setNodeAttributes(position: ResolvedPos, before: Attrs, after: Attrs): void;
33
+ setNodeType(position: ResolvedPos, before: string, after: string, beforeAttrs: Attrs, afterAttrs: Attrs): void;
34
+ insertText(position: ResolvedPos, text: string, marks?: SmartMark[]): void;
35
+ deleteText(position: ResolvedPos, text: string, marks?: SmartMark[]): void;
36
+ addMark(range: SmartRange, mark: SmartMark): void;
37
+ removeMark(range: SmartRange, mark: SmartMark): void;
38
+ setSelection(selection: SmartSelection): void;
39
+ setStoredMarks(marks: SmartMark[] | undefined): void;
40
+ }
41
+ export interface FoundationEditorOptions {
42
+ document: SmartNode;
43
+ selection: SmartSelection;
44
+ revision?: number;
45
+ schema?: SmartSchema;
46
+ /** Defaults to the built-in registry's commands - a custom plugin list's registry.commands should be passed here to keep it in sync with a custom `schema`. */
47
+ commands?: ReadonlyMap<string, PluginCommand>;
48
+ /** Defaults to the built-in registry's keyboardShortcuts, for the same reason as `commands`. */
49
+ keyboardShortcuts?: readonly KeyboardShortcutContribution[];
50
+ /** Defaults to the built-in registry's contextMenu, for the same reason as `commands`. */
51
+ contextMenu?: readonly ContextMenuContribution[];
52
+ normalizers?: readonly NormalizerRegistration[];
53
+ historyLimit?: number;
54
+ historyByteLimit?: number;
55
+ coalescenceWindowMs?: number;
56
+ storedMarks?: readonly SmartMark[];
57
+ /**
58
+ * How many committed transactions to retain for rebasing an incoming
59
+ * transaction whose baseRevision is behind current (Phase 12b-client -
60
+ * without a real transport, single-writer usage never falls behind, so
61
+ * this never matters). Unbounded retention isn't viable for a long-lived
62
+ * session; a transaction behind this window fails with
63
+ * ResyncRequiredError rather than attempting a partial rebase.
64
+ */
65
+ revisionLogLimit?: number;
66
+ }
67
+ export interface ReplaceFoundationStateOptions {
68
+ selection: SmartSelection;
69
+ storedMarks?: readonly SmartMark[];
70
+ /** External replacement starts a new history epoch by default. */
71
+ resetHistory?: boolean;
72
+ }
73
+ export declare class FoundationEditor {
74
+ readonly schema: SmartSchema;
75
+ readonly commands: ReadonlyMap<string, PluginCommand>;
76
+ readonly keyboardShortcuts: readonly KeyboardShortcutContribution[];
77
+ readonly contextMenu: readonly ContextMenuContribution[];
78
+ private current;
79
+ private currentHistory;
80
+ private readonly normalizers;
81
+ private activeBuilder;
82
+ private listeners;
83
+ private readonly semanticIndex;
84
+ private readonly historyOptions;
85
+ lastNormalization: NormalizationRun | null;
86
+ /**
87
+ * Every committed transaction since this instance was created or last
88
+ * replaceState'd, oldest first, bounded to `revisionLogLimit` - the
89
+ * "missed operations" source for rebasing an incoming stale transaction
90
+ * (see dispatch()). Index i holds the transaction that advanced the
91
+ * document from revision (current.revision - revisionLog.length + i) to
92
+ * the next.
93
+ */
94
+ private revisionLog;
95
+ private readonly revisionLogLimit;
96
+ constructor(options: FoundationEditorOptions);
97
+ get state(): FoundationEditorState;
98
+ get history(): SmartHistory;
99
+ /** Readonly canonical references for memoized renderers and indexes. */
100
+ get document(): FoundationEditorState["document"];
101
+ get selection(): SmartSelection;
102
+ get storedMarks(): readonly SmartMark[] | undefined;
103
+ /**
104
+ * Commit listeners receive the canonical readonly document reference. This
105
+ * preserves the structural-sharing memoization contract and avoids cloning
106
+ * the complete document once per listener on every keystroke.
107
+ */
108
+ private notify;
109
+ /**
110
+ * Replaces the canonical envelope without recreating the editor instance.
111
+ * This is an explicit host revision boundary, never a response to a prop diff.
112
+ */
113
+ replaceState(envelope: PersistedEditorDocument, options: ReplaceFoundationStateOptions): SmartTransaction;
114
+ resolveScope(request: ScopeRequest, selection?: SmartSelection): ScopeResult;
115
+ get positions(): PositionLookup;
116
+ resolve(position: {
117
+ pos: ResolvedPos["pos"];
118
+ affinity?: ResolvedPos["affinity"];
119
+ }): ResolvedPos;
120
+ transact(callback: (transaction: TransactionBuilder) => unknown, options?: TransactOptions): SmartTransaction | null;
121
+ /**
122
+ * Transactions committed since revision `sinceRevision`, oldest first -
123
+ * the same source `dispatch()` itself uses to rebase an incoming stale
124
+ * transaction, exposed so a host-implemented CollabTransport can answer
125
+ * `getRevisionHistory` without this editor needing to know anything about
126
+ * transports. Returns null when `sinceRevision` is older than what this
127
+ * instance retained (see `revisionLogLimit`) - the caller must resync.
128
+ */
129
+ getTransactionsSince(sinceRevision: number): readonly SmartTransaction[] | null;
130
+ dispatch(input: SmartTransaction): void;
131
+ typeText(text: string, options?: Omit<TransactOptions, "source">): SmartTransaction | null;
132
+ setSelection(selection: SmartSelection, options?: Omit<TransactOptions, "addToHistory">): SmartTransaction | null;
133
+ setStoredMarks(marks: readonly SmartMark[] | undefined, options?: Omit<TransactOptions, "addToHistory">): SmartTransaction | null;
134
+ undo(): boolean;
135
+ redo(): boolean;
136
+ subscribe(listener: (transaction: SmartTransaction, state: FoundationEditorState) => void): () => void;
137
+ }
138
+ export declare const createFoundationEditor: (options: FoundationEditorOptions) => FoundationEditor;