@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,489 @@
|
|
|
1
|
+
import { StateError } from "./errors.js";
|
|
2
|
+
import { Selection } from "./selection.js";
|
|
3
|
+
import { Transaction } from "./transaction.js";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Option from "effect/Option";
|
|
6
|
+
import * as Cause from "effect/Cause";
|
|
7
|
+
import * as Exit from "effect/Exit";
|
|
8
|
+
// src/state/state.ts
|
|
9
|
+
/**
|
|
10
|
+
* The editor state.
|
|
11
|
+
*
|
|
12
|
+
* @since 0.1.0
|
|
13
|
+
*/
|
|
14
|
+
const baseFields = [
|
|
15
|
+
{
|
|
16
|
+
name: "doc",
|
|
17
|
+
self: void 0,
|
|
18
|
+
init(config) {
|
|
19
|
+
if (config.doc) return config.doc;
|
|
20
|
+
const node = config.schema.topNodeType.createAndFillOrNull();
|
|
21
|
+
if (!node) throw new StateError({ message: "Can't create a default document for the schema" });
|
|
22
|
+
return node;
|
|
23
|
+
},
|
|
24
|
+
apply(tr) {
|
|
25
|
+
return tr.doc;
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "selection",
|
|
30
|
+
self: void 0,
|
|
31
|
+
init(config, instance) {
|
|
32
|
+
return config.selection || Selection.atStart(instance.doc);
|
|
33
|
+
},
|
|
34
|
+
apply(tr) {
|
|
35
|
+
return tr.selection;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "storedMarks",
|
|
40
|
+
self: void 0,
|
|
41
|
+
init(config) {
|
|
42
|
+
return config.storedMarks || null;
|
|
43
|
+
},
|
|
44
|
+
apply(tr, _marks, _old, state) {
|
|
45
|
+
return state.selection.$cursorOrNull ? tr.storedMarksOrNull : null;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "scrollToSelection",
|
|
50
|
+
self: void 0,
|
|
51
|
+
init() {
|
|
52
|
+
return 0;
|
|
53
|
+
},
|
|
54
|
+
apply(tr, prev) {
|
|
55
|
+
return tr.scrolledIntoView ? prev + 1 : prev;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
];
|
|
59
|
+
/**
|
|
60
|
+
* Object wrapping the part of a state object that stays the same across
|
|
61
|
+
* transactions. Stored in the state's `config` property.
|
|
62
|
+
*
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
var Configuration = class {
|
|
66
|
+
constructor(schema, plugins) {
|
|
67
|
+
this.schema = schema;
|
|
68
|
+
this.plugins = [];
|
|
69
|
+
this.pluginsByKey = Object.create(null);
|
|
70
|
+
this.fields = baseFields.slice();
|
|
71
|
+
if (plugins) plugins.forEach((plugin) => {
|
|
72
|
+
if (this.pluginsByKey[plugin.key]) throw new StateError({ message: "Adding different instances of a keyed plugin (" + plugin.key + ")" });
|
|
73
|
+
this.plugins.push(plugin);
|
|
74
|
+
this.pluginsByKey[plugin.key] = plugin;
|
|
75
|
+
const state = plugin.spec.state;
|
|
76
|
+
if (state) this.fields.push({
|
|
77
|
+
name: plugin.key,
|
|
78
|
+
self: plugin,
|
|
79
|
+
init: state.init,
|
|
80
|
+
apply: state.apply
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const isStateError = (u) => u instanceof StateError;
|
|
86
|
+
function startCreate(config) {
|
|
87
|
+
const schema = config.doc ? config.doc.type.schema : config.schema;
|
|
88
|
+
if (!schema) throw new StateError({ message: "Either `doc` or `schema` must be provided" });
|
|
89
|
+
const instance = new EditorState(new Configuration(schema, config.plugins));
|
|
90
|
+
return {
|
|
91
|
+
instance,
|
|
92
|
+
pending: instance.config.fields.findIndex((field) => Effect.isEffect(instance.slots[field.name] = field.init.call(field.self, config, instance)))
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const finishCreate = (config, instance, pending) => Effect.gen(function* () {
|
|
96
|
+
for (const [i, field] of instance.config.fields.entries()) {
|
|
97
|
+
if (i < pending) continue;
|
|
98
|
+
const result = i === pending ? instance.slots[field.name] : yield* trySync(() => field.init.call(field.self, config, instance));
|
|
99
|
+
instance.slots[field.name] = Effect.isEffect(result) ? yield* result : result;
|
|
100
|
+
}
|
|
101
|
+
return instance;
|
|
102
|
+
});
|
|
103
|
+
/** Marker returned by the synchronous fast path when a hook returned an Effect. */
|
|
104
|
+
const NeedsEffect = Symbol("effect-richtext/state/NeedsEffect");
|
|
105
|
+
/** Lift a synchronous computation that may throw a `StateError` into an Effect. */
|
|
106
|
+
const trySync = (f) => Effect.suspend(() => {
|
|
107
|
+
try {
|
|
108
|
+
return Effect.succeed(f());
|
|
109
|
+
} catch (e) {
|
|
110
|
+
return isStateError(e) ? Effect.fail(e) : Effect.die(e);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
/**
|
|
114
|
+
* Run an effect synchronously, throwing its (first) failure or defect.
|
|
115
|
+
*
|
|
116
|
+
* @internal
|
|
117
|
+
*/
|
|
118
|
+
const runSyncUnsafe = (effect) => {
|
|
119
|
+
const exit = Effect.runSyncExit(effect);
|
|
120
|
+
if (Exit.isSuccess(exit)) return exit.value;
|
|
121
|
+
throw Cause.squash(exit.cause);
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* @since 0.1.0
|
|
125
|
+
* @category symbols
|
|
126
|
+
*/
|
|
127
|
+
const EditorStateTypeId = Symbol.for("effect-richtext/state/EditorState");
|
|
128
|
+
/**
|
|
129
|
+
* The state of an editor is represented by an object of this type. A
|
|
130
|
+
* state is a persistent data structure—it isn't updated, but rather a
|
|
131
|
+
* new state value is computed from an old one using the `apply` method.
|
|
132
|
+
*
|
|
133
|
+
* A state holds a number of built-in fields, and plugins can define
|
|
134
|
+
* additional fields.
|
|
135
|
+
*
|
|
136
|
+
* `R` and `E` are the unions of the requirements and errors of the
|
|
137
|
+
* state's plugins: applying a transaction runs the plugins' hooks, so
|
|
138
|
+
* `apply` returns an `Effect<EditorState<Sc, R, E>, E | StateError, R>`.
|
|
139
|
+
* States whose plugins require no services (`R = never`) can also be
|
|
140
|
+
* updated synchronously with `applyUnsafe`.
|
|
141
|
+
*
|
|
142
|
+
* @since 0.1.0
|
|
143
|
+
* @category models
|
|
144
|
+
*/
|
|
145
|
+
var EditorState = class EditorState {
|
|
146
|
+
/** @internal */
|
|
147
|
+
constructor(config, slots = Object.create(null)) {
|
|
148
|
+
this.config = config;
|
|
149
|
+
this.slots = slots;
|
|
150
|
+
}
|
|
151
|
+
/** The current document. */
|
|
152
|
+
get doc() {
|
|
153
|
+
return this.slots.doc;
|
|
154
|
+
}
|
|
155
|
+
/** The selection. */
|
|
156
|
+
get selection() {
|
|
157
|
+
return this.slots.selection;
|
|
158
|
+
}
|
|
159
|
+
/** A set of marks to apply to the next input. `None` when no explicit marks have been set. */
|
|
160
|
+
get storedMarks() {
|
|
161
|
+
return Option.fromNullishOr(this.storedMarksOrNull);
|
|
162
|
+
}
|
|
163
|
+
/** @internal */
|
|
164
|
+
get storedMarksOrNull() {
|
|
165
|
+
return this.slots.storedMarks;
|
|
166
|
+
}
|
|
167
|
+
/** @internal */
|
|
168
|
+
get scrollToSelection() {
|
|
169
|
+
return this.slots.scrollToSelection;
|
|
170
|
+
}
|
|
171
|
+
/** The schema of the state's document. */
|
|
172
|
+
get schema() {
|
|
173
|
+
return this.config.schema;
|
|
174
|
+
}
|
|
175
|
+
/** The plugins that are active in this state. */
|
|
176
|
+
get plugins() {
|
|
177
|
+
return this.config.plugins;
|
|
178
|
+
}
|
|
179
|
+
/** Create a new transaction from this state. */
|
|
180
|
+
get tr() {
|
|
181
|
+
return new Transaction(this);
|
|
182
|
+
}
|
|
183
|
+
/** Apply the given transaction to produce a new state. */
|
|
184
|
+
apply(tr) {
|
|
185
|
+
return Effect.map(this.applyTransaction(tr), (r) => r.state);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Like `apply`, but runs synchronously, throwing failures. Only
|
|
189
|
+
* available for states whose plugins require no services; fails with
|
|
190
|
+
* a defect (thrown) when a plugin hook is asynchronous.
|
|
191
|
+
*/
|
|
192
|
+
applyUnsafe(tr) {
|
|
193
|
+
return this.applyTransactionUnsafe(tr).state;
|
|
194
|
+
}
|
|
195
|
+
/** @internal */
|
|
196
|
+
filterTransaction(tr, ignore = -1) {
|
|
197
|
+
const plugins = this.config.plugins;
|
|
198
|
+
const self = this;
|
|
199
|
+
return Effect.gen(function* () {
|
|
200
|
+
for (let i = 0; i < plugins.length; i++) {
|
|
201
|
+
if (i === ignore) continue;
|
|
202
|
+
const plugin = plugins[i], filter = plugin.spec.filterTransaction;
|
|
203
|
+
if (filter) {
|
|
204
|
+
const result = filter.call(plugin, tr, self);
|
|
205
|
+
if (!(Effect.isEffect(result) ? yield* result : result)) return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return true;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Verbose variant of `apply` that returns the precise transactions
|
|
213
|
+
* that were applied (which might be influenced by the transaction
|
|
214
|
+
* hooks of plugins) along with the new state.
|
|
215
|
+
*/
|
|
216
|
+
applyTransaction(rootTr) {
|
|
217
|
+
return Effect.suspend(() => {
|
|
218
|
+
let result;
|
|
219
|
+
try {
|
|
220
|
+
result = this.applyTransactionSync(rootTr);
|
|
221
|
+
} catch (e) {
|
|
222
|
+
return isStateError(e) ? Effect.fail(e) : Effect.die(e);
|
|
223
|
+
}
|
|
224
|
+
return result === NeedsEffect ? this.applyTransactionEffect(rootTr) : Effect.succeed(result);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/** @internal */
|
|
228
|
+
applyTransactionSync(rootTr) {
|
|
229
|
+
const filtered = this.filterTransactionSync(rootTr);
|
|
230
|
+
if (filtered === NeedsEffect) return NeedsEffect;
|
|
231
|
+
if (!filtered) return {
|
|
232
|
+
state: this,
|
|
233
|
+
transactions: []
|
|
234
|
+
};
|
|
235
|
+
const trs = [rootTr];
|
|
236
|
+
let newState = this.applyInnerSync(rootTr);
|
|
237
|
+
if (newState === NeedsEffect) return NeedsEffect;
|
|
238
|
+
let seen = null;
|
|
239
|
+
const plugins = this.config.plugins;
|
|
240
|
+
for (;;) {
|
|
241
|
+
let haveNew = false;
|
|
242
|
+
for (let i = 0; i < plugins.length; i++) {
|
|
243
|
+
const plugin = plugins[i], append = plugin.spec.appendTransaction;
|
|
244
|
+
if (append) {
|
|
245
|
+
const n = seen ? seen[i].n : 0, oldState = seen ? seen[i].state : this;
|
|
246
|
+
let tr = void 0;
|
|
247
|
+
if (n < trs.length) {
|
|
248
|
+
const result = append.call(plugin, n ? trs.slice(n) : trs, oldState, newState);
|
|
249
|
+
if (Effect.isEffect(result)) return NeedsEffect;
|
|
250
|
+
tr = result;
|
|
251
|
+
}
|
|
252
|
+
if (tr) {
|
|
253
|
+
const ok = newState.filterTransactionSync(tr, i);
|
|
254
|
+
if (ok === NeedsEffect) return NeedsEffect;
|
|
255
|
+
if (ok) {
|
|
256
|
+
tr.setMeta("appendedTransaction", rootTr);
|
|
257
|
+
if (!seen) {
|
|
258
|
+
seen = [];
|
|
259
|
+
for (let j = 0; j < plugins.length; j++) seen.push(j < i ? {
|
|
260
|
+
state: newState,
|
|
261
|
+
n: trs.length
|
|
262
|
+
} : {
|
|
263
|
+
state: this,
|
|
264
|
+
n: 0
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
trs.push(tr);
|
|
268
|
+
const next = newState.applyInnerSync(tr);
|
|
269
|
+
if (next === NeedsEffect) return NeedsEffect;
|
|
270
|
+
newState = next;
|
|
271
|
+
haveNew = true;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (seen) seen[i] = {
|
|
275
|
+
state: newState,
|
|
276
|
+
n: trs.length
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (!haveNew) return {
|
|
281
|
+
state: newState,
|
|
282
|
+
transactions: trs
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/** @internal */
|
|
287
|
+
filterTransactionSync(tr, ignore = -1) {
|
|
288
|
+
const plugins = this.config.plugins;
|
|
289
|
+
for (let i = 0; i < plugins.length; i++) {
|
|
290
|
+
if (i === ignore) continue;
|
|
291
|
+
const plugin = plugins[i], filter = plugin.spec.filterTransaction;
|
|
292
|
+
if (filter) {
|
|
293
|
+
const result = filter.call(plugin, tr, this);
|
|
294
|
+
if (Effect.isEffect(result)) return NeedsEffect;
|
|
295
|
+
if (!result) return false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
/** @internal */
|
|
301
|
+
applyInnerSync(tr) {
|
|
302
|
+
if (!tr.before.eq(this.doc)) throw new StateError({ message: "Applying a mismatched transaction" });
|
|
303
|
+
const newInstance = new EditorState(this.config), fields = this.config.fields;
|
|
304
|
+
for (let i = 0; i < fields.length; i++) {
|
|
305
|
+
const field = fields[i];
|
|
306
|
+
const result = field.apply.call(field.self, tr, this.slots[field.name], this, newInstance);
|
|
307
|
+
if (Effect.isEffect(result)) return NeedsEffect;
|
|
308
|
+
newInstance.slots[field.name] = result;
|
|
309
|
+
}
|
|
310
|
+
return newInstance;
|
|
311
|
+
}
|
|
312
|
+
/** @internal */
|
|
313
|
+
applyTransactionEffect(rootTr) {
|
|
314
|
+
const self = this;
|
|
315
|
+
return Effect.gen(function* () {
|
|
316
|
+
if (!(yield* self.filterTransaction(rootTr))) return {
|
|
317
|
+
state: self,
|
|
318
|
+
transactions: []
|
|
319
|
+
};
|
|
320
|
+
const trs = [rootTr];
|
|
321
|
+
let newState = yield* self.applyInner(rootTr);
|
|
322
|
+
let seen = null;
|
|
323
|
+
const plugins = self.config.plugins;
|
|
324
|
+
for (;;) {
|
|
325
|
+
let haveNew = false;
|
|
326
|
+
for (let i = 0; i < plugins.length; i++) {
|
|
327
|
+
const plugin = plugins[i], append = plugin.spec.appendTransaction;
|
|
328
|
+
if (append) {
|
|
329
|
+
const n = seen ? seen[i].n : 0, oldState = seen ? seen[i].state : self;
|
|
330
|
+
let tr = void 0;
|
|
331
|
+
if (n < trs.length) {
|
|
332
|
+
const result = append.call(plugin, n ? trs.slice(n) : trs, oldState, newState);
|
|
333
|
+
tr = Effect.isEffect(result) ? yield* result : result;
|
|
334
|
+
}
|
|
335
|
+
if (tr && (yield* newState.filterTransaction(tr, i))) {
|
|
336
|
+
tr.setMeta("appendedTransaction", rootTr);
|
|
337
|
+
if (!seen) {
|
|
338
|
+
seen = [];
|
|
339
|
+
for (let j = 0; j < plugins.length; j++) seen.push(j < i ? {
|
|
340
|
+
state: newState,
|
|
341
|
+
n: trs.length
|
|
342
|
+
} : {
|
|
343
|
+
state: self,
|
|
344
|
+
n: 0
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
trs.push(tr);
|
|
348
|
+
newState = yield* newState.applyInner(tr);
|
|
349
|
+
haveNew = true;
|
|
350
|
+
}
|
|
351
|
+
if (seen) seen[i] = {
|
|
352
|
+
state: newState,
|
|
353
|
+
n: trs.length
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (!haveNew) return {
|
|
358
|
+
state: newState,
|
|
359
|
+
transactions: trs
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
/** Like `applyTransaction`, but runs synchronously (see `applyUnsafe`). */
|
|
365
|
+
applyTransactionUnsafe(rootTr) {
|
|
366
|
+
const result = this.applyTransactionSync(rootTr);
|
|
367
|
+
return result === NeedsEffect ? runSyncUnsafe(this.applyTransactionEffect(rootTr)) : result;
|
|
368
|
+
}
|
|
369
|
+
/** @internal */
|
|
370
|
+
applyInner(tr) {
|
|
371
|
+
const self = this;
|
|
372
|
+
return Effect.gen(function* () {
|
|
373
|
+
if (!tr.before.eq(self.doc)) return yield* Effect.fail(new StateError({ message: "Applying a mismatched transaction" }));
|
|
374
|
+
const newInstance = new EditorState(self.config), fields = self.config.fields;
|
|
375
|
+
for (let i = 0; i < fields.length; i++) {
|
|
376
|
+
const field = fields[i];
|
|
377
|
+
const result = field.apply.call(field.self, tr, self.slots[field.name], self, newInstance);
|
|
378
|
+
newInstance.slots[field.name] = Effect.isEffect(result) ? yield* result : result;
|
|
379
|
+
}
|
|
380
|
+
return newInstance;
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Create a new state based on this one, but with an adjusted set of
|
|
385
|
+
* active plugins. State fields that exist in both sets of plugins are
|
|
386
|
+
* kept unchanged. Those that no longer exist are dropped, and those
|
|
387
|
+
* that are new are initialized using their `init` method, passing in
|
|
388
|
+
* the new configuration object.
|
|
389
|
+
*/
|
|
390
|
+
reconfigure(config) {
|
|
391
|
+
const self = this;
|
|
392
|
+
return Effect.gen(function* () {
|
|
393
|
+
const $config = yield* trySync(() => new Configuration(self.schema, config.plugins));
|
|
394
|
+
const fields = $config.fields, instance = new EditorState($config);
|
|
395
|
+
for (let i = 0; i < fields.length; i++) {
|
|
396
|
+
const field = fields[i], name = field.name;
|
|
397
|
+
if (Object.prototype.hasOwnProperty.call(self.slots, name)) instance.slots[name] = self.slots[name];
|
|
398
|
+
else {
|
|
399
|
+
const result = field.init.call(field.self, config, instance);
|
|
400
|
+
instance.slots[name] = Effect.isEffect(result) ? yield* result : result;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return instance;
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
/** Like `reconfigure`, but runs synchronously (see `applyUnsafe`). */
|
|
407
|
+
reconfigureUnsafe(config) {
|
|
408
|
+
return runSyncUnsafe(this.reconfigure(config));
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Serialize this state to JSON. If you want to serialize the state of
|
|
412
|
+
* plugins, pass an object mapping property names to use in the
|
|
413
|
+
* resulting JSON object to plugin objects. The argument may also be a
|
|
414
|
+
* string or number, in which case it is ignored, to support the way
|
|
415
|
+
* `JSON.stringify` calls `toJSON` methods. Throws a `StateError` when
|
|
416
|
+
* a plugin is mapped to the reserved `doc` or `selection` fields.
|
|
417
|
+
*/
|
|
418
|
+
toJSON(pluginFields) {
|
|
419
|
+
const result = {
|
|
420
|
+
doc: this.doc.toJSON(),
|
|
421
|
+
selection: this.selection.toJSON()
|
|
422
|
+
};
|
|
423
|
+
if (this.storedMarksOrNull) result.storedMarks = this.storedMarksOrNull.map((m) => m.toJSON());
|
|
424
|
+
if (pluginFields && typeof pluginFields === "object") for (const prop in pluginFields) {
|
|
425
|
+
if (prop === "doc" || prop === "selection") throw new StateError({ message: "The JSON fields `doc` and `selection` are reserved" });
|
|
426
|
+
const plugin = pluginFields[prop], state = plugin.spec.state;
|
|
427
|
+
if (state && state.toJSON) result[prop] = state.toJSON.call(plugin, this.slots[plugin.key]);
|
|
428
|
+
}
|
|
429
|
+
return result;
|
|
430
|
+
}
|
|
431
|
+
/** Create a new state. */
|
|
432
|
+
static create(config) {
|
|
433
|
+
return Effect.flatMap(trySync(() => startCreate(config)), ({ instance, pending }) => pending < 0 ? Effect.succeed(instance) : finishCreate(config, instance, pending));
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Like `create`, but runs synchronously. Only available when the
|
|
437
|
+
* plugins require no services; throws on failure.
|
|
438
|
+
*/
|
|
439
|
+
static createUnsafe(config) {
|
|
440
|
+
const { instance, pending } = startCreate(config);
|
|
441
|
+
return pending < 0 ? instance : runSyncUnsafe(finishCreate(config, instance, pending));
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Deserialize a JSON representation of a state. `config` should have
|
|
445
|
+
* at least a `schema` field, and should contain an array of plugins to
|
|
446
|
+
* initialize the state with. `pluginFields` can be used to deserialize
|
|
447
|
+
* the state of plugins, by associating plugin instances with the
|
|
448
|
+
* property names they use in the JSON object.
|
|
449
|
+
*/
|
|
450
|
+
static fromJSON(config, json, pluginFields) {
|
|
451
|
+
return Effect.gen(function* () {
|
|
452
|
+
const data = json;
|
|
453
|
+
if (!data || typeof data !== "object") return yield* Effect.fail(new StateError({ message: "Invalid input for EditorState.fromJSON" }));
|
|
454
|
+
if (!config.schema) return yield* Effect.fail(new StateError({ message: "Required config field 'schema' missing" }));
|
|
455
|
+
const $config = yield* trySync(() => new Configuration(config.schema, config.plugins));
|
|
456
|
+
const instance = new EditorState($config);
|
|
457
|
+
for (const field of $config.fields) if (field.name === "doc") instance.slots.doc = yield* config.schema.nodeFromJSON(data.doc);
|
|
458
|
+
else if (field.name === "selection") instance.slots.selection = yield* Selection.fromJSON(instance.doc, data.selection);
|
|
459
|
+
else if (field.name === "storedMarks") {
|
|
460
|
+
if (data.storedMarks) {
|
|
461
|
+
const marks = [];
|
|
462
|
+
for (const m of data.storedMarks) marks.push(yield* config.schema.markFromJSON(m));
|
|
463
|
+
instance.slots.storedMarks = marks;
|
|
464
|
+
} else instance.slots.storedMarks = null;
|
|
465
|
+
} else {
|
|
466
|
+
let done = false;
|
|
467
|
+
if (pluginFields) for (const prop in pluginFields) {
|
|
468
|
+
const plugin = pluginFields[prop], state = plugin.spec.state;
|
|
469
|
+
if (plugin.key === field.name && state && state.fromJSON && Object.prototype.hasOwnProperty.call(data, prop)) {
|
|
470
|
+
const result = state.fromJSON.call(plugin, config, data[prop], instance);
|
|
471
|
+
instance.slots[field.name] = Effect.isEffect(result) ? yield* result : result;
|
|
472
|
+
done = true;
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (!done) {
|
|
477
|
+
const result = field.init.call(field.self, config, instance);
|
|
478
|
+
instance.slots[field.name] = Effect.isEffect(result) ? yield* result : result;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return instance;
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
/** Like `fromJSON`, but runs synchronously (see `createUnsafe`). */
|
|
485
|
+
static fromJSONUnsafe(config, json, pluginFields) {
|
|
486
|
+
return runSyncUnsafe(EditorState.fromJSON(config, json, pluginFields));
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
export { Configuration, EditorState, EditorStateTypeId, runSyncUnsafe };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Slice } from "../model/replace.js";
|
|
2
|
+
import { AnySchema, MarkType } from "../model/schema.js";
|
|
3
|
+
import { Mark } from "../model/mark.js";
|
|
4
|
+
import { Node } from "../model/node.js";
|
|
5
|
+
import "../model.js";
|
|
6
|
+
import "../transform.js";
|
|
7
|
+
import { Step } from "../transform/step.js";
|
|
8
|
+
import { ReplaceFailure, Transform } from "../transform/transform.js";
|
|
9
|
+
import { SelectionError } from "./errors.js";
|
|
10
|
+
import { EditorViewLike, Plugin, PluginKey } from "./plugin.js";
|
|
11
|
+
import { Selection } from "./selection.js";
|
|
12
|
+
import { EditorState } from "./state.js";
|
|
13
|
+
import * as Effect from "effect/Effect";
|
|
14
|
+
import * as Option from "effect/Option";
|
|
15
|
+
/**
|
|
16
|
+
* A function that applies a transaction, as passed to commands.
|
|
17
|
+
*
|
|
18
|
+
* @since 0.1.0
|
|
19
|
+
* @category models
|
|
20
|
+
*/
|
|
21
|
+
export type Dispatch<Sc extends AnySchema = AnySchema> = (tr: Transaction<Sc>) => Effect.Effect<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Commands are functions that take a state and an optional transaction
|
|
24
|
+
* dispatch function and...
|
|
25
|
+
*
|
|
26
|
+
* - determine whether they apply to this state
|
|
27
|
+
* - if not, succeed with `false`
|
|
28
|
+
* - if `dispatch` was passed, perform their effect, possibly by passing
|
|
29
|
+
* a transaction to `dispatch`
|
|
30
|
+
* - succeed with `true`
|
|
31
|
+
*
|
|
32
|
+
* In some cases, the editor view is passed as a third argument.
|
|
33
|
+
* Commands carry the errors (`E`) and requirements (`R`) of the effects
|
|
34
|
+
* they run.
|
|
35
|
+
*
|
|
36
|
+
* @since 0.1.0
|
|
37
|
+
* @category models
|
|
38
|
+
*/
|
|
39
|
+
export type Command<Sc extends AnySchema = AnySchema, E = never, R = never> = (state: EditorState<Sc, any, any>, dispatch?: Dispatch<Sc>, view?: EditorViewLike) => Effect.Effect<boolean, E, R>;
|
|
40
|
+
/**
|
|
41
|
+
* Keys under which transaction metadata can be stored.
|
|
42
|
+
*
|
|
43
|
+
* @since 0.1.0
|
|
44
|
+
* @category models
|
|
45
|
+
*/
|
|
46
|
+
export type MetaKey = string | Plugin<any, any, any, any> | PluginKey<any>;
|
|
47
|
+
/**
|
|
48
|
+
* An editor state transaction, which can be applied to a state to
|
|
49
|
+
* create an updated state. Use `EditorState.tr` to create an instance.
|
|
50
|
+
*
|
|
51
|
+
* Transactions track changes to the document (they are a subclass of
|
|
52
|
+
* `Transform`), but also other state changes, like selection updates and
|
|
53
|
+
* adjustments of the set of stored marks. In addition, you can store
|
|
54
|
+
* metadata properties in a transaction, which are extra pieces of
|
|
55
|
+
* information that client code or plugins can use to describe what a
|
|
56
|
+
* transaction represents, so that they can update their own state
|
|
57
|
+
* accordingly.
|
|
58
|
+
*
|
|
59
|
+
* Like transforms, transactions are mutable builders.
|
|
60
|
+
*
|
|
61
|
+
* @since 0.1.0
|
|
62
|
+
* @category models
|
|
63
|
+
*/
|
|
64
|
+
export declare class Transaction<Sc extends AnySchema = AnySchema> extends Transform<Sc> {
|
|
65
|
+
/** The timestamp associated with this transaction, in the same format as `Date.now()`. */
|
|
66
|
+
time: number;
|
|
67
|
+
private curSelection;
|
|
68
|
+
private curSelectionFor;
|
|
69
|
+
private updated;
|
|
70
|
+
private readonly meta;
|
|
71
|
+
/** @internal */
|
|
72
|
+
storedMarksOrNull: ReadonlyArray<Mark<Sc>> | null;
|
|
73
|
+
/** @internal */
|
|
74
|
+
constructor(state: EditorState<Sc, any, any>);
|
|
75
|
+
/** The stored marks set by this transaction, if any. */
|
|
76
|
+
get storedMarks(): Option.Option<ReadonlyArray<Mark<Sc>>>;
|
|
77
|
+
/**
|
|
78
|
+
* The transaction's current selection. This defaults to the editor
|
|
79
|
+
* selection mapped through the steps in the transaction, but can be
|
|
80
|
+
* overwritten with `setSelection`.
|
|
81
|
+
*/
|
|
82
|
+
get selection(): Selection<Sc>;
|
|
83
|
+
/**
|
|
84
|
+
* Update the transaction's current selection. Will determine the
|
|
85
|
+
* selection that the editor gets when the transaction is applied.
|
|
86
|
+
* Fails with a `SelectionError` when the selection doesn't point into
|
|
87
|
+
* the transaction's current document.
|
|
88
|
+
*/
|
|
89
|
+
setSelection(selection: Selection<Sc>): Effect.Effect<this, SelectionError>;
|
|
90
|
+
/** Like `setSelection`, but throws a `SelectionError` on failure. */
|
|
91
|
+
setSelectionUnsafe(selection: Selection<Sc>): this;
|
|
92
|
+
/** Whether the selection was explicitly updated by this transaction. */
|
|
93
|
+
get selectionSet(): boolean;
|
|
94
|
+
/** Set the current stored marks. */
|
|
95
|
+
setStoredMarks(marks: ReadonlyArray<Mark<Sc>> | null): this;
|
|
96
|
+
/**
|
|
97
|
+
* Make sure the current stored marks or, if that is null, the marks at
|
|
98
|
+
* the selection, match the given set of marks. Does nothing if this is
|
|
99
|
+
* already the case.
|
|
100
|
+
*/
|
|
101
|
+
ensureMarks(marks: ReadonlyArray<Mark<Sc>>): this;
|
|
102
|
+
/** Add a mark to the set of stored marks. */
|
|
103
|
+
addStoredMark(mark: Mark<Sc>): this;
|
|
104
|
+
/** Remove a mark or mark type from the set of stored marks. */
|
|
105
|
+
removeStoredMark(mark: Mark<Sc> | MarkType<Sc>): this;
|
|
106
|
+
/** Whether the stored marks were explicitly set for this transaction. */
|
|
107
|
+
get storedMarksSet(): boolean;
|
|
108
|
+
/** @internal */
|
|
109
|
+
addStep(step: Step<Sc>, doc: Node<Sc>): void;
|
|
110
|
+
/** Update the timestamp for the transaction. */
|
|
111
|
+
setTime(time: number): this;
|
|
112
|
+
/** Replace the current selection with the given slice. */
|
|
113
|
+
replaceSelection(slice: Slice<Sc>): this;
|
|
114
|
+
/**
|
|
115
|
+
* Replace the selection with the given node. When `inheritMarks` is
|
|
116
|
+
* true and the content is inline, it inherits the marks from the place
|
|
117
|
+
* where it is inserted.
|
|
118
|
+
*/
|
|
119
|
+
replaceSelectionWith(node: Node<Sc>, inheritMarks?: boolean): this;
|
|
120
|
+
/** Delete the selection. */
|
|
121
|
+
deleteSelection(): this;
|
|
122
|
+
/**
|
|
123
|
+
* Replace the given range, or the selection if no range is given,
|
|
124
|
+
* with a text node containing the given string.
|
|
125
|
+
*/
|
|
126
|
+
insertText(text: string, from?: number, to?: number): Effect.Effect<this, ReplaceFailure>;
|
|
127
|
+
/** Like `insertText`, but throws on failure (for example for positions outside of the document). */
|
|
128
|
+
insertTextUnsafe(text: string, from?: number, to?: number): this;
|
|
129
|
+
/** Store a metadata property in this transaction, keyed either by name or by plugin. */
|
|
130
|
+
setMeta(key: MetaKey, value: unknown): this;
|
|
131
|
+
/** Retrieve a metadata property for a given name or plugin, if it was set. */
|
|
132
|
+
getMeta<A = unknown>(key: MetaKey): Option.Option<A>;
|
|
133
|
+
/**
|
|
134
|
+
* Returns true if this transaction doesn't contain any metadata, and
|
|
135
|
+
* can thus safely be extended.
|
|
136
|
+
*/
|
|
137
|
+
get isGeneric(): boolean;
|
|
138
|
+
/**
|
|
139
|
+
* Indicate that the editor should scroll the selection into view when
|
|
140
|
+
* updated to the state produced by this transaction.
|
|
141
|
+
*/
|
|
142
|
+
scrollIntoView(): this;
|
|
143
|
+
/** True when this transaction has had `scrollIntoView` called on it. */
|
|
144
|
+
get scrolledIntoView(): boolean;
|
|
145
|
+
}
|