@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,623 @@
|
|
|
1
|
+
import { NodeSelection } from "../state/selection.js";
|
|
2
|
+
import { chrome, ie, safari } from "./browser.js";
|
|
3
|
+
import { parseFromClipboard, serializeForClipboard } from "./clipboard.js";
|
|
4
|
+
import { Decoration, viewDecorations } from "./decoration.js";
|
|
5
|
+
import { clearReusedRange, deepActiveElement } from "./dom.js";
|
|
6
|
+
import { readDOMChange } from "./domchange.js";
|
|
7
|
+
import { coordsAtPos, endOfTextblock, focusPreventScroll, posAtCoords, resetScrollPos, scrollRectIntoView, storeScrollPos } from "./domcoords.js";
|
|
8
|
+
import { DOMObserver, safariShadowSelectionRange } from "./domobserver.js";
|
|
9
|
+
import { ViewError } from "./errors.js";
|
|
10
|
+
import { Dragging, InputState, clearComposition, destroyInput, dispatchEvent, doPaste, endComposition, ensureListeners, findCompositionNode, initInput } from "./input.js";
|
|
11
|
+
import { anchorInRightPlace, selectionToDOM, syncNodeSelection } from "./selection.js";
|
|
12
|
+
import { docViewDesc } from "./viewdesc.js";
|
|
13
|
+
import * as Effect from "effect/Effect";
|
|
14
|
+
import * as Option from "effect/Option";
|
|
15
|
+
import * as Cause from "effect/Cause";
|
|
16
|
+
import * as Exit from "effect/Exit";
|
|
17
|
+
import * as Context from "effect/Context";
|
|
18
|
+
import { PreventSchedulerYield } from "effect/Scheduler";
|
|
19
|
+
// src/view/view.ts
|
|
20
|
+
/**
|
|
21
|
+
* The editor view.
|
|
22
|
+
*
|
|
23
|
+
* @since 0.1.0
|
|
24
|
+
*/
|
|
25
|
+
/** @internal */
|
|
26
|
+
const __parseFromClipboard = parseFromClipboard;
|
|
27
|
+
/** @internal */
|
|
28
|
+
const __endComposition = endComposition;
|
|
29
|
+
/**
|
|
30
|
+
* @since 0.1.0
|
|
31
|
+
* @category symbols
|
|
32
|
+
*/
|
|
33
|
+
const EditorViewTypeId = Symbol.for("effect-richtext/view/EditorView");
|
|
34
|
+
/**
|
|
35
|
+
* An editor view manages the DOM structure that represents an editable
|
|
36
|
+
* document. Its state and behavior are determined by its props.
|
|
37
|
+
*
|
|
38
|
+
* `R` and `E` are the requirements and errors of the effects the view
|
|
39
|
+
* runs: those of its state's plugins and of its effectful props. Create
|
|
40
|
+
* a view with `EditorView.make`, inside an Effect that provides `R` and a
|
|
41
|
+
* `Scope`: the view captures the current context to run handler effects
|
|
42
|
+
* later, and is destroyed when the scope closes.
|
|
43
|
+
*
|
|
44
|
+
* @since 0.1.0
|
|
45
|
+
* @category models
|
|
46
|
+
*/
|
|
47
|
+
var EditorView = class EditorView {
|
|
48
|
+
/** @internal */
|
|
49
|
+
constructor(place, props, context) {
|
|
50
|
+
this._root = null;
|
|
51
|
+
this.focused = false;
|
|
52
|
+
this.trackWrites = null;
|
|
53
|
+
this.mounted = false;
|
|
54
|
+
this.markCursor = null;
|
|
55
|
+
this.cursorWrapper = null;
|
|
56
|
+
this.lastSelectedViewDesc = void 0;
|
|
57
|
+
this.input = new InputState();
|
|
58
|
+
this.prevDirectPlugins = [];
|
|
59
|
+
this.pluginViews = [];
|
|
60
|
+
this.listeners = [];
|
|
61
|
+
this.dispatching = null;
|
|
62
|
+
this.requiresGeckoHackNode = false;
|
|
63
|
+
this.dragging = null;
|
|
64
|
+
this.dispatch = (tr) => Effect.catchCause(this.tryDispatch(tr), (cause) => Effect.sync(() => this.reportError(cause)));
|
|
65
|
+
this.dispatchUnsafe = (tr) => {
|
|
66
|
+
const fiber = Effect.runForkWith(this.runContext)(this.tryDispatch(tr));
|
|
67
|
+
const exit = fiber.pollUnsafe();
|
|
68
|
+
if (exit) {
|
|
69
|
+
if (Exit.isFailure(exit)) throw Cause.squash(exit.cause);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
fiber.addObserver((exit) => {
|
|
73
|
+
if (Exit.isFailure(exit)) this.reportError(exit.cause);
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
this.context = context;
|
|
77
|
+
this.runContext = Context.add(context, PreventSchedulerYield, true);
|
|
78
|
+
this._props = props;
|
|
79
|
+
this.state = props.state;
|
|
80
|
+
this.directPlugins = props.plugins || [];
|
|
81
|
+
this.directPlugins.forEach(checkStateComponent);
|
|
82
|
+
this.dom = place && place.mount || document.createElement("div");
|
|
83
|
+
if (place) {
|
|
84
|
+
if (place.appendChild) place.appendChild(this.dom);
|
|
85
|
+
else if (typeof place == "function") place(this.dom);
|
|
86
|
+
else if (place.mount) this.mounted = true;
|
|
87
|
+
}
|
|
88
|
+
this.editable = getEditable(this);
|
|
89
|
+
updateCursorWrapper(this);
|
|
90
|
+
this.nodeViews = buildNodeViews(this);
|
|
91
|
+
this.docView = docViewDesc(this.state.doc, computeDocDeco(this), viewDecorations(this), this.dom, this);
|
|
92
|
+
this.domObserver = new DOMObserver(this, (from, to, typeOver, added) => readDOMChange(this, from, to, typeOver, added));
|
|
93
|
+
this.domObserver.start();
|
|
94
|
+
initInput(this);
|
|
95
|
+
this.updatePluginViews();
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Create a view inside a scope. `place` may be a DOM node that the
|
|
99
|
+
* editor should be appended to, a function that will place it into the
|
|
100
|
+
* document, or an object whose `mount` property holds the node to use
|
|
101
|
+
* as the document container. If it is `null`, the editor will not be
|
|
102
|
+
* added to the document.
|
|
103
|
+
*
|
|
104
|
+
* The view captures the current context (which must provide `R`) to
|
|
105
|
+
* run the effects of its plugins and props, and is destroyed when the
|
|
106
|
+
* scope closes.
|
|
107
|
+
*
|
|
108
|
+
* When props' handlers require services that the state's plugins
|
|
109
|
+
* don't, pass the type arguments explicitly
|
|
110
|
+
* (`EditorView.make<typeof schema, ServiceA | ServiceB>(...)`).
|
|
111
|
+
*/
|
|
112
|
+
static make(place, props) {
|
|
113
|
+
return Effect.acquireRelease(Effect.flatMap(Effect.context(), (context) => Effect.sync(() => new EditorView(place, props, context))), (view) => Effect.sync(() => view.destroy()));
|
|
114
|
+
}
|
|
115
|
+
static makeUnsafe(place, props, context) {
|
|
116
|
+
return new EditorView(place, props, context ?? Context.empty());
|
|
117
|
+
}
|
|
118
|
+
/** Holds `true` when a composition is active. */
|
|
119
|
+
get composing() {
|
|
120
|
+
return this.input.composing;
|
|
121
|
+
}
|
|
122
|
+
/** The view's current props. */
|
|
123
|
+
get props() {
|
|
124
|
+
if (this._props.state != this.state) {
|
|
125
|
+
const prev = this._props;
|
|
126
|
+
this._props = {};
|
|
127
|
+
for (const name in prev) this._props[name] = prev[name];
|
|
128
|
+
this._props.state = this.state;
|
|
129
|
+
}
|
|
130
|
+
return this._props;
|
|
131
|
+
}
|
|
132
|
+
/** Update the view's props. Will immediately cause an update to the DOM. */
|
|
133
|
+
update(props) {
|
|
134
|
+
if (props.handleDOMEvents != this._props.handleDOMEvents) ensureListeners(this);
|
|
135
|
+
const prevProps = this._props;
|
|
136
|
+
this._props = props;
|
|
137
|
+
if (props.plugins) {
|
|
138
|
+
props.plugins.forEach(checkStateComponent);
|
|
139
|
+
this.directPlugins = props.plugins;
|
|
140
|
+
}
|
|
141
|
+
this.updateStateInner(props.state, prevProps);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Update the view by updating existing props object with the object
|
|
145
|
+
* given as argument. Equivalent to
|
|
146
|
+
* `view.update(Object.assign({}, view.props, props))`.
|
|
147
|
+
*/
|
|
148
|
+
setProps(props) {
|
|
149
|
+
const updated = {};
|
|
150
|
+
for (const name in this._props) updated[name] = this._props[name];
|
|
151
|
+
updated.state = this.state;
|
|
152
|
+
for (const name in props) updated[name] = props[name];
|
|
153
|
+
this.update(updated);
|
|
154
|
+
}
|
|
155
|
+
/** Update the editor's `state` prop, without touching any of the other props. */
|
|
156
|
+
updateState(state) {
|
|
157
|
+
this.updateStateInner(state, this._props);
|
|
158
|
+
}
|
|
159
|
+
updateStateInner(state, prevProps) {
|
|
160
|
+
const prev = this.state;
|
|
161
|
+
const transactions = this.dispatching ?? [];
|
|
162
|
+
this.dispatching = null;
|
|
163
|
+
let redraw = false, updateSel = false;
|
|
164
|
+
if (state.storedMarksOrNull && this.composing) {
|
|
165
|
+
clearComposition(this);
|
|
166
|
+
updateSel = true;
|
|
167
|
+
}
|
|
168
|
+
this.state = state;
|
|
169
|
+
const pluginsChanged = prev.plugins != state.plugins || this._props.plugins != prevProps.plugins;
|
|
170
|
+
if (pluginsChanged || this._props.plugins != prevProps.plugins || this._props.nodeViews != prevProps.nodeViews) {
|
|
171
|
+
const nodeViews = buildNodeViews(this);
|
|
172
|
+
if (changedNodeViews(nodeViews, this.nodeViews)) {
|
|
173
|
+
this.nodeViews = nodeViews;
|
|
174
|
+
redraw = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (pluginsChanged || prevProps.handleDOMEvents != this._props.handleDOMEvents) ensureListeners(this);
|
|
178
|
+
this.editable = getEditable(this);
|
|
179
|
+
updateCursorWrapper(this);
|
|
180
|
+
const innerDeco = viewDecorations(this), outerDeco = computeDocDeco(this);
|
|
181
|
+
const scroll = prev.plugins != state.plugins && !prev.doc.eq(state.doc) ? "reset" : state.scrollToSelection > prev.scrollToSelection ? "to selection" : "preserve";
|
|
182
|
+
const updateDoc = redraw || !this.docView.matchesNode(state.doc, outerDeco, innerDeco);
|
|
183
|
+
if (updateDoc || !state.selection.eq(prev.selection)) updateSel = true;
|
|
184
|
+
const oldScrollPos = scroll == "preserve" && updateSel && this.dom.style.overflowAnchor == null && storeScrollPos(this);
|
|
185
|
+
if (updateSel) {
|
|
186
|
+
this.domObserver.stop();
|
|
187
|
+
let forceSelUpdate = updateDoc && (ie || chrome) && !this.composing && !prev.selection.empty && !state.selection.empty && selectionContextChanged(prev.selection, state.selection);
|
|
188
|
+
if (updateDoc) {
|
|
189
|
+
const chromeKludge = chrome ? this.trackWrites = this.domSelectionRange().focusNode : null;
|
|
190
|
+
if (this.composing) this.input.compositionNode = findCompositionNode(this);
|
|
191
|
+
if (redraw || !this.docView.update(state.doc, outerDeco, innerDeco, this)) {
|
|
192
|
+
this.docView.updateOuterDeco(outerDeco);
|
|
193
|
+
this.docView.destroy();
|
|
194
|
+
this.docView = docViewDesc(state.doc, outerDeco, innerDeco, this.dom, this);
|
|
195
|
+
}
|
|
196
|
+
if (chromeKludge && (!this.trackWrites || !this.dom.contains(this.trackWrites))) forceSelUpdate = true;
|
|
197
|
+
}
|
|
198
|
+
if (forceSelUpdate || !(this.input.mouseDown && this.domObserver.currentSelection.eq(this.domSelectionRange()) && anchorInRightPlace(this) && this.input.mouseDown.delaySelUpdate())) selectionToDOM(this, forceSelUpdate);
|
|
199
|
+
else {
|
|
200
|
+
syncNodeSelection(this, state.selection);
|
|
201
|
+
this.domObserver.setCurSelection();
|
|
202
|
+
}
|
|
203
|
+
this.domObserver.start();
|
|
204
|
+
}
|
|
205
|
+
this.updatePluginViews(prev);
|
|
206
|
+
if (this.dragging?.node && !prev.doc.eq(state.doc)) this.updateDraggedNode(this.dragging, prev);
|
|
207
|
+
if (scroll == "reset") this.dom.scrollTop = 0;
|
|
208
|
+
else if (scroll == "to selection") this.scrollToSelection();
|
|
209
|
+
else if (oldScrollPos) resetScrollPos(oldScrollPos);
|
|
210
|
+
if (this.listeners.length) this.notifyListeners({
|
|
211
|
+
state,
|
|
212
|
+
prevState: prev,
|
|
213
|
+
transactions
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
notifyListeners(update) {
|
|
217
|
+
const listeners = this.listeners;
|
|
218
|
+
for (let i = 0; i < listeners.length; i++) try {
|
|
219
|
+
listeners[i](update);
|
|
220
|
+
} catch (e) {
|
|
221
|
+
this.reportError(Cause.die(e));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Register a listener called after every state update of the view
|
|
226
|
+
* (once the DOM and plugin views have been updated). Returns a
|
|
227
|
+
* function that removes the listener. Exceptions thrown by listeners
|
|
228
|
+
* are reported to `onError`.
|
|
229
|
+
*/
|
|
230
|
+
subscribe(listener) {
|
|
231
|
+
this.listeners = [...this.listeners, listener];
|
|
232
|
+
return () => {
|
|
233
|
+
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
withDispatching(transactions, f) {
|
|
237
|
+
const prev = this.dispatching;
|
|
238
|
+
this.dispatching = transactions;
|
|
239
|
+
try {
|
|
240
|
+
return f();
|
|
241
|
+
} finally {
|
|
242
|
+
this.dispatching = prev;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/** @internal */
|
|
246
|
+
scrollToSelection() {
|
|
247
|
+
const startDOM = this.domSelectionRange().focusNode;
|
|
248
|
+
if (!startDOM || !this.dom.contains(startDOM.nodeType == 1 ? startDOM : startDOM.parentNode)) {} else if (this.someHandler("handleScrollToSelection", (f) => f(this))) {} else if (this.state.selection instanceof NodeSelection) {
|
|
249
|
+
const target = this.docView.domAfterPos(this.state.selection.from);
|
|
250
|
+
if (target.nodeType == 1) scrollRectIntoView(this, target.getBoundingClientRect(), startDOM);
|
|
251
|
+
} else scrollRectIntoView(this, this.coordsAtPos(this.state.selection.head, 1), startDOM);
|
|
252
|
+
}
|
|
253
|
+
destroyPluginViews() {
|
|
254
|
+
let view;
|
|
255
|
+
while (view = this.pluginViews.pop()) if (view.destroy) view.destroy();
|
|
256
|
+
}
|
|
257
|
+
updatePluginViews(prevState) {
|
|
258
|
+
if (!prevState || prevState.plugins != this.state.plugins || this.directPlugins != this.prevDirectPlugins) {
|
|
259
|
+
this.prevDirectPlugins = this.directPlugins;
|
|
260
|
+
this.destroyPluginViews();
|
|
261
|
+
for (let i = 0; i < this.directPlugins.length; i++) {
|
|
262
|
+
const plugin = this.directPlugins[i];
|
|
263
|
+
if (plugin.spec.view) this.pluginViews.push(plugin.spec.view(this));
|
|
264
|
+
}
|
|
265
|
+
for (let i = 0; i < this.state.plugins.length; i++) {
|
|
266
|
+
const plugin = this.state.plugins[i];
|
|
267
|
+
if (plugin.spec.view) this.pluginViews.push(plugin.spec.view(this));
|
|
268
|
+
}
|
|
269
|
+
} else for (let i = 0; i < this.pluginViews.length; i++) {
|
|
270
|
+
const pluginView = this.pluginViews[i];
|
|
271
|
+
if (pluginView.update) pluginView.update(this, prevState);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
updateDraggedNode(dragging, prev) {
|
|
275
|
+
const sel = dragging.node;
|
|
276
|
+
let found = -1;
|
|
277
|
+
if (sel.from < this.state.doc.content.size && this.state.doc.nodeAtOrNull(sel.from) == sel.node) found = sel.from;
|
|
278
|
+
else {
|
|
279
|
+
const movedPos = sel.from + (this.state.doc.content.size - prev.doc.content.size);
|
|
280
|
+
if ((movedPos > 0 && movedPos < this.state.doc.content.size && this.state.doc.nodeAtOrNull(movedPos)) == sel.node) found = movedPos;
|
|
281
|
+
}
|
|
282
|
+
this.dragging = new Dragging(dragging.slice, dragging.move, found < 0 ? void 0 : NodeSelection.createUnsafe(this.state.doc, found));
|
|
283
|
+
}
|
|
284
|
+
someProp(propName, f) {
|
|
285
|
+
const prop = this._props && this._props[propName];
|
|
286
|
+
let value;
|
|
287
|
+
if (prop != null && (value = f ? f(prop) : prop)) return value;
|
|
288
|
+
for (let i = 0; i < this.directPlugins.length; i++) {
|
|
289
|
+
const prop = this.directPlugins[i].props[propName];
|
|
290
|
+
if (prop != null && (value = f ? f(prop) : prop)) return value;
|
|
291
|
+
}
|
|
292
|
+
const plugins = this.state.plugins;
|
|
293
|
+
if (plugins) for (let i = 0; i < plugins.length; i++) {
|
|
294
|
+
const prop = plugins[i].props[propName];
|
|
295
|
+
if (prop != null && (value = f ? f(prop) : prop)) return value;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Like `someProp`, but for event handler props, whose results may be
|
|
300
|
+
* Effects: those are run (see `HandlerResult`) and their results used.
|
|
301
|
+
*
|
|
302
|
+
* @internal
|
|
303
|
+
*/
|
|
304
|
+
someHandler(propName, f) {
|
|
305
|
+
return !!this.someProp(propName, (h) => this.runHandler(f(h)));
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Interpret the result of an event handler prop, running it with the
|
|
309
|
+
* view's context when it is an Effect.
|
|
310
|
+
*
|
|
311
|
+
* @internal
|
|
312
|
+
*/
|
|
313
|
+
runHandler(result) {
|
|
314
|
+
if (!Effect.isEffect(result)) return !!result;
|
|
315
|
+
if (Exit.isExit(result)) {
|
|
316
|
+
if (Exit.isSuccess(result)) return !!result.value;
|
|
317
|
+
this.reportError(result.cause);
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
const exit = this.runEffect(result);
|
|
321
|
+
if (exit === void 0) return true;
|
|
322
|
+
return Exit.isSuccess(exit) ? !!exit.value : false;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Run an effect with the view's captured context. Returns its exit
|
|
326
|
+
* when it completed synchronously (reporting failures to `onError`),
|
|
327
|
+
* or `undefined` when it is still running in the background (in which
|
|
328
|
+
* case failures are reported when it completes).
|
|
329
|
+
*
|
|
330
|
+
* @internal
|
|
331
|
+
*/
|
|
332
|
+
runEffect(effect) {
|
|
333
|
+
const fiber = Effect.runForkWith(this.runContext)(effect);
|
|
334
|
+
const exit = fiber.pollUnsafe();
|
|
335
|
+
if (exit) {
|
|
336
|
+
if (Exit.isFailure(exit)) this.reportError(exit.cause);
|
|
337
|
+
return exit;
|
|
338
|
+
}
|
|
339
|
+
fiber.addObserver((exit) => {
|
|
340
|
+
if (Exit.isFailure(exit)) this.reportError(exit.cause);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
/** @internal */
|
|
344
|
+
reportError(cause) {
|
|
345
|
+
const onError = this._props.onError;
|
|
346
|
+
if (onError) onError(cause);
|
|
347
|
+
else Effect.runForkWith(this.context)(Effect.logError(cause));
|
|
348
|
+
}
|
|
349
|
+
/** Query whether the view has focus. */
|
|
350
|
+
hasFocus() {
|
|
351
|
+
if (ie) {
|
|
352
|
+
let node = this.root.activeElement;
|
|
353
|
+
if (node == this.dom) return true;
|
|
354
|
+
if (!node || !this.dom.contains(node)) return false;
|
|
355
|
+
while (node && this.dom != node && this.dom.contains(node)) {
|
|
356
|
+
if (node.contentEditable == "false") return false;
|
|
357
|
+
node = node.parentElement;
|
|
358
|
+
}
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
return this.root.activeElement == this.dom;
|
|
362
|
+
}
|
|
363
|
+
/** Focus the editor. */
|
|
364
|
+
focus() {
|
|
365
|
+
this.domObserver.stop();
|
|
366
|
+
if (this.editable) focusPreventScroll(this.dom);
|
|
367
|
+
selectionToDOM(this);
|
|
368
|
+
this.domObserver.start();
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Get the document root in which the editor exists. This will usually
|
|
372
|
+
* be the top-level `document`, but might be a shadow DOM root if the
|
|
373
|
+
* editor is inside one.
|
|
374
|
+
*/
|
|
375
|
+
get root() {
|
|
376
|
+
const cached = this._root;
|
|
377
|
+
if (cached == null) {
|
|
378
|
+
for (let search = this.dom.parentNode; search; search = search.parentNode) if (search.nodeType == 9 || search.nodeType == 11 && search.host) {
|
|
379
|
+
if (!search.getSelection) Object.getPrototypeOf(search).getSelection = () => search.ownerDocument.getSelection();
|
|
380
|
+
return this._root = search;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return cached || document;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* When an existing editor view is moved to a new document or shadow
|
|
387
|
+
* tree, call this to make it recompute its root.
|
|
388
|
+
*/
|
|
389
|
+
updateRoot() {
|
|
390
|
+
this._root = null;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Given a pair of viewport coordinates, return the document position
|
|
394
|
+
* that corresponds to them. Returns `None` if the given coordinates
|
|
395
|
+
* aren't inside of the editor.
|
|
396
|
+
*/
|
|
397
|
+
posAtCoords(coords) {
|
|
398
|
+
return Option.fromNullishOr(posAtCoords(this, coords));
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Returns the viewport rectangle at a given document position. `left`
|
|
402
|
+
* and `right` will be the same number, as this returns a flat
|
|
403
|
+
* cursor-ish rectangle. If the position is between two things that
|
|
404
|
+
* aren't directly adjacent, `side` determines which element is used.
|
|
405
|
+
* When < 0, the element before the position is used, otherwise the
|
|
406
|
+
* element after.
|
|
407
|
+
*/
|
|
408
|
+
coordsAtPos(pos, side = 1) {
|
|
409
|
+
return coordsAtPos(this, pos, side);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Find the DOM position that corresponds to the given document
|
|
413
|
+
* position. When `side` is negative, find the position as close as
|
|
414
|
+
* possible to the content before the position. When positive, prefer
|
|
415
|
+
* positions close to the content after the position. When zero,
|
|
416
|
+
* prefer as shallow a position as possible.
|
|
417
|
+
*
|
|
418
|
+
* Note that you should **not** mutate the editor's internal DOM, only
|
|
419
|
+
* inspect it (and even that is usually not necessary).
|
|
420
|
+
*/
|
|
421
|
+
domAtPos(pos, side = 0) {
|
|
422
|
+
return this.docView.domFromPos(pos, side);
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Find the DOM node that represents the document node after the given
|
|
426
|
+
* position. Returns `None` when the position doesn't point in front of
|
|
427
|
+
* a node or if the node is inside an opaque node view.
|
|
428
|
+
*
|
|
429
|
+
* This is intended to be able to call things like
|
|
430
|
+
* `getBoundingClientRect` on that DOM node. Do **not** mutate the
|
|
431
|
+
* editor DOM directly, or add styling this way, since that will be
|
|
432
|
+
* immediately overriden by the editor as it redraws the node.
|
|
433
|
+
*/
|
|
434
|
+
nodeDOM(pos) {
|
|
435
|
+
const desc = this.docView.descAt(pos);
|
|
436
|
+
return Option.fromNullishOr(desc ? desc.nodeDOM : null);
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Find the document position that corresponds to a given DOM position.
|
|
440
|
+
* (Whenever possible, it is preferable to inspect the document
|
|
441
|
+
* structure directly, rather than poking around in the DOM, but
|
|
442
|
+
* sometimes—for example when interpreting an event target—you don't
|
|
443
|
+
* have a choice.)
|
|
444
|
+
*
|
|
445
|
+
* The `bias` parameter can be used to influence which side of a DOM
|
|
446
|
+
* node to use when the position is inside a leaf node. Fails with a
|
|
447
|
+
* `ViewError` when the DOM position is not inside the editor.
|
|
448
|
+
*/
|
|
449
|
+
posAtDOM(node, offset, bias = -1) {
|
|
450
|
+
return Effect.suspend(() => {
|
|
451
|
+
const pos = this.docView.posFromDOM(node, offset, bias);
|
|
452
|
+
return pos == null || pos < 0 ? Effect.fail(new ViewError({ message: "DOM position not inside the editor" })) : Effect.succeed(pos);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
/** Like `posAtDOM`, but throws a `ViewError` when the position is not inside the editor. */
|
|
456
|
+
posAtDOMUnsafe(node, offset, bias = -1) {
|
|
457
|
+
const pos = this.docView.posFromDOM(node, offset, bias);
|
|
458
|
+
if (pos == null || pos < 0) throw new ViewError({ message: "DOM position not inside the editor" });
|
|
459
|
+
return pos;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Find out whether the selection is at the end of a textblock when
|
|
463
|
+
* moving in a given direction. When, for example, given `"left"`, it
|
|
464
|
+
* will return true if moving left from the current cursor position
|
|
465
|
+
* would leave that position's parent textblock. Will apply to the
|
|
466
|
+
* view's current state by default, but it is possible to pass a
|
|
467
|
+
* different state.
|
|
468
|
+
*/
|
|
469
|
+
endOfTextblock(dir, state) {
|
|
470
|
+
return endOfTextblock(this, state || this.state, dir);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Run the editor's paste logic with the given HTML string. The
|
|
474
|
+
* `event`, if given, will be passed to the `handlePaste` hook.
|
|
475
|
+
*/
|
|
476
|
+
pasteHTML(html, event) {
|
|
477
|
+
return doPaste(this, "", html, false, event || new ClipboardEvent("paste"));
|
|
478
|
+
}
|
|
479
|
+
/** Run the editor's paste logic with the given plain-text input. */
|
|
480
|
+
pasteText(text, event) {
|
|
481
|
+
return doPaste(this, text, null, true, event || new ClipboardEvent("paste"));
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Serialize the given slice as it would be if it was copied from this
|
|
485
|
+
* editor. Returns a DOM element that contains a representation of the
|
|
486
|
+
* slice as its children, a textual representation, and the
|
|
487
|
+
* transformed slice (which can be different from the given input due
|
|
488
|
+
* to hooks like `transformCopied`).
|
|
489
|
+
*/
|
|
490
|
+
serializeForClipboard(slice) {
|
|
491
|
+
return serializeForClipboard(this, slice);
|
|
492
|
+
}
|
|
493
|
+
/** Removes the editor from the DOM and destroys all node views. */
|
|
494
|
+
destroy() {
|
|
495
|
+
if (!this.docView) return;
|
|
496
|
+
destroyInput(this);
|
|
497
|
+
this.destroyPluginViews();
|
|
498
|
+
if (this.mounted) {
|
|
499
|
+
this.docView.update(this.state.doc, [], viewDecorations(this), this);
|
|
500
|
+
this.dom.textContent = "";
|
|
501
|
+
} else if (this.dom.parentNode) this.dom.parentNode.removeChild(this.dom);
|
|
502
|
+
this.docView.destroy();
|
|
503
|
+
this.docView = null;
|
|
504
|
+
clearReusedRange();
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* This is true when the view has been destroyed (and thus should not
|
|
508
|
+
* be used anymore).
|
|
509
|
+
*/
|
|
510
|
+
get isDestroyed() {
|
|
511
|
+
return this.docView == null;
|
|
512
|
+
}
|
|
513
|
+
/** Used for testing. */
|
|
514
|
+
dispatchEvent(event) {
|
|
515
|
+
return dispatchEvent(this, event);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Like `dispatch`, but fails with the errors of the plugin hooks and
|
|
519
|
+
* of `dispatchTransaction` instead of reporting them to `onError`.
|
|
520
|
+
* Runs with the view's captured context.
|
|
521
|
+
*/
|
|
522
|
+
tryDispatch(tr) {
|
|
523
|
+
return Effect.provideContext(Effect.suspend(() => {
|
|
524
|
+
const dispatchTransaction = this._props.dispatchTransaction;
|
|
525
|
+
if (dispatchTransaction) {
|
|
526
|
+
const result = this.withDispatching([tr], () => dispatchTransaction.call(this, tr));
|
|
527
|
+
if (!Effect.isEffect(result)) return Effect.void;
|
|
528
|
+
return Effect.suspend(() => {
|
|
529
|
+
const transactions = [tr];
|
|
530
|
+
this.dispatching = transactions;
|
|
531
|
+
return Effect.ensuring(result, Effect.sync(() => {
|
|
532
|
+
if (this.dispatching === transactions) this.dispatching = null;
|
|
533
|
+
}));
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
return Effect.map(this.state.applyTransaction(tr), (result) => this.withDispatching(result.transactions, () => this.updateState(result.state)));
|
|
537
|
+
}), this.context);
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Dispatch a transaction on behalf of a DOM event: like
|
|
541
|
+
* `dispatchUnsafe`, but failures are reported to `onError` instead of
|
|
542
|
+
* thrown.
|
|
543
|
+
*
|
|
544
|
+
* @internal
|
|
545
|
+
*/
|
|
546
|
+
dispatchInternal(tr) {
|
|
547
|
+
this.runEffect(this.tryDispatch(tr));
|
|
548
|
+
}
|
|
549
|
+
/** @internal */
|
|
550
|
+
domSelectionRange() {
|
|
551
|
+
const sel = this.dom.isConnected && this.domSelection();
|
|
552
|
+
if (!sel) return {
|
|
553
|
+
focusNode: null,
|
|
554
|
+
focusOffset: 0,
|
|
555
|
+
anchorNode: null,
|
|
556
|
+
anchorOffset: 0
|
|
557
|
+
};
|
|
558
|
+
return safari && this.root.nodeType === 11 && deepActiveElement(this.dom.ownerDocument) == this.dom && safariShadowSelectionRange(this, sel) || sel;
|
|
559
|
+
}
|
|
560
|
+
/** @internal */
|
|
561
|
+
domSelection() {
|
|
562
|
+
return this.root.getSelection();
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
function computeDocDeco(view) {
|
|
566
|
+
const attrs = Object.create(null);
|
|
567
|
+
attrs.class = "ProseMirror";
|
|
568
|
+
attrs.contenteditable = String(view.editable);
|
|
569
|
+
view.someProp("attributes", (prop) => {
|
|
570
|
+
const value = typeof prop == "function" ? prop(view.state) : prop;
|
|
571
|
+
if (value) {
|
|
572
|
+
for (const attr in value) if (attr == "class") attrs.class += " " + value[attr];
|
|
573
|
+
else if (attr == "style") attrs.style = (attrs.style ? attrs.style + ";" : "") + value[attr];
|
|
574
|
+
else if (!attrs[attr] && attr != "contenteditable" && attr != "nodeName") attrs[attr] = String(value[attr]);
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
if (!attrs.translate) attrs.translate = "no";
|
|
578
|
+
return [Decoration.node(0, view.state.doc.content.size, attrs)];
|
|
579
|
+
}
|
|
580
|
+
function updateCursorWrapper(view) {
|
|
581
|
+
if (view.markCursor) {
|
|
582
|
+
const dom = document.createElement("img");
|
|
583
|
+
dom.className = "ProseMirror-separator";
|
|
584
|
+
dom.setAttribute("mark-placeholder", "true");
|
|
585
|
+
dom.setAttribute("alt", "");
|
|
586
|
+
view.cursorWrapper = {
|
|
587
|
+
dom,
|
|
588
|
+
deco: Decoration.widget(view.state.selection.from, dom, {
|
|
589
|
+
raw: true,
|
|
590
|
+
marks: view.markCursor
|
|
591
|
+
})
|
|
592
|
+
};
|
|
593
|
+
} else view.cursorWrapper = null;
|
|
594
|
+
}
|
|
595
|
+
function getEditable(view) {
|
|
596
|
+
return !view.someProp("editable", (value) => value(view.state) === false);
|
|
597
|
+
}
|
|
598
|
+
function selectionContextChanged(sel1, sel2) {
|
|
599
|
+
const depth = Math.min(sel1.$anchor.sharedDepth(sel1.head), sel2.$anchor.sharedDepth(sel2.head));
|
|
600
|
+
return sel1.$anchor.start(depth) != sel2.$anchor.start(depth);
|
|
601
|
+
}
|
|
602
|
+
function buildNodeViews(view) {
|
|
603
|
+
const result = Object.create(null);
|
|
604
|
+
function add(obj) {
|
|
605
|
+
for (const prop in obj) if (!Object.prototype.hasOwnProperty.call(result, prop)) result[prop] = obj[prop];
|
|
606
|
+
}
|
|
607
|
+
view.someProp("nodeViews", add);
|
|
608
|
+
view.someProp("markViews", add);
|
|
609
|
+
return result;
|
|
610
|
+
}
|
|
611
|
+
function changedNodeViews(a, b) {
|
|
612
|
+
let nA = 0, nB = 0;
|
|
613
|
+
for (const prop in a) {
|
|
614
|
+
if (a[prop] != b[prop]) return true;
|
|
615
|
+
nA++;
|
|
616
|
+
}
|
|
617
|
+
for (const _ in b) nB++;
|
|
618
|
+
return nA != nB;
|
|
619
|
+
}
|
|
620
|
+
function checkStateComponent(plugin) {
|
|
621
|
+
if (plugin.spec.state || plugin.spec.filterTransaction || plugin.spec.appendTransaction) throw new ViewError({ message: "Plugins passed directly to the view must not have a state component" });
|
|
622
|
+
}
|
|
623
|
+
export { EditorView, EditorViewTypeId, __endComposition, __parseFromClipboard };
|