smartrte-core 0.2.1 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/CHANGELOG.md +12 -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 +21 -0
  19. package/dist/foundation/atom/formats.js +77 -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 +20 -0
  127. package/dist/foundation/list/formats.js +795 -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 +26 -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,477 @@
1
+ import { getNodeAtPath, paragraph, } from "../model.js";
2
+ import { listFromBlocks } from "../table.js";
3
+ import { replaceNodeAtPath } from "../tree.js";
4
+ import { resolveListSelectionScope } from "../listScope.js";
5
+ import { listStyleForPresetDepth } from "../listPresets.js";
6
+ const samePath = (left, right) => left.length === right.length && left.every((part, index) => part === right[index]);
7
+ const isBlock = (node) => Boolean(node) && typeof node === "object" &&
8
+ ["paragraph", "heading", "list", "blockquote", "codeBlock", "table"]
9
+ .includes(node.type || "");
10
+ const selectionPaths = (selection) => {
11
+ if (selection.type === "text")
12
+ return [selection.anchor.path, selection.focus.path];
13
+ if (selection.type === "node")
14
+ return [selection.path, selection.path];
15
+ return null;
16
+ };
17
+ const structuralUnit = (document, path) => {
18
+ let firstBlockPath = null;
19
+ for (let depth = 1; depth <= path.length; depth += 1) {
20
+ const candidatePath = path.slice(0, depth);
21
+ const node = getNodeAtPath(document, candidatePath);
22
+ if (!isBlock(node))
23
+ continue;
24
+ if (!firstBlockPath)
25
+ firstBlockPath = candidatePath;
26
+ if (node.type === "list") {
27
+ const itemIndex = path[depth];
28
+ return {
29
+ path: candidatePath,
30
+ ...(Number.isInteger(itemIndex) ? { listItemIndex: itemIndex } : {}),
31
+ };
32
+ }
33
+ // A table is a container boundary, not a selectable text block.
34
+ if (node.type === "table")
35
+ firstBlockPath = null;
36
+ }
37
+ return firstBlockPath ? { path: firstBlockPath } : null;
38
+ };
39
+ const resolveListTarget = (context) => {
40
+ const scope = resolveListSelectionScope(context.document, context.selection);
41
+ if (scope.kind === "none")
42
+ return null;
43
+ if (scope.kind === "all")
44
+ return { kind: "range", containerPath: [], start: 0, end: context.document.children.length - 1 };
45
+ if (scope.kind === "blocks")
46
+ return { kind: "range", containerPath: scope.containerPath, start: scope.start, end: scope.end };
47
+ if (scope.kind === "list")
48
+ return { kind: "items", listPath: scope.listPath, start: scope.start, end: scope.end };
49
+ if (scope.kind === "mixed")
50
+ return { kind: "mixed", containerPath: scope.containerPath, blockIndex: scope.blockIndex, listPath: scope.listPath, itemStart: scope.itemStart, itemEnd: scope.itemEnd };
51
+ return null;
52
+ };
53
+ const itemFromBlock = (block) => ({
54
+ type: "listItem",
55
+ children: [block],
56
+ });
57
+ const blocksFromItem = (item) => item.children.length ? item.children : [paragraph()];
58
+ const replaceContainerChildren = (document, containerPath, children) => {
59
+ const container = getNodeAtPath(document, containerPath);
60
+ if (!container?.children)
61
+ throw new Error("List selection does not resolve to a block container.");
62
+ return replaceNodeAtPath(document, containerPath, { ...container, children });
63
+ };
64
+ const transformBlockRange = (document, target, style, forceList = false) => {
65
+ const container = getNodeAtPath(document, target.containerPath);
66
+ if (!container?.children || target.start < 0 || target.end >= container.children.length) {
67
+ throw new Error("Selected block range is out of bounds.");
68
+ }
69
+ const selected = container.children.slice(target.start, target.end + 1);
70
+ const allSameStyleLists = selected.every((block) => block.type === "list" && block.style === style);
71
+ const replacement = allSameStyleLists && !forceList
72
+ ? selected.flatMap((block) => block.type === "list" ? block.children.flatMap(blocksFromItem) : [block])
73
+ : [{
74
+ type: "list",
75
+ style,
76
+ children: selected.flatMap((block) => block.type === "list" ? block.children : [itemFromBlock(block)]),
77
+ }];
78
+ const children = [
79
+ ...container.children.slice(0, target.start),
80
+ ...replacement,
81
+ ...container.children.slice(target.end + 1),
82
+ ];
83
+ return {
84
+ document: replaceContainerChildren(document, target.containerPath, children),
85
+ path: [...target.containerPath, target.start],
86
+ };
87
+ };
88
+ const transformListItems = (document, target, style) => {
89
+ const list = getNodeAtPath(document, target.listPath);
90
+ if (!list || list.type !== "list" || target.start < 0 || target.end >= list.children.length) {
91
+ throw new Error("Selected list-item range is out of bounds.");
92
+ }
93
+ const before = list.children.slice(0, target.start);
94
+ const selected = list.children.slice(target.start, target.end + 1);
95
+ const after = list.children.slice(target.end + 1);
96
+ const parentPath = target.listPath.slice(0, -1);
97
+ const listIndex = target.listPath[target.listPath.length - 1];
98
+ const parent = getNodeAtPath(document, parentPath);
99
+ if (!parent?.children)
100
+ throw new Error("List parent is not a block container.");
101
+ const replacement = [];
102
+ if (before.length)
103
+ replacement.push({ ...list, children: before });
104
+ if (list.style === style) {
105
+ replacement.push(...selected.flatMap(blocksFromItem));
106
+ }
107
+ else {
108
+ replacement.push({ ...list, style, children: selected });
109
+ }
110
+ if (after.length)
111
+ replacement.push({ ...list, children: after });
112
+ const children = [
113
+ ...parent.children.slice(0, listIndex),
114
+ ...replacement,
115
+ ...parent.children.slice(listIndex + 1),
116
+ ];
117
+ return {
118
+ document: replaceContainerChildren(document, parentPath, children),
119
+ path: [...parentPath, listIndex + (before.length ? 1 : 0)],
120
+ };
121
+ };
122
+ const transformMixedRange = (document, target, style) => {
123
+ const container = getNodeAtPath(document, target.containerPath);
124
+ const source = getNodeAtPath(document, target.listPath);
125
+ if (!container?.children || source?.type !== "list")
126
+ throw new Error("Mixed list selection is invalid.");
127
+ const selectedItems = source.children.slice(target.itemStart, target.itemEnd + 1);
128
+ const mixedList = {
129
+ type: "list",
130
+ style,
131
+ children: [
132
+ itemFromBlock(container.children[target.blockIndex]),
133
+ ...selectedItems,
134
+ ],
135
+ };
136
+ const remaining = source.children.filter((_, index) => index < target.itemStart || index > target.itemEnd);
137
+ const replacement = remaining.length > 0
138
+ ? [mixedList, { ...source, children: remaining }]
139
+ : [mixedList];
140
+ const children = [...container.children];
141
+ children.splice(Math.min(target.blockIndex, target.listPath[target.listPath.length - 1]), 2, ...replacement);
142
+ return {
143
+ document: replaceContainerChildren(document, target.containerPath, children),
144
+ path: [...target.containerPath, Math.min(target.blockIndex, target.listPath[target.listPath.length - 1])],
145
+ };
146
+ };
147
+ const cascadeListStyles = (list, depth = 0) => {
148
+ if (list.preset) {
149
+ return {
150
+ ...list,
151
+ style: listStyleForPresetDepth(list.preset, depth),
152
+ children: list.children.map((item) => ({
153
+ ...item,
154
+ children: item.children.map((child) => child.type === "list"
155
+ ? cascadeListStyles({ ...child, preset: list.preset }, depth + 1)
156
+ : child),
157
+ })),
158
+ };
159
+ }
160
+ const ordered = ["decimal", "lower-alpha", "lower-roman", "upper-alpha", "upper-roman"];
161
+ const unordered = ["disc", "circle", "square"];
162
+ const orderedStyle = list.style === "decimal-leading-zero" || ordered.includes(list.style);
163
+ const family = orderedStyle ? ordered : unordered;
164
+ const style = depth === 0 ? list.style : family[Math.min(depth, family.length - 1)];
165
+ return {
166
+ ...list,
167
+ style,
168
+ children: list.children.map((item) => ({
169
+ ...item,
170
+ children: item.children.map((child) => child.type === "list" ? cascadeListStyles({ ...child, style }, depth + 1) : child),
171
+ })),
172
+ };
173
+ };
174
+ export const toggleList = {
175
+ id: "list.toggle",
176
+ isEnabled: (context, input) => Boolean(input && resolveListTarget(context)),
177
+ execute: (context, input) => {
178
+ if (!input)
179
+ throw new Error("list.toggle requires a list style.");
180
+ const target = resolveListTarget(context);
181
+ if (!target)
182
+ throw new Error("list.toggle requires a compatible block selection.");
183
+ const effectiveTarget = target.kind === "items" && input.scope !== "selected-items"
184
+ ? {
185
+ kind: "range",
186
+ containerPath: target.listPath.slice(0, -1),
187
+ start: target.listPath[target.listPath.length - 1],
188
+ end: target.listPath[target.listPath.length - 1],
189
+ }
190
+ : target;
191
+ const selectedNode = effectiveTarget.kind === "range" && effectiveTarget.start === effectiveTarget.end
192
+ ? getNodeAtPath(context.document, [...effectiveTarget.containerPath, effectiveTarget.start])
193
+ : null;
194
+ if (input.checklist &&
195
+ effectiveTarget.kind === "range" &&
196
+ selectedNode?.type === "list" &&
197
+ selectedNode.checklist) {
198
+ const list = selectedNode;
199
+ const document = replaceNodeAtPath(context.document, [...effectiveTarget.containerPath, effectiveTarget.start], {
200
+ ...list,
201
+ checklist: undefined,
202
+ strikeCompleted: undefined,
203
+ children: list.children.map((item) => ({ ...item, checked: undefined })),
204
+ });
205
+ return {
206
+ id: "list.toggle",
207
+ source: "user",
208
+ operations: [{ type: "replaceNode", path: [], node: document }],
209
+ selectionBefore: context.selection,
210
+ selectionAfter: context.selection,
211
+ addToHistory: true,
212
+ timestamp: context.now?.() ?? Date.now(),
213
+ };
214
+ }
215
+ let result = effectiveTarget.kind === "range"
216
+ ? transformBlockRange(context.document, effectiveTarget, input.style, input.checklist || Boolean(input.preset))
217
+ : effectiveTarget.kind === "items"
218
+ ? transformListItems(context.document, effectiveTarget, input.style)
219
+ : transformMixedRange(context.document, effectiveTarget, input.style);
220
+ if (input.preset) {
221
+ const transformed = getNodeAtPath(result.document, result.path);
222
+ if (transformed?.type === "list") {
223
+ const list = transformed;
224
+ result = {
225
+ ...result,
226
+ document: replaceNodeAtPath(result.document, result.path, {
227
+ ...list,
228
+ preset: input.preset,
229
+ style: listStyleForPresetDepth(input.preset, 0),
230
+ }),
231
+ };
232
+ }
233
+ }
234
+ if (input.checklist) {
235
+ const transformed = getNodeAtPath(result.document, result.path);
236
+ if (transformed?.type === "list") {
237
+ const list = transformed;
238
+ result = {
239
+ ...result,
240
+ document: replaceNodeAtPath(result.document, result.path, {
241
+ ...list,
242
+ checklist: true,
243
+ ...(input.strikeCompleted ? { strikeCompleted: true } : {}),
244
+ children: list.children.map((item) => ({
245
+ ...item,
246
+ checked: item.checked ?? false,
247
+ })),
248
+ }),
249
+ };
250
+ }
251
+ }
252
+ if (input.cascadeStyles) {
253
+ const transformed = getNodeAtPath(result.document, result.path);
254
+ if (transformed?.type === "list") {
255
+ result = {
256
+ ...result,
257
+ document: replaceNodeAtPath(result.document, result.path, cascadeListStyles(transformed)),
258
+ };
259
+ }
260
+ }
261
+ return {
262
+ id: "list.toggle",
263
+ source: "user",
264
+ operations: [{ type: "replaceNode", path: [], node: result.document }],
265
+ selectionBefore: context.selection,
266
+ selectionAfter: { type: "node", path: result.path },
267
+ addToHistory: true,
268
+ timestamp: context.now?.() ?? Date.now(),
269
+ };
270
+ },
271
+ };
272
+ const deepestListUnit = (document, path) => {
273
+ let result = null;
274
+ for (let depth = 1; depth < path.length; depth += 1) {
275
+ const listPath = path.slice(0, depth);
276
+ const node = getNodeAtPath(document, listPath);
277
+ if (node?.type === "list" && Number.isInteger(path[depth])) {
278
+ result = { listPath, itemIndex: path[depth] };
279
+ }
280
+ }
281
+ return result;
282
+ };
283
+ const listItemTarget = (context) => {
284
+ const paths = selectionPaths(context.selection);
285
+ if (!paths)
286
+ return null;
287
+ const anchor = deepestListUnit(context.document, paths[0]);
288
+ const focus = deepestListUnit(context.document, paths[1]);
289
+ if (!anchor || !focus || !samePath(anchor.listPath, focus.listPath))
290
+ return null;
291
+ return {
292
+ kind: "items",
293
+ listPath: anchor.listPath,
294
+ start: Math.min(anchor.itemIndex, focus.itemIndex),
295
+ end: Math.max(anchor.itemIndex, focus.itemIndex),
296
+ };
297
+ };
298
+ const listTransaction = (id, context, document, selectionAfter) => ({
299
+ id,
300
+ source: "user",
301
+ operations: [{ type: "replaceNode", path: [], node: document }],
302
+ selectionBefore: context.selection,
303
+ selectionAfter,
304
+ addToHistory: true,
305
+ timestamp: context.now?.() ?? Date.now(),
306
+ });
307
+ const pathStartsWith = (path, prefix) => prefix.length <= path.length && prefix.every((part, index) => path[index] === part);
308
+ const mapSelectedTextItems = (selection, listPath, start, end, mapPath) => {
309
+ if (selection.type !== "text")
310
+ return null;
311
+ const mapPoint = (point) => {
312
+ if (!pathStartsWith(point.path, listPath) || point.path.length <= listPath.length)
313
+ return null;
314
+ const itemIndex = point.path[listPath.length];
315
+ if (itemIndex < start || itemIndex > end)
316
+ return null;
317
+ return {
318
+ path: mapPath(itemIndex, point.path.slice(listPath.length + 1)),
319
+ offset: point.offset,
320
+ };
321
+ };
322
+ const anchor = mapPoint(selection.anchor);
323
+ const focus = mapPoint(selection.focus);
324
+ return anchor && focus ? { type: "text", anchor, focus } : null;
325
+ };
326
+ export const indentListItems = {
327
+ id: "list.indent",
328
+ isEnabled: (context) => {
329
+ const target = listItemTarget(context);
330
+ return Boolean(target && target.start > 0);
331
+ },
332
+ execute: (context) => {
333
+ const target = listItemTarget(context);
334
+ if (!target || target.start === 0) {
335
+ throw new Error("list.indent requires list items with a previous sibling.");
336
+ }
337
+ const list = getNodeAtPath(context.document, target.listPath);
338
+ const previous = list.children[target.start - 1];
339
+ const selected = list.children.slice(target.start, target.end + 1);
340
+ const lastChild = previous.children[previous.children.length - 1];
341
+ const existingNestedCount = lastChild?.type === "list" && lastChild.style === list.style
342
+ ? lastChild.children.length
343
+ : 0;
344
+ const nested = lastChild?.type === "list" && lastChild.style === list.style
345
+ ? { ...lastChild, children: [...lastChild.children, ...selected] }
346
+ : { type: "list", style: list.style, children: selected };
347
+ const previousChildren = lastChild?.type === "list" && lastChild.style === list.style
348
+ ? [...previous.children.slice(0, -1), nested]
349
+ : [...previous.children, nested];
350
+ const nextList = {
351
+ ...list,
352
+ children: [
353
+ ...list.children.slice(0, target.start - 1),
354
+ { ...previous, children: previousChildren },
355
+ ...list.children.slice(target.end + 1),
356
+ ],
357
+ };
358
+ const document = replaceNodeAtPath(context.document, target.listPath, nextList);
359
+ const selectionAfter = mapSelectedTextItems(context.selection, target.listPath, target.start, target.end, (itemIndex, suffix) => [
360
+ ...target.listPath,
361
+ target.start - 1,
362
+ previousChildren.length - 1,
363
+ existingNestedCount + itemIndex - target.start,
364
+ ...suffix,
365
+ ]) || { type: "node", path: [...target.listPath, target.start - 1, previousChildren.length - 1] };
366
+ return listTransaction("list.indent", context, document, selectionAfter);
367
+ },
368
+ };
369
+ export const outdentListItems = {
370
+ id: "list.outdent",
371
+ isEnabled: (context) => {
372
+ const target = listItemTarget(context);
373
+ if (!target || target.listPath.length < 3)
374
+ return false;
375
+ const parentItemPath = target.listPath.slice(0, -1);
376
+ const parentItem = getNodeAtPath(context.document, parentItemPath);
377
+ const ancestorList = getNodeAtPath(context.document, parentItemPath.slice(0, -1));
378
+ return parentItem?.type === "listItem" &&
379
+ ancestorList?.type === "list";
380
+ },
381
+ execute: (context) => {
382
+ const target = listItemTarget(context);
383
+ if (!target || !outdentListItems.isEnabled(context)) {
384
+ throw new Error("list.outdent requires items in a nested list.");
385
+ }
386
+ const nestedList = getNodeAtPath(context.document, target.listPath);
387
+ const parentItemPath = target.listPath.slice(0, -1);
388
+ const parentItem = getNodeAtPath(context.document, parentItemPath);
389
+ const ancestorListPath = parentItemPath.slice(0, -1);
390
+ const ancestorList = getNodeAtPath(context.document, ancestorListPath);
391
+ const parentIndex = parentItemPath[parentItemPath.length - 1];
392
+ const nestedIndex = target.listPath[target.listPath.length - 1];
393
+ const selected = nestedList.children.slice(target.start, target.end + 1);
394
+ const remaining = [
395
+ ...nestedList.children.slice(0, target.start),
396
+ ...nestedList.children.slice(target.end + 1),
397
+ ];
398
+ const nextParent = {
399
+ ...parentItem,
400
+ children: remaining.length
401
+ ? [
402
+ ...parentItem.children.slice(0, nestedIndex),
403
+ { ...nestedList, children: remaining },
404
+ ...parentItem.children.slice(nestedIndex + 1),
405
+ ]
406
+ : [
407
+ ...parentItem.children.slice(0, nestedIndex),
408
+ ...parentItem.children.slice(nestedIndex + 1),
409
+ ],
410
+ };
411
+ const nextAncestor = {
412
+ ...ancestorList,
413
+ children: [
414
+ ...ancestorList.children.slice(0, parentIndex),
415
+ nextParent,
416
+ ...selected,
417
+ ...ancestorList.children.slice(parentIndex + 1),
418
+ ],
419
+ };
420
+ const document = replaceNodeAtPath(context.document, ancestorListPath, nextAncestor);
421
+ const selectionAfter = mapSelectedTextItems(context.selection, target.listPath, target.start, target.end, (itemIndex, suffix) => [
422
+ ...ancestorListPath,
423
+ parentIndex + 1 + itemIndex - target.start,
424
+ ...suffix,
425
+ ]) || { type: "node", path: [...ancestorListPath, parentIndex + 1] };
426
+ return listTransaction("list.outdent", context, document, selectionAfter);
427
+ },
428
+ };
429
+ /**
430
+ * Converts explicitly selected block nodes in one table cell into a single
431
+ * list. No DOM ranges, wrappers, or React state participate in this command.
432
+ */
433
+ export const toggleTableCellList = (document, selection, input) => {
434
+ const table = getNodeAtPath(document, input.tablePath);
435
+ if (!table || table.type !== "table")
436
+ throw new Error("Expected a table at tablePath.");
437
+ const cell = table.children[input.row]?.children[input.column];
438
+ if (!cell || (cell.type !== "tableCell" && cell.type !== "tableHeaderCell")) {
439
+ throw new Error("Expected a table cell at the requested coordinates.");
440
+ }
441
+ const indexes = [...new Set(input.blockIndexes)].sort((left, right) => left - right);
442
+ if (indexes.length === 0)
443
+ throw new Error("At least one table-cell block must be selected.");
444
+ if (indexes.some((index) => index < 0 || index >= cell.children.length)) {
445
+ throw new Error("Selected table-cell block index is out of bounds.");
446
+ }
447
+ const selected = indexes.map((index) => cell.children[index]);
448
+ const selectedIndexes = new Set(indexes);
449
+ const firstIndex = indexes[0];
450
+ const list = listFromBlocks(selected, input.style);
451
+ const nextCell = {
452
+ ...cell,
453
+ children: cell.children.reduce((children, block, index) => {
454
+ if (index === firstIndex)
455
+ children.push(list);
456
+ if (!selectedIndexes.has(index))
457
+ children.push(block);
458
+ return children;
459
+ }, []),
460
+ };
461
+ const cellPath = [...input.tablePath, input.row, input.column];
462
+ const nextDocument = replaceNodeAtPath(document, cellPath, nextCell);
463
+ const listPath = [...cellPath, firstIndex];
464
+ const selectionAfter = { type: "node", path: listPath };
465
+ return {
466
+ document: nextDocument,
467
+ transaction: {
468
+ id: "toggle-table-cell-list",
469
+ source: "user",
470
+ operations: [{ type: "replaceNode", path: cellPath, node: nextCell }],
471
+ selectionBefore: selection,
472
+ selectionAfter,
473
+ addToHistory: true,
474
+ timestamp: Date.now(),
475
+ },
476
+ };
477
+ };
@@ -2,11 +2,14 @@ import type { SmartCommand } from "../command.js";
2
2
  export declare const toggleBold: SmartCommand<void>;
3
3
  export declare const toggleItalic: SmartCommand<void>;
4
4
  export declare const toggleUnderline: SmartCommand<void>;
5
+ export declare const toggleStrike: SmartCommand<void>;
6
+ export declare const toggleInlineCode: SmartCommand<void>;
5
7
  export declare const toggleSuperscript: SmartCommand<void>;
6
8
  export declare const toggleSubscript: SmartCommand<void>;
7
9
  export declare const applyTextColor: SmartCommand<string>;
8
10
  export declare const applyBackgroundColor: SmartCommand<string>;
9
11
  export declare const applyFontSize: SmartCommand<number>;
12
+ export declare const applyFontFamily: SmartCommand<string>;
10
13
  type LinkInput = {
11
14
  href: string;
12
15
  target?: string;
@@ -1,5 +1,5 @@
1
1
  import { addMark, hasMark, removeMark } from "../marks.js";
2
- import { getNodeAtPath } from "../model.js";
2
+ import { getNodeAtPath, } from "../model.js";
3
3
  import { sanitizeLinkAttrs } from "../security/urlPolicy.js";
4
4
  const samePath = (left, right) => left.length === right.length && left.every((part, index) => part === right[index]);
5
5
  const getTextRange = (context) => {
@@ -20,11 +20,15 @@ const getTextRange = (context) => {
20
20
  const nodes = parent?.children;
21
21
  if (!nodes || startIndex < 0 || endIndex >= nodes.length || startOffset < 0 || endOffset < 0)
22
22
  return null;
23
- if (nodes.slice(startIndex, endIndex + 1).some((node) => node.type !== "text"))
23
+ const startNode = nodes[startIndex];
24
+ const endNode = nodes[endIndex];
25
+ if (startNode?.type !== "text" || endNode?.type !== "text")
24
26
  return null;
25
- if (startIndex === endIndex && (startOffset === endOffset || endOffset > nodes[startIndex].text.length))
27
+ if (startIndex === endIndex && (startOffset === endOffset || endOffset > startNode.text.length))
26
28
  return null;
27
- if (startOffset > nodes[startIndex].text.length || endOffset > nodes[endIndex].text.length)
29
+ if (startOffset > startNode.text.length || endOffset > endNode.text.length)
30
+ return null;
31
+ if (!nodes.slice(startIndex, endIndex + 1).some((node) => node.type === "text"))
28
32
  return null;
29
33
  return { selection: context.selection, parentPath, startIndex, endIndex, startOffset, endOffset, nodes };
30
34
  };
@@ -37,6 +41,10 @@ const replaceRange = (range, mutate) => {
37
41
  children.push(node);
38
42
  return;
39
43
  }
44
+ if (node.type !== "text") {
45
+ children.push(node);
46
+ return;
47
+ }
40
48
  const from = index === range.startIndex ? range.startOffset : 0;
41
49
  const to = index === range.endIndex ? range.endOffset : node.text.length;
42
50
  if (from > 0)
@@ -54,16 +62,23 @@ const replaceRange = (range, mutate) => {
54
62
  return { children, start: selectionStart, end: selectionEnd };
55
63
  };
56
64
  const hasLink = (marks) => marks?.some((mark) => mark.type === "link") || false;
65
+ const selectedTextNodes = (range) => range.nodes
66
+ .slice(range.startIndex, range.endIndex + 1)
67
+ .filter((node) => node.type === "text");
57
68
  const createMarkTransaction = (id, context, input, mutate) => {
58
69
  const range = getTextRange(context);
59
70
  if (!range)
60
71
  throw new Error(`${id} requires text in one inline container.`);
61
72
  const result = replaceRange(range, (marks) => mutate(marks, range));
62
73
  const parent = getNodeAtPath(context.document, range.parentPath);
74
+ const selectionEnd = result.children[result.end];
63
75
  const selectionAfter = {
64
76
  type: "text",
65
77
  anchor: { path: [...range.parentPath, result.start], offset: 0 },
66
- focus: { path: [...range.parentPath, result.end], offset: result.children[result.end]?.text.length || 0 },
78
+ focus: {
79
+ path: [...range.parentPath, result.end],
80
+ offset: selectionEnd?.type === "text" ? selectionEnd.text.length : 0,
81
+ },
67
82
  };
68
83
  return {
69
84
  id,
@@ -83,7 +98,7 @@ const createMarkCommand = (id, nextMark, toggle) => ({
83
98
  if (!range)
84
99
  throw new Error(`${id} requires text in one inline container.`);
85
100
  const mark = nextMark(input);
86
- const selected = range.nodes.slice(range.startIndex, range.endIndex + 1);
101
+ const selected = selectedTextNodes(range);
87
102
  const remove = toggle && selected.every((node) => hasMark(node.marks, mark.type));
88
103
  return createMarkTransaction(id, context, input, (marks) => remove ? removeMark(marks, mark.type) : addMark(marks, mark));
89
104
  },
@@ -96,8 +111,7 @@ const toggleScript = (id, type) => ({
96
111
  const range = getTextRange(context);
97
112
  if (!range)
98
113
  throw new Error(`${id} requires text in one inline container.`);
99
- const remove = range.nodes
100
- .slice(range.startIndex, range.endIndex + 1)
114
+ const remove = selectedTextNodes(range)
101
115
  .every((node) => hasMark(node.marks, type));
102
116
  const opposite = type === "superscript" ? "subscript" : "superscript";
103
117
  return createMarkTransaction(id, context, undefined, (marks) => {
@@ -109,11 +123,14 @@ const toggleScript = (id, type) => ({
109
123
  export const toggleBold = toggle("toggle-bold", "bold");
110
124
  export const toggleItalic = toggle("toggle-italic", "italic");
111
125
  export const toggleUnderline = toggle("toggle-underline", "underline");
126
+ export const toggleStrike = toggle("toggle-strike", "strike");
127
+ export const toggleInlineCode = toggle("toggle-inline-code", "code");
112
128
  export const toggleSuperscript = toggleScript("toggle-superscript", "superscript");
113
129
  export const toggleSubscript = toggleScript("toggle-subscript", "subscript");
114
130
  export const applyTextColor = createMarkCommand("apply-text-color", (value) => ({ type: "textColor", value }), false);
115
131
  export const applyBackgroundColor = createMarkCommand("apply-background-color", (value) => ({ type: "backgroundColor", value }), false);
116
132
  export const applyFontSize = createMarkCommand("apply-font-size", (valuePx) => ({ type: "fontSize", valuePx }), false);
133
+ export const applyFontFamily = createMarkCommand("apply-font-family", (value) => ({ type: "fontFamily", value }), false);
117
134
  export const applyLink = {
118
135
  id: "apply-link",
119
136
  isEnabled: (context, input) => Boolean(getTextRange(context) && input && sanitizeLinkAttrs(input)),
@@ -128,12 +145,12 @@ export const updateLink = {
128
145
  id: "update-link",
129
146
  isEnabled: (context, input) => {
130
147
  const range = getTextRange(context);
131
- return Boolean(range && input && sanitizeLinkAttrs(input) && range.nodes.slice(range.startIndex, range.endIndex + 1).some((node) => hasLink(node.marks)));
148
+ return Boolean(range && input && sanitizeLinkAttrs(input) && selectedTextNodes(range).some((node) => hasLink(node.marks)));
132
149
  },
133
150
  execute: (context, input) => {
134
151
  const range = getTextRange(context);
135
152
  const safe = sanitizeLinkAttrs(input || {});
136
- if (!range || !safe || !range.nodes.slice(range.startIndex, range.endIndex + 1).some((node) => hasLink(node.marks))) {
153
+ if (!range || !safe || !selectedTextNodes(range).some((node) => hasLink(node.marks))) {
137
154
  throw new Error("update-link requires selected linked text and a safe href.");
138
155
  }
139
156
  return createMarkTransaction("update-link", context, input, (marks) => addMark(marks, { type: "link", ...safe }));
@@ -143,7 +160,7 @@ export const removeLink = {
143
160
  id: "remove-link",
144
161
  isEnabled: (context) => {
145
162
  const range = getTextRange(context);
146
- return Boolean(range && range.nodes.slice(range.startIndex, range.endIndex + 1).some((node) => hasLink(node.marks)));
163
+ return Boolean(range && selectedTextNodes(range).some((node) => hasLink(node.marks)));
147
164
  },
148
165
  execute: (context) => {
149
166
  if (!removeLink.isEnabled(context))
@@ -0,0 +1,31 @@
1
+ import type { SmartCommand } from "../command.js";
2
+ import { type Path } from "../model.js";
3
+ export interface InsertImageInput {
4
+ src: string;
5
+ alt?: string;
6
+ title?: string;
7
+ width?: number;
8
+ height?: number;
9
+ path?: Path;
10
+ }
11
+ export interface InsertInlineImageInput extends InsertImageInput {
12
+ }
13
+ export declare const insertImage: SmartCommand<InsertImageInput>;
14
+ export declare const insertInlineImage: SmartCommand<InsertInlineImageInput>;
15
+ export declare const deleteInlineImage: SmartCommand<import("./inlineAtoms.js").InlineAtomPathInput>;
16
+ export interface UpdateInlineImageInput {
17
+ path: Path;
18
+ alt?: string;
19
+ title?: string;
20
+ width?: number;
21
+ height?: number;
22
+ }
23
+ export declare const updateInlineImage: SmartCommand<UpdateInlineImageInput>;
24
+ export interface InsertMediaInput {
25
+ src: string;
26
+ mediaType: "audio" | "video" | "file";
27
+ title?: string;
28
+ mimeType?: string;
29
+ path?: Path;
30
+ }
31
+ export declare const insertMedia: SmartCommand<InsertMediaInput>;