@thomasfosterau/effect-richtext 0.0.0-stage → 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 +118 -2
  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 +206 -3
@@ -0,0 +1,451 @@
1
+ import { attempt } from "../internal/attempt.js";
2
+ import { showJson } from "../internal/json.js";
3
+ import { ContentMatch } from "./content.js";
4
+ import { AttributeError, ContentError, SchemaDefinitionError, isModelError } from "./errors.js";
5
+ import { Fragment } from "./fragment.js";
6
+ import { markFromJSON, nodeFromJSON } from "./json.js";
7
+ import { Mark } from "./mark.js";
8
+ import { Node, TextNode } from "./node.js";
9
+ import * as Arr from "effect/Array";
10
+ import * as Option from "effect/Option";
11
+ import * as S from "effect/Schema";
12
+ import * as Rec from "effect/Record";
13
+ import * as SchemaAST from "effect/SchemaAST";
14
+ // src/model/schema.ts
15
+ /** @internal */
16
+ var Attribute = class {
17
+ constructor(typeName, attrName, definition) {
18
+ const options = S.isSchema(definition) ? { schema: definition } : definition;
19
+ this.hasDefault = Object.prototype.hasOwnProperty.call(options, "default");
20
+ this.default = options.default;
21
+ this.schema = options.schema;
22
+ this.is = options.schema ? S.is(options.schema) : void 0;
23
+ this.identityCodec = !options.schema || SchemaAST.toEncoded(options.schema.ast) === options.schema.ast;
24
+ this.validate = typeof options.validate === "string" ? validateType(typeName, attrName, options.validate) : options.validate;
25
+ }
26
+ get isRequired() {
27
+ return !this.hasDefault;
28
+ }
29
+ };
30
+ function validateType(typeName, attrName, type) {
31
+ const types = type.split("|");
32
+ return (value) => {
33
+ const name = value === null ? "null" : typeof value;
34
+ if (types.indexOf(name) < 0) throw new AttributeError({ message: `Expected value of type ${types.join(",")} for attribute ${attrName} on type ${typeName}, got ${name}` });
35
+ };
36
+ }
37
+ const identityAttrs = (attrs) => Arr.every(Rec.values(attrs), (a) => a.identityCodec);
38
+ function initAttrs(typeName, attrs) {
39
+ const result = Object.create(null);
40
+ if (attrs) for (const name in attrs) result[name] = new Attribute(typeName, name, attrs[name]);
41
+ return result;
42
+ }
43
+ function defaultAttrs(attrs) {
44
+ const defaults = Object.create(null);
45
+ for (const attrName in attrs) {
46
+ const attr = attrs[attrName];
47
+ if (!attr.hasDefault) return Option.none();
48
+ defaults[attrName] = attr.default;
49
+ }
50
+ return Option.some(defaults);
51
+ }
52
+ /** @internal */
53
+ function computeAttrs(attrs, value, type, typeName, decoded = false) {
54
+ const built = Object.create(null);
55
+ for (const name in attrs) {
56
+ const attr = attrs[name];
57
+ let given = value && value[name];
58
+ if (given === void 0) {
59
+ if (attr.hasDefault) given = attr.default;
60
+ else throw new AttributeError({ message: "No value supplied for attribute " + name });
61
+ } else {
62
+ if (attr.validate) attr.validate(given);
63
+ if (!decoded && attr.is && !attr.is(given)) throw new AttributeError({ message: `Invalid value for attribute ${name} on ${type} of type ${typeName}: ${showJson(given)}` });
64
+ }
65
+ built[name] = given;
66
+ }
67
+ return built;
68
+ }
69
+ /** @internal */
70
+ function checkAttrs(attrs, values, type, typeName) {
71
+ for (const name in values) if (!(name in attrs)) throw new AttributeError({ message: `Unsupported attribute ${name} for ${type} of type ${typeName}` });
72
+ for (const name in attrs) {
73
+ const attr = attrs[name];
74
+ if (attr.validate) attr.validate(values[name]);
75
+ if (attr.is && !attr.is(values[name])) throw new AttributeError({ message: `Invalid value for attribute ${name} on ${type} of type ${typeName}: ${showJson(values[name])}` });
76
+ }
77
+ }
78
+ /**
79
+ * Node types are objects allocated once per schema and used to tag
80
+ * `Node` instances. They contain information about the node type, such
81
+ * as its name and what kind of node it represents.
82
+ *
83
+ * @since 0.1.0
84
+ * @category models
85
+ */
86
+ var NodeType = class {
87
+ /** @internal */
88
+ constructor(name, schema, spec) {
89
+ this.name = name;
90
+ this.schema = schema;
91
+ this.spec = spec;
92
+ this.markSet = null;
93
+ this.groups = spec.group ? spec.group.split(" ") : [];
94
+ this.attrs = initAttrs(name, spec.attrs);
95
+ this.defaultAttrs = Option.getOrNull(defaultAttrs(this.attrs));
96
+ this.identityAttrs = identityAttrs(this.attrs);
97
+ this.isBlock = !(spec.inline || name === "text");
98
+ this.isText = name === "text";
99
+ }
100
+ /** True if this is an inline type. */
101
+ get isInline() {
102
+ return !this.isBlock;
103
+ }
104
+ /** True if this is a textblock type, a block that contains inline content. */
105
+ get isTextblock() {
106
+ return this.isBlock && this.inlineContent;
107
+ }
108
+ /** True for node types that allow no content. */
109
+ get isLeaf() {
110
+ return this.contentMatch === ContentMatch.empty;
111
+ }
112
+ /** True when this node is an atom, i.e. when it does not have directly editable content. */
113
+ get isAtom() {
114
+ return this.isLeaf || !!this.spec.atom;
115
+ }
116
+ /** Return true when this node type is part of the given group. */
117
+ isInGroup(group) {
118
+ return this.groups.indexOf(group) > -1;
119
+ }
120
+ /** The node type's whitespace option. */
121
+ get whitespace() {
122
+ return this.spec.whitespace || (this.spec.code ? "pre" : "normal");
123
+ }
124
+ /** Tells you whether this node type has any required attributes. */
125
+ hasRequiredAttrs() {
126
+ for (const n in this.attrs) if (this.attrs[n].isRequired) return true;
127
+ return false;
128
+ }
129
+ /** Indicates whether this node allows some of the same content as the given node type. */
130
+ compatibleContent(other) {
131
+ return this === other || this.contentMatch.compatible(other.contentMatch);
132
+ }
133
+ /** @internal */
134
+ computeAttrs(attrs) {
135
+ if (!attrs && this.defaultAttrs) return this.defaultAttrs;
136
+ return computeAttrs(this.attrs, attrs, "node", this.name);
137
+ }
138
+ create(attrs, content, marks) {
139
+ return attempt(() => this.createUnsafe(attrs, content, marks), isModelError);
140
+ }
141
+ createUnsafe(attrs, content, marks) {
142
+ if (this.isText) throw new ContentError({ message: "NodeType.create can't construct text nodes" });
143
+ return new Node(this, this.computeAttrs(attrs), Fragment.from(content), Mark.setFrom(marks));
144
+ }
145
+ /**
146
+ * Like `createUnsafe`, for attributes already decoded by their Schemas.
147
+ *
148
+ * @internal
149
+ */
150
+ createDecodedUnsafe(attrs, content, marks) {
151
+ if (this.isText) throw new ContentError({ message: "NodeType.create can't construct text nodes" });
152
+ return new Node(this, this.computeDecodedAttrs(attrs), content, Mark.setFrom(marks));
153
+ }
154
+ computeDecodedAttrs(attrs) {
155
+ return Option.isSome(attrs) ? computeAttrs(this.attrs, attrs.value, "node", this.name, true) : this.computeAttrs(Option.getOrNull(attrs));
156
+ }
157
+ createChecked(attrs, content, marks) {
158
+ return attempt(() => this.createCheckedUnsafe(attrs, content, marks), isModelError);
159
+ }
160
+ createCheckedUnsafe(attrs, content, marks) {
161
+ const frag = Fragment.from(content);
162
+ this.checkContent(frag);
163
+ return new Node(this, this.computeAttrs(attrs), frag, Mark.setFrom(marks));
164
+ }
165
+ createAndFill(attrs, content, marks) {
166
+ return attempt(() => {
167
+ const node = this.createAndFillOrNull(attrs, content, marks);
168
+ if (!node) throw new ContentError({ message: `Content does not fit node type ${this.name} and cannot be filled` });
169
+ return node;
170
+ }, isModelError);
171
+ }
172
+ /** @internal */
173
+ createAndFillOrNull(attrs, content, marks) {
174
+ const computed = this.computeAttrs(attrs);
175
+ let frag = Fragment.from(content);
176
+ if (frag.size) {
177
+ const before = this.contentMatch.fillBeforeOrNull(frag);
178
+ if (!before) return null;
179
+ frag = before.append(frag);
180
+ }
181
+ const matched = this.contentMatch.matchFragmentOrNull(frag);
182
+ const after = matched && matched.fillBeforeOrNull(Fragment.empty, true);
183
+ if (!after) return null;
184
+ return new Node(this, computed, frag.append(after), Mark.setFrom(marks));
185
+ }
186
+ /** Returns true if the given fragment is valid content for this node type. */
187
+ validContent(content) {
188
+ const result = this.contentMatch.matchFragmentOrNull(content);
189
+ if (!result || !result.validEnd) return false;
190
+ for (let i = 0; i < content.childCount; i++) if (!this.allowsMarks(content.content[i].marks)) return false;
191
+ return true;
192
+ }
193
+ /** @internal */
194
+ checkContent(content) {
195
+ if (!this.validContent(content)) throw new ContentError({ message: `Invalid content for node ${this.name}: ${content.toString().slice(0, 50)}` });
196
+ }
197
+ /** @internal */
198
+ checkAttrs(attrs) {
199
+ checkAttrs(this.attrs, attrs, "node", this.name);
200
+ }
201
+ /** Check whether the given mark type is allowed in this node. */
202
+ allowsMarkType(markType) {
203
+ return this.markSet === null || this.markSet.indexOf(markType) > -1;
204
+ }
205
+ /** Test whether the given set of marks are allowed in this node. */
206
+ allowsMarks(marks) {
207
+ if (this.markSet === null) return true;
208
+ for (let i = 0; i < marks.length; i++) if (!this.allowsMarkType(marks[i].type)) return false;
209
+ return true;
210
+ }
211
+ /** Removes the marks that are not allowed in this node from the given set. */
212
+ allowedMarks(marks) {
213
+ if (this.allowsMarks(marks)) return marks;
214
+ const kept = marks.filter((mark) => this.allowsMarkType(mark.type));
215
+ return kept.length ? kept : Mark.none;
216
+ }
217
+ /** @internal */
218
+ toString() {
219
+ return `NodeType(${this.name})`;
220
+ }
221
+ };
222
+ const isAttributeError = (u) => u instanceof AttributeError;
223
+ /**
224
+ * Like nodes, marks are tagged with type objects, which are
225
+ * instantiated once per schema.
226
+ *
227
+ * @since 0.1.0
228
+ * @category models
229
+ */
230
+ var MarkType = class {
231
+ /** @internal */
232
+ constructor(name, rank, schema, spec) {
233
+ this.name = name;
234
+ this.rank = rank;
235
+ this.schema = schema;
236
+ this.spec = spec;
237
+ this.attrs = initAttrs(name, spec.attrs);
238
+ this.identityAttrs = identityAttrs(this.attrs);
239
+ this.instance = Option.map(defaultAttrs(this.attrs), (defaults) => new Mark(this, defaults));
240
+ }
241
+ create(attrs) {
242
+ return attempt(() => this.createUnsafe(attrs), isAttributeError);
243
+ }
244
+ createUnsafe(attrs) {
245
+ if (!attrs && Option.isSome(this.instance)) return this.instance.value;
246
+ return new Mark(this, computeAttrs(this.attrs, attrs, "mark", this.name));
247
+ }
248
+ /**
249
+ * Like `createUnsafe`, for attributes already decoded by their Schemas.
250
+ *
251
+ * @internal
252
+ */
253
+ createDecodedUnsafe(attrs) {
254
+ return Option.isSome(attrs) ? new Mark(this, computeAttrs(this.attrs, attrs.value, "mark", this.name, true)) : this.createUnsafe();
255
+ }
256
+ /**
257
+ * When there is a mark of this type in the given set, a new set
258
+ * without it is returned. Otherwise, the input set is returned.
259
+ */
260
+ removeFromSet(set) {
261
+ for (let i = 0; i < set.length; i++) if (set[i].type === this) {
262
+ set = set.slice(0, i).concat(set.slice(i + 1));
263
+ i--;
264
+ }
265
+ return set;
266
+ }
267
+ /** Find a mark of this type in the given set. */
268
+ isInSet(set) {
269
+ return Option.fromNullishOr(this.findInSet(set));
270
+ }
271
+ /** @internal */
272
+ findInSet(set) {
273
+ for (let i = 0; i < set.length; i++) if (set[i].type === this) return set[i];
274
+ }
275
+ /** @internal */
276
+ checkAttrs(attrs) {
277
+ checkAttrs(this.attrs, attrs, "mark", this.name);
278
+ }
279
+ /** Queries whether a given mark type is excluded by this one. */
280
+ excludes(other) {
281
+ return this.excluded.indexOf(other) > -1;
282
+ }
283
+ /** @internal */
284
+ toString() {
285
+ return `MarkType(${this.name})`;
286
+ }
287
+ };
288
+ /**
289
+ * A document schema. Holds node and mark type objects for the nodes and
290
+ * marks that may occur in conforming documents, and provides
291
+ * functionality for creating and deserializing such documents.
292
+ *
293
+ * @since 0.1.0
294
+ * @category models
295
+ */
296
+ var DocSchema = class DocSchema {
297
+ /** @internal */
298
+ constructor(spec) {
299
+ this.cached = Object.create(null);
300
+ this.spec = {
301
+ ...spec,
302
+ nodes: spec.nodes,
303
+ marks: spec.marks ?? {}
304
+ };
305
+ const nodes = Object.create(null);
306
+ for (const name in this.spec.nodes) nodes[name] = new NodeType(name, this, this.spec.nodes[name]);
307
+ const topType = this.spec.topNode || "doc";
308
+ if (!nodes[topType]) throw new SchemaDefinitionError({ message: "Schema is missing its top node type ('" + topType + "')" });
309
+ if (!nodes.text) throw new SchemaDefinitionError({ message: "Every schema needs a 'text' type" });
310
+ for (const _ in nodes.text.attrs) throw new SchemaDefinitionError({ message: "The text node type should not have attributes" });
311
+ const marks = Object.create(null);
312
+ let rank = 0;
313
+ for (const name in this.spec.marks) marks[name] = new MarkType(name, rank++, this, this.spec.marks[name]);
314
+ this.nodes = nodes;
315
+ this.marks = marks;
316
+ let linebreak = Option.none();
317
+ for (const prop in nodes) {
318
+ if (prop in marks) throw new SchemaDefinitionError({ message: prop + " can not be both a node and a mark" });
319
+ const type = nodes[prop];
320
+ const markExpr = type.spec.marks;
321
+ type.contentMatch = ContentMatch.compile(type.spec.content, nodes, prop);
322
+ type.inlineContent = type.contentMatch.inlineContent;
323
+ if (type.spec.linebreakReplacement) {
324
+ if (Option.isSome(linebreak)) throw new SchemaDefinitionError({ message: "Multiple linebreak nodes defined" });
325
+ if (!type.isInline || !type.isLeaf) throw new SchemaDefinitionError({ message: "Linebreak replacement nodes must be inline leaf nodes" });
326
+ linebreak = Option.some(type);
327
+ }
328
+ type.markSet = markExpr === "_" ? null : markExpr ? gatherMarks(this, markExpr.split(" ")) : markExpr === "" || !type.inlineContent ? [] : null;
329
+ }
330
+ for (const prop in marks) {
331
+ const type = marks[prop];
332
+ const excl = type.spec.excludes;
333
+ type.excluded = excl == null ? [type] : excl === "" ? [] : gatherMarks(this, excl.split(" "));
334
+ }
335
+ this.linebreakReplacement = linebreak;
336
+ this.topNodeType = nodes[topType];
337
+ }
338
+ /**
339
+ * Construct a schema from a schema specification. A spec with literal
340
+ * names is also checked statically (`SchemaSpecCheck`): an unknown
341
+ * content, `marks` or `excludes` reference, a missing top or `text`
342
+ * node, a name used by both a node and a mark, a `default` outside its
343
+ * `schema` or an unknown `validate` type name fails to compile, where the
344
+ * runtime would fail with a `SchemaDefinitionError`.
345
+ */
346
+ static make(spec) {
347
+ return attempt(() => new DocSchema(spec), (u) => u instanceof SchemaDefinitionError);
348
+ }
349
+ /** Like `make`, but throws a `SchemaDefinitionError` for invalid specs. */
350
+ static makeUnsafe(spec) {
351
+ return new DocSchema(spec);
352
+ }
353
+ node(type, attrs, content, marks) {
354
+ return attempt(() => this.nodeUnsafe(type, attrs, content, marks), isModelError);
355
+ }
356
+ nodeUnsafe(type, attrs, content, marks) {
357
+ let nodeType;
358
+ if (typeof type === "string") nodeType = this.nodeType(type);
359
+ else if (!(type instanceof NodeType)) throw new ContentError({ message: "Invalid node type: " + type });
360
+ else if (type.schema !== this) throw new ContentError({ message: "Node type from different schema used (" + type.name + ")" });
361
+ else nodeType = type;
362
+ return nodeType.createCheckedUnsafe(attrs, content, marks);
363
+ }
364
+ /**
365
+ * Create a text node in the schema. Fails for empty strings, since
366
+ * empty text nodes are not allowed.
367
+ */
368
+ text(text, marks) {
369
+ return attempt(() => this.textUnsafe(text, marks), (u) => u instanceof ContentError);
370
+ }
371
+ /** Like `text`, but throws a `ContentError` for empty strings. */
372
+ textUnsafe(text, marks) {
373
+ const type = this.nodes.text;
374
+ return new TextNode(type, type.defaultAttrs, text, Mark.setFrom(marks));
375
+ }
376
+ mark(type, attrs) {
377
+ return attempt(() => this.markUnsafe(type, attrs), isAttributeError);
378
+ }
379
+ markUnsafe(type, attrs) {
380
+ const markType = typeof type === "string" ? this.marks[type] : type;
381
+ if (!markType) throw new AttributeError({ message: "Unknown mark type: " + String(type) });
382
+ return markType.createUnsafe(attrs);
383
+ }
384
+ /** Deserialize a node from its JSON representation. */
385
+ nodeFromJSON(value) {
386
+ return attempt(() => nodeFromJSON(this, value), isModelError);
387
+ }
388
+ /** Like `nodeFromJSON`, but throws on failure. */
389
+ nodeFromJSONUnsafe(value) {
390
+ return nodeFromJSON(this, value);
391
+ }
392
+ /** Deserialize a mark from its JSON representation. */
393
+ markFromJSON(value) {
394
+ return attempt(() => markFromJSON(this, value), isModelError);
395
+ }
396
+ /** Like `markFromJSON`, but throws on failure. */
397
+ markFromJSONUnsafe(value) {
398
+ return markFromJSON(this, value);
399
+ }
400
+ /** @internal */
401
+ nodeType(name) {
402
+ const found = this.nodes[name];
403
+ if (!found) throw new ContentError({ message: "Unknown node type: " + name });
404
+ return found;
405
+ }
406
+ };
407
+ function gatherMarks(schema, marks) {
408
+ const found = [];
409
+ for (let i = 0; i < marks.length; i++) {
410
+ const name = marks[i];
411
+ const mark = schema.marks[name];
412
+ let ok = mark;
413
+ if (mark) found.push(mark);
414
+ else for (const prop in schema.marks) {
415
+ const mark = schema.marks[prop];
416
+ if (name === "_" || mark.spec.group && mark.spec.group.split(" ").indexOf(name) > -1) found.push(ok = mark);
417
+ }
418
+ if (!ok) throw new SchemaDefinitionError({ message: "Unknown mark type: '" + marks[i] + "'" });
419
+ }
420
+ return found;
421
+ }
422
+ /**
423
+ * Construct a schema from a specification.
424
+ *
425
+ * @since 0.1.0
426
+ * @category constructors
427
+ */
428
+ const make = DocSchema.make;
429
+ /**
430
+ * Construct a schema from a specification, throwing on invalid specs.
431
+ *
432
+ * @since 0.1.0
433
+ * @category constructors
434
+ */
435
+ const makeUnsafe = DocSchema.makeUnsafe;
436
+ /**
437
+ * Identity helper that gives a node spec's callbacks access to
438
+ * precisely typed attributes.
439
+ *
440
+ * @since 0.1.0
441
+ * @category constructors
442
+ */
443
+ const nodeSpec = (spec) => spec;
444
+ /**
445
+ * Identity helper for mark specs.
446
+ *
447
+ * @since 0.1.0
448
+ * @category constructors
449
+ */
450
+ const markSpec = (spec) => spec;
451
+ export { Attribute, DocSchema, MarkType, NodeType, checkAttrs, computeAttrs, make, makeUnsafe, markSpec, nodeSpec };
@@ -0,0 +1,169 @@
1
+ import { DOMSpecError } from "./errors.js";
2
+ import { Fragment } from "./fragment.js";
3
+ import { Mark } from "./mark.js";
4
+ import { Node } from "./node.js";
5
+ import { AnySchema, Attrs } from "./schema.js";
6
+ import * as Effect from "effect/Effect";
7
+ import * as Option from "effect/Option";
8
+ /**
9
+ * A description of a DOM structure. Can be either a DOM node, a
10
+ * `{dom, contentDOM}` object, or an array.
11
+ *
12
+ * An array describes a DOM element. The first value in the array
13
+ * should be a string—the name of the DOM element, optionally prefixed
14
+ * by a namespace URL and a space. If the second element is plain
15
+ * object, it is interpreted as a set of attributes for the element.
16
+ * Any elements after that (including the 2nd if it's not an attribute
17
+ * object) are interpreted as children of the DOM elements, and must
18
+ * either be valid `DOMOutputSpec` values, strings (for text nodes), or
19
+ * the number zero ("hole"), which marks where the node's content goes.
20
+ * A hole should be the only child element in its parent node.
21
+ *
22
+ * Plain arrays are accepted as well as tuples, because TypeScript does
23
+ * not always infer a tuple for an array literal returned from a `toDOM`
24
+ * callback; the shape is validated when the spec is rendered.
25
+ *
26
+ * @since 0.1.0
27
+ * @category models
28
+ */
29
+ export type DOMOutputSpec = Element | {
30
+ readonly dom: Element;
31
+ readonly contentDOM?: HTMLElement | undefined;
32
+ } | readonly [string, ...Array<any>] | ReadonlyArray<any>;
33
+ /**
34
+ * The result of rendering a [`DOMOutputSpec`](#DOMOutputSpec). When
35
+ * the spec contained a hole, `contentDOM` points at the element that
36
+ * should hold the content.
37
+ *
38
+ * @since 0.1.0
39
+ * @category models
40
+ */
41
+ export interface RenderedSpec {
42
+ readonly dom: globalThis.Node;
43
+ readonly contentDOM?: HTMLElement | undefined;
44
+ }
45
+ /**
46
+ * Options accepted by the serialization methods. When not running in
47
+ * an environment with a global `document`, pass one explicitly.
48
+ *
49
+ * @since 0.1.0
50
+ * @category models
51
+ */
52
+ export interface SerializeOptions {
53
+ readonly document?: Document | undefined;
54
+ }
55
+ /**
56
+ * A function serializing a node to a [`DOMOutputSpec`](#DOMOutputSpec).
57
+ *
58
+ * @since 0.1.0
59
+ * @category models
60
+ */
61
+ export type NodeSerializer<Sc extends AnySchema = AnySchema> = (node: Node<Sc, any>) => DOMOutputSpec;
62
+ /**
63
+ * A function serializing a mark to a [`DOMOutputSpec`](#DOMOutputSpec).
64
+ * `inline` tells it whether the mark's content is inline content.
65
+ *
66
+ * @since 0.1.0
67
+ * @category models
68
+ */
69
+ export type MarkSerializer<Sc extends AnySchema = AnySchema> = (mark: Mark<Sc, any>, inline: boolean) => DOMOutputSpec;
70
+ /**
71
+ * A DOM serializer knows how to convert nodes and marks of various
72
+ * types to DOM nodes.
73
+ *
74
+ * The serialization methods are synchronous and infallible for valid
75
+ * specs. An invalid output spec returned by a `toDOM` function is a
76
+ * programming error: the serialization methods then throw a
77
+ * `DOMSpecError` (a defect). Use [`renderSpec`](#DOMSerializer.renderSpec)
78
+ * to check specs in the Effect error channel.
79
+ *
80
+ * @since 0.1.0
81
+ * @category models
82
+ */
83
+ export declare class DOMSerializer<Sc extends AnySchema = AnySchema> {
84
+ /** The node serialization functions. */
85
+ readonly nodes: {
86
+ readonly [node: string]: NodeSerializer<Sc>;
87
+ };
88
+ /** The mark serialization functions. */
89
+ readonly marks: {
90
+ readonly [mark: string]: MarkSerializer<Sc> | null | undefined;
91
+ };
92
+ /**
93
+ * Create a serializer. `nodes` should map node names to functions
94
+ * that take a node and return a description of the corresponding
95
+ * DOM. `marks` does the same for mark names, but also gets an
96
+ * argument that tells it whether the mark's content is block or
97
+ * inline content (for typical use, it'll always be inline). A mark
98
+ * serializer may be `null` to indicate that marks of that type
99
+ * should not be serialized.
100
+ */
101
+ constructor(
102
+ /** The node serialization functions. */
103
+ nodes: {
104
+ readonly [node: string]: NodeSerializer<Sc>;
105
+ },
106
+ /** The mark serialization functions. */
107
+ marks: {
108
+ readonly [mark: string]: MarkSerializer<Sc> | null | undefined;
109
+ });
110
+ /**
111
+ * Serialize the content of this fragment to a DOM fragment. When
112
+ * not in the browser, the `document` option, containing a DOM
113
+ * document, should be passed so that the serializer can create
114
+ * nodes.
115
+ *
116
+ * Throws a `DOMSpecError` (as a defect) for invalid output specs.
117
+ */
118
+ serializeFragment(fragment: Fragment<Sc, any>, options?: SerializeOptions, target?: HTMLElement | DocumentFragment): HTMLElement | DocumentFragment;
119
+ /** @internal */
120
+ serializeNodeInner(node: Node<Sc, any>, options: SerializeOptions): globalThis.Node;
121
+ /**
122
+ * Serialize this node to a DOM node. This can be useful when you
123
+ * need to serialize a part of a document, as opposed to the whole
124
+ * document. To serialize a whole document, use
125
+ * [`serializeFragment`](#DOMSerializer.serializeFragment) on its
126
+ * `content`.
127
+ *
128
+ * Throws a `DOMSpecError` (as a defect) for invalid output specs.
129
+ */
130
+ serializeNode(node: Node<Sc, any>, options?: SerializeOptions): globalThis.Node;
131
+ /**
132
+ * Serialize a mark. Returns `None` when there is no serializer for
133
+ * the mark's type.
134
+ *
135
+ * Throws a `DOMSpecError` (as a defect) for invalid output specs.
136
+ */
137
+ serializeMark(mark: Mark<Sc, any>, inline: boolean, options?: SerializeOptions): Option.Option<RenderedSpec>;
138
+ /** @internal */
139
+ serializeMarkOrNull(mark: Mark<Sc, any>, inline: boolean, options?: SerializeOptions): RenderedSpec | null;
140
+ /**
141
+ * Render an [output spec](#DOMOutputSpec) to a DOM node. If the spec
142
+ * has a hole (zero) in it, `contentDOM` will point at the node with
143
+ * the hole. Fails with a `DOMSpecError` for invalid specs.
144
+ */
145
+ static renderSpec(doc: Document, structure: DOMOutputSpec, xmlNS?: string | null): Effect.Effect<RenderedSpec, DOMSpecError>;
146
+ /**
147
+ * Like `renderSpec`, but throws a `DOMSpecError` for invalid specs.
148
+ * When `blockArraysIn` (usually the attributes of the node or mark
149
+ * being rendered) is given, arrays found in it are refused as specs.
150
+ */
151
+ static renderSpecUnsafe(doc: Document, structure: DOMOutputSpec, xmlNS?: string | null, blockArraysIn?: Attrs): RenderedSpec;
152
+ /**
153
+ * Build a serializer using the [`toDOM`](#NodeSpec.toDOM) properties
154
+ * in a schema's node and mark specs. The result is cached on the
155
+ * schema.
156
+ */
157
+ static fromSchema<Sc extends AnySchema>(schema: Sc): DOMSerializer<Sc>;
158
+ /**
159
+ * Gather the serializers in a schema's node specs into an object.
160
+ * This can be useful as a base to build a custom serializer from.
161
+ */
162
+ static nodesFromSchema<Sc extends AnySchema>(schema: Sc): {
163
+ [node: string]: NodeSerializer<Sc>;
164
+ };
165
+ /** Gather the serializers in a schema's mark specs into an object. */
166
+ static marksFromSchema<Sc extends AnySchema>(schema: Sc): {
167
+ [mark: string]: MarkSerializer<Sc>;
168
+ };
169
+ }