prosemirror-view 1.18.7 → 1.18.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prosemirror-view",
3
- "version": "1.18.7",
3
+ "version": "1.18.11",
4
4
  "description": "ProseMirror's view component",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.es.js",
@@ -18,7 +18,7 @@
18
18
  "url": "git://github.com/prosemirror/prosemirror-view.git"
19
19
  },
20
20
  "dependencies": {
21
- "prosemirror-model": "^1.1.0",
21
+ "prosemirror-model": "^1.14.3",
22
22
  "prosemirror-state": "^1.0.0",
23
23
  "prosemirror-transform": "^1.1.0"
24
24
  },
package/src/clipboard.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {Slice, Fragment, DOMParser, DOMSerializer} from "prosemirror-model"
2
+ import browser from "./browser"
2
3
 
3
4
  export function serializeForClipboard(view, slice) {
4
5
  let context = [], {content, openStart, openEnd} = slice
@@ -46,14 +47,17 @@ export function parseFromClipboard(view, text, html, plainText, $context) {
46
47
  if (parsed) {
47
48
  slice = parsed
48
49
  } else {
50
+ let marks = $context.marks()
51
+ let {schema} = view.state, serializer = DOMSerializer.fromSchema(schema)
49
52
  dom = document.createElement("div")
50
53
  text.trim().split(/(?:\r\n?|\n)+/).forEach(block => {
51
- dom.appendChild(document.createElement("p")).textContent = block
54
+ dom.appendChild(document.createElement("p")).appendChild(serializer.serializeNode(schema.text(block, marks)))
52
55
  })
53
56
  }
54
57
  } else {
55
58
  view.someProp("transformPastedHTML", f => { html = f(html) })
56
59
  dom = readHTML(html)
60
+ if (browser.webkit) restoreReplacedSpaces(dom)
57
61
  }
58
62
 
59
63
  let contextNode = dom && dom.querySelector("[data-pm-slice]")
@@ -178,6 +182,20 @@ function readHTML(html) {
178
182
  return elt
179
183
  }
180
184
 
185
+ // Webkit browsers do some hard-to-predict replacement of regular
186
+ // spaces with non-breaking spaces when putting content on the
187
+ // clipboard. This tries to convert such non-breaking spaces (which
188
+ // will be wrapped in a plain span on Chrome, a span with class
189
+ // Apple-converted-space on Safari) back to regular spaces.
190
+ function restoreReplacedSpaces(dom) {
191
+ let nodes = dom.querySelectorAll(browser.chrome ? "span:not([class]):not([style])" : "span.Apple-converted-space")
192
+ for (let i = 0; i < nodes.length; i++) {
193
+ let node = nodes[i]
194
+ if (node.childNodes.length == 1 && node.textContent == "\u00a0" && node.parentNode)
195
+ node.parentNode.replaceChild(dom.ownerDocument.createTextNode(" "), node)
196
+ }
197
+ }
198
+
181
199
  function addContext(slice, context) {
182
200
  if (!slice.size) return slice
183
201
  let schema = slice.content.firstChild.type.schema, array
package/src/input.js CHANGED
@@ -100,7 +100,7 @@ export function dispatchEvent(view, event) {
100
100
  editHandlers.keydown = (view, event) => {
101
101
  view.shiftKey = event.keyCode == 16 || event.shiftKey
102
102
  if (inOrNearComposition(view, event)) return
103
- view.domObserver.forceFlush()
103
+ if (event.keyCode != 229) view.domObserver.forceFlush()
104
104
  view.lastKeyCode = event.keyCode
105
105
  view.lastKeyCodeTime = Date.now()
106
106
  // On iOS, if we preventDefault enter key presses, the virtual
@@ -220,10 +220,11 @@ function handleDoubleClick(view, pos, inside, event) {
220
220
  function handleTripleClick(view, pos, inside, event) {
221
221
  return runHandlerOnContext(view, "handleTripleClickOn", pos, inside, event) ||
222
222
  view.someProp("handleTripleClick", f => f(view, pos, event)) ||
223
- defaultTripleClick(view, inside)
223
+ defaultTripleClick(view, inside, event)
224
224
  }
225
225
 
226
- function defaultTripleClick(view, inside) {
226
+ function defaultTripleClick(view, inside, event) {
227
+ if (event.button != 0) return false
227
228
  let doc = view.state.doc
228
229
  if (inside == -1) {
229
230
  if (doc.inlineContent) {
@@ -303,7 +304,8 @@ class MouseDown {
303
304
  this.target = targetDesc ? targetDesc.dom : null
304
305
 
305
306
  let {selection} = view.state
306
- if (targetNode.type.spec.draggable && targetNode.type.spec.selectable !== false ||
307
+ if (event.button == 0 &&
308
+ targetNode.type.spec.draggable && targetNode.type.spec.selectable !== false ||
307
309
  selection instanceof NodeSelection && selection.from <= targetPos && selection.to > targetPos)
308
310
  this.mightDrag = {node: targetNode,
309
311
  pos: targetPos,
@@ -350,19 +352,20 @@ class MouseDown {
350
352
  setSelectionOrigin(this.view, "pointer")
351
353
  } else if (handleSingleClick(this.view, pos.pos, pos.inside, event, this.selectNode)) {
352
354
  event.preventDefault()
353
- } else if (this.flushed ||
354
- // Safari ignores clicks on draggable elements
355
- (browser.safari && this.mightDrag && !this.mightDrag.node.isAtom) ||
356
- // Chrome will sometimes treat a node selection as a
357
- // cursor, but still report that the node is selected
358
- // when asked through getSelection. You'll then get a
359
- // situation where clicking at the point where that
360
- // (hidden) cursor is doesn't change the selection, and
361
- // thus doesn't get a reaction from ProseMirror. This
362
- // works around that.
363
- (browser.chrome && !(this.view.state.selection instanceof TextSelection) &&
364
- Math.min(Math.abs(pos.pos - this.view.state.selection.from),
365
- Math.abs(pos.pos - this.view.state.selection.to)) <= 2)) {
355
+ } else if (event.button == 0 &&
356
+ (this.flushed ||
357
+ // Safari ignores clicks on draggable elements
358
+ (browser.safari && this.mightDrag && !this.mightDrag.node.isAtom) ||
359
+ // Chrome will sometimes treat a node selection as a
360
+ // cursor, but still report that the node is selected
361
+ // when asked through getSelection. You'll then get a
362
+ // situation where clicking at the point where that
363
+ // (hidden) cursor is doesn't change the selection, and
364
+ // thus doesn't get a reaction from ProseMirror. This
365
+ // works around that.
366
+ (browser.chrome && !(this.view.state.selection instanceof TextSelection) &&
367
+ Math.min(Math.abs(pos.pos - this.view.state.selection.from),
368
+ Math.abs(pos.pos - this.view.state.selection.to)) <= 2))) {
366
369
  updateSelection(this.view, Selection.near(this.view.state.doc.resolve(pos.pos)), "pointer")
367
370
  event.preventDefault()
368
371
  } else {
@@ -577,8 +580,8 @@ handlers.dragstart = (view, e) => {
577
580
  view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, mouseDown.mightDrag.pos)))
578
581
  } else if (e.target && e.target.nodeType == 1) {
579
582
  let desc = view.docView.nearestDesc(e.target, true)
580
- if (!desc || !desc.node.type.spec.draggable || desc == view.docView) return
581
- view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, desc.posBefore)))
583
+ if (desc && desc.node.type.spec.draggable && desc != view.docView)
584
+ view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, desc.posBefore)))
582
585
  }
583
586
  let slice = view.state.selection.content(), {dom, text} = serializeForClipboard(view, slice)
584
587
  e.dataTransfer.clearData()
package/src/viewdesc.js CHANGED
@@ -113,7 +113,7 @@ class ViewDesc {
113
113
  matchesWidget() { return false }
114
114
  matchesMark() { return false }
115
115
  matchesNode() { return false }
116
- matchesHack() { return false }
116
+ matchesHack(_nodeName) { return false }
117
117
 
118
118
  get beforePosition() { return false }
119
119
 
@@ -374,9 +374,17 @@ class ViewDesc {
374
374
  if (node.nodeType == 3) {
375
375
  brKludge = offset && node.nodeValue[offset - 1] == "\n"
376
376
  // Issue #1128
377
- if (brKludge && offset == node.nodeValue.length &&
378
- node.nextSibling && node.nextSibling.nodeName == "BR")
379
- anchorDOM = headDOM = {node: node.parentNode, offset: domIndex(node) + 1}
377
+ if (brKludge && offset == node.nodeValue.length) {
378
+ for (let scan = node, after; scan; scan = scan.parentNode) {
379
+ if (after = scan.nextSibling) {
380
+ if (after.nodeName == "BR")
381
+ anchorDOM = headDOM = {node: after.parentNode, offset: domIndex(after) + 1}
382
+ break
383
+ }
384
+ let desc = scan.pmViewDesc
385
+ if (desc && desc.node && desc.node.isBlock) break
386
+ }
387
+ }
380
388
  } else {
381
389
  let prev = node.childNodes[offset - 1]
382
390
  brKludge = prev && (prev.nodeName == "BR" || prev.contentEditable == "false")
@@ -445,7 +453,7 @@ class ViewDesc {
445
453
  else child.markDirty(from - startInside, to - startInside)
446
454
  return
447
455
  } else {
448
- child.dirty = NODE_DIRTY
456
+ child.dirty = child.dom == child.contentDOM && child.dom.parentNode == this.contentDOM ? CONTENT_DIRTY : NODE_DIRTY
449
457
  }
450
458
  }
451
459
  offset = end
@@ -668,8 +676,10 @@ class NodeViewDesc extends ViewDesc {
668
676
  // `this.children`.
669
677
  updateChildren(view, pos) {
670
678
  let inline = this.node.inlineContent, off = pos
671
- let composition = inline && view.composing && this.localCompositionNode(view, pos)
672
- let updater = new ViewTreeUpdater(this, composition && composition.node)
679
+ let composition = view.composing && this.localCompositionInfo(view, pos)
680
+ let localComposition = composition && composition.pos > -1 ? composition : null
681
+ let compositionInChild = composition && composition.pos < 0
682
+ let updater = new ViewTreeUpdater(this, localComposition && localComposition.node)
673
683
  iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => {
674
684
  if (widget.spec.marks)
675
685
  updater.syncToMarks(widget.spec.marks, inline, view)
@@ -681,13 +691,21 @@ class NodeViewDesc extends ViewDesc {
681
691
  }, (child, outerDeco, innerDeco, i) => {
682
692
  // Make sure the wrapping mark descs match the node's marks.
683
693
  updater.syncToMarks(child.marks, inline, view)
684
- // Either find an existing desc that exactly matches this node,
685
- // and drop the descs before it.
686
- updater.findNodeMatch(child, outerDeco, innerDeco, i) ||
687
- // Or try updating the next desc to reflect this node.
688
- updater.updateNextNode(child, outerDeco, innerDeco, view, i) ||
689
- // Or just add it as a new desc.
694
+ // Try several strategies for drawing this node
695
+ let compIndex
696
+ if (updater.findNodeMatch(child, outerDeco, innerDeco, i)) {
697
+ // Found precise match with existing node view
698
+ } else if (compositionInChild && view.state.selection.from > off &&
699
+ view.state.selection.to < off + child.nodeSize &&
700
+ (compIndex = updater.findIndexWithChild(composition.node)) > -1 &&
701
+ updater.updateNodeAt(child, outerDeco, innerDeco, compIndex, view)) {
702
+ // Updated the specific node that holds the composition
703
+ } else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i)) {
704
+ // Could update an existing node to reflect this node
705
+ } else {
706
+ // Add it as a new view
690
707
  updater.addNode(child, outerDeco, innerDeco, view, off)
708
+ }
691
709
  off += child.nodeSize
692
710
  })
693
711
  // Drop all remaining descs after the current position.
@@ -698,29 +716,31 @@ class NodeViewDesc extends ViewDesc {
698
716
  // Sync the DOM if anything changed
699
717
  if (updater.changed || this.dirty == CONTENT_DIRTY) {
700
718
  // May have to protect focused DOM from being changed if a composition is active
701
- if (composition) this.protectLocalComposition(view, composition)
719
+ if (localComposition) this.protectLocalComposition(view, localComposition)
702
720
  renderDescs(this.contentDOM, this.children, view)
703
721
  if (browser.ios) iosHacks(this.dom)
704
722
  }
705
723
  }
706
724
 
707
- localCompositionNode(view, pos) {
725
+ localCompositionInfo(view, pos) {
708
726
  // Only do something if both the selection and a focused text node
709
- // are inside of this node, and the node isn't already part of a
710
- // view that's a child of this view
727
+ // are inside of this node
711
728
  let {from, to} = view.state.selection
712
729
  if (!(view.state.selection instanceof TextSelection) || from < pos || to > pos + this.node.content.size) return
713
730
  let sel = view.root.getSelection()
714
731
  let textNode = nearbyTextNode(sel.focusNode, sel.focusOffset)
715
732
  if (!textNode || !this.dom.contains(textNode.parentNode)) return
716
733
 
717
- // Find the text in the focused node in the node, stop if it's not
718
- // there (may have been modified through other means, in which
719
- // case it should overwritten)
720
- let text = textNode.nodeValue
721
- let textPos = findTextInFragment(this.node.content, text, from - pos, to - pos)
722
-
723
- return textPos < 0 ? null : {node: textNode, pos: textPos, text}
734
+ if (this.node.inlineContent) {
735
+ // Find the text in the focused node in the node, stop if it's not
736
+ // there (may have been modified through other means, in which
737
+ // case it should overwritten)
738
+ let text = textNode.nodeValue
739
+ let textPos = findTextInFragment(this.node.content, text, from - pos, to - pos)
740
+ return textPos < 0 ? null : {node: textNode, pos: textPos, text}
741
+ } else {
742
+ return {node: textNode, pos: -1}
743
+ }
724
744
  }
725
745
 
726
746
  protectLocalComposition(view, {node, pos, text}) {
@@ -844,6 +864,12 @@ class TextViewDesc extends NodeViewDesc {
844
864
  return new TextViewDesc(this.parent, node, this.outerDeco, this.innerDeco, dom, dom, view)
845
865
  }
846
866
 
867
+ markDirty(from, to) {
868
+ super.markDirty(from, to)
869
+ if (this.dom != this.nodeDOM && (from == 0 || to == this.nodeDOM.nodeValue.length))
870
+ this.dirty = NODE_DIRTY
871
+ }
872
+
847
873
  get domAtom() { return false }
848
874
  }
849
875
 
@@ -851,7 +877,7 @@ class TextViewDesc extends NodeViewDesc {
851
877
  // around contentEditable terribleness.
852
878
  class TrailingHackViewDesc extends ViewDesc {
853
879
  parseRule() { return {ignore: true} }
854
- matchesHack() { return this.dirty == NOT_DIRTY }
880
+ matchesHack(nodeName) { return this.dirty == NOT_DIRTY && this.dom.nodeName == nodeName }
855
881
  get domAtom() { return true }
856
882
  }
857
883
 
@@ -1132,6 +1158,29 @@ class ViewTreeUpdater {
1132
1158
  return true
1133
1159
  }
1134
1160
 
1161
+ updateNodeAt(node, outerDeco, innerDeco, index, view) {
1162
+ let child = this.top.children[index]
1163
+ if (!child.update(node, outerDeco, innerDeco, view)) return false
1164
+ this.destroyBetween(this.index, index)
1165
+ this.index = index + 1
1166
+ return true
1167
+ }
1168
+
1169
+ findIndexWithChild(domNode) {
1170
+ for (;;) {
1171
+ let parent = domNode.parentNode
1172
+ if (!parent) return -1
1173
+ if (parent == this.top.contentDOM) {
1174
+ let desc = domNode.pmViewDesc
1175
+ if (desc) for (let i = this.index; i < this.top.children.length; i++) {
1176
+ if (this.top.children[i] == desc) return i
1177
+ }
1178
+ return -1
1179
+ }
1180
+ domNode = parent
1181
+ }
1182
+ }
1183
+
1135
1184
  // : (Node, [Decoration], DecorationSource, EditorView, Fragment, number) → bool
1136
1185
  // Try to update the next node, if any, to the given data. Checks
1137
1186
  // pre-matches to avoid overwriting nodes that could still be used.
@@ -1188,13 +1237,20 @@ class ViewTreeUpdater {
1188
1237
  if (!lastChild || // Empty textblock
1189
1238
  !(lastChild instanceof TextViewDesc) ||
1190
1239
  /\n$/.test(lastChild.node.text)) {
1191
- if (this.index < this.top.children.length && this.top.children[this.index].matchesHack()) {
1192
- this.index++
1193
- } else {
1194
- let dom = document.createElement("br")
1195
- this.top.children.splice(this.index++, 0, new TrailingHackViewDesc(this.top, nothing, dom, null))
1196
- this.changed = true
1197
- }
1240
+ // Avoid a bug in Safari's cursor drawing (#1165)
1241
+ if (browser.safari && lastChild && lastChild.dom.contentEditable == "false")
1242
+ this.addHackNode("IMG")
1243
+ this.addHackNode("BR")
1244
+ }
1245
+ }
1246
+
1247
+ addHackNode(nodeName) {
1248
+ if (this.index < this.top.children.length && this.top.children[this.index].matchesHack(nodeName)) {
1249
+ this.index++
1250
+ } else {
1251
+ let dom = document.createElement(nodeName)
1252
+ this.top.children.splice(this.index++, 0, new TrailingHackViewDesc(this.top, nothing, dom, null))
1253
+ this.changed = true
1198
1254
  }
1199
1255
  }
1200
1256
  }