@thomasfosterau/effect-richtext 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +117 -1
  3. package/dist/_virtual/_rolldown/runtime.js +12 -0
  4. package/dist/commands/commands.d.ts +323 -0
  5. package/dist/commands/commands.js +722 -0
  6. package/dist/commands/internal.js +43 -0
  7. package/dist/commands/keymaps.d.ts +46 -0
  8. package/dist/commands/keymaps.js +64 -0
  9. package/dist/commands.d.ts +3 -0
  10. package/dist/commands.js +3 -0
  11. package/dist/compat/commands.d.ts +250 -0
  12. package/dist/compat/commands.js +275 -0
  13. package/dist/compat/internal/core.d.ts +20 -0
  14. package/dist/compat/internal/core.js +148 -0
  15. package/dist/compat/internal/props.js +82 -0
  16. package/dist/compat/keymap.d.ts +21 -0
  17. package/dist/compat/keymap.js +38 -0
  18. package/dist/compat/model.d.ts +893 -0
  19. package/dist/compat/model.js +1212 -0
  20. package/dist/compat/orderedmap.d.ts +74 -0
  21. package/dist/compat/orderedmap.js +124 -0
  22. package/dist/compat/schema-basic.d.ts +38 -0
  23. package/dist/compat/schema-basic.js +238 -0
  24. package/dist/compat/schema-list.d.ts +81 -0
  25. package/dist/compat/schema-list.js +156 -0
  26. package/dist/compat/state.d.ts +415 -0
  27. package/dist/compat/state.js +689 -0
  28. package/dist/compat/test-builder.d.ts +98 -0
  29. package/dist/compat/test-builder.js +197 -0
  30. package/dist/compat/transform.d.ts +458 -0
  31. package/dist/compat/transform.js +803 -0
  32. package/dist/compat/view.d.ts +411 -0
  33. package/dist/compat/view.js +409 -0
  34. package/dist/dropcursor.d.ts +46 -0
  35. package/dist/dropcursor.js +176 -0
  36. package/dist/extensions/defaults.d.ts +716 -0
  37. package/dist/extensions/defaults.js +209 -0
  38. package/dist/extensions/editor.d.ts +246 -0
  39. package/dist/extensions/editor.js +271 -0
  40. package/dist/extensions/extension.d.ts +375 -0
  41. package/dist/extensions/extension.js +233 -0
  42. package/dist/extensions.d.ts +4 -0
  43. package/dist/extensions.js +4 -0
  44. package/dist/gapcursor.d.ts +66 -0
  45. package/dist/gapcursor.js +273 -0
  46. package/dist/history.d.ts +178 -0
  47. package/dist/history.js +412 -0
  48. package/dist/html.d.ts +158 -0
  49. package/dist/html.js +164 -0
  50. package/dist/inputrules.d.ts +271 -0
  51. package/dist/inputrules.js +321 -0
  52. package/dist/internal/attempt.js +15 -0
  53. package/dist/internal/json.js +29 -0
  54. package/dist/keymap/keymap.d.ts +130 -0
  55. package/dist/keymap/keymap.js +159 -0
  56. package/dist/keymap.d.ts +2 -0
  57. package/dist/keymap.js +1 -0
  58. package/dist/markdown/errors.d.ts +23 -0
  59. package/dist/markdown/errors.js +26 -0
  60. package/dist/markdown/from_markdown.d.ts +185 -0
  61. package/dist/markdown/from_markdown.js +266 -0
  62. package/dist/markdown/schema.d.ts +522 -0
  63. package/dist/markdown/schema.js +292 -0
  64. package/dist/markdown/to_markdown.d.ts +251 -0
  65. package/dist/markdown/to_markdown.js +413 -0
  66. package/dist/markdown.d.ts +5 -0
  67. package/dist/markdown.js +5 -0
  68. package/dist/model/attrsCodec.js +48 -0
  69. package/dist/model/comparedeep.js +17 -0
  70. package/dist/model/content.d.ts +232 -0
  71. package/dist/model/content.js +630 -0
  72. package/dist/model/diff.js +66 -0
  73. package/dist/model/dom.d.ts +3 -0
  74. package/dist/model/errors.d.ts +90 -0
  75. package/dist/model/errors.js +76 -0
  76. package/dist/model/fragment.d.ts +131 -0
  77. package/dist/model/fragment.js +262 -0
  78. package/dist/model/from_dom.d.ts +299 -0
  79. package/dist/model/from_dom.js +618 -0
  80. package/dist/model/json.d.ts +37 -0
  81. package/dist/model/json.js +126 -0
  82. package/dist/model/mark.d.ts +73 -0
  83. package/dist/model/mark.js +121 -0
  84. package/dist/model/node.d.ts +267 -0
  85. package/dist/model/node.js +434 -0
  86. package/dist/model/replace.d.ts +78 -0
  87. package/dist/model/replace.js +207 -0
  88. package/dist/model/resolvedpos.d.ts +196 -0
  89. package/dist/model/resolvedpos.js +305 -0
  90. package/dist/model/schema.d.ts +740 -0
  91. package/dist/model/schema.js +451 -0
  92. package/dist/model/to_dom.d.ts +169 -0
  93. package/dist/model/to_dom.js +238 -0
  94. package/dist/model.d.ts +12 -0
  95. package/dist/model.js +12 -0
  96. package/dist/placeholder.d.ts +53 -0
  97. package/dist/placeholder.js +79 -0
  98. package/dist/reactive/observer.d.ts +54 -0
  99. package/dist/reactive/observer.js +61 -0
  100. package/dist/reactive/selectors.d.ts +22 -0
  101. package/dist/reactive/selectors.js +61 -0
  102. package/dist/reactive/stateref.d.ts +72 -0
  103. package/dist/reactive/stateref.js +71 -0
  104. package/dist/reactive/streams.d.ts +83 -0
  105. package/dist/reactive/streams.js +85 -0
  106. package/dist/reactive.d.ts +16 -0
  107. package/dist/reactive.js +26 -0
  108. package/dist/schema-basic.d.ts +448 -0
  109. package/dist/schema-basic.js +269 -0
  110. package/dist/schema-list/commands.d.ts +66 -0
  111. package/dist/schema-list/commands.js +203 -0
  112. package/dist/schema-list.d.ts +92 -0
  113. package/dist/schema-list.js +100 -0
  114. package/dist/state/errors.d.ts +39 -0
  115. package/dist/state/errors.js +32 -0
  116. package/dist/state/plugin.d.ts +232 -0
  117. package/dist/state/plugin.js +82 -0
  118. package/dist/state/selection.d.ts +297 -0
  119. package/dist/state/selection.js +486 -0
  120. package/dist/state/state.d.ts +212 -0
  121. package/dist/state/state.js +489 -0
  122. package/dist/state/transaction.d.ts +145 -0
  123. package/dist/state/transaction.js +195 -0
  124. package/dist/state.d.ts +6 -0
  125. package/dist/state.js +6 -0
  126. package/dist/test-builder.d.ts +3582 -0
  127. package/dist/test-builder.js +239 -0
  128. package/dist/transform/attr_step.d.ts +60 -0
  129. package/dist/transform/attr_step.js +105 -0
  130. package/dist/transform/errors.d.ts +50 -0
  131. package/dist/transform/errors.js +35 -0
  132. package/dist/transform/map.d.ts +182 -0
  133. package/dist/transform/map.js +280 -0
  134. package/dist/transform/mark.js +98 -0
  135. package/dist/transform/mark_set_step.js +69 -0
  136. package/dist/transform/mark_step.d.ts +113 -0
  137. package/dist/transform/mark_step.js +225 -0
  138. package/dist/transform/replace.d.ts +18 -0
  139. package/dist/transform/replace.js +381 -0
  140. package/dist/transform/replace_step.d.ts +109 -0
  141. package/dist/transform/replace_step.js +175 -0
  142. package/dist/transform/step.d.ts +157 -0
  143. package/dist/transform/step.js +168 -0
  144. package/dist/transform/structure.d.ts +89 -0
  145. package/dist/transform/structure.js +350 -0
  146. package/dist/transform/transform.d.ts +248 -0
  147. package/dist/transform/transform.js +373 -0
  148. package/dist/transform.d.ts +10 -0
  149. package/dist/transform.js +10 -0
  150. package/dist/view/browser.js +39 -0
  151. package/dist/view/capturekeys.js +282 -0
  152. package/dist/view/clipboard.d.ts +8 -0
  153. package/dist/view/clipboard.js +245 -0
  154. package/dist/view/decoration.d.ts +339 -0
  155. package/dist/view/decoration.js +566 -0
  156. package/dist/view/dom.d.ts +18 -0
  157. package/dist/view/dom.js +140 -0
  158. package/dist/view/domchange.js +295 -0
  159. package/dist/view/domcoords.d.ts +22 -0
  160. package/dist/view/domcoords.js +448 -0
  161. package/dist/view/domobserver.d.ts +47 -0
  162. package/dist/view/domobserver.js +328 -0
  163. package/dist/view/errors.d.ts +18 -0
  164. package/dist/view/errors.js +21 -0
  165. package/dist/view/input.d.ts +58 -0
  166. package/dist/view/input.js +704 -0
  167. package/dist/view/selection.js +198 -0
  168. package/dist/view/view.d.ts +699 -0
  169. package/dist/view/view.js +623 -0
  170. package/dist/view/viewdesc.d.ts +236 -0
  171. package/dist/view/viewdesc.js +1205 -0
  172. package/dist/view-css.d.ts +43 -0
  173. package/dist/view-css.js +124 -0
  174. package/dist/view.d.ts +6 -0
  175. package/dist/view.js +4 -0
  176. package/package.json +200 -4
@@ -0,0 +1,434 @@
1
+ import { attempt } from "../internal/attempt.js";
2
+ import { showJson } from "../internal/json.js";
3
+ import { encodeAttrs } from "./attrsCodec.js";
4
+ import { compareDeep } from "./comparedeep.js";
5
+ import { ContentError, PositionError, isModelError } from "./errors.js";
6
+ import { Fragment } from "./fragment.js";
7
+ import { Mark } from "./mark.js";
8
+ import { Slice, replace } from "./replace.js";
9
+ import { ResolvedPos } from "./resolvedpos.js";
10
+ import * as Option from "effect/Option";
11
+ import * as Equal from "effect/Equal";
12
+ import * as Hash from "effect/Hash";
13
+ // src/model/node.ts
14
+ const emptyAttrs = Object.create(null);
15
+ const isPositionError = (u) => u instanceof PositionError;
16
+ /**
17
+ * This class represents a node in the tree that makes up a document.
18
+ * So a document is an instance of `Node`, with children that are also
19
+ * instances of `Node`.
20
+ *
21
+ * Nodes are persistent data structures. Instead of changing them, you
22
+ * create new ones with the content you want. Old ones keep pointing at
23
+ * the old document shape. This is made cheaper by sharing structure
24
+ * between the old and new data as much as possible.
25
+ *
26
+ * The type parameters track the schema and the node type name (or
27
+ * union of names), which determines the type of `attrs` and of the
28
+ * node's children. Use `is` to narrow a node to a specific type.
29
+ *
30
+ * Nodes implement `Equal`, comparing structurally.
31
+ *
32
+ * @since 0.1.0
33
+ * @category models
34
+ */
35
+ var Node = class Node {
36
+ /** @internal */
37
+ constructor(type, attrs, content, marks = Mark.none) {
38
+ this.type = type;
39
+ this.attrs = attrs;
40
+ this.marks = marks;
41
+ this.content = content || Fragment.empty;
42
+ }
43
+ /** Narrow this node to the given node type(s). */
44
+ is(...names) {
45
+ return names.indexOf(this.type.name) > -1;
46
+ }
47
+ /** Narrow this node to a text node. */
48
+ isTextNode() {
49
+ return this.type.isText;
50
+ }
51
+ /** The array of this node's child nodes. */
52
+ get children() {
53
+ return this.content.content;
54
+ }
55
+ /**
56
+ * The size of this node, as defined by the integer-based indexing
57
+ * scheme. For text nodes, this is the amount of characters. For other
58
+ * leaf nodes, it is one. For non-leaf nodes, it is the size of the
59
+ * content plus two (the start and end token).
60
+ */
61
+ get nodeSize() {
62
+ return this.isLeaf ? 1 : 2 + this.content.size;
63
+ }
64
+ /** The number of children that the node has. */
65
+ get childCount() {
66
+ return this.content.childCount;
67
+ }
68
+ /** Get the child node at the given index. Fails when the index is out of range. */
69
+ child(index) {
70
+ return this.content.child(index);
71
+ }
72
+ /** Like `child`, but throws a `PositionError` when the index is out of range. */
73
+ childUnsafe(index) {
74
+ return this.content.childUnsafe(index);
75
+ }
76
+ /** Get the child node at the given index, if it exists. */
77
+ maybeChild(index) {
78
+ return this.content.maybeChild(index);
79
+ }
80
+ /** Call `f` for every child node, passing the node, its offset into this parent node, and its index. */
81
+ forEach(f) {
82
+ this.content.forEach(f);
83
+ }
84
+ /**
85
+ * Invoke a callback for all descendant nodes recursively overlapping
86
+ * the given two positions that are relative to start of this node's
87
+ * content. This includes all ancestors of the nodes containing the
88
+ * two positions. The callback is invoked with the node, its position
89
+ * relative to the original node (method receiver), its parent node,
90
+ * and its child index. When the callback returns false for a given
91
+ * node, that node's children will not be recursed over. The last
92
+ * parameter can be used to specify a starting position to count
93
+ * from.
94
+ */
95
+ nodesBetween(from, to, f, startPos = 0) {
96
+ this.content.nodesBetween(from, to, f, startPos, this);
97
+ }
98
+ /** Call the given callback for every descendant node. Doesn't descend into a node when the callback returns `false`. */
99
+ descendants(f) {
100
+ this.nodesBetween(0, this.content.size, f);
101
+ }
102
+ /** Concatenates all the text nodes found in this node and its children. */
103
+ get textContent() {
104
+ return this.isLeaf && this.type.spec.leafText ? this.type.spec.leafText(this) : this.textBetween(0, this.content.size, "");
105
+ }
106
+ /**
107
+ * Get all text between positions `from` and `to`. When
108
+ * `blockSeparator` is given, it will be inserted to separate text from
109
+ * different block nodes. If `leafText` is given, it'll be inserted for
110
+ * every non-text leaf node encountered, otherwise the spec's
111
+ * `leafText` will be used.
112
+ */
113
+ textBetween(from, to, blockSeparator, leafText) {
114
+ return this.content.textBetween(from, to, blockSeparator, leafText);
115
+ }
116
+ /** Returns this node's first child, if any. */
117
+ get firstChild() {
118
+ return this.content.firstChild;
119
+ }
120
+ /** Returns this node's last child, if any. */
121
+ get lastChild() {
122
+ return this.content.lastChild;
123
+ }
124
+ /** Test whether two nodes represent the same piece of document. */
125
+ eq(other) {
126
+ return this === other || this.sameMarkup(other) && this.content.eq(other.content);
127
+ }
128
+ /** @internal */
129
+ [Equal.symbol](that) {
130
+ return that instanceof Node && this.eq(that);
131
+ }
132
+ /** @internal */
133
+ [Hash.symbol]() {
134
+ return Hash.combine(Hash.string(this.type.name))(Hash.combine(Hash.structure(this.attrs))(Hash.combine(Hash.array(this.marks))(Hash.hash(this.content))));
135
+ }
136
+ /** Compare the markup (type, attributes, and marks) of this node to those of another. */
137
+ sameMarkup(other) {
138
+ return this.hasMarkup(other.type, other.attrs, other.marks);
139
+ }
140
+ /** Check whether this node's markup correspond to the given type, attributes, and marks. */
141
+ hasMarkup(type, attrs, marks) {
142
+ return this.type === type && compareDeep(this.attrs, attrs || type.defaultAttrs || emptyAttrs) && Mark.sameSet(this.marks, marks || Mark.none);
143
+ }
144
+ /** Create a new node with the same markup as this node, containing the given content (or empty, if no content is given). */
145
+ copy(content = null) {
146
+ if (content === this.content) return this;
147
+ return new Node(this.type, this.attrs, content, this.marks);
148
+ }
149
+ /** Create a copy of this node, with the given set of marks instead of the node's own marks. */
150
+ mark(marks) {
151
+ return marks === this.marks ? this : new Node(this.type, this.attrs, this.content, marks);
152
+ }
153
+ /**
154
+ * Create a copy of this node with only the content between the given
155
+ * positions. If `to` is not given, it defaults to the end of the node.
156
+ */
157
+ cut(from, to = this.content.size) {
158
+ if (from === 0 && to === this.content.size) return this;
159
+ return this.copy(this.content.cut(from, to));
160
+ }
161
+ /** Cut out the part of the document between the given positions, and return it as a `Slice`. */
162
+ slice(from, to = this.content.size, includeParents = false) {
163
+ return attempt(() => this.sliceUnsafe(from, to, includeParents), isPositionError);
164
+ }
165
+ /** Like `slice`, but throws a `PositionError` for invalid positions. */
166
+ sliceUnsafe(from, to = this.content.size, includeParents = false) {
167
+ if (from === to) return Slice.empty;
168
+ const $from = this.resolveUnsafe(from), $to = this.resolveUnsafe(to);
169
+ const depth = includeParents ? 0 : $from.sharedDepth(to);
170
+ const start = $from.start(depth);
171
+ const content = $from.node(depth).content.cut($from.pos - start, $to.pos - start);
172
+ return new Slice(content, $from.depth - depth, $to.depth - depth);
173
+ }
174
+ /**
175
+ * Replace the part of the document between the given positions with
176
+ * the given slice. The slice must 'fit', meaning its open sides must
177
+ * be able to connect to the surrounding content, and its content
178
+ * nodes must be valid children for the node they are placed into. If
179
+ * any of this is violated, the effect fails with a `ReplaceError`.
180
+ */
181
+ replace(from, to, slice) {
182
+ return attempt(() => this.replaceUnsafe(from, to, slice), isModelError);
183
+ }
184
+ /** Like `replace`, but throws on failure. */
185
+ replaceUnsafe(from, to, slice) {
186
+ return replace(this.resolveUnsafe(from), this.resolveUnsafe(to), slice);
187
+ }
188
+ /** Find the node directly after the given position, if any. */
189
+ nodeAt(pos) {
190
+ return Option.fromNullishOr(this.nodeAtOrNull(pos));
191
+ }
192
+ /** @internal */
193
+ nodeAtOrNull(pos) {
194
+ if (pos < 0 || pos > this.content.size) return null;
195
+ for (let node = this;;) {
196
+ const { index, offset } = node.content.findIndex(pos);
197
+ node = node.content.content[index];
198
+ if (!node) return null;
199
+ if (offset === pos || node.isText) return node;
200
+ pos -= offset + 1;
201
+ }
202
+ }
203
+ /**
204
+ * Find the (direct) child node after the given offset, if any, and
205
+ * return it along with its index and offset relative to this node.
206
+ */
207
+ childAfter(pos) {
208
+ const { index, offset } = this.content.findIndex(pos);
209
+ return {
210
+ node: this.content.maybeChild(index),
211
+ index,
212
+ offset
213
+ };
214
+ }
215
+ /**
216
+ * Find the (direct) child node before the given offset, if any, and
217
+ * return it along with its index and offset relative to this node.
218
+ */
219
+ childBefore(pos) {
220
+ if (pos === 0) return {
221
+ node: Option.none(),
222
+ index: 0,
223
+ offset: 0
224
+ };
225
+ const { index, offset } = this.content.findIndex(pos);
226
+ if (offset < pos) return {
227
+ node: Option.some(this.content.content[index]),
228
+ index,
229
+ offset
230
+ };
231
+ const node = this.content.content[index - 1];
232
+ return {
233
+ node: Option.some(node),
234
+ index: index - 1,
235
+ offset: offset - node.nodeSize
236
+ };
237
+ }
238
+ /**
239
+ * Resolve the given position in the document, returning an object
240
+ * with information about its context.
241
+ */
242
+ resolve(pos) {
243
+ return attempt(() => this.resolveUnsafe(pos), isPositionError);
244
+ }
245
+ /** Like `resolve`, but throws a `PositionError` for out-of-range positions. */
246
+ resolveUnsafe(pos) {
247
+ return ResolvedPos.resolveCached(this, pos);
248
+ }
249
+ /** @internal */
250
+ resolveNoCache(pos) {
251
+ return ResolvedPos.resolve(this, pos);
252
+ }
253
+ /** Test whether a given mark or mark type occurs in this document between the two given positions. */
254
+ rangeHasMark(from, to, type) {
255
+ let found = false;
256
+ if (to > from) this.nodesBetween(from, to, (node) => {
257
+ if (type instanceof Mark ? type.isInSet(node.marks) : type.findInSet(node.marks)) found = true;
258
+ return !found;
259
+ });
260
+ return found;
261
+ }
262
+ /** True when this is a block (non-inline node) */
263
+ get isBlock() {
264
+ return this.type.isBlock;
265
+ }
266
+ /** True when this is a textblock node, a block node with inline content. */
267
+ get isTextblock() {
268
+ return this.type.isTextblock;
269
+ }
270
+ /** True when this node allows inline content. */
271
+ get inlineContent() {
272
+ return this.type.inlineContent;
273
+ }
274
+ /** True when this is an inline node (a text node or a node that can appear among text). */
275
+ get isInline() {
276
+ return this.type.isInline;
277
+ }
278
+ /** True when this is a text node. */
279
+ get isText() {
280
+ return this.type.isText;
281
+ }
282
+ /** True when this is a leaf node. */
283
+ get isLeaf() {
284
+ return this.type.isLeaf;
285
+ }
286
+ /**
287
+ * True when this is an atom, i.e. when it does not have directly
288
+ * editable content. This is usually the same as `isLeaf`, but can be
289
+ * configured with the `atom` property on a node's spec.
290
+ */
291
+ get isAtom() {
292
+ return this.type.isAtom;
293
+ }
294
+ /** Return a string representation of this node for debugging purposes. */
295
+ toString() {
296
+ if (this.type.spec.toDebugString) return this.type.spec.toDebugString(this);
297
+ let name = this.type.name;
298
+ if (this.content.size) name += "(" + this.content.toStringInner() + ")";
299
+ return wrapMarks(this.marks, name);
300
+ }
301
+ /** Get the content match in this node at the given index. */
302
+ contentMatchAt(index) {
303
+ return attempt(() => this.contentMatchAtUnsafe(index), (u) => u instanceof ContentError);
304
+ }
305
+ /** Like `contentMatchAt`, but throws when the node's content is invalid. */
306
+ contentMatchAtUnsafe(index) {
307
+ const match = this.type.contentMatch.matchFragmentOrNull(this.content, 0, index);
308
+ if (!match) throw new ContentError({ message: "Called contentMatchAt on a node with invalid content" });
309
+ return match;
310
+ }
311
+ /**
312
+ * Test whether replacing the range between `from` and `to` (by child
313
+ * index) with the given replacement fragment (which defaults to the
314
+ * empty fragment) would leave the node's content valid. You can
315
+ * optionally pass `start` and `end` indices into the replacement
316
+ * fragment.
317
+ */
318
+ canReplace(from, to, replacement = Fragment.empty, start = 0, end = replacement.childCount) {
319
+ const one = this.contentMatchAtUnsafe(from).matchFragmentOrNull(replacement, start, end);
320
+ const two = one && one.matchFragmentOrNull(this.content, to);
321
+ if (!two || !two.validEnd) return false;
322
+ for (let i = start; i < end; i++) if (!this.type.allowsMarks(replacement.content[i].marks)) return false;
323
+ return true;
324
+ }
325
+ /**
326
+ * Test whether replacing the range `from` to `to` (by index) with a
327
+ * node of the given type would leave the node's content valid.
328
+ */
329
+ canReplaceWith(from, to, type, marks) {
330
+ if (marks && !this.type.allowsMarks(marks)) return false;
331
+ const start = this.contentMatchAtUnsafe(from).matchTypeOrNull(type);
332
+ const end = start && start.matchFragmentOrNull(this.content, to);
333
+ return end ? end.validEnd : false;
334
+ }
335
+ /**
336
+ * Test whether the given node's content could be appended to this
337
+ * node. If that node is empty, this will only return true if there is
338
+ * at least one node type that can appear in both nodes (to avoid
339
+ * merging completely incompatible nodes).
340
+ */
341
+ canAppend(other) {
342
+ if (other.content.size) return this.canReplace(this.childCount, this.childCount, other.content);
343
+ return this.type.compatibleContent(other.type);
344
+ }
345
+ /**
346
+ * Check whether this node and its descendants conform to the schema
347
+ * (including attribute schemas), failing with a `ContentError` or
348
+ * `AttributeError` when they do not.
349
+ */
350
+ check() {
351
+ return attempt(() => this.checkUnsafe(), isModelError);
352
+ }
353
+ /** Like `check`, but throws on failure. */
354
+ checkUnsafe() {
355
+ this.type.checkContent(this.content);
356
+ this.type.checkAttrs(this.attrs);
357
+ let copy = Mark.none;
358
+ for (let i = 0; i < this.marks.length; i++) {
359
+ const mark = this.marks[i];
360
+ mark.type.checkAttrs(mark.attrs);
361
+ copy = mark.addToSet(copy);
362
+ }
363
+ if (!Mark.sameSet(copy, this.marks)) throw new ContentError({ message: `Invalid collection of marks for node ${this.type.name}: ${this.marks.map((m) => m.type.name).join(",")}` });
364
+ this.content.forEach((node) => node.checkUnsafe());
365
+ }
366
+ /** Return a JSON-serializable representation of this node. */
367
+ toJSON() {
368
+ const obj = { type: this.type.name };
369
+ for (const _ in this.attrs) {
370
+ obj.attrs = this.type.identityAttrs ? this.attrs : encodeAttrs(this.type.attrs, this.attrs);
371
+ break;
372
+ }
373
+ if (this.content.size) obj.content = this.content.toJSON();
374
+ if (this.marks.length) obj.marks = this.marks.map((n) => n.toJSON());
375
+ return obj;
376
+ }
377
+ };
378
+ /**
379
+ * A text node. Text nodes are always leaves and carry their text in
380
+ * `text`.
381
+ *
382
+ * @since 0.1.0
383
+ * @category models
384
+ */
385
+ var TextNode = class TextNode extends Node {
386
+ /** @internal */
387
+ constructor(type, attrs, content, marks) {
388
+ super(type, attrs, null, marks);
389
+ if (!content) throw new ContentError({ message: "Empty text nodes are not allowed" });
390
+ this.text = content;
391
+ }
392
+ toString() {
393
+ if (this.type.spec.toDebugString) return this.type.spec.toDebugString(this);
394
+ return wrapMarks(this.marks, showJson(this.text));
395
+ }
396
+ get textContent() {
397
+ return this.text;
398
+ }
399
+ textBetween(from, to) {
400
+ return this.text.slice(from, to);
401
+ }
402
+ get nodeSize() {
403
+ return this.text.length;
404
+ }
405
+ mark(marks) {
406
+ return marks === this.marks ? this : new TextNode(this.type, this.attrs, this.text, marks);
407
+ }
408
+ /** Create a copy of this text node with different text. */
409
+ withText(text) {
410
+ if (text === this.text) return this;
411
+ return new TextNode(this.type, this.attrs, text, this.marks);
412
+ }
413
+ cut(from = 0, to = this.text.length) {
414
+ if (from === 0 && to === this.text.length) return this;
415
+ return this.withText(this.text.slice(from, to));
416
+ }
417
+ eq(other) {
418
+ return this.sameMarkup(other) && other instanceof TextNode && this.text === other.text;
419
+ }
420
+ [Hash.symbol]() {
421
+ return Hash.combine(Hash.string(this.text))(Hash.array(this.marks));
422
+ }
423
+ toJSON() {
424
+ return {
425
+ ...super.toJSON(),
426
+ text: this.text
427
+ };
428
+ }
429
+ };
430
+ function wrapMarks(marks, str) {
431
+ for (let i = marks.length - 1; i >= 0; i--) str = marks[i].type.name + "(" + str + ")";
432
+ return str;
433
+ }
434
+ export { Node, TextNode };
@@ -0,0 +1,78 @@
1
+ import { Fragment } from "./fragment.js";
2
+ import { Node, NodeJSON } from "./node.js";
3
+ import { ResolvedPos } from "./resolvedpos.js";
4
+ import { AnySchema } from "./schema.js";
5
+ import * as Equal from "effect/Equal";
6
+ import * as Hash from "effect/Hash";
7
+ /**
8
+ * The JSON representation of a slice.
9
+ *
10
+ * @since 0.1.0
11
+ * @category models
12
+ */
13
+ export interface SliceJSON {
14
+ readonly content: ReadonlyArray<NodeJSON>;
15
+ readonly openStart?: number;
16
+ readonly openEnd?: number;
17
+ }
18
+ /**
19
+ * A slice represents a piece cut out of a larger document. It stores
20
+ * not only a fragment, but also the depth up to which nodes on both
21
+ * side are ‘open’ (cut through).
22
+ *
23
+ * @since 0.1.0
24
+ * @category models
25
+ */
26
+ export declare class Slice<Sc extends AnySchema = AnySchema> implements Equal.Equal {
27
+ /** The slice's content. */
28
+ readonly content: Fragment<Sc>;
29
+ /** The open depth at the start of the fragment. */
30
+ readonly openStart: number;
31
+ /** The open depth at the end. */
32
+ readonly openEnd: number;
33
+ /**
34
+ * Create a slice. When specifying a non-zero open depth, you must
35
+ * make sure that there are nodes of at least that depth at the
36
+ * appropriate side of the fragment—i.e. if the fragment is an empty
37
+ * paragraph node, `openStart` and `openEnd` can't be greater than 1.
38
+ *
39
+ * It is not necessary for the content of open nodes to conform to the
40
+ * schema's content constraints, though it should be a valid
41
+ * start/end/middle for such a node, depending on which sides are
42
+ * open.
43
+ */
44
+ constructor(
45
+ /** The slice's content. */
46
+ content: Fragment<Sc>,
47
+ /** The open depth at the start of the fragment. */
48
+ openStart: number,
49
+ /** The open depth at the end. */
50
+ openEnd: number);
51
+ /** The size this slice would add when inserted into a document. */
52
+ get size(): number;
53
+ /** @internal */
54
+ insertAt(pos: number, fragment: Fragment<Sc>): Slice<Sc> | null;
55
+ /** @internal */
56
+ removeBetween(from: number, to: number): Slice<Sc>;
57
+ /** Tests whether this slice is equal to another slice. */
58
+ eq(other: Slice<any>): boolean;
59
+ /** @internal */
60
+ [Equal.symbol](that: Equal.Equal): boolean;
61
+ /** @internal */
62
+ [Hash.symbol](): number;
63
+ /** @internal */
64
+ toString(): string;
65
+ /** Convert a slice to a JSON-serializable representation. */
66
+ toJSON(): SliceJSON | null;
67
+ /** @internal */
68
+ static fromJSONUnsafe<Sc extends AnySchema>(schema: Sc, json: any): Slice<Sc>;
69
+ /**
70
+ * Create a slice from a fragment by taking the maximum possible open
71
+ * value on both side of the fragment.
72
+ */
73
+ static maxOpen<Sc extends AnySchema>(fragment: Fragment<Sc>, openIsolating?: boolean): Slice<Sc>;
74
+ /** The empty slice. */
75
+ static readonly empty: Slice<any>;
76
+ }
77
+ /** @internal */
78
+ export declare function replace<Sc extends AnySchema>($from: ResolvedPos<Sc>, $to: ResolvedPos<Sc>, slice: Slice<Sc>): Node<Sc>;