@panphora/clayjs 1.16.0 → 1.18.0
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/all.js +1 -1
- package/dist/clay-dom.js +1 -1
- package/dist/clay-events.js +1 -1
- package/dist/clay-internals.js +1 -1
- package/dist/clay-options.js +1 -1
- package/dist/clay-router.js +1 -1
- package/dist/clay-ui.js +1 -1
- package/dist/clay-utils.js +1 -1
- package/dist/clay.js +1 -1
- package/dist/clay.standalone.js +72519 -59839
- package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
- package/dist/m/attrs/save-freeze-LWGF6XUN.js +1 -0
- package/dist/m/c-2GRUM77Q.js +30 -0
- package/dist/m/c-2KIEGNC4.js +1 -0
- package/dist/m/{c-5R4PTRG2.js → c-2KL3BD52.js} +1 -1
- package/dist/m/c-2YYDMLNZ.js +1 -0
- package/dist/m/c-2ZZBC45O.js +1 -0
- package/dist/m/c-372N6H3B.js +1 -0
- package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
- package/dist/m/c-3LDGIBMN.js +1 -0
- package/dist/m/c-3O7OBDCM.js +1 -0
- package/dist/m/c-4ASQPRQL.js +1 -0
- package/dist/m/c-4TXNLX4K.js +6 -0
- package/dist/m/c-4YU3BR4D.js +6 -0
- package/dist/m/c-5734MF2P.js +1 -0
- package/dist/m/c-57XDY6HC.js +1 -0
- package/dist/m/{c-VFX45IZQ.js → c-5B4YBZXC.js} +1 -1
- package/dist/m/c-5ONTYIUI.js +1 -0
- package/dist/m/c-5OOVFCJM.js +1 -0
- package/dist/m/c-5T6NZHNM.js +1 -0
- package/dist/m/{c-YDH7Q3NY.js → c-5ZQV7KC4.js} +1 -1
- package/dist/m/{c-Z36J63PP.js → c-6DB2OLRJ.js} +1 -1
- package/dist/m/c-6DNRCPNM.js +1 -0
- package/dist/m/c-6UGWFMQC.js +2 -0
- package/dist/m/c-7K4SCQB7.js +1 -0
- package/dist/m/c-7MG5RU63.js +1 -0
- package/dist/m/c-7PRJCA3E.js +1 -0
- package/dist/m/c-A6L3FMUS.js +1 -0
- package/dist/m/c-AMU3BPPR.js +4 -0
- package/dist/m/c-AMYEFA2E.js +1 -0
- package/dist/m/c-AQYCG7QW.js +1 -0
- package/dist/m/c-ATKAOH23.js +1 -0
- package/dist/m/c-B3JM5ZXC.js +1 -0
- package/dist/m/c-B5CS55WR.js +1 -0
- package/dist/m/c-B5UJQT7W.js +1 -0
- package/dist/m/c-B7J4K2AE.js +3 -0
- package/dist/m/c-BHZYSHW4.js +1 -0
- package/dist/m/c-BIJALSRD.js +1 -0
- package/dist/m/c-BJ7YQ5QZ.js +1 -0
- package/dist/m/c-BPF2NSPV.js +1 -0
- package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
- package/dist/m/c-BUAYFEV5.js +1 -0
- package/dist/m/c-C6IPRG34.js +1 -0
- package/dist/m/{c-2JL6E75N.js → c-CAS3NND4.js} +1 -1
- package/dist/m/{c-4DRMS4QG.js → c-CCQ25GFY.js} +1 -1
- package/dist/m/c-CJFDLD43.js +1 -0
- package/dist/m/c-COPTL5WB.js +1 -0
- package/dist/m/c-D3E4WJ4M.js +166 -0
- package/dist/m/c-D5IKEKXW.js +7 -0
- package/dist/m/c-DHBRA6DL.js +1 -0
- package/dist/m/c-DKLS74XB.js +72 -0
- package/dist/m/c-DVYGFTSK.js +3 -0
- package/dist/m/c-E3ZXARWA.js +1 -0
- package/dist/m/c-EK63G3CG.js +1 -0
- package/dist/m/c-ERQSNR6K.js +13 -0
- package/dist/m/c-EVYAISP3.js +1 -0
- package/dist/m/c-EXKHPOVC.js +3 -0
- package/dist/m/{c-7VGZ7RU5.js → c-FWKHM4E7.js} +1 -1
- package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
- package/dist/m/c-GNDL3TKB.js +1 -0
- package/dist/m/c-GRWMD6O5.js +1 -0
- package/dist/m/c-GRXHHFPO.js +1 -0
- package/dist/m/{c-OYTFP5QN.js → c-GY422N3Q.js} +1 -1
- package/dist/m/c-HAWFNBYG.js +1 -0
- package/dist/m/c-HG2C77BM.js +4 -0
- package/dist/m/c-HHGJBU4D.js +1 -0
- package/dist/m/c-HMV6P4RX.js +1 -0
- package/dist/m/c-HMYWIHFG.js +1 -0
- package/dist/m/c-HT2DKBV2.js +1 -0
- package/dist/m/c-I2SCCUEW.js +1 -0
- package/dist/m/{c-HRDCD3WN.js → c-ICJYHABF.js} +2 -2
- package/dist/m/c-IMZCWYHR.js +7 -0
- package/dist/m/c-IVMD3FEM.js +1 -0
- package/dist/m/c-IVTRVENZ.js +1 -0
- package/dist/m/c-IW6GGOU2.js +1 -0
- package/dist/m/c-JKZJIUIP.js +1 -0
- package/dist/m/{c-NZMNXVF6.js → c-JNLX3RJB.js} +1 -1
- package/dist/m/c-JRKY44DK.js +1 -0
- package/dist/m/c-K3ADYNLU.js +1 -0
- package/dist/m/c-KBS3UV6B.js +1 -0
- package/dist/m/c-KGHVFRR3.js +1 -0
- package/dist/m/c-KJJPV77R.js +1 -0
- package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
- package/dist/m/{c-5Y6TNX5W.js → c-KP64LT2A.js} +2 -2
- package/dist/m/c-KQ7E3PYY.js +1 -0
- package/dist/m/c-L3F5ZLIV.js +1 -0
- package/dist/m/{c-LR4CGEPT.js → c-L42OLXWM.js} +3 -2
- package/dist/m/c-L5VFATT6.js +1 -0
- package/dist/m/c-L7JL5TL3.js +3 -0
- package/dist/m/c-LKSL4AUJ.js +1 -0
- package/dist/m/c-M6FNQCTQ.js +1 -0
- package/dist/m/c-MCOOHCSG.js +1 -0
- package/dist/m/c-MHI3GYKF.js +1 -0
- package/dist/m/c-MWXFLW4J.js +1 -0
- package/dist/m/c-NEL7HQW3.js +1 -0
- package/dist/m/c-NQJRVJ56.js +1 -0
- package/dist/m/c-NWX3MFT4.js +1 -0
- package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
- package/dist/m/c-OKXI52N7.js +1 -0
- package/dist/m/{c-MMUIUUZG.js → c-OPLUZR3A.js} +1 -1
- package/dist/m/{c-J7CA54IP.js → c-OTMKN2OO.js} +1 -1
- package/dist/m/{c-LTGMXXKO.js → c-OYRZ7XG2.js} +1 -1
- package/dist/m/c-P3OFYUMA.js +1 -0
- package/dist/m/c-PLN76GF3.js +6 -0
- package/dist/m/c-PLP4JKFC.js +1 -0
- package/dist/m/c-Q26MME6N.js +1 -0
- package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
- package/dist/m/{c-SWRJFCNN.js → c-QIKT6SKM.js} +1 -1
- package/dist/m/c-QIXT3Q7C.js +28 -0
- package/dist/m/c-QO4HNGCQ.js +1 -0
- package/dist/m/c-R27DWQKX.js +1 -0
- package/dist/m/{c-HVYXIS72.js → c-R2XUODVY.js} +1 -1
- package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
- package/dist/m/c-RND3G2S2.js +1 -0
- package/dist/m/c-S2U6NJU4.js +1 -0
- package/dist/m/c-SPZPGD3R.js +1 -0
- package/dist/m/c-SW4YEG2D.js +8 -0
- package/dist/m/c-SZGYW5HT.js +1 -0
- package/dist/m/c-T637T7SN.js +1 -0
- package/dist/m/c-TJAXGIRV.js +1 -0
- package/dist/m/{c-KKWLDOPM.js → c-TNEM3RNM.js} +1 -1
- package/dist/m/c-TTB5EK4S.js +32 -0
- package/dist/m/c-U7SHNWDH.js +1 -0
- package/dist/m/c-UDHIVLYH.js +20 -0
- package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
- package/dist/m/{c-TQZ2JYHD.js → c-UORXLSD3.js} +1 -1
- package/dist/m/c-UPD33ZPE.js +1 -0
- package/dist/m/c-URSMSFCB.js +1 -0
- package/dist/m/c-UY4QX42B.js +1 -0
- package/dist/m/c-V44KWI2L.js +4 -0
- package/dist/m/c-VG5XIN4T.js +1 -0
- package/dist/m/c-VILENZTD.js +1 -0
- package/dist/m/{c-33HUYEDI.js → c-VJYEVRYK.js} +1 -1
- package/dist/m/c-VR2BJ4AN.js +1 -0
- package/dist/m/c-W4JCVLAB.js +1 -0
- package/dist/m/c-W6DQSHXI.js +135 -0
- package/dist/m/c-WBGWGOM7.js +1 -0
- package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
- package/dist/m/c-WGW764WX.js +1 -0
- package/dist/m/c-WMRJKQ2V.js +5 -0
- package/dist/m/c-WNTYKCDG.js +1 -0
- package/dist/m/c-WQWIRWO3.js +72 -0
- package/dist/m/c-X5LNHET5.js +1 -0
- package/dist/m/c-X5TSXZYJ.js +2 -0
- package/dist/m/c-XGH4GS2T.js +1 -0
- package/dist/m/c-Y6F2FORL.js +1 -0
- package/dist/m/c-YBKHUAK7.js +10 -0
- package/dist/m/c-YBSS55KU.js +20 -0
- package/dist/m/c-YCLU4UNT.js +2 -0
- package/dist/m/c-YVZ2L7CE.js +1 -0
- package/dist/m/c-ZK4GIZH2.js +1 -0
- package/dist/m/c-ZRXJD4G6.js +1 -0
- package/dist/m/c-ZWBI4HYK.js +1 -0
- package/dist/m/{c-HT33EQRN.js → c-ZWIOJGMT.js} +1 -1
- package/dist/m/core/admin-attrs-YY7T5FGU.js +1 -0
- package/dist/m/core/autosave-S6OMNDCK.js +1 -0
- package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
- package/dist/m/core/host-permission-6EVJGNPL.js +1 -0
- package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
- package/dist/m/core/{persist-HKHA66IY.js → persist-UKXV5KLA.js} +1 -1
- package/dist/m/core/recovery-DRFZRJED.js +1 -0
- package/dist/m/core/save-LYY7K6U7.js +1 -0
- package/dist/m/core/save-core-MKRPSEEO.js +1 -0
- package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
- package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
- package/dist/m/events/index-RN2JNGBV.js +1 -0
- package/dist/m/internals/index-TGLQINN3.js +1 -0
- package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
- package/dist/m/loader-3BBNZZGM.js +1 -0
- package/dist/m/manifest-F449FF98.js +1 -0
- package/dist/m/manifest-edit-4396A009.js +1 -0
- package/dist/m/manifest.json +2509 -1474
- package/dist/m/options/options-EJMS3K24.js +1 -0
- package/dist/m/page/editor-6LRB3GE4.js +304 -0
- package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
- package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
- package/dist/m/plugins/demo-OYO5GCAO.js +1 -0
- package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
- package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
- package/dist/m/plugins/live-AD4H6XSL.js +1 -0
- package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
- package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
- package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
- package/dist/m/plugins/people-JCHVPF7P.js +1 -0
- package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
- package/dist/m/plugins/{richclay-RC3L2F6X.js → richclay-KLGETK77.js} +2 -2
- package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
- package/dist/m/plugins/source-TJXOIOYY.js +1 -0
- package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
- package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
- package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
- package/dist/m/plugins/upload-NHGJXZK2.js +1 -0
- package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
- package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
- package/dist/m/plugins/wire-VBWXEXU2.js +1 -0
- package/dist/m/reader-core-4W2KYEWQ.js +77 -0
- package/dist/m/route/route-62CHXL5B.js +1 -0
- package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
- package/dist/m/ui/index-IVWN2QC4.js +1 -0
- package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
- package/dist/offline-manifest.json +1560 -889
- package/dist/page/themes/light-1.css +33 -8
- package/dist/server/core.cjs +1554 -311
- package/dist/server/offline-runtime.cjs +63 -63
- package/dist/server/offline-runtime.mjs +63 -63
- package/dist/server/page-compaction-worker.cjs +3710 -0
- package/dist/server/rooms.mjs +5 -0
- package/docs/guide.md +653 -101
- package/entries/sap.js +5 -5
- package/package.json +1 -1
- package/src/core/admin-contenteditable.js +5 -2
- package/src/core/admin-inputs.js +1 -0
- package/src/core/admin-onclick.js +2 -1
- package/src/core/boot-phase.js +2 -1
- package/src/core/conflict-download.js +3 -29
- package/src/core/conflict-notice-ui.js +4 -7
- package/src/core/download.js +32 -0
- package/src/core/host-meta.js +56 -0
- package/src/core/is-edit-mode.js +17 -1
- package/src/core/mode-key.js +125 -19
- package/src/core/mode-support.js +5 -0
- package/src/core/mode-transition.js +20 -6
- package/src/core/preserve-authored.js +4 -4
- package/src/core/recovery.js +121 -33
- package/src/core/save-accepted.js +33 -0
- package/src/core/save-core.js +111 -32
- package/src/core/save-job.js +108 -0
- package/src/core/save-prepare.js +53 -0
- package/src/core/save-size-notice.js +3 -1
- package/src/core/save-slices.js +15 -0
- package/src/core/save.js +557 -127
- package/src/core/serialize-steps.js +109 -0
- package/src/core/snapshot.js +209 -40
- package/src/core/source-map.js +83 -38
- package/src/core/stale-host-notice.js +1 -0
- package/src/core/upgrade-run.js +59 -8
- package/src/dom/active-element.js +5 -0
- package/src/lib/assets.js +1 -0
- package/src/lib/autosave-scheduler.js +3 -4
- package/src/lib/slice.js +65 -0
- package/src/lib/uimotion.js +774 -0
- package/src/lib/unsaved-state.js +2 -2
- package/src/live/fast-lane.js +10 -3
- package/src/live/local-lane.js +202 -11
- package/src/live/ops.js +16 -0
- package/src/live/page-compaction.js +162 -0
- package/src/live/server-lane.js +1 -0
- package/src/live/store.js +11 -3
- package/src/loader-logic.js +3 -1
- package/src/loader.js +14 -4
- package/src/page/adopt-boot.js +2 -1
- package/src/page/awareness.js +33 -2
- package/src/page/blocks.js +122 -27
- package/src/page/board-views.js +2 -16
- package/src/page/boot-pack.js +1 -1
- package/src/page/chart-tables.js +29 -0
- package/src/page/chart.js +411 -72
- package/src/page/checklist.js +10 -135
- package/src/page/checkpoint-proof.js +295 -0
- package/src/page/chrome.js +257 -40
- package/src/page/clipboard-shape.js +126 -0
- package/src/page/document-title-dom.js +12 -0
- package/src/page/document-title-runtime.js +15 -1
- package/src/page/document-title.js +4 -1
- package/src/page/drag.js +33 -3
- package/src/page/editor.js +207 -31
- package/src/page/emoji-data.js +162 -0
- package/src/page/export-assets.js +357 -0
- package/src/page/export-html.js +209 -0
- package/src/page/export-markdown.js +361 -0
- package/src/page/features/board.js +12 -3
- package/src/page/features/chart.js +11 -0
- package/src/page/features/checklist.js +428 -0
- package/src/page/features/dates.js +180 -0
- package/src/page/features/emoji.js +187 -0
- package/src/page/features/export-html.js +189 -0
- package/src/page/features/export-markdown.js +169 -0
- package/src/page/features/import-markdown.js +349 -0
- package/src/page/features/media.js +61 -5
- package/src/page/features/mentions.js +188 -0
- package/src/page/features/registry.js +24 -1
- package/src/page/features/table.js +375 -85
- package/src/page/features/title-cover.js +331 -0
- package/src/page/features/title-icon.js +213 -0
- package/src/page/features/toc.js +87 -0
- package/src/page/format-command.js +33 -0
- package/src/page/format-gate.js +68 -18
- package/src/page/format-host.js +71 -0
- package/src/page/format-native-commands.js +158 -0
- package/src/page/format-native.js +42 -0
- package/src/page/image-actions.js +64 -0
- package/src/page/import-markdown.js +262 -0
- package/src/page/inline.js +32 -8
- package/src/page/items.js +141 -11
- package/src/page/markdown.js +30 -2
- package/src/page/media.js +35 -119
- package/src/page/mention-picker.js +99 -0
- package/src/page/merge.js +1 -1
- package/src/page/placeholder.js +14 -1
- package/src/page/projection.js +156 -25
- package/src/page/recover.js +10 -12
- package/src/page/recovery-ui.js +464 -0
- package/src/page/rich/chart.js +63 -7
- package/src/page/save-projection.js +67 -0
- package/src/page/save-states.js +14 -4
- package/src/page/schema.js +52 -3
- package/src/page/table-paste.js +3 -1
- package/src/page/table-style.js +4 -4
- package/src/page/table.js +44 -2
- package/src/page/themes/content.css +25 -1
- package/src/page/themes/light-1.css +33 -8
- package/src/page/themes/light-1.source.css +8 -7
- package/src/page/toc-link.js +31 -0
- package/src/page/toc.js +5 -0
- package/src/page/view-hooks.js +58 -0
- package/src/page/view.js +13 -0
- package/src/page/ybind/binding.js +116 -6
- package/src/page/ybind/bridge.js +187 -20
- package/src/page/ybind/containment.js +162 -0
- package/src/page/ybind/doc.js +17 -9
- package/src/page/ybind/file-debt.js +63 -0
- package/src/page/ybind/layout.js +7 -2
- package/src/page/ybind/marks.js +2 -1
- package/src/page/ybind/moves.js +5 -4
- package/src/page/ybind/origins.js +106 -4
- package/src/page/ybind/parts.js +8 -1
- package/src/page/ybind/provider.js +125 -18
- package/src/page/ybind/recovery-proof.js +32 -9
- package/src/page/ybind/recovery-replay.js +905 -4
- package/src/page/ybind/recovery.js +174 -45
- package/src/page/ybind/table.js +67 -11
- package/src/page/ybind/walk-delta.js +258 -0
- package/src/page/ybind/walk.js +54 -29
- package/src/page/ybind/writer.js +1 -1
- package/src/plugins/ai-edit.js +13 -9
- package/src/plugins/indicator-chip.js +91 -0
- package/src/plugins/indicator.js +13 -80
- package/src/plugins/live.js +126 -15
- package/src/plugins/menu/actions.js +196 -0
- package/src/plugins/menu/people.js +279 -0
- package/src/plugins/menu/placement.js +121 -0
- package/src/plugins/menu/preferences.js +9 -0
- package/src/plugins/menu/seo.js +316 -397
- package/src/plugins/menu/style.js +171 -0
- package/src/plugins/menu/variants.js +296 -0
- package/src/plugins/menu.js +186 -183
- package/src/plugins/page-view.js +3 -2
- package/src/plugins/page.js +362 -43
- package/src/plugins/sortable.js +8 -1
- package/src/plugins/source.js +6 -0
- package/src/plugins/upgrade.js +51 -17
- package/src/plugins/upload-ui.js +12 -17
- package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
- package/src/plugins/visitor-drafts/intro-style.js +7 -2
- package/src/plugins/visitor-drafts/intro.js +7 -4
- package/src/plugins/visitor-drafts/owner-chip.js +8 -12
- package/src/plugins/visitor-drafts/prompt.js +11 -9
- package/src/plugins/visitor-drafts/source.js +4 -3
- package/src/plugins/visitor-drafts/store.js +138 -9
- package/src/plugins/visitor-drafts/ui-memory.js +2 -2
- package/src/plugins/visitor-drafts-owner.js +4 -6
- package/src/plugins/visitor-drafts.js +5 -3
- package/src/recovery/store.js +1 -1
- package/src/route/route.js +4 -0
- package/src/server/checkpoint/index.js +2 -2
- package/src/server/rooms/index.js +5 -1
- package/src/server/rooms/live-data-rooms.js +302 -50
- package/src/server/rooms/page-compaction-worker.js +12 -0
- package/src/server/rooms/page-compactor.js +92 -0
- package/src/server/rooms/page-room-conformance.js +461 -0
- package/src/source/parse-steps.js +94 -0
- package/src/source/session.js +233 -57
- package/src/sync/editing-sender.js +12 -0
- package/src/sync/live-sync-edit.js +22 -2
- package/src/sync/live-sync.js +150 -23
- package/src/sync/page-relay.js +52 -0
- package/src/sync/presence-store.js +44 -4
- package/src/sync/presence.js +6 -3
- package/src/sync/saved-stream.js +28 -14
- package/src/ui/bevel-controls.js +1 -0
- package/src/ui/bevel-dialog.js +5 -2
- package/src/ui/editor-ui.js +77 -0
- package/src/ui/page-colour.js +281 -0
- package/src/ui/toast.js +16 -7
- package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
- package/src/vendor/hyper-undo.vendor.js +1 -1
- package/src/vendor/hypercms.vendor.js +9 -9
- package/src/vendor/parse5.vendor.js +2 -2
- package/src/vendor/sap-core.vendor.js +5 -5
- package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +0 -1
- package/dist/m/attrs/save-freeze-FUP4Q4X3.js +0 -1
- package/dist/m/c-2M73252O.js +0 -1
- package/dist/m/c-2SPVQSHC.js +0 -1
- package/dist/m/c-35NBGMZB.js +0 -1
- package/dist/m/c-3IQRECYS.js +0 -1
- package/dist/m/c-4B6H4G5V.js +0 -1
- package/dist/m/c-4FFHZM32.js +0 -1
- package/dist/m/c-4YSCRICO.js +0 -1
- package/dist/m/c-54RCDCBU.js +0 -1
- package/dist/m/c-5M4YEKCX.js +0 -4
- package/dist/m/c-5V3MOVJT.js +0 -1
- package/dist/m/c-66O32MV3.js +0 -28
- package/dist/m/c-6HMB24EV.js +0 -1
- package/dist/m/c-7O6AFR65.js +0 -1
- package/dist/m/c-ANQTJ3B5.js +0 -2
- package/dist/m/c-AYVOREBE.js +0 -1
- package/dist/m/c-B2ZXPOS3.js +0 -3
- package/dist/m/c-BMIZKMPV.js +0 -1
- package/dist/m/c-BTDI4Y23.js +0 -1
- package/dist/m/c-CJZSLMVQ.js +0 -1
- package/dist/m/c-DBE6ORSI.js +0 -1
- package/dist/m/c-EDHUR4MX.js +0 -1
- package/dist/m/c-EY6UOM54.js +0 -1
- package/dist/m/c-F3CKJTJS.js +0 -1
- package/dist/m/c-F7Y6FLPY.js +0 -1
- package/dist/m/c-FA4DZVTC.js +0 -1
- package/dist/m/c-G2BUZITR.js +0 -1
- package/dist/m/c-GB3JTV6E.js +0 -1
- package/dist/m/c-GBGWXTX4.js +0 -1
- package/dist/m/c-GHBCCVG4.js +0 -1
- package/dist/m/c-GJAEGGXY.js +0 -1
- package/dist/m/c-GOWBDM6J.js +0 -1
- package/dist/m/c-IB52R237.js +0 -1
- package/dist/m/c-IKWNE4R4.js +0 -1
- package/dist/m/c-IU3IYEU6.js +0 -1
- package/dist/m/c-IZ2EBRAJ.js +0 -1
- package/dist/m/c-KR4UW4KW.js +0 -1
- package/dist/m/c-KY3VJGUA.js +0 -8
- package/dist/m/c-LFPHCXY3.js +0 -1
- package/dist/m/c-LRPUO2MX.js +0 -1
- package/dist/m/c-LXENEBV7.js +0 -1
- package/dist/m/c-NMJRXKPC.js +0 -8
- package/dist/m/c-OIBUTDXI.js +0 -1
- package/dist/m/c-PPOSCAQD.js +0 -1
- package/dist/m/c-QIX5BJYN.js +0 -1
- package/dist/m/c-QQHEX6HA.js +0 -1
- package/dist/m/c-R7RMANJI.js +0 -9
- package/dist/m/c-RI4NT75Y.js +0 -1
- package/dist/m/c-RM7J7BV2.js +0 -1
- package/dist/m/c-RUJKSUQW.js +0 -1
- package/dist/m/c-SGCCC4FW.js +0 -25
- package/dist/m/c-T4VO4OCB.js +0 -1
- package/dist/m/c-TQEMVJCS.js +0 -1
- package/dist/m/c-TR6RMDQ7.js +0 -3
- package/dist/m/c-UKHTLWUW.js +0 -1
- package/dist/m/c-UNN7LWCT.js +0 -1
- package/dist/m/c-UXBMRDZB.js +0 -235
- package/dist/m/c-V7PZZRG7.js +0 -1
- package/dist/m/c-VL52ADDR.js +0 -1
- package/dist/m/c-WNYBLIOZ.js +0 -1
- package/dist/m/c-WWDBYXLK.js +0 -1
- package/dist/m/c-XNNW2CWK.js +0 -1
- package/dist/m/c-Y6USGTS5.js +0 -5
- package/dist/m/c-YG2NJYS6.js +0 -135
- package/dist/m/c-Z4VRC7GU.js +0 -7
- package/dist/m/c-Z6K52OLP.js +0 -19
- package/dist/m/c-ZDY2MDAA.js +0 -21
- package/dist/m/c-ZUCOKOHT.js +0 -1
- package/dist/m/core/admin-attrs-VGNLAUDK.js +0 -1
- package/dist/m/core/autosave-E4PEFPZ6.js +0 -1
- package/dist/m/core/conflict-notice-VQ6Y64PH.js +0 -1
- package/dist/m/core/host-permission-XN3K2IW3.js +0 -1
- package/dist/m/core/is-edit-mode-JXHDVGUL.js +0 -1
- package/dist/m/core/recovery-TBBSICE2.js +0 -1
- package/dist/m/core/save-WUCHTQBE.js +0 -1
- package/dist/m/core/save-core-FJVHT5M3.js +0 -1
- package/dist/m/core/snapshot-F3SWDZMM.js +0 -1
- package/dist/m/core/unsaved-warning-STKBHE6G.js +0 -1
- package/dist/m/events/index-23DPTUT4.js +0 -1
- package/dist/m/internals/index-WOVAP3RU.js +0 -1
- package/dist/m/lib/cache-bust-CGWT6ZJI.js +0 -1
- package/dist/m/loader-HXX6YNH5.js +0 -1
- package/dist/m/manifest-F22DEDAC.js +0 -1
- package/dist/m/manifest-edit-C3C7376B.js +0 -1
- package/dist/m/options/options-2LH76JQ3.js +0 -1
- package/dist/m/page/editor-XUTB2HB2.js +0 -189
- package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +0 -6
- package/dist/m/plugins/demo-ZIBFAA3R.js +0 -1
- package/dist/m/plugins/indicator-PV4YW2ID.js +0 -1
- package/dist/m/plugins/live-NUTTXSBS.js +0 -1
- package/dist/m/plugins/menu-HCKO3DSP.js +0 -138
- package/dist/m/plugins/page-KSUH7G6J.js +0 -2
- package/dist/m/plugins/page-view-W73OW7WI.js +0 -1
- package/dist/m/plugins/people-AJJ32YCG.js +0 -1
- package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
- package/dist/m/plugins/sortable-YRMQX3ZU.js +0 -1
- package/dist/m/plugins/source-KHK5UUSK.js +0 -1
- package/dist/m/plugins/undo-OF2L3CXU.js +0 -1
- package/dist/m/plugins/upgrade-ZNYODP5Y.js +0 -1
- package/dist/m/plugins/upload-XSGNILUY.js +0 -1
- package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +0 -21
- package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +0 -1
- package/dist/m/plugins/wire-ZWVWKF76.js +0 -1
- package/dist/m/reader-core-ILWLXVMP.js +0 -68
- package/dist/m/route/route-KOCZSEGN.js +0 -1
- package/dist/m/sync/live-sync-4AUVXVJU.js +0 -1
- package/dist/m/ui/index-EVHKMKBL.js +0 -1
- package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
- /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
- /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
- /package/dist/m/plugins/{richclay-RC3L2F6X.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
package/src/page/ybind/moves.js
CHANGED
|
@@ -4,6 +4,7 @@ import { state as pmState } from "../../vendor/prosemirror.vendor.js";
|
|
|
4
4
|
import { walk } from "./walk.js";
|
|
5
5
|
import { docOf } from "./doc.js";
|
|
6
6
|
import { createProjectionMap } from "./projection-map.js";
|
|
7
|
+
import { beforeFence } from "./writer.js";
|
|
7
8
|
import { unitIds } from "./origins.js";
|
|
8
9
|
import { attrKey, copyActiveKey, parseGoneIntents, physicalKeyOf, recoveredRoot, removeKey, restoreKey } from "./recovery.js";
|
|
9
10
|
|
|
@@ -113,12 +114,12 @@ const liveRemovals = (pd, current, selected) => {
|
|
|
113
114
|
return out;
|
|
114
115
|
};
|
|
115
116
|
|
|
116
|
-
export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
|
|
117
|
+
export function moveBlocks({ pd, view, um, origins, render, walked = null }, args = {}) {
|
|
117
118
|
const ids = Array.isArray(args?.ids) ? [...new Set(args.ids)] : [];
|
|
118
119
|
const afterId = args?.afterId === undefined ? null : args.afterId;
|
|
119
120
|
const hasParent = args?.parent !== undefined;
|
|
120
121
|
|
|
121
|
-
const current = walk(pd, origins);
|
|
122
|
+
const current = walked ?? walk(pd, origins);
|
|
122
123
|
// An opaque block is an operation this runtime must refuse before writing: it
|
|
123
124
|
// has no turn-into, it cannot be carried, and its record is never rewritten.
|
|
124
125
|
const protectedIds = new Set(current.visible.filter((seg) => seg.opaque).map((seg) => seg.id));
|
|
@@ -264,7 +265,7 @@ export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
|
|
|
264
265
|
let text = null, textAttrs = null;
|
|
265
266
|
const flush = () => {
|
|
266
267
|
if (text === null) return;
|
|
267
|
-
pd.flow.insert(cursor, text, textAttrs ?? undefined);
|
|
268
|
+
beforeFence(pd, cursor, after.fences, () => pd.flow.insert(cursor, text, textAttrs ?? undefined));
|
|
268
269
|
cursor += text.length;
|
|
269
270
|
text = null;
|
|
270
271
|
};
|
|
@@ -275,7 +276,7 @@ export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
|
|
|
275
276
|
text += unit.ch;
|
|
276
277
|
} else {
|
|
277
278
|
flush();
|
|
278
|
-
pd.flow.insertEmbed(cursor, unit.embed, unit.attrs);
|
|
279
|
+
beforeFence(pd, cursor, after.fences, () => pd.flow.insertEmbed(cursor, unit.embed, unit.attrs));
|
|
279
280
|
cursor += 1;
|
|
280
281
|
}
|
|
281
282
|
}
|
|
@@ -34,6 +34,15 @@ const boundaryRecord = (value) => {
|
|
|
34
34
|
|
|
35
35
|
const sameBoundary = (a, b) => a.blockId === b.blockId && a.kind === b.kind && a.recoveryOf === b.recoveryOf;
|
|
36
36
|
|
|
37
|
+
// The keys walkFlow reads boundary and alias positions from: any key that parses to
|
|
38
|
+
// ["boundary" | "alias", client, clock], whatever its value.
|
|
39
|
+
const markerKey = (key) => {
|
|
40
|
+
let parts;
|
|
41
|
+
try { parts = JSON.parse(key); } catch { return null; }
|
|
42
|
+
if (!Array.isArray(parts) || parts.length !== 3 || (parts[0] !== "boundary" && parts[0] !== "alias")) return null;
|
|
43
|
+
return isClock(parts[1]) && isClock(parts[2]) ? parts : null;
|
|
44
|
+
};
|
|
45
|
+
|
|
37
46
|
const recordKey = (key) => {
|
|
38
47
|
if (typeof key !== "string" || key.charCodeAt(0) !== 91) return null;
|
|
39
48
|
let parts;
|
|
@@ -46,6 +55,7 @@ const recordKey = (key) => {
|
|
|
46
55
|
export function createOriginIndex(origins) {
|
|
47
56
|
const spans = new Map();
|
|
48
57
|
const aliases = new Map();
|
|
58
|
+
const aliasClocks = new Map();
|
|
49
59
|
const boundaries = new Map();
|
|
50
60
|
|
|
51
61
|
const lower = (list, clock) => {
|
|
@@ -53,13 +63,56 @@ export function createOriginIndex(origins) {
|
|
|
53
63
|
while (lo < hi) { const mid = (lo + hi) >> 1; if (list[mid].clock < clock) lo = mid + 1; else hi = mid; }
|
|
54
64
|
return lo;
|
|
55
65
|
};
|
|
66
|
+
const lowerClock = (list, clock) => {
|
|
67
|
+
let lo = 0, hi = list.length;
|
|
68
|
+
while (lo < hi) { const mid = (lo + hi) >> 1; if (list[mid] < clock) lo = mid + 1; else hi = mid; }
|
|
69
|
+
return lo;
|
|
70
|
+
};
|
|
71
|
+
const markAlias = (client, clock, present) => {
|
|
72
|
+
const list = aliasClocks.get(client) ?? [];
|
|
73
|
+
const at = lowerClock(list, clock);
|
|
74
|
+
const there = list[at] === clock;
|
|
75
|
+
if (present && !there) list.splice(at, 0, clock);
|
|
76
|
+
else if (!present && there) list.splice(at, 1);
|
|
77
|
+
if (list.length) aliasClocks.set(client, list);
|
|
78
|
+
else aliasClocks.delete(client);
|
|
79
|
+
};
|
|
80
|
+
// Boundary and alias keys in the map's key order (Yjs keeps a key where it was first
|
|
81
|
+
// written), and each client's clocks that have one, sorted, for walkFlow.
|
|
82
|
+
const markerRecords = [];
|
|
83
|
+
const markerEntries = new Map();
|
|
84
|
+
const markerCounts = new Map();
|
|
85
|
+
const markerClocks = new Map();
|
|
86
|
+
const trackMarker = (key, live) => {
|
|
87
|
+
let entry = markerEntries.get(key);
|
|
88
|
+
if (entry === undefined) {
|
|
89
|
+
const parts = markerKey(key);
|
|
90
|
+
entry = parts ? { kind: parts[0], client: parts[1], clock: parts[2], live: false } : null;
|
|
91
|
+
markerEntries.set(key, entry);
|
|
92
|
+
if (entry) markerRecords.push(entry);
|
|
93
|
+
}
|
|
94
|
+
if (!entry || entry.live === live) return;
|
|
95
|
+
entry.live = live;
|
|
96
|
+
const counts = markerCounts.get(entry.client) ?? markerCounts.set(entry.client, new Map()).get(entry.client);
|
|
97
|
+
const list = markerClocks.get(entry.client) ?? markerClocks.set(entry.client, []).get(entry.client);
|
|
98
|
+
const n = (counts.get(entry.clock) ?? 0) + (live ? 1 : -1);
|
|
99
|
+
if (n) counts.set(entry.clock, n);
|
|
100
|
+
else counts.delete(entry.clock);
|
|
101
|
+
const at = lowerClock(list, entry.clock);
|
|
102
|
+
if (live && n === 1) list.splice(at, 0, entry.clock);
|
|
103
|
+
else if (!live && n === 0) list.splice(at, 1);
|
|
104
|
+
if (!list.length) { markerClocks.delete(entry.client); markerCounts.delete(entry.client); }
|
|
105
|
+
};
|
|
56
106
|
const apply = (key, value) => {
|
|
107
|
+
trackMarker(key, value !== undefined);
|
|
57
108
|
const parts = recordKey(key);
|
|
58
109
|
if (!parts) return;
|
|
59
110
|
const [kind, client, clock] = parts;
|
|
60
111
|
if (kind === "alias") {
|
|
61
|
-
|
|
112
|
+
const valid = Boolean(value && isClock(value.sourceClient) && isClock(value.sourceClock));
|
|
113
|
+
if (valid) aliases.set(idKey(client, clock), { client: value.sourceClient, clock: value.sourceClock });
|
|
62
114
|
else aliases.delete(idKey(client, clock));
|
|
115
|
+
markAlias(client, clock, valid);
|
|
63
116
|
return;
|
|
64
117
|
}
|
|
65
118
|
if (kind === "boundary") {
|
|
@@ -77,12 +130,17 @@ export function createOriginIndex(origins) {
|
|
|
77
130
|
if (list.length) spans.set(client, list);
|
|
78
131
|
else spans.delete(client);
|
|
79
132
|
};
|
|
80
|
-
for (const
|
|
133
|
+
for (const key of origins._map.keys()) apply(key, origins.get(key));
|
|
81
134
|
const observer = (event) => { for (const key of event.keysChanged) apply(key, origins.get(key)); };
|
|
82
135
|
origins.observe(observer);
|
|
83
136
|
|
|
137
|
+
const hasAlias = (client, clock) => {
|
|
138
|
+
const list = aliasClocks.get(client);
|
|
139
|
+
return list !== undefined && list[lowerClock(list, clock)] === clock;
|
|
140
|
+
};
|
|
141
|
+
|
|
84
142
|
const resolve = (id) => {
|
|
85
|
-
const alias = aliases.get(idKey(id.client, id.clock));
|
|
143
|
+
const alias = hasAlias(id.client, id.clock) ? aliases.get(idKey(id.client, id.clock)) : undefined;
|
|
86
144
|
if (alias) return { client: alias.client, clock: alias.clock };
|
|
87
145
|
const list = spans.get(id.client);
|
|
88
146
|
if (!list) return { client: id.client, clock: id.clock };
|
|
@@ -95,8 +153,45 @@ export function createOriginIndex(origins) {
|
|
|
95
153
|
return { client: record.sourceClient, clock: record.sourceClock + offset };
|
|
96
154
|
};
|
|
97
155
|
|
|
156
|
+
const resolveRange = (client, clock, length) => {
|
|
157
|
+
const out = [];
|
|
158
|
+
const push = (sourceClient, sourceClock, size) => {
|
|
159
|
+
const last = out[out.length - 1];
|
|
160
|
+
if (last && last.client === sourceClient && last.clock + last.length === sourceClock) last.length += size;
|
|
161
|
+
else out.push({ client: sourceClient, clock: sourceClock, length: size });
|
|
162
|
+
};
|
|
163
|
+
const list = spans.get(client) ?? [];
|
|
164
|
+
const marks = aliasClocks.get(client) ?? [];
|
|
165
|
+
const end = clock + length;
|
|
166
|
+
let s = lower(list, clock + 1) - 1, a = lowerClock(marks, clock), at = clock;
|
|
167
|
+
while (at < end) {
|
|
168
|
+
if (a < marks.length && marks[a] === at) {
|
|
169
|
+
const target = aliases.get(idKey(client, at));
|
|
170
|
+
push(target.client, target.clock, 1);
|
|
171
|
+
at += 1;
|
|
172
|
+
a += 1;
|
|
173
|
+
} else {
|
|
174
|
+
let stop = end;
|
|
175
|
+
if (a < marks.length && marks[a] < stop) stop = marks[a];
|
|
176
|
+
if (s + 1 < list.length && list[s + 1].clock < stop) stop = list[s + 1].clock;
|
|
177
|
+
const span = s >= 0 ? list[s] : null;
|
|
178
|
+
if (span && at < span.clock + span.length) {
|
|
179
|
+
stop = Math.min(stop, span.clock + span.length);
|
|
180
|
+
push(span.sourceClient, span.sourceClock + at - span.clock, stop - at);
|
|
181
|
+
} else push(client, at, stop - at);
|
|
182
|
+
at = stop;
|
|
183
|
+
}
|
|
184
|
+
while (s + 1 < list.length && list[s + 1].clock <= at) s += 1;
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
};
|
|
188
|
+
|
|
98
189
|
const boundaryOf = (id) => {
|
|
99
190
|
if (!id || !isClock(id.client) || !isClock(id.clock)) return null;
|
|
191
|
+
if (!hasAlias(id.client, id.clock)) {
|
|
192
|
+
const record = boundaries.get(idKey(id.client, id.clock));
|
|
193
|
+
return record ? { ...record } : null;
|
|
194
|
+
}
|
|
100
195
|
let key = idKey(id.client, id.clock);
|
|
101
196
|
const seen = new Set();
|
|
102
197
|
while (!seen.has(key)) {
|
|
@@ -164,8 +259,15 @@ export function createOriginIndex(origins) {
|
|
|
164
259
|
if (!previous || !sameBoundary(previous, record)) throw conflicting(key);
|
|
165
260
|
};
|
|
166
261
|
|
|
262
|
+
// null while a transaction that wrote origin records has not settled: the observer has
|
|
263
|
+
// not seen those keys yet, and walkFlow then reads the map's keys itself.
|
|
264
|
+
const markers = () => {
|
|
265
|
+
for (const t of origins.doc?._transactionCleanups ?? []) if (t.changed.has(origins)) return null;
|
|
266
|
+
return { records: markerRecords, clocks: markerClocks };
|
|
267
|
+
};
|
|
268
|
+
|
|
167
269
|
return {
|
|
168
|
-
origins, resolve, recordSpans, recordAlias, recordBoundary, boundaryOf,
|
|
270
|
+
origins, resolve, resolveRange, recordSpans, recordAlias, recordBoundary, boundaryOf, markers,
|
|
169
271
|
get size() { return origins.size; },
|
|
170
272
|
dispose() { origins.unobserve(observer); },
|
|
171
273
|
};
|
package/src/page/ybind/parts.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
export const PART_KEY = /^(.+)\.(\d+)\.(\d+)$/;
|
|
2
|
-
|
|
2
|
+
// A regexp group per quad keeps backtrack state per quad and overflows on a page of a few MB.
|
|
3
|
+
const NOT_BASE64 = /[^A-Za-z0-9+/]/;
|
|
4
|
+
export function isBase64(text) {
|
|
5
|
+
const pad = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
|
|
6
|
+
const body = text.length - pad;
|
|
7
|
+
if (NOT_BASE64.test(pad ? text.slice(0, body) : text)) return false;
|
|
8
|
+
return pad ? body % 4 + pad === 4 : body % 4 !== 1;
|
|
9
|
+
}
|
|
3
10
|
const PART_CHARS = /^[A-Za-z0-9+/=\t\n\f\r ]*$/;
|
|
4
11
|
|
|
5
12
|
export function entriesOf(y) {
|
|
@@ -2,8 +2,9 @@ import * as Y from "../../vendor/yjs.vendor.js";
|
|
|
2
2
|
import { toBase64, fromBase64 } from "../../vendor/yjs.vendor.js";
|
|
3
3
|
import { NET } from "./layout.js";
|
|
4
4
|
import { ORIGINS_ORIGIN } from "./origins.js";
|
|
5
|
-
import { PART_KEY,
|
|
5
|
+
import { PART_KEY, isBase64, entriesOf, captureYValue } from "./parts.js";
|
|
6
6
|
import { containsUpdate } from "./recovery-proof.js";
|
|
7
|
+
import { rangesOf, unite, within, docRanges } from "./containment.js";
|
|
7
8
|
|
|
8
9
|
export { entriesOf } from "./parts.js";
|
|
9
10
|
|
|
@@ -11,6 +12,10 @@ export const enc = (u) => toBase64(u);
|
|
|
11
12
|
export const dec = (s) => fromBase64(s);
|
|
12
13
|
|
|
13
14
|
export const PART = 12 * 1024;
|
|
15
|
+
|
|
16
|
+
// The origin a replayed journal is applied through. It is offered to the room like any
|
|
17
|
+
// other local work and is never journaled again: a recovery is not this tab's typing.
|
|
18
|
+
export const RECOVERY_ORIGIN = "page-recovery";
|
|
14
19
|
const baseOf = (k) => PART_KEY.exec(k)?.[1] ?? k;
|
|
15
20
|
|
|
16
21
|
export function yValueOf(ydoc) {
|
|
@@ -59,7 +64,7 @@ export function docFromValue(value, { strict = false } = {}) {
|
|
|
59
64
|
try {
|
|
60
65
|
for (const [key, value] of entries) {
|
|
61
66
|
const text = strict ? value.replace(/[\t\n\f\r ]/g, "") : value;
|
|
62
|
-
if (strict && !
|
|
67
|
+
if (strict && !isBase64(text)) throw new YDecodeError("invalid-update", `Invalid base64 in Yjs update: ${key}`);
|
|
63
68
|
Y.applyUpdateV2(d, dec(text), NET);
|
|
64
69
|
}
|
|
65
70
|
if (strict && (d.store.pendingStructs || d.store.pendingDs)) {
|
|
@@ -80,7 +85,7 @@ const utf8Bytes = (text) => (encoder ? encoder.encode(text).length : text.length
|
|
|
80
85
|
const sameDeletes = (a, b) => Y.equalDeleteSets(Y.createDeleteSetFromStructStore(a.store), Y.createDeleteSetFromStructStore(b.store));
|
|
81
86
|
const svEqual = (a, b) => { if (a.size !== b.size) return false; for (const [c, n] of a) if (b.get(c) !== n) return false; return true; };
|
|
82
87
|
|
|
83
|
-
export function createYProvider({ store, ydoc, compact: limits = {}, setTimer = setTimeout, clearTimer = clearTimeout, onStopped = () => {}, stats = { applied: 0, offered: 0, compactions: 0 } }) {
|
|
88
|
+
export function createYProvider({ store, ydoc, compact: limits = {}, setTimer = setTimeout, clearTimer = clearTimeout, onStopped = () => {}, stats = { applied: 0, offered: 0, compactions: 0 }, journal = null, onLocalUpdate = () => {} }) {
|
|
84
89
|
const initial = entriesOf(store.value.y);
|
|
85
90
|
const known = new Set();
|
|
86
91
|
// Set when this tab's page can no longer reach the room: "lineage" when the room holds
|
|
@@ -95,8 +100,14 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
95
100
|
let canCompact = !initial.some(([k]) => k[0] === "m");
|
|
96
101
|
let counter = 0;
|
|
97
102
|
const me = ydoc.clientID.toString(36);
|
|
98
|
-
const
|
|
103
|
+
const inst = Math.floor(Math.random() * 36 ** 6).toString(36);
|
|
104
|
+
let mode = store.room?.compaction?.v === 1 ? "host" : "legacy";
|
|
105
|
+
const nextKey = (p) => (mode === "host" ? `${p}${me}-${inst}-${(counter++).toString(36)}` : `${p}${me}-${(counter++).toString(36)}`);
|
|
99
106
|
let timer = null;
|
|
107
|
+
let outbox = [];
|
|
108
|
+
// A refusal took back writes of this tab's that later updates build on: until a reconcile
|
|
109
|
+
// puts them back, nothing newer goes out.
|
|
110
|
+
let refusedGap = false;
|
|
100
111
|
const generation = store.value.g;
|
|
101
112
|
// Keys this tab wrote itself: the updates and offers the room has not confirmed yet.
|
|
102
113
|
const own = new Set();
|
|
@@ -113,12 +124,50 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
113
124
|
const parts = partsOf(key, enc(bytes));
|
|
114
125
|
store.handle.update((data) => { for (const [k, v] of parts) data.y[k] = v; });
|
|
115
126
|
};
|
|
127
|
+
const putWhole = (key, bytes) => {
|
|
128
|
+
known.add(key);
|
|
129
|
+
own.add(key);
|
|
130
|
+
const text = enc(bytes);
|
|
131
|
+
store.handle.update((data) => { data.y[key] = text; });
|
|
132
|
+
};
|
|
133
|
+
const isOwnYOp = (op) => {
|
|
134
|
+
const path = op.path;
|
|
135
|
+
if (!Array.isArray(path) || path.length !== 2 || path[0] !== "y") return true;
|
|
136
|
+
return own.has(path[1]);
|
|
137
|
+
};
|
|
138
|
+
const mineInFlight = () => store.pending.some((op) => isOwnYOp(op));
|
|
139
|
+
// Host mode orders this tab's writes only while the store is in the room: at save
|
|
140
|
+
// speed nothing confirms a write until the save lands, so updates go out at once.
|
|
141
|
+
const hosted = () => mode === "host" && store.handle.status?.mode !== "file";
|
|
142
|
+
const flushOutbox = () => {
|
|
143
|
+
if (!outbox.length) return;
|
|
144
|
+
const update = outbox.length === 1 ? outbox[0] : Y.mergeUpdatesV2(outbox);
|
|
145
|
+
outbox = [];
|
|
146
|
+
putWhole(nextKey("u"), update);
|
|
147
|
+
};
|
|
148
|
+
const offer = () => {
|
|
149
|
+
if (!hosted() || stopped || mineInFlight()) return;
|
|
150
|
+
if (refusedGap) {
|
|
151
|
+
refusedGap = false;
|
|
152
|
+
if (reconcile()) return;
|
|
153
|
+
}
|
|
154
|
+
if (!outbox.length) return;
|
|
155
|
+
const update = outbox.length === 1 ? outbox[0] : Y.mergeUpdatesV2(outbox);
|
|
156
|
+
outbox = [];
|
|
157
|
+
putWhole(nextKey("u"), update);
|
|
158
|
+
};
|
|
116
159
|
|
|
117
160
|
for (const [k] of initial) known.add(k);
|
|
118
161
|
|
|
162
|
+
// Ahead of the stop guards, and of the room's own delivery: work this tab can no longer
|
|
163
|
+
// offer, and work a refused offer took back, is still work a crash must not lose.
|
|
119
164
|
const onUpdate = (u, origin) => {
|
|
165
|
+
if (origin !== NET && origin !== ORIGINS_ORIGIN) onLocalUpdate(u);
|
|
166
|
+
if (origin !== NET && origin !== ORIGINS_ORIGIN && origin !== RECOVERY_ORIGIN) journal?.local(u);
|
|
120
167
|
if (stopped || origin === NET || origin === ORIGINS_ORIGIN) return;
|
|
121
|
-
|
|
168
|
+
if (hosted()) { outbox.push(u); offer(); }
|
|
169
|
+
else if (mode === "host") { outbox.push(u); flushOutbox(); }
|
|
170
|
+
else put(nextKey("u"), u);
|
|
122
171
|
};
|
|
123
172
|
ydoc.on("updateV2", onUpdate);
|
|
124
173
|
|
|
@@ -127,7 +176,11 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
127
176
|
canCompact = true;
|
|
128
177
|
known.add(k);
|
|
129
178
|
stats.applied++;
|
|
130
|
-
|
|
179
|
+
const update = dec(v);
|
|
180
|
+
Y.applyUpdateV2(ydoc, update, NET);
|
|
181
|
+
// A received update can be the prerequisite of a later local insert, so the journal
|
|
182
|
+
// keeps it beside the work it owns, and never counts it as this tab's own.
|
|
183
|
+
journal?.dependency(update);
|
|
131
184
|
};
|
|
132
185
|
|
|
133
186
|
let wantReconcile = false;
|
|
@@ -140,15 +193,24 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
140
193
|
timer = null;
|
|
141
194
|
const full = wantReconcile;
|
|
142
195
|
if (full) reconcile();
|
|
143
|
-
maybeCompact();
|
|
196
|
+
if (mode === "legacy") maybeCompact();
|
|
144
197
|
}, 0);
|
|
145
198
|
};
|
|
146
199
|
|
|
147
|
-
const settle = () => { reconcile(); maybeCompact(); };
|
|
200
|
+
const settle = () => { reconcile(); if (mode === "legacy") maybeCompact(); };
|
|
201
|
+
|
|
202
|
+
// The room's entries already hold every item and deletion of this tab's document, read
|
|
203
|
+
// from each entry's cached ranges: nothing to offer, and no copy of the room is built.
|
|
204
|
+
const roomHolds = (y) => {
|
|
205
|
+
const mine = docRanges(ydoc);
|
|
206
|
+
return mine.complete && within(unite(entriesOf(y ?? {}).map(([k, v]) => rangesFor(k, v))), mine);
|
|
207
|
+
};
|
|
148
208
|
|
|
149
209
|
function reconcile() {
|
|
150
210
|
if (stopped) return false;
|
|
211
|
+
if (hosted() && mineInFlight()) return false;
|
|
151
212
|
const y = store.value.y;
|
|
213
|
+
if (roomHolds(y)) { wantReconcile = false; reconcilePending = false; return false; }
|
|
152
214
|
const scratch = new Y.Doc();
|
|
153
215
|
for (const [, v] of entriesOf(y)) Y.applyUpdateV2(scratch, dec(v), NET);
|
|
154
216
|
const sv = Y.encodeStateVector(scratch);
|
|
@@ -158,7 +220,8 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
158
220
|
const diff = Y.encodeStateAsUpdateV2(ydoc, sv);
|
|
159
221
|
scratch.destroy();
|
|
160
222
|
stats.offered++;
|
|
161
|
-
|
|
223
|
+
if (mode === "host") { outbox = []; putWhole(nextKey("r"), diff); }
|
|
224
|
+
else put(nextKey("r"), diff);
|
|
162
225
|
wantReconcile = false;
|
|
163
226
|
reconcilePending = false;
|
|
164
227
|
return true;
|
|
@@ -171,24 +234,38 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
171
234
|
let quietUntil = 0;
|
|
172
235
|
const COVER_MAX = 256;
|
|
173
236
|
const coverCache = new Map();
|
|
237
|
+
const META_MAX = 256;
|
|
238
|
+
const metas = new Map();
|
|
174
239
|
const drop = (keys) => store.handle.update((data) => { for (const k of keys) delete data.y[k]; });
|
|
175
240
|
const keysOf = (auth, bases) => Object.keys(auth).filter((k) => bases.has(baseOf(k)));
|
|
176
241
|
// A key is retired only while what this tab shows under it is still what the proof was
|
|
177
242
|
// made over: a late write under the same key keeps its own entry.
|
|
178
243
|
const retire = (auth, keys) => { const visible = store.value.y ?? {}; drop(keys.filter((k) => k in visible && visible[k] === auth[k])); };
|
|
179
244
|
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
245
|
+
// The items and deletions one entry holds, read once per value.
|
|
246
|
+
const rangesFor = (key, value) => {
|
|
247
|
+
const meta = metas.get(key);
|
|
248
|
+
if (meta && meta.value === value) return meta.ranges;
|
|
249
|
+
if (metas.size >= META_MAX) metas.delete(metas.keys().next().value);
|
|
250
|
+
const ranges = rangesOf(dec(value));
|
|
251
|
+
metas.set(key, { value, ranges });
|
|
252
|
+
return ranges;
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// `known` and the entry memo keep live references only: the authoritative entries, by
|
|
256
|
+
// their whole keys, this tab's own unconfirmed writes and the fold in progress. Pruned
|
|
257
|
+
// after a rebase, a fold and a refusal. A key that is forgotten can be decoded again;
|
|
258
|
+
// Yjs is idempotent.
|
|
183
259
|
function prune() {
|
|
184
260
|
const auth = store.authoritative.y ?? {};
|
|
185
261
|
const visible = store.value.y ?? {};
|
|
186
262
|
for (const k of own) if (!(k in visible)) own.delete(k);
|
|
187
|
-
const held = fold ? [...fold.parts.map(([k]) => k), ...fold.inputs.keys()] : [];
|
|
188
|
-
const retained = new Set([...Object.keys(auth), ...own, ...held]);
|
|
189
|
-
for (const key of known) if (!retained.has(key)) known.delete(key);
|
|
190
263
|
const values = new Map(entriesOf(auth));
|
|
264
|
+
const held = fold ? [fold.key, ...fold.parts.map(([k]) => k), ...fold.inputs.keys()] : [];
|
|
265
|
+
const retained = new Set([...Object.keys(auth), ...values.keys(), ...own, ...held]);
|
|
266
|
+
for (const key of known) if (!retained.has(key)) known.delete(key);
|
|
191
267
|
for (const [key, proof] of coverCache) if (!retained.has(key) || values.get(key) !== proof.value) coverCache.delete(key);
|
|
268
|
+
for (const [key, meta] of metas) if (!retained.has(key) || (values.has(key) && values.get(key) !== meta.value)) metas.delete(key);
|
|
192
269
|
}
|
|
193
270
|
|
|
194
271
|
// Whether a fold's merged bytes already carry this input: merging it in again changes
|
|
@@ -229,7 +306,15 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
229
306
|
prune();
|
|
230
307
|
return true;
|
|
231
308
|
}
|
|
232
|
-
if (store.pending.length
|
|
309
|
+
if (store.pending.length) return false;
|
|
310
|
+
if (!stillHeld(auth, active) || !coversInputs(active)) {
|
|
311
|
+
// A fold that can never speak for its inputs is re-planned, never held.
|
|
312
|
+
fold = null;
|
|
313
|
+
if (arrived.length) retire(auth, arrived.map(([k]) => k));
|
|
314
|
+
quietUntil = entriesOf(auth).length + compact.keys;
|
|
315
|
+
prune();
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
233
318
|
fold = null;
|
|
234
319
|
retire(auth, keysOf(auth, active.inputs));
|
|
235
320
|
prune();
|
|
@@ -295,7 +380,7 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
295
380
|
// with this tab's document is a page rebuilt from another article while the tab was away.
|
|
296
381
|
const clientsOf = (y) => {
|
|
297
382
|
const clients = new Set();
|
|
298
|
-
for (const [, v] of entriesOf(y ?? {})) for (const c of
|
|
383
|
+
for (const [k, v] of entriesOf(y ?? {})) for (const [c, spans] of rangesFor(k, v).items) if (spans[0][0] === 0) clients.add(c);
|
|
299
384
|
return clients;
|
|
300
385
|
};
|
|
301
386
|
// A page whose article has no blocks seeds with no client at all; it still shares its
|
|
@@ -310,6 +395,18 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
310
395
|
|
|
311
396
|
const off = store.onRebase((ev) => {
|
|
312
397
|
if (stopped) return;
|
|
398
|
+
if (ev.room) {
|
|
399
|
+
const was = mode;
|
|
400
|
+
mode = ev.room.compaction?.v === 1 ? "host" : "legacy";
|
|
401
|
+
// A room that stopped compacting confirms nothing in host order: what host mode
|
|
402
|
+
// was holding back goes out as ordinary updates.
|
|
403
|
+
if (was === "host" && mode === "legacy" && outbox.length) setTimer(() => {
|
|
404
|
+
if (stopped || mode !== "legacy" || !outbox.length) return;
|
|
405
|
+
const update = outbox.length === 1 ? outbox[0] : Y.mergeUpdatesV2(outbox);
|
|
406
|
+
outbox = [];
|
|
407
|
+
put(nextKey("u"), update);
|
|
408
|
+
}, 0);
|
|
409
|
+
}
|
|
313
410
|
const ops = ev.source === "remote" && ev.frame ? ev.frame.ops : null;
|
|
314
411
|
const plain = ops && ops.every((op) => op.path[0] === "y" && op.path.length === 2 && !PART_KEY.test(op.path[1]));
|
|
315
412
|
if (!plain && ev.cause !== "refused" && !sharesLineage(ev.next.y)) {
|
|
@@ -323,6 +420,7 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
323
420
|
}
|
|
324
421
|
prune();
|
|
325
422
|
schedule(ev.source !== "remote" || !ops, ev.source !== "remote");
|
|
423
|
+
offer();
|
|
326
424
|
});
|
|
327
425
|
|
|
328
426
|
// A page update the room refused as too large is never offered again: the difference would
|
|
@@ -331,6 +429,12 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
331
429
|
// hold whole, and the room's page stays one a checkpoint can render.
|
|
332
430
|
const offDelivery = store.onDelivery((event) => {
|
|
333
431
|
if (event.phase !== "refused") return;
|
|
432
|
+
if (mode === "host") {
|
|
433
|
+
if (event.code === "page-full" || event.code === "compaction-failed") { stop(event.code); return; }
|
|
434
|
+
refusedGap = true;
|
|
435
|
+
schedule(true, false);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
334
438
|
if (event.code !== "too-large" || stopped) { prune(); return; }
|
|
335
439
|
stop("too-large");
|
|
336
440
|
const auth = store.authoritative.y ?? {};
|
|
@@ -341,7 +445,10 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
341
445
|
prune();
|
|
342
446
|
});
|
|
343
447
|
|
|
448
|
+
const offConfirm = store.onConfirm(() => { offer(); });
|
|
449
|
+
|
|
344
450
|
return { known, stats, settle, reconcile, get reconcilePending() { return reconcilePending; }, get stopped() { return stopped; },
|
|
451
|
+
get pending() { return outbox.length > 0; },
|
|
345
452
|
// Test-only snapshot of the counters and cache sizes; no mutable set leaves the provider.
|
|
346
|
-
_diagnostics: () => ({ known: known.size, cover: coverCache.size, fold: fold ? fold.parts.length : 0, generation }), compact: maybeCompact, dispose() { off(); offDelivery(); ydoc.off("updateV2", onUpdate); if (timer) clearTimer(timer); } };
|
|
453
|
+
_diagnostics: () => ({ known: known.size, cover: coverCache.size, fold: fold ? fold.parts.length : 0, generation }), compact: maybeCompact, dispose() { off(); offDelivery(); offConfirm(); ydoc.off("updateV2", onUpdate); if (timer) clearTimer(timer); } };
|
|
347
454
|
}
|
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* state and the receipt arrive in the proof itself.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { encodeStateAsUpdateV2
|
|
17
|
+
import { encodeStateAsUpdateV2 } from "../../vendor/yjs.vendor.js";
|
|
18
18
|
import { docFromValue } from "./provider.js";
|
|
19
|
+
import { indexOf, indexContains } from "./containment.js";
|
|
19
20
|
|
|
20
21
|
export class PageProofError extends Error {
|
|
21
22
|
constructor(code, message) {
|
|
@@ -40,17 +41,19 @@ const copyState = (state) => ({ v: state.v, g: state.g, y: { ...(state.y ?? {})
|
|
|
40
41
|
export const equalBytes = (a, b) => a.length === b.length && a.every((value, index) => value === b[index]);
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
|
-
* Whether
|
|
44
|
+
* Whether applying `update` on top of `checkpoint` adds nothing: no insertion, no
|
|
45
|
+
* deletion. Compared by structure (the items and deletions the checkpoint holds), since
|
|
46
|
+
* merged bytes are not stable across merge orders. An update whose dependencies the
|
|
47
|
+
* checkpoint lacks is not contained: Yjs would hold it pending rather than refuse it. The
|
|
48
|
+
* checkpoint is integrated once for as long as its bytes are held, and each update after
|
|
49
|
+
* it is read, not applied.
|
|
44
50
|
*
|
|
45
51
|
* @param {Uint8Array} checkpoint - canonical or merged Yjs V2 update bytes
|
|
46
52
|
* @param {Uint8Array} update - the bytes to look for
|
|
47
|
-
* @param {Object} [Yjs] - the vendored Yjs module
|
|
48
53
|
* @returns {boolean}
|
|
49
54
|
*/
|
|
50
|
-
export function containsUpdate(checkpoint, update
|
|
51
|
-
|
|
52
|
-
const canonical = merge([checkpoint]);
|
|
53
|
-
return equalBytes(canonical, merge([canonical, update]));
|
|
55
|
+
export function containsUpdate(checkpoint, update) {
|
|
56
|
+
return indexContains(indexOf(checkpoint), update);
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
export async function digestHex(bytes) {
|
|
@@ -58,27 +61,47 @@ export async function digestHex(bytes) {
|
|
|
58
61
|
return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
// The last few states decoded, by their entries: a proof, its validation, the file debt
|
|
65
|
+
// and the journal's rebase all ask for the same state's bytes after one accepted save.
|
|
66
|
+
const CANONICAL_MAX = 4;
|
|
67
|
+
const canonical = [];
|
|
68
|
+
const sameState = (memo, state) => {
|
|
69
|
+
if (memo.v !== state.v || memo.g !== state.g) return false;
|
|
70
|
+
const keys = Object.keys(state.y ?? {});
|
|
71
|
+
if (keys.length !== memo.y.size) return false;
|
|
72
|
+
for (const key of keys) if (memo.y.get(key) !== state.y[key]) return false;
|
|
73
|
+
return true;
|
|
74
|
+
};
|
|
75
|
+
|
|
61
76
|
/**
|
|
62
77
|
* The accepted page state as one canonical update, decoded strictly.
|
|
63
78
|
*
|
|
64
79
|
* Pending structs, missing part and a delete set naming an unknown item all refuse:
|
|
65
|
-
* a checkpoint nobody can decode proves nothing about a journaled record.
|
|
80
|
+
* a checkpoint nobody can decode proves nothing about a journaled record. The same state
|
|
81
|
+
* answers with the same bytes, which no caller may change.
|
|
66
82
|
*
|
|
67
83
|
* @param {Object} state - a version 1 page value
|
|
68
84
|
* @returns {Uint8Array}
|
|
69
85
|
*/
|
|
70
86
|
export function canonicalPageBytes(state) {
|
|
87
|
+
const hit = state && typeof state === "object" && state.y && typeof state.y === "object" ? canonical.find((memo) => sameState(memo, state)) : null;
|
|
88
|
+
if (hit) return hit.bytes;
|
|
71
89
|
let doc;
|
|
72
90
|
try {
|
|
73
91
|
doc = docFromValue(state, { strict: true });
|
|
74
92
|
} catch (error) {
|
|
75
93
|
fail("invalid-state", `The accepted page state does not decode: ${error.message}`);
|
|
76
94
|
}
|
|
95
|
+
let bytes;
|
|
77
96
|
try {
|
|
78
|
-
|
|
97
|
+
bytes = encodeStateAsUpdateV2(doc);
|
|
98
|
+
indexOf(bytes, doc);
|
|
79
99
|
} finally {
|
|
80
100
|
doc.destroy();
|
|
81
101
|
}
|
|
102
|
+
if (canonical.length >= CANONICAL_MAX) canonical.shift();
|
|
103
|
+
canonical.push({ v: state.v, g: state.g, y: new Map(Object.entries(state.y)), bytes });
|
|
104
|
+
return bytes;
|
|
82
105
|
}
|
|
83
106
|
|
|
84
107
|
// The same answer `sameAuthority` gives in recovery/identity.js, written here because the
|