@tiptap/y-tiptap 3.0.5 → 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 +4 -0
- package/dist/src/y-tiptap.d.ts +1 -1
- package/dist/y-tiptap.cjs +224 -17
- package/dist/y-tiptap.cjs.map +1 -1
- package/dist/y-tiptap.js +221 -18
- 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
|
|
@@ -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));
|
|
@@ -319,18 +321,41 @@ const restoreRelativeSelection = (tr, relSel, binding) => {
|
|
|
319
321
|
tr.setSelection(selection);
|
|
320
322
|
}
|
|
321
323
|
} else {
|
|
322
|
-
|
|
324
|
+
let anchor = relativePositionToAbsolutePosition(
|
|
323
325
|
binding.doc,
|
|
324
326
|
binding.type,
|
|
325
327
|
relSel.anchor,
|
|
326
328
|
binding.mapping
|
|
327
329
|
);
|
|
328
|
-
|
|
330
|
+
let head = relativePositionToAbsolutePosition(
|
|
329
331
|
binding.doc,
|
|
330
332
|
binding.type,
|
|
331
333
|
relSel.head,
|
|
332
334
|
binding.mapping
|
|
333
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
|
+
}
|
|
334
359
|
if (anchor !== null && head !== null) {
|
|
335
360
|
tr.setSelection(TextSelection.between(tr.doc.resolve(anchor), tr.doc.resolve(head)));
|
|
336
361
|
}
|
|
@@ -411,7 +436,9 @@ const getRelativeSelection = (pmbinding, state) => {
|
|
|
411
436
|
state.selection.head,
|
|
412
437
|
pmbinding.type,
|
|
413
438
|
pmbinding.mapping
|
|
414
|
-
)
|
|
439
|
+
),
|
|
440
|
+
absAnchor: state.selection.anchor,
|
|
441
|
+
absHead: state.selection.head
|
|
415
442
|
}
|
|
416
443
|
};
|
|
417
444
|
|
|
@@ -736,6 +763,9 @@ class ProsemirrorBinding {
|
|
|
736
763
|
);
|
|
737
764
|
transaction.changed.forEach(delType);
|
|
738
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();
|
|
739
769
|
const fragmentContent = this.type.toArray().map((t) =>
|
|
740
770
|
createNodeIfNotExists(
|
|
741
771
|
/** @type {Y.XmlElement | Y.XmlHook} */ (t),
|
|
@@ -743,13 +773,14 @@ class ProsemirrorBinding {
|
|
|
743
773
|
this
|
|
744
774
|
)
|
|
745
775
|
).filter((n) => n !== null);
|
|
776
|
+
const oldDoc = this.prosemirrorView.state.doc;
|
|
746
777
|
// @ts-ignore
|
|
747
778
|
let tr = this._tr.replace(
|
|
748
779
|
0,
|
|
749
780
|
this.prosemirrorView.state.doc.content.size,
|
|
750
781
|
new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0)
|
|
751
782
|
);
|
|
752
|
-
restoreRelativeSelection(tr, this.beforeTransactionSelection, this);
|
|
783
|
+
restoreRelativeSelection(tr, this.beforeTransactionSelection, this, oldDoc);
|
|
753
784
|
tr = tr.setMeta(ySyncPluginKey, { isChangeOrigin: true, isUndoRedoOperation: transaction.origin instanceof Y.UndoManager });
|
|
754
785
|
if (
|
|
755
786
|
this.beforeTransactionSelection !== null && this._isLocalCursorInView()
|
|
@@ -1635,6 +1666,26 @@ const absolutePositionToRelativePosition = (pos, type, mapping) => {
|
|
|
1635
1666
|
return Y.createRelativePositionFromTypeIndex(type, type._length, -1)
|
|
1636
1667
|
};
|
|
1637
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
|
+
|
|
1638
1689
|
const createRelativePosition = (type, item) => {
|
|
1639
1690
|
let typeid = null;
|
|
1640
1691
|
let tname = null;
|
|
@@ -1712,7 +1763,133 @@ const relativePositionToAbsolutePosition = (y, documentType, relPos, mapping) =>
|
|
|
1712
1763
|
}
|
|
1713
1764
|
type = /** @type {Y.AbstractType} */ (parent);
|
|
1714
1765
|
}
|
|
1715
|
-
|
|
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
|
|
1716
1893
|
};
|
|
1717
1894
|
|
|
1718
1895
|
/**
|
|
@@ -2131,7 +2308,7 @@ const yCursorPlugin = (
|
|
|
2131
2308
|
selectionBuilder
|
|
2132
2309
|
)
|
|
2133
2310
|
},
|
|
2134
|
-
apply (tr, prevState,
|
|
2311
|
+
apply (tr, prevState, oldState, newState) {
|
|
2135
2312
|
const ystate = ySyncPluginKey.getState(newState);
|
|
2136
2313
|
const yCursorState = tr.getMeta(yCursorPluginKey);
|
|
2137
2314
|
if (
|
|
@@ -2146,7 +2323,19 @@ const yCursorPlugin = (
|
|
|
2146
2323
|
selectionBuilder
|
|
2147
2324
|
)
|
|
2148
2325
|
}
|
|
2149
|
-
|
|
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
|
|
2150
2339
|
}
|
|
2151
2340
|
},
|
|
2152
2341
|
props: {
|
|
@@ -2161,15 +2350,19 @@ const yCursorPlugin = (
|
|
|
2161
2350
|
setMeta(view, yCursorPluginKey, { awarenessUpdated: true });
|
|
2162
2351
|
}
|
|
2163
2352
|
};
|
|
2164
|
-
|
|
2165
|
-
|
|
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);
|
|
2166
2359
|
// `ystate` is undefined during sync-plugin init and Y.Doc transitions;
|
|
2167
2360
|
// reading from it here would throw and break cursor tracking for the view.
|
|
2168
2361
|
if (!ystate || !ystate.binding) return
|
|
2169
2362
|
// @note We make implicit checks when checking for the cursor property
|
|
2170
2363
|
const current = awareness.getLocalState() || {};
|
|
2171
|
-
if (
|
|
2172
|
-
const selection = getSelection(
|
|
2364
|
+
if (editorView.hasFocus()) {
|
|
2365
|
+
const selection = getSelection(editorView.state);
|
|
2173
2366
|
/**
|
|
2174
2367
|
* @type {Y.RelativePosition}
|
|
2175
2368
|
*/
|
|
@@ -2187,6 +2380,7 @@ const yCursorPlugin = (
|
|
|
2187
2380
|
ystate.binding.mapping
|
|
2188
2381
|
);
|
|
2189
2382
|
if (
|
|
2383
|
+
opts.force ||
|
|
2190
2384
|
current.cursor == null ||
|
|
2191
2385
|
!Y.compareRelativePositions(
|
|
2192
2386
|
Y.createRelativePositionFromJSON(current.cursor.anchor),
|
|
@@ -2215,14 +2409,23 @@ const yCursorPlugin = (
|
|
|
2215
2409
|
awareness.setLocalStateField(cursorStateField, null);
|
|
2216
2410
|
}
|
|
2217
2411
|
};
|
|
2412
|
+
const onFocusChange = () => updateCursorInfo(view);
|
|
2218
2413
|
awareness.on('change', awarenessListener);
|
|
2219
|
-
view.dom.addEventListener('focusin',
|
|
2220
|
-
view.dom.addEventListener('focusout',
|
|
2414
|
+
view.dom.addEventListener('focusin', onFocusChange);
|
|
2415
|
+
view.dom.addEventListener('focusout', onFocusChange);
|
|
2221
2416
|
return {
|
|
2222
|
-
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
|
+
},
|
|
2223
2426
|
destroy: () => {
|
|
2224
|
-
view.dom.removeEventListener('focusin',
|
|
2225
|
-
view.dom.removeEventListener('focusout',
|
|
2427
|
+
view.dom.removeEventListener('focusin', onFocusChange);
|
|
2428
|
+
view.dom.removeEventListener('focusout', onFocusChange);
|
|
2226
2429
|
awareness.off('change', awarenessListener);
|
|
2227
2430
|
awareness.setLocalStateField(cursorStateField, null);
|
|
2228
2431
|
}
|
|
@@ -2344,5 +2547,5 @@ const yUndoPlugin = ({ protectedNodes = defaultProtectedNodes, trackedOrigins =
|
|
|
2344
2547
|
}
|
|
2345
2548
|
});
|
|
2346
2549
|
|
|
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 };
|
|
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 };
|
|
2348
2551
|
//# sourceMappingURL=y-tiptap.js.map
|