@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.
- package/LICENSE +48 -0
- package/README.md +117 -1
- package/dist/_virtual/_rolldown/runtime.js +12 -0
- package/dist/commands/commands.d.ts +323 -0
- package/dist/commands/commands.js +722 -0
- package/dist/commands/internal.js +43 -0
- package/dist/commands/keymaps.d.ts +46 -0
- package/dist/commands/keymaps.js +64 -0
- package/dist/commands.d.ts +3 -0
- package/dist/commands.js +3 -0
- package/dist/compat/commands.d.ts +250 -0
- package/dist/compat/commands.js +275 -0
- package/dist/compat/internal/core.d.ts +20 -0
- package/dist/compat/internal/core.js +148 -0
- package/dist/compat/internal/props.js +82 -0
- package/dist/compat/keymap.d.ts +21 -0
- package/dist/compat/keymap.js +38 -0
- package/dist/compat/model.d.ts +893 -0
- package/dist/compat/model.js +1212 -0
- package/dist/compat/orderedmap.d.ts +74 -0
- package/dist/compat/orderedmap.js +124 -0
- package/dist/compat/schema-basic.d.ts +38 -0
- package/dist/compat/schema-basic.js +238 -0
- package/dist/compat/schema-list.d.ts +81 -0
- package/dist/compat/schema-list.js +156 -0
- package/dist/compat/state.d.ts +415 -0
- package/dist/compat/state.js +689 -0
- package/dist/compat/test-builder.d.ts +98 -0
- package/dist/compat/test-builder.js +197 -0
- package/dist/compat/transform.d.ts +458 -0
- package/dist/compat/transform.js +803 -0
- package/dist/compat/view.d.ts +411 -0
- package/dist/compat/view.js +409 -0
- package/dist/dropcursor.d.ts +46 -0
- package/dist/dropcursor.js +176 -0
- package/dist/extensions/defaults.d.ts +716 -0
- package/dist/extensions/defaults.js +209 -0
- package/dist/extensions/editor.d.ts +246 -0
- package/dist/extensions/editor.js +271 -0
- package/dist/extensions/extension.d.ts +375 -0
- package/dist/extensions/extension.js +233 -0
- package/dist/extensions.d.ts +4 -0
- package/dist/extensions.js +4 -0
- package/dist/gapcursor.d.ts +66 -0
- package/dist/gapcursor.js +273 -0
- package/dist/history.d.ts +178 -0
- package/dist/history.js +412 -0
- package/dist/html.d.ts +158 -0
- package/dist/html.js +164 -0
- package/dist/inputrules.d.ts +271 -0
- package/dist/inputrules.js +321 -0
- package/dist/internal/attempt.js +15 -0
- package/dist/internal/json.js +29 -0
- package/dist/keymap/keymap.d.ts +130 -0
- package/dist/keymap/keymap.js +159 -0
- package/dist/keymap.d.ts +2 -0
- package/dist/keymap.js +1 -0
- package/dist/markdown/errors.d.ts +23 -0
- package/dist/markdown/errors.js +26 -0
- package/dist/markdown/from_markdown.d.ts +185 -0
- package/dist/markdown/from_markdown.js +266 -0
- package/dist/markdown/schema.d.ts +522 -0
- package/dist/markdown/schema.js +292 -0
- package/dist/markdown/to_markdown.d.ts +251 -0
- package/dist/markdown/to_markdown.js +413 -0
- package/dist/markdown.d.ts +5 -0
- package/dist/markdown.js +5 -0
- package/dist/model/attrsCodec.js +48 -0
- package/dist/model/comparedeep.js +17 -0
- package/dist/model/content.d.ts +232 -0
- package/dist/model/content.js +630 -0
- package/dist/model/diff.js +66 -0
- package/dist/model/dom.d.ts +3 -0
- package/dist/model/errors.d.ts +90 -0
- package/dist/model/errors.js +76 -0
- package/dist/model/fragment.d.ts +131 -0
- package/dist/model/fragment.js +262 -0
- package/dist/model/from_dom.d.ts +299 -0
- package/dist/model/from_dom.js +618 -0
- package/dist/model/json.d.ts +37 -0
- package/dist/model/json.js +126 -0
- package/dist/model/mark.d.ts +73 -0
- package/dist/model/mark.js +121 -0
- package/dist/model/node.d.ts +267 -0
- package/dist/model/node.js +434 -0
- package/dist/model/replace.d.ts +78 -0
- package/dist/model/replace.js +207 -0
- package/dist/model/resolvedpos.d.ts +196 -0
- package/dist/model/resolvedpos.js +305 -0
- package/dist/model/schema.d.ts +740 -0
- package/dist/model/schema.js +451 -0
- package/dist/model/to_dom.d.ts +169 -0
- package/dist/model/to_dom.js +238 -0
- package/dist/model.d.ts +12 -0
- package/dist/model.js +12 -0
- package/dist/placeholder.d.ts +53 -0
- package/dist/placeholder.js +79 -0
- package/dist/reactive/observer.d.ts +54 -0
- package/dist/reactive/observer.js +61 -0
- package/dist/reactive/selectors.d.ts +22 -0
- package/dist/reactive/selectors.js +61 -0
- package/dist/reactive/stateref.d.ts +72 -0
- package/dist/reactive/stateref.js +71 -0
- package/dist/reactive/streams.d.ts +83 -0
- package/dist/reactive/streams.js +85 -0
- package/dist/reactive.d.ts +16 -0
- package/dist/reactive.js +26 -0
- package/dist/schema-basic.d.ts +448 -0
- package/dist/schema-basic.js +269 -0
- package/dist/schema-list/commands.d.ts +66 -0
- package/dist/schema-list/commands.js +203 -0
- package/dist/schema-list.d.ts +92 -0
- package/dist/schema-list.js +100 -0
- package/dist/state/errors.d.ts +39 -0
- package/dist/state/errors.js +32 -0
- package/dist/state/plugin.d.ts +232 -0
- package/dist/state/plugin.js +82 -0
- package/dist/state/selection.d.ts +297 -0
- package/dist/state/selection.js +486 -0
- package/dist/state/state.d.ts +212 -0
- package/dist/state/state.js +489 -0
- package/dist/state/transaction.d.ts +145 -0
- package/dist/state/transaction.js +195 -0
- package/dist/state.d.ts +6 -0
- package/dist/state.js +6 -0
- package/dist/test-builder.d.ts +3582 -0
- package/dist/test-builder.js +239 -0
- package/dist/transform/attr_step.d.ts +60 -0
- package/dist/transform/attr_step.js +105 -0
- package/dist/transform/errors.d.ts +50 -0
- package/dist/transform/errors.js +35 -0
- package/dist/transform/map.d.ts +182 -0
- package/dist/transform/map.js +280 -0
- package/dist/transform/mark.js +98 -0
- package/dist/transform/mark_set_step.js +69 -0
- package/dist/transform/mark_step.d.ts +113 -0
- package/dist/transform/mark_step.js +225 -0
- package/dist/transform/replace.d.ts +18 -0
- package/dist/transform/replace.js +381 -0
- package/dist/transform/replace_step.d.ts +109 -0
- package/dist/transform/replace_step.js +175 -0
- package/dist/transform/step.d.ts +157 -0
- package/dist/transform/step.js +168 -0
- package/dist/transform/structure.d.ts +89 -0
- package/dist/transform/structure.js +350 -0
- package/dist/transform/transform.d.ts +248 -0
- package/dist/transform/transform.js +373 -0
- package/dist/transform.d.ts +10 -0
- package/dist/transform.js +10 -0
- package/dist/view/browser.js +39 -0
- package/dist/view/capturekeys.js +282 -0
- package/dist/view/clipboard.d.ts +8 -0
- package/dist/view/clipboard.js +245 -0
- package/dist/view/decoration.d.ts +339 -0
- package/dist/view/decoration.js +566 -0
- package/dist/view/dom.d.ts +18 -0
- package/dist/view/dom.js +140 -0
- package/dist/view/domchange.js +295 -0
- package/dist/view/domcoords.d.ts +22 -0
- package/dist/view/domcoords.js +448 -0
- package/dist/view/domobserver.d.ts +47 -0
- package/dist/view/domobserver.js +328 -0
- package/dist/view/errors.d.ts +18 -0
- package/dist/view/errors.js +21 -0
- package/dist/view/input.d.ts +58 -0
- package/dist/view/input.js +704 -0
- package/dist/view/selection.js +198 -0
- package/dist/view/view.d.ts +699 -0
- package/dist/view/view.js +623 -0
- package/dist/view/viewdesc.d.ts +236 -0
- package/dist/view/viewdesc.js +1205 -0
- package/dist/view-css.d.ts +43 -0
- package/dist/view-css.js +124 -0
- package/dist/view.d.ts +6 -0
- package/dist/view.js +4 -0
- package/package.json +200 -4
|
@@ -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 };
|
package/dist/model.d.ts
ADDED
|
@@ -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 };
|