prosemirror-view 1.41.3 → 1.41.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/CHANGELOG.md +20 -0
- package/dist/index.cjs +105 -21
- package/dist/index.js +102 -29
- package/package.json +1 -1
- package/src/dom.ts +1 -1
- package/src/domchange.ts +2 -2
- package/src/domcoords.ts +7 -4
- package/src/domobserver.ts +54 -6
- package/src/index.ts +2 -2
- package/src/input.ts +20 -6
- package/src/viewdesc.ts +18 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## 1.41.5 (2026-01-14)
|
|
2
|
+
|
|
3
|
+
### Bug fixes
|
|
4
|
+
|
|
5
|
+
Work around an issue where, after some kinds of changes, Chrome misreports the cursor position and breaks composition.
|
|
6
|
+
|
|
7
|
+
Improve DOM update behavior when deleting three or more nodes right before a mark, which would unnecessarily re-render the entire mark.
|
|
8
|
+
|
|
9
|
+
Work around a newly introduced bug in Safari where composition in an empty table cell causes it to move the text out of the cell into the row element.
|
|
10
|
+
|
|
11
|
+
## 1.41.4 (2025-12-01)
|
|
12
|
+
|
|
13
|
+
### Bug fixes
|
|
14
|
+
|
|
15
|
+
`EditorView.dragging` is now only cleared after the drop event has entirely been handled.
|
|
16
|
+
|
|
17
|
+
Add a workaround for an issue where Chrome and Safari will, if you backspace out the last text before an inline-flex node, insert a bogus hard break.
|
|
18
|
+
|
|
19
|
+
Fix a composition problem on Chrome Windows when starting a composition before an absolutely positioned widget.
|
|
20
|
+
|
|
1
21
|
## 1.41.3 (2025-10-09)
|
|
2
22
|
|
|
3
23
|
### Bug fixes
|
package/dist/index.cjs
CHANGED
|
@@ -360,18 +360,23 @@ function findOffsetInNode(node, coords) {
|
|
|
360
360
|
}
|
|
361
361
|
function findOffsetInText(node, coords) {
|
|
362
362
|
var len = node.nodeValue.length;
|
|
363
|
-
var range = document.createRange()
|
|
363
|
+
var range = document.createRange(),
|
|
364
|
+
result;
|
|
364
365
|
for (var i = 0; i < len; i++) {
|
|
365
366
|
range.setEnd(node, i + 1);
|
|
366
367
|
range.setStart(node, i);
|
|
367
368
|
var rect = singleRect(range, 1);
|
|
368
369
|
if (rect.top == rect.bottom) continue;
|
|
369
|
-
if (inRect(coords, rect))
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
370
|
+
if (inRect(coords, rect)) {
|
|
371
|
+
result = {
|
|
372
|
+
node: node,
|
|
373
|
+
offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)
|
|
374
|
+
};
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
373
377
|
}
|
|
374
|
-
|
|
378
|
+
range.detach();
|
|
379
|
+
return result || {
|
|
375
380
|
node: node,
|
|
376
381
|
offset: 0
|
|
377
382
|
};
|
|
@@ -1333,17 +1338,17 @@ var NodeViewDesc = function (_ViewDesc4) {
|
|
|
1333
1338
|
var compositionInChild = composition && composition.pos < 0;
|
|
1334
1339
|
var updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view);
|
|
1335
1340
|
iterDeco(this.node, this.innerDeco, function (widget, i, insideNode) {
|
|
1336
|
-
if (widget.spec.marks) updater.syncToMarks(widget.spec.marks, inline, view);else if (widget.type.side >= 0 && !insideNode) updater.syncToMarks(i == _this6.node.childCount ? prosemirrorModel.Mark.none : _this6.node.child(i).marks, inline, view);
|
|
1341
|
+
if (widget.spec.marks) updater.syncToMarks(widget.spec.marks, inline, view, i);else if (widget.type.side >= 0 && !insideNode) updater.syncToMarks(i == _this6.node.childCount ? prosemirrorModel.Mark.none : _this6.node.child(i).marks, inline, view, i);
|
|
1337
1342
|
updater.placeWidget(widget, view, off);
|
|
1338
1343
|
}, function (child, outerDeco, innerDeco, i) {
|
|
1339
|
-
updater.syncToMarks(child.marks, inline, view);
|
|
1344
|
+
updater.syncToMarks(child.marks, inline, view, i);
|
|
1340
1345
|
var compIndex;
|
|
1341
1346
|
if (updater.findNodeMatch(child, outerDeco, innerDeco, i)) ;else if (compositionInChild && view.state.selection.from > off && view.state.selection.to < off + child.nodeSize && (compIndex = updater.findIndexWithChild(composition.node)) > -1 && updater.updateNodeAt(child, outerDeco, innerDeco, compIndex, view)) ;else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i, off)) ;else {
|
|
1342
1347
|
updater.addNode(child, outerDeco, innerDeco, view, off);
|
|
1343
1348
|
}
|
|
1344
1349
|
off += child.nodeSize;
|
|
1345
1350
|
});
|
|
1346
|
-
updater.syncToMarks([], inline, view);
|
|
1351
|
+
updater.syncToMarks([], inline, view, 0);
|
|
1347
1352
|
if (this.node.isTextblock) updater.addTextblockHacks();
|
|
1348
1353
|
updater.destroyRest();
|
|
1349
1354
|
if (updater.changed || this.dirty == CONTENT_DIRTY) {
|
|
@@ -1781,7 +1786,7 @@ var ViewTreeUpdater = function () {
|
|
|
1781
1786
|
}
|
|
1782
1787
|
}, {
|
|
1783
1788
|
key: "syncToMarks",
|
|
1784
|
-
value: function syncToMarks(marks, inline, view) {
|
|
1789
|
+
value: function syncToMarks(marks, inline, view, parentIndex) {
|
|
1785
1790
|
var keep = 0,
|
|
1786
1791
|
depth = this.stack.length >> 1;
|
|
1787
1792
|
var maxKeep = Math.min(depth, marks.length);
|
|
@@ -1795,8 +1800,10 @@ var ViewTreeUpdater = function () {
|
|
|
1795
1800
|
}
|
|
1796
1801
|
while (depth < marks.length) {
|
|
1797
1802
|
this.stack.push(this.top, this.index + 1);
|
|
1798
|
-
var found = -1
|
|
1799
|
-
|
|
1803
|
+
var found = -1,
|
|
1804
|
+
scanTo = this.top.children.length;
|
|
1805
|
+
if (parentIndex < this.preMatch.index) scanTo = Math.min(this.index + 3, scanTo);
|
|
1806
|
+
for (var i = this.index; i < scanTo; i++) {
|
|
1800
1807
|
var next = this.top.children[i];
|
|
1801
1808
|
if (next.matchesMark(marks[depth]) && !this.isLocked(next.dom)) {
|
|
1802
1809
|
found = i;
|
|
@@ -2914,6 +2921,7 @@ var InputState = _createClass(function InputState() {
|
|
|
2914
2921
|
this.compositionNodes = [];
|
|
2915
2922
|
this.compositionEndedAt = -2e8;
|
|
2916
2923
|
this.compositionID = 1;
|
|
2924
|
+
this.badSafariComposition = false;
|
|
2917
2925
|
this.compositionPendingChanges = 0;
|
|
2918
2926
|
this.domChangeCount = 0;
|
|
2919
2927
|
this.eventHandlers = Object.create(null);
|
|
@@ -3272,7 +3280,7 @@ editHandlers.compositionstart = editHandlers.compositionupdate = function (view)
|
|
|
3272
3280
|
$pos = state.selection.$to;
|
|
3273
3281
|
if (state.selection instanceof prosemirrorState.TextSelection && (state.storedMarks || !$pos.textOffset && $pos.parentOffset && $pos.nodeBefore.marks.some(function (m) {
|
|
3274
3282
|
return m.type.spec.inclusive === false;
|
|
3275
|
-
}))) {
|
|
3283
|
+
}) || chrome && windows && selectionBeforeUneditable(view))) {
|
|
3276
3284
|
view.markCursor = view.state.storedMarks || $pos.marks();
|
|
3277
3285
|
endComposition(view, true);
|
|
3278
3286
|
view.markCursor = null;
|
|
@@ -3298,13 +3306,21 @@ editHandlers.compositionstart = editHandlers.compositionupdate = function (view)
|
|
|
3298
3306
|
}
|
|
3299
3307
|
scheduleComposeEnd(view, timeoutComposition);
|
|
3300
3308
|
};
|
|
3309
|
+
function selectionBeforeUneditable(view) {
|
|
3310
|
+
var _view$domSelectionRan4 = view.domSelectionRange(),
|
|
3311
|
+
focusNode = _view$domSelectionRan4.focusNode,
|
|
3312
|
+
focusOffset = _view$domSelectionRan4.focusOffset;
|
|
3313
|
+
if (!focusNode || focusNode.nodeType != 1 || focusOffset >= focusNode.childNodes.length) return false;
|
|
3314
|
+
var next = focusNode.childNodes[focusOffset];
|
|
3315
|
+
return next.nodeType == 1 && next.contentEditable == "false";
|
|
3316
|
+
}
|
|
3301
3317
|
editHandlers.compositionend = function (view, event) {
|
|
3302
3318
|
if (view.composing) {
|
|
3303
3319
|
view.input.composing = false;
|
|
3304
3320
|
view.input.compositionEndedAt = event.timeStamp;
|
|
3305
3321
|
view.input.compositionPendingChanges = view.domObserver.pendingRecords().length ? view.input.compositionID : 0;
|
|
3306
3322
|
view.input.compositionNode = null;
|
|
3307
|
-
if (view.input.compositionPendingChanges) Promise.resolve().then(function () {
|
|
3323
|
+
if (view.input.badSafariComposition) view.domObserver.forceFlush();else if (view.input.compositionPendingChanges) Promise.resolve().then(function () {
|
|
3308
3324
|
return view.domObserver.flush();
|
|
3309
3325
|
});
|
|
3310
3326
|
view.input.compositionID++;
|
|
@@ -3485,10 +3501,14 @@ handlers.dragend = function (view) {
|
|
|
3485
3501
|
editHandlers.dragover = editHandlers.dragenter = function (_, e) {
|
|
3486
3502
|
return e.preventDefault();
|
|
3487
3503
|
};
|
|
3488
|
-
editHandlers.drop = function (view,
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3504
|
+
editHandlers.drop = function (view, event) {
|
|
3505
|
+
try {
|
|
3506
|
+
handleDrop(view, event, view.dragging);
|
|
3507
|
+
} finally {
|
|
3508
|
+
view.dragging = null;
|
|
3509
|
+
}
|
|
3510
|
+
};
|
|
3511
|
+
function handleDrop(view, event, dragging) {
|
|
3492
3512
|
if (!event.dataTransfer) return;
|
|
3493
3513
|
var eventPos = view.posAtCoords(eventCoords(event));
|
|
3494
3514
|
if (!eventPos) return;
|
|
@@ -3534,7 +3554,7 @@ editHandlers.drop = function (view, _event) {
|
|
|
3534
3554
|
}
|
|
3535
3555
|
view.focus();
|
|
3536
3556
|
view.dispatch(tr.setMeta("uiEvent", "drop"));
|
|
3537
|
-
}
|
|
3557
|
+
}
|
|
3538
3558
|
handlers.focus = function (view) {
|
|
3539
3559
|
view.input.lastFocus = Date.now();
|
|
3540
3560
|
if (!view.focused) {
|
|
@@ -4218,7 +4238,16 @@ var DOMObserver = function () {
|
|
|
4218
4238
|
for (var i = 0; i < mutations.length; i++) _this11.queue.push(mutations[i]);
|
|
4219
4239
|
if (ie && ie_version <= 11 && mutations.some(function (m) {
|
|
4220
4240
|
return m.type == "childList" && m.removedNodes.length || m.type == "characterData" && m.oldValue.length > m.target.nodeValue.length;
|
|
4221
|
-
}))
|
|
4241
|
+
})) {
|
|
4242
|
+
_this11.flushSoon();
|
|
4243
|
+
} else if (safari && view.composing && mutations.some(function (m) {
|
|
4244
|
+
return m.type == "childList" && m.target.nodeName == "TR";
|
|
4245
|
+
})) {
|
|
4246
|
+
view.input.badSafariComposition = true;
|
|
4247
|
+
_this11.flushSoon();
|
|
4248
|
+
} else {
|
|
4249
|
+
_this11.flush();
|
|
4250
|
+
}
|
|
4222
4251
|
});
|
|
4223
4252
|
if (useCharData) {
|
|
4224
4253
|
this.onCharData = function (e) {
|
|
@@ -4399,6 +4428,24 @@ var DOMObserver = function () {
|
|
|
4399
4428
|
_iterator3.f();
|
|
4400
4429
|
}
|
|
4401
4430
|
}
|
|
4431
|
+
} else if ((chrome || safari) && added.some(function (n) {
|
|
4432
|
+
return n.nodeName == "BR";
|
|
4433
|
+
}) && (view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46)) {
|
|
4434
|
+
var _iterator4 = _createForOfIteratorHelper(added),
|
|
4435
|
+
_step4;
|
|
4436
|
+
try {
|
|
4437
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
4438
|
+
var node = _step4.value;
|
|
4439
|
+
if (node.nodeName == "BR" && node.parentNode) {
|
|
4440
|
+
var after = node.nextSibling;
|
|
4441
|
+
if (after && after.nodeType == 1 && after.contentEditable == "false") node.parentNode.removeChild(node);
|
|
4442
|
+
}
|
|
4443
|
+
}
|
|
4444
|
+
} catch (err) {
|
|
4445
|
+
_iterator4.e(err);
|
|
4446
|
+
} finally {
|
|
4447
|
+
_iterator4.f();
|
|
4448
|
+
}
|
|
4402
4449
|
}
|
|
4403
4450
|
var readSel = null;
|
|
4404
4451
|
if (from < 0 && newSel && view.input.lastFocus > Date.now() - 200 && Math.max(view.input.lastTouch, view.input.lastClick.time) < Date.now() - 300 && selectionCollapsed(sel) && (readSel = selectionFromDOM(view)) && readSel.eq(prosemirrorState.Selection.near(view.state.doc.resolve(0), 1))) {
|
|
@@ -4411,6 +4458,10 @@ var DOMObserver = function () {
|
|
|
4411
4458
|
view.docView.markDirty(from, to);
|
|
4412
4459
|
checkCSS(view);
|
|
4413
4460
|
}
|
|
4461
|
+
if (view.input.badSafariComposition) {
|
|
4462
|
+
view.input.badSafariComposition = false;
|
|
4463
|
+
fixUpBadSafariComposition(view, added);
|
|
4464
|
+
}
|
|
4414
4465
|
this.handleDOMChange(from, to, typeOver, added);
|
|
4415
4466
|
if (view.docView && view.docView.dirty) view.updateState(view.state);else if (!this.currentSelection.eq(sel)) selectionToDOM(view);
|
|
4416
4467
|
this.currentSelection.set(sel);
|
|
@@ -4524,6 +4575,39 @@ function blockParent(view, node) {
|
|
|
4524
4575
|
}
|
|
4525
4576
|
return null;
|
|
4526
4577
|
}
|
|
4578
|
+
function fixUpBadSafariComposition(view, addedNodes) {
|
|
4579
|
+
var _a;
|
|
4580
|
+
var _view$domSelectionRan5 = view.domSelectionRange(),
|
|
4581
|
+
focusNode = _view$domSelectionRan5.focusNode,
|
|
4582
|
+
focusOffset = _view$domSelectionRan5.focusOffset;
|
|
4583
|
+
var _iterator5 = _createForOfIteratorHelper(addedNodes),
|
|
4584
|
+
_step5;
|
|
4585
|
+
try {
|
|
4586
|
+
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
|
|
4587
|
+
var node = _step5.value;
|
|
4588
|
+
if (((_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.nodeName) == "TR") {
|
|
4589
|
+
var nextCell = node.nextSibling;
|
|
4590
|
+
while (nextCell && nextCell.nodeName != "TD" && nextCell.nodeName != "TH") nextCell = nextCell.nextSibling;
|
|
4591
|
+
if (nextCell) {
|
|
4592
|
+
var parent = nextCell;
|
|
4593
|
+
for (;;) {
|
|
4594
|
+
var first = parent.firstChild;
|
|
4595
|
+
if (!first || first.nodeType != 1 || first.contentEditable == "false" || /^(BR|IMG)$/.test(first.nodeName)) break;
|
|
4596
|
+
parent = first;
|
|
4597
|
+
}
|
|
4598
|
+
parent.insertBefore(node, parent.firstChild);
|
|
4599
|
+
if (focusNode == node) view.domSelection().collapse(node, focusOffset);
|
|
4600
|
+
} else {
|
|
4601
|
+
node.parentNode.removeChild(node);
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4605
|
+
} catch (err) {
|
|
4606
|
+
_iterator5.e(err);
|
|
4607
|
+
} finally {
|
|
4608
|
+
_iterator5.f();
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4527
4611
|
function parseBetween(view, from_, to_) {
|
|
4528
4612
|
var _view$docView$parseRa = view.docView.parseRange(from_, to_),
|
|
4529
4613
|
parent = _view$docView$parseRa.node,
|
|
@@ -4979,7 +5063,7 @@ var EditorView = function () {
|
|
|
4979
5063
|
this.docView.destroy();
|
|
4980
5064
|
this.docView = docViewDesc(state.doc, outerDeco, innerDeco, this.dom, this);
|
|
4981
5065
|
}
|
|
4982
|
-
if (chromeKludge && !this.trackWrites) forceSelUpdate = true;
|
|
5066
|
+
if (chromeKludge && (!this.trackWrites || !this.dom.contains(this.trackWrites))) forceSelUpdate = true;
|
|
4983
5067
|
}
|
|
4984
5068
|
if (forceSelUpdate || !(this.input.mouseDown && this.domObserver.currentSelection.eq(this.domSelectionRange()) && anchorInRightPlace(this))) {
|
|
4985
5069
|
selectionToDOM(this, forceSelUpdate);
|
package/dist/index.js
CHANGED
|
@@ -367,17 +367,20 @@ function findOffsetInNode(node, coords) {
|
|
|
367
367
|
}
|
|
368
368
|
function findOffsetInText(node, coords) {
|
|
369
369
|
let len = node.nodeValue.length;
|
|
370
|
-
let range = document.createRange();
|
|
370
|
+
let range = document.createRange(), result;
|
|
371
371
|
for (let i = 0; i < len; i++) {
|
|
372
372
|
range.setEnd(node, i + 1);
|
|
373
373
|
range.setStart(node, i);
|
|
374
374
|
let rect = singleRect(range, 1);
|
|
375
375
|
if (rect.top == rect.bottom)
|
|
376
376
|
continue;
|
|
377
|
-
if (inRect(coords, rect))
|
|
378
|
-
|
|
377
|
+
if (inRect(coords, rect)) {
|
|
378
|
+
result = { node, offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0) };
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
379
381
|
}
|
|
380
|
-
|
|
382
|
+
range.detach();
|
|
383
|
+
return result || { node, offset: 0 };
|
|
381
384
|
}
|
|
382
385
|
function inRect(coords, rect) {
|
|
383
386
|
return coords.left >= rect.left - 1 && coords.left <= rect.right + 1 &&
|
|
@@ -1373,15 +1376,15 @@ class NodeViewDesc extends ViewDesc {
|
|
|
1373
1376
|
let updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view);
|
|
1374
1377
|
iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => {
|
|
1375
1378
|
if (widget.spec.marks)
|
|
1376
|
-
updater.syncToMarks(widget.spec.marks, inline, view);
|
|
1379
|
+
updater.syncToMarks(widget.spec.marks, inline, view, i);
|
|
1377
1380
|
else if (widget.type.side >= 0 && !insideNode)
|
|
1378
|
-
updater.syncToMarks(i == this.node.childCount ? Mark.none : this.node.child(i).marks, inline, view);
|
|
1381
|
+
updater.syncToMarks(i == this.node.childCount ? Mark.none : this.node.child(i).marks, inline, view, i);
|
|
1379
1382
|
// If the next node is a desc matching this widget, reuse it,
|
|
1380
1383
|
// otherwise insert the widget as a new view desc.
|
|
1381
1384
|
updater.placeWidget(widget, view, off);
|
|
1382
1385
|
}, (child, outerDeco, innerDeco, i) => {
|
|
1383
1386
|
// Make sure the wrapping mark descs match the node's marks.
|
|
1384
|
-
updater.syncToMarks(child.marks, inline, view);
|
|
1387
|
+
updater.syncToMarks(child.marks, inline, view, i);
|
|
1385
1388
|
// Try several strategies for drawing this node
|
|
1386
1389
|
let compIndex;
|
|
1387
1390
|
if (updater.findNodeMatch(child, outerDeco, innerDeco, i)) ;
|
|
@@ -1397,7 +1400,7 @@ class NodeViewDesc extends ViewDesc {
|
|
|
1397
1400
|
off += child.nodeSize;
|
|
1398
1401
|
});
|
|
1399
1402
|
// Drop all remaining descs after the current position.
|
|
1400
|
-
updater.syncToMarks([], inline, view);
|
|
1403
|
+
updater.syncToMarks([], inline, view, 0);
|
|
1401
1404
|
if (this.node.isTextblock)
|
|
1402
1405
|
updater.addTextblockHacks();
|
|
1403
1406
|
updater.destroyRest();
|
|
@@ -1787,7 +1790,7 @@ class ViewTreeUpdater {
|
|
|
1787
1790
|
}
|
|
1788
1791
|
// Sync the current stack of mark descs with the given array of
|
|
1789
1792
|
// marks, reusing existing mark descs when possible.
|
|
1790
|
-
syncToMarks(marks, inline, view) {
|
|
1793
|
+
syncToMarks(marks, inline, view, parentIndex) {
|
|
1791
1794
|
let keep = 0, depth = this.stack.length >> 1;
|
|
1792
1795
|
let maxKeep = Math.min(depth, marks.length);
|
|
1793
1796
|
while (keep < maxKeep &&
|
|
@@ -1803,8 +1806,10 @@ class ViewTreeUpdater {
|
|
|
1803
1806
|
}
|
|
1804
1807
|
while (depth < marks.length) {
|
|
1805
1808
|
this.stack.push(this.top, this.index + 1);
|
|
1806
|
-
let found = -1;
|
|
1807
|
-
|
|
1809
|
+
let found = -1, scanTo = this.top.children.length;
|
|
1810
|
+
if (parentIndex < this.preMatch.index)
|
|
1811
|
+
scanTo = Math.min(this.index + 3, scanTo);
|
|
1812
|
+
for (let i = this.index; i < scanTo; i++) {
|
|
1808
1813
|
let next = this.top.children[i];
|
|
1809
1814
|
if (next.matchesMark(marks[depth]) && !this.isLocked(next.dom)) {
|
|
1810
1815
|
found = i;
|
|
@@ -2000,9 +2005,7 @@ class ViewTreeUpdater {
|
|
|
2000
2005
|
}
|
|
2001
2006
|
// Iterate from the end of the fragment and array of descs to find
|
|
2002
2007
|
// directly matching ones, in order to avoid overeagerly reusing those
|
|
2003
|
-
// for other nodes.
|
|
2004
|
-
// is part of the sequence of matched nodes at the end of the
|
|
2005
|
-
// fragment.
|
|
2008
|
+
// for other nodes.
|
|
2006
2009
|
function preMatch(frag, parentDesc) {
|
|
2007
2010
|
let curDesc = parentDesc, descI = curDesc.children.length;
|
|
2008
2011
|
let fI = frag.childCount, matched = new Map, matches = [];
|
|
@@ -2025,7 +2028,6 @@ function preMatch(frag, parentDesc) {
|
|
|
2025
2028
|
break outer;
|
|
2026
2029
|
}
|
|
2027
2030
|
else {
|
|
2028
|
-
// FIXME
|
|
2029
2031
|
descI = curDesc.parent.children.indexOf(curDesc);
|
|
2030
2032
|
curDesc = curDesc.parent;
|
|
2031
2033
|
}
|
|
@@ -3072,6 +3074,7 @@ class InputState {
|
|
|
3072
3074
|
this.compositionNodes = [];
|
|
3073
3075
|
this.compositionEndedAt = -2e8;
|
|
3074
3076
|
this.compositionID = 1;
|
|
3077
|
+
this.badSafariComposition = false;
|
|
3075
3078
|
// Set to a composition ID when there are pending changes at compositionend
|
|
3076
3079
|
this.compositionPendingChanges = 0;
|
|
3077
3080
|
this.domChangeCount = 0;
|
|
@@ -3471,7 +3474,8 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
|
|
|
3471
3474
|
let { state } = view, $pos = state.selection.$to;
|
|
3472
3475
|
if (state.selection instanceof TextSelection &&
|
|
3473
3476
|
(state.storedMarks ||
|
|
3474
|
-
(!$pos.textOffset && $pos.parentOffset && $pos.nodeBefore.marks.some(m => m.type.spec.inclusive === false))
|
|
3477
|
+
(!$pos.textOffset && $pos.parentOffset && $pos.nodeBefore.marks.some(m => m.type.spec.inclusive === false)) ||
|
|
3478
|
+
chrome && windows && selectionBeforeUneditable(view))) { // Issue #1500
|
|
3475
3479
|
// Need to wrap the cursor in mark nodes different from the ones in the DOM context
|
|
3476
3480
|
view.markCursor = view.state.storedMarks || $pos.marks();
|
|
3477
3481
|
endComposition(view, true);
|
|
@@ -3505,13 +3509,22 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
|
|
|
3505
3509
|
}
|
|
3506
3510
|
scheduleComposeEnd(view, timeoutComposition);
|
|
3507
3511
|
};
|
|
3512
|
+
function selectionBeforeUneditable(view) {
|
|
3513
|
+
let { focusNode, focusOffset } = view.domSelectionRange();
|
|
3514
|
+
if (!focusNode || focusNode.nodeType != 1 || focusOffset >= focusNode.childNodes.length)
|
|
3515
|
+
return false;
|
|
3516
|
+
let next = focusNode.childNodes[focusOffset];
|
|
3517
|
+
return next.nodeType == 1 && next.contentEditable == "false";
|
|
3518
|
+
}
|
|
3508
3519
|
editHandlers.compositionend = (view, event) => {
|
|
3509
3520
|
if (view.composing) {
|
|
3510
3521
|
view.input.composing = false;
|
|
3511
3522
|
view.input.compositionEndedAt = event.timeStamp;
|
|
3512
3523
|
view.input.compositionPendingChanges = view.domObserver.pendingRecords().length ? view.input.compositionID : 0;
|
|
3513
3524
|
view.input.compositionNode = null;
|
|
3514
|
-
if (view.input.
|
|
3525
|
+
if (view.input.badSafariComposition)
|
|
3526
|
+
view.domObserver.forceFlush();
|
|
3527
|
+
else if (view.input.compositionPendingChanges)
|
|
3515
3528
|
Promise.resolve().then(() => view.domObserver.flush());
|
|
3516
3529
|
view.input.compositionID++;
|
|
3517
3530
|
scheduleComposeEnd(view, 20);
|
|
@@ -3732,10 +3745,15 @@ handlers.dragend = view => {
|
|
|
3732
3745
|
}, 50);
|
|
3733
3746
|
};
|
|
3734
3747
|
editHandlers.dragover = editHandlers.dragenter = (_, e) => e.preventDefault();
|
|
3735
|
-
editHandlers.drop = (view,
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3748
|
+
editHandlers.drop = (view, event) => {
|
|
3749
|
+
try {
|
|
3750
|
+
handleDrop(view, event, view.dragging);
|
|
3751
|
+
}
|
|
3752
|
+
finally {
|
|
3753
|
+
view.dragging = null;
|
|
3754
|
+
}
|
|
3755
|
+
};
|
|
3756
|
+
function handleDrop(view, event, dragging) {
|
|
3739
3757
|
if (!event.dataTransfer)
|
|
3740
3758
|
return;
|
|
3741
3759
|
let eventPos = view.posAtCoords(eventCoords(event));
|
|
@@ -3789,7 +3807,7 @@ editHandlers.drop = (view, _event) => {
|
|
|
3789
3807
|
}
|
|
3790
3808
|
view.focus();
|
|
3791
3809
|
view.dispatch(tr.setMeta("uiEvent", "drop"));
|
|
3792
|
-
}
|
|
3810
|
+
}
|
|
3793
3811
|
handlers.focus = view => {
|
|
3794
3812
|
view.input.lastFocus = Date.now();
|
|
3795
3813
|
if (!view.focused) {
|
|
@@ -4569,15 +4587,24 @@ class DOMObserver {
|
|
|
4569
4587
|
new window.MutationObserver(mutations => {
|
|
4570
4588
|
for (let i = 0; i < mutations.length; i++)
|
|
4571
4589
|
this.queue.push(mutations[i]);
|
|
4572
|
-
// IE11 will sometimes (on backspacing out a single character
|
|
4573
|
-
// text node after a BR node) call the observer callback
|
|
4574
|
-
// before actually updating the DOM, which will cause
|
|
4575
|
-
// ProseMirror to miss the change (see #930)
|
|
4576
4590
|
if (ie && ie_version <= 11 && mutations.some(m => m.type == "childList" && m.removedNodes.length ||
|
|
4577
|
-
m.type == "characterData" && m.oldValue.length > m.target.nodeValue.length))
|
|
4591
|
+
m.type == "characterData" && m.oldValue.length > m.target.nodeValue.length)) {
|
|
4592
|
+
// IE11 will sometimes (on backspacing out a single character
|
|
4593
|
+
// text node after a BR node) call the observer callback
|
|
4594
|
+
// before actually updating the DOM, which will cause
|
|
4595
|
+
// ProseMirror to miss the change (see #930)
|
|
4578
4596
|
this.flushSoon();
|
|
4579
|
-
|
|
4597
|
+
}
|
|
4598
|
+
else if (safari && view.composing && mutations.some(m => m.type == "childList" && m.target.nodeName == "TR")) {
|
|
4599
|
+
// Safari does weird stuff when finishing a composition in a
|
|
4600
|
+
// table cell, which tends to involve inserting inappropriate
|
|
4601
|
+
// nodes in the table row.
|
|
4602
|
+
view.input.badSafariComposition = true;
|
|
4603
|
+
this.flushSoon();
|
|
4604
|
+
}
|
|
4605
|
+
else {
|
|
4580
4606
|
this.flush();
|
|
4607
|
+
}
|
|
4581
4608
|
});
|
|
4582
4609
|
if (useCharData) {
|
|
4583
4610
|
this.onCharData = e => {
|
|
@@ -4715,6 +4742,17 @@ class DOMObserver {
|
|
|
4715
4742
|
}
|
|
4716
4743
|
}
|
|
4717
4744
|
}
|
|
4745
|
+
else if ((chrome || safari) && added.some(n => n.nodeName == "BR") &&
|
|
4746
|
+
(view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46)) {
|
|
4747
|
+
// Chrome/Safari sometimes insert a bogus break node if you
|
|
4748
|
+
// backspace out the last bit of text before an inline-flex node (#1552)
|
|
4749
|
+
for (let node of added)
|
|
4750
|
+
if (node.nodeName == "BR" && node.parentNode) {
|
|
4751
|
+
let after = node.nextSibling;
|
|
4752
|
+
if (after && after.nodeType == 1 && after.contentEditable == "false")
|
|
4753
|
+
node.parentNode.removeChild(node);
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4718
4756
|
let readSel = null;
|
|
4719
4757
|
// If it looks like the browser has reset the selection to the
|
|
4720
4758
|
// start of the document after focus, restore the selection from
|
|
@@ -4733,6 +4771,10 @@ class DOMObserver {
|
|
|
4733
4771
|
view.docView.markDirty(from, to);
|
|
4734
4772
|
checkCSS(view);
|
|
4735
4773
|
}
|
|
4774
|
+
if (view.input.badSafariComposition) {
|
|
4775
|
+
view.input.badSafariComposition = false;
|
|
4776
|
+
fixUpBadSafariComposition(view, added);
|
|
4777
|
+
}
|
|
4736
4778
|
this.handleDOMChange(from, to, typeOver, added);
|
|
4737
4779
|
if (view.docView && view.docView.dirty)
|
|
4738
4780
|
view.updateState(view.state);
|
|
@@ -4856,6 +4898,37 @@ function blockParent(view, node) {
|
|
|
4856
4898
|
}
|
|
4857
4899
|
return null;
|
|
4858
4900
|
}
|
|
4901
|
+
// Kludge for a Safari bug where, on ending a composition in an
|
|
4902
|
+
// otherwise empty table cell, it randomly moves the composed text
|
|
4903
|
+
// into the table row around that cell, greatly confusing everything
|
|
4904
|
+
// (#188).
|
|
4905
|
+
function fixUpBadSafariComposition(view, addedNodes) {
|
|
4906
|
+
var _a;
|
|
4907
|
+
let { focusNode, focusOffset } = view.domSelectionRange();
|
|
4908
|
+
for (let node of addedNodes) {
|
|
4909
|
+
if (((_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.nodeName) == "TR") {
|
|
4910
|
+
let nextCell = node.nextSibling;
|
|
4911
|
+
while (nextCell && (nextCell.nodeName != "TD" && nextCell.nodeName != "TH"))
|
|
4912
|
+
nextCell = nextCell.nextSibling;
|
|
4913
|
+
if (nextCell) {
|
|
4914
|
+
let parent = nextCell;
|
|
4915
|
+
for (;;) {
|
|
4916
|
+
let first = parent.firstChild;
|
|
4917
|
+
if (!first || first.nodeType != 1 || first.contentEditable == "false" ||
|
|
4918
|
+
/^(BR|IMG)$/.test(first.nodeName))
|
|
4919
|
+
break;
|
|
4920
|
+
parent = first;
|
|
4921
|
+
}
|
|
4922
|
+
parent.insertBefore(node, parent.firstChild);
|
|
4923
|
+
if (focusNode == node)
|
|
4924
|
+
view.domSelection().collapse(node, focusOffset);
|
|
4925
|
+
}
|
|
4926
|
+
else {
|
|
4927
|
+
node.parentNode.removeChild(node);
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
}
|
|
4859
4932
|
|
|
4860
4933
|
// Note that all referencing and parsing is done with the
|
|
4861
4934
|
// start-of-operation selection and document, since that's the one
|
|
@@ -5428,7 +5501,7 @@ class EditorView {
|
|
|
5428
5501
|
this.docView.destroy();
|
|
5429
5502
|
this.docView = docViewDesc(state.doc, outerDeco, innerDeco, this.dom, this);
|
|
5430
5503
|
}
|
|
5431
|
-
if (chromeKludge && !this.trackWrites)
|
|
5504
|
+
if (chromeKludge && (!this.trackWrites || !this.dom.contains(this.trackWrites)))
|
|
5432
5505
|
forceSelUpdate = true;
|
|
5433
5506
|
}
|
|
5434
5507
|
// Work around for an issue where an update arriving right between
|
package/package.json
CHANGED
package/src/dom.ts
CHANGED
package/src/domchange.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {Fragment, DOMParser, TagParseRule, Node, Mark, ResolvedPos} from "prosemirror-model"
|
|
2
|
-
import {
|
|
2
|
+
import {TextSelection, Transaction} from "prosemirror-state"
|
|
3
3
|
|
|
4
4
|
import {selectionBetween, selectionFromDOM, selectionToDOM} from "./selection"
|
|
5
5
|
import {selectionCollapsed, keyEvent, DOMNode} from "./dom"
|
|
@@ -81,7 +81,7 @@ const isInline = /^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|
|
|
|
81
81
|
export function readDOMChange(view: EditorView, from: number, to: number, typeOver: boolean, addedNodes: readonly DOMNode[]) {
|
|
82
82
|
let compositionID = view.input.compositionPendingChanges || (view.composing ? view.input.compositionID : 0)
|
|
83
83
|
view.input.compositionPendingChanges = 0
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
if (from < 0) {
|
|
86
86
|
let origin = view.input.lastSelectionTime > Date.now() - 50 ? view.input.lastSelectionOrigin : null
|
|
87
87
|
let newSel = selectionFromDOM(view, origin)
|
package/src/domcoords.ts
CHANGED
|
@@ -184,16 +184,19 @@ function findOffsetInNode(node: HTMLElement, coords: {top: number, left: number}
|
|
|
184
184
|
|
|
185
185
|
function findOffsetInText(node: Text, coords: {top: number, left: number}) {
|
|
186
186
|
let len = node.nodeValue!.length
|
|
187
|
-
let range = document.createRange()
|
|
187
|
+
let range = document.createRange(), result: {node: Node, offset: number} | undefined
|
|
188
188
|
for (let i = 0; i < len; i++) {
|
|
189
189
|
range.setEnd(node, i + 1)
|
|
190
190
|
range.setStart(node, i)
|
|
191
191
|
let rect = singleRect(range, 1)
|
|
192
192
|
if (rect.top == rect.bottom) continue
|
|
193
|
-
if (inRect(coords, rect))
|
|
194
|
-
|
|
193
|
+
if (inRect(coords, rect)) {
|
|
194
|
+
result = {node, offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)}
|
|
195
|
+
break
|
|
196
|
+
}
|
|
195
197
|
}
|
|
196
|
-
|
|
198
|
+
range.detach()
|
|
199
|
+
return result || {node, offset: 0}
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
function inRect(coords: {top: number, left: number}, rect: Rect) {
|
package/src/domobserver.ts
CHANGED
|
@@ -52,16 +52,24 @@ export class DOMObserver {
|
|
|
52
52
|
this.observer = window.MutationObserver &&
|
|
53
53
|
new window.MutationObserver(mutations => {
|
|
54
54
|
for (let i = 0; i < mutations.length; i++) this.queue.push(mutations[i])
|
|
55
|
-
// IE11 will sometimes (on backspacing out a single character
|
|
56
|
-
// text node after a BR node) call the observer callback
|
|
57
|
-
// before actually updating the DOM, which will cause
|
|
58
|
-
// ProseMirror to miss the change (see #930)
|
|
59
55
|
if (browser.ie && browser.ie_version <= 11 && mutations.some(
|
|
60
56
|
m => m.type == "childList" && m.removedNodes.length ||
|
|
61
|
-
m.type == "characterData" && m.oldValue!.length > m.target.nodeValue!.length))
|
|
57
|
+
m.type == "characterData" && m.oldValue!.length > m.target.nodeValue!.length)) {
|
|
58
|
+
// IE11 will sometimes (on backspacing out a single character
|
|
59
|
+
// text node after a BR node) call the observer callback
|
|
60
|
+
// before actually updating the DOM, which will cause
|
|
61
|
+
// ProseMirror to miss the change (see #930)
|
|
62
62
|
this.flushSoon()
|
|
63
|
-
else
|
|
63
|
+
} else if (browser.safari && view.composing && mutations.some(
|
|
64
|
+
m => m.type == "childList" && m.target.nodeName == "TR")) {
|
|
65
|
+
// Safari does weird stuff when finishing a composition in a
|
|
66
|
+
// table cell, which tends to involve inserting inappropriate
|
|
67
|
+
// nodes in the table row.
|
|
68
|
+
view.input.badSafariComposition = true
|
|
69
|
+
this.flushSoon()
|
|
70
|
+
} else {
|
|
64
71
|
this.flush()
|
|
72
|
+
}
|
|
65
73
|
})
|
|
66
74
|
if (useCharData) {
|
|
67
75
|
this.onCharData = e => {
|
|
@@ -198,6 +206,15 @@ export class DOMObserver {
|
|
|
198
206
|
br.remove()
|
|
199
207
|
}
|
|
200
208
|
}
|
|
209
|
+
} else if ((browser.chrome || browser.safari) && added.some(n => n.nodeName == "BR") &&
|
|
210
|
+
(view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46)) {
|
|
211
|
+
// Chrome/Safari sometimes insert a bogus break node if you
|
|
212
|
+
// backspace out the last bit of text before an inline-flex node (#1552)
|
|
213
|
+
for (let node of added) if (node.nodeName == "BR" && node.parentNode) {
|
|
214
|
+
let after = node.nextSibling
|
|
215
|
+
if (after && after.nodeType == 1 && (after as HTMLElement).contentEditable == "false")
|
|
216
|
+
node.parentNode.removeChild(node)
|
|
217
|
+
}
|
|
201
218
|
}
|
|
202
219
|
|
|
203
220
|
let readSel: Selection | null = null
|
|
@@ -217,6 +234,10 @@ export class DOMObserver {
|
|
|
217
234
|
view.docView.markDirty(from, to)
|
|
218
235
|
checkCSS(view)
|
|
219
236
|
}
|
|
237
|
+
if (view.input.badSafariComposition) {
|
|
238
|
+
view.input.badSafariComposition = false
|
|
239
|
+
fixUpBadSafariComposition(view, added)
|
|
240
|
+
}
|
|
220
241
|
this.handleDOMChange(from, to, typeOver, added)
|
|
221
242
|
if (view.docView && view.docView.dirty) view.updateState(view.state)
|
|
222
243
|
else if (!this.currentSelection.eq(sel)) selectionToDOM(view)
|
|
@@ -338,3 +359,30 @@ function blockParent(view: EditorView, node: DOMNode): Node | null {
|
|
|
338
359
|
}
|
|
339
360
|
return null
|
|
340
361
|
}
|
|
362
|
+
|
|
363
|
+
// Kludge for a Safari bug where, on ending a composition in an
|
|
364
|
+
// otherwise empty table cell, it randomly moves the composed text
|
|
365
|
+
// into the table row around that cell, greatly confusing everything
|
|
366
|
+
// (#188).
|
|
367
|
+
function fixUpBadSafariComposition(view: EditorView, addedNodes: readonly DOMNode[]) {
|
|
368
|
+
let {focusNode, focusOffset} = view.domSelectionRange()
|
|
369
|
+
for (let node of addedNodes) {
|
|
370
|
+
if (node.parentNode?.nodeName == "TR") {
|
|
371
|
+
let nextCell = node.nextSibling
|
|
372
|
+
while (nextCell && (nextCell.nodeName != "TD" && nextCell.nodeName != "TH")) nextCell = nextCell.nextSibling
|
|
373
|
+
if (nextCell) {
|
|
374
|
+
let parent = nextCell
|
|
375
|
+
for (;;) {
|
|
376
|
+
let first = parent.firstChild
|
|
377
|
+
if (!first || first.nodeType != 1 || (first as HTMLElement).contentEditable == "false" ||
|
|
378
|
+
/^(BR|IMG)$/.test(first.nodeName)) break
|
|
379
|
+
parent = first
|
|
380
|
+
}
|
|
381
|
+
parent.insertBefore(node, parent.firstChild)
|
|
382
|
+
if (focusNode == node) view.domSelection()!.collapse(node, focusOffset)
|
|
383
|
+
} else {
|
|
384
|
+
node.parentNode.removeChild(node)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -202,7 +202,7 @@ export class EditorView {
|
|
|
202
202
|
this.docView.destroy()
|
|
203
203
|
this.docView = docViewDesc(state.doc, outerDeco, innerDeco, this.dom, this)
|
|
204
204
|
}
|
|
205
|
-
if (chromeKludge && !this.trackWrites) forceSelUpdate = true
|
|
205
|
+
if (chromeKludge && (!this.trackWrites || !this.dom.contains(this.trackWrites))) forceSelUpdate = true
|
|
206
206
|
}
|
|
207
207
|
// Work around for an issue where an update arriving right between
|
|
208
208
|
// a DOM selection change and the "selectionchange" event for it
|
|
@@ -469,7 +469,7 @@ export class EditorView {
|
|
|
469
469
|
}
|
|
470
470
|
this.docView.destroy()
|
|
471
471
|
;(this as any).docView = null
|
|
472
|
-
clearReusedRange()
|
|
472
|
+
clearReusedRange()
|
|
473
473
|
}
|
|
474
474
|
|
|
475
475
|
/// This is true when the view has been
|
package/src/input.ts
CHANGED
|
@@ -35,6 +35,7 @@ export class InputState {
|
|
|
35
35
|
compositionNodes: ViewDesc[] = []
|
|
36
36
|
compositionEndedAt = -2e8
|
|
37
37
|
compositionID = 1
|
|
38
|
+
badSafariComposition = false
|
|
38
39
|
// Set to a composition ID when there are pending changes at compositionend
|
|
39
40
|
compositionPendingChanges = 0
|
|
40
41
|
domChangeCount = 0
|
|
@@ -459,7 +460,8 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
|
|
|
459
460
|
let {state} = view, $pos = state.selection.$to
|
|
460
461
|
if (state.selection instanceof TextSelection &&
|
|
461
462
|
(state.storedMarks ||
|
|
462
|
-
(!$pos.textOffset && $pos.parentOffset && $pos.nodeBefore!.marks.some(m => m.type.spec.inclusive === false))
|
|
463
|
+
(!$pos.textOffset && $pos.parentOffset && $pos.nodeBefore!.marks.some(m => m.type.spec.inclusive === false)) ||
|
|
464
|
+
browser.chrome && browser.windows && selectionBeforeUneditable(view))) { // Issue #1500
|
|
463
465
|
// Need to wrap the cursor in mark nodes different from the ones in the DOM context
|
|
464
466
|
view.markCursor = view.state.storedMarks || $pos.marks()
|
|
465
467
|
endComposition(view, true)
|
|
@@ -490,13 +492,21 @@ editHandlers.compositionstart = editHandlers.compositionupdate = view => {
|
|
|
490
492
|
scheduleComposeEnd(view, timeoutComposition)
|
|
491
493
|
}
|
|
492
494
|
|
|
495
|
+
function selectionBeforeUneditable(view: EditorView) {
|
|
496
|
+
let {focusNode, focusOffset} = view.domSelectionRange()
|
|
497
|
+
if (!focusNode || focusNode.nodeType != 1 || focusOffset >= focusNode.childNodes.length) return false
|
|
498
|
+
let next = focusNode.childNodes[focusOffset]
|
|
499
|
+
return next.nodeType == 1 && (next as HTMLElement).contentEditable == "false"
|
|
500
|
+
}
|
|
501
|
+
|
|
493
502
|
editHandlers.compositionend = (view, event) => {
|
|
494
503
|
if (view.composing) {
|
|
495
504
|
view.input.composing = false
|
|
496
505
|
view.input.compositionEndedAt = event.timeStamp
|
|
497
506
|
view.input.compositionPendingChanges = view.domObserver.pendingRecords().length ? view.input.compositionID : 0
|
|
498
507
|
view.input.compositionNode = null
|
|
499
|
-
if (view.input.
|
|
508
|
+
if (view.input.badSafariComposition) view.domObserver.forceFlush()
|
|
509
|
+
else if (view.input.compositionPendingChanges) Promise.resolve().then(() => view.domObserver.flush())
|
|
500
510
|
view.input.compositionID++
|
|
501
511
|
scheduleComposeEnd(view, 20)
|
|
502
512
|
}
|
|
@@ -706,11 +716,15 @@ handlers.dragend = view => {
|
|
|
706
716
|
|
|
707
717
|
editHandlers.dragover = editHandlers.dragenter = (_, e) => e.preventDefault()
|
|
708
718
|
|
|
709
|
-
editHandlers.drop = (view,
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
719
|
+
editHandlers.drop = (view, event) => {
|
|
720
|
+
try {
|
|
721
|
+
handleDrop(view, event as DragEvent, view.dragging)
|
|
722
|
+
} finally {
|
|
723
|
+
view.dragging = null
|
|
724
|
+
}
|
|
725
|
+
}
|
|
713
726
|
|
|
727
|
+
function handleDrop(view: EditorView, event: DragEvent, dragging: Dragging | null) {
|
|
714
728
|
if (!event.dataTransfer) return
|
|
715
729
|
|
|
716
730
|
let eventPos = view.posAtCoords(eventCoords(event))
|
package/src/viewdesc.ts
CHANGED
|
@@ -772,15 +772,15 @@ export class NodeViewDesc extends ViewDesc {
|
|
|
772
772
|
let updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view)
|
|
773
773
|
iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => {
|
|
774
774
|
if (widget.spec.marks)
|
|
775
|
-
updater.syncToMarks(widget.spec.marks, inline, view)
|
|
775
|
+
updater.syncToMarks(widget.spec.marks, inline, view, i)
|
|
776
776
|
else if ((widget.type as WidgetType).side >= 0 && !insideNode)
|
|
777
|
-
updater.syncToMarks(i == this.node.childCount ? Mark.none : this.node.child(i).marks, inline, view)
|
|
777
|
+
updater.syncToMarks(i == this.node.childCount ? Mark.none : this.node.child(i).marks, inline, view, i)
|
|
778
778
|
// If the next node is a desc matching this widget, reuse it,
|
|
779
779
|
// otherwise insert the widget as a new view desc.
|
|
780
780
|
updater.placeWidget(widget, view, off)
|
|
781
781
|
}, (child, outerDeco, innerDeco, i) => {
|
|
782
782
|
// Make sure the wrapping mark descs match the node's marks.
|
|
783
|
-
updater.syncToMarks(child.marks, inline, view)
|
|
783
|
+
updater.syncToMarks(child.marks, inline, view, i)
|
|
784
784
|
// Try several strategies for drawing this node
|
|
785
785
|
let compIndex
|
|
786
786
|
if (updater.findNodeMatch(child, outerDeco, innerDeco, i)) {
|
|
@@ -799,7 +799,7 @@ export class NodeViewDesc extends ViewDesc {
|
|
|
799
799
|
off += child.nodeSize
|
|
800
800
|
})
|
|
801
801
|
// Drop all remaining descs after the current position.
|
|
802
|
-
updater.syncToMarks([], inline, view)
|
|
802
|
+
updater.syncToMarks([], inline, view, 0)
|
|
803
803
|
if (this.node.isTextblock) updater.addTextblockHacks()
|
|
804
804
|
updater.destroyRest()
|
|
805
805
|
|
|
@@ -1197,7 +1197,7 @@ class ViewTreeUpdater {
|
|
|
1197
1197
|
|
|
1198
1198
|
// Sync the current stack of mark descs with the given array of
|
|
1199
1199
|
// marks, reusing existing mark descs when possible.
|
|
1200
|
-
syncToMarks(marks: readonly Mark[], inline: boolean, view: EditorView) {
|
|
1200
|
+
syncToMarks(marks: readonly Mark[], inline: boolean, view: EditorView, parentIndex: number) {
|
|
1201
1201
|
let keep = 0, depth = this.stack.length >> 1
|
|
1202
1202
|
let maxKeep = Math.min(depth, marks.length)
|
|
1203
1203
|
while (keep < maxKeep &&
|
|
@@ -1214,8 +1214,9 @@ class ViewTreeUpdater {
|
|
|
1214
1214
|
}
|
|
1215
1215
|
while (depth < marks.length) {
|
|
1216
1216
|
this.stack.push(this.top, this.index + 1)
|
|
1217
|
-
let found = -1
|
|
1218
|
-
|
|
1217
|
+
let found = -1, scanTo = this.top.children.length
|
|
1218
|
+
if (parentIndex < this.preMatch.index) scanTo = Math.min(this.index + 3, scanTo)
|
|
1219
|
+
for (let i = this.index; i < scanTo; i++) {
|
|
1219
1220
|
let next = this.top.children[i]
|
|
1220
1221
|
if (next.matchesMark(marks[depth]) && !this.isLocked(next.dom)) { found = i; break }
|
|
1221
1222
|
}
|
|
@@ -1404,12 +1405,16 @@ class ViewTreeUpdater {
|
|
|
1404
1405
|
|
|
1405
1406
|
// Iterate from the end of the fragment and array of descs to find
|
|
1406
1407
|
// directly matching ones, in order to avoid overeagerly reusing those
|
|
1407
|
-
// for other nodes.
|
|
1408
|
-
|
|
1409
|
-
// fragment
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1408
|
+
// for other nodes.
|
|
1409
|
+
function preMatch(frag: Fragment, parentDesc: ViewDesc): {
|
|
1410
|
+
// The fragment index of the first node that is part of the sequence
|
|
1411
|
+
// of matched nodes at the end of the fragment.
|
|
1412
|
+
index: number,
|
|
1413
|
+
// A map from matched descs to fragment indices.
|
|
1414
|
+
matched: Map<ViewDesc, number>,
|
|
1415
|
+
// The matched descs.
|
|
1416
|
+
matches: readonly ViewDesc[]
|
|
1417
|
+
} {
|
|
1413
1418
|
let curDesc = parentDesc, descI = curDesc.children.length
|
|
1414
1419
|
let fI = frag.childCount, matched = new Map, matches = []
|
|
1415
1420
|
outer: while (fI > 0) {
|
|
@@ -1428,7 +1433,6 @@ function preMatch(
|
|
|
1428
1433
|
} else if (curDesc == parentDesc) {
|
|
1429
1434
|
break outer
|
|
1430
1435
|
} else {
|
|
1431
|
-
// FIXME
|
|
1432
1436
|
descI = curDesc.parent!.children.indexOf(curDesc)
|
|
1433
1437
|
curDesc = curDesc.parent!
|
|
1434
1438
|
}
|