@tiptap/y-tiptap 3.0.4 → 3.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/lib.d.ts +4 -0
- package/dist/src/plugins/sync-plugin.d.ts +6 -0
- package/dist/src/y-tiptap.d.ts +1 -1
- package/dist/y-tiptap.cjs +309 -31
- package/dist/y-tiptap.cjs.map +1 -1
- package/dist/y-tiptap.js +307 -33
- package/dist/y-tiptap.js.map +1 -1
- package/package.json +1 -1
package/dist/src/lib.d.ts
CHANGED
|
@@ -96,7 +96,11 @@ export function yDocToProsemirrorJSON(ydoc: Y.Doc, xmlFragment?: string): Record
|
|
|
96
96
|
export function yXmlFragmentToProsemirrorJSON(xmlFragment: Y.XmlFragment): Record<string, any>;
|
|
97
97
|
export function setMeta(view: any, key: any, value: any): void;
|
|
98
98
|
export function absolutePositionToRelativePosition(pos: number, type: Y.XmlFragment, mapping: ProsemirrorMapping): any;
|
|
99
|
+
export function isMisresolvedTextPosition(y: Y.Doc, relPos: Y.RelativePosition, absPos: number | null): boolean;
|
|
99
100
|
export function relativePositionToAbsolutePosition(y: Y.Doc, documentType: Y.XmlFragment, relPos: any, mapping: ProsemirrorMapping): null | number;
|
|
101
|
+
export function findAbsolutePositionAfterStructuralChange(oldDoc: import('prosemirror-model').Node, newDoc: import('prosemirror-model').Node, absPos: number): number | null;
|
|
102
|
+
export function isStructuralTransaction(tr: import('prosemirror-state').Transaction, oldDoc: import('prosemirror-model').Node): boolean;
|
|
103
|
+
export function isMisresolvedAfterStructuralChange(oldDoc: import('prosemirror-model').Node, newDoc: import('prosemirror-model').Node, oldAbs: number, resolvedAbs: number | null): boolean;
|
|
100
104
|
export function yXmlFragmentToProseMirrorFragment(yXmlFragment: Y.XmlFragment, schema: Schema): Fragment;
|
|
101
105
|
export function yXmlFragmentToProseMirrorRootNode(yXmlFragment: Y.XmlFragment, schema: Schema): Node;
|
|
102
106
|
export function initProseMirrorDoc(yXmlFragment: Y.XmlFragment, schema: Schema): {
|
|
@@ -6,8 +6,11 @@ export function isVisible(item: Y.Item, snapshot?: Y.Snapshot): boolean;
|
|
|
6
6
|
export function ySyncPlugin(yXmlFragment: Y.XmlFragment, { colors, colorMapping, permanentUserData, onFirstRender, mapping }?: YSyncOpts): any;
|
|
7
7
|
export function getRelativeSelection(pmbinding: ProsemirrorBinding, state: import('prosemirror-state').EditorState): {
|
|
8
8
|
type: any;
|
|
9
|
+
depth: any;
|
|
9
10
|
anchor: any;
|
|
10
11
|
head: any;
|
|
12
|
+
absAnchor: number;
|
|
13
|
+
absHead: number;
|
|
11
14
|
};
|
|
12
15
|
/**
|
|
13
16
|
* Binding for prosemirror.
|
|
@@ -44,8 +47,11 @@ export class ProsemirrorBinding {
|
|
|
44
47
|
*/
|
|
45
48
|
beforeTransactionSelection: {
|
|
46
49
|
type: any;
|
|
50
|
+
depth: any;
|
|
47
51
|
anchor: any;
|
|
48
52
|
head: any;
|
|
53
|
+
absAnchor: number;
|
|
54
|
+
absHead: number;
|
|
49
55
|
};
|
|
50
56
|
beforeAllTransactions: () => void;
|
|
51
57
|
afterAllTransactions: () => void;
|
package/dist/src/y-tiptap.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ export * from "./plugins/cursor-plugin.js";
|
|
|
2
2
|
export * from "./plugins/undo-plugin.js";
|
|
3
3
|
export * from "./plugins/keys.js";
|
|
4
4
|
export { ySyncPlugin, isVisible, getRelativeSelection, ProsemirrorBinding, updateYFragment } from "./plugins/sync-plugin.js";
|
|
5
|
-
export { absolutePositionToRelativePosition, relativePositionToAbsolutePosition, setMeta, prosemirrorJSONToYDoc, yDocToProsemirrorJSON, yDocToProsemirror, prosemirrorToYDoc, prosemirrorJSONToYXmlFragment, yXmlFragmentToProsemirrorJSON, yXmlFragmentToProsemirror, prosemirrorToYXmlFragment, yXmlFragmentToProseMirrorRootNode, yXmlFragmentToProseMirrorFragment, initProseMirrorDoc } from "./lib.js";
|
|
5
|
+
export { absolutePositionToRelativePosition, relativePositionToAbsolutePosition, setMeta, findAbsolutePositionAfterStructuralChange, isMisresolvedTextPosition, isMisresolvedAfterStructuralChange, isStructuralTransaction, prosemirrorJSONToYDoc, yDocToProsemirrorJSON, yDocToProsemirror, prosemirrorToYDoc, prosemirrorJSONToYXmlFragment, yXmlFragmentToProsemirrorJSON, yXmlFragmentToProsemirror, prosemirrorToYXmlFragment, yXmlFragmentToProseMirrorRootNode, yXmlFragmentToProseMirrorFragment, initProseMirrorDoc } from "./lib.js";
|
package/dist/y-tiptap.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var eventloop = require('lib0/eventloop');
|
|
|
20
20
|
var map = require('lib0/map');
|
|
21
21
|
var sha256 = require('lib0/hash/sha256');
|
|
22
22
|
var buf = require('lib0/buffer');
|
|
23
|
+
var prosemirrorTransform = require('prosemirror-transform');
|
|
23
24
|
|
|
24
25
|
function _interopNamespace(e) {
|
|
25
26
|
if (e && e.__esModule) return e;
|
|
@@ -318,8 +319,9 @@ const ySyncPlugin = (yXmlFragment, {
|
|
|
318
319
|
* @param {import('prosemirror-state').Transaction} tr
|
|
319
320
|
* @param {ReturnType<typeof getRelativeSelection>} relSel
|
|
320
321
|
* @param {ProsemirrorBinding} binding
|
|
322
|
+
* @param {import('prosemirror-model').Node} [oldDoc]
|
|
321
323
|
*/
|
|
322
|
-
const restoreRelativeSelection = (tr, relSel, binding) => {
|
|
324
|
+
const restoreRelativeSelection = (tr, relSel, binding, oldDoc) => {
|
|
323
325
|
if (relSel !== null && relSel.anchor !== null && relSel.head !== null) {
|
|
324
326
|
if (relSel.type === 'all') {
|
|
325
327
|
tr.setSelection(new prosemirrorState.AllSelection(tr.doc));
|
|
@@ -330,8 +332,12 @@ const restoreRelativeSelection = (tr, relSel, binding) => {
|
|
|
330
332
|
relSel.anchor,
|
|
331
333
|
binding.mapping
|
|
332
334
|
);
|
|
333
|
-
|
|
334
|
-
|
|
335
|
+
// anchor is null when the referenced node was deleted or moved out of
|
|
336
|
+
// binding.type by a remote update; resolving null would throw.
|
|
337
|
+
if (anchor !== null) {
|
|
338
|
+
tr.setSelection(createSafeNodeSelection(tr, anchor));
|
|
339
|
+
}
|
|
340
|
+
} else if (relSel.type === 'nodeRange') {
|
|
335
341
|
const anchor = relativePositionToAbsolutePosition(
|
|
336
342
|
binding.doc,
|
|
337
343
|
binding.type,
|
|
@@ -344,6 +350,46 @@ const restoreRelativeSelection = (tr, relSel, binding) => {
|
|
|
344
350
|
relSel.head,
|
|
345
351
|
binding.mapping
|
|
346
352
|
);
|
|
353
|
+
const selection = createSafeNodeRangeSelection(tr, anchor, head, relSel.depth);
|
|
354
|
+
if (selection !== null) {
|
|
355
|
+
tr.setSelection(selection);
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
let anchor = relativePositionToAbsolutePosition(
|
|
359
|
+
binding.doc,
|
|
360
|
+
binding.type,
|
|
361
|
+
relSel.anchor,
|
|
362
|
+
binding.mapping
|
|
363
|
+
);
|
|
364
|
+
let head = relativePositionToAbsolutePosition(
|
|
365
|
+
binding.doc,
|
|
366
|
+
binding.type,
|
|
367
|
+
relSel.head,
|
|
368
|
+
binding.mapping
|
|
369
|
+
);
|
|
370
|
+
const needsContentFallback = oldDoc != null &&
|
|
371
|
+
relSel.absAnchor != null &&
|
|
372
|
+
relSel.absHead != null &&
|
|
373
|
+
(
|
|
374
|
+
anchor === null ||
|
|
375
|
+
head === null ||
|
|
376
|
+
(relSel.absAnchor > 1 && anchor !== null && anchor <= 1) ||
|
|
377
|
+
(relSel.absHead > 1 && head !== null && head <= 1) ||
|
|
378
|
+
isMisresolvedAfterStructuralChange(oldDoc, tr.doc, relSel.absAnchor, anchor) ||
|
|
379
|
+
isMisresolvedAfterStructuralChange(oldDoc, tr.doc, relSel.absHead, head)
|
|
380
|
+
);
|
|
381
|
+
if (needsContentFallback) {
|
|
382
|
+
anchor = findAbsolutePositionAfterStructuralChange(
|
|
383
|
+
oldDoc,
|
|
384
|
+
tr.doc,
|
|
385
|
+
relSel.absAnchor
|
|
386
|
+
);
|
|
387
|
+
head = findAbsolutePositionAfterStructuralChange(
|
|
388
|
+
oldDoc,
|
|
389
|
+
tr.doc,
|
|
390
|
+
relSel.absHead
|
|
391
|
+
);
|
|
392
|
+
}
|
|
347
393
|
if (anchor !== null && head !== null) {
|
|
348
394
|
tr.setSelection(prosemirrorState.TextSelection.between(tr.doc.resolve(anchor), tr.doc.resolve(head)));
|
|
349
395
|
}
|
|
@@ -368,23 +414,67 @@ const createSafeNodeSelection = (tr, pos) => {
|
|
|
368
414
|
}
|
|
369
415
|
};
|
|
370
416
|
|
|
417
|
+
/**
|
|
418
|
+
* Safely reconstructs a NodeRangeSelection from resolved absolute positions.
|
|
419
|
+
*
|
|
420
|
+
* @param {import('prosemirror-state').Transaction} tr - The transaction whose document provides resolved positions.
|
|
421
|
+
* @param {number|null} anchor - Absolute document position marking the start (boundary) of the node range.
|
|
422
|
+
* Use `relativePositionToAbsolutePosition` before calling this function.
|
|
423
|
+
* @param {number|null} head - Absolute document position marking the end (boundary) of the node range.
|
|
424
|
+
* Use `relativePositionToAbsolutePosition` before calling this function.
|
|
425
|
+
* @param {number|undefined} depth - The nesting depth at which the range operates (e.g. 0 for
|
|
426
|
+
* top-level blocks, 1 for blocks nested inside a wrapper). Passed through to
|
|
427
|
+
* `Selection.fromJSON`; ignored by `@tiptap/extension-node-range` < v2.29 but
|
|
428
|
+
* properly stored starting from that version.
|
|
429
|
+
* @returns {import('prosemirror-state').Selection|null} Reconstructed selection, or null if
|
|
430
|
+
* anchor/head could not be resolved.
|
|
431
|
+
*/
|
|
432
|
+
const createSafeNodeRangeSelection = (tr, anchor, head, depth) => {
|
|
433
|
+
if (anchor === null || head === null) {
|
|
434
|
+
return null
|
|
435
|
+
}
|
|
436
|
+
const clampedAnchor = Math.min(Math.max(anchor, 0), tr.doc.content.size);
|
|
437
|
+
const clampedHead = Math.min(Math.max(head, 0), tr.doc.content.size);
|
|
438
|
+
try {
|
|
439
|
+
const selection = prosemirrorState.Selection.fromJSON(tr.doc, {
|
|
440
|
+
type: 'nodeRange',
|
|
441
|
+
anchor: clampedAnchor,
|
|
442
|
+
head: clampedHead,
|
|
443
|
+
depth
|
|
444
|
+
});
|
|
445
|
+
if (!selection.ranges.length) {
|
|
446
|
+
return prosemirrorState.TextSelection.near(tr.doc.resolve(clampedAnchor))
|
|
447
|
+
}
|
|
448
|
+
return selection
|
|
449
|
+
} catch (e) {
|
|
450
|
+
return prosemirrorState.TextSelection.near(tr.doc.resolve(clampedAnchor))
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
371
454
|
/**
|
|
372
455
|
* @param {ProsemirrorBinding} pmbinding
|
|
373
456
|
* @param {import('prosemirror-state').EditorState} state
|
|
374
457
|
*/
|
|
375
|
-
const getRelativeSelection = (pmbinding, state) =>
|
|
376
|
-
type
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
458
|
+
const getRelativeSelection = (pmbinding, state) => {
|
|
459
|
+
const type = /** @type {any} */ (state.selection).jsonID;
|
|
460
|
+
return {
|
|
461
|
+
type,
|
|
462
|
+
// `depth` is only meaningful for NodeRangeSelection; undefined for every other type.
|
|
463
|
+
depth: type === 'nodeRange' ? /** @type {any} */ (state.selection).depth : undefined,
|
|
464
|
+
anchor: absolutePositionToRelativePosition(
|
|
465
|
+
state.selection.anchor,
|
|
466
|
+
pmbinding.type,
|
|
467
|
+
pmbinding.mapping
|
|
468
|
+
),
|
|
469
|
+
head: absolutePositionToRelativePosition(
|
|
470
|
+
state.selection.head,
|
|
471
|
+
pmbinding.type,
|
|
472
|
+
pmbinding.mapping
|
|
473
|
+
),
|
|
474
|
+
absAnchor: state.selection.anchor,
|
|
475
|
+
absHead: state.selection.head
|
|
476
|
+
}
|
|
477
|
+
};
|
|
388
478
|
|
|
389
479
|
/**
|
|
390
480
|
* Binding for prosemirror.
|
|
@@ -707,6 +797,9 @@ class ProsemirrorBinding {
|
|
|
707
797
|
);
|
|
708
798
|
transaction.changed.forEach(delType);
|
|
709
799
|
transaction.changedParentTypes.forEach(delType);
|
|
800
|
+
// Rebuild the full Y↔PM mapping so relative cursor positions resolve against
|
|
801
|
+
// current node sizes after structural changes (e.g. drag-and-drop block moves).
|
|
802
|
+
this.mapping.clear();
|
|
710
803
|
const fragmentContent = this.type.toArray().map((t) =>
|
|
711
804
|
createNodeIfNotExists(
|
|
712
805
|
/** @type {Y.XmlElement | Y.XmlHook} */ (t),
|
|
@@ -714,13 +807,14 @@ class ProsemirrorBinding {
|
|
|
714
807
|
this
|
|
715
808
|
)
|
|
716
809
|
).filter((n) => n !== null);
|
|
810
|
+
const oldDoc = this.prosemirrorView.state.doc;
|
|
717
811
|
// @ts-ignore
|
|
718
812
|
let tr = this._tr.replace(
|
|
719
813
|
0,
|
|
720
814
|
this.prosemirrorView.state.doc.content.size,
|
|
721
815
|
new PModel__namespace.Slice(PModel__namespace.Fragment.from(fragmentContent), 0, 0)
|
|
722
816
|
);
|
|
723
|
-
restoreRelativeSelection(tr, this.beforeTransactionSelection, this);
|
|
817
|
+
restoreRelativeSelection(tr, this.beforeTransactionSelection, this, oldDoc);
|
|
724
818
|
tr = tr.setMeta(ySyncPluginKey, { isChangeOrigin: true, isUndoRedoOperation: transaction.origin instanceof Y__namespace.UndoManager });
|
|
725
819
|
if (
|
|
726
820
|
this.beforeTransactionSelection !== null && this._isLocalCursorInView()
|
|
@@ -1606,6 +1700,26 @@ const absolutePositionToRelativePosition = (pos, type, mapping) => {
|
|
|
1606
1700
|
return Y__namespace.createRelativePositionFromTypeIndex(type, type._length, -1)
|
|
1607
1701
|
};
|
|
1608
1702
|
|
|
1703
|
+
/**
|
|
1704
|
+
* Item-id based relative positions can misresolve to the document start after
|
|
1705
|
+
* block reorder during collaborative drag-and-drop.
|
|
1706
|
+
*
|
|
1707
|
+
* @param {Y.Doc} y
|
|
1708
|
+
* @param {Y.RelativePosition} relPos
|
|
1709
|
+
* @param {number|null} absPos
|
|
1710
|
+
* @return {boolean}
|
|
1711
|
+
*/
|
|
1712
|
+
const isMisresolvedTextPosition = (y, relPos, absPos) => {
|
|
1713
|
+
if (absPos === null) {
|
|
1714
|
+
return false
|
|
1715
|
+
}
|
|
1716
|
+
const decoded = Y__namespace.createAbsolutePositionFromRelativePosition(relPos, y);
|
|
1717
|
+
return decoded !== null &&
|
|
1718
|
+
decoded.type instanceof Y__namespace.XmlText &&
|
|
1719
|
+
relPos.item !== null &&
|
|
1720
|
+
absPos <= 1
|
|
1721
|
+
};
|
|
1722
|
+
|
|
1609
1723
|
const createRelativePosition = (type, item) => {
|
|
1610
1724
|
let typeid = null;
|
|
1611
1725
|
let tname = null;
|
|
@@ -1643,7 +1757,11 @@ const relativePositionToAbsolutePosition = (y, documentType, relPos, mapping) =>
|
|
|
1643
1757
|
if (t instanceof Y__namespace.XmlText) {
|
|
1644
1758
|
pos += t._length;
|
|
1645
1759
|
} else {
|
|
1646
|
-
|
|
1760
|
+
const mapped = mapping.get(t);
|
|
1761
|
+
if (mapped == null) {
|
|
1762
|
+
return null
|
|
1763
|
+
}
|
|
1764
|
+
pos += /** @type {any} */ (mapped).nodeSize;
|
|
1647
1765
|
}
|
|
1648
1766
|
}
|
|
1649
1767
|
n = /** @type {Y.Item} */ (n.right);
|
|
@@ -1667,7 +1785,11 @@ const relativePositionToAbsolutePosition = (y, documentType, relPos, mapping) =>
|
|
|
1667
1785
|
if (contentType instanceof Y__namespace.XmlText) {
|
|
1668
1786
|
pos += contentType._length;
|
|
1669
1787
|
} else {
|
|
1670
|
-
|
|
1788
|
+
const mapped = mapping.get(contentType);
|
|
1789
|
+
if (mapped == null) {
|
|
1790
|
+
return null
|
|
1791
|
+
}
|
|
1792
|
+
pos += /** @type {any} */ (mapped).nodeSize;
|
|
1671
1793
|
}
|
|
1672
1794
|
}
|
|
1673
1795
|
n = n.right;
|
|
@@ -1675,7 +1797,133 @@ const relativePositionToAbsolutePosition = (y, documentType, relPos, mapping) =>
|
|
|
1675
1797
|
}
|
|
1676
1798
|
type = /** @type {Y.AbstractType} */ (parent);
|
|
1677
1799
|
}
|
|
1678
|
-
|
|
1800
|
+
const absPos = pos - 1; // we don't count the most outer tag, because it is a fragment
|
|
1801
|
+
if (isMisresolvedTextPosition(y, relPos, absPos)) {
|
|
1802
|
+
return null
|
|
1803
|
+
}
|
|
1804
|
+
return absPos
|
|
1805
|
+
};
|
|
1806
|
+
|
|
1807
|
+
/**
|
|
1808
|
+
* @param {import('prosemirror-model').Node} oldDoc
|
|
1809
|
+
* @param {import('prosemirror-model').Node} newDoc
|
|
1810
|
+
* @param {number} absPos
|
|
1811
|
+
* @return {number|null}
|
|
1812
|
+
*/
|
|
1813
|
+
const findAbsolutePositionAfterStructuralChange = (oldDoc, newDoc, absPos) => {
|
|
1814
|
+
let pos = 0;
|
|
1815
|
+
let targetIdx = 0;
|
|
1816
|
+
for (; targetIdx < oldDoc.childCount; targetIdx++) {
|
|
1817
|
+
const child = oldDoc.child(targetIdx);
|
|
1818
|
+
if (pos + child.nodeSize > absPos) {
|
|
1819
|
+
break
|
|
1820
|
+
}
|
|
1821
|
+
pos += child.nodeSize;
|
|
1822
|
+
}
|
|
1823
|
+
if (targetIdx >= oldDoc.childCount) {
|
|
1824
|
+
return null
|
|
1825
|
+
}
|
|
1826
|
+
const targetChild = oldDoc.child(targetIdx);
|
|
1827
|
+
const offsetInChild = absPos - pos;
|
|
1828
|
+
|
|
1829
|
+
let occurrence = 0;
|
|
1830
|
+
for (let i = 0; i <= targetIdx; i++) {
|
|
1831
|
+
const child = oldDoc.child(i);
|
|
1832
|
+
if (child.type === targetChild.type && child.textContent === targetChild.textContent) {
|
|
1833
|
+
occurrence++;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
let matchCount = 0;
|
|
1838
|
+
let newPos = 0;
|
|
1839
|
+
for (let i = 0; i < newDoc.childCount; i++) {
|
|
1840
|
+
const child = newDoc.child(i);
|
|
1841
|
+
if (child.type === targetChild.type && child.textContent === targetChild.textContent) {
|
|
1842
|
+
matchCount++;
|
|
1843
|
+
if (matchCount === occurrence) {
|
|
1844
|
+
const remapped = newPos + offsetInChild;
|
|
1845
|
+
const contentStart = newPos + 1;
|
|
1846
|
+
const contentEnd = newPos + child.nodeSize - 1;
|
|
1847
|
+
return Math.max(contentStart, Math.min(remapped, contentEnd))
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
newPos += child.nodeSize;
|
|
1851
|
+
}
|
|
1852
|
+
return null
|
|
1853
|
+
};
|
|
1854
|
+
|
|
1855
|
+
/**
|
|
1856
|
+
* Returns true when a transaction changes block structure rather than only
|
|
1857
|
+
* editing inline content inside existing blocks.
|
|
1858
|
+
*
|
|
1859
|
+
* @param {import('prosemirror-state').Transaction} tr
|
|
1860
|
+
* @param {import('prosemirror-model').Node} oldDoc
|
|
1861
|
+
* @return {boolean}
|
|
1862
|
+
*/
|
|
1863
|
+
const isStructuralTransaction = (tr, oldDoc) => {
|
|
1864
|
+
if (!tr.docChanged) {
|
|
1865
|
+
return false
|
|
1866
|
+
}
|
|
1867
|
+
if (tr.doc.childCount !== oldDoc.childCount) {
|
|
1868
|
+
return true
|
|
1869
|
+
}
|
|
1870
|
+
for (const step of tr.steps) {
|
|
1871
|
+
if (step instanceof prosemirrorTransform.ReplaceStep) {
|
|
1872
|
+
if (step.from === 0 && step.to === oldDoc.content.size) {
|
|
1873
|
+
return true
|
|
1874
|
+
}
|
|
1875
|
+
if (step.slice.content.size > 0) {
|
|
1876
|
+
let hasBlock = false;
|
|
1877
|
+
step.slice.content.forEach((node) => {
|
|
1878
|
+
if (node.isBlock) {
|
|
1879
|
+
hasBlock = true;
|
|
1880
|
+
}
|
|
1881
|
+
});
|
|
1882
|
+
if (hasBlock) {
|
|
1883
|
+
return true
|
|
1884
|
+
}
|
|
1885
|
+
} else if (step.to > step.from) {
|
|
1886
|
+
const $from = oldDoc.resolve(step.from);
|
|
1887
|
+
const $to = oldDoc.resolve(step.to);
|
|
1888
|
+
if ($from.depth === 0 && $to.depth === 0 && $from.index() !== $to.index()) {
|
|
1889
|
+
return true
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return false
|
|
1895
|
+
};
|
|
1896
|
+
|
|
1897
|
+
/**
|
|
1898
|
+
* Detect stale relative positions after structural changes that resolve to the
|
|
1899
|
+
* wrong text block or to the start of the correct block.
|
|
1900
|
+
*
|
|
1901
|
+
* @param {import('prosemirror-model').Node} oldDoc
|
|
1902
|
+
* @param {import('prosemirror-model').Node} newDoc
|
|
1903
|
+
* @param {number} oldAbs
|
|
1904
|
+
* @param {number|null} resolvedAbs
|
|
1905
|
+
* @return {boolean}
|
|
1906
|
+
*/
|
|
1907
|
+
const isMisresolvedAfterStructuralChange = (oldDoc, newDoc, oldAbs, resolvedAbs) => {
|
|
1908
|
+
if (resolvedAbs === null) {
|
|
1909
|
+
return false
|
|
1910
|
+
}
|
|
1911
|
+
const $old = oldDoc.resolve(oldAbs);
|
|
1912
|
+
const $new = newDoc.resolve(resolvedAbs);
|
|
1913
|
+
if (!$old.parent.isTextblock || !$new.parent.isTextblock) {
|
|
1914
|
+
return false
|
|
1915
|
+
}
|
|
1916
|
+
if ($old.parent.textContent !== $new.parent.textContent) {
|
|
1917
|
+
return true
|
|
1918
|
+
}
|
|
1919
|
+
if ($old.parentOffset !== 0 && $new.parentOffset === 0) {
|
|
1920
|
+
return true
|
|
1921
|
+
}
|
|
1922
|
+
if ($old.parentOffset === 0 && $new.parentOffset === 0) {
|
|
1923
|
+
const expected = findAbsolutePositionAfterStructuralChange(oldDoc, newDoc, oldAbs);
|
|
1924
|
+
return expected !== null && expected !== resolvedAbs
|
|
1925
|
+
}
|
|
1926
|
+
return false
|
|
1679
1927
|
};
|
|
1680
1928
|
|
|
1681
1929
|
/**
|
|
@@ -2094,7 +2342,7 @@ const yCursorPlugin = (
|
|
|
2094
2342
|
selectionBuilder
|
|
2095
2343
|
)
|
|
2096
2344
|
},
|
|
2097
|
-
apply (tr, prevState,
|
|
2345
|
+
apply (tr, prevState, oldState, newState) {
|
|
2098
2346
|
const ystate = ySyncPluginKey.getState(newState);
|
|
2099
2347
|
const yCursorState = tr.getMeta(yCursorPluginKey);
|
|
2100
2348
|
if (
|
|
@@ -2109,7 +2357,19 @@ const yCursorPlugin = (
|
|
|
2109
2357
|
selectionBuilder
|
|
2110
2358
|
)
|
|
2111
2359
|
}
|
|
2112
|
-
|
|
2360
|
+
if (tr.docChanged) {
|
|
2361
|
+
if (isStructuralTransaction(tr, oldState.doc)) {
|
|
2362
|
+
return createDecorations(
|
|
2363
|
+
newState,
|
|
2364
|
+
awareness,
|
|
2365
|
+
awarenessStateFilter,
|
|
2366
|
+
cursorBuilder,
|
|
2367
|
+
selectionBuilder
|
|
2368
|
+
)
|
|
2369
|
+
}
|
|
2370
|
+
return prevState.map(tr.mapping, tr.doc)
|
|
2371
|
+
}
|
|
2372
|
+
return prevState
|
|
2113
2373
|
}
|
|
2114
2374
|
},
|
|
2115
2375
|
props: {
|
|
@@ -2124,15 +2384,19 @@ const yCursorPlugin = (
|
|
|
2124
2384
|
setMeta(view, yCursorPluginKey, { awarenessUpdated: true });
|
|
2125
2385
|
}
|
|
2126
2386
|
};
|
|
2127
|
-
|
|
2128
|
-
|
|
2387
|
+
/**
|
|
2388
|
+
* @param {import('prosemirror-view').EditorView} editorView
|
|
2389
|
+
* @param {{ force?: boolean }} [opts]
|
|
2390
|
+
*/
|
|
2391
|
+
const updateCursorInfo = (editorView, opts = {}) => {
|
|
2392
|
+
const ystate = ySyncPluginKey.getState(editorView.state);
|
|
2129
2393
|
// `ystate` is undefined during sync-plugin init and Y.Doc transitions;
|
|
2130
2394
|
// reading from it here would throw and break cursor tracking for the view.
|
|
2131
2395
|
if (!ystate || !ystate.binding) return
|
|
2132
2396
|
// @note We make implicit checks when checking for the cursor property
|
|
2133
2397
|
const current = awareness.getLocalState() || {};
|
|
2134
|
-
if (
|
|
2135
|
-
const selection = getSelection(
|
|
2398
|
+
if (editorView.hasFocus()) {
|
|
2399
|
+
const selection = getSelection(editorView.state);
|
|
2136
2400
|
/**
|
|
2137
2401
|
* @type {Y.RelativePosition}
|
|
2138
2402
|
*/
|
|
@@ -2150,6 +2414,7 @@ const yCursorPlugin = (
|
|
|
2150
2414
|
ystate.binding.mapping
|
|
2151
2415
|
);
|
|
2152
2416
|
if (
|
|
2417
|
+
opts.force ||
|
|
2153
2418
|
current.cursor == null ||
|
|
2154
2419
|
!Y__namespace.compareRelativePositions(
|
|
2155
2420
|
Y__namespace.createRelativePositionFromJSON(current.cursor.anchor),
|
|
@@ -2178,14 +2443,23 @@ const yCursorPlugin = (
|
|
|
2178
2443
|
awareness.setLocalStateField(cursorStateField, null);
|
|
2179
2444
|
}
|
|
2180
2445
|
};
|
|
2446
|
+
const onFocusChange = () => updateCursorInfo(view);
|
|
2181
2447
|
awareness.on('change', awarenessListener);
|
|
2182
|
-
view.dom.addEventListener('focusin',
|
|
2183
|
-
view.dom.addEventListener('focusout',
|
|
2448
|
+
view.dom.addEventListener('focusin', onFocusChange);
|
|
2449
|
+
view.dom.addEventListener('focusout', onFocusChange);
|
|
2184
2450
|
return {
|
|
2185
|
-
update:
|
|
2451
|
+
update: (editorView, prevState) => {
|
|
2452
|
+
const ystate = ySyncPluginKey.getState(editorView.state);
|
|
2453
|
+
const forceAwarenessUpdate = !!(
|
|
2454
|
+
ystate?.isChangeOrigin &&
|
|
2455
|
+
prevState?.doc &&
|
|
2456
|
+
!prevState.doc.eq(editorView.state.doc)
|
|
2457
|
+
);
|
|
2458
|
+
updateCursorInfo(editorView, { force: forceAwarenessUpdate });
|
|
2459
|
+
},
|
|
2186
2460
|
destroy: () => {
|
|
2187
|
-
view.dom.removeEventListener('focusin',
|
|
2188
|
-
view.dom.removeEventListener('focusout',
|
|
2461
|
+
view.dom.removeEventListener('focusin', onFocusChange);
|
|
2462
|
+
view.dom.removeEventListener('focusout', onFocusChange);
|
|
2189
2463
|
awareness.off('change', awarenessListener);
|
|
2190
2464
|
awareness.setLocalStateField(cursorStateField, null);
|
|
2191
2465
|
}
|
|
@@ -2315,8 +2589,12 @@ exports.defaultCursorBuilder = defaultCursorBuilder;
|
|
|
2315
2589
|
exports.defaultDeleteFilter = defaultDeleteFilter;
|
|
2316
2590
|
exports.defaultProtectedNodes = defaultProtectedNodes;
|
|
2317
2591
|
exports.defaultSelectionBuilder = defaultSelectionBuilder;
|
|
2592
|
+
exports.findAbsolutePositionAfterStructuralChange = findAbsolutePositionAfterStructuralChange;
|
|
2318
2593
|
exports.getRelativeSelection = getRelativeSelection;
|
|
2319
2594
|
exports.initProseMirrorDoc = initProseMirrorDoc;
|
|
2595
|
+
exports.isMisresolvedAfterStructuralChange = isMisresolvedAfterStructuralChange;
|
|
2596
|
+
exports.isMisresolvedTextPosition = isMisresolvedTextPosition;
|
|
2597
|
+
exports.isStructuralTransaction = isStructuralTransaction;
|
|
2320
2598
|
exports.isVisible = isVisible;
|
|
2321
2599
|
exports.prosemirrorJSONToYDoc = prosemirrorJSONToYDoc;
|
|
2322
2600
|
exports.prosemirrorJSONToYXmlFragment = prosemirrorJSONToYXmlFragment;
|