prosemirror-view 0.20.1 → 0.20.5

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/dist/browser.js CHANGED
@@ -9,6 +9,7 @@ if (typeof navigator != "undefined") {
9
9
  var ie = result.ie = !!(ie_upto10 || ie_11up || ie_edge)
10
10
  result.ie_version = ie_upto10 ? document.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : null
11
11
  result.gecko = !ie && /gecko\/\d/i.test(navigator.userAgent)
12
+ result.chrome = !ie && /Chrome\//.test(navigator.userAgent)
12
13
  result.ios = !ie && /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent)
13
14
  result.webkit = !ie && 'WebkitAppearance' in document.documentElement.style
14
15
  }
@@ -209,9 +209,9 @@ function getMods(event) {
209
209
 
210
210
  function captureKeyDown(view, event) {
211
211
  var code = event.keyCode, mods = getMods(event)
212
- if (code == 8) { // Backspace
212
+ if (code == 8 || (browser.mac && code == 72 && mods == "c")) { // Backspace, Ctrl-h on Mac
213
213
  return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodesLeft(view)
214
- } else if (code == 46) { // Delete
214
+ } else if (code == 46 || (browser.mac && code == 68 && mods == "c")) { // Delete, Ctrl-d on Mac
215
215
  return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodesRight(view)
216
216
  } else if (code == 13 || code == 27) { // Enter, Esc
217
217
  return true
@@ -226,10 +226,6 @@ function captureKeyDown(view, event) {
226
226
  } else if (mods == (browser.mac ? "m" : "c") &&
227
227
  (code == 66 || code == 73 || code == 89 || code == 90)) { // Mod-[biyz]
228
228
  return true
229
- } else if (browser.mac && // Ctrl-[dh] and Alt-d on Mac
230
- ((code == 68 || code == 72) && mods == "c") ||
231
- (code == 68 && mods == "a")) {
232
- return stopNativeHorizontalDelete(view, code == 68 ? 1 : -1) || skipIgnoredNodesRight(view)
233
229
  }
234
230
  return false
235
231
  }
package/dist/clipboard.js CHANGED
@@ -4,8 +4,6 @@ var Fragment = ref.Fragment;
4
4
  var DOMParser = ref.DOMParser;
5
5
  var DOMSerializer = ref.DOMSerializer;
6
6
 
7
- var browser = require("./browser")
8
-
9
7
  function serializeForClipboard(view, slice) {
10
8
  var context = [];
11
9
  var content = slice.content;
@@ -35,7 +33,7 @@ exports.serializeForClipboard = serializeForClipboard
35
33
  // Read a slice of content from the clipboard (or drop data).
36
34
  function parseFromClipboard(view, text, html, plainText, $context) {
37
35
  var dom, inCode = $context.parent.type.spec.code
38
- if (!html && (!text || browser.ie && !(inCode, plainText))) { return null }
36
+ if (!html && !text) { return null }
39
37
  if ((plainText || inCode || !html) && text) {
40
38
  view.someProp("transformPastedText", function (f) { return text = f(text); })
41
39
  if (inCode) { return new Slice(Fragment.from(view.state.schema.text(text)), 0, 0) }
package/dist/dom.js CHANGED
@@ -27,12 +27,14 @@ exports.isEquivalentPosition = function(node, off, targetNode, targetOff) {
27
27
  scanFor(node, off, targetNode, targetOff, 1))
28
28
  }
29
29
 
30
+ var atomElements = /^(img|br|input|textarea|hr)$/i
31
+
30
32
  function scanFor(node, off, targetNode, targetOff, dir) {
31
33
  for (;;) {
32
34
  if (node == targetNode && off == targetOff) { return true }
33
35
  if (off == (dir < 0 ? 0 : nodeSize(node))) {
34
36
  var parent = node.parentNode
35
- if (parent.nodeType != 1 || hasBlockDesc(parent)) { return false }
37
+ if (parent.nodeType != 1 || hasBlockDesc(node) || atomElements.test(node.nodeName)) { return false }
36
38
  off = domIndex(node) + (dir < 0 ? 0 : 1)
37
39
  node = parent
38
40
  } else if (node.nodeType == 1) {
package/dist/domcoords.js CHANGED
@@ -197,10 +197,13 @@ function posAtCoords(view, coords) {
197
197
  ((assign$1 = range, node = assign$1.startContainer, offset = assign$1.startOffset)) }
198
198
  }
199
199
 
200
- var elt = root.elementFromPoint(coords.left, coords.top + 1)
200
+ var elt = root.elementFromPoint(coords.left, coords.top + 1), pos
201
201
  if (!elt) { return null }
202
- var pos = node ? posFromCaret(view, node, offset, coords) : posFromElement(view, elt, coords)
203
- if (pos == null) { return null }
202
+ if (node) { pos = posFromCaret(view, node, offset, coords) }
203
+ if (pos == null) {
204
+ pos = posFromElement(view, elt, coords)
205
+ if (pos == null) { return null }
206
+ }
204
207
 
205
208
  var desc = view.docView.nearestDesc(elt, true)
206
209
  return {pos: pos, inside: desc ? desc.posAtStart - desc.border : -1}
package/dist/input.js CHANGED
@@ -419,14 +419,16 @@ function sliceSingleNode(slice) {
419
419
  }
420
420
 
421
421
  function capturePaste(view, e) {
422
- var target = document.body.appendChild(document.createElement("div"))
422
+ var plainText = view.shiftKey || view.state.selection.$from.parent.type.spec.code
423
+ var target = document.body.appendChild(document.createElement(plainText ? "textarea" : "div"))
424
+ if (!plainText) { target.contentEditable = "true" }
423
425
  target.style.cssText = "position: fixed; left: -10000px; top: 10px"
424
- target.contentEditable = "true"
425
426
  target.focus()
426
427
  setTimeout(function () {
427
428
  view.focus()
428
429
  document.body.removeChild(target)
429
- doPaste(view, target.textContent, target.innerHTML, e)
430
+ if (plainText) { doPaste(view, target.value, null, e) }
431
+ else { doPaste(view, target.textContent, target.innerHTML, e) }
430
432
  }, 50)
431
433
  }
432
434
 
@@ -484,8 +486,8 @@ handlers.dragstart = function (view, e) {
484
486
  }
485
487
  var slice = view.state.selection.content(), dom = serializeForClipboard(view, slice)
486
488
  e.dataTransfer.clearData()
487
- e.dataTranser.setData("text/html", dom.innerHTML)
488
- e.dataTranser.setData("text/plain", slice.content.textBetween(0, slice.content.size, "\n\n"))
489
+ e.dataTransfer.setData("text/html", dom.innerHTML)
490
+ e.dataTransfer.setData("text/plain", slice.content.textBetween(0, slice.content.size, "\n\n"))
489
491
  view.dragging = new Dragging(slice, !e.ctrlKey)
490
492
  }
491
493
 
package/dist/selection.js CHANGED
@@ -75,8 +75,8 @@ SelectionReader.prototype.readFromDOM = function (origin) {
75
75
  if (selectionCollapsed(domSel)) {
76
76
  $anchor = $head
77
77
  while (nearestDesc && !nearestDesc.node) { nearestDesc = nearestDesc.parent }
78
- if (nearestDesc && nearestDesc.node.isAtom && NodeSelection.isSelectable(nearestDesc.node)) {
79
- var pos = nearestDesc.posAtStart
78
+ if (nearestDesc && nearestDesc.node.isAtom && NodeSelection.isSelectable(nearestDesc.node) && nearestDesc.parent) {
79
+ var pos = nearestDesc.posBefore
80
80
  selection = new NodeSelection(head == pos ? $head : doc.resolve(pos))
81
81
  }
82
82
  } else {
@@ -287,5 +287,5 @@ exports.selectionBetween = selectionBetween
287
287
  function hasFocusAndSelection(view) {
288
288
  if (view.editable && view.root.activeElement != view.dom) { return false }
289
289
  var sel = view.root.getSelection()
290
- return sel.rangeCount && view.dom.contains(sel.anchorNode.nodeType == 3 ? sel.anchorNode.parentNode : sel.anchorNode)
290
+ return sel.anchorNode && view.dom.contains(sel.anchorNode.nodeType == 3 ? sel.anchorNode.parentNode : sel.anchorNode)
291
291
  }
package/dist/viewdesc.js CHANGED
@@ -282,6 +282,9 @@ ViewDesc.prototype.parseRange = function (from, to, base) {
282
282
  var this$1 = this;
283
283
  if ( base === void 0 ) base = 0;
284
284
 
285
+ if (this.children.length == 0)
286
+ { return {node: this.contentDOM, from: from, to: to, fromOffset: 0, toOffset: this.contentDOM.childNodes.length} }
287
+
285
288
  var fromOffset = -1, toOffset = -1
286
289
  for (var offset = 0, i = 0;; i++) {
287
290
  var child = this$1.children[i], end = offset + child.size
@@ -703,7 +706,8 @@ var TextViewDesc = (function (NodeViewDesc) {
703
706
  TextViewDesc.prototype.constructor = TextViewDesc;
704
707
 
705
708
  TextViewDesc.prototype.parseRule = function () {
706
- return {skip: this.nodeDOM.parentNode}
709
+ var parent = this.nodeDOM.parentNode
710
+ return parent ? {skip: parent} : {ignore: true}
707
711
  };
708
712
 
709
713
  TextViewDesc.prototype.update = function (node, outerDeco) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "0.20.1",
3
+ "version": "0.20.5",
4
4
  "description": "ProseMirror's view component",
5
5
  "main": "dist/index.js",
6
6
  "style": "style/prosemirror.css",
package/src/browser.js CHANGED
@@ -9,6 +9,7 @@ if (typeof navigator != "undefined") {
9
9
  let ie = result.ie = !!(ie_upto10 || ie_11up || ie_edge)
10
10
  result.ie_version = ie_upto10 ? document.documentMode || 6 : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : null
11
11
  result.gecko = !ie && /gecko\/\d/i.test(navigator.userAgent)
12
+ result.chrome = !ie && /Chrome\//.test(navigator.userAgent)
12
13
  result.ios = !ie && /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent)
13
14
  result.webkit = !ie && 'WebkitAppearance' in document.documentElement.style
14
15
  }
@@ -199,9 +199,9 @@ function getMods(event) {
199
199
 
200
200
  function captureKeyDown(view, event) {
201
201
  let code = event.keyCode, mods = getMods(event)
202
- if (code == 8) { // Backspace
202
+ if (code == 8 || (browser.mac && code == 72 && mods == "c")) { // Backspace, Ctrl-h on Mac
203
203
  return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodesLeft(view)
204
- } else if (code == 46) { // Delete
204
+ } else if (code == 46 || (browser.mac && code == 68 && mods == "c")) { // Delete, Ctrl-d on Mac
205
205
  return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodesRight(view)
206
206
  } else if (code == 13 || code == 27) { // Enter, Esc
207
207
  return true
@@ -216,10 +216,6 @@ function captureKeyDown(view, event) {
216
216
  } else if (mods == (browser.mac ? "m" : "c") &&
217
217
  (code == 66 || code == 73 || code == 89 || code == 90)) { // Mod-[biyz]
218
218
  return true
219
- } else if (browser.mac && // Ctrl-[dh] and Alt-d on Mac
220
- ((code == 68 || code == 72) && mods == "c") ||
221
- (code == 68 && mods == "a")) {
222
- return stopNativeHorizontalDelete(view, code == 68 ? 1 : -1) || skipIgnoredNodesRight(view)
223
219
  }
224
220
  return false
225
221
  }
package/src/clipboard.js CHANGED
@@ -1,7 +1,5 @@
1
1
  const {Slice, Fragment, DOMParser, DOMSerializer} = require("prosemirror-model")
2
2
 
3
- const browser = require("./browser")
4
-
5
3
  function serializeForClipboard(view, slice) {
6
4
  let context = [], {content, openLeft, openRight} = slice
7
5
  while (openLeft > 1 && openRight > 1 && content.childCount == 1 && content.firstChild.childCount == 1) {
@@ -28,7 +26,7 @@ exports.serializeForClipboard = serializeForClipboard
28
26
  // Read a slice of content from the clipboard (or drop data).
29
27
  function parseFromClipboard(view, text, html, plainText, $context) {
30
28
  let dom, inCode = $context.parent.type.spec.code
31
- if (!html && (!text || browser.ie && !(inCode, plainText))) return null
29
+ if (!html && !text) return null
32
30
  if ((plainText || inCode || !html) && text) {
33
31
  view.someProp("transformPastedText", f => text = f(text))
34
32
  if (inCode) return new Slice(Fragment.from(view.state.schema.text(text)), 0, 0)
package/src/dom.js CHANGED
@@ -27,12 +27,14 @@ exports.isEquivalentPosition = function(node, off, targetNode, targetOff) {
27
27
  scanFor(node, off, targetNode, targetOff, 1))
28
28
  }
29
29
 
30
+ const atomElements = /^(img|br|input|textarea|hr)$/i
31
+
30
32
  function scanFor(node, off, targetNode, targetOff, dir) {
31
33
  for (;;) {
32
34
  if (node == targetNode && off == targetOff) return true
33
35
  if (off == (dir < 0 ? 0 : nodeSize(node))) {
34
36
  let parent = node.parentNode
35
- if (parent.nodeType != 1 || hasBlockDesc(parent)) return false
37
+ if (parent.nodeType != 1 || hasBlockDesc(node) || atomElements.test(node.nodeName)) return false
36
38
  off = domIndex(node) + (dir < 0 ? 0 : 1)
37
39
  node = parent
38
40
  } else if (node.nodeType == 1) {
package/src/domcoords.js CHANGED
@@ -183,10 +183,13 @@ function posAtCoords(view, coords) {
183
183
  if (range) ({startContainer: node, startOffset: offset} = range)
184
184
  }
185
185
 
186
- let elt = root.elementFromPoint(coords.left, coords.top + 1)
186
+ let elt = root.elementFromPoint(coords.left, coords.top + 1), pos
187
187
  if (!elt) return null
188
- let pos = node ? posFromCaret(view, node, offset, coords) : posFromElement(view, elt, coords)
189
- if (pos == null) return null
188
+ if (node) pos = posFromCaret(view, node, offset, coords)
189
+ if (pos == null) {
190
+ pos = posFromElement(view, elt, coords)
191
+ if (pos == null) return null
192
+ }
190
193
 
191
194
  let desc = view.docView.nearestDesc(elt, true)
192
195
  return {pos, inside: desc ? desc.posAtStart - desc.border : -1}
package/src/input.js CHANGED
@@ -402,14 +402,16 @@ function sliceSingleNode(slice) {
402
402
  }
403
403
 
404
404
  function capturePaste(view, e) {
405
- let target = document.body.appendChild(document.createElement("div"))
405
+ let plainText = view.shiftKey || view.state.selection.$from.parent.type.spec.code
406
+ let target = document.body.appendChild(document.createElement(plainText ? "textarea" : "div"))
407
+ if (!plainText) target.contentEditable = "true"
406
408
  target.style.cssText = "position: fixed; left: -10000px; top: 10px"
407
- target.contentEditable = "true"
408
409
  target.focus()
409
410
  setTimeout(() => {
410
411
  view.focus()
411
412
  document.body.removeChild(target)
412
- doPaste(view, target.textContent, target.innerHTML, e)
413
+ if (plainText) doPaste(view, target.value, null, e)
414
+ else doPaste(view, target.textContent, target.innerHTML, e)
413
415
  }, 50)
414
416
  }
415
417
 
@@ -469,8 +471,8 @@ handlers.dragstart = (view, e) => {
469
471
  }
470
472
  let slice = view.state.selection.content(), dom = serializeForClipboard(view, slice)
471
473
  e.dataTransfer.clearData()
472
- e.dataTranser.setData("text/html", dom.innerHTML)
473
- e.dataTranser.setData("text/plain", slice.content.textBetween(0, slice.content.size, "\n\n"))
474
+ e.dataTransfer.setData("text/html", dom.innerHTML)
475
+ e.dataTransfer.setData("text/plain", slice.content.textBetween(0, slice.content.size, "\n\n"))
474
476
  view.dragging = new Dragging(slice, !e.ctrlKey)
475
477
  }
476
478
 
package/src/selection.js CHANGED
@@ -71,8 +71,8 @@ class SelectionReader {
71
71
  if (selectionCollapsed(domSel)) {
72
72
  $anchor = $head
73
73
  while (nearestDesc && !nearestDesc.node) nearestDesc = nearestDesc.parent
74
- if (nearestDesc && nearestDesc.node.isAtom && NodeSelection.isSelectable(nearestDesc.node)) {
75
- let pos = nearestDesc.posAtStart
74
+ if (nearestDesc && nearestDesc.node.isAtom && NodeSelection.isSelectable(nearestDesc.node) && nearestDesc.parent) {
75
+ let pos = nearestDesc.posBefore
76
76
  selection = new NodeSelection(head == pos ? $head : doc.resolve(pos))
77
77
  }
78
78
  } else {
@@ -282,5 +282,5 @@ exports.selectionBetween = selectionBetween
282
282
  function hasFocusAndSelection(view) {
283
283
  if (view.editable && view.root.activeElement != view.dom) return false
284
284
  let sel = view.root.getSelection()
285
- return sel.rangeCount && view.dom.contains(sel.anchorNode.nodeType == 3 ? sel.anchorNode.parentNode : sel.anchorNode)
285
+ return sel.anchorNode && view.dom.contains(sel.anchorNode.nodeType == 3 ? sel.anchorNode.parentNode : sel.anchorNode)
286
286
  }
package/src/viewdesc.js CHANGED
@@ -257,6 +257,9 @@ class ViewDesc {
257
257
  // Used to find a DOM range in a single parent for a given changed
258
258
  // range.
259
259
  parseRange(from, to, base = 0) {
260
+ if (this.children.length == 0)
261
+ return {node: this.contentDOM, from, to, fromOffset: 0, toOffset: this.contentDOM.childNodes.length}
262
+
260
263
  let fromOffset = -1, toOffset = -1
261
264
  for (let offset = 0, i = 0;; i++) {
262
265
  let child = this.children[i], end = offset + child.size
@@ -633,7 +636,8 @@ class TextViewDesc extends NodeViewDesc {
633
636
  }
634
637
 
635
638
  parseRule() {
636
- return {skip: this.nodeDOM.parentNode}
639
+ let parent = this.nodeDOM.parentNode
640
+ return parent ? {skip: parent} : {ignore: true}
637
641
  }
638
642
 
639
643
  update(node, outerDeco) {
package/src/#input.js# DELETED
@@ -1,532 +0,0 @@
1
- const {Selection, NodeSelection, TextSelection} = require("prosemirror-state")
2
-
3
- const browser = require("./browser")
4
- const {captureKeyDown} = require("./capturekeys")
5
- const {DOMChange} = require("./domchange")
6
- const {parseFromClipboard, serializeForClipboard} = require("./clipboard")
7
- const {DOMObserver} = require("./domobserver")
8
- const {selectionBetween} = require("./selection")
9
-
10
- // A collection of DOM events that occur within the editor, and callback functions
11
- // to invoke when the event fires.
12
- const handlers = {}, editHandlers = {}
13
-
14
- function initInput(view) {
15
- view.shiftKey = false
16
- view.mouseDown = null
17
- view.dragging = null
18
- view.inDOMChange = null
19
- view.domObserver = new DOMObserver(view)
20
- view.domObserver.start()
21
-
22
- for (let event in handlers) {
23
- let handler = handlers[event]
24
- view.dom.addEventListener(event, event => {
25
- if (eventBelongsToView(view, event) && !runCustomHandler(view, event) &&
26
- (view.editable || !(event.type in editHandlers)))
27
- handler(view, event)
28
- })
29
- }
30
- view.extraHandlers = Object.create(null)
31
- ensureListeners(view)
32
- }
33
- exports.initInput = initInput
34
-
35
- function destroyInput(view) {
36
- view.domObserver.stop()
37
- if (view.inDOMChange) view.inDOMChange.destroy()
38
- }
39
- exports.destroyInput = destroyInput
40
-
41
- function ensureListeners(view) {
42
- view.someProp("handleDOMEvents", currentHandlers => {
43
- for (let type in currentHandlers) if (!view.extraHandlers[type] && !handlers.hasOwnProperty(type)) {
44
- view.extraHandlers[type] = true
45
- view.dom.addEventListener(type, event => runCustomHandler(view, event))
46
- }
47
- })
48
- }
49
- exports.ensureListeners = ensureListeners
50
-
51
- function runCustomHandler(view, event) {
52
- return view.someProp("handleDOMEvents", handlers => {
53
- let handler = handlers[event.type]
54
- return handler ? handler(view, event) || event.defaultPrevented : false
55
- })
56
- }
57
-
58
- function eventBelongsToView(view, event) {
59
- if (!event.bubbles) return true
60
- if (event.defaultPrevented) return false
61
- for (let node = event.target; node != view.dom; node = node.parentNode)
62
- if (!node || node.nodeType == 11 ||
63
- (node.pmViewDesc && node.pmViewDesc.stopEvent(event)))
64
- return false
65
- return true
66
- }
67
-
68
- function dispatchEvent(view, event) {
69
- if (!runCustomHandler(view, event) && handlers[event.type] &&
70
- (view.editable || !(event.type in editHandlers)))
71
- handlers[event.type](view, event)
72
- }
73
- exports.dispatchEvent = dispatchEvent
74
-
75
- editHandlers.keydown = (view, event) => {
76
- if (event.keyCode == 16) view.shiftKey = true
77
- if (view.inDOMChange) return
78
- if (view.someProp("handleKeyDown", f => f(view, event)) || captureKeyDown(view, event))
79
- event.preventDefault()
80
- else
81
- view.selectionReader.poll()
82
- }
83
-
84
- editHandlers.keyup = (view, e) => {
85
- if (e.keyCode == 16) view.shiftKey = false
86
- }
87
-
88
- editHandlers.keypress = (view, event) => {
89
- if (view.inDOMChange || !event.charCode ||
90
- event.ctrlKey && !event.altKey || browser.mac && event.metaKey) return
91
-
92
- if (view.someProp("handleKeyPress", f => f(view, event))) {
93
- event.preventDefault()
94
- return
95
- }
96
-
97
- let sel = view.state.selection
98
- if (!(sel instanceof TextSelection) || !sel.$from.sameParent(sel.$to)) {
99
- let text = String.fromCharCode(event.charCode)
100
- if (!view.someProp("handleTextInput", f => f(view, sel.$from.pos, sel.$to.pos, text)))
101
- view.dispatch(view.state.tr.insertText(text).scrollIntoView())
102
- event.preventDefault()
103
- }
104
- }
105
-
106
- function eventCoords(event) { return {left: event.clientX, top: event.clientY} }
107
-
108
- let lastClick = {time: 0, x: 0, y: 0}, oneButLastClick = lastClick
109
-
110
- function isNear(event, click) {
111
- let dx = click.x - event.clientX, dy = click.y - event.clientY
112
- return dx * dx + dy * dy < 100
113
- }
114
-
115
- function runHandlerOnContext(view, propName, pos, inside, event) {
116
- if (inside == -1) return false
117
- let $pos = view.state.doc.resolve(inside)
118
- for (let i = $pos.depth + 1; i > 0; i--) {
119
- if (view.someProp(propName, f => i > $pos.depth ? f(view, pos, $pos.nodeAfter, $pos.before(i), event, true)
120
- : f(view, pos, $pos.node(i), $pos.before(i), event, false)))
121
- return true
122
- }
123
- return false
124
- }
125
-
126
- function updateSelection(view, selection, origin) {
127
- if (!view.focused) view.focus()
128
- let tr = view.state.tr.setSelection(selection)
129
- if (origin == "pointer") tr.setMeta("pointer", true)
130
- view.dispatch(tr)
131
- }
132
-
133
- function selectClickedLeaf(view, inside) {
134
- if (inside == -1) return false
135
- let $pos = view.state.doc.resolve(inside), node = $pos.nodeAfter
136
- if (node && node.isAtom && NodeSelection.isSelectable(node)) {
137
- updateSelection(view, new NodeSelection($pos), "pointer")
138
- return true
139
- }
140
- return false
141
- }
142
-
143
- function selectClickedNode(view, inside) {
144
- if (inside == -1) return false
145
- let sel = view.state.selection, selectedNode, selectAt
146
- if (sel instanceof NodeSelection) selectedNode = sel.node
147
-
148
- let $pos = view.state.doc.resolve(inside)
149
- for (let i = $pos.depth + 1; i > 0; i--) {
150
- let node = i > $pos.depth ? $pos.nodeAfter : $pos.node(i)
151
- if (NodeSelection.isSelectable(node)) {
152
- if (selectedNode && sel.$from.depth > 0 &&
153
- i >= sel.$from.depth && $pos.before(sel.$from.depth + 1) == sel.$from.pos)
154
- selectAt = $pos.before(sel.$from.depth)
155
- else
156
- selectAt = $pos.before(i)
157
- break
158
- }
159
- }
160
-
161
- if (selectAt != null) {
162
- updateSelection(view, NodeSelection.create(view.state.doc, selectAt), "pointer")
163
- return true
164
- } else {
165
- return false
166
- }
167
- }
168
-
169
- function handleSingleClick(view, pos, inside, event, selectNode) {
170
- return runHandlerOnContext(view, "handleClickOn", pos, inside, event) ||
171
- view.someProp("handleClick", f => f(view, pos, event)) ||
172
- (selectNode ? selectClickedNode(view, inside) : selectClickedLeaf(view, inside))
173
- }
174
-
175
- function handleDoubleClick(view, pos, inside, event) {
176
- return runHandlerOnContext(view, "handleDoubleClickOn", pos, inside, event) ||
177
- view.someProp("handleDoubleClick", f => f(view, pos, event))
178
- }
179
-
180
- function handleTripleClick(view, pos, inside, event) {
181
- return runHandlerOnContext(view, "handleTripleClickOn", pos, inside, event) ||
182
- view.someProp("handleTripleClick", f => f(view, pos, event)) ||
183
- defaultTripleClick(view, inside)
184
- }
185
-
186
- function defaultTripleClick(view, inside) {
187
- let doc = view.state.doc
188
- if (inside == -1) {
189
- if (doc.inlineContent) {
190
- updateSelection(view, TextSelection.create(doc, 0, doc.content.size), "pointer")
191
- return true
192
- }
193
- return false
194
- }
195
-
196
- let $pos = doc.resolve(inside)
197
- for (let i = $pos.depth + 1; i > 0; i--) {
198
- let node = i > $pos.depth ? $pos.nodeAfter : $pos.node(i)
199
- let nodePos = $pos.before(i)
200
- if (node.inlineContent)
201
- updateSelection(view, TextSelection.create(doc, nodePos + 1, nodePos + 1 + node.content.size), "pointer")
202
- else if (NodeSelection.isSelectable(node))
203
- updateSelection(view, NodeSelection.create(doc, nodePos), "pointer")
204
- else
205
- continue
206
- return true
207
- }
208
- }
209
-
210
- function forceDOMFlush(view) {
211
- if (!view.inDOMChange) return false
212
- view.inDOMChange.finish(true)
213
- return true
214
- }
215
-
216
- const selectNodeModifier = browser.mac ? "metaKey" : "ctrlKey"
217
-
218
- handlers.mousedown = (view, event) => {
219
- let flushed = forceDOMFlush(view)
220
- let now = Date.now(), type
221
- if (now - lastClick.time >= 500 || !isNear(event, lastClick) || event[selectNodeModifier]) type = "singleClick"
222
- else if (now - oneButLastClick.time >= 600 || !isNear(event, oneButLastClick)) type = "doubleClick"
223
- else type = "tripleClick"
224
- oneButLastClick = lastClick
225
- lastClick = {time: now, x: event.clientX, y: event.clientY}
226
-
227
- let pos = view.posAtCoords(eventCoords(event))
228
- if (!pos) return
229
-
230
- if (type == "singleClick")
231
- view.mouseDown = new MouseDown(view, pos, event, flushed)
232
- else if ((type == "doubleClick" ? handleDoubleClick : handleTripleClick)(view, pos.pos, pos.inside, event))
233
- event.preventDefault()
234
- else
235
- view.selectionReader.poll("pointer")
236
- }
237
-
238
- class MouseDown {
239
- constructor(view, pos, event, flushed) {
240
- this.view = view
241
- this.pos = pos
242
- this.event = event
243
- this.flushed = flushed
244
- this.selectNode = event[selectNodeModifier]
245
- this.allowDefault = event.shiftKey
246
-
247
- let targetNode, targetPos
248
- if (pos.inside > -1) {
249
- targetNode = view.state.doc.nodeAt(pos.inside)
250
- targetPos = pos.inside
251
- } else {
252
- let $pos = view.state.doc.resolve(pos.pos)
253
- targetNode = $pos.parent
254
- targetPos = $pos.depth ? $pos.before() : 0
255
- }
256
-
257
- this.mightDrag = null
258
- if (targetNode.type.spec.draggable && targetNode.type.spec.selectable !== false ||
259
- view.state.selection instanceof NodeSelection && targetPos == view.state.selection.from)
260
- this.mightDrag = {node: targetNode, pos: targetPos}
261
-
262
- this.target = flushed ? null : event.target
263
- if (this.target && this.mightDrag) {
264
- this.view.domObserver.stop()
265
- this.target.draggable = true
266
- if (browser.gecko && (this.setContentEditable = !this.target.hasAttribute("contentEditable")))
267
- setTimeout(() => this.target.setAttribute("contentEditable", "false"), 20)
268
- this.view.domObserver.start()
269
- }
270
-
271
- view.root.addEventListener("mouseup", this.up = this.up.bind(this))
272
- view.root.addEventListener("mousemove", this.move = this.move.bind(this))
273
- view.selectionReader.poll("pointer")
274
- }
275
-
276
- done() {
277
- this.view.root.removeEventListener("mouseup", this.up)
278
- this.view.root.removeEventListener("mousemove", this.move)
279
- if (this.mightDrag && this.target) {
280
- this.view.domObserver.stop()
281
- this.target.draggable = false
282
- if (browser.gecko && this.setContentEditable)
283
- this.target.removeAttribute("contentEditable")
284
- this.view.domObserver.start()
285
- }
286
- }
287
-
288
- up(event) {
289
- this.done()
290
-
291
- if (!this.view.dom.contains(event.target.nodeType == 3 ? event.target.parentNode : event.target))
292
- return
293
-
294
- if (this.allowDefault) {
295
- this.view.selectionReader.poll("pointer")
296
- } else if (handleSingleClick(this.view, this.pos.pos, this.pos.inside, event, this.selectNode)) {
297
- event.preventDefault()
298
- } else if (this.flushed) {
299
- updateSelection(this.view, Selection.near(this.view.state.doc.resolve(this.pos.pos)), "pointer")
300
- event.preventDefault()
301
- } else {
302
- this.view.selectionReader.poll("pointer")
303
- }
304
- }
305
-
306
- move(event) {
307
- if (!this.allowDefault && (Math.abs(this.event.x - event.clientX) > 4 ||
308
- Math.abs(this.event.y - event.clientY) > 4))
309
- this.allowDefault = true
310
- this.view.selectionReader.poll("pointer")
311
- }
312
- }
313
-
314
- handlers.touchdown = view => {
315
- forceDOMFlush(view)
316
- view.selectionReader.poll("pointer")
317
- }
318
-
319
- handlers.contextmenu = (view, e) => {
320
- forceDOMFlush(view)
321
- let pos = view.posAtCoords(eventCoords(e))
322
- if (pos && view.someProp("handleContextMenu", f => f(view, pos.pos, e)))
323
- e.preventDefault()
324
- }
325
-
326
- // Input compositions are hard. Mostly because the events fired by
327
- // browsers are A) very unpredictable and inconsistent, and B) not
328
- // cancelable.
329
- //
330
- // ProseMirror has the problem that it must not update the DOM during
331
- // a composition, or the browser will cancel it. What it does is keep
332
- // long-running operations (delayed DOM updates) when a composition is
333
- // active.
334
- //
335
- // We _do not_ trust the information in the composition events which,
336
- // apart from being very uninformative to begin with, is often just
337
- // plain wrong. Instead, when a composition ends, we parse the dom
338
- // around the original selection, and derive an update from that.
339
-
340
- editHandlers.compositionstart = editHandlers.compositionupdate = view => {
341
- DOMChange.start(view, true)
342
- }
343
-
344
- editHandlers.compositionend = (view, e) => {
345
- if (!view.inDOMChange) {
346
- // We received a compositionend without having seen any previous
347
- // events for the composition. If there's data in the event
348
- // object, we assume that it's a real change, and start a
349
- // composition. Otherwise, we just ignore it.
350
- if (e.data) DOMChange.start(view, true)
351
- else return
352
- }
353
-
354
- view.inDOMChange.compositionEnd()
355
- }
356
-
357
- editHandlers.input = view => DOMChange.start(view)
358
-
359
- function captureCopy(view, dom) {
360
- // The extra wrapper is somehow necessary on IE/Edge to prevent the
361
- // content from being mangled when it is put onto the clipboard
362
- let wrap = document.body.appendChild(document.createElement("div"))
363
- wrap.appendChild(dom)
364
- wrap.style.cssText = "position: fixed; left: -10000px; top: 10px"
365
- let sel = getSelection(), range = document.createRange()
366
- range.selectNodeContents(dom)
367
- sel.removeAllRanges()
368
- sel.addRange(range)
369
- setTimeout(() => {
370
- document.body.removeChild(wrap)
371
- view.focus()
372
- }, 50)
373
- }
374
-
375
- // This is very crude, but unfortunately both these browsers _pretend_
376
- // that they have a clipboard API---all the objects and methods are there
377
- const brokenClipboardAPI = browser.ie || browser.ios
378
-
379
- handlers.copy = editHandlers.cut = (view, e) => {
380
- let sel = view.state.selection, cut = e.type == "cut"
381
- if (sel.empty) return
382
-
383
- // IE and Edge's clipboard interface is completely broken
384
- let data = brokenClipboardAPI ? null : e.clipboardData
385
- let slice = sel.content(), dom = serializeForClipboard(view, slice)
386
- if (data) {
387
- e.preventDefault()
388
- data.clearData()
389
- data.setData("text/html", dom.innerHTML)
390
- data.setData("text/plain", slice.content.textBetween(0, slice.content.size, "\n\n"))
391
- } else {
392
- captureCopy(view, dom)
393
- }
394
- if (cut) view.dispatch(view.state.tr.deleteSelection().scrollIntoView())
395
- }
396
-
397
- function sliceSingleNode(slice) {
398
- return slice.openLeft == 0 && slice.openRight == 0 && slice.content.childCount == 1 ? slice.content.firstChild : null
399
- }
400
-
401
- function capturePaste(view, e) {
402
- let target = document.body.appendChild(document.createElement("div"))
403
- target.style.cssText = "position: fixed; left: -10000px; top: 10px"
404
- target.contentEditable = "true"
405
- target.focus()
406
- setTimeout(() => {
407
- view.focus()
408
- document.body.removeChild(target)
409
- doPaste(view, target.textContent, target.innerHTML, e)
410
- }, 50)
411
- }
412
-
413
- function doPaste(view, text, html, e) {
414
- let slice = parseFromClipboard(view, text, html, view.shiftKey, view.state.selection.$from)
415
- if (!slice) return false
416
-
417
- if (view.someProp("handlePaste", f => f(view, e, slice))) return true
418
-
419
- let singleNode = sliceSingleNode(slice)
420
- let tr = singleNode ? view.state.tr.replaceSelectionWith(singleNode) : view.state.tr.replaceSelection(slice)
421
- view.dispatch(tr.scrollIntoView())
422
- return true
423
- }
424
-
425
- editHandlers.paste = (view, e) => {
426
- let data = brokenClipboardAPI ? null : e.clipboardData
427
- if (data && doPaste(view, data.getData("text/plain"), data.getData("text/html"), e))
428
- e.preventDefault()
429
- else
430
- capturePaste(view, e)
431
- }
432
-
433
- class Dragging {
434
- constructor(slice, move) {
435
- this.slice = slice
436
- this.move = move
437
- }
438
- }
439
-
440
- function dropPos(slice, $pos) {
441
- if (!slice || !slice.content.size) return $pos.pos
442
- let content = slice.content
443
- for (let i = 0; i < slice.openLeft; i++) content = content.firstChild.content
444
- for (let d = $pos.depth; d >= 0; d--) {
445
- let bias = d == $pos.depth ? 0 : $pos.pos <= ($pos.start(d + 1) + $pos.end(d + 1)) / 2 ? -1 : 1
446
- let insertPos = $pos.index(d) + (bias > 0 ? 1 : 0)
447
- if ($pos.node(d).canReplace(insertPos, insertPos, content))
448
- return bias == 0 ? $pos.pos : bias < 0 ? $pos.before(d + 1) : $pos.after(d + 1)
449
- }
450
- return $pos.pos
451
- }
452
-
453
- handlers.dragstart = (view, e) => {
454
- let mouseDown = view.mouseDown
455
- if (mouseDown) mouseDown.done()
456
- if (!e.dataTransfer) return
457
-
458
- let sel = view.state.selection
459
- let pos = sel.empty ? null : view.posAtCoords(eventCoords(e))
460
- if (pos && pos.pos >= sel.from && pos.pos <= sel.to) {
461
- // In selection
462
- } else if (mouseDown && mouseDown.mightDrag) {
463
- view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, mouseDown.mightDrag.pos)))
464
- } else {
465
- return
466
- }
467
- let slice = view.state.selection.content(), dom = serializeForClipboard(view, slice)
468
- e.dataTransfer.clearData()
469
- e.dataTranser.setData("text/html", dom.innerHTML)
470
- e.dataTranser.setData("text/plain", slice.content.textBetween(0, slice.content.size, "\n\n"))
471
- view.dragging = new Dragging(slice, !e.ctrlKey)
472
- }
473
-
474
- handlers.dragend = view => {
475
- window.setTimeout(() => view.dragging = null, 50)
476
- }
477
-
478
- editHandlers.dragover = editHandlers.dragenter = (_, e) => e.preventDefault()
479
-
480
- editHandlers.drop = (view, e) => {
481
- let dragging = view.dragging
482
- view.dragging = null
483
-
484
- if (!e.dataTransfer) return
485
-
486
- let $mouse = view.state.doc.resolve(view.posAtCoords(eventCoords(e)).pos)
487
- if (!$mouse) return
488
- let slice = dragging && dragging.slice ||
489
- parseFromClipboard(view, e.dataTransfer.getData("text/plain"), e.dataTransfer.getData("text/html"), false, $mouse)
490
- if (!slice) return
491
-
492
- e.preventDefault()
493
- if (view.someProp("handleDrop", e, slice, dragging && dragging.move)) return
494
- let insertPos = dropPos(slice, view.state.doc.resolve($mouse.pos))
495
-
496
- let tr = view.state.tr
497
- if (dragging && dragging.move) tr.deleteSelection()
498
-
499
- let pos = tr.mapping.map(insertPos)
500
- let isNode = slice.openLeft == 0 && slice.openRight == 0 && slice.content.childCount == 1
501
- if (isNode)
502
- tr.replaceRangeWith(pos, pos, slice.content.firstChild)
503
- else
504
- tr.replaceRange(pos, pos, slice)
505
- let $pos = tr.doc.resolve(pos)
506
- if (isNode && NodeSelection.isSelectable(slice.content.firstChild) &&
507
- $pos.nodeAfter && $pos.nodeAfter.sameMarkup(slice.content.firstChild))
508
- tr.setSelection(new NodeSelection($pos))
509
- else
510
- tr.setSelection(selectionBetween(view, $pos, tr.doc.resolve(tr.mapping.map(insertPos))))
511
- view.focus()
512
- view.dispatch(tr)
513
- }
514
-
515
- handlers.focus = (view, event) => {
516
- if (!view.focused) {
517
- view.dom.classList.add("ProseMirror-focused")
518
- view.focused = true
519
- }
520
- view.someProp("onFocus", f => { f(view, event) })
521
- }
522
-
523
- handlers.blur = (view, event) => {
524
- if (view.focused) {
525
- view.dom.classList.remove("ProseMirror-focused")
526
- view.focused = false
527
- }
528
- view.someProp("onBlur", f => { f(view, event) })
529
- }
530
-
531
- // Make sure all handlers get registered
532
- for (let prop in editHandlers) handlers[prop] = editHandlers[prop]