prosemirror-view 1.27.0 → 1.28.0
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 +28 -0
- package/dist/index.cjs +146 -81
- package/dist/index.d.ts +7 -2
- package/dist/index.js +129 -62
- package/package.json +1 -1
- package/src/clipboard.ts +2 -0
- package/src/decoration.ts +17 -12
- package/src/domchange.ts +8 -9
- package/src/domcoords.ts +1 -1
- package/src/domobserver.ts +37 -18
- package/src/index.ts +39 -11
- package/src/input.ts +22 -8
- package/src/selection.ts +1 -1
- package/src/viewdesc.ts +4 -3
- package/dist/index.es.js +0 -5558
- package/dist/index.es.js.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -458,7 +458,7 @@ function coordsAtPos(view, pos, side) {
|
|
|
458
458
|
else {
|
|
459
459
|
to++;
|
|
460
460
|
}
|
|
461
|
-
return flattenV(singleRect(textRange(node, from, to),
|
|
461
|
+
return flattenV(singleRect(textRange(node, from, to), 1), takeSide < 0);
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
464
|
let $dom = view.state.doc.resolve(pos - (atom || 0));
|
|
@@ -1242,7 +1242,7 @@ class NodeViewDesc extends ViewDesc {
|
|
|
1242
1242
|
let composition = view.composing ? this.localCompositionInfo(view, pos) : null;
|
|
1243
1243
|
let localComposition = composition && composition.pos > -1 ? composition : null;
|
|
1244
1244
|
let compositionInChild = composition && composition.pos < 0;
|
|
1245
|
-
let updater = new ViewTreeUpdater(this, localComposition && localComposition.node);
|
|
1245
|
+
let updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view);
|
|
1246
1246
|
iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => {
|
|
1247
1247
|
if (widget.spec.marks)
|
|
1248
1248
|
updater.syncToMarks(widget.spec.marks, inline, view);
|
|
@@ -1624,8 +1624,9 @@ function rm(dom) {
|
|
|
1624
1624
|
// Helper class for incrementally updating a tree of mark descs and
|
|
1625
1625
|
// the widget and node descs inside of them.
|
|
1626
1626
|
class ViewTreeUpdater {
|
|
1627
|
-
constructor(top, lock) {
|
|
1627
|
+
constructor(top, lock, view) {
|
|
1628
1628
|
this.lock = lock;
|
|
1629
|
+
this.view = view;
|
|
1629
1630
|
// Index into `this.top`'s child array, represents the current
|
|
1630
1631
|
// update position.
|
|
1631
1632
|
this.index = 0;
|
|
@@ -1799,7 +1800,8 @@ class ViewTreeUpdater {
|
|
|
1799
1800
|
}
|
|
1800
1801
|
if (!lastChild || // Empty textblock
|
|
1801
1802
|
!(lastChild instanceof TextViewDesc) ||
|
|
1802
|
-
/\n$/.test(lastChild.node.text)
|
|
1803
|
+
/\n$/.test(lastChild.node.text) ||
|
|
1804
|
+
(this.view.requiresGeckoHackNode && /\s$/.test(lastChild.node.text))) {
|
|
1803
1805
|
// Avoid bugs in Safari's cursor drawing (#1165) and Chrome's mouse selection (#1152)
|
|
1804
1806
|
if ((safari || chrome) && lastChild && lastChild.dom.contentEditable == "false")
|
|
1805
1807
|
this.addHackNode("IMG", parent);
|
|
@@ -2201,7 +2203,7 @@ function selectionBetween(view, $anchor, $head, bias) {
|
|
|
2201
2203
|
|| TextSelection.between($anchor, $head, bias);
|
|
2202
2204
|
}
|
|
2203
2205
|
function hasFocusAndSelection(view) {
|
|
2204
|
-
if (view.editable && view.
|
|
2206
|
+
if (view.editable && !view.hasFocus())
|
|
2205
2207
|
return false;
|
|
2206
2208
|
return hasSelection(view);
|
|
2207
2209
|
}
|
|
@@ -2530,6 +2532,7 @@ function captureKeyDown(view, event) {
|
|
|
2530
2532
|
}
|
|
2531
2533
|
|
|
2532
2534
|
function serializeForClipboard(view, slice) {
|
|
2535
|
+
view.someProp("transformCopied", f => { slice = f(slice); });
|
|
2533
2536
|
let context = [], { content, openStart, openEnd } = slice;
|
|
2534
2537
|
while (openStart > 1 && openEnd > 1 && content.childCount == 1 && content.firstChild.childCount == 1) {
|
|
2535
2538
|
openStart--;
|
|
@@ -2772,7 +2775,8 @@ function addContext(slice, context) {
|
|
|
2772
2775
|
// A collection of DOM events that occur within the editor, and callback functions
|
|
2773
2776
|
// to invoke when the event fires.
|
|
2774
2777
|
const handlers = {};
|
|
2775
|
-
|
|
2778
|
+
const editHandlers = {};
|
|
2779
|
+
const passiveHandlers = { touchstart: true, touchmove: true };
|
|
2776
2780
|
class InputState {
|
|
2777
2781
|
constructor() {
|
|
2778
2782
|
this.shiftKey = false;
|
|
@@ -2784,6 +2788,8 @@ class InputState {
|
|
|
2784
2788
|
this.lastSelectionTime = 0;
|
|
2785
2789
|
this.lastIOSEnter = 0;
|
|
2786
2790
|
this.lastIOSEnterFallbackTimeout = -1;
|
|
2791
|
+
this.lastFocus = 0;
|
|
2792
|
+
this.lastTouch = 0;
|
|
2787
2793
|
this.lastAndroidDelete = 0;
|
|
2788
2794
|
this.composing = false;
|
|
2789
2795
|
this.composingTimeout = -1;
|
|
@@ -2801,7 +2807,7 @@ function initInput(view) {
|
|
|
2801
2807
|
if (eventBelongsToView(view, event) && !runCustomHandler(view, event) &&
|
|
2802
2808
|
(view.editable || !(event.type in editHandlers)))
|
|
2803
2809
|
handler(view, event);
|
|
2804
|
-
});
|
|
2810
|
+
}, passiveHandlers[event] ? { passive: true } : undefined);
|
|
2805
2811
|
}
|
|
2806
2812
|
// On Safari, for reasons beyond my understanding, adding an input
|
|
2807
2813
|
// event handler makes an issue where the composition vanishes when
|
|
@@ -3106,6 +3112,7 @@ class MouseDown {
|
|
|
3106
3112
|
let pos = this.pos;
|
|
3107
3113
|
if (this.view.state.doc != this.startDoc)
|
|
3108
3114
|
pos = this.view.posAtCoords(eventCoords(event));
|
|
3115
|
+
this.updateAllowDefault(event);
|
|
3109
3116
|
if (this.allowDefault || !pos) {
|
|
3110
3117
|
setSelectionOrigin(this.view, "pointer");
|
|
3111
3118
|
}
|
|
@@ -3123,7 +3130,7 @@ class MouseDown {
|
|
|
3123
3130
|
// (hidden) cursor is doesn't change the selection, and
|
|
3124
3131
|
// thus doesn't get a reaction from ProseMirror. This
|
|
3125
3132
|
// works around that.
|
|
3126
|
-
(chrome && !
|
|
3133
|
+
(chrome && !this.view.state.selection.visible &&
|
|
3127
3134
|
Math.min(Math.abs(pos.pos - this.view.state.selection.from), Math.abs(pos.pos - this.view.state.selection.to)) <= 2))) {
|
|
3128
3135
|
updateSelection(this.view, Selection.near(this.view.state.doc.resolve(pos.pos)), "pointer");
|
|
3129
3136
|
event.preventDefault();
|
|
@@ -3133,18 +3140,26 @@ class MouseDown {
|
|
|
3133
3140
|
}
|
|
3134
3141
|
}
|
|
3135
3142
|
move(event) {
|
|
3136
|
-
|
|
3137
|
-
Math.abs(this.event.y - event.clientY) > 4))
|
|
3138
|
-
this.allowDefault = true;
|
|
3143
|
+
this.updateAllowDefault(event);
|
|
3139
3144
|
setSelectionOrigin(this.view, "pointer");
|
|
3140
3145
|
if (event.buttons == 0)
|
|
3141
3146
|
this.done();
|
|
3142
3147
|
}
|
|
3148
|
+
updateAllowDefault(event) {
|
|
3149
|
+
if (!this.allowDefault && (Math.abs(this.event.x - event.clientX) > 4 ||
|
|
3150
|
+
Math.abs(this.event.y - event.clientY) > 4))
|
|
3151
|
+
this.allowDefault = true;
|
|
3152
|
+
}
|
|
3143
3153
|
}
|
|
3144
|
-
handlers.
|
|
3154
|
+
handlers.touchstart = view => {
|
|
3155
|
+
view.input.lastTouch = Date.now();
|
|
3145
3156
|
forceDOMFlush(view);
|
|
3146
3157
|
setSelectionOrigin(view, "pointer");
|
|
3147
3158
|
};
|
|
3159
|
+
handlers.touchmove = view => {
|
|
3160
|
+
view.input.lastTouch = Date.now();
|
|
3161
|
+
setSelectionOrigin(view, "pointer");
|
|
3162
|
+
};
|
|
3148
3163
|
handlers.contextmenu = view => forceDOMFlush(view);
|
|
3149
3164
|
function inOrNearComposition(view, event) {
|
|
3150
3165
|
if (view.composing)
|
|
@@ -3396,8 +3411,6 @@ editHandlers.drop = (view, _event) => {
|
|
|
3396
3411
|
if (!eventPos)
|
|
3397
3412
|
return;
|
|
3398
3413
|
let $mouse = view.state.doc.resolve(eventPos.pos);
|
|
3399
|
-
if (!$mouse)
|
|
3400
|
-
return;
|
|
3401
3414
|
let slice = dragging && dragging.slice;
|
|
3402
3415
|
if (slice) {
|
|
3403
3416
|
view.someProp("transformPasted", f => { slice = f(slice); });
|
|
@@ -3442,6 +3455,7 @@ editHandlers.drop = (view, _event) => {
|
|
|
3442
3455
|
view.dispatch(tr.setMeta("uiEvent", "drop"));
|
|
3443
3456
|
};
|
|
3444
3457
|
handlers.focus = view => {
|
|
3458
|
+
view.input.lastFocus = Date.now();
|
|
3445
3459
|
if (!view.focused) {
|
|
3446
3460
|
view.domObserver.stop();
|
|
3447
3461
|
view.dom.classList.add("ProseMirror-focused");
|
|
@@ -3956,23 +3970,27 @@ function mapChildren(oldChildren, newLocal, mapping, node, offset, oldOffset, op
|
|
|
3956
3970
|
let children = oldChildren.slice();
|
|
3957
3971
|
// Mark the children that are directly touched by changes, and
|
|
3958
3972
|
// move those that are after the changes.
|
|
3959
|
-
let
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3973
|
+
for (let i = 0, baseOffset = oldOffset; i < mapping.maps.length; i++) {
|
|
3974
|
+
let moved = 0;
|
|
3975
|
+
mapping.maps[i].forEach((oldStart, oldEnd, newStart, newEnd) => {
|
|
3976
|
+
let dSize = (newEnd - newStart) - (oldEnd - oldStart);
|
|
3977
|
+
for (let i = 0; i < children.length; i += 3) {
|
|
3978
|
+
let end = children[i + 1];
|
|
3979
|
+
if (end < 0 || oldStart > end + baseOffset - moved)
|
|
3980
|
+
continue;
|
|
3981
|
+
let start = children[i] + baseOffset - moved;
|
|
3982
|
+
if (oldEnd >= start) {
|
|
3983
|
+
children[i + 1] = oldStart <= start ? -2 : -1;
|
|
3984
|
+
}
|
|
3985
|
+
else if (newStart >= offset && dSize) {
|
|
3986
|
+
children[i] += dSize;
|
|
3987
|
+
children[i + 1] += dSize;
|
|
3988
|
+
}
|
|
3971
3989
|
}
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3990
|
+
moved += dSize;
|
|
3991
|
+
});
|
|
3992
|
+
baseOffset = mapping.maps[i].map(baseOffset, -1);
|
|
3993
|
+
}
|
|
3976
3994
|
// Find the child nodes that still correspond to a single node,
|
|
3977
3995
|
// recursively call mapInner on them and update their positions.
|
|
3978
3996
|
let mustRebuild = false;
|
|
@@ -4300,17 +4318,18 @@ class DOMObserver {
|
|
|
4300
4318
|
}
|
|
4301
4319
|
}
|
|
4302
4320
|
flush() {
|
|
4303
|
-
|
|
4321
|
+
let { view } = this;
|
|
4322
|
+
if (!view.docView || this.flushingSoon > -1)
|
|
4304
4323
|
return;
|
|
4305
4324
|
let mutations = this.observer ? this.observer.takeRecords() : [];
|
|
4306
4325
|
if (this.queue.length) {
|
|
4307
4326
|
mutations = this.queue.concat(mutations);
|
|
4308
4327
|
this.queue.length = 0;
|
|
4309
4328
|
}
|
|
4310
|
-
let sel =
|
|
4311
|
-
let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(
|
|
4329
|
+
let sel = view.domSelection();
|
|
4330
|
+
let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel);
|
|
4312
4331
|
let from = -1, to = -1, typeOver = false, added = [];
|
|
4313
|
-
if (
|
|
4332
|
+
if (view.editable) {
|
|
4314
4333
|
for (let i = 0; i < mutations.length; i++) {
|
|
4315
4334
|
let result = this.registerMutation(mutations[i], added);
|
|
4316
4335
|
if (result) {
|
|
@@ -4331,16 +4350,29 @@ class DOMObserver {
|
|
|
4331
4350
|
a.remove();
|
|
4332
4351
|
}
|
|
4333
4352
|
}
|
|
4334
|
-
|
|
4353
|
+
let readSel = null;
|
|
4354
|
+
// If it looks like the browser has reset the selection to the
|
|
4355
|
+
// start of the document after focus, restore the selection from
|
|
4356
|
+
// the state
|
|
4357
|
+
if (from < 0 && newSel && view.input.lastFocus > Date.now() - 200 &&
|
|
4358
|
+
view.input.lastTouch < Date.now() - 300 &&
|
|
4359
|
+
selectionCollapsed(sel) && (readSel = selectionFromDOM(view)) &&
|
|
4360
|
+
readSel.eq(Selection.near(view.state.doc.resolve(0), 1))) {
|
|
4361
|
+
view.input.lastFocus = 0;
|
|
4362
|
+
selectionToDOM(view);
|
|
4363
|
+
this.currentSelection.set(sel);
|
|
4364
|
+
view.scrollToSelection();
|
|
4365
|
+
}
|
|
4366
|
+
else if (from > -1 || newSel) {
|
|
4335
4367
|
if (from > -1) {
|
|
4336
|
-
|
|
4337
|
-
checkCSS(
|
|
4368
|
+
view.docView.markDirty(from, to);
|
|
4369
|
+
checkCSS(view);
|
|
4338
4370
|
}
|
|
4339
4371
|
this.handleDOMChange(from, to, typeOver, added);
|
|
4340
|
-
if (
|
|
4341
|
-
|
|
4372
|
+
if (view.docView && view.docView.dirty)
|
|
4373
|
+
view.updateState(view.state);
|
|
4342
4374
|
else if (!this.currentSelection.eq(sel))
|
|
4343
|
-
selectionToDOM(
|
|
4375
|
+
selectionToDOM(view);
|
|
4344
4376
|
this.currentSelection.set(sel);
|
|
4345
4377
|
}
|
|
4346
4378
|
}
|
|
@@ -4397,13 +4429,19 @@ class DOMObserver {
|
|
|
4397
4429
|
}
|
|
4398
4430
|
}
|
|
4399
4431
|
}
|
|
4400
|
-
let cssChecked =
|
|
4432
|
+
let cssChecked = new WeakMap();
|
|
4433
|
+
let cssCheckWarned = false;
|
|
4401
4434
|
function checkCSS(view) {
|
|
4402
|
-
if (cssChecked)
|
|
4435
|
+
if (cssChecked.has(view))
|
|
4403
4436
|
return;
|
|
4404
|
-
cssChecked
|
|
4405
|
-
if (getComputedStyle(view.dom).whiteSpace
|
|
4437
|
+
cssChecked.set(view, null);
|
|
4438
|
+
if (['normal', 'nowrap', 'pre-line'].indexOf(getComputedStyle(view.dom).whiteSpace) !== -1) {
|
|
4439
|
+
view.requiresGeckoHackNode = gecko;
|
|
4440
|
+
if (cssCheckWarned)
|
|
4441
|
+
return;
|
|
4406
4442
|
console["warn"]("ProseMirror expects the CSS white-space property to be set, preferably to 'pre-wrap'. It is recommended to load style/prosemirror.css from the prosemirror-view package.");
|
|
4443
|
+
cssCheckWarned = true;
|
|
4444
|
+
}
|
|
4407
4445
|
}
|
|
4408
4446
|
|
|
4409
4447
|
// Note that all referencing and parsing is done with the
|
|
@@ -4499,14 +4537,6 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4499
4537
|
to = view.state.doc.resolve(to).after(shared + 1);
|
|
4500
4538
|
let sel = view.state.selection;
|
|
4501
4539
|
let parse = parseBetween(view, from, to);
|
|
4502
|
-
// Chrome sometimes leaves the cursor before the inserted text when
|
|
4503
|
-
// composing after a cursor wrapper. This moves it forward.
|
|
4504
|
-
if (chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from &&
|
|
4505
|
-
parse.sel.head == parse.sel.anchor) {
|
|
4506
|
-
let text = view.cursorWrapper.deco.type.toDOM.nextSibling;
|
|
4507
|
-
let size = text && text.nodeValue ? text.nodeValue.length : 1;
|
|
4508
|
-
parse.sel = { anchor: parse.sel.anchor + size, head: parse.sel.anchor + size };
|
|
4509
|
-
}
|
|
4510
4540
|
let doc = view.state.doc, compare = doc.slice(parse.from, parse.to);
|
|
4511
4541
|
let preferredPos, preferredSide;
|
|
4512
4542
|
// Prefer anchoring to end when Backspace is pressed
|
|
@@ -4541,6 +4571,13 @@ function readDOMChange(view, from, to, typeOver, addedNodes) {
|
|
|
4541
4571
|
return;
|
|
4542
4572
|
}
|
|
4543
4573
|
}
|
|
4574
|
+
// Chrome sometimes leaves the cursor before the inserted text when
|
|
4575
|
+
// composing after a cursor wrapper. This moves it forward.
|
|
4576
|
+
if (chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from &&
|
|
4577
|
+
parse.sel.head == parse.sel.anchor) {
|
|
4578
|
+
let size = change.endB - change.start;
|
|
4579
|
+
parse.sel = { anchor: parse.sel.anchor + size, head: parse.sel.anchor + size };
|
|
4580
|
+
}
|
|
4544
4581
|
view.input.domChangeCount++;
|
|
4545
4582
|
// Handle the case where overwriting a selection by typing matches
|
|
4546
4583
|
// the start or end of the selected content, creating a change
|
|
@@ -4810,6 +4847,12 @@ class EditorView {
|
|
|
4810
4847
|
this.prevDirectPlugins = [];
|
|
4811
4848
|
this.pluginViews = [];
|
|
4812
4849
|
/**
|
|
4850
|
+
Holds `true` when a hack node is needed in Firefox to prevent the
|
|
4851
|
+
[space is eaten issue](https://github.com/ProseMirror/prosemirror/issues/651)
|
|
4852
|
+
@internal
|
|
4853
|
+
*/
|
|
4854
|
+
this.requiresGeckoHackNode = false;
|
|
4855
|
+
/**
|
|
4813
4856
|
When editor content is being dragged, this object contains
|
|
4814
4857
|
information about the dragged slice and whether it is being
|
|
4815
4858
|
copied or moved. At any other time, it is null.
|
|
@@ -4960,21 +5003,27 @@ class EditorView {
|
|
|
4960
5003
|
this.dom.scrollTop = 0;
|
|
4961
5004
|
}
|
|
4962
5005
|
else if (scroll == "to selection") {
|
|
4963
|
-
|
|
4964
|
-
if (this.someProp("handleScrollToSelection", f => f(this))) ;
|
|
4965
|
-
else if (state.selection instanceof NodeSelection) {
|
|
4966
|
-
let target = this.docView.domAfterPos(state.selection.from);
|
|
4967
|
-
if (target.nodeType == 1)
|
|
4968
|
-
scrollRectIntoView(this, target.getBoundingClientRect(), startDOM);
|
|
4969
|
-
}
|
|
4970
|
-
else {
|
|
4971
|
-
scrollRectIntoView(this, this.coordsAtPos(state.selection.head, 1), startDOM);
|
|
4972
|
-
}
|
|
5006
|
+
this.scrollToSelection();
|
|
4973
5007
|
}
|
|
4974
5008
|
else if (oldScrollPos) {
|
|
4975
5009
|
resetScrollPos(oldScrollPos);
|
|
4976
5010
|
}
|
|
4977
5011
|
}
|
|
5012
|
+
/**
|
|
5013
|
+
@internal
|
|
5014
|
+
*/
|
|
5015
|
+
scrollToSelection() {
|
|
5016
|
+
let startDOM = this.domSelection().focusNode;
|
|
5017
|
+
if (this.someProp("handleScrollToSelection", f => f(this))) ;
|
|
5018
|
+
else if (this.state.selection instanceof NodeSelection) {
|
|
5019
|
+
let target = this.docView.domAfterPos(this.state.selection.from);
|
|
5020
|
+
if (target.nodeType == 1)
|
|
5021
|
+
scrollRectIntoView(this, target.getBoundingClientRect(), startDOM);
|
|
5022
|
+
}
|
|
5023
|
+
else {
|
|
5024
|
+
scrollRectIntoView(this, this.coordsAtPos(this.state.selection.head, 1), startDOM);
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
4978
5027
|
destroyPluginViews() {
|
|
4979
5028
|
let view;
|
|
4980
5029
|
while (view = this.pluginViews.pop())
|
|
@@ -5025,6 +5074,24 @@ class EditorView {
|
|
|
5025
5074
|
Query whether the view has focus.
|
|
5026
5075
|
*/
|
|
5027
5076
|
hasFocus() {
|
|
5077
|
+
// Work around IE not handling focus correctly if resize handles are shown.
|
|
5078
|
+
// If the cursor is inside an element with resize handles, activeElement
|
|
5079
|
+
// will be that element instead of this.dom.
|
|
5080
|
+
if (ie) {
|
|
5081
|
+
// If activeElement is within this.dom, and there are no other elements
|
|
5082
|
+
// setting `contenteditable` to false in between, treat it as focused.
|
|
5083
|
+
let node = this.root.activeElement;
|
|
5084
|
+
if (node == this.dom)
|
|
5085
|
+
return true;
|
|
5086
|
+
if (!node || !this.dom.contains(node))
|
|
5087
|
+
return false;
|
|
5088
|
+
while (node && this.dom != node && this.dom.contains(node)) {
|
|
5089
|
+
if (node.contentEditable == 'false')
|
|
5090
|
+
return false;
|
|
5091
|
+
node = node.parentElement;
|
|
5092
|
+
}
|
|
5093
|
+
return true;
|
|
5094
|
+
}
|
|
5028
5095
|
return this.root.activeElement == this.dom;
|
|
5029
5096
|
}
|
|
5030
5097
|
/**
|
package/package.json
CHANGED
package/src/clipboard.ts
CHANGED
|
@@ -3,6 +3,8 @@ import * as browser from "./browser"
|
|
|
3
3
|
import {EditorView} from "./index"
|
|
4
4
|
|
|
5
5
|
export function serializeForClipboard(view: EditorView, slice: Slice) {
|
|
6
|
+
view.someProp("transformCopied", f => { slice = f(slice!) })
|
|
7
|
+
|
|
6
8
|
let context = [], {content, openStart, openEnd} = slice
|
|
7
9
|
while (openStart > 1 && openEnd > 1 && content.childCount == 1 && content.firstChild!.childCount == 1) {
|
|
8
10
|
openStart--
|
package/src/decoration.ts
CHANGED
|
@@ -550,20 +550,25 @@ function mapChildren(
|
|
|
550
550
|
|
|
551
551
|
// Mark the children that are directly touched by changes, and
|
|
552
552
|
// move those that are after the changes.
|
|
553
|
-
let
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
let
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
553
|
+
for (let i = 0, baseOffset = oldOffset; i < mapping.maps.length; i++) {
|
|
554
|
+
let moved = 0
|
|
555
|
+
mapping.maps[i].forEach((oldStart: number, oldEnd: number, newStart: number, newEnd: number) => {
|
|
556
|
+
let dSize = (newEnd - newStart) - (oldEnd - oldStart)
|
|
557
|
+
for (let i = 0; i < children.length; i += 3) {
|
|
558
|
+
let end = children[i + 1] as number
|
|
559
|
+
if (end < 0 || oldStart > end + baseOffset - moved) continue
|
|
560
|
+
let start = (children[i] as number) + baseOffset - moved
|
|
561
|
+
if (oldEnd >= start) {
|
|
562
|
+
children[i + 1] = oldStart <= start ? -2 : -1
|
|
563
|
+
} else if (newStart >= offset && dSize) {
|
|
564
|
+
;(children[i] as number) += dSize
|
|
565
|
+
;(children[i + 1] as number) += dSize
|
|
566
|
+
}
|
|
563
567
|
}
|
|
564
|
-
|
|
568
|
+
moved += dSize
|
|
569
|
+
})
|
|
570
|
+
baseOffset = mapping.maps[i].map(baseOffset, -1)
|
|
565
571
|
}
|
|
566
|
-
for (let i = 0; i < mapping.maps.length; i++) mapping.maps[i].forEach(shift)
|
|
567
572
|
|
|
568
573
|
// Find the child nodes that still correspond to a single node,
|
|
569
574
|
// recursively call mapInner on them and update their positions.
|
package/src/domchange.ts
CHANGED
|
@@ -97,15 +97,6 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
|
|
|
97
97
|
let sel = view.state.selection
|
|
98
98
|
let parse = parseBetween(view, from, to)
|
|
99
99
|
|
|
100
|
-
// Chrome sometimes leaves the cursor before the inserted text when
|
|
101
|
-
// composing after a cursor wrapper. This moves it forward.
|
|
102
|
-
if (browser.chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from &&
|
|
103
|
-
parse.sel.head == parse.sel.anchor) {
|
|
104
|
-
let text = (view.cursorWrapper.deco.type as any).toDOM.nextSibling as DOMNode
|
|
105
|
-
let size = text && text.nodeValue ? text.nodeValue.length : 1
|
|
106
|
-
parse.sel = {anchor: parse.sel.anchor + size, head: parse.sel.anchor + size}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
100
|
let doc = view.state.doc, compare = doc.slice(parse.from, parse.to)
|
|
110
101
|
let preferredPos, preferredSide: "start" | "end"
|
|
111
102
|
// Prefer anchoring to end when Backspace is pressed
|
|
@@ -138,6 +129,14 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
|
|
|
138
129
|
return
|
|
139
130
|
}
|
|
140
131
|
}
|
|
132
|
+
// Chrome sometimes leaves the cursor before the inserted text when
|
|
133
|
+
// composing after a cursor wrapper. This moves it forward.
|
|
134
|
+
if (browser.chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from &&
|
|
135
|
+
parse.sel.head == parse.sel.anchor) {
|
|
136
|
+
let size = change.endB - change.start
|
|
137
|
+
parse.sel = {anchor: parse.sel.anchor + size, head: parse.sel.anchor + size}
|
|
138
|
+
}
|
|
139
|
+
|
|
141
140
|
view.input.domChangeCount++
|
|
142
141
|
// Handle the case where overwriting a selection by typing matches
|
|
143
142
|
// the start or end of the selected content, creating a change
|
package/src/domcoords.ts
CHANGED
|
@@ -341,7 +341,7 @@ export function coordsAtPos(view: EditorView, pos: number, side: number): Rect {
|
|
|
341
341
|
else if (side >= 0 && offset == node.nodeValue!.length) { from--; takeSide = 1 }
|
|
342
342
|
else if (side < 0) { from-- }
|
|
343
343
|
else { to ++ }
|
|
344
|
-
return flattenV(singleRect(textRange(node as Text, from, to),
|
|
344
|
+
return flattenV(singleRect(textRange(node as Text, from, to), 1), takeSide < 0)
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
347
|
|
package/src/domobserver.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import {Selection} from "prosemirror-state"
|
|
1
2
|
import * as browser from "./browser"
|
|
2
|
-
import {domIndex, isEquivalentPosition} from "./dom"
|
|
3
|
-
import {hasFocusAndSelection, selectionToDOM} from "./selection"
|
|
3
|
+
import {domIndex, isEquivalentPosition, selectionCollapsed, DOMSelection} from "./dom"
|
|
4
|
+
import {hasFocusAndSelection, selectionToDOM, selectionFromDOM} from "./selection"
|
|
4
5
|
import {EditorView} from "./index"
|
|
5
6
|
|
|
6
7
|
const observeOptions = {
|
|
@@ -20,7 +21,7 @@ class SelectionState {
|
|
|
20
21
|
focusNode: Node | null = null
|
|
21
22
|
focusOffset: number = 0
|
|
22
23
|
|
|
23
|
-
set(sel:
|
|
24
|
+
set(sel: DOMSelection) {
|
|
24
25
|
this.anchorNode = sel.anchorNode; this.anchorOffset = sel.anchorOffset
|
|
25
26
|
this.focusNode = sel.focusNode; this.focusOffset = sel.focusOffset
|
|
26
27
|
}
|
|
@@ -29,7 +30,7 @@ class SelectionState {
|
|
|
29
30
|
this.anchorNode = this.focusNode = null
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
eq(sel:
|
|
33
|
+
eq(sel: DOMSelection) {
|
|
33
34
|
return sel.anchorNode == this.anchorNode && sel.anchorOffset == this.anchorOffset &&
|
|
34
35
|
sel.focusNode == this.focusNode && sel.focusOffset == this.focusOffset
|
|
35
36
|
}
|
|
@@ -138,7 +139,7 @@ export class DOMObserver {
|
|
|
138
139
|
this.currentSelection.set(this.view.domSelection())
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
ignoreSelectionChange(sel:
|
|
142
|
+
ignoreSelectionChange(sel: DOMSelection) {
|
|
142
143
|
if (sel.rangeCount == 0) return true
|
|
143
144
|
let container = sel.getRangeAt(0).commonAncestorContainer
|
|
144
145
|
let desc = this.view.docView.nearestDesc(container)
|
|
@@ -152,18 +153,19 @@ export class DOMObserver {
|
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
flush() {
|
|
155
|
-
|
|
156
|
+
let {view} = this
|
|
157
|
+
if (!view.docView || this.flushingSoon > -1) return
|
|
156
158
|
let mutations = this.observer ? this.observer.takeRecords() : []
|
|
157
159
|
if (this.queue.length) {
|
|
158
160
|
mutations = this.queue.concat(mutations)
|
|
159
161
|
this.queue.length = 0
|
|
160
162
|
}
|
|
161
163
|
|
|
162
|
-
let sel =
|
|
163
|
-
let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(
|
|
164
|
+
let sel = view.domSelection()
|
|
165
|
+
let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel)
|
|
164
166
|
|
|
165
167
|
let from = -1, to = -1, typeOver = false, added: Node[] = []
|
|
166
|
-
if (
|
|
168
|
+
if (view.editable) {
|
|
167
169
|
for (let i = 0; i < mutations.length; i++) {
|
|
168
170
|
let result = this.registerMutation(mutations[i], added)
|
|
169
171
|
if (result) {
|
|
@@ -183,14 +185,26 @@ export class DOMObserver {
|
|
|
183
185
|
}
|
|
184
186
|
}
|
|
185
187
|
|
|
186
|
-
|
|
188
|
+
let readSel: Selection | null = null
|
|
189
|
+
// If it looks like the browser has reset the selection to the
|
|
190
|
+
// start of the document after focus, restore the selection from
|
|
191
|
+
// the state
|
|
192
|
+
if (from < 0 && newSel && view.input.lastFocus > Date.now() - 200 &&
|
|
193
|
+
view.input.lastTouch < Date.now() - 300 &&
|
|
194
|
+
selectionCollapsed(sel) && (readSel = selectionFromDOM(view)) &&
|
|
195
|
+
readSel.eq(Selection.near(view.state.doc.resolve(0), 1))) {
|
|
196
|
+
view.input.lastFocus = 0
|
|
197
|
+
selectionToDOM(view)
|
|
198
|
+
this.currentSelection.set(sel)
|
|
199
|
+
view.scrollToSelection()
|
|
200
|
+
} else if (from > -1 || newSel) {
|
|
187
201
|
if (from > -1) {
|
|
188
|
-
|
|
189
|
-
checkCSS(
|
|
202
|
+
view.docView.markDirty(from, to)
|
|
203
|
+
checkCSS(view)
|
|
190
204
|
}
|
|
191
205
|
this.handleDOMChange(from, to, typeOver, added)
|
|
192
|
-
if (
|
|
193
|
-
else if (!this.currentSelection.eq(sel)) selectionToDOM(
|
|
206
|
+
if (view.docView && view.docView.dirty) view.updateState(view.state)
|
|
207
|
+
else if (!this.currentSelection.eq(sel)) selectionToDOM(view)
|
|
194
208
|
this.currentSelection.set(sel)
|
|
195
209
|
}
|
|
196
210
|
}
|
|
@@ -243,11 +257,16 @@ export class DOMObserver {
|
|
|
243
257
|
}
|
|
244
258
|
}
|
|
245
259
|
|
|
246
|
-
let cssChecked =
|
|
260
|
+
let cssChecked: WeakMap<EditorView, null> = new WeakMap()
|
|
261
|
+
let cssCheckWarned: boolean = false
|
|
247
262
|
|
|
248
263
|
function checkCSS(view: EditorView) {
|
|
249
|
-
if (cssChecked) return
|
|
250
|
-
cssChecked
|
|
251
|
-
if (getComputedStyle(view.dom).whiteSpace
|
|
264
|
+
if (cssChecked.has(view)) return
|
|
265
|
+
cssChecked.set(view, null)
|
|
266
|
+
if (['normal', 'nowrap', 'pre-line'].indexOf(getComputedStyle(view.dom).whiteSpace) !== -1) {
|
|
267
|
+
view.requiresGeckoHackNode = browser.gecko
|
|
268
|
+
if (cssCheckWarned) return
|
|
252
269
|
console["warn"]("ProseMirror expects the CSS white-space property to be set, preferably to 'pre-wrap'. It is recommended to load style/prosemirror.css from the prosemirror-view package.")
|
|
270
|
+
cssCheckWarned = true
|
|
271
|
+
}
|
|
253
272
|
}
|