@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/y-tiptap.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as Y from 'yjs';
|
|
2
2
|
import { Item, ContentType, Text, XmlElement, UndoManager } from 'yjs';
|
|
3
3
|
import { DecorationSet, Decoration } from 'prosemirror-view';
|
|
4
|
-
import { PluginKey, Plugin, TextSelection, AllSelection, NodeSelection } from 'prosemirror-state';
|
|
4
|
+
import { PluginKey, Plugin, TextSelection, AllSelection, NodeSelection, Selection } from 'prosemirror-state';
|
|
5
5
|
import 'y-protocols/awareness';
|
|
6
6
|
import { createMutex } from 'lib0/mutex';
|
|
7
7
|
import * as PModel from 'prosemirror-model';
|
|
@@ -18,6 +18,7 @@ import * as eventloop from 'lib0/eventloop';
|
|
|
18
18
|
import * as map from 'lib0/map';
|
|
19
19
|
import * as sha256 from 'lib0/hash/sha256';
|
|
20
20
|
import * as buf from 'lib0/buffer';
|
|
21
|
+
import { ReplaceStep } from 'prosemirror-transform';
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* The unique prosemirror plugin key for syncPlugin
|
|
@@ -284,8 +285,9 @@ const ySyncPlugin = (yXmlFragment, {
|
|
|
284
285
|
* @param {import('prosemirror-state').Transaction} tr
|
|
285
286
|
* @param {ReturnType<typeof getRelativeSelection>} relSel
|
|
286
287
|
* @param {ProsemirrorBinding} binding
|
|
288
|
+
* @param {import('prosemirror-model').Node} [oldDoc]
|
|
287
289
|
*/
|
|
288
|
-
const restoreRelativeSelection = (tr, relSel, binding) => {
|
|
290
|
+
const restoreRelativeSelection = (tr, relSel, binding, oldDoc) => {
|
|
289
291
|
if (relSel !== null && relSel.anchor !== null && relSel.head !== null) {
|
|
290
292
|
if (relSel.type === 'all') {
|
|
291
293
|
tr.setSelection(new AllSelection(tr.doc));
|
|
@@ -296,8 +298,12 @@ const restoreRelativeSelection = (tr, relSel, binding) => {
|
|
|
296
298
|
relSel.anchor,
|
|
297
299
|
binding.mapping
|
|
298
300
|
);
|
|
299
|
-
|
|
300
|
-
|
|
301
|
+
// anchor is null when the referenced node was deleted or moved out of
|
|
302
|
+
// binding.type by a remote update; resolving null would throw.
|
|
303
|
+
if (anchor !== null) {
|
|
304
|
+
tr.setSelection(createSafeNodeSelection(tr, anchor));
|
|
305
|
+
}
|
|
306
|
+
} else if (relSel.type === 'nodeRange') {
|
|
301
307
|
const anchor = relativePositionToAbsolutePosition(
|
|
302
308
|
binding.doc,
|
|
303
309
|
binding.type,
|
|
@@ -310,6 +316,46 @@ const restoreRelativeSelection = (tr, relSel, binding) => {
|
|
|
310
316
|
relSel.head,
|
|
311
317
|
binding.mapping
|
|
312
318
|
);
|
|
319
|
+
const selection = createSafeNodeRangeSelection(tr, anchor, head, relSel.depth);
|
|
320
|
+
if (selection !== null) {
|
|
321
|
+
tr.setSelection(selection);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
let anchor = relativePositionToAbsolutePosition(
|
|
325
|
+
binding.doc,
|
|
326
|
+
binding.type,
|
|
327
|
+
relSel.anchor,
|
|
328
|
+
binding.mapping
|
|
329
|
+
);
|
|
330
|
+
let head = relativePositionToAbsolutePosition(
|
|
331
|
+
binding.doc,
|
|
332
|
+
binding.type,
|
|
333
|
+
relSel.head,
|
|
334
|
+
binding.mapping
|
|
335
|
+
);
|
|
336
|
+
const needsContentFallback = oldDoc != null &&
|
|
337
|
+
relSel.absAnchor != null &&
|
|
338
|
+
relSel.absHead != null &&
|
|
339
|
+
(
|
|
340
|
+
anchor === null ||
|
|
341
|
+
head === null ||
|
|
342
|
+
(relSel.absAnchor > 1 && anchor !== null && anchor <= 1) ||
|
|
343
|
+
(relSel.absHead > 1 && head !== null && head <= 1) ||
|
|
344
|
+
isMisresolvedAfterStructuralChange(oldDoc, tr.doc, relSel.absAnchor, anchor) ||
|
|
345
|
+
isMisresolvedAfterStructuralChange(oldDoc, tr.doc, relSel.absHead, head)
|
|
346
|
+
);
|
|
347
|
+
if (needsContentFallback) {
|
|
348
|
+
anchor = findAbsolutePositionAfterStructuralChange(
|
|
349
|
+
oldDoc,
|
|
350
|
+
tr.doc,
|
|
351
|
+
relSel.absAnchor
|
|
352
|
+
);
|
|
353
|
+
head = findAbsolutePositionAfterStructuralChange(
|
|
354
|
+
oldDoc,
|
|
355
|
+
tr.doc,
|
|
356
|
+
relSel.absHead
|
|
357
|
+
);
|
|
358
|
+
}
|
|
313
359
|
if (anchor !== null && head !== null) {
|
|
314
360
|
tr.setSelection(TextSelection.between(tr.doc.resolve(anchor), tr.doc.resolve(head)));
|
|
315
361
|
}
|
|
@@ -334,23 +380,67 @@ const createSafeNodeSelection = (tr, pos) => {
|
|
|
334
380
|
}
|
|
335
381
|
};
|
|
336
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Safely reconstructs a NodeRangeSelection from resolved absolute positions.
|
|
385
|
+
*
|
|
386
|
+
* @param {import('prosemirror-state').Transaction} tr - The transaction whose document provides resolved positions.
|
|
387
|
+
* @param {number|null} anchor - Absolute document position marking the start (boundary) of the node range.
|
|
388
|
+
* Use `relativePositionToAbsolutePosition` before calling this function.
|
|
389
|
+
* @param {number|null} head - Absolute document position marking the end (boundary) of the node range.
|
|
390
|
+
* Use `relativePositionToAbsolutePosition` before calling this function.
|
|
391
|
+
* @param {number|undefined} depth - The nesting depth at which the range operates (e.g. 0 for
|
|
392
|
+
* top-level blocks, 1 for blocks nested inside a wrapper). Passed through to
|
|
393
|
+
* `Selection.fromJSON`; ignored by `@tiptap/extension-node-range` < v2.29 but
|
|
394
|
+
* properly stored starting from that version.
|
|
395
|
+
* @returns {import('prosemirror-state').Selection|null} Reconstructed selection, or null if
|
|
396
|
+
* anchor/head could not be resolved.
|
|
397
|
+
*/
|
|
398
|
+
const createSafeNodeRangeSelection = (tr, anchor, head, depth) => {
|
|
399
|
+
if (anchor === null || head === null) {
|
|
400
|
+
return null
|
|
401
|
+
}
|
|
402
|
+
const clampedAnchor = Math.min(Math.max(anchor, 0), tr.doc.content.size);
|
|
403
|
+
const clampedHead = Math.min(Math.max(head, 0), tr.doc.content.size);
|
|
404
|
+
try {
|
|
405
|
+
const selection = Selection.fromJSON(tr.doc, {
|
|
406
|
+
type: 'nodeRange',
|
|
407
|
+
anchor: clampedAnchor,
|
|
408
|
+
head: clampedHead,
|
|
409
|
+
depth
|
|
410
|
+
});
|
|
411
|
+
if (!selection.ranges.length) {
|
|
412
|
+
return TextSelection.near(tr.doc.resolve(clampedAnchor))
|
|
413
|
+
}
|
|
414
|
+
return selection
|
|
415
|
+
} catch (e) {
|
|
416
|
+
return TextSelection.near(tr.doc.resolve(clampedAnchor))
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
337
420
|
/**
|
|
338
421
|
* @param {ProsemirrorBinding} pmbinding
|
|
339
422
|
* @param {import('prosemirror-state').EditorState} state
|
|
340
423
|
*/
|
|
341
|
-
const getRelativeSelection = (pmbinding, state) =>
|
|
342
|
-
type
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
424
|
+
const getRelativeSelection = (pmbinding, state) => {
|
|
425
|
+
const type = /** @type {any} */ (state.selection).jsonID;
|
|
426
|
+
return {
|
|
427
|
+
type,
|
|
428
|
+
// `depth` is only meaningful for NodeRangeSelection; undefined for every other type.
|
|
429
|
+
depth: type === 'nodeRange' ? /** @type {any} */ (state.selection).depth : undefined,
|
|
430
|
+
anchor: absolutePositionToRelativePosition(
|
|
431
|
+
state.selection.anchor,
|
|
432
|
+
pmbinding.type,
|
|
433
|
+
pmbinding.mapping
|
|
434
|
+
),
|
|
435
|
+
head: absolutePositionToRelativePosition(
|
|
436
|
+
state.selection.head,
|
|
437
|
+
pmbinding.type,
|
|
438
|
+
pmbinding.mapping
|
|
439
|
+
),
|
|
440
|
+
absAnchor: state.selection.anchor,
|
|
441
|
+
absHead: state.selection.head
|
|
442
|
+
}
|
|
443
|
+
};
|
|
354
444
|
|
|
355
445
|
/**
|
|
356
446
|
* Binding for prosemirror.
|
|
@@ -673,6 +763,9 @@ class ProsemirrorBinding {
|
|
|
673
763
|
);
|
|
674
764
|
transaction.changed.forEach(delType);
|
|
675
765
|
transaction.changedParentTypes.forEach(delType);
|
|
766
|
+
// Rebuild the full Y↔PM mapping so relative cursor positions resolve against
|
|
767
|
+
// current node sizes after structural changes (e.g. drag-and-drop block moves).
|
|
768
|
+
this.mapping.clear();
|
|
676
769
|
const fragmentContent = this.type.toArray().map((t) =>
|
|
677
770
|
createNodeIfNotExists(
|
|
678
771
|
/** @type {Y.XmlElement | Y.XmlHook} */ (t),
|
|
@@ -680,13 +773,14 @@ class ProsemirrorBinding {
|
|
|
680
773
|
this
|
|
681
774
|
)
|
|
682
775
|
).filter((n) => n !== null);
|
|
776
|
+
const oldDoc = this.prosemirrorView.state.doc;
|
|
683
777
|
// @ts-ignore
|
|
684
778
|
let tr = this._tr.replace(
|
|
685
779
|
0,
|
|
686
780
|
this.prosemirrorView.state.doc.content.size,
|
|
687
781
|
new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0)
|
|
688
782
|
);
|
|
689
|
-
restoreRelativeSelection(tr, this.beforeTransactionSelection, this);
|
|
783
|
+
restoreRelativeSelection(tr, this.beforeTransactionSelection, this, oldDoc);
|
|
690
784
|
tr = tr.setMeta(ySyncPluginKey, { isChangeOrigin: true, isUndoRedoOperation: transaction.origin instanceof Y.UndoManager });
|
|
691
785
|
if (
|
|
692
786
|
this.beforeTransactionSelection !== null && this._isLocalCursorInView()
|
|
@@ -1572,6 +1666,26 @@ const absolutePositionToRelativePosition = (pos, type, mapping) => {
|
|
|
1572
1666
|
return Y.createRelativePositionFromTypeIndex(type, type._length, -1)
|
|
1573
1667
|
};
|
|
1574
1668
|
|
|
1669
|
+
/**
|
|
1670
|
+
* Item-id based relative positions can misresolve to the document start after
|
|
1671
|
+
* block reorder during collaborative drag-and-drop.
|
|
1672
|
+
*
|
|
1673
|
+
* @param {Y.Doc} y
|
|
1674
|
+
* @param {Y.RelativePosition} relPos
|
|
1675
|
+
* @param {number|null} absPos
|
|
1676
|
+
* @return {boolean}
|
|
1677
|
+
*/
|
|
1678
|
+
const isMisresolvedTextPosition = (y, relPos, absPos) => {
|
|
1679
|
+
if (absPos === null) {
|
|
1680
|
+
return false
|
|
1681
|
+
}
|
|
1682
|
+
const decoded = Y.createAbsolutePositionFromRelativePosition(relPos, y);
|
|
1683
|
+
return decoded !== null &&
|
|
1684
|
+
decoded.type instanceof Y.XmlText &&
|
|
1685
|
+
relPos.item !== null &&
|
|
1686
|
+
absPos <= 1
|
|
1687
|
+
};
|
|
1688
|
+
|
|
1575
1689
|
const createRelativePosition = (type, item) => {
|
|
1576
1690
|
let typeid = null;
|
|
1577
1691
|
let tname = null;
|
|
@@ -1609,7 +1723,11 @@ const relativePositionToAbsolutePosition = (y, documentType, relPos, mapping) =>
|
|
|
1609
1723
|
if (t instanceof Y.XmlText) {
|
|
1610
1724
|
pos += t._length;
|
|
1611
1725
|
} else {
|
|
1612
|
-
|
|
1726
|
+
const mapped = mapping.get(t);
|
|
1727
|
+
if (mapped == null) {
|
|
1728
|
+
return null
|
|
1729
|
+
}
|
|
1730
|
+
pos += /** @type {any} */ (mapped).nodeSize;
|
|
1613
1731
|
}
|
|
1614
1732
|
}
|
|
1615
1733
|
n = /** @type {Y.Item} */ (n.right);
|
|
@@ -1633,7 +1751,11 @@ const relativePositionToAbsolutePosition = (y, documentType, relPos, mapping) =>
|
|
|
1633
1751
|
if (contentType instanceof Y.XmlText) {
|
|
1634
1752
|
pos += contentType._length;
|
|
1635
1753
|
} else {
|
|
1636
|
-
|
|
1754
|
+
const mapped = mapping.get(contentType);
|
|
1755
|
+
if (mapped == null) {
|
|
1756
|
+
return null
|
|
1757
|
+
}
|
|
1758
|
+
pos += /** @type {any} */ (mapped).nodeSize;
|
|
1637
1759
|
}
|
|
1638
1760
|
}
|
|
1639
1761
|
n = n.right;
|
|
@@ -1641,7 +1763,133 @@ const relativePositionToAbsolutePosition = (y, documentType, relPos, mapping) =>
|
|
|
1641
1763
|
}
|
|
1642
1764
|
type = /** @type {Y.AbstractType} */ (parent);
|
|
1643
1765
|
}
|
|
1644
|
-
|
|
1766
|
+
const absPos = pos - 1; // we don't count the most outer tag, because it is a fragment
|
|
1767
|
+
if (isMisresolvedTextPosition(y, relPos, absPos)) {
|
|
1768
|
+
return null
|
|
1769
|
+
}
|
|
1770
|
+
return absPos
|
|
1771
|
+
};
|
|
1772
|
+
|
|
1773
|
+
/**
|
|
1774
|
+
* @param {import('prosemirror-model').Node} oldDoc
|
|
1775
|
+
* @param {import('prosemirror-model').Node} newDoc
|
|
1776
|
+
* @param {number} absPos
|
|
1777
|
+
* @return {number|null}
|
|
1778
|
+
*/
|
|
1779
|
+
const findAbsolutePositionAfterStructuralChange = (oldDoc, newDoc, absPos) => {
|
|
1780
|
+
let pos = 0;
|
|
1781
|
+
let targetIdx = 0;
|
|
1782
|
+
for (; targetIdx < oldDoc.childCount; targetIdx++) {
|
|
1783
|
+
const child = oldDoc.child(targetIdx);
|
|
1784
|
+
if (pos + child.nodeSize > absPos) {
|
|
1785
|
+
break
|
|
1786
|
+
}
|
|
1787
|
+
pos += child.nodeSize;
|
|
1788
|
+
}
|
|
1789
|
+
if (targetIdx >= oldDoc.childCount) {
|
|
1790
|
+
return null
|
|
1791
|
+
}
|
|
1792
|
+
const targetChild = oldDoc.child(targetIdx);
|
|
1793
|
+
const offsetInChild = absPos - pos;
|
|
1794
|
+
|
|
1795
|
+
let occurrence = 0;
|
|
1796
|
+
for (let i = 0; i <= targetIdx; i++) {
|
|
1797
|
+
const child = oldDoc.child(i);
|
|
1798
|
+
if (child.type === targetChild.type && child.textContent === targetChild.textContent) {
|
|
1799
|
+
occurrence++;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
let matchCount = 0;
|
|
1804
|
+
let newPos = 0;
|
|
1805
|
+
for (let i = 0; i < newDoc.childCount; i++) {
|
|
1806
|
+
const child = newDoc.child(i);
|
|
1807
|
+
if (child.type === targetChild.type && child.textContent === targetChild.textContent) {
|
|
1808
|
+
matchCount++;
|
|
1809
|
+
if (matchCount === occurrence) {
|
|
1810
|
+
const remapped = newPos + offsetInChild;
|
|
1811
|
+
const contentStart = newPos + 1;
|
|
1812
|
+
const contentEnd = newPos + child.nodeSize - 1;
|
|
1813
|
+
return Math.max(contentStart, Math.min(remapped, contentEnd))
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
newPos += child.nodeSize;
|
|
1817
|
+
}
|
|
1818
|
+
return null
|
|
1819
|
+
};
|
|
1820
|
+
|
|
1821
|
+
/**
|
|
1822
|
+
* Returns true when a transaction changes block structure rather than only
|
|
1823
|
+
* editing inline content inside existing blocks.
|
|
1824
|
+
*
|
|
1825
|
+
* @param {import('prosemirror-state').Transaction} tr
|
|
1826
|
+
* @param {import('prosemirror-model').Node} oldDoc
|
|
1827
|
+
* @return {boolean}
|
|
1828
|
+
*/
|
|
1829
|
+
const isStructuralTransaction = (tr, oldDoc) => {
|
|
1830
|
+
if (!tr.docChanged) {
|
|
1831
|
+
return false
|
|
1832
|
+
}
|
|
1833
|
+
if (tr.doc.childCount !== oldDoc.childCount) {
|
|
1834
|
+
return true
|
|
1835
|
+
}
|
|
1836
|
+
for (const step of tr.steps) {
|
|
1837
|
+
if (step instanceof ReplaceStep) {
|
|
1838
|
+
if (step.from === 0 && step.to === oldDoc.content.size) {
|
|
1839
|
+
return true
|
|
1840
|
+
}
|
|
1841
|
+
if (step.slice.content.size > 0) {
|
|
1842
|
+
let hasBlock = false;
|
|
1843
|
+
step.slice.content.forEach((node) => {
|
|
1844
|
+
if (node.isBlock) {
|
|
1845
|
+
hasBlock = true;
|
|
1846
|
+
}
|
|
1847
|
+
});
|
|
1848
|
+
if (hasBlock) {
|
|
1849
|
+
return true
|
|
1850
|
+
}
|
|
1851
|
+
} else if (step.to > step.from) {
|
|
1852
|
+
const $from = oldDoc.resolve(step.from);
|
|
1853
|
+
const $to = oldDoc.resolve(step.to);
|
|
1854
|
+
if ($from.depth === 0 && $to.depth === 0 && $from.index() !== $to.index()) {
|
|
1855
|
+
return true
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
return false
|
|
1861
|
+
};
|
|
1862
|
+
|
|
1863
|
+
/**
|
|
1864
|
+
* Detect stale relative positions after structural changes that resolve to the
|
|
1865
|
+
* wrong text block or to the start of the correct block.
|
|
1866
|
+
*
|
|
1867
|
+
* @param {import('prosemirror-model').Node} oldDoc
|
|
1868
|
+
* @param {import('prosemirror-model').Node} newDoc
|
|
1869
|
+
* @param {number} oldAbs
|
|
1870
|
+
* @param {number|null} resolvedAbs
|
|
1871
|
+
* @return {boolean}
|
|
1872
|
+
*/
|
|
1873
|
+
const isMisresolvedAfterStructuralChange = (oldDoc, newDoc, oldAbs, resolvedAbs) => {
|
|
1874
|
+
if (resolvedAbs === null) {
|
|
1875
|
+
return false
|
|
1876
|
+
}
|
|
1877
|
+
const $old = oldDoc.resolve(oldAbs);
|
|
1878
|
+
const $new = newDoc.resolve(resolvedAbs);
|
|
1879
|
+
if (!$old.parent.isTextblock || !$new.parent.isTextblock) {
|
|
1880
|
+
return false
|
|
1881
|
+
}
|
|
1882
|
+
if ($old.parent.textContent !== $new.parent.textContent) {
|
|
1883
|
+
return true
|
|
1884
|
+
}
|
|
1885
|
+
if ($old.parentOffset !== 0 && $new.parentOffset === 0) {
|
|
1886
|
+
return true
|
|
1887
|
+
}
|
|
1888
|
+
if ($old.parentOffset === 0 && $new.parentOffset === 0) {
|
|
1889
|
+
const expected = findAbsolutePositionAfterStructuralChange(oldDoc, newDoc, oldAbs);
|
|
1890
|
+
return expected !== null && expected !== resolvedAbs
|
|
1891
|
+
}
|
|
1892
|
+
return false
|
|
1645
1893
|
};
|
|
1646
1894
|
|
|
1647
1895
|
/**
|
|
@@ -2060,7 +2308,7 @@ const yCursorPlugin = (
|
|
|
2060
2308
|
selectionBuilder
|
|
2061
2309
|
)
|
|
2062
2310
|
},
|
|
2063
|
-
apply (tr, prevState,
|
|
2311
|
+
apply (tr, prevState, oldState, newState) {
|
|
2064
2312
|
const ystate = ySyncPluginKey.getState(newState);
|
|
2065
2313
|
const yCursorState = tr.getMeta(yCursorPluginKey);
|
|
2066
2314
|
if (
|
|
@@ -2075,7 +2323,19 @@ const yCursorPlugin = (
|
|
|
2075
2323
|
selectionBuilder
|
|
2076
2324
|
)
|
|
2077
2325
|
}
|
|
2078
|
-
|
|
2326
|
+
if (tr.docChanged) {
|
|
2327
|
+
if (isStructuralTransaction(tr, oldState.doc)) {
|
|
2328
|
+
return createDecorations(
|
|
2329
|
+
newState,
|
|
2330
|
+
awareness,
|
|
2331
|
+
awarenessStateFilter,
|
|
2332
|
+
cursorBuilder,
|
|
2333
|
+
selectionBuilder
|
|
2334
|
+
)
|
|
2335
|
+
}
|
|
2336
|
+
return prevState.map(tr.mapping, tr.doc)
|
|
2337
|
+
}
|
|
2338
|
+
return prevState
|
|
2079
2339
|
}
|
|
2080
2340
|
},
|
|
2081
2341
|
props: {
|
|
@@ -2090,15 +2350,19 @@ const yCursorPlugin = (
|
|
|
2090
2350
|
setMeta(view, yCursorPluginKey, { awarenessUpdated: true });
|
|
2091
2351
|
}
|
|
2092
2352
|
};
|
|
2093
|
-
|
|
2094
|
-
|
|
2353
|
+
/**
|
|
2354
|
+
* @param {import('prosemirror-view').EditorView} editorView
|
|
2355
|
+
* @param {{ force?: boolean }} [opts]
|
|
2356
|
+
*/
|
|
2357
|
+
const updateCursorInfo = (editorView, opts = {}) => {
|
|
2358
|
+
const ystate = ySyncPluginKey.getState(editorView.state);
|
|
2095
2359
|
// `ystate` is undefined during sync-plugin init and Y.Doc transitions;
|
|
2096
2360
|
// reading from it here would throw and break cursor tracking for the view.
|
|
2097
2361
|
if (!ystate || !ystate.binding) return
|
|
2098
2362
|
// @note We make implicit checks when checking for the cursor property
|
|
2099
2363
|
const current = awareness.getLocalState() || {};
|
|
2100
|
-
if (
|
|
2101
|
-
const selection = getSelection(
|
|
2364
|
+
if (editorView.hasFocus()) {
|
|
2365
|
+
const selection = getSelection(editorView.state);
|
|
2102
2366
|
/**
|
|
2103
2367
|
* @type {Y.RelativePosition}
|
|
2104
2368
|
*/
|
|
@@ -2116,6 +2380,7 @@ const yCursorPlugin = (
|
|
|
2116
2380
|
ystate.binding.mapping
|
|
2117
2381
|
);
|
|
2118
2382
|
if (
|
|
2383
|
+
opts.force ||
|
|
2119
2384
|
current.cursor == null ||
|
|
2120
2385
|
!Y.compareRelativePositions(
|
|
2121
2386
|
Y.createRelativePositionFromJSON(current.cursor.anchor),
|
|
@@ -2144,14 +2409,23 @@ const yCursorPlugin = (
|
|
|
2144
2409
|
awareness.setLocalStateField(cursorStateField, null);
|
|
2145
2410
|
}
|
|
2146
2411
|
};
|
|
2412
|
+
const onFocusChange = () => updateCursorInfo(view);
|
|
2147
2413
|
awareness.on('change', awarenessListener);
|
|
2148
|
-
view.dom.addEventListener('focusin',
|
|
2149
|
-
view.dom.addEventListener('focusout',
|
|
2414
|
+
view.dom.addEventListener('focusin', onFocusChange);
|
|
2415
|
+
view.dom.addEventListener('focusout', onFocusChange);
|
|
2150
2416
|
return {
|
|
2151
|
-
update:
|
|
2417
|
+
update: (editorView, prevState) => {
|
|
2418
|
+
const ystate = ySyncPluginKey.getState(editorView.state);
|
|
2419
|
+
const forceAwarenessUpdate = !!(
|
|
2420
|
+
ystate?.isChangeOrigin &&
|
|
2421
|
+
prevState?.doc &&
|
|
2422
|
+
!prevState.doc.eq(editorView.state.doc)
|
|
2423
|
+
);
|
|
2424
|
+
updateCursorInfo(editorView, { force: forceAwarenessUpdate });
|
|
2425
|
+
},
|
|
2152
2426
|
destroy: () => {
|
|
2153
|
-
view.dom.removeEventListener('focusin',
|
|
2154
|
-
view.dom.removeEventListener('focusout',
|
|
2427
|
+
view.dom.removeEventListener('focusin', onFocusChange);
|
|
2428
|
+
view.dom.removeEventListener('focusout', onFocusChange);
|
|
2155
2429
|
awareness.off('change', awarenessListener);
|
|
2156
2430
|
awareness.setLocalStateField(cursorStateField, null);
|
|
2157
2431
|
}
|
|
@@ -2273,5 +2547,5 @@ const yUndoPlugin = ({ protectedNodes = defaultProtectedNodes, trackedOrigins =
|
|
|
2273
2547
|
}
|
|
2274
2548
|
});
|
|
2275
2549
|
|
|
2276
|
-
export { ProsemirrorBinding, absolutePositionToRelativePosition, createDecorations, defaultAwarenessStateFilter, defaultCursorBuilder, defaultDeleteFilter, defaultProtectedNodes, defaultSelectionBuilder, getRelativeSelection, initProseMirrorDoc, isVisible, prosemirrorJSONToYDoc, prosemirrorJSONToYXmlFragment, prosemirrorToYDoc, prosemirrorToYXmlFragment, redo, relativePositionToAbsolutePosition, setMeta, undo, updateYFragment, yCursorPlugin, yCursorPluginKey, yDocToProsemirror, yDocToProsemirrorJSON, ySyncPlugin, ySyncPluginKey, yUndoPlugin, yUndoPluginKey, yXmlFragmentToProseMirrorFragment, yXmlFragmentToProseMirrorRootNode, yXmlFragmentToProsemirror, yXmlFragmentToProsemirrorJSON };
|
|
2550
|
+
export { ProsemirrorBinding, absolutePositionToRelativePosition, createDecorations, defaultAwarenessStateFilter, defaultCursorBuilder, defaultDeleteFilter, defaultProtectedNodes, defaultSelectionBuilder, findAbsolutePositionAfterStructuralChange, getRelativeSelection, initProseMirrorDoc, isMisresolvedAfterStructuralChange, isMisresolvedTextPosition, isStructuralTransaction, isVisible, prosemirrorJSONToYDoc, prosemirrorJSONToYXmlFragment, prosemirrorToYDoc, prosemirrorToYXmlFragment, redo, relativePositionToAbsolutePosition, setMeta, undo, updateYFragment, yCursorPlugin, yCursorPluginKey, yDocToProsemirror, yDocToProsemirrorJSON, ySyncPlugin, ySyncPluginKey, yUndoPlugin, yUndoPluginKey, yXmlFragmentToProseMirrorFragment, yXmlFragmentToProseMirrorRootNode, yXmlFragmentToProsemirror, yXmlFragmentToProsemirrorJSON };
|
|
2277
2551
|
//# sourceMappingURL=y-tiptap.js.map
|