@tiptap/y-tiptap 3.0.1 → 3.0.3

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/y-tiptap.js CHANGED
@@ -1382,24 +1382,115 @@ const matchNodeName = (yElement, pNode) =>
1382
1382
  */
1383
1383
  let viewsToUpdate = null;
1384
1384
 
1385
- const updateMetas = () => {
1386
- const ups = /** @type {Map<EditorView, Map<any, any>>} */ (viewsToUpdate);
1387
- viewsToUpdate = null;
1388
- ups.forEach((metas, view) => {
1389
- const tr = view.state.tr;
1390
- const syncState = ySyncPluginKey.getState(view.state);
1385
+ class MetaEntry {
1386
+ /**
1387
+ * @param {EditorView} view
1388
+ * @param {any} key
1389
+ * @param {any} value
1390
+ */
1391
+ constructor (view, key, value) {
1392
+ this.view = view;
1393
+ this.key = key;
1394
+ this.value = value;
1395
+ }
1396
+
1397
+ apply () {
1398
+ const syncState = ySyncPluginKey.getState(this.view.state);
1391
1399
  if (syncState && syncState.binding && !syncState.binding.isDestroyed) {
1392
- metas.forEach((val, key) => {
1393
- tr.setMeta(key, val);
1400
+ const tr = this.view.state.tr;
1401
+ tr.setMeta(this.key, this.value);
1402
+ this.view.dispatch(tr);
1403
+ }
1404
+ }
1405
+ }
1406
+
1407
+ class MetaEntriesQueue {
1408
+ /**
1409
+ * @param {Array<MetaEntry>} [entries=[]]
1410
+ */
1411
+ constructor (entries = []) {
1412
+ this.entries = entries;
1413
+ }
1414
+
1415
+ /**
1416
+ * @return {MetaEntry|undefined}
1417
+ */
1418
+ getFirst () {
1419
+ return this.entries[0]
1420
+ }
1421
+
1422
+ /**
1423
+ * @return {MetaEntry|undefined}
1424
+ */
1425
+ dequeueFirst () {
1426
+ return this.entries.shift()
1427
+ }
1428
+
1429
+ /**
1430
+ * @return {boolean}
1431
+ */
1432
+ isEmpty () {
1433
+ return this.entries.length === 0
1434
+ }
1435
+
1436
+ static fromViewsToUpdate () {
1437
+ const ups = /** @type {Map<EditorView, Map<any, any>>} */ (viewsToUpdate);
1438
+ viewsToUpdate = null;
1439
+ const entries = [];
1440
+ ups.forEach((metas, view) => {
1441
+ metas.forEach((value, key) => {
1442
+ entries.push(new MetaEntry(view, key, value));
1394
1443
  });
1395
- view.dispatch(tr);
1444
+ });
1445
+ return new MetaEntriesQueue(entries)
1446
+ }
1447
+ }
1448
+
1449
+ /**
1450
+ * Dispatch queued plugin metadata in order, retrying only the remaining
1451
+ * entries if a transaction becomes stale while the async queue is flushing.
1452
+ *
1453
+ * Cursor awareness updates are decoration-only refreshes. If a real document
1454
+ * transaction lands before one of these queued meta transactions is applied,
1455
+ * ProseMirror can reject it with a RangeError. In that case we reschedule the
1456
+ * remaining entries on the next tick. If the first remaining entry fails again
1457
+ * on retry, we drop that entry and continue with the rest of the queue instead
1458
+ * of retrying forever or crashing the editor.
1459
+ *
1460
+ * @param {MetaEntriesQueue} [metaEntries=MetaEntriesQueue.fromViewsToUpdate()]
1461
+ * @param {boolean} [isRetry=false]
1462
+ */
1463
+ const updateMetas = (metaEntries = MetaEntriesQueue.fromViewsToUpdate(), isRetry = false) => {
1464
+ let isFirst = true;
1465
+ while (!metaEntries.isEmpty()) {
1466
+ const metaEntry = metaEntries.getFirst();
1467
+ try {
1468
+ metaEntry.apply();
1469
+ } catch (err) {
1470
+ // ProseMirror throws a RangeError when this transaction was created from
1471
+ // an older state and another transaction changed the document before this
1472
+ // meta-only dispatch was applied ("Applying a mismatched transaction").
1473
+ if (err instanceof RangeError) {
1474
+ if (isRetry && isFirst) {
1475
+ // Drop the repeatedly stale entry so the queue can continue flushing.
1476
+ metaEntries.dequeueFirst();
1477
+ }
1478
+ if (!metaEntries.isEmpty()) {
1479
+ eventloop.timeout(0, () => updateMetas(metaEntries, true));
1480
+ }
1481
+ return
1482
+ }
1483
+ throw err
1396
1484
  }
1397
- });
1485
+ isFirst = false;
1486
+ metaEntries.dequeueFirst();
1487
+ }
1398
1488
  };
1399
1489
 
1400
1490
  const setMeta = (view, key, value) => {
1401
1491
  if (!viewsToUpdate) {
1402
1492
  viewsToUpdate = new Map();
1493
+ // Awareness listeners can fire in bursts, so batch them into one tick.
1403
1494
  eventloop.timeout(0, updateMetas);
1404
1495
  }
1405
1496
  map.setIfUndefined(viewsToUpdate, view, map.create).set(key, value);
@@ -1808,7 +1899,11 @@ function yXmlFragmentToProsemirrorJSON (xmlFragment) {
1808
1899
  * @param {any} _user user data
1809
1900
  * @return {boolean}
1810
1901
  */
1811
- const defaultAwarenessStateFilter = (currentClientId, userClientId, _user) => currentClientId !== userClientId;
1902
+ const defaultAwarenessStateFilter = (
1903
+ currentClientId,
1904
+ userClientId,
1905
+ _user
1906
+ ) => currentClientId !== userClientId;
1812
1907
 
1813
1908
  /**
1814
1909
  * Default generator for a cursor element
@@ -1862,10 +1957,14 @@ const createDecorations = (
1862
1957
  createSelection
1863
1958
  ) => {
1864
1959
  const ystate = ySyncPluginKey.getState(state);
1960
+ if (ystate == null || ystate.doc == null || ystate.binding == null) {
1961
+ return DecorationSet.create(state.doc, [])
1962
+ }
1865
1963
  const y = ystate.doc;
1866
1964
  const decorations = [];
1867
1965
  if (
1868
- ystate.snapshot != null || ystate.prevSnapshot != null ||
1966
+ ystate.snapshot != null ||
1967
+ ystate.prevSnapshot != null ||
1869
1968
  ystate.binding.mapping.size === 0
1870
1969
  ) {
1871
1970
  // do not render cursors while snapshot is active