/
index.js
619 lines (570 loc) · 24.6 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
import {Mark} from "prosemirror-model"
import {NodeSelection} from "prosemirror-state"
import {scrollRectIntoView, posAtCoords, coordsAtPos, endOfTextblock, storeScrollPos, resetScrollPos} from "./domcoords"
import {docViewDesc} from "./viewdesc"
import {initInput, destroyInput, dispatchEvent, ensureListeners} from "./input"
import {selectionToDOM, needsCursorWrapper, anchorInRightPlace, syncNodeSelection} from "./selection"
import {Decoration, viewDecorations} from "./decoration"
import browser from "./browser"
export {Decoration, DecorationSet} from "./decoration"
// Exported for testing
export {serializeForClipboard as __serializeForClipboard, parseFromClipboard as __parseFromClipboard} from "./clipboard"
export {endComposition as __endComposition} from "./input"
// ::- An editor view manages the DOM structure that represents an
// editable document. Its state and behavior are determined by its
// [props](#view.DirectEditorProps).
export class EditorView {
// :: (?union<dom.Node, (dom.Node), {mount: dom.Node}>, DirectEditorProps)
// Create a view. `place` may be a DOM node that the editor should
// be appended to, a function that will place it into the document,
// or an object whose `mount` property holds the node to use as the
// document container. If it is `null`, the editor will not be added
// to the document.
constructor(place, props) {
this._props = props
// :: EditorState
// The view's current [state](#state.EditorState).
this.state = props.state
this.dispatch = this.dispatch.bind(this)
this._root = null
this.focused = false
// :: dom.Element
// An editable DOM node containing the document. (You probably
// should not directly interfere with its content.)
this.dom = (place && place.mount) || document.createElement("div")
if (place) {
if (place.appendChild) place.appendChild(this.dom)
else if (place.apply) place(this.dom)
else if (place.mount) this.mounted = true
}
this.editable = getEditable(this)
this.cursorWrapper = null
updateCursorWrapper(this)
this.nodeViews = buildNodeViews(this)
this.docView = docViewDesc(this.state.doc, computeDocDeco(this), viewDecorations(this), this.dom, this)
this.lastSelectedViewDesc = null
// :: ?{slice: Slice, move: bool}
// When editor content is being dragged, this object contains
// information about the dragged slice and whether it is being
// copied or moved. At any other time, it is null.
this.dragging = null
initInput(this)
this.pluginViews = []
this.updatePluginViews()
}
// composing:: boolean
// Holds `true` when a
// [composition](https://developer.mozilla.org/en-US/docs/Mozilla/IME_handling_guide)
// is active.
// :: DirectEditorProps
// The view's current [props](#view.EditorProps).
get props() {
if (this._props.state != this.state) {
let prev = this._props
this._props = {}
for (let name in prev) this._props[name] = prev[name]
this._props.state = this.state
}
return this._props
}
// :: (DirectEditorProps)
// Update the view's props. Will immediately cause an update to
// the DOM.
update(props) {
if (props.handleDOMEvents != this._props.handleDOMEvents) ensureListeners(this)
this._props = props
this.updateStateInner(props.state, true)
}
// :: (DirectEditorProps)
// Update the view by updating existing props object with the object
// given as argument. Equivalent to `view.update(Object.assign({},
// view.props, props))`.
setProps(props) {
let updated = {}
for (let name in this._props) updated[name] = this._props[name]
updated.state = this.state
for (let name in props) updated[name] = props[name]
this.update(updated)
}
// :: (EditorState)
// Update the editor's `state` prop, without touching any of the
// other props.
updateState(state) {
this.updateStateInner(state, this.state.plugins != state.plugins)
}
updateStateInner(state, reconfigured) {
let prev = this.state, redraw = false
this.state = state
if (reconfigured) {
let nodeViews = buildNodeViews(this)
if (changedNodeViews(nodeViews, this.nodeViews)) {
this.nodeViews = nodeViews
redraw = true
}
ensureListeners(this)
}
this.editable = getEditable(this)
updateCursorWrapper(this)
let innerDeco = viewDecorations(this), outerDeco = computeDocDeco(this)
let scroll = reconfigured ? "reset"
: state.scrollToSelection > prev.scrollToSelection ? "to selection" : "preserve"
let updateDoc = redraw || !this.docView.matchesNode(state.doc, outerDeco, innerDeco)
let updateSel = updateDoc || !state.selection.eq(prev.selection)
let oldScrollPos = scroll == "preserve" && updateSel && this.dom.style.overflowAnchor == null && storeScrollPos(this)
if (updateSel) {
this.domObserver.stop()
let forceSelUpdate = false
if (updateDoc) {
// Work around an issue in Chrome where changing the DOM
// around the active selection puts it into a broken state
// where the thing the user sees differs from the selection
// reported by the Selection object (#710)
let startSelContext = browser.chrome && selectionContext(this.root)
if (redraw || !this.docView.update(state.doc, outerDeco, innerDeco, this)) {
this.docView.destroy()
this.docView = docViewDesc(state.doc, outerDeco, innerDeco, this.dom, this)
}
if (startSelContext)
forceSelUpdate = needChromeSelectionForce(startSelContext, this.root)
}
// Work around for an issue where an update arriving right between
// a DOM selection change and the "selectionchange" event for it
// can cause a spurious DOM selection update, disrupting mouse
// drag selection.
if (forceSelUpdate ||
!(this.mouseDown && this.domObserver.currentSelection.eq(this.root.getSelection()) && anchorInRightPlace(this))) {
selectionToDOM(this, false, forceSelUpdate)
} else {
syncNodeSelection(this, state.selection)
this.domObserver.setCurSelection()
}
this.domObserver.start()
}
this.updatePluginViews(prev)
if (scroll == "reset") {
this.dom.scrollTop = 0
} else if (scroll == "to selection") {
let startDOM = this.root.getSelection().focusNode
if (this.someProp("handleScrollToSelection", f => f(this)))
{} // Handled
else if (state.selection instanceof NodeSelection)
scrollRectIntoView(this, this.docView.domAfterPos(state.selection.from).getBoundingClientRect(), startDOM)
else
scrollRectIntoView(this, this.coordsAtPos(state.selection.head), startDOM)
} else if (oldScrollPos) {
resetScrollPos(oldScrollPos)
}
}
destroyPluginViews() {
let view
while (view = this.pluginViews.pop()) if (view.destroy) view.destroy()
}
updatePluginViews(prevState) {
if (!prevState || prevState.plugins != this.state.plugins) {
this.destroyPluginViews()
for (let i = 0; i < this.state.plugins.length; i++) {
let plugin = this.state.plugins[i]
if (plugin.spec.view) this.pluginViews.push(plugin.spec.view(this))
}
} else {
for (let i = 0; i < this.pluginViews.length; i++) {
let pluginView = this.pluginViews[i]
if (pluginView.update) pluginView.update(this, prevState)
}
}
}
// :: (string, ?(prop: *) → *) → *
// Goes over the values of a prop, first those provided directly,
// then those from plugins (in order), and calls `f` every time a
// non-undefined value is found. When `f` returns a truthy value,
// that is immediately returned. When `f` isn't provided, it is
// treated as the identity function (the prop value is returned
// directly).
someProp(propName, f) {
let prop = this._props && this._props[propName], value
if (prop != null && (value = f ? f(prop) : prop)) return value
let plugins = this.state.plugins
if (plugins) for (let i = 0; i < plugins.length; i++) {
let prop = plugins[i].props[propName]
if (prop != null && (value = f ? f(prop) : prop)) return value
}
}
// :: () → bool
// Query whether the view has focus.
hasFocus() {
return this.root.activeElement == this.dom
}
// :: ()
// Focus the editor.
focus() {
this.domObserver.stop()
selectionToDOM(this, true)
if (this.editable) {
if (this.dom.setActive) this.dom.setActive() // for IE
else this.dom.focus({preventScroll: true})
}
this.domObserver.start()
}
// :: union<dom.Document, dom.DocumentFragment>
// Get the document root in which the editor exists. This will
// usually be the top-level `document`, but might be a [shadow
// DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
// root if the editor is inside one.
get root() {
let cached = this._root
if (cached == null) for (let search = this.dom.parentNode; search; search = search.parentNode) {
if (search.nodeType == 9 || (search.nodeType == 11 && search.host))
return this._root = search
}
return cached || document
}
// :: ({left: number, top: number}) → ?{pos: number, inside: number}
// Given a pair of viewport coordinates, return the document
// position that corresponds to them. May return null if the given
// coordinates aren't inside of the editor. When an object is
// returned, its `pos` property is the position nearest to the
// coordinates, and its `inside` property holds the position of the
// inner node that the position falls inside of, or -1 if it is at
// the top level, not in any node.
posAtCoords(coords) {
return posAtCoords(this, coords)
}
// :: (number) → {left: number, right: number, top: number, bottom: number}
// Returns the viewport rectangle at a given document position. `left`
// and `right` will be the same number, as this returns a flat
// cursor-ish rectangle.
coordsAtPos(pos) {
return coordsAtPos(this, pos)
}
// :: (number) → {node: dom.Node, offset: number}
// Find the DOM position that corresponds to the given document
// position. Note that you should **not** mutate the editor's
// internal DOM, only inspect it (and even that is usually not
// necessary).
domAtPos(pos) {
return this.docView.domFromPos(pos)
}
// :: (number) → ?dom.Node
// Find the DOM node that represents the document node after the
// given position. May return `null` when the position doesn't point
// in front of a node or if the node is inside an opaque node view.
//
// This is intended to be able to call things like
// `getBoundingClientRect` on that DOM node. Do **not** mutate the
// editor DOM directly, or add styling this way, since that will be
// immediately overriden by the editor as it redraws the node.
nodeDOM(pos) {
let desc = this.docView.descAt(pos)
return desc ? desc.nodeDOM : null
}
// :: (dom.Node, number, ?number) → number
// Find the document position that corresponds to a given DOM
// position. (Whenever possible, it is preferable to inspect the
// document structure directly, rather than poking around in the
// DOM, but sometimes—for example when interpreting an event
// target—you don't have a choice.)
//
// The `bias` parameter can be used to influence which side of a DOM
// node to use when the position is inside a leaf node.
posAtDOM(node, offset, bias = -1) {
let pos = this.docView.posFromDOM(node, offset, bias)
if (pos == null) throw new RangeError("DOM position not inside the editor")
return pos
}
// :: (union<"up", "down", "left", "right", "forward", "backward">, ?EditorState) → bool
// Find out whether the selection is at the end of a textblock when
// moving in a given direction. When, for example, given `"left"`,
// it will return true if moving left from the current cursor
// position would leave that position's parent textblock. Will apply
// to the view's current state by default, but it is possible to
// pass a different state.
endOfTextblock(dir, state) {
return endOfTextblock(this, state || this.state, dir)
}
// :: ()
// Removes the editor from the DOM and destroys all [node
// views](#view.NodeView).
destroy() {
if (!this.docView) return
destroyInput(this)
this.destroyPluginViews()
if (this.mounted) {
this.docView.update(this.state.doc, [], viewDecorations(this), this)
this.dom.textContent = ""
} else if (this.dom.parentNode) {
this.dom.parentNode.removeChild(this.dom)
}
this.docView.destroy()
this.docView = null
}
// Used for testing.
dispatchEvent(event) {
return dispatchEvent(this, event)
}
// :: (Transaction)
// Dispatch a transaction. Will call
// [`dispatchTransaction`](#view.DirectEditorProps.dispatchTransaction)
// when given, and otherwise defaults to applying the transaction to
// the current state and calling
// [`updateState`](#view.EditorView.updateState) with the result.
// This method is bound to the view instance, so that it can be
// easily passed around.
dispatch(tr) {
let dispatchTransaction = this._props.dispatchTransaction
if (dispatchTransaction) dispatchTransaction.call(this, tr)
else this.updateState(this.state.apply(tr))
}
}
function computeDocDeco(view) {
let attrs = Object.create(null)
attrs.class = "ProseMirror"
attrs.contenteditable = String(view.editable)
view.someProp("attributes", value => {
if (typeof value == "function") value = value(view.state)
if (value) for (let attr in value) {
if (attr == "class")
attrs.class += " " + value[attr]
else if (!attrs[attr] && attr != "contenteditable" && attr != "nodeName")
attrs[attr] = String(value[attr])
}
})
return [Decoration.node(0, view.state.doc.content.size, attrs)]
}
function cursorWrapperDOM(visible) {
let span = document.createElement("span")
span.textContent = "\ufeff" // zero-width non-breaking space
if (!visible) {
span.style.position = "absolute"
span.style.left = "-100000px"
}
return span
}
function updateCursorWrapper(view) {
let $pos = needsCursorWrapper(view.state)
// On IE/Edge, moving the DOM selection will abort a mouse drag, so
// there we delay the creation of the wrapper when the mouse is down.
if ($pos && !(browser.ie && view.mouseDown)) {
let visible = view.state.selection.visible
// Needs a cursor wrapper
let marks = view.state.storedMarks || $pos.marks(), dom
if (!view.cursorWrapper || !Mark.sameSet(view.cursorWrapper.deco.spec.marks, marks) ||
view.cursorWrapper.dom.textContent != "\ufeff" ||
view.cursorWrapper.deco.spec.visible != visible)
dom = cursorWrapperDOM(visible)
else if (view.cursorWrapper.deco.pos != $pos.pos)
dom = view.cursorWrapper.dom
if (dom)
view.cursorWrapper = {dom, deco: Decoration.widget($pos.pos, dom, {isCursorWrapper: true, marks, raw: true, visible})}
} else {
view.cursorWrapper = null
}
}
function getEditable(view) {
return !view.someProp("editable", value => value(view.state) === false)
}
function selectionContext(root) {
let {focusOffset: offset, focusNode: node} = root.getSelection()
if (!node || node.nodeType == 3) return null
return [node, offset,
node.nodeType == 1 ? node.childNodes[offset - 1] : null,
node.nodeType == 1 ? node.childNodes[offset] : null]
}
function needChromeSelectionForce(context, root) {
let newContext = selectionContext(root)
if (!newContext || newContext[0].nodeType == 3) return false
for (let i = 0; i < context.length; i++) if (newContext[i] != context[i]) return true
return false
}
function buildNodeViews(view) {
let result = {}
view.someProp("nodeViews", obj => {
for (let prop in obj) if (!Object.prototype.hasOwnProperty.call(result, prop))
result[prop] = obj[prop]
})
return result
}
function changedNodeViews(a, b) {
let nA = 0, nB = 0
for (let prop in a) {
if (a[prop] != b[prop]) return true
nA++
}
for (let _ in b) nB++
return nA != nB
}
// EditorProps:: interface
//
// Props are configuration values that can be passed to an editor view
// or included in a plugin. This interface lists the supported props.
//
// The various event-handling functions may all return `true` to
// indicate that they handled the given event. The view will then take
// care to call `preventDefault` on the event, except with
// `handleDOMEvents`, where the handler itself is responsible for that.
//
// How a prop is resolved depends on the prop. Handler functions are
// called one at a time, starting with the base props and then
// searching through the plugins (in order of appearance) until one of
// them returns true. For some props, the first plugin that yields a
// value gets precedence.
//
// handleDOMEvents:: ?Object<(view: EditorView, event: dom.Event) → bool>
// Can be an object mapping DOM event type names to functions that
// handle them. Such functions will be called before any handling
// ProseMirror does of events fired on the editable DOM element.
// Contrary to the other event handling props, when returning true
// from such a function, you are responsible for calling
// `preventDefault` yourself (or not, if you want to allow the
// default behavior).
//
// handleKeyDown:: ?(view: EditorView, event: dom.KeyboardEvent) → bool
// Called when the editor receives a `keydown` event.
//
// handleKeyPress:: ?(view: EditorView, event: dom.KeyboardEvent) → bool
// Handler for `keypress` events.
//
// handleTextInput:: ?(view: EditorView, from: number, to: number, text: string) → bool
// Whenever the user directly input text, this handler is called
// before the input is applied. If it returns `true`, the default
// behavior of actually inserting the text is suppressed.
//
// handleClickOn:: ?(view: EditorView, pos: number, node: Node, nodePos: number, event: dom.MouseEvent, direct: bool) → bool
// Called for each node around a click, from the inside out. The
// `direct` flag will be true for the inner node.
//
// handleClick:: ?(view: EditorView, pos: number, event: dom.MouseEvent) → bool
// Called when the editor is clicked, after `handleClickOn` handlers
// have been called.
//
// handleDoubleClickOn:: ?(view: EditorView, pos: number, node: Node, nodePos: number, event: dom.MouseEvent, direct: bool) → bool
// Called for each node around a double click.
//
// handleDoubleClick:: ?(view: EditorView, pos: number, event: dom.MouseEvent) → bool
// Called when the editor is double-clicked, after `handleDoubleClickOn`.
//
// handleTripleClickOn:: ?(view: EditorView, pos: number, node: Node, nodePos: number, event: dom.MouseEvent, direct: bool) → bool
// Called for each node around a triple click.
//
// handleTripleClick:: ?(view: EditorView, pos: number, event: dom.MouseEvent) → bool
// Called when the editor is triple-clicked, after `handleTripleClickOn`.
//
// handlePaste:: ?(view: EditorView, event: dom.Event, slice: Slice) → bool
// Can be used to override the behavior of pasting. `slice` is the
// pasted content parsed by the editor, but you can directly access
// the event to get at the raw content.
//
// handleDrop:: ?(view: EditorView, event: dom.Event, slice: Slice, moved: bool) → bool
// Called when something is dropped on the editor. `moved` will be
// true if this drop moves from the current selection (which should
// thus be deleted).
//
// handleScrollToSelection:: ?(view: EditorView) → bool
// Called when the view, after updating its state, tries to scroll
// the selection into view. A handler function may return false to
// indicate that it did not handle the scrolling and further
// handlers or the default behavior should be tried.
//
// createSelectionBetween:: ?(view: EditorView, anchor: ResolvedPos, head: ResolvedPos) → ?Selection
// Can be used to override the way a selection is created when
// reading a DOM selection between the given anchor and head.
//
// domParser:: ?DOMParser
// The [parser](#model.DOMParser) to use when reading editor changes
// from the DOM. Defaults to calling
// [`DOMParser.fromSchema`](#model.DOMParser^fromSchema) on the
// editor's schema.
//
// transformPastedHTML:: ?(html: string) → string
// Can be used to transform pasted HTML text, _before_ it is parsed,
// for example to clean it up.
//
// clipboardParser:: ?DOMParser
// The [parser](#model.DOMParser) to use when reading content from
// the clipboard. When not given, the value of the
// [`domParser`](#view.EditorProps.domParser) prop is used.
//
// transformPastedText:: ?(text: string) → string
// Transform pasted plain text.
//
// clipboardTextParser:: ?(text: string, $context: ResolvedPos) → Slice
// A function to parse text from the clipboard into a document
// slice. Called after
// [`transformPastedText`](#view.EditorProps.transformPastedText).
// The default behavior is to split the text into lines, wrap them
// in `<p>` tags, and call
// [`clipboardParser`](#view.EditorProps.clipboardParser) on it.
//
// transformPasted:: ?(Slice) → Slice
// Can be used to transform pasted content before it is applied to
// the document.
//
// nodeViews:: ?Object<(node: Node, view: EditorView, getPos: () → number, decorations: [Decoration]) → NodeView>
// Allows you to pass custom rendering and behavior logic for nodes
// and marks. Should map node and mark names to constructor
// functions that produce a [`NodeView`](#view.NodeView) object
// implementing the node's display behavior. For nodes, the third
// argument `getPos` is a function that can be called to get the
// node's current position, which can be useful when creating
// transactions to update it. For marks, the third argument is a
// boolean that indicates whether the mark's content is inline.
//
// `decorations` is an array of node or inline decorations that are
// active around the node. They are automatically drawn in the
// normal way, and you will usually just want to ignore this, but
// they can also be used as a way to provide context information to
// the node view without adding it to the document itself.
//
// clipboardSerializer:: ?DOMSerializer
// The DOM serializer to use when putting content onto the
// clipboard. If not given, the result of
// [`DOMSerializer.fromSchema`](#model.DOMSerializer^fromSchema)
// will be used.
//
// clipboardTextSerializer:: ?(Slice) → string
// A function that will be called to get the text for the current
// selection when copying text to the clipboard. By default, the
// editor will use [`textBetween`](#model.Node.textBetween) on the
// selected range.
//
// decorations:: ?(state: EditorState) → ?DecorationSet
// A set of [document decorations](#view.Decoration) to show in the
// view.
//
// editable:: ?(state: EditorState) → bool
// When this returns false, the content of the view is not directly
// editable.
//
// attributes:: ?union<Object<string>, (EditorState) → ?Object<string>>
// Control the DOM attributes of the editable element. May be either
// an object or a function going from an editor state to an object.
// By default, the element will get a class `"ProseMirror"`, and
// will have its `contentEditable` attribute determined by the
// [`editable` prop](#view.EditorProps.editable). Additional classes
// provided here will be added to the class. For other attributes,
// the value provided first (as in
// [`someProp`](#view.EditorView.someProp)) will be used.
//
// scrollThreshold:: ?union<number, {top: number, right: number, bottom: number, left: number}>
// Determines the distance (in pixels) between the cursor and the
// end of the visible viewport at which point, when scrolling the
// cursor into view, scrolling takes place. Defaults to 0.
//
// scrollMargin:: ?union<number, {top: number, right: number, bottom: number, left: number}>
// Determines the extra space (in pixels) that is left above or
// below the cursor when it is scrolled into view. Defaults to 5.
// DirectEditorProps:: interface extends EditorProps
//
// The props object given directly to the editor view supports two
// fields that can't be used in plugins:
//
// state:: EditorState
// The current state of the editor.
//
// dispatchTransaction:: ?(tr: Transaction)
// The callback over which to send transactions (state updates)
// produced by the view. If you specify this, you probably want to
// make sure this ends up calling the view's
// [`updateState`](#view.EditorView.updateState) method with a new
// state that has the transaction
// [applied](#state.EditorState.apply). The callback will be bound to have
// the view instance as its `this` binding.