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/CHANGELOG.md CHANGED
@@ -1,3 +1,37 @@
1
+ ## 1.18.11 (2021-07-22)
2
+
3
+ ### Bug fixes
4
+
5
+ Work around an issue where Chrome and Safari will replace some spaces with non-breaking spaces when putting HTML on the clipboard.
6
+
7
+ When pasting as plain text (shift-mod-v) apply the marks at the selection to the inserted content.
8
+
9
+ Fix flaky behavior when starting a composition with a selection that spans multiple blocks.
10
+
11
+ ## 1.18.10 (2021-07-15)
12
+
13
+ ### Bug fixes
14
+
15
+ Fix an issue where dragging from just outside a draggable node on Chrome would cause odd dragging behavior.
16
+
17
+ ## 1.18.9 (2021-07-11)
18
+
19
+ ### Bug fixes
20
+
21
+ Fix a bug in the previous release where `handleClickOn` wasn't fired anymore for clicks with the middle or right mouse button.
22
+
23
+ ## 1.18.8 (2021-06-23)
24
+
25
+ ### Bug fixes
26
+
27
+ Work around a Safari bug where it draws the cursor at the start of the line when it is after an uneditable node at the end of the line.
28
+
29
+ Fix an issue where the DOM could get out of sync when editing decorated text.
30
+
31
+ Work around an issue where Firefox draws the cursor on the wrong line when after a newline.
32
+
33
+ Fix a bug where double-clicking with the left mouse button and then pressing another mouse button was treated as a triple click.
34
+
1
35
  ## 1.18.7 (2021-05-20)
2
36
 
3
37
  ### Bug fixes
package/dist/index.es.js CHANGED
@@ -694,7 +694,7 @@ var prototypeAccessors = { beforePosition: { configurable: true },size: { config
694
694
  ViewDesc.prototype.matchesWidget = function matchesWidget () { return false };
695
695
  ViewDesc.prototype.matchesMark = function matchesMark () { return false };
696
696
  ViewDesc.prototype.matchesNode = function matchesNode () { return false };
697
- ViewDesc.prototype.matchesHack = function matchesHack () { return false };
697
+ ViewDesc.prototype.matchesHack = function matchesHack (_nodeName) { return false };
698
698
 
699
699
  prototypeAccessors.beforePosition.get = function () { return false };
700
700
 
@@ -960,9 +960,17 @@ ViewDesc.prototype.setSelection = function setSelection (anchor, head, root, for
960
960
  if (node.nodeType == 3) {
961
961
  brKludge = offset$1 && node.nodeValue[offset$1 - 1] == "\n";
962
962
  // Issue #1128
963
- if (brKludge && offset$1 == node.nodeValue.length &&
964
- node.nextSibling && node.nextSibling.nodeName == "BR")
965
- { anchorDOM = headDOM = {node: node.parentNode, offset: domIndex(node) + 1}; }
963
+ if (brKludge && offset$1 == node.nodeValue.length) {
964
+ for (var scan = node, after = (void 0); scan; scan = scan.parentNode) {
965
+ if (after = scan.nextSibling) {
966
+ if (after.nodeName == "BR")
967
+ { anchorDOM = headDOM = {node: after.parentNode, offset: domIndex(after) + 1}; }
968
+ break
969
+ }
970
+ var desc = scan.pmViewDesc;
971
+ if (desc && desc.node && desc.node.isBlock) { break }
972
+ }
973
+ }
966
974
  } else {
967
975
  var prev = node.childNodes[offset$1 - 1];
968
976
  brKludge = prev && (prev.nodeName == "BR" || prev.contentEditable == "false");
@@ -971,8 +979,8 @@ ViewDesc.prototype.setSelection = function setSelection (anchor, head, root, for
971
979
  // Firefox can act strangely when the selection is in front of an
972
980
  // uneditable node. See #1163 and https://bugzilla.mozilla.org/show_bug.cgi?id=1709536
973
981
  if (result.gecko && domSel.focusNode && domSel.focusNode != headDOM.node && domSel.focusNode.nodeType == 1) {
974
- var after = domSel.focusNode.childNodes[domSel.focusOffset];
975
- if (after && after.contentEditable == "false") { force = true; }
982
+ var after$1 = domSel.focusNode.childNodes[domSel.focusOffset];
983
+ if (after$1 && after$1.contentEditable == "false") { force = true; }
976
984
  }
977
985
 
978
986
  if (!(force || brKludge && result.safari) &&
@@ -1031,7 +1039,7 @@ ViewDesc.prototype.markDirty = function markDirty (from, to) {
1031
1039
  else { child.markDirty(from - startInside, to - startInside); }
1032
1040
  return
1033
1041
  } else {
1034
- child.dirty = NODE_DIRTY;
1042
+ child.dirty = child.dom == child.contentDOM && child.dom.parentNode == this.contentDOM ? CONTENT_DIRTY : NODE_DIRTY;
1035
1043
  }
1036
1044
  }
1037
1045
  offset = end;
@@ -1290,8 +1298,10 @@ var NodeViewDesc = /*@__PURE__*/(function (ViewDesc) {
1290
1298
  var this$1 = this;
1291
1299
 
1292
1300
  var inline = this.node.inlineContent, off = pos;
1293
- var composition = inline && view.composing && this.localCompositionNode(view, pos);
1294
- var updater = new ViewTreeUpdater(this, composition && composition.node);
1301
+ var composition = view.composing && this.localCompositionInfo(view, pos);
1302
+ var localComposition = composition && composition.pos > -1 ? composition : null;
1303
+ var compositionInChild = composition && composition.pos < 0;
1304
+ var updater = new ViewTreeUpdater(this, localComposition && localComposition.node);
1295
1305
  iterDeco(this.node, this.innerDeco, function (widget, i, insideNode) {
1296
1306
  if (widget.spec.marks)
1297
1307
  { updater.syncToMarks(widget.spec.marks, inline, view); }
@@ -1303,13 +1313,15 @@ var NodeViewDesc = /*@__PURE__*/(function (ViewDesc) {
1303
1313
  }, function (child, outerDeco, innerDeco, i) {
1304
1314
  // Make sure the wrapping mark descs match the node's marks.
1305
1315
  updater.syncToMarks(child.marks, inline, view);
1306
- // Either find an existing desc that exactly matches this node,
1307
- // and drop the descs before it.
1308
- updater.findNodeMatch(child, outerDeco, innerDeco, i) ||
1309
- // Or try updating the next desc to reflect this node.
1310
- updater.updateNextNode(child, outerDeco, innerDeco, view, i) ||
1311
- // Or just add it as a new desc.
1316
+ // Try several strategies for drawing this node
1317
+ var compIndex;
1318
+ if (updater.findNodeMatch(child, outerDeco, innerDeco, i)) ; else if (compositionInChild && view.state.selection.from > off &&
1319
+ view.state.selection.to < off + child.nodeSize &&
1320
+ (compIndex = updater.findIndexWithChild(composition.node)) > -1 &&
1321
+ updater.updateNodeAt(child, outerDeco, innerDeco, compIndex, view)) ; else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i)) ; else {
1322
+ // Add it as a new view
1312
1323
  updater.addNode(child, outerDeco, innerDeco, view, off);
1324
+ }
1313
1325
  off += child.nodeSize;
1314
1326
  });
1315
1327
  // Drop all remaining descs after the current position.
@@ -1320,16 +1332,15 @@ var NodeViewDesc = /*@__PURE__*/(function (ViewDesc) {
1320
1332
  // Sync the DOM if anything changed
1321
1333
  if (updater.changed || this.dirty == CONTENT_DIRTY) {
1322
1334
  // May have to protect focused DOM from being changed if a composition is active
1323
- if (composition) { this.protectLocalComposition(view, composition); }
1335
+ if (localComposition) { this.protectLocalComposition(view, localComposition); }
1324
1336
  renderDescs(this.contentDOM, this.children, view);
1325
1337
  if (result.ios) { iosHacks(this.dom); }
1326
1338
  }
1327
1339
  };
1328
1340
 
1329
- NodeViewDesc.prototype.localCompositionNode = function localCompositionNode (view, pos) {
1341
+ NodeViewDesc.prototype.localCompositionInfo = function localCompositionInfo (view, pos) {
1330
1342
  // Only do something if both the selection and a focused text node
1331
- // are inside of this node, and the node isn't already part of a
1332
- // view that's a child of this view
1343
+ // are inside of this node
1333
1344
  var ref = view.state.selection;
1334
1345
  var from = ref.from;
1335
1346
  var to = ref.to;
@@ -1338,13 +1349,16 @@ var NodeViewDesc = /*@__PURE__*/(function (ViewDesc) {
1338
1349
  var textNode = nearbyTextNode(sel.focusNode, sel.focusOffset);
1339
1350
  if (!textNode || !this.dom.contains(textNode.parentNode)) { return }
1340
1351
 
1341
- // Find the text in the focused node in the node, stop if it's not
1342
- // there (may have been modified through other means, in which
1343
- // case it should overwritten)
1344
- var text = textNode.nodeValue;
1345
- var textPos = findTextInFragment(this.node.content, text, from - pos, to - pos);
1346
-
1347
- return textPos < 0 ? null : {node: textNode, pos: textPos, text: text}
1352
+ if (this.node.inlineContent) {
1353
+ // Find the text in the focused node in the node, stop if it's not
1354
+ // there (may have been modified through other means, in which
1355
+ // case it should overwritten)
1356
+ var text = textNode.nodeValue;
1357
+ var textPos = findTextInFragment(this.node.content, text, from - pos, to - pos);
1358
+ return textPos < 0 ? null : {node: textNode, pos: textPos, text: text}
1359
+ } else {
1360
+ return {node: textNode, pos: -1}
1361
+ }
1348
1362
  };
1349
1363
 
1350
1364
  NodeViewDesc.prototype.protectLocalComposition = function protectLocalComposition (view, ref) {
@@ -1482,6 +1496,12 @@ var TextViewDesc = /*@__PURE__*/(function (NodeViewDesc) {
1482
1496
  return new TextViewDesc(this.parent, node, this.outerDeco, this.innerDeco, dom, dom, view)
1483
1497
  };
1484
1498
 
1499
+ TextViewDesc.prototype.markDirty = function markDirty (from, to) {
1500
+ NodeViewDesc.prototype.markDirty.call(this, from, to);
1501
+ if (this.dom != this.nodeDOM && (from == 0 || to == this.nodeDOM.nodeValue.length))
1502
+ { this.dirty = NODE_DIRTY; }
1503
+ };
1504
+
1485
1505
  prototypeAccessors$4.domAtom.get = function () { return false };
1486
1506
 
1487
1507
  Object.defineProperties( TextViewDesc.prototype, prototypeAccessors$4 );
@@ -1503,7 +1523,7 @@ var TrailingHackViewDesc = /*@__PURE__*/(function (ViewDesc) {
1503
1523
  var prototypeAccessors$5 = { domAtom: { configurable: true } };
1504
1524
 
1505
1525
  TrailingHackViewDesc.prototype.parseRule = function parseRule () { return {ignore: true} };
1506
- TrailingHackViewDesc.prototype.matchesHack = function matchesHack () { return this.dirty == NOT_DIRTY };
1526
+ TrailingHackViewDesc.prototype.matchesHack = function matchesHack (nodeName) { return this.dirty == NOT_DIRTY && this.dom.nodeName == nodeName };
1507
1527
  prototypeAccessors$5.domAtom.get = function () { return true };
1508
1528
 
1509
1529
  Object.defineProperties( TrailingHackViewDesc.prototype, prototypeAccessors$5 );
@@ -1791,6 +1811,29 @@ ViewTreeUpdater.prototype.findNodeMatch = function findNodeMatch (node, outerDec
1791
1811
  return true
1792
1812
  };
1793
1813
 
1814
+ ViewTreeUpdater.prototype.updateNodeAt = function updateNodeAt (node, outerDeco, innerDeco, index, view) {
1815
+ var child = this.top.children[index];
1816
+ if (!child.update(node, outerDeco, innerDeco, view)) { return false }
1817
+ this.destroyBetween(this.index, index);
1818
+ this.index = index + 1;
1819
+ return true
1820
+ };
1821
+
1822
+ ViewTreeUpdater.prototype.findIndexWithChild = function findIndexWithChild (domNode) {
1823
+ for (;;) {
1824
+ var parent = domNode.parentNode;
1825
+ if (!parent) { return -1 }
1826
+ if (parent == this.top.contentDOM) {
1827
+ var desc = domNode.pmViewDesc;
1828
+ if (desc) { for (var i = this.index; i < this.top.children.length; i++) {
1829
+ if (this.top.children[i] == desc) { return i }
1830
+ } }
1831
+ return -1
1832
+ }
1833
+ domNode = parent;
1834
+ }
1835
+ };
1836
+
1794
1837
  // : (Node, [Decoration], DecorationSource, EditorView, Fragment, number) → bool
1795
1838
  // Try to update the next node, if any, to the given data. Checks
1796
1839
  // pre-matches to avoid overwriting nodes that could still be used.
@@ -1847,13 +1890,20 @@ ViewTreeUpdater.prototype.addTextblockHacks = function addTextblockHacks () {
1847
1890
  if (!lastChild || // Empty textblock
1848
1891
  !(lastChild instanceof TextViewDesc) ||
1849
1892
  /\n$/.test(lastChild.node.text)) {
1850
- if (this.index < this.top.children.length && this.top.children[this.index].matchesHack()) {
1851
- this.index++;
1852
- } else {
1853
- var dom = document.createElement("br");
1854
- this.top.children.splice(this.index++, 0, new TrailingHackViewDesc(this.top, nothing, dom, null));
1855
- this.changed = true;
1856
- }
1893
+ // Avoid a bug in Safari's cursor drawing (#1165)
1894
+ if (result.safari && lastChild && lastChild.dom.contentEditable == "false")
1895
+ { this.addHackNode("IMG"); }
1896
+ this.addHackNode("BR");
1897
+ }
1898
+ };
1899
+
1900
+ ViewTreeUpdater.prototype.addHackNode = function addHackNode (nodeName) {
1901
+ if (this.index < this.top.children.length && this.top.children[this.index].matchesHack(nodeName)) {
1902
+ this.index++;
1903
+ } else {
1904
+ var dom = document.createElement(nodeName);
1905
+ this.top.children.splice(this.index++, 0, new TrailingHackViewDesc(this.top, nothing, dom, null));
1906
+ this.changed = true;
1857
1907
  }
1858
1908
  };
1859
1909
 
@@ -2873,14 +2923,19 @@ function parseFromClipboard(view, text, html, plainText, $context) {
2873
2923
  if (parsed) {
2874
2924
  slice = parsed;
2875
2925
  } else {
2926
+ var marks = $context.marks();
2927
+ var ref = view.state;
2928
+ var schema = ref.schema;
2929
+ var serializer = DOMSerializer.fromSchema(schema);
2876
2930
  dom = document.createElement("div");
2877
2931
  text.trim().split(/(?:\r\n?|\n)+/).forEach(function (block) {
2878
- dom.appendChild(document.createElement("p")).textContent = block;
2932
+ dom.appendChild(document.createElement("p")).appendChild(serializer.serializeNode(schema.text(block, marks)));
2879
2933
  });
2880
2934
  }
2881
2935
  } else {
2882
2936
  view.someProp("transformPastedHTML", function (f) { html = f(html); });
2883
2937
  dom = readHTML(html);
2938
+ if (result.webkit) { restoreReplacedSpaces(dom); }
2884
2939
  }
2885
2940
 
2886
2941
  var contextNode = dom && dom.querySelector("[data-pm-slice]");
@@ -3013,6 +3068,20 @@ function readHTML(html) {
3013
3068
  return elt
3014
3069
  }
3015
3070
 
3071
+ // Webkit browsers do some hard-to-predict replacement of regular
3072
+ // spaces with non-breaking spaces when putting content on the
3073
+ // clipboard. This tries to convert such non-breaking spaces (which
3074
+ // will be wrapped in a plain span on Chrome, a span with class
3075
+ // Apple-converted-space on Safari) back to regular spaces.
3076
+ function restoreReplacedSpaces(dom) {
3077
+ var nodes = dom.querySelectorAll(result.chrome ? "span:not([class]):not([style])" : "span.Apple-converted-space");
3078
+ for (var i = 0; i < nodes.length; i++) {
3079
+ var node = nodes[i];
3080
+ if (node.childNodes.length == 1 && node.textContent == "\u00a0" && node.parentNode)
3081
+ { node.parentNode.replaceChild(dom.ownerDocument.createTextNode(" "), node); }
3082
+ }
3083
+ }
3084
+
3016
3085
  function addContext(slice, context) {
3017
3086
  if (!slice.size) { return slice }
3018
3087
  var schema = slice.content.firstChild.type.schema, array;
@@ -3364,7 +3433,7 @@ function dispatchEvent(view, event) {
3364
3433
  editHandlers.keydown = function (view, event) {
3365
3434
  view.shiftKey = event.keyCode == 16 || event.shiftKey;
3366
3435
  if (inOrNearComposition(view, event)) { return }
3367
- view.domObserver.forceFlush();
3436
+ if (event.keyCode != 229) { view.domObserver.forceFlush(); }
3368
3437
  view.lastKeyCode = event.keyCode;
3369
3438
  view.lastKeyCodeTime = Date.now();
3370
3439
  // On iOS, if we preventDefault enter key presses, the virtual
@@ -3490,10 +3559,11 @@ function handleDoubleClick(view, pos, inside, event) {
3490
3559
  function handleTripleClick(view, pos, inside, event) {
3491
3560
  return runHandlerOnContext(view, "handleTripleClickOn", pos, inside, event) ||
3492
3561
  view.someProp("handleTripleClick", function (f) { return f(view, pos, event); }) ||
3493
- defaultTripleClick(view, inside)
3562
+ defaultTripleClick(view, inside, event)
3494
3563
  }
3495
3564
 
3496
- function defaultTripleClick(view, inside) {
3565
+ function defaultTripleClick(view, inside, event) {
3566
+ if (event.button != 0) { return false }
3497
3567
  var doc = view.state.doc;
3498
3568
  if (inside == -1) {
3499
3569
  if (doc.inlineContent) {
@@ -3575,7 +3645,8 @@ var MouseDown = function MouseDown(view, pos, event, flushed) {
3575
3645
 
3576
3646
  var ref = view.state;
3577
3647
  var selection = ref.selection;
3578
- if (targetNode.type.spec.draggable && targetNode.type.spec.selectable !== false ||
3648
+ if (event.button == 0 &&
3649
+ targetNode.type.spec.draggable && targetNode.type.spec.selectable !== false ||
3579
3650
  selection instanceof NodeSelection && selection.from <= targetPos && selection.to > targetPos)
3580
3651
  { this.mightDrag = {node: targetNode,
3581
3652
  pos: targetPos,
@@ -3622,19 +3693,20 @@ MouseDown.prototype.up = function up (event) {
3622
3693
  setSelectionOrigin(this.view, "pointer");
3623
3694
  } else if (handleSingleClick(this.view, pos.pos, pos.inside, event, this.selectNode)) {
3624
3695
  event.preventDefault();
3625
- } else if (this.flushed ||
3626
- // Safari ignores clicks on draggable elements
3627
- (result.safari && this.mightDrag && !this.mightDrag.node.isAtom) ||
3628
- // Chrome will sometimes treat a node selection as a
3629
- // cursor, but still report that the node is selected
3630
- // when asked through getSelection. You'll then get a
3631
- // situation where clicking at the point where that
3632
- // (hidden) cursor is doesn't change the selection, and
3633
- // thus doesn't get a reaction from ProseMirror. This
3634
- // works around that.
3635
- (result.chrome && !(this.view.state.selection instanceof TextSelection) &&
3636
- Math.min(Math.abs(pos.pos - this.view.state.selection.from),
3637
- Math.abs(pos.pos - this.view.state.selection.to)) <= 2)) {
3696
+ } else if (event.button == 0 &&
3697
+ (this.flushed ||
3698
+ // Safari ignores clicks on draggable elements
3699
+ (result.safari && this.mightDrag && !this.mightDrag.node.isAtom) ||
3700
+ // Chrome will sometimes treat a node selection as a
3701
+ // cursor, but still report that the node is selected
3702
+ // when asked through getSelection. You'll then get a
3703
+ // situation where clicking at the point where that
3704
+ // (hidden) cursor is doesn't change the selection, and
3705
+ // thus doesn't get a reaction from ProseMirror. This
3706
+ // works around that.
3707
+ (result.chrome && !(this.view.state.selection instanceof TextSelection) &&
3708
+ Math.min(Math.abs(pos.pos - this.view.state.selection.from),
3709
+ Math.abs(pos.pos - this.view.state.selection.to)) <= 2))) {
3638
3710
  updateSelection(this.view, Selection.near(this.view.state.doc.resolve(pos.pos)), "pointer");
3639
3711
  event.preventDefault();
3640
3712
  } else {
@@ -3848,8 +3920,8 @@ handlers.dragstart = function (view, e) {
3848
3920
  view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, mouseDown.mightDrag.pos)));
3849
3921
  } else if (e.target && e.target.nodeType == 1) {
3850
3922
  var desc = view.docView.nearestDesc(e.target, true);
3851
- if (!desc || !desc.node.type.spec.draggable || desc == view.docView) { return }
3852
- view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, desc.posBefore)));
3923
+ if (desc && desc.node.type.spec.draggable && desc != view.docView)
3924
+ { view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, desc.posBefore))); }
3853
3925
  }
3854
3926
  var slice = view.state.selection.content();
3855
3927
  var ref = serializeForClipboard(view, slice);