@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,271 @@
|
|
|
1
|
+
import { fromHTMLUnsafe } from "../html.js";
|
|
2
|
+
import { attempt } from "../internal/attempt.js";
|
|
3
|
+
import { DocSchema } from "../model/schema.js";
|
|
4
|
+
import { Fragment } from "../model/fragment.js";
|
|
5
|
+
import { isModelError } from "../model/errors.js";
|
|
6
|
+
import { Mark } from "../model/mark.js";
|
|
7
|
+
import { Node } from "../model/node.js";
|
|
8
|
+
import { EditorState, runSyncUnsafe } from "../state/state.js";
|
|
9
|
+
import { StateError, isStateFailure } from "../state/errors.js";
|
|
10
|
+
import { EditorView } from "../view/view.js";
|
|
11
|
+
import { flatten, resolvePlugins } from "./extension.js";
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import * as Option from "effect/Option";
|
|
14
|
+
// src/extensions/editor.ts
|
|
15
|
+
/**
|
|
16
|
+
* The `Editor`: an editor state, an optional view, and typed commands
|
|
17
|
+
* and builders, created from an extension.
|
|
18
|
+
*
|
|
19
|
+
* @since 0.1.0
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* An editor state seen by chained commands. It shares one transaction
|
|
23
|
+
* between all commands in a chain: its `doc`, `selection` and stored
|
|
24
|
+
* marks are the transaction's current ones, and its `tr` is the shared
|
|
25
|
+
* transaction itself. Plugin state is that of the state the chain
|
|
26
|
+
* started from.
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
var ChainableState = class extends EditorState {
|
|
31
|
+
constructor(base, chainTr) {
|
|
32
|
+
super(base.config, base.slots);
|
|
33
|
+
this.chainTr = chainTr;
|
|
34
|
+
}
|
|
35
|
+
get doc() {
|
|
36
|
+
return this.chainTr.doc;
|
|
37
|
+
}
|
|
38
|
+
get selection() {
|
|
39
|
+
return this.chainTr.selection;
|
|
40
|
+
}
|
|
41
|
+
get storedMarksOrNull() {
|
|
42
|
+
return this.chainTr.storedMarksOrNull;
|
|
43
|
+
}
|
|
44
|
+
get tr() {
|
|
45
|
+
return this.chainTr;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const foldInto = (target, tr) => {
|
|
49
|
+
if (tr === target) return;
|
|
50
|
+
if (!tr.before.eq(target.doc)) throw new StateError({ message: "A chained command dispatched a transaction that doesn't match the chain" });
|
|
51
|
+
for (const step of tr.steps) target.stepUnsafe(step);
|
|
52
|
+
if (tr.selectionSet) target.setSelectionUnsafe(tr.selection.map(target.doc, target.mapping.slice(target.steps.length)));
|
|
53
|
+
if (tr.storedMarksSet) target.setStoredMarks(tr.storedMarksOrNull);
|
|
54
|
+
if (tr.scrolledIntoView) target.scrollIntoView();
|
|
55
|
+
const meta = tr.meta;
|
|
56
|
+
for (const key in meta) target.setMeta(key, meta[key]);
|
|
57
|
+
};
|
|
58
|
+
const isPlainObject = (u) => {
|
|
59
|
+
if (u === null || typeof u !== "object" || Array.isArray(u)) return false;
|
|
60
|
+
const proto = Object.getPrototypeOf(u);
|
|
61
|
+
return proto === Object.prototype || proto === null;
|
|
62
|
+
};
|
|
63
|
+
const dieOnStateFailure = (effect) => Effect.catchIf(effect, isStateFailure, (e) => Effect.die(e));
|
|
64
|
+
/**
|
|
65
|
+
* @since 0.1.0
|
|
66
|
+
* @category symbols
|
|
67
|
+
*/
|
|
68
|
+
const EditorTypeId = Symbol.for("effect-richtext/extensions/Editor");
|
|
69
|
+
/**
|
|
70
|
+
* An editor created from an extension by `createEditor`. It owns the
|
|
71
|
+
* current `EditorState`, and, while mounted, an `EditorView` (which then
|
|
72
|
+
* holds the current state).
|
|
73
|
+
*
|
|
74
|
+
* @since 0.1.0
|
|
75
|
+
* @category models
|
|
76
|
+
*/
|
|
77
|
+
var Editor = class {
|
|
78
|
+
/** @internal */
|
|
79
|
+
constructor(schema, state, creators) {
|
|
80
|
+
this.schema = schema;
|
|
81
|
+
this._view = void 0;
|
|
82
|
+
this._state = state;
|
|
83
|
+
this.creators = creators;
|
|
84
|
+
const nodes = {};
|
|
85
|
+
for (const name in schema.nodes) nodes[name] = this.nodeBuilder(name);
|
|
86
|
+
this.nodes = nodes;
|
|
87
|
+
const marks = {};
|
|
88
|
+
for (const name in schema.marks) marks[name] = (attrs) => schema.markUnsafe(name, attrs);
|
|
89
|
+
this.marks = marks;
|
|
90
|
+
const commands = {};
|
|
91
|
+
for (const name in creators) commands[name] = (...args) => Effect.suspend(() => this.runCommand(creators[name](...args), true));
|
|
92
|
+
this.commands = commands;
|
|
93
|
+
}
|
|
94
|
+
/** The current editor state (the view's state while mounted). */
|
|
95
|
+
get state() {
|
|
96
|
+
return this._view ? this._view.state : this._state;
|
|
97
|
+
}
|
|
98
|
+
/** The view, while the editor is mounted. */
|
|
99
|
+
get view() {
|
|
100
|
+
return Option.fromUndefinedOr(this._view);
|
|
101
|
+
}
|
|
102
|
+
/** Whether the editor is currently mounted. */
|
|
103
|
+
get mounted() {
|
|
104
|
+
return this._view !== void 0;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Mount the editor: create an `EditorView` (with `EditorView.make`)
|
|
108
|
+
* for the current state in `place`. The view is destroyed, and the
|
|
109
|
+
* editor unmounted, when the scope closes (or on `unmount`). Mounting
|
|
110
|
+
* an already mounted editor first unmounts it.
|
|
111
|
+
*/
|
|
112
|
+
mount(place, props) {
|
|
113
|
+
return Effect.gen({ self: this }, function* () {
|
|
114
|
+
yield* this.unmount;
|
|
115
|
+
const view = yield* EditorView.make(place, {
|
|
116
|
+
...props,
|
|
117
|
+
state: this.state
|
|
118
|
+
});
|
|
119
|
+
this._view = view;
|
|
120
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => this.detach(view)));
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** Destroy the view, if mounted. The editor keeps the view's last state. */
|
|
124
|
+
get unmount() {
|
|
125
|
+
return Effect.sync(() => {
|
|
126
|
+
const view = this._view;
|
|
127
|
+
if (view) {
|
|
128
|
+
this.detach(view);
|
|
129
|
+
view.destroy();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
detach(view) {
|
|
134
|
+
if (this._view !== view) return;
|
|
135
|
+
this._state = view.state;
|
|
136
|
+
this._view = void 0;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Dispatch a transaction: through the view when mounted (honouring its
|
|
140
|
+
* `dispatchTransaction` prop), otherwise by applying it to the
|
|
141
|
+
* editor's state. Fails with the plugins' errors.
|
|
142
|
+
*/
|
|
143
|
+
dispatch(tr) {
|
|
144
|
+
return Effect.suspend(() => {
|
|
145
|
+
const view = this._view;
|
|
146
|
+
if (view) return dieOnStateFailure(view.tryDispatch(tr));
|
|
147
|
+
return dieOnStateFailure(Effect.map(this._state.apply(tr), (state) => {
|
|
148
|
+
this._state = state;
|
|
149
|
+
}));
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
runCommand(command, dispatching) {
|
|
153
|
+
if (!dispatching) return command(this.state, void 0, this._view);
|
|
154
|
+
return Effect.flatMap(Effect.context(), (context) => {
|
|
155
|
+
const dispatch = (tr) => Effect.provideContext(this.dispatch(tr), context);
|
|
156
|
+
return command(this.state, dispatch, this._view);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/** Check whether commands apply to the current state, without dispatching. */
|
|
160
|
+
can() {
|
|
161
|
+
const result = {};
|
|
162
|
+
for (const name in this.creators) result[name] = (...args) => Effect.suspend(() => this.runCommand(this.creators[name](...args), false));
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Queue commands to apply them in a single transaction. Each command
|
|
167
|
+
* sees the document, selection and stored marks produced by the
|
|
168
|
+
* previous ones (through a state whose `tr` is the shared
|
|
169
|
+
* transaction). `run()` runs every queued command, then dispatches
|
|
170
|
+
* the transaction (when any command changed it), and succeeds with
|
|
171
|
+
* `true` when all commands applied.
|
|
172
|
+
*/
|
|
173
|
+
chain() {
|
|
174
|
+
const queue = [];
|
|
175
|
+
const chain = {};
|
|
176
|
+
for (const name in this.creators) {
|
|
177
|
+
if (name === "run") continue;
|
|
178
|
+
chain[name] = (...args) => {
|
|
179
|
+
queue.push(this.creators[name](...args));
|
|
180
|
+
return chain;
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
chain.run = () => Effect.gen({ self: this }, function* () {
|
|
184
|
+
const state = this.state;
|
|
185
|
+
const tr = state.tr;
|
|
186
|
+
const chained = new ChainableState(state, tr);
|
|
187
|
+
const dispatch = (t) => Effect.sync(() => foldInto(tr, t));
|
|
188
|
+
let all = true;
|
|
189
|
+
for (const command of queue) if (!(yield* command(chained, dispatch, this._view))) all = false;
|
|
190
|
+
if (tr.docChanged || tr.selectionSet || tr.storedMarksSet || tr.scrolledIntoView || !tr.isGeneric) yield* this.dispatch(tr);
|
|
191
|
+
return all;
|
|
192
|
+
});
|
|
193
|
+
return chain;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Replace the document's content (through a transaction, so plugins
|
|
197
|
+
* like the history see it). Accepts a node, its JSON, or HTML.
|
|
198
|
+
*/
|
|
199
|
+
setContent(content) {
|
|
200
|
+
return Effect.flatMap(attempt(() => parseContent(this.schema, content), isModelError), (doc) => Effect.suspend(() => {
|
|
201
|
+
const tr = this.state.tr;
|
|
202
|
+
tr.replaceWithUnsafe(0, tr.doc.content.size, doc.content);
|
|
203
|
+
return this.dispatch(tr);
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
/** The current document as JSON. */
|
|
207
|
+
getDocJSON() {
|
|
208
|
+
return this.state.doc.toJSON();
|
|
209
|
+
}
|
|
210
|
+
/** Create a text node with the given marks. Throws for empty strings. */
|
|
211
|
+
text(text, ...marks) {
|
|
212
|
+
return this.schema.textUnsafe(text, marks.length ? Mark.setFrom(marks) : null);
|
|
213
|
+
}
|
|
214
|
+
nodeBuilder(name) {
|
|
215
|
+
const type = this.schema.nodes[name];
|
|
216
|
+
return (...args) => {
|
|
217
|
+
let attrs = null;
|
|
218
|
+
if (args.length && (args[0] === null || isPlainObject(args[0]))) attrs = args.shift();
|
|
219
|
+
const children = [];
|
|
220
|
+
const add = (child) => {
|
|
221
|
+
if (typeof child === "string") {
|
|
222
|
+
if (child) children.push(this.schema.textUnsafe(child));
|
|
223
|
+
} else if (Array.isArray(child)) child.forEach(add);
|
|
224
|
+
else if (child instanceof Fragment) child.forEach((n) => children.push(n));
|
|
225
|
+
else if (child instanceof Node) children.push(child);
|
|
226
|
+
else throw new TypeError("Invalid child passed to a node builder: " + String(child));
|
|
227
|
+
};
|
|
228
|
+
args.forEach(add);
|
|
229
|
+
return type.createCheckedUnsafe(attrs, children);
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
const parseContent = (schema, content) => {
|
|
234
|
+
if (content instanceof Node) return content.type.schema === schema ? content : schema.nodeFromJSONUnsafe(content.toJSON());
|
|
235
|
+
if (typeof content === "string") return fromHTMLUnsafe(schema, content);
|
|
236
|
+
return schema.nodeFromJSONUnsafe(content);
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* Create an editor from an extension: builds the schema from the merged
|
|
240
|
+
* node and mark specs (ordered by priority, then declaration), resolves
|
|
241
|
+
* the plugins (keymaps included, same order) and creates the initial
|
|
242
|
+
* state. The editor isn't mounted; see `Editor.mount`.
|
|
243
|
+
*
|
|
244
|
+
* @since 0.1.0
|
|
245
|
+
* @category constructors
|
|
246
|
+
*/
|
|
247
|
+
const createEditor = (extension, options = {}) => Effect.gen(function* () {
|
|
248
|
+
const flat = flatten(extension);
|
|
249
|
+
const schema = yield* DocSchema.make({
|
|
250
|
+
nodes: flat.nodes,
|
|
251
|
+
marks: flat.marks
|
|
252
|
+
});
|
|
253
|
+
const plugins = resolvePlugins(flat.plugins, schema);
|
|
254
|
+
const doc = options.doc === void 0 ? void 0 : yield* attempt(() => parseContent(schema, options.doc), isModelError);
|
|
255
|
+
const config = {
|
|
256
|
+
schema,
|
|
257
|
+
doc,
|
|
258
|
+
selection: doc && options.selection ? options.selection(doc) : void 0,
|
|
259
|
+
plugins
|
|
260
|
+
};
|
|
261
|
+
return new Editor(schema, yield* EditorState.create(config), flat.commands);
|
|
262
|
+
});
|
|
263
|
+
/**
|
|
264
|
+
* Like `createEditor`, but runs synchronously and throws failures. Only
|
|
265
|
+
* available for extensions that require no services.
|
|
266
|
+
*
|
|
267
|
+
* @since 0.1.0
|
|
268
|
+
* @category constructors
|
|
269
|
+
*/
|
|
270
|
+
const createEditorUnsafe = (extension, options = {}) => runSyncUnsafe(createEditor(extension, options));
|
|
271
|
+
export { Editor, EditorTypeId, createEditor, createEditorUnsafe };
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { AnySchema, MarkSpec, MarkSpecs, NodeSpec, NodeSpecs } from "../model/schema.js";
|
|
2
|
+
import "../keymap.js";
|
|
3
|
+
import { Command } from "../state/transaction.js";
|
|
4
|
+
import { AnyPlugin, Plugin } from "../state/plugin.js";
|
|
5
|
+
import "../model.js";
|
|
6
|
+
import { Bindings, BindingsError, BindingsRequirements } from "../keymap/keymap.js";
|
|
7
|
+
import "../state.js";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
/**
|
|
10
|
+
* Extension priorities. Within a merged extension, facets with a higher
|
|
11
|
+
* priority come first: their node and mark specs come earlier in the
|
|
12
|
+
* schema (which matters for the default node of a group and for parse
|
|
13
|
+
* rule precedence), their plugins and keymaps come earlier in the
|
|
14
|
+
* plugin list (so their key bindings and props take precedence), and
|
|
15
|
+
* their commands win over commands with the same name. Facets with the
|
|
16
|
+
* same priority keep their declaration order.
|
|
17
|
+
*
|
|
18
|
+
* @since 0.1.0
|
|
19
|
+
* @category models
|
|
20
|
+
*/
|
|
21
|
+
export declare const Priority: {
|
|
22
|
+
readonly lowest: 0;
|
|
23
|
+
readonly low: 1;
|
|
24
|
+
readonly default: 2;
|
|
25
|
+
readonly high: 3;
|
|
26
|
+
readonly highest: 4;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* @since 0.1.0
|
|
30
|
+
* @category models
|
|
31
|
+
*/
|
|
32
|
+
export type Priority = (typeof Priority)[keyof typeof Priority];
|
|
33
|
+
/**
|
|
34
|
+
* A command whose argument types depend on the final (merged) schema,
|
|
35
|
+
* described as a type lambda: `this["Schema"]` is the editor's schema
|
|
36
|
+
* and `this["Return"]` the type the call returns (an Effect for
|
|
37
|
+
* `editor.commands`, the chain for `editor.chain()`).
|
|
38
|
+
*
|
|
39
|
+
* ```ts skip-type-checking
|
|
40
|
+
* interface ToggleMark extends CommandSignature {
|
|
41
|
+
* readonly signature: <M extends MarkNames<this["Schema"]>>(mark: M) => this["Return"]
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @since 0.1.0
|
|
46
|
+
* @category type-level
|
|
47
|
+
*/
|
|
48
|
+
export interface CommandSignature {
|
|
49
|
+
readonly Schema: unknown;
|
|
50
|
+
readonly Return: unknown;
|
|
51
|
+
readonly signature: (...args: any) => unknown;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The type-level description of a command: its argument tuple, or a
|
|
55
|
+
* `CommandSignature` for schema-dependent commands.
|
|
56
|
+
*
|
|
57
|
+
* @since 0.1.0
|
|
58
|
+
* @category type-level
|
|
59
|
+
*/
|
|
60
|
+
export type CommandType = ReadonlyArray<any> | CommandSignature;
|
|
61
|
+
/**
|
|
62
|
+
* @since 0.1.0
|
|
63
|
+
* @category type-level
|
|
64
|
+
*/
|
|
65
|
+
export interface CommandTypes {
|
|
66
|
+
readonly [name: string]: CommandType;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* What an extension contributes at the type level: node specs, mark
|
|
70
|
+
* specs, commands (name to argument types), and the errors (`E`) and
|
|
71
|
+
* requirements (`R`) of its plugins and commands.
|
|
72
|
+
*
|
|
73
|
+
* @since 0.1.0
|
|
74
|
+
* @category type-level
|
|
75
|
+
*/
|
|
76
|
+
export interface ExtensionTypes {
|
|
77
|
+
readonly nodes: NodeSpecs;
|
|
78
|
+
readonly marks: MarkSpecs;
|
|
79
|
+
readonly commands: CommandTypes;
|
|
80
|
+
readonly E: unknown;
|
|
81
|
+
readonly R: unknown;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The types of an extension that contributes nothing.
|
|
85
|
+
*
|
|
86
|
+
* @since 0.1.0
|
|
87
|
+
* @category type-level
|
|
88
|
+
*/
|
|
89
|
+
export interface EmptyTypes {
|
|
90
|
+
readonly nodes: {};
|
|
91
|
+
readonly marks: {};
|
|
92
|
+
readonly commands: {};
|
|
93
|
+
readonly E: never;
|
|
94
|
+
readonly R: never;
|
|
95
|
+
}
|
|
96
|
+
type Simplify<A> = { [K in keyof A]: A[K]; } & {};
|
|
97
|
+
type Cast<A, B> = A extends B ? A : never;
|
|
98
|
+
type UnionToIntersection<U> = (U extends any ? (u: U) => void : never) extends ((i: infer I) => void) ? I : never;
|
|
99
|
+
/**
|
|
100
|
+
* Merge a union of extension types: node, mark and command records are
|
|
101
|
+
* intersected, `E` and `R` are unions.
|
|
102
|
+
*
|
|
103
|
+
* @since 0.1.0
|
|
104
|
+
* @category type-level
|
|
105
|
+
*/
|
|
106
|
+
export type MergeTypes<T extends ExtensionTypes> = {
|
|
107
|
+
readonly nodes: Cast<Simplify<UnionToIntersection<T["nodes"]>>, NodeSpecs>;
|
|
108
|
+
readonly marks: Cast<Simplify<UnionToIntersection<T["marks"]>>, MarkSpecs>;
|
|
109
|
+
readonly commands: Cast<Simplify<UnionToIntersection<T["commands"]>>, CommandTypes>;
|
|
110
|
+
readonly E: T["E"];
|
|
111
|
+
readonly R: T["R"];
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* The types of an extension.
|
|
115
|
+
*
|
|
116
|
+
* @since 0.1.0
|
|
117
|
+
* @category type-level
|
|
118
|
+
*/
|
|
119
|
+
export type TypesOf<X> = X extends Extension<infer T> ? T : never;
|
|
120
|
+
/**
|
|
121
|
+
* The node specs contributed by an extension.
|
|
122
|
+
*
|
|
123
|
+
* @since 0.1.0
|
|
124
|
+
* @category type-level
|
|
125
|
+
*/
|
|
126
|
+
export type ExtensionNodes<X> = TypesOf<X>["nodes"];
|
|
127
|
+
/**
|
|
128
|
+
* The mark specs contributed by an extension.
|
|
129
|
+
*
|
|
130
|
+
* @since 0.1.0
|
|
131
|
+
* @category type-level
|
|
132
|
+
*/
|
|
133
|
+
export type ExtensionMarks<X> = TypesOf<X>["marks"];
|
|
134
|
+
/**
|
|
135
|
+
* The commands contributed by an extension.
|
|
136
|
+
*
|
|
137
|
+
* @since 0.1.0
|
|
138
|
+
* @category type-level
|
|
139
|
+
*/
|
|
140
|
+
export type ExtensionCommands<X> = TypesOf<X>["commands"];
|
|
141
|
+
/**
|
|
142
|
+
* Creates a command from its arguments.
|
|
143
|
+
*
|
|
144
|
+
* @since 0.1.0
|
|
145
|
+
* @category models
|
|
146
|
+
*/
|
|
147
|
+
export type CommandCreator = (...args: any) => Command<any, any, any>;
|
|
148
|
+
/**
|
|
149
|
+
* @since 0.1.0
|
|
150
|
+
* @category models
|
|
151
|
+
*/
|
|
152
|
+
export interface CommandCreators {
|
|
153
|
+
readonly [name: string]: CommandCreator;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* A plugin, a list of plugins, or a function creating them from the
|
|
157
|
+
* editor's schema.
|
|
158
|
+
*
|
|
159
|
+
* @since 0.1.0
|
|
160
|
+
* @category models
|
|
161
|
+
*/
|
|
162
|
+
export type PluginInput = AnyPlugin | ReadonlyArray<AnyPlugin> | ((schema: AnySchema) => AnyPlugin | ReadonlyArray<AnyPlugin>);
|
|
163
|
+
/**
|
|
164
|
+
* The contents of a single extension, as given to `defineExtension`.
|
|
165
|
+
*
|
|
166
|
+
* @since 0.1.0
|
|
167
|
+
* @category models
|
|
168
|
+
*/
|
|
169
|
+
export interface ExtensionSpec {
|
|
170
|
+
readonly nodes?: NodeSpecs | undefined;
|
|
171
|
+
readonly marks?: MarkSpecs | undefined;
|
|
172
|
+
readonly plugins?: ReadonlyArray<PluginInput> | undefined;
|
|
173
|
+
readonly keymap?: Bindings | undefined;
|
|
174
|
+
readonly commands?: CommandCreators | undefined;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* @since 0.1.0
|
|
178
|
+
* @category symbols
|
|
179
|
+
*/
|
|
180
|
+
export declare const ExtensionTypeId: unique symbol;
|
|
181
|
+
/**
|
|
182
|
+
* @since 0.1.0
|
|
183
|
+
* @category symbols
|
|
184
|
+
*/
|
|
185
|
+
export type ExtensionTypeId = typeof ExtensionTypeId;
|
|
186
|
+
/**
|
|
187
|
+
* An extension: a tree of facets (node and mark specs, plugins,
|
|
188
|
+
* keymaps and commands), each with an optional priority. `T` records,
|
|
189
|
+
* at the type level, what the extension contributes. Extensions are
|
|
190
|
+
* immutable values; combine them with `union` and turn them into an
|
|
191
|
+
* editor with `createEditor`.
|
|
192
|
+
*
|
|
193
|
+
* @since 0.1.0
|
|
194
|
+
* @category models
|
|
195
|
+
*/
|
|
196
|
+
export declare class Extension<out T extends ExtensionTypes = EmptyTypes> {
|
|
197
|
+
/** @internal */
|
|
198
|
+
readonly spec: ExtensionSpec | undefined;
|
|
199
|
+
/** @internal */
|
|
200
|
+
readonly children: ReadonlyArray<AnyExtension>;
|
|
201
|
+
/** @internal */
|
|
202
|
+
readonly priority: Priority | undefined;
|
|
203
|
+
readonly [ExtensionTypeId]: {
|
|
204
|
+
readonly _T: (_: never) => T;
|
|
205
|
+
};
|
|
206
|
+
/** @internal */
|
|
207
|
+
constructor(
|
|
208
|
+
/** @internal */
|
|
209
|
+
spec: ExtensionSpec | undefined,
|
|
210
|
+
/** @internal */
|
|
211
|
+
children: ReadonlyArray<AnyExtension>,
|
|
212
|
+
/** @internal */
|
|
213
|
+
priority: Priority | undefined);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Any extension.
|
|
217
|
+
*
|
|
218
|
+
* @since 0.1.0
|
|
219
|
+
* @category models
|
|
220
|
+
*/
|
|
221
|
+
export type AnyExtension = Extension<any>;
|
|
222
|
+
/**
|
|
223
|
+
* Whether a value is an `Extension`.
|
|
224
|
+
*
|
|
225
|
+
* @since 0.1.0
|
|
226
|
+
* @category guards
|
|
227
|
+
*/
|
|
228
|
+
export declare const isExtension: (u: unknown) => u is AnyExtension;
|
|
229
|
+
type PluginInputE<P> = P extends ((...args: any) => infer X) ? PluginInputE<X> : P extends ReadonlyArray<infer X> ? PluginInputE<X> : P extends Plugin<any, any, infer E, any> ? E : never;
|
|
230
|
+
type PluginInputR<P> = P extends ((...args: any) => infer X) ? PluginInputR<X> : P extends ReadonlyArray<infer X> ? PluginInputR<X> : P extends Plugin<any, any, any, infer R> ? R : never;
|
|
231
|
+
type CreatorE<C> = C extends ((...args: any) => (...args: any) => Effect.Effect<any, infer E, any>) ? E : never;
|
|
232
|
+
type CreatorR<C> = C extends ((...args: any) => (...args: any) => Effect.Effect<any, any, infer R>) ? R : never;
|
|
233
|
+
/**
|
|
234
|
+
* The command types of a set of command creators: their argument
|
|
235
|
+
* tuples.
|
|
236
|
+
*
|
|
237
|
+
* @since 0.1.0
|
|
238
|
+
* @category type-level
|
|
239
|
+
*/
|
|
240
|
+
export type CommandArgsOf<C extends CommandCreators> = { readonly [K in keyof C]: Parameters<C[K]>; };
|
|
241
|
+
/**
|
|
242
|
+
* The errors of a set of command creators' commands.
|
|
243
|
+
*
|
|
244
|
+
* @since 0.1.0
|
|
245
|
+
* @category type-level
|
|
246
|
+
*/
|
|
247
|
+
export type CommandsError<C extends CommandCreators> = { [K in keyof C]: CreatorE<C[K]>; }[keyof C];
|
|
248
|
+
/**
|
|
249
|
+
* The requirements of a set of command creators' commands.
|
|
250
|
+
*
|
|
251
|
+
* @since 0.1.0
|
|
252
|
+
* @category type-level
|
|
253
|
+
*/
|
|
254
|
+
export type CommandsRequirements<C extends CommandCreators> = { [K in keyof C]: CreatorR<C[K]>; }[keyof C];
|
|
255
|
+
/**
|
|
256
|
+
* The extension types described by an `ExtensionSpec`.
|
|
257
|
+
*
|
|
258
|
+
* @since 0.1.0
|
|
259
|
+
* @category type-level
|
|
260
|
+
*/
|
|
261
|
+
export interface SpecTypes<N extends NodeSpecs, M extends MarkSpecs, P extends ReadonlyArray<PluginInput>, B extends Bindings, C extends CommandCreators> {
|
|
262
|
+
readonly nodes: N;
|
|
263
|
+
readonly marks: M;
|
|
264
|
+
readonly commands: CommandArgsOf<C>;
|
|
265
|
+
readonly E: PluginInputE<P[number]> | BindingsError<B> | CommandsError<C>;
|
|
266
|
+
readonly R: PluginInputR<P[number]> | BindingsRequirements<B> | CommandsRequirements<C>;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Define an extension from node and mark specs, plugins, a keymap and
|
|
270
|
+
* commands. Every field is optional; the types of all of them are
|
|
271
|
+
* recorded in the extension's type.
|
|
272
|
+
*
|
|
273
|
+
* ```ts skip-type-checking
|
|
274
|
+
* const bold = defineExtension({
|
|
275
|
+
* marks: { strong: { parseDOM: [{ tag: "strong" }], toDOM: () => ["strong", 0] } },
|
|
276
|
+
* commands: { toggleBold: () => (state, dispatch) => toggleMark(state.schema.marks.strong!)(state, dispatch) },
|
|
277
|
+
* keymap: { "Mod-b": (state, dispatch) => toggleMark(state.schema.marks.strong!)(state, dispatch) }
|
|
278
|
+
* })
|
|
279
|
+
* ```
|
|
280
|
+
*
|
|
281
|
+
* Throws a `KeymapError` when a key name in `keymap` is invalid.
|
|
282
|
+
*
|
|
283
|
+
* @since 0.1.0
|
|
284
|
+
* @category constructors
|
|
285
|
+
*/
|
|
286
|
+
export declare const defineExtension: <const N extends NodeSpecs = {}, const M extends MarkSpecs = {}, const P extends ReadonlyArray<PluginInput> = readonly [], const B extends Bindings = {}, const C extends CommandCreators = {}>(spec: {
|
|
287
|
+
readonly nodes?: N | undefined;
|
|
288
|
+
readonly marks?: M | undefined;
|
|
289
|
+
readonly plugins?: P | undefined;
|
|
290
|
+
readonly keymap?: B | undefined;
|
|
291
|
+
readonly commands?: C | undefined;
|
|
292
|
+
}) => Extension<SpecTypes<N, M, P, B, C>>;
|
|
293
|
+
/**
|
|
294
|
+
* Merge extensions. The result contributes everything its parts
|
|
295
|
+
* contribute: node, mark and command types are intersected, `E` and `R`
|
|
296
|
+
* are unions. At runtime facets are ordered by priority, then by
|
|
297
|
+
* declaration order (depth first); including the same extension value
|
|
298
|
+
* more than once only uses its first occurrence.
|
|
299
|
+
*
|
|
300
|
+
* @since 0.1.0
|
|
301
|
+
* @category combinators
|
|
302
|
+
*/
|
|
303
|
+
export declare const union: <const Exts extends ReadonlyArray<AnyExtension>>(...extensions: Exts) => Extension<MergeTypes<TypesOf<Exts[number]>>>;
|
|
304
|
+
/**
|
|
305
|
+
* Give an extension (and every part of it that doesn't set its own
|
|
306
|
+
* priority) a priority.
|
|
307
|
+
*
|
|
308
|
+
* @since 0.1.0
|
|
309
|
+
* @category combinators
|
|
310
|
+
*/
|
|
311
|
+
export declare const withPriority: <T extends ExtensionTypes>(extension: Extension<T>, priority: Priority) => Extension<T>;
|
|
312
|
+
/**
|
|
313
|
+
* Define an extension adding a single node spec.
|
|
314
|
+
*
|
|
315
|
+
* @since 0.1.0
|
|
316
|
+
* @category constructors
|
|
317
|
+
*/
|
|
318
|
+
export declare const defineNodeSpec: <const Name extends string, const Spec extends NodeSpec>(name: Name, spec: Spec) => Extension<SpecTypes<{ readonly [K in Name]: Spec; }, {}, readonly [], {}, {}>>;
|
|
319
|
+
/**
|
|
320
|
+
* Define an extension adding a single mark spec.
|
|
321
|
+
*
|
|
322
|
+
* @since 0.1.0
|
|
323
|
+
* @category constructors
|
|
324
|
+
*/
|
|
325
|
+
export declare const defineMarkSpec: <const Name extends string, const Spec extends MarkSpec>(name: Name, spec: Spec) => Extension<SpecTypes<{}, { readonly [K in Name]: Spec; }, readonly [], {}, {}>>;
|
|
326
|
+
/**
|
|
327
|
+
* Define an extension adding plugins: a plugin, an array of plugins, or
|
|
328
|
+
* a function creating them from the editor's schema.
|
|
329
|
+
*
|
|
330
|
+
* @since 0.1.0
|
|
331
|
+
* @category constructors
|
|
332
|
+
*/
|
|
333
|
+
export declare const definePlugin: <const P extends PluginInput>(plugin: P) => Extension<SpecTypes<{}, {}, readonly [P], {}, {}>>;
|
|
334
|
+
/**
|
|
335
|
+
* Define an extension adding a keymap (see `keymap`). Throws a
|
|
336
|
+
* `KeymapError` when a key name is invalid or bound twice.
|
|
337
|
+
*
|
|
338
|
+
* @since 0.1.0
|
|
339
|
+
* @category constructors
|
|
340
|
+
*/
|
|
341
|
+
export declare const defineKeymap: <const B extends Bindings>(bindings: B) => Extension<SpecTypes<{}, {}, readonly [], B, {}>>;
|
|
342
|
+
/**
|
|
343
|
+
* Define an extension adding commands. Each command is given as a
|
|
344
|
+
* function from the command's arguments to a `Command`; the argument
|
|
345
|
+
* types become part of the editor's `commands`, `can()` and `chain()`
|
|
346
|
+
* APIs.
|
|
347
|
+
*
|
|
348
|
+
* ```ts skip-type-checking
|
|
349
|
+
* const ext = defineCommands({
|
|
350
|
+
* insertHello: (times: number) => (state, dispatch) => ...
|
|
351
|
+
* })
|
|
352
|
+
* ```
|
|
353
|
+
*
|
|
354
|
+
* For commands whose argument types depend on the final schema, declare
|
|
355
|
+
* a `CommandSignature` and cast the extension (see `CommandsExtension`).
|
|
356
|
+
*
|
|
357
|
+
* @since 0.1.0
|
|
358
|
+
* @category constructors
|
|
359
|
+
*/
|
|
360
|
+
export declare const defineCommands: <const C extends CommandCreators>(commands: C) => Extension<SpecTypes<{}, {}, readonly [], {}, C>>;
|
|
361
|
+
/**
|
|
362
|
+
* The type of an extension that only contributes commands, described
|
|
363
|
+
* by command types (argument tuples or `CommandSignature`s).
|
|
364
|
+
*
|
|
365
|
+
* @since 0.1.0
|
|
366
|
+
* @category type-level
|
|
367
|
+
*/
|
|
368
|
+
export type CommandsExtension<C extends CommandTypes, E = never, R = never> = Extension<{
|
|
369
|
+
readonly nodes: {};
|
|
370
|
+
readonly marks: {};
|
|
371
|
+
readonly commands: C;
|
|
372
|
+
readonly E: E;
|
|
373
|
+
readonly R: R;
|
|
374
|
+
}>;
|
|
375
|
+
export {};
|