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,398 @@
1
+ import { createNodeId, isTextNode } from "./identity.js";
2
+ import { popRedo, popUndo, recordHistory, createHistory, rebaseHistoryNodeAttributes } from "./history.js";
3
+ import { runNormalization } from "./normalization.js";
4
+ import { applyOperations } from "./operations.js";
5
+ import { createTransactionMap } from "./mapping.js";
6
+ import { resolvePos } from "./positions.js";
7
+ import { foundationRegistry, foundationSchema, repair, restoreUnknownMarks, restoreUnknownNodes, validate } from "./schema.js";
8
+ import { applyTransactionAtomic, RebaseConflictError, ResyncRequiredError } from "./transactions.js";
9
+ import { rebaseTransaction } from "./collab/rebase.js";
10
+ import { FoundationScopeIndex } from "./scope/resolveScope.js";
11
+ import { migrateNewlineTextToHardBreaks } from "./marks/hardBreak.js";
12
+ import { canonicalMarkOrder, createMarkNormalizer, marksAtInsertion } from "./marks/index.js";
13
+ import { createEditableBoundaryNormalizer, editableBoundaryOperations } from "./boundaries.js";
14
+ export class TransactionBuilder {
15
+ constructor(selectionBefore, storedMarks) {
16
+ this.selectionBefore = selectionBefore;
17
+ this.operations = [];
18
+ /** True when the caller intentionally set the post-transaction marks. */
19
+ /** @internal Transaction assembly uses this to distinguish an explicit clear from the default. */
20
+ this.storedMarksExplicit = false;
21
+ this.selectionAfter = structuredClone(selectionBefore);
22
+ this.storedMarksAfter = structuredClone(storedMarks);
23
+ }
24
+ insertNode(position, node) {
25
+ this.operations.push({ type: "insertNode", pos: position.pos, node: structuredClone(node) });
26
+ }
27
+ removeNode(position, node) {
28
+ this.operations.push({ type: "removeNode", pos: position.pos, node: structuredClone(node) });
29
+ }
30
+ replaceNode(position, before, after) {
31
+ this.operations.push({ type: "replaceNode", pos: position.pos, before: structuredClone(before), after: structuredClone(after) });
32
+ }
33
+ moveNode(from, to, nodeId) {
34
+ this.operations.push({ type: "moveNode", from: from.pos, to: to.pos, nodeId });
35
+ }
36
+ splitNode(position, depth, newId = createNodeId()) {
37
+ this.operations.push({ type: "splitNode", pos: position.pos, depth, newId });
38
+ }
39
+ mergeNode(position, depth, retiredId, splitOffset) {
40
+ this.operations.push({ type: "mergeNode", pos: position.pos, depth, retiredId, splitOffset });
41
+ }
42
+ setNodeAttributes(position, before, after) {
43
+ this.operations.push({ type: "setNodeAttributes", pos: position.pos, before: structuredClone(before), after: structuredClone(after) });
44
+ }
45
+ setNodeType(position, before, after, beforeAttrs, afterAttrs) {
46
+ this.operations.push({
47
+ type: "setNodeType",
48
+ pos: position.pos,
49
+ before,
50
+ after,
51
+ beforeAttrs: structuredClone(beforeAttrs),
52
+ afterAttrs: structuredClone(afterAttrs),
53
+ });
54
+ }
55
+ insertText(position, text, marks) {
56
+ this.operations.push({ type: "insertText", pos: position.pos, text, ...(marks?.length ? { marks: structuredClone(marks) } : {}) });
57
+ }
58
+ deleteText(position, text, marks) {
59
+ this.operations.push({ type: "deleteText", pos: position.pos, text, ...(marks?.length ? { marks: structuredClone(marks) } : {}) });
60
+ }
61
+ addMark(range, mark) {
62
+ this.operations.push({ type: "addMark", range: structuredClone(range), mark: structuredClone(mark) });
63
+ }
64
+ removeMark(range, mark) {
65
+ this.operations.push({ type: "removeMark", range: structuredClone(range), mark: structuredClone(mark) });
66
+ }
67
+ setSelection(selection) {
68
+ this.selectionAfter = structuredClone(selection);
69
+ }
70
+ setStoredMarks(marks) {
71
+ this.storedMarksExplicit = true;
72
+ this.storedMarksAfter = structuredClone(marks);
73
+ }
74
+ }
75
+ const mapSelectionThroughBoundaryOperations = (selection, operations) => {
76
+ if (!operations.length)
77
+ return selection;
78
+ const map = createTransactionMap(operations);
79
+ // A node selection's head is the boundary immediately after the selected
80
+ // node. When a caret paragraph is inserted at that boundary, biasing the
81
+ // head forward would skip the new line and make ArrowRight manufacture a
82
+ // second paragraph. Keep both node-selection endpoints on the original
83
+ // side of the inserted boundary.
84
+ if (selection.type === "node")
85
+ return {
86
+ ...selection,
87
+ anchor: map.map(selection.anchor, -1),
88
+ head: map.map(selection.head, -1),
89
+ };
90
+ return map.mapSelection(selection);
91
+ };
92
+ export class FoundationEditor {
93
+ constructor(options) {
94
+ this.activeBuilder = null;
95
+ this.listeners = new Set();
96
+ this.semanticIndex = new FoundationScopeIndex();
97
+ this.lastNormalization = null;
98
+ /**
99
+ * Every committed transaction since this instance was created or last
100
+ * replaceState'd, oldest first, bounded to `revisionLogLimit` - the
101
+ * "missed operations" source for rebasing an incoming stale transaction
102
+ * (see dispatch()). Index i holds the transaction that advanced the
103
+ * document from revision (current.revision - revisionLog.length + i) to
104
+ * the next.
105
+ */
106
+ this.revisionLog = [];
107
+ this.schema = options.schema || foundationSchema;
108
+ this.commands = options.commands || foundationRegistry.commands;
109
+ this.keyboardShortcuts = options.keyboardShortcuts || foundationRegistry.keyboardShortcuts;
110
+ this.contextMenu = options.contextMenu || foundationRegistry.contextMenu;
111
+ const candidate = options.document.type === "doc"
112
+ ? options.document
113
+ : { type: "doc", id: createNodeId(), children: [options.document] };
114
+ const migrated = migrateNewlineTextToHardBreaks(candidate);
115
+ const repaired = repair(migrated.document, this.schema);
116
+ // Symmetric with repair() above: an `unknown`/`unknown-mark` node whose
117
+ // recorded originalType this schema *does* recognize (e.g. a
118
+ // previously plugin-disabled table, now loading into a schema with the
119
+ // table plugin re-enabled) restores losslessly - a no-op for every
120
+ // other document, since restoreUnknownNodes/Marks only ever act on a
121
+ // matching originalType. See docs/bugs/disable-safety-restore-never-wired.md.
122
+ const restored = restoreUnknownMarks(restoreUnknownNodes(repaired.doc, this.schema), this.schema);
123
+ const boundaryOperations = editableBoundaryOperations(restored, this.schema);
124
+ const boundedDocument = boundaryOperations.length ? applyOperations(restored, boundaryOperations) : restored;
125
+ const boundedSelection = mapSelectionThroughBoundaryOperations(options.selection, boundaryOperations);
126
+ const errors = validate(boundedDocument, this.schema);
127
+ if (errors.length)
128
+ throw new Error(`Initial document is invalid: ${errors[0].message}`);
129
+ resolvePos(boundedDocument, boundedSelection.anchor);
130
+ resolvePos(boundedDocument, boundedSelection.head);
131
+ this.current = {
132
+ schemaVersion: this.schema.version,
133
+ revision: options.revision ?? 0,
134
+ document: boundedDocument,
135
+ selection: structuredClone(boundedSelection),
136
+ ...(options.storedMarks?.length ? { storedMarks: canonicalMarkOrder(options.storedMarks) } : {}),
137
+ };
138
+ this.normalizers = [createEditableBoundaryNormalizer(), createMarkNormalizer(), ...(options.normalizers || [])];
139
+ this.historyOptions = {
140
+ historyLimit: options.historyLimit,
141
+ historyByteLimit: options.historyByteLimit,
142
+ coalescenceWindowMs: options.coalescenceWindowMs,
143
+ };
144
+ this.currentHistory = createHistory({
145
+ limit: options.historyLimit,
146
+ byteLimit: options.historyByteLimit,
147
+ coalescenceWindowMs: options.coalescenceWindowMs,
148
+ });
149
+ this.revisionLogLimit = Math.max(1, Math.floor(options.revisionLogLimit ?? 500));
150
+ }
151
+ get state() { return structuredClone(this.current); }
152
+ get history() { return structuredClone(this.currentHistory); }
153
+ /** Readonly canonical references for memoized renderers and indexes. */
154
+ get document() { return this.current.document; }
155
+ get selection() { return structuredClone(this.current.selection); }
156
+ get storedMarks() { return this.current.storedMarks; }
157
+ /**
158
+ * Commit listeners receive the canonical readonly document reference. This
159
+ * preserves the structural-sharing memoization contract and avoids cloning
160
+ * the complete document once per listener on every keystroke.
161
+ */
162
+ notify(transaction) {
163
+ if (!this.listeners.size)
164
+ return;
165
+ const state = {
166
+ schemaVersion: this.current.schemaVersion,
167
+ revision: this.current.revision,
168
+ document: this.current.document,
169
+ selection: structuredClone(this.current.selection),
170
+ ...(this.current.storedMarks ? { storedMarks: structuredClone(this.current.storedMarks) } : {}),
171
+ };
172
+ this.listeners.forEach((listener) => listener(structuredClone(transaction), state));
173
+ }
174
+ /**
175
+ * Replaces the canonical envelope without recreating the editor instance.
176
+ * This is an explicit host revision boundary, never a response to a prop diff.
177
+ */
178
+ replaceState(envelope, options) {
179
+ const migrated = migrateNewlineTextToHardBreaks(envelope.document);
180
+ const repaired = repair(migrated.document, this.schema);
181
+ // See the matching comment in the constructor above.
182
+ const restored = restoreUnknownMarks(restoreUnknownNodes(repaired.doc, this.schema), this.schema);
183
+ const boundaryOperations = editableBoundaryOperations(restored, this.schema);
184
+ const boundedDocument = boundaryOperations.length ? applyOperations(restored, boundaryOperations) : restored;
185
+ const boundedSelection = mapSelectionThroughBoundaryOperations(options.selection, boundaryOperations);
186
+ const errors = validate(boundedDocument, this.schema);
187
+ if (errors.length)
188
+ throw new Error(`Replacement document is invalid: ${errors[0].message}`);
189
+ resolvePos(boundedDocument, boundedSelection.anchor);
190
+ resolvePos(boundedDocument, boundedSelection.head);
191
+ const before = this.current;
192
+ this.current = {
193
+ schemaVersion: this.schema.version,
194
+ revision: envelope.revision,
195
+ document: boundedDocument,
196
+ selection: structuredClone(boundedSelection),
197
+ ...(options.storedMarks?.length ? { storedMarks: canonicalMarkOrder(options.storedMarks) } : {}),
198
+ };
199
+ if (options.resetHistory !== false) {
200
+ this.currentHistory = createHistory({
201
+ limit: this.historyOptions.historyLimit,
202
+ byteLimit: this.historyOptions.historyByteLimit,
203
+ coalescenceWindowMs: this.historyOptions.coalescenceWindowMs,
204
+ });
205
+ }
206
+ // The revision counter is jumping to a new, unrelated baseline
207
+ // (envelope.revision, not before.revision + 1) - nothing in the old log
208
+ // could ever be correctly composed with transactions issued against
209
+ // this new baseline, so it's discarded rather than carried forward.
210
+ this.revisionLog = [];
211
+ const replacement = {
212
+ id: createNodeId(),
213
+ baseRevision: before.revision,
214
+ operations: [],
215
+ selectionBefore: structuredClone(before.selection),
216
+ selectionAfter: structuredClone(boundedSelection),
217
+ ...(before.storedMarks ? { storedMarksBefore: structuredClone(before.storedMarks) } : {}),
218
+ ...(options.storedMarks?.length ? { storedMarksAfter: canonicalMarkOrder(options.storedMarks) } : {}),
219
+ metadata: { source: "api", timestamp: Date.now(), addToHistory: false },
220
+ };
221
+ this.notify(replacement);
222
+ return replacement;
223
+ }
224
+ resolveScope(request, selection = this.current.selection) {
225
+ return this.semanticIndex.resolve(this.current.document, selection, request, this.schema);
226
+ }
227
+ get positions() { return this.semanticIndex.positions(this.current.document, this.schema); }
228
+ resolve(position) {
229
+ return resolvePos(this.current.document, position.pos, position.affinity);
230
+ }
231
+ transact(callback, options = {}) {
232
+ if (this.activeBuilder) {
233
+ const result = callback(this.activeBuilder);
234
+ if (result && typeof result.then === "function")
235
+ throw new Error("A transaction cannot remain open across await.");
236
+ return null;
237
+ }
238
+ const builder = new TransactionBuilder(this.current.selection, this.current.storedMarks);
239
+ this.activeBuilder = builder;
240
+ try {
241
+ const result = callback(builder);
242
+ if (result && typeof result.then === "function")
243
+ throw new Error("A transaction cannot remain open across await.");
244
+ }
245
+ finally {
246
+ this.activeBuilder = null;
247
+ }
248
+ if (builder.operations.length && !builder.storedMarksExplicit && !builder.operations.every((operation) => operation.type === "insertText" && (!this.current.storedMarks?.length
249
+ || JSON.stringify(canonicalMarkOrder(operation.marks)) === JSON.stringify(canonicalMarkOrder(this.current.storedMarks))))) {
250
+ builder.setStoredMarks(undefined);
251
+ }
252
+ const transaction = {
253
+ id: createNodeId(),
254
+ baseRevision: this.current.revision,
255
+ operations: builder.operations,
256
+ selectionBefore: structuredClone(builder.selectionBefore),
257
+ selectionAfter: structuredClone(builder.selectionAfter),
258
+ ...(this.current.storedMarks ? { storedMarksBefore: structuredClone(this.current.storedMarks) } : {}),
259
+ ...(builder.storedMarksAfter ? { storedMarksAfter: structuredClone(builder.storedMarksAfter) } : {}),
260
+ metadata: {
261
+ source: options.source || "api",
262
+ timestamp: options.timestamp ?? Date.now(),
263
+ ...(options.historyGroup ? { historyGroup: options.historyGroup } : {}),
264
+ addToHistory: options.addToHistory ?? builder.operations.length > 0,
265
+ ...(options.compositionId ? { compositionId: options.compositionId } : {}),
266
+ ...(options.authorId ? { authorId: options.authorId } : {}),
267
+ },
268
+ };
269
+ this.dispatch(transaction);
270
+ return transaction;
271
+ }
272
+ /**
273
+ * Transactions committed since revision `sinceRevision`, oldest first -
274
+ * the same source `dispatch()` itself uses to rebase an incoming stale
275
+ * transaction, exposed so a host-implemented CollabTransport can answer
276
+ * `getRevisionHistory` without this editor needing to know anything about
277
+ * transports. Returns null when `sinceRevision` is older than what this
278
+ * instance retained (see `revisionLogLimit`) - the caller must resync.
279
+ */
280
+ getTransactionsSince(sinceRevision) {
281
+ const behindBy = this.current.revision - sinceRevision;
282
+ if (behindBy < 0)
283
+ return null;
284
+ if (behindBy > this.revisionLog.length)
285
+ return null;
286
+ return structuredClone(this.revisionLog.slice(this.revisionLog.length - behindBy));
287
+ }
288
+ dispatch(input) {
289
+ if (input.baseRevision !== this.current.revision) {
290
+ const missed = this.getTransactionsSince(input.baseRevision);
291
+ if (missed === null)
292
+ throw new ResyncRequiredError(this.current.revision);
293
+ const rebased = rebaseTransaction(input, missed, this.current.revision);
294
+ if (rebased.kind === "conflict")
295
+ throw new RebaseConflictError(rebased.reasons);
296
+ if (rebased.kind === "resync-required")
297
+ throw new ResyncRequiredError(rebased.atRevision);
298
+ if (rebased.kind === "dropped")
299
+ return;
300
+ input = rebased.transaction;
301
+ }
302
+ const historyAttributeUpdates = input.metadata.addToHistory ? [] : input.operations.flatMap((operation) => {
303
+ if (operation.type !== "setNodeAttributes")
304
+ return [];
305
+ const node = operation.pos.path.reduce((current, part) => current && !isTextNode(current) ? current.children?.[part] : undefined, this.current.document);
306
+ return node && !isTextNode(node) ? [{ nodeId: node.id, before: operation.before, after: operation.after }] : [];
307
+ });
308
+ const afterUserOperations = applyOperations(this.current.document, input.operations);
309
+ const normalization = runNormalization({
310
+ document: afterUserOperations,
311
+ originatingOperations: input.operations,
312
+ schema: this.schema,
313
+ normalizers: this.normalizers,
314
+ });
315
+ this.lastNormalization = normalization;
316
+ const normalizedSelection = normalization.operations.length
317
+ ? createTransactionMap(normalization.operations).mapSelection(input.selectionAfter)
318
+ : input.selectionAfter;
319
+ const transaction = {
320
+ ...structuredClone(input),
321
+ operations: [...structuredClone(input.operations), ...normalization.operations],
322
+ selectionAfter: normalizedSelection,
323
+ };
324
+ const envelope = applyTransactionAtomic(this.current, transaction, this.schema);
325
+ this.current = {
326
+ ...envelope,
327
+ selection: structuredClone(transaction.selectionAfter),
328
+ storedMarks: structuredClone(transaction.storedMarksAfter),
329
+ };
330
+ this.currentHistory = recordHistory(this.currentHistory, transaction);
331
+ historyAttributeUpdates.forEach((update) => {
332
+ this.currentHistory = rebaseHistoryNodeAttributes(this.currentHistory, update.nodeId, update.before, update.after);
333
+ });
334
+ // Recorded regardless of addToHistory (unlike currentHistory/undo) -
335
+ // this log exists to rebase *other* incoming transactions, which needs
336
+ // every committed change, not just the user-undoable ones.
337
+ this.revisionLog.push(transaction);
338
+ if (this.revisionLog.length > this.revisionLogLimit)
339
+ this.revisionLog.splice(0, this.revisionLog.length - this.revisionLogLimit);
340
+ this.notify(transaction);
341
+ }
342
+ typeText(text, options = {}) {
343
+ const resolved = resolvePos(this.current.document, this.current.selection.head);
344
+ const marks = this.current.storedMarks || marksAtInsertion(this.current.document, resolved.pos, this.schema);
345
+ return this.transact((transaction) => {
346
+ transaction.insertText(resolved, text, marks);
347
+ const next = { path: [...resolved.pos.path], offset: resolved.pos.offset + text.length };
348
+ transaction.setSelection({ type: "text", anchor: next, head: next });
349
+ }, { ...options, source: "input", addToHistory: true });
350
+ }
351
+ setSelection(selection, options = {}) {
352
+ return this.transact((transaction) => {
353
+ transaction.setSelection(selection);
354
+ transaction.setStoredMarks(undefined);
355
+ }, { ...options, addToHistory: false });
356
+ }
357
+ setStoredMarks(marks, options = {}) {
358
+ // `marks?.length ? ... : undefined` used to collapse an explicitly-passed
359
+ // *empty* array down to `undefined` - indistinguishable, downstream, from
360
+ // "no override, infer from surrounding text" (marksAtInsertion). That
361
+ // silently defeated toggling the last active mark off at a collapsed
362
+ // cursor: marksAtInsertion's inclusive-boundary rule re-includes the mark
363
+ // from the text just typed, so the very next keystroke was re-marked
364
+ // regardless of the explicit toggle-off. An explicit `[]` must survive as
365
+ // a real (truthy) empty array so `editor.storedMarks || marksAtInsertion(...)`
366
+ // resolves to `[]`, not the fallback.
367
+ return this.transact((transaction) => transaction.setStoredMarks(marks !== undefined ? canonicalMarkOrder(marks) : undefined), {
368
+ ...options, addToHistory: false,
369
+ });
370
+ }
371
+ undo() {
372
+ const popped = popUndo(this.currentHistory);
373
+ if (!popped)
374
+ return false;
375
+ const inverse = { ...structuredClone(popped.entry.inverse), baseRevision: this.current.revision };
376
+ const envelope = applyTransactionAtomic(this.current, inverse, this.schema);
377
+ this.current = { ...envelope, selection: structuredClone(inverse.selectionAfter), storedMarks: structuredClone(inverse.storedMarksAfter) };
378
+ this.currentHistory = popped.history;
379
+ this.notify(inverse);
380
+ return true;
381
+ }
382
+ redo() {
383
+ const popped = popRedo(this.currentHistory);
384
+ if (!popped)
385
+ return false;
386
+ const forward = { ...structuredClone(popped.entry.forward), baseRevision: this.current.revision, metadata: { ...popped.entry.forward.metadata, addToHistory: false } };
387
+ const envelope = applyTransactionAtomic(this.current, forward, this.schema);
388
+ this.current = { ...envelope, selection: structuredClone(forward.selectionAfter), storedMarks: structuredClone(forward.storedMarksAfter) };
389
+ this.currentHistory = popped.history;
390
+ this.notify(forward);
391
+ return true;
392
+ }
393
+ subscribe(listener) {
394
+ this.listeners.add(listener);
395
+ return () => this.listeners.delete(listener);
396
+ }
397
+ }
398
+ export const createFoundationEditor = (options) => new FoundationEditor(options);
@@ -0,0 +1,45 @@
1
+ import type { SmartDocument, SmartNode } from "../types.js";
2
+ export type FormatId = "html" | "markdown" | "docx" | "pdf";
3
+ export type FormatFidelityLevel = "full" | "semantic" | "lossy" | "unsupported";
4
+ export interface ParseContext {
5
+ readonly format: FormatId;
6
+ }
7
+ export interface SerializeContext {
8
+ readonly format: FormatId;
9
+ }
10
+ /**
11
+ * The per-feature, per-format contract Phase 9 SS2.2 formalizes. Each of the
12
+ * five foundation features (list, marks, block, table, atom) declares one of
13
+ * these per format it participates in, rather than a single serializer
14
+ * containing scattered per-feature type checks.
15
+ *
16
+ * `parse`/`serialize` operate on a single feature's node(s) - they are the
17
+ * semantic mapping rule for "what does this feature mean in this format",
18
+ * not a whole-document driver. Whole-document assembly (correct ordering
19
+ * and nesting across all five features together) is a separate concern,
20
+ * owned by the format's top-level codec (see packages/core/src/foundation/
21
+ * formats/docx/export.ts, pdf/format.ts, and list/formats.ts's
22
+ * serializeCanonicalListHtml/serializeCanonicalListMarkdown for HTML/MD).
23
+ * This mirrors an existing split already present before this contract was
24
+ * formalized: packages/core/src/foundation/{list,marks,block,table,atom}/
25
+ * formats.ts already contain canonical*To{Docx,Pdf}* functions with exactly
26
+ * this per-feature, single-node shape, originally written for fixture-based
27
+ * fidelity verification (see SS2.3) rather than production document assembly.
28
+ */
29
+ export interface FeatureFormatCodec<TFeature extends string = string> {
30
+ readonly feature: TFeature;
31
+ readonly format: FormatId;
32
+ readonly fidelity: FormatFidelityLevel;
33
+ /** Human-readable justification for the fidelity level - required, not decorative (see fidelity.ts's test enforcement). */
34
+ readonly note: string;
35
+ parse?: (input: unknown, ctx: ParseContext) => SmartNode | SmartNode[] | null;
36
+ serialize?: (node: SmartNode, ctx: SerializeContext) => unknown;
37
+ /** Applied when this feature's fidelity for this format is `lossy` or `unsupported` and no better representation exists. */
38
+ fallback?: (node: SmartNode) => SmartNode | null;
39
+ }
40
+ /** Whole-document entry points a format must provide, independent of any single feature. */
41
+ export interface DocumentFormatCodec {
42
+ readonly format: FormatId;
43
+ parseDocument?: (input: unknown, ctx: ParseContext) => SmartDocument;
44
+ serializeDocument?: (document: SmartDocument, ctx: SerializeContext) => unknown;
45
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import type { SmartDocument } from "../../types.js";
2
+ export declare const DOCX_MEDIA_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
3
+ export declare const smartDocumentToDocxXml: (document: SmartDocument) => string;
4
+ export declare const exportDocxDocument: (document: SmartDocument) => Promise<Blob>;