@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,238 @@
1
+ import { attempt } from "../internal/attempt.js";
2
+ import { DOMSpecError } from "./errors.js";
3
+ import * as Option from "effect/Option";
4
+ // src/model/to_dom.ts
5
+ /**
6
+ * A DOM serializer knows how to convert nodes and marks of various
7
+ * types to DOM nodes.
8
+ *
9
+ * The serialization methods are synchronous and infallible for valid
10
+ * specs. An invalid output spec returned by a `toDOM` function is a
11
+ * programming error: the serialization methods then throw a
12
+ * `DOMSpecError` (a defect). Use [`renderSpec`](#DOMSerializer.renderSpec)
13
+ * to check specs in the Effect error channel.
14
+ *
15
+ * @since 0.1.0
16
+ * @category models
17
+ */
18
+ var DOMSerializer = class DOMSerializer {
19
+ /**
20
+ * Create a serializer. `nodes` should map node names to functions
21
+ * that take a node and return a description of the corresponding
22
+ * DOM. `marks` does the same for mark names, but also gets an
23
+ * argument that tells it whether the mark's content is block or
24
+ * inline content (for typical use, it'll always be inline). A mark
25
+ * serializer may be `null` to indicate that marks of that type
26
+ * should not be serialized.
27
+ */
28
+ constructor(nodes, marks) {
29
+ this.nodes = nodes;
30
+ this.marks = marks;
31
+ }
32
+ /**
33
+ * Serialize the content of this fragment to a DOM fragment. When
34
+ * not in the browser, the `document` option, containing a DOM
35
+ * document, should be passed so that the serializer can create
36
+ * nodes.
37
+ *
38
+ * Throws a `DOMSpecError` (as a defect) for invalid output specs.
39
+ */
40
+ serializeFragment(fragment, options = {}, target) {
41
+ if (!target) target = doc(options).createDocumentFragment();
42
+ let top = target;
43
+ const active = [];
44
+ fragment.forEach((node) => {
45
+ if (active.length || node.marks.length) {
46
+ let keep = 0, rendered = 0;
47
+ while (keep < active.length && rendered < node.marks.length) {
48
+ const next = node.marks[rendered];
49
+ if (!this.marks[next.type.name]) {
50
+ rendered++;
51
+ continue;
52
+ }
53
+ if (!next.eq(active[keep][0]) || next.type.spec.spanning === false) break;
54
+ keep++;
55
+ rendered++;
56
+ }
57
+ while (keep < active.length) top = active.pop()[1];
58
+ while (rendered < node.marks.length) {
59
+ const add = node.marks[rendered++];
60
+ const markDOM = this.serializeMarkOrNull(add, node.isInline, options);
61
+ if (markDOM) {
62
+ active.push([add, top]);
63
+ top.appendChild(markDOM.dom);
64
+ top = markDOM.contentDOM || markDOM.dom;
65
+ }
66
+ }
67
+ }
68
+ top.appendChild(this.serializeNodeInner(node, options));
69
+ });
70
+ return target;
71
+ }
72
+ /** @internal */
73
+ serializeNodeInner(node, options) {
74
+ if (node.isText) return doc(options).createTextNode(node.text);
75
+ const toDOM = this.nodes[node.type.name];
76
+ if (!toDOM) throw new DOMSpecError({ message: `No serializer for node type ${node.type.name}` });
77
+ const { contentDOM, dom } = renderSpecInner(doc(options), toDOM(node), null, node.attrs);
78
+ if (contentDOM) {
79
+ if (node.isLeaf) throw new DOMSpecError({ message: "Content hole not allowed in a leaf node spec" });
80
+ this.serializeFragment(node.content, options, contentDOM);
81
+ }
82
+ return dom;
83
+ }
84
+ /**
85
+ * Serialize this node to a DOM node. This can be useful when you
86
+ * need to serialize a part of a document, as opposed to the whole
87
+ * document. To serialize a whole document, use
88
+ * [`serializeFragment`](#DOMSerializer.serializeFragment) on its
89
+ * `content`.
90
+ *
91
+ * Throws a `DOMSpecError` (as a defect) for invalid output specs.
92
+ */
93
+ serializeNode(node, options = {}) {
94
+ let dom = this.serializeNodeInner(node, options);
95
+ for (let i = node.marks.length - 1; i >= 0; i--) {
96
+ const wrap = this.serializeMarkOrNull(node.marks[i], node.isInline, options);
97
+ if (wrap) {
98
+ (wrap.contentDOM || wrap.dom).appendChild(dom);
99
+ dom = wrap.dom;
100
+ }
101
+ }
102
+ return dom;
103
+ }
104
+ /**
105
+ * Serialize a mark. Returns `None` when there is no serializer for
106
+ * the mark's type.
107
+ *
108
+ * Throws a `DOMSpecError` (as a defect) for invalid output specs.
109
+ */
110
+ serializeMark(mark, inline, options = {}) {
111
+ return Option.fromNullishOr(this.serializeMarkOrNull(mark, inline, options));
112
+ }
113
+ /** @internal */
114
+ serializeMarkOrNull(mark, inline, options = {}) {
115
+ const toDOM = this.marks[mark.type.name];
116
+ return toDOM ? renderSpecInner(doc(options), toDOM(mark, inline), null, mark.attrs) : null;
117
+ }
118
+ /**
119
+ * Render an [output spec](#DOMOutputSpec) to a DOM node. If the spec
120
+ * has a hole (zero) in it, `contentDOM` will point at the node with
121
+ * the hole. Fails with a `DOMSpecError` for invalid specs.
122
+ */
123
+ static renderSpec(doc, structure, xmlNS = null) {
124
+ return attempt(() => DOMSerializer.renderSpecUnsafe(doc, structure, xmlNS), isDOMSpecError);
125
+ }
126
+ /**
127
+ * Like `renderSpec`, but throws a `DOMSpecError` for invalid specs.
128
+ * When `blockArraysIn` (usually the attributes of the node or mark
129
+ * being rendered) is given, arrays found in it are refused as specs.
130
+ */
131
+ static renderSpecUnsafe(doc, structure, xmlNS = null, blockArraysIn) {
132
+ if (typeof structure == "string") return { dom: doc.createTextNode(structure) };
133
+ return renderSpecInner(doc, structure, xmlNS, blockArraysIn);
134
+ }
135
+ /**
136
+ * Build a serializer using the [`toDOM`](#NodeSpec.toDOM) properties
137
+ * in a schema's node and mark specs. The result is cached on the
138
+ * schema.
139
+ */
140
+ static fromSchema(schema) {
141
+ return schema.cached.domSerializer || (schema.cached.domSerializer = new DOMSerializer(DOMSerializer.nodesFromSchema(schema), DOMSerializer.marksFromSchema(schema)));
142
+ }
143
+ /**
144
+ * Gather the serializers in a schema's node specs into an object.
145
+ * This can be useful as a base to build a custom serializer from.
146
+ */
147
+ static nodesFromSchema(schema) {
148
+ const result = gatherToDOM(schema.nodes);
149
+ if (!result.text) result.text = (node) => node.text;
150
+ return result;
151
+ }
152
+ /** Gather the serializers in a schema's mark specs into an object. */
153
+ static marksFromSchema(schema) {
154
+ return gatherToDOM(schema.marks);
155
+ }
156
+ };
157
+ const isDOMSpecError = (u) => u instanceof DOMSpecError;
158
+ function gatherToDOM(obj) {
159
+ const result = {};
160
+ for (const name in obj) {
161
+ const toDOM = obj[name].spec.toDOM;
162
+ if (toDOM) result[name] = toDOM;
163
+ }
164
+ return result;
165
+ }
166
+ function doc(options) {
167
+ return options.document || globalThis.document;
168
+ }
169
+ const suspiciousAttributeCache = /* @__PURE__ */ new WeakMap();
170
+ function suspiciousAttributes(attrs) {
171
+ let value = suspiciousAttributeCache.get(attrs);
172
+ if (value === void 0) suspiciousAttributeCache.set(attrs, value = suspiciousAttributesInner(attrs));
173
+ return value;
174
+ }
175
+ function suspiciousAttributesInner(attrs) {
176
+ let result = null;
177
+ function scan(value) {
178
+ if (value && typeof value == "object") {
179
+ if (Array.isArray(value)) {
180
+ if (typeof value[0] == "string") {
181
+ if (!result) result = [];
182
+ result.push(value);
183
+ } else for (let i = 0; i < value.length; i++) scan(value[i]);
184
+ } else for (const prop in value) scan(value[prop]);
185
+ }
186
+ }
187
+ scan(attrs);
188
+ return result;
189
+ }
190
+ function renderSpecInner(doc, structure, xmlNS, blockArraysIn) {
191
+ if (structure.nodeType === 1) return { dom: structure };
192
+ if (structure.dom && structure.dom.nodeType === 1) return structure;
193
+ let tagName = structure[0], suspicious;
194
+ if (typeof tagName != "string") throw new DOMSpecError({ message: "Invalid array passed to renderSpec" });
195
+ if (blockArraysIn && (suspicious = suspiciousAttributes(blockArraysIn)) && suspicious.indexOf(structure) > -1) throw new DOMSpecError({ message: "Using an array from an attribute object as a DOM spec. This may be an attempted cross site scripting attack." });
196
+ const space = tagName.indexOf(" ");
197
+ if (space > 0) {
198
+ xmlNS = tagName.slice(0, space);
199
+ tagName = tagName.slice(space + 1);
200
+ }
201
+ let contentDOM;
202
+ const dom = xmlNS ? doc.createElementNS(xmlNS, tagName) : doc.createElement(tagName);
203
+ const spec = structure;
204
+ const attrs = spec[1];
205
+ let start = 1;
206
+ if (attrs && typeof attrs == "object" && attrs.nodeType == null && !Array.isArray(attrs)) {
207
+ start = 2;
208
+ for (const name in attrs) if (attrs[name] != null) {
209
+ const space = name.indexOf(" ");
210
+ if (space > 0) dom.setAttributeNS(name.slice(0, space), name.slice(space + 1), attrs[name]);
211
+ else if (name == "style" && dom.style) dom.style.cssText = attrs[name];
212
+ else dom.setAttribute(name, attrs[name]);
213
+ }
214
+ }
215
+ for (let i = start; i < spec.length; i++) {
216
+ const child = spec[i];
217
+ if (child === 0) {
218
+ if (i < spec.length - 1 || i > start) throw new DOMSpecError({ message: "Content hole must be the only child of its parent node" });
219
+ return {
220
+ dom,
221
+ contentDOM: dom
222
+ };
223
+ } else if (typeof child == "string") dom.appendChild(doc.createTextNode(child));
224
+ else {
225
+ const { contentDOM: innerContent, dom: inner } = renderSpecInner(doc, child, xmlNS, blockArraysIn);
226
+ dom.appendChild(inner);
227
+ if (innerContent) {
228
+ if (contentDOM) throw new DOMSpecError({ message: "Multiple content holes" });
229
+ contentDOM = innerContent;
230
+ }
231
+ }
232
+ }
233
+ return {
234
+ dom,
235
+ contentDOM
236
+ };
237
+ }
238
+ export { DOMSerializer };
@@ -0,0 +1,12 @@
1
+ import { ContentExpr, ContentInput, ContentMatch, MatchEdge, content_d_exports } from "./model/content.js";
2
+ import { AttributeError, ContentError, DOMSpecError, JSONError, ModelError, PositionError, ReplaceError, SchemaDefinitionError, isModelError } from "./model/errors.js";
3
+ import { Fragment, NodeVisitor } from "./model/fragment.js";
4
+ import { DOMParser, DOMPosition, GenericParseRule, ParseOptions, ParseRule, RuleContext, StyleParseRule, TagParseRule } from "./model/from_dom.js";
5
+ import { DocFromJSON, NodeFromJSON, fragmentFromJSON, markFromJSON, nodeFromJSON, sliceFromJSON } from "./model/json.js";
6
+ import { Mark, MarkJSON } from "./model/mark.js";
7
+ import { ChildInfo, Node, NodeJSON, TextNode } from "./model/node.js";
8
+ import { Slice, SliceJSON, replace } from "./model/replace.js";
9
+ import { NodeRange, ResolvedPos } from "./model/resolvedpos.js";
10
+ import { AnySchema, Attribute, AttributeDefinition, AttributeDefinitions, AttributeSpec, AttributeType, Attrs, AttrsInputOfSpec, AttrsOfSpec, AttrsOptional, ChildNames, ContentRefsOf, DocSchema, FragmentInput, GroupsOf, MarkAttrs, MarkAttrsInput, MarkNames, MarkSpec, MarkSpecOf, MarkSpecs, MarkSpecsOf, MarkType, MarksInGroup, MarksInput, MarksWithOptionalAttrs, NodeAttrs, NodeAttrsInput, NodeNames, NodeSpec, NodeSpecOf, NodeSpecs, NodeSpecsOf, NodeType, NodesInGroup, NodesWithOptionalAttrs, ResolveRefs, SchemaSpec, SchemaSpecCheck, checkAttrs, computeAttrs, make, makeUnsafe, markSpec, nodeSpec } from "./model/schema.js";
11
+ import { DOMOutputSpec, DOMSerializer, MarkSerializer, NodeSerializer, RenderedSpec, SerializeOptions } from "./model/to_dom.js";
12
+ export { AnySchema, Attribute, AttributeDefinition, AttributeDefinitions, AttributeError, AttributeSpec, AttributeType, Attrs, AttrsInputOfSpec, AttrsOfSpec, AttrsOptional, ChildInfo, ChildNames, content_d_exports as Content, ContentError, type ContentExpr, type ContentInput, ContentMatch, ContentRefsOf, DOMOutputSpec, DOMParser, DOMPosition, DOMSerializer, DOMSpecError, DocFromJSON, DocSchema, Fragment, FragmentInput, GenericParseRule, GroupsOf, JSONError, Mark, MarkAttrs, MarkAttrsInput, MarkJSON, MarkNames, MarkSerializer, MarkSpec, MarkSpecOf, MarkSpecs, MarkSpecsOf, MarkType, MarksInGroup, MarksInput, MarksWithOptionalAttrs, type MatchEdge, ModelError, Node, NodeAttrs, NodeAttrsInput, NodeFromJSON, NodeJSON, NodeNames, NodeRange, NodeSerializer, NodeSpec, NodeSpecOf, NodeSpecs, NodeSpecsOf, NodeType, NodeVisitor, NodesInGroup, NodesWithOptionalAttrs, ParseOptions, ParseRule, PositionError, RenderedSpec, ReplaceError, ResolveRefs, ResolvedPos, RuleContext, SchemaDefinitionError, SchemaSpec, SchemaSpecCheck, SerializeOptions, Slice, SliceJSON, StyleParseRule, TagParseRule, TextNode, checkAttrs, computeAttrs, fragmentFromJSON, isModelError, make, makeUnsafe, markFromJSON, markSpec, nodeFromJSON, nodeSpec, replace, sliceFromJSON };
package/dist/model.js ADDED
@@ -0,0 +1,12 @@
1
+ import { ContentMatch, content_exports } from "./model/content.js";
2
+ import { AttributeError, ContentError, DOMSpecError, JSONError, PositionError, ReplaceError, SchemaDefinitionError, isModelError } from "./model/errors.js";
3
+ import { Fragment } from "./model/fragment.js";
4
+ import { DOMParser } from "./model/from_dom.js";
5
+ import { DocFromJSON, NodeFromJSON, fragmentFromJSON, markFromJSON, nodeFromJSON, sliceFromJSON } from "./model/json.js";
6
+ import { Mark } from "./model/mark.js";
7
+ import { Node, TextNode } from "./model/node.js";
8
+ import { Slice, replace } from "./model/replace.js";
9
+ import { NodeRange, ResolvedPos } from "./model/resolvedpos.js";
10
+ import { Attribute, DocSchema, MarkType, NodeType, checkAttrs, computeAttrs, make, makeUnsafe, markSpec, nodeSpec } from "./model/schema.js";
11
+ import { DOMSerializer } from "./model/to_dom.js";
12
+ export { Attribute, AttributeError, content_exports as Content, ContentError, ContentMatch, DOMParser, DOMSerializer, DOMSpecError, DocFromJSON, DocSchema, Fragment, JSONError, Mark, MarkType, Node, NodeFromJSON, NodeRange, NodeType, PositionError, ReplaceError, ResolvedPos, SchemaDefinitionError, Slice, TextNode, checkAttrs, computeAttrs, fragmentFromJSON, isModelError, make, makeUnsafe, markFromJSON, markSpec, nodeFromJSON, nodeSpec, replace, sliceFromJSON };
@@ -0,0 +1,53 @@
1
+ import { AnySchema } from "./model/schema.js";
2
+ import { Node } from "./model/node.js";
3
+ import "./model.js";
4
+ import { EditorState } from "./state/state.js";
5
+ import { Plugin } from "./state/plugin.js";
6
+ import "./state.js";
7
+ /**
8
+ * Where to show the placeholder:
9
+ *
10
+ * - `"doc"`: only when the whole document is empty (consists of a
11
+ * single empty textblock), on that textblock.
12
+ * - `"block"`: on the empty textblock that holds the cursor (which
13
+ * includes the case of an empty document).
14
+ *
15
+ * @since 0.1.0
16
+ * @category models
17
+ */
18
+ export type PlaceholderStrategy = "doc" | "block";
19
+ /**
20
+ * Options for `placeholder`.
21
+ *
22
+ * @since 0.1.0
23
+ * @category models
24
+ */
25
+ export interface PlaceholderOptions<Sc extends AnySchema = AnySchema> {
26
+ /**
27
+ * The placeholder text, or a function computing it from the state and
28
+ * the empty textblock (and its position).
29
+ */
30
+ readonly placeholder: string | ((state: EditorState<Sc, any, any>, node: Node<Sc>, pos: number) => string);
31
+ /** Where to show the placeholder. Defaults to `"doc"`. */
32
+ readonly strategy?: PlaceholderStrategy | undefined;
33
+ /** The class added to the empty textblock. Defaults to `"is-empty"`. */
34
+ readonly className?: string | undefined;
35
+ }
36
+ /**
37
+ * Create a placeholder plugin, which adds node decorations to empty
38
+ * textblocks: a `data-placeholder` attribute holding the placeholder
39
+ * text and a class (`is-empty` by default). Use CSS (for example
40
+ * `placeholderCSS`) to show the text.
41
+ *
42
+ * @since 0.1.0
43
+ * @category constructors
44
+ */
45
+ export declare const placeholder: <Sc extends AnySchema = AnySchema>(options: PlaceholderOptions<Sc>) => Plugin<undefined, Sc>;
46
+ /**
47
+ * A default stylesheet that shows the placeholder text (in grey) for
48
+ * textblocks with the default `is-empty` class.
49
+ *
50
+ * @since 0.1.0
51
+ * @category styles
52
+ */
53
+ export declare const placeholderCSS: string;
@@ -0,0 +1,79 @@
1
+ import { Plugin } from "./state/plugin.js";
2
+ import { Decoration, DecorationSet } from "./view/decoration.js";
3
+ import * as Predicate from "effect/Predicate";
4
+ // src/placeholder.ts
5
+ /**
6
+ * Placeholder text for empty documents or empty textblocks.
7
+ *
8
+ * The plugin only adds node decorations: the empty textblock gets a
9
+ * `data-placeholder` attribute holding the text and a class (`is-empty`
10
+ * by default). Showing the text is left to CSS; `placeholderCSS` is a
11
+ * small default stylesheet.
12
+ *
13
+ * ```ts skip-type-checking
14
+ * const plugins = [placeholder({ placeholder: "Write something…" })]
15
+ * ```
16
+ *
17
+ * @since 0.1.0
18
+ */
19
+ const isEmptyTextblock = (node) => node.isTextblock && node.content.size === 0;
20
+ /**
21
+ * Find the empty textblock (and its position) a placeholder should be
22
+ * shown on with the given strategy, if any.
23
+ */
24
+ const findEmptyBlock = (state, strategy) => {
25
+ const doc = state.doc;
26
+ if (strategy === "doc") {
27
+ if (doc.childCount !== 1) return null;
28
+ const first = doc.content.content[0];
29
+ return isEmptyTextblock(first) ? {
30
+ node: first,
31
+ pos: 0
32
+ } : null;
33
+ }
34
+ const { $from, empty } = state.selection;
35
+ if (!empty || $from.depth === 0) return null;
36
+ const parent = $from.parent;
37
+ return isEmptyTextblock(parent) ? {
38
+ node: parent,
39
+ pos: $from.beforeUnsafe()
40
+ } : null;
41
+ };
42
+ /**
43
+ * Create a placeholder plugin, which adds node decorations to empty
44
+ * textblocks: a `data-placeholder` attribute holding the placeholder
45
+ * text and a class (`is-empty` by default). Use CSS (for example
46
+ * `placeholderCSS`) to show the text.
47
+ *
48
+ * @since 0.1.0
49
+ * @category constructors
50
+ */
51
+ const placeholder = (options) => {
52
+ const strategy = options.strategy ?? "doc";
53
+ const className = options.className ?? "is-empty";
54
+ return new Plugin({ props: { decorations(state) {
55
+ const found = findEmptyBlock(state, strategy);
56
+ if (!found) return null;
57
+ const text = Predicate.isFunction(options.placeholder) ? options.placeholder(state, found.node, found.pos) : options.placeholder;
58
+ return DecorationSet.create(state.doc, [Decoration.node(found.pos, found.pos + found.node.nodeSize, {
59
+ class: className,
60
+ "data-placeholder": text
61
+ })]);
62
+ } } });
63
+ };
64
+ /**
65
+ * A default stylesheet that shows the placeholder text (in grey) for
66
+ * textblocks with the default `is-empty` class.
67
+ *
68
+ * @since 0.1.0
69
+ * @category styles
70
+ */
71
+ const placeholderCSS = `.ProseMirror .is-empty[data-placeholder]::before {
72
+ content: attr(data-placeholder);
73
+ float: left;
74
+ height: 0;
75
+ color: #adb5bd;
76
+ pointer-events: none;
77
+ }
78
+ `;
79
+ export { placeholder, placeholderCSS };
@@ -0,0 +1,54 @@
1
+ import { AnySchema } from "../model/schema.js";
2
+ import "../model.js";
3
+ import { EditorView } from "../view/view.js";
4
+ import "../view.js";
5
+ import { EditorStreams } from "./streams.js";
6
+ import * as Effect from "effect/Effect";
7
+ import * as Stream from "effect/Stream";
8
+ import * as Scope from "effect/Scope";
9
+ declare namespace observer_d_exports {
10
+ export { EditorObserver, EditorObserverTypeId, make };
11
+ }
12
+ /**
13
+ * @since 0.1.0
14
+ * @category symbols
15
+ */
16
+ export declare const EditorObserverTypeId: unique symbol;
17
+ /**
18
+ * @since 0.1.0
19
+ * @category symbols
20
+ */
21
+ export type EditorObserverTypeId = typeof EditorObserverTypeId;
22
+ /**
23
+ * Reactive streams following the state of an editor view (see
24
+ * `EditorStreams`), plus its focus.
25
+ *
26
+ * @since 0.1.0
27
+ * @category models
28
+ */
29
+ export interface EditorObserver<Sc extends AnySchema = AnySchema, R = never, E = never> extends EditorStreams<Sc, R, E> {
30
+ readonly [EditorObserverTypeId]: EditorObserverTypeId;
31
+ /** The observed view. */
32
+ readonly view: EditorView<Sc, R, E>;
33
+ /** Whether the view has focus: the current value, then every change. */
34
+ readonly focus: Stream.Stream<boolean>;
35
+ }
36
+ /**
37
+ * Observe an editor view. The observer registers a listener with
38
+ * `view.subscribe` and focus listeners on `view.dom`, and removes them
39
+ * (ending all its streams) when the scope closes. It doesn't change the
40
+ * view's props or plugins.
41
+ *
42
+ * ```ts skip-type-checking
43
+ * const observer = yield* EditorObserver.make(view)
44
+ * yield* observer.isMarkActive("strong").pipe(
45
+ * Stream.runForEach((active) => Effect.sync(() => button.classList.toggle("active", active))),
46
+ * Effect.forkScoped
47
+ * )
48
+ * ```
49
+ *
50
+ * @since 0.1.0
51
+ * @category constructors
52
+ */
53
+ export declare const make: <Sc extends AnySchema, R, E>(view: EditorView<Sc, R, E>) => Effect.Effect<EditorObserver<Sc, R, E>, never, Scope.Scope>;
54
+ export { observer_d_exports };
@@ -0,0 +1,61 @@
1
+ import { __exportAll } from "../_virtual/_rolldown/runtime.js";
2
+ import { acquireRef, acquireSource, publishTransactions, setRefUnsafe, setState, streamsOf } from "./streams.js";
3
+ import * as Effect from "effect/Effect";
4
+ import * as SubscriptionRef from "effect/SubscriptionRef";
5
+ // src/reactive/observer.ts
6
+ /**
7
+ * `EditorObserver`: reactive streams for an `EditorView`.
8
+ *
9
+ * @since 0.1.0
10
+ */
11
+ var observer_exports = /* @__PURE__ */ __exportAll({
12
+ EditorObserverTypeId: () => EditorObserverTypeId,
13
+ make: () => make
14
+ });
15
+ /**
16
+ * @since 0.1.0
17
+ * @category symbols
18
+ */
19
+ const EditorObserverTypeId = Symbol.for("effect-richtext/reactive/EditorObserver");
20
+ /**
21
+ * Observe an editor view. The observer registers a listener with
22
+ * `view.subscribe` and focus listeners on `view.dom`, and removes them
23
+ * (ending all its streams) when the scope closes. It doesn't change the
24
+ * view's props or plugins.
25
+ *
26
+ * ```ts skip-type-checking
27
+ * const observer = yield* EditorObserver.make(view)
28
+ * yield* observer.isMarkActive("strong").pipe(
29
+ * Stream.runForEach((active) => Effect.sync(() => button.classList.toggle("active", active))),
30
+ * Effect.forkScoped
31
+ * )
32
+ * ```
33
+ *
34
+ * @since 0.1.0
35
+ * @category constructors
36
+ */
37
+ const make = (view) => Effect.gen(function* () {
38
+ const source = yield* acquireSource(view.state);
39
+ const focused = yield* acquireRef(view.hasFocus());
40
+ yield* Effect.acquireRelease(Effect.sync(() => view.subscribe((update) => {
41
+ setState(source, view.state);
42
+ publishTransactions(source, update.transactions, update.state, update.prevState);
43
+ })), (unsubscribe) => Effect.sync(unsubscribe));
44
+ const setFocus = (value) => setRefUnsafe(focused, value);
45
+ const onFocusIn = () => setFocus(true);
46
+ const onFocusOut = (event) => setFocus(!!event.relatedTarget && view.dom.contains(event.relatedTarget));
47
+ yield* Effect.acquireRelease(Effect.sync(() => {
48
+ view.dom.addEventListener("focusin", onFocusIn);
49
+ view.dom.addEventListener("focusout", onFocusOut);
50
+ }), () => Effect.sync(() => {
51
+ view.dom.removeEventListener("focusin", onFocusIn);
52
+ view.dom.removeEventListener("focusout", onFocusOut);
53
+ }));
54
+ return {
55
+ [EditorObserverTypeId]: EditorObserverTypeId,
56
+ ...streamsOf(source, view),
57
+ view,
58
+ focus: SubscriptionRef.changes(focused)
59
+ };
60
+ });
61
+ export { EditorObserverTypeId, make, observer_exports };
@@ -0,0 +1,22 @@
1
+ import { AnySchema, Attrs, MarkType, NodeType } from "../model/schema.js";
2
+ import "../model.js";
3
+ import { EditorState } from "../state/state.js";
4
+ import "../state.js";
5
+ /**
6
+ * Whether a mark of the given type is active: for an empty selection, it
7
+ * is in the stored marks (or the marks at the cursor); otherwise it is
8
+ * present somewhere in one of the selection's ranges.
9
+ *
10
+ * @since 0.1.0
11
+ * @category selectors
12
+ */
13
+ export declare const isMarkActive: <Sc extends AnySchema>(state: EditorState<Sc, any, any>, type: MarkType<Sc, any> | string) => boolean;
14
+ /**
15
+ * Whether a node of the given type (with the given attributes, when
16
+ * provided) is selected (as a node selection) or wraps the start of the
17
+ * selection.
18
+ *
19
+ * @since 0.1.0
20
+ * @category selectors
21
+ */
22
+ export declare const isNodeActive: <Sc extends AnySchema>(state: EditorState<Sc, any, any>, type: NodeType<Sc, any> | string, attrs?: Attrs) => boolean;
@@ -0,0 +1,61 @@
1
+ import { AttributeError, ContentError } from "../model/errors.js";
2
+ import { NodeSelection } from "../state/selection.js";
3
+ import * as Predicate from "effect/Predicate";
4
+ // src/reactive/selectors.ts
5
+ /**
6
+ * Pure state selectors used by the reactive streams.
7
+ *
8
+ * @since 0.1.0
9
+ */
10
+ const markTypeOf = (state, type) => {
11
+ if (!Predicate.isString(type)) return type;
12
+ const markType = state.schema.marks[type];
13
+ if (!markType) throw new AttributeError({ message: "Unknown mark type " + type });
14
+ return markType;
15
+ };
16
+ const nodeTypeOf = (state, type) => {
17
+ if (!Predicate.isString(type)) return type;
18
+ const nodeType = state.schema.nodes[type];
19
+ if (!nodeType) throw new ContentError({ message: "Unknown node type " + type });
20
+ return nodeType;
21
+ };
22
+ const attrsMatch = (attrs, expected) => {
23
+ if (!expected) return true;
24
+ for (const name in expected) if (attrs[name] !== expected[name]) return false;
25
+ return true;
26
+ };
27
+ /**
28
+ * Whether a mark of the given type is active: for an empty selection, it
29
+ * is in the stored marks (or the marks at the cursor); otherwise it is
30
+ * present somewhere in one of the selection's ranges.
31
+ *
32
+ * @since 0.1.0
33
+ * @category selectors
34
+ */
35
+ const isMarkActive = (state, type) => {
36
+ const markType = markTypeOf(state, type);
37
+ const { empty, $from, ranges } = state.selection;
38
+ if (empty) return markType.findInSet(state.storedMarksOrNull || $from.marks()) !== void 0;
39
+ for (const range of ranges) if (state.doc.rangeHasMark(range.$from.pos, range.$to.pos, markType)) return true;
40
+ return false;
41
+ };
42
+ /**
43
+ * Whether a node of the given type (with the given attributes, when
44
+ * provided) is selected (as a node selection) or wraps the start of the
45
+ * selection.
46
+ *
47
+ * @since 0.1.0
48
+ * @category selectors
49
+ */
50
+ const isNodeActive = (state, type, attrs) => {
51
+ const nodeType = nodeTypeOf(state, type);
52
+ const { selection } = state;
53
+ if (selection instanceof NodeSelection && selection.node.type === nodeType) return attrsMatch(selection.node.attrs, attrs);
54
+ const { $from, to } = selection;
55
+ for (let d = $from.depth; d >= 0; d--) {
56
+ const node = $from.node(d);
57
+ if (node.type === nodeType && to <= $from.end(d) && attrsMatch(node.attrs, attrs)) return true;
58
+ }
59
+ return false;
60
+ };
61
+ export { isMarkActive, isNodeActive };
@@ -0,0 +1,72 @@
1
+ import { AnySchema } from "../model/schema.js";
2
+ import "../model.js";
3
+ import { StateFailure } from "../state/errors.js";
4
+ import { Command, Transaction } from "../state/transaction.js";
5
+ import { EditorState } from "../state/state.js";
6
+ import "../state.js";
7
+ import { EditorStreams } from "./streams.js";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Scope from "effect/Scope";
10
+ declare namespace stateref_d_exports {
11
+ export { StateRef, StateRefTypeId, make };
12
+ }
13
+ /**
14
+ * @since 0.1.0
15
+ * @category symbols
16
+ */
17
+ export declare const StateRefTypeId: unique symbol;
18
+ /**
19
+ * @since 0.1.0
20
+ * @category symbols
21
+ */
22
+ export type StateRefTypeId = typeof StateRefTypeId;
23
+ /**
24
+ * An editor state held in a `SubscriptionRef`, updated by dispatching
25
+ * transactions to it, without an editor view. Useful for headless or
26
+ * server-side editing, and for tests.
27
+ *
28
+ * Dispatches are serialized and run the state's plugins with the context
29
+ * captured by `StateRef.make`.
30
+ *
31
+ * @since 0.1.0
32
+ * @category models
33
+ */
34
+ export interface StateRef<Sc extends AnySchema = AnySchema, R = never, E = never> extends EditorStreams<Sc, R, E> {
35
+ readonly [StateRefTypeId]: StateRefTypeId;
36
+ /**
37
+ * Apply a transaction to the current state, publishing the new state
38
+ * and the applied transactions. Fails with the errors of the plugin
39
+ * hooks, or a `StateError` when the transaction doesn't start from the
40
+ * current document.
41
+ */
42
+ readonly dispatch: (tr: Transaction<Sc>) => Effect.Effect<void, E | StateFailure>;
43
+ /**
44
+ * Like `dispatch`, but runs synchronously and throws failures. Throws a
45
+ * defect when a plugin hook is asynchronous.
46
+ */
47
+ readonly dispatchUnsafe: (tr: Transaction<Sc>) => void;
48
+ /** Replace the state (e.g. with a reconfigured one), without a transaction. */
49
+ readonly set: (state: EditorState<Sc, R, E>) => Effect.Effect<void>;
50
+ /**
51
+ * Run a command against the current state, dispatching its
52
+ * transactions. Succeeds with the command's result; fails when the
53
+ * command or one of its dispatches fails.
54
+ */
55
+ readonly execute: <CE, CR>(command: Command<Sc, CE, CR>) => Effect.Effect<boolean, CE | E | StateFailure, CR>;
56
+ }
57
+ /**
58
+ * Create a `StateRef` holding `initial`. The current context (providing
59
+ * the plugins' requirements `R`) is captured to run plugin hooks when
60
+ * transactions are dispatched. Its streams end when the scope closes.
61
+ *
62
+ * ```ts skip-type-checking
63
+ * const ref = yield* StateRef.make(EditorState.createUnsafe({ schema }))
64
+ * yield* ref.dispatch(ref.state.getUnsafe().tr.insertTextUnsafe("hi"))
65
+ * yield* ref.execute(toggleMark(schema.marks.strong))
66
+ * ```
67
+ *
68
+ * @since 0.1.0
69
+ * @category constructors
70
+ */
71
+ export declare const make: <Sc extends AnySchema, R = never, E = never>(initial: EditorState<Sc, R, E>) => Effect.Effect<StateRef<Sc, R, E>, never, Scope.Scope | R>;
72
+ export { stateref_d_exports };