@tiptap/y-tiptap 3.0.5 → 3.0.7
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 +4 -0
- package/dist/src/y-tiptap.d.ts +1 -1
- package/dist/y-tiptap.cjs +386 -18
- package/dist/y-tiptap.cjs.map +1 -1
- package/dist/y-tiptap.js +383 -19
- package/dist/y-tiptap.js.map +1 -1
- package/package.json +1 -1
package/dist/y-tiptap.js
CHANGED
|
@@ -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
|
|
@@ -280,12 +281,38 @@ const ySyncPlugin = (yXmlFragment, {
|
|
|
280
281
|
return plugin
|
|
281
282
|
};
|
|
282
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Resolves one text-selection endpoint after a remote update. Falls back to
|
|
286
|
+
* content-based matching when the Yjs resolution is missing or lands in the
|
|
287
|
+
* wrong block, and keeps the Yjs resolution when the fallback finds nothing.
|
|
288
|
+
*
|
|
289
|
+
* @param {import('prosemirror-model').Node} newDoc
|
|
290
|
+
* @param {import('prosemirror-model').Node} oldDoc
|
|
291
|
+
* @param {number|null|undefined} oldAbs
|
|
292
|
+
* @param {number|null} resolved
|
|
293
|
+
* @return {number|null}
|
|
294
|
+
*/
|
|
295
|
+
const recoverSelectionEndpoint = (newDoc, oldDoc, oldAbs, resolved) => {
|
|
296
|
+
if (oldAbs == null) {
|
|
297
|
+
return resolved
|
|
298
|
+
}
|
|
299
|
+
const misresolved = resolved === null ||
|
|
300
|
+
(oldAbs > 1 && resolved <= 1) ||
|
|
301
|
+
isMisresolvedAfterStructuralChange(oldDoc, newDoc, oldAbs, resolved);
|
|
302
|
+
if (!misresolved) {
|
|
303
|
+
return resolved
|
|
304
|
+
}
|
|
305
|
+
const recovered = findAbsolutePositionAfterStructuralChange(oldDoc, newDoc, oldAbs);
|
|
306
|
+
return recovered !== null ? recovered : resolved
|
|
307
|
+
};
|
|
308
|
+
|
|
283
309
|
/**
|
|
284
310
|
* @param {import('prosemirror-state').Transaction} tr
|
|
285
311
|
* @param {ReturnType<typeof getRelativeSelection>} relSel
|
|
286
312
|
* @param {ProsemirrorBinding} binding
|
|
313
|
+
* @param {import('prosemirror-model').Node} [oldDoc]
|
|
287
314
|
*/
|
|
288
|
-
const restoreRelativeSelection = (tr, relSel, binding) => {
|
|
315
|
+
const restoreRelativeSelection = (tr, relSel, binding, oldDoc) => {
|
|
289
316
|
if (relSel !== null && relSel.anchor !== null && relSel.head !== null) {
|
|
290
317
|
if (relSel.type === 'all') {
|
|
291
318
|
tr.setSelection(new AllSelection(tr.doc));
|
|
@@ -319,18 +346,30 @@ const restoreRelativeSelection = (tr, relSel, binding) => {
|
|
|
319
346
|
tr.setSelection(selection);
|
|
320
347
|
}
|
|
321
348
|
} else {
|
|
322
|
-
|
|
349
|
+
let anchor = relativePositionToAbsolutePosition(
|
|
323
350
|
binding.doc,
|
|
324
351
|
binding.type,
|
|
325
352
|
relSel.anchor,
|
|
326
353
|
binding.mapping
|
|
327
354
|
);
|
|
328
|
-
|
|
355
|
+
let head = relativePositionToAbsolutePosition(
|
|
329
356
|
binding.doc,
|
|
330
357
|
binding.type,
|
|
331
358
|
relSel.head,
|
|
332
359
|
binding.mapping
|
|
333
360
|
);
|
|
361
|
+
if (oldDoc != null) {
|
|
362
|
+
anchor = recoverSelectionEndpoint(tr.doc, oldDoc, relSel.absAnchor, anchor);
|
|
363
|
+
head = recoverSelectionEndpoint(tr.doc, oldDoc, relSel.absHead, head);
|
|
364
|
+
}
|
|
365
|
+
// Collapse to the surviving endpoint instead of dropping the selection;
|
|
366
|
+
// an unset selection maps through the full-doc replace to the doc start.
|
|
367
|
+
if (anchor === null) {
|
|
368
|
+
anchor = head;
|
|
369
|
+
}
|
|
370
|
+
if (head === null) {
|
|
371
|
+
head = anchor;
|
|
372
|
+
}
|
|
334
373
|
if (anchor !== null && head !== null) {
|
|
335
374
|
tr.setSelection(TextSelection.between(tr.doc.resolve(anchor), tr.doc.resolve(head)));
|
|
336
375
|
}
|
|
@@ -411,7 +450,9 @@ const getRelativeSelection = (pmbinding, state) => {
|
|
|
411
450
|
state.selection.head,
|
|
412
451
|
pmbinding.type,
|
|
413
452
|
pmbinding.mapping
|
|
414
|
-
)
|
|
453
|
+
),
|
|
454
|
+
absAnchor: state.selection.anchor,
|
|
455
|
+
absHead: state.selection.head
|
|
415
456
|
}
|
|
416
457
|
};
|
|
417
458
|
|
|
@@ -736,6 +777,9 @@ class ProsemirrorBinding {
|
|
|
736
777
|
);
|
|
737
778
|
transaction.changed.forEach(delType);
|
|
738
779
|
transaction.changedParentTypes.forEach(delType);
|
|
780
|
+
// Rebuild the full Y↔PM mapping so relative cursor positions resolve against
|
|
781
|
+
// current node sizes after structural changes (e.g. drag-and-drop block moves).
|
|
782
|
+
this.mapping.clear();
|
|
739
783
|
const fragmentContent = this.type.toArray().map((t) =>
|
|
740
784
|
createNodeIfNotExists(
|
|
741
785
|
/** @type {Y.XmlElement | Y.XmlHook} */ (t),
|
|
@@ -743,13 +787,14 @@ class ProsemirrorBinding {
|
|
|
743
787
|
this
|
|
744
788
|
)
|
|
745
789
|
).filter((n) => n !== null);
|
|
790
|
+
const oldDoc = this.prosemirrorView.state.doc;
|
|
746
791
|
// @ts-ignore
|
|
747
792
|
let tr = this._tr.replace(
|
|
748
793
|
0,
|
|
749
794
|
this.prosemirrorView.state.doc.content.size,
|
|
750
795
|
new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0)
|
|
751
796
|
);
|
|
752
|
-
restoreRelativeSelection(tr, this.beforeTransactionSelection, this);
|
|
797
|
+
restoreRelativeSelection(tr, this.beforeTransactionSelection, this, oldDoc);
|
|
753
798
|
tr = tr.setMeta(ySyncPluginKey, { isChangeOrigin: true, isUndoRedoOperation: transaction.origin instanceof Y.UndoManager });
|
|
754
799
|
if (
|
|
755
800
|
this.beforeTransactionSelection !== null && this._isLocalCursorInView()
|
|
@@ -1635,6 +1680,26 @@ const absolutePositionToRelativePosition = (pos, type, mapping) => {
|
|
|
1635
1680
|
return Y.createRelativePositionFromTypeIndex(type, type._length, -1)
|
|
1636
1681
|
};
|
|
1637
1682
|
|
|
1683
|
+
/**
|
|
1684
|
+
* Item-id based relative positions can misresolve to the document start after
|
|
1685
|
+
* block reorder during collaborative drag-and-drop.
|
|
1686
|
+
*
|
|
1687
|
+
* @param {Y.Doc} y
|
|
1688
|
+
* @param {Y.RelativePosition} relPos
|
|
1689
|
+
* @param {number|null} absPos
|
|
1690
|
+
* @return {boolean}
|
|
1691
|
+
*/
|
|
1692
|
+
const isMisresolvedTextPosition = (y, relPos, absPos) => {
|
|
1693
|
+
if (absPos === null) {
|
|
1694
|
+
return false
|
|
1695
|
+
}
|
|
1696
|
+
const decoded = Y.createAbsolutePositionFromRelativePosition(relPos, y);
|
|
1697
|
+
return decoded !== null &&
|
|
1698
|
+
decoded.type instanceof Y.XmlText &&
|
|
1699
|
+
relPos.item !== null &&
|
|
1700
|
+
absPos <= 1
|
|
1701
|
+
};
|
|
1702
|
+
|
|
1638
1703
|
const createRelativePosition = (type, item) => {
|
|
1639
1704
|
let typeid = null;
|
|
1640
1705
|
let tname = null;
|
|
@@ -1712,7 +1777,279 @@ const relativePositionToAbsolutePosition = (y, documentType, relPos, mapping) =>
|
|
|
1712
1777
|
}
|
|
1713
1778
|
type = /** @type {Y.AbstractType} */ (parent);
|
|
1714
1779
|
}
|
|
1715
|
-
|
|
1780
|
+
const absPos = pos - 1; // we don't count the most outer tag, because it is a fragment
|
|
1781
|
+
if (isMisresolvedTextPosition(y, relPos, absPos)) {
|
|
1782
|
+
return null
|
|
1783
|
+
}
|
|
1784
|
+
return absPos
|
|
1785
|
+
};
|
|
1786
|
+
|
|
1787
|
+
/**
|
|
1788
|
+
* Shallow attrs comparison. Attr values are primitives in most schemas;
|
|
1789
|
+
* non-primitive values fail the check and callers fall back to text matching.
|
|
1790
|
+
*
|
|
1791
|
+
* @param {Object<string, any>} a
|
|
1792
|
+
* @param {Object<string, any>} b
|
|
1793
|
+
* @return {boolean}
|
|
1794
|
+
*/
|
|
1795
|
+
const attrsEqual = (a, b) => {
|
|
1796
|
+
if (a === b) {
|
|
1797
|
+
return true
|
|
1798
|
+
}
|
|
1799
|
+
const aKeys = Object.keys(a);
|
|
1800
|
+
return aKeys.length === Object.keys(b).length && aKeys.every((k) => a[k] === b[k])
|
|
1801
|
+
};
|
|
1802
|
+
|
|
1803
|
+
/**
|
|
1804
|
+
* Returns true when any attr deviates from its spec default or has none.
|
|
1805
|
+
* Default-only attrs cannot tell same-type siblings apart.
|
|
1806
|
+
*
|
|
1807
|
+
* @param {import('prosemirror-model').Node} node
|
|
1808
|
+
* @return {boolean}
|
|
1809
|
+
*/
|
|
1810
|
+
const hasDistinctiveAttrs = (node) => {
|
|
1811
|
+
const specAttrs = node.type.spec.attrs || {};
|
|
1812
|
+
return Object.keys(node.attrs).some((key) => {
|
|
1813
|
+
const spec = specAttrs[key];
|
|
1814
|
+
return spec == null ||
|
|
1815
|
+
!Object.prototype.hasOwnProperty.call(spec, 'default') ||
|
|
1816
|
+
spec.default !== node.attrs[key]
|
|
1817
|
+
})
|
|
1818
|
+
};
|
|
1819
|
+
|
|
1820
|
+
/**
|
|
1821
|
+
* Remaps a position into a matched block by walking the same child-index path
|
|
1822
|
+
* it had in the old block. A raw byte offset would overshoot into a sibling
|
|
1823
|
+
* inner textblock when the old block contains local keystrokes that are not
|
|
1824
|
+
* yet part of the rebuilt document.
|
|
1825
|
+
*
|
|
1826
|
+
* @param {import('prosemirror-model').ResolvedPos} $oldPos
|
|
1827
|
+
* @param {number} newBlockStart
|
|
1828
|
+
* @param {import('prosemirror-model').Node} newBlock
|
|
1829
|
+
* @return {number|null}
|
|
1830
|
+
*/
|
|
1831
|
+
const remapIntoBlock = ($oldPos, newBlockStart, newBlock) => {
|
|
1832
|
+
let pos = newBlockStart + 1;
|
|
1833
|
+
let node = newBlock;
|
|
1834
|
+
for (let depth = 1; depth < $oldPos.depth; depth++) {
|
|
1835
|
+
const idx = $oldPos.index(depth);
|
|
1836
|
+
if (idx >= node.childCount) {
|
|
1837
|
+
return null
|
|
1838
|
+
}
|
|
1839
|
+
for (let i = 0; i < idx; i++) {
|
|
1840
|
+
pos += node.child(i).nodeSize;
|
|
1841
|
+
}
|
|
1842
|
+
pos += 1;
|
|
1843
|
+
node = node.child(idx);
|
|
1844
|
+
if (node.type !== $oldPos.node(depth + 1).type) {
|
|
1845
|
+
return null
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
if (!node.isTextblock) {
|
|
1849
|
+
return null
|
|
1850
|
+
}
|
|
1851
|
+
return pos + Math.min($oldPos.parentOffset, node.content.size)
|
|
1852
|
+
};
|
|
1853
|
+
|
|
1854
|
+
/**
|
|
1855
|
+
* @param {import('prosemirror-model').Node} oldDoc
|
|
1856
|
+
* @param {import('prosemirror-model').Node} newDoc
|
|
1857
|
+
* @param {number} absPos
|
|
1858
|
+
* @return {number|null}
|
|
1859
|
+
*/
|
|
1860
|
+
const findAbsolutePositionAfterStructuralChange = (oldDoc, newDoc, absPos) => {
|
|
1861
|
+
let pos = 0;
|
|
1862
|
+
let targetIdx = 0;
|
|
1863
|
+
for (; targetIdx < oldDoc.childCount; targetIdx++) {
|
|
1864
|
+
const child = oldDoc.child(targetIdx);
|
|
1865
|
+
if (pos + child.nodeSize > absPos) {
|
|
1866
|
+
break
|
|
1867
|
+
}
|
|
1868
|
+
pos += child.nodeSize;
|
|
1869
|
+
}
|
|
1870
|
+
if (targetIdx >= oldDoc.childCount) {
|
|
1871
|
+
return null
|
|
1872
|
+
}
|
|
1873
|
+
const targetChild = oldDoc.child(targetIdx);
|
|
1874
|
+
const $oldPos = oldDoc.resolve(absPos);
|
|
1875
|
+
|
|
1876
|
+
/**
|
|
1877
|
+
* @param {number} newBlockStart
|
|
1878
|
+
* @param {import('prosemirror-model').Node} newBlock
|
|
1879
|
+
* @return {number|null}
|
|
1880
|
+
*/
|
|
1881
|
+
const place = (newBlockStart, newBlock) => {
|
|
1882
|
+
// Positions between top-level blocks carry no inner path; clamp them just
|
|
1883
|
+
// inside the matched block like the previous raw-offset remap did.
|
|
1884
|
+
if ($oldPos.depth === 0) {
|
|
1885
|
+
const remapped = newBlockStart + (absPos - pos);
|
|
1886
|
+
const contentStart = newBlockStart + 1;
|
|
1887
|
+
const contentEnd = newBlockStart + newBlock.nodeSize - 1;
|
|
1888
|
+
return Math.max(contentStart, Math.min(remapped, contentEnd))
|
|
1889
|
+
}
|
|
1890
|
+
return remapIntoBlock($oldPos, newBlockStart, newBlock)
|
|
1891
|
+
};
|
|
1892
|
+
|
|
1893
|
+
/**
|
|
1894
|
+
* Finds the Nth block in newDoc matching `pred`, where N is the number of
|
|
1895
|
+
* matching blocks in oldDoc up to and including the target block.
|
|
1896
|
+
*
|
|
1897
|
+
* @param {function(import('prosemirror-model').Node): boolean} pred
|
|
1898
|
+
* @param {boolean} requireUnique
|
|
1899
|
+
* @return {number|null}
|
|
1900
|
+
*/
|
|
1901
|
+
const findByPredicate = (pred, requireUnique = false) => {
|
|
1902
|
+
let occurrence = 0;
|
|
1903
|
+
for (let i = 0; i <= targetIdx; i++) {
|
|
1904
|
+
if (pred(oldDoc.child(i))) {
|
|
1905
|
+
occurrence++;
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
let matchCount = 0;
|
|
1909
|
+
let matchStart = -1;
|
|
1910
|
+
let matchBlock = null;
|
|
1911
|
+
let newPos = 0;
|
|
1912
|
+
for (let i = 0; i < newDoc.childCount; i++) {
|
|
1913
|
+
const child = newDoc.child(i);
|
|
1914
|
+
if (pred(child)) {
|
|
1915
|
+
matchCount++;
|
|
1916
|
+
if (matchCount === occurrence) {
|
|
1917
|
+
matchStart = newPos;
|
|
1918
|
+
matchBlock = child;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
newPos += child.nodeSize;
|
|
1922
|
+
}
|
|
1923
|
+
if (matchBlock === null || (requireUnique && (occurrence !== 1 || matchCount !== 1))) {
|
|
1924
|
+
return null
|
|
1925
|
+
}
|
|
1926
|
+
return place(matchStart, matchBlock)
|
|
1927
|
+
};
|
|
1928
|
+
|
|
1929
|
+
/**
|
|
1930
|
+
* @param {import('prosemirror-model').Node} child
|
|
1931
|
+
* @return {boolean}
|
|
1932
|
+
*/
|
|
1933
|
+
const sameTypeAndAttrs = (child) =>
|
|
1934
|
+
child.type === targetChild.type && attrsEqual(child.attrs, targetChild.attrs);
|
|
1935
|
+
const oldText = targetChild.textContent;
|
|
1936
|
+
|
|
1937
|
+
const byAll = findByPredicate((child) => sameTypeAndAttrs(child) && child.textContent === oldText);
|
|
1938
|
+
if (byAll !== null) {
|
|
1939
|
+
return byAll
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
// Text must be matched before attrs: after a remote attr-only edit, the
|
|
1943
|
+
// attrs pass would steer the cursor into a sibling that kept the old attrs.
|
|
1944
|
+
const byText = findByPredicate(
|
|
1945
|
+
(child) => child.type === targetChild.type && child.textContent === oldText
|
|
1946
|
+
);
|
|
1947
|
+
if (byText !== null) {
|
|
1948
|
+
return byText
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
// In-flight local typing diverges the text between both docs. Distinctive
|
|
1952
|
+
// attrs still identify the block; default-only attrs match every sibling.
|
|
1953
|
+
if (hasDistinctiveAttrs(targetChild)) {
|
|
1954
|
+
const byAttrs = findByPredicate(sameTypeAndAttrs, true);
|
|
1955
|
+
if (byAttrs !== null) {
|
|
1956
|
+
return byAttrs
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
// Trailing in-flight keystrokes leave a prefix relation between old and new
|
|
1961
|
+
// text. Empty text is a prefix of everything and must never match.
|
|
1962
|
+
return findByPredicate(
|
|
1963
|
+
(child) => sameTypeAndAttrs(child) &&
|
|
1964
|
+
oldText !== '' && child.textContent !== '' &&
|
|
1965
|
+
(oldText.startsWith(child.textContent) || child.textContent.startsWith(oldText)),
|
|
1966
|
+
true
|
|
1967
|
+
)
|
|
1968
|
+
};
|
|
1969
|
+
|
|
1970
|
+
/**
|
|
1971
|
+
* Returns true when a transaction changes block structure rather than only
|
|
1972
|
+
* editing inline content inside existing blocks.
|
|
1973
|
+
*
|
|
1974
|
+
* @param {import('prosemirror-state').Transaction} tr
|
|
1975
|
+
* @param {import('prosemirror-model').Node} oldDoc
|
|
1976
|
+
* @return {boolean}
|
|
1977
|
+
*/
|
|
1978
|
+
const isStructuralTransaction = (tr, oldDoc) => {
|
|
1979
|
+
if (!tr.docChanged) {
|
|
1980
|
+
return false
|
|
1981
|
+
}
|
|
1982
|
+
if (tr.doc.childCount !== oldDoc.childCount) {
|
|
1983
|
+
return true
|
|
1984
|
+
}
|
|
1985
|
+
for (const step of tr.steps) {
|
|
1986
|
+
if (step instanceof ReplaceStep) {
|
|
1987
|
+
if (step.from === 0 && step.to === oldDoc.content.size) {
|
|
1988
|
+
return true
|
|
1989
|
+
}
|
|
1990
|
+
if (step.slice.content.size > 0) {
|
|
1991
|
+
let hasBlock = false;
|
|
1992
|
+
step.slice.content.forEach((node) => {
|
|
1993
|
+
if (node.isBlock) {
|
|
1994
|
+
hasBlock = true;
|
|
1995
|
+
}
|
|
1996
|
+
});
|
|
1997
|
+
if (hasBlock) {
|
|
1998
|
+
return true
|
|
1999
|
+
}
|
|
2000
|
+
} else if (step.to > step.from) {
|
|
2001
|
+
const $from = oldDoc.resolve(step.from);
|
|
2002
|
+
const $to = oldDoc.resolve(step.to);
|
|
2003
|
+
if ($from.depth === 0 && $to.depth === 0 && $from.index() !== $to.index()) {
|
|
2004
|
+
return true
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
return false
|
|
2010
|
+
};
|
|
2011
|
+
|
|
2012
|
+
/**
|
|
2013
|
+
* Detect stale relative positions after structural changes that resolve to the
|
|
2014
|
+
* wrong text block or to the start of the correct block.
|
|
2015
|
+
*
|
|
2016
|
+
* @param {import('prosemirror-model').Node} oldDoc
|
|
2017
|
+
* @param {import('prosemirror-model').Node} newDoc
|
|
2018
|
+
* @param {number} oldAbs
|
|
2019
|
+
* @param {number|null} resolvedAbs
|
|
2020
|
+
* @return {boolean}
|
|
2021
|
+
*/
|
|
2022
|
+
const isMisresolvedAfterStructuralChange = (oldDoc, newDoc, oldAbs, resolvedAbs) => {
|
|
2023
|
+
if (resolvedAbs === null) {
|
|
2024
|
+
return false
|
|
2025
|
+
}
|
|
2026
|
+
const $old = oldDoc.resolve(oldAbs);
|
|
2027
|
+
const $new = newDoc.resolve(resolvedAbs);
|
|
2028
|
+
if (!$old.parent.isTextblock) {
|
|
2029
|
+
return false
|
|
2030
|
+
}
|
|
2031
|
+
// A textblock cursor cannot legitimately resolve into a non-textblock;
|
|
2032
|
+
// a structural reorder replaced the block via delete + insert.
|
|
2033
|
+
if (!$new.parent.isTextblock) {
|
|
2034
|
+
return true
|
|
2035
|
+
}
|
|
2036
|
+
if ($old.parent.textContent !== $new.parent.textContent) {
|
|
2037
|
+
return true
|
|
2038
|
+
}
|
|
2039
|
+
if ($old.parentOffset !== 0 && $new.parentOffset === 0) {
|
|
2040
|
+
return true
|
|
2041
|
+
}
|
|
2042
|
+
const bothAtStart = $old.parentOffset === 0 && $new.parentOffset === 0;
|
|
2043
|
+
// A changed offset, type or attrs hints at a same-text sibling. When all
|
|
2044
|
+
// of them agree there is no signal left and the Yjs resolution must win.
|
|
2045
|
+
const suspicious = $old.parentOffset !== $new.parentOffset ||
|
|
2046
|
+
$old.parent.type !== $new.parent.type ||
|
|
2047
|
+
!attrsEqual($old.parent.attrs, $new.parent.attrs);
|
|
2048
|
+
if (bothAtStart || suspicious) {
|
|
2049
|
+
const expected = findAbsolutePositionAfterStructuralChange(oldDoc, newDoc, oldAbs);
|
|
2050
|
+
return expected !== null && expected !== resolvedAbs
|
|
2051
|
+
}
|
|
2052
|
+
return false
|
|
1716
2053
|
};
|
|
1717
2054
|
|
|
1718
2055
|
/**
|
|
@@ -2131,11 +2468,21 @@ const yCursorPlugin = (
|
|
|
2131
2468
|
selectionBuilder
|
|
2132
2469
|
)
|
|
2133
2470
|
},
|
|
2134
|
-
apply (tr, prevState,
|
|
2471
|
+
apply (tr, prevState, oldState, newState) {
|
|
2135
2472
|
const ystate = ySyncPluginKey.getState(newState);
|
|
2136
2473
|
const yCursorState = tr.getMeta(yCursorPluginKey);
|
|
2474
|
+
const isRemoteChange = ystate && ystate.isChangeOrigin;
|
|
2137
2475
|
if (
|
|
2138
|
-
|
|
2476
|
+
tr.docChanged &&
|
|
2477
|
+
!isRemoteChange &&
|
|
2478
|
+
isStructuralTransaction(tr, oldState.doc)
|
|
2479
|
+
) {
|
|
2480
|
+
// The ProseMirror document leads the Yjs mapping during local moves.
|
|
2481
|
+
// Hide stale awareness until the collaborator publishes its new cursor.
|
|
2482
|
+
return DecorationSet.empty
|
|
2483
|
+
}
|
|
2484
|
+
if (
|
|
2485
|
+
isRemoteChange ||
|
|
2139
2486
|
(yCursorState && yCursorState.awarenessUpdated)
|
|
2140
2487
|
) {
|
|
2141
2488
|
return createDecorations(
|
|
@@ -2146,7 +2493,10 @@ const yCursorPlugin = (
|
|
|
2146
2493
|
selectionBuilder
|
|
2147
2494
|
)
|
|
2148
2495
|
}
|
|
2149
|
-
|
|
2496
|
+
if (tr.docChanged) {
|
|
2497
|
+
return prevState.map(tr.mapping, tr.doc)
|
|
2498
|
+
}
|
|
2499
|
+
return prevState
|
|
2150
2500
|
}
|
|
2151
2501
|
},
|
|
2152
2502
|
props: {
|
|
@@ -2161,15 +2511,19 @@ const yCursorPlugin = (
|
|
|
2161
2511
|
setMeta(view, yCursorPluginKey, { awarenessUpdated: true });
|
|
2162
2512
|
}
|
|
2163
2513
|
};
|
|
2164
|
-
|
|
2165
|
-
|
|
2514
|
+
/**
|
|
2515
|
+
* @param {import('prosemirror-view').EditorView} editorView
|
|
2516
|
+
* @param {{ force?: boolean }} [opts]
|
|
2517
|
+
*/
|
|
2518
|
+
const updateCursorInfo = (editorView, opts = {}) => {
|
|
2519
|
+
const ystate = ySyncPluginKey.getState(editorView.state);
|
|
2166
2520
|
// `ystate` is undefined during sync-plugin init and Y.Doc transitions;
|
|
2167
2521
|
// reading from it here would throw and break cursor tracking for the view.
|
|
2168
2522
|
if (!ystate || !ystate.binding) return
|
|
2169
2523
|
// @note We make implicit checks when checking for the cursor property
|
|
2170
2524
|
const current = awareness.getLocalState() || {};
|
|
2171
|
-
if (
|
|
2172
|
-
const selection = getSelection(
|
|
2525
|
+
if (editorView.hasFocus()) {
|
|
2526
|
+
const selection = getSelection(editorView.state);
|
|
2173
2527
|
/**
|
|
2174
2528
|
* @type {Y.RelativePosition}
|
|
2175
2529
|
*/
|
|
@@ -2187,6 +2541,7 @@ const yCursorPlugin = (
|
|
|
2187
2541
|
ystate.binding.mapping
|
|
2188
2542
|
);
|
|
2189
2543
|
if (
|
|
2544
|
+
opts.force ||
|
|
2190
2545
|
current.cursor == null ||
|
|
2191
2546
|
!Y.compareRelativePositions(
|
|
2192
2547
|
Y.createRelativePositionFromJSON(current.cursor.anchor),
|
|
@@ -2215,14 +2570,23 @@ const yCursorPlugin = (
|
|
|
2215
2570
|
awareness.setLocalStateField(cursorStateField, null);
|
|
2216
2571
|
}
|
|
2217
2572
|
};
|
|
2573
|
+
const onFocusChange = () => updateCursorInfo(view);
|
|
2218
2574
|
awareness.on('change', awarenessListener);
|
|
2219
|
-
view.dom.addEventListener('focusin',
|
|
2220
|
-
view.dom.addEventListener('focusout',
|
|
2575
|
+
view.dom.addEventListener('focusin', onFocusChange);
|
|
2576
|
+
view.dom.addEventListener('focusout', onFocusChange);
|
|
2221
2577
|
return {
|
|
2222
|
-
update:
|
|
2578
|
+
update: (editorView, prevState) => {
|
|
2579
|
+
const ystate = ySyncPluginKey.getState(editorView.state);
|
|
2580
|
+
const forceAwarenessUpdate = !!(
|
|
2581
|
+
ystate?.isChangeOrigin &&
|
|
2582
|
+
prevState?.doc &&
|
|
2583
|
+
!prevState.doc.eq(editorView.state.doc)
|
|
2584
|
+
);
|
|
2585
|
+
updateCursorInfo(editorView, { force: forceAwarenessUpdate });
|
|
2586
|
+
},
|
|
2223
2587
|
destroy: () => {
|
|
2224
|
-
view.dom.removeEventListener('focusin',
|
|
2225
|
-
view.dom.removeEventListener('focusout',
|
|
2588
|
+
view.dom.removeEventListener('focusin', onFocusChange);
|
|
2589
|
+
view.dom.removeEventListener('focusout', onFocusChange);
|
|
2226
2590
|
awareness.off('change', awarenessListener);
|
|
2227
2591
|
awareness.setLocalStateField(cursorStateField, null);
|
|
2228
2592
|
}
|
|
@@ -2344,5 +2708,5 @@ const yUndoPlugin = ({ protectedNodes = defaultProtectedNodes, trackedOrigins =
|
|
|
2344
2708
|
}
|
|
2345
2709
|
});
|
|
2346
2710
|
|
|
2347
|
-
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 };
|
|
2711
|
+
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 };
|
|
2348
2712
|
//# sourceMappingURL=y-tiptap.js.map
|