@panphora/clayjs 1.15.2 → 1.17.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/README.md +4 -5
- package/THIRD-PARTY-NOTICES.md +16 -0
- package/compat/highlight-11.9.0.min.js +1213 -0
- package/compat/highlight-LICENSE +29 -0
- package/compat/journal-assets.lock.json +22 -0
- package/compat/overtype-2.6.1.min.js +116 -0
- package/compat/overtype-LICENSE +21 -0
- package/dist/all.js +90 -0
- package/dist/clay-dom.js +84 -0
- package/dist/clay-events.js +103 -0
- package/dist/clay-internals.js +85 -0
- package/dist/clay-options.js +93 -0
- package/dist/clay-router.js +98 -0
- package/dist/clay-ui.js +95 -0
- package/dist/clay-utils.js +84 -0
- package/dist/clay.js +146 -0
- package/dist/clay.standalone.js +84291 -57611
- package/dist/m/attrs/add-from-T5LJXFFI.js +1 -0
- package/dist/m/attrs/onaftersave-OKSWEJQF.js +1 -0
- package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
- package/dist/m/attrs/save-freeze-MYNVXREN.js +1 -0
- package/dist/m/c-2AZDZWCK.js +1 -0
- package/dist/m/c-2SPVQSHC.js +1 -0
- package/dist/m/c-2YYDMLNZ.js +1 -0
- package/dist/m/c-336SGU5W.js +1 -0
- package/dist/m/c-33JJ6CBX.js +2 -0
- package/dist/m/c-3GJ4SDE2.js +1 -0
- package/dist/m/c-3H6BZDNJ.js +6 -0
- package/dist/m/c-3OWDMNJR.js +2 -0
- package/dist/m/c-3RUU4TPX.js +91 -0
- package/dist/m/c-3XIRD4NU.js +1 -0
- package/dist/m/c-46XYM55L.js +1 -0
- package/dist/m/c-4DFEJ2PO.js +3 -0
- package/dist/m/c-4JZDZFF6.js +1 -0
- package/dist/m/c-4L3BTFRK.js +1 -0
- package/dist/m/c-4Q2XTQ2N.js +2 -0
- package/dist/m/c-4WKFKEKF.js +1 -0
- package/dist/m/c-56QRZTZH.js +1 -0
- package/dist/m/c-5J4E7O4Y.js +4 -0
- package/dist/m/c-5LKDWQTL.js +19 -0
- package/dist/m/c-5QWQ7A5L.js +1 -0
- package/dist/m/c-5RJFTLSF.js +1 -0
- package/dist/m/c-5V3MOVJT.js +1 -0
- package/dist/m/c-5VYOWDTI.js +1 -0
- package/dist/m/c-5YIHJ3TJ.js +1 -0
- package/dist/m/c-6AZOGAPE.js +1 -0
- package/dist/m/c-6CRUUXOX.js +5 -0
- package/dist/m/c-6HMB24EV.js +1 -0
- package/dist/m/c-6KEVEQ7L.js +1 -0
- package/dist/m/c-6NC5I2TV.js +1 -0
- package/dist/m/c-74MGYWZF.js +1 -0
- package/dist/m/c-7DJDKPGL.js +1 -0
- package/dist/m/c-7OLLJPWP.js +1 -0
- package/dist/m/c-7SWMYKAI.js +1 -0
- package/dist/m/c-A3E3NIMC.js +1 -0
- package/dist/m/c-A7LS5UZW.js +1 -0
- package/dist/m/c-AMSNPOCX.js +1 -0
- package/dist/m/c-AQLFEATB.js +1 -0
- package/dist/m/c-B3JM5ZXC.js +1 -0
- package/dist/m/c-BBVGTQG7.js +1 -0
- package/dist/m/c-BPF2NSPV.js +1 -0
- package/dist/m/c-BQ6LOBEE.js +1 -0
- package/dist/m/c-BTDI4Y23.js +1 -0
- package/dist/m/c-BWFVSBDF.js +1 -0
- package/dist/m/c-BYTKL6UX.js +1 -0
- package/dist/m/c-CFHW5AMY.js +6 -0
- package/dist/m/c-CJ3A7ZKE.js +1 -0
- package/dist/m/c-CQHPGKX2.js +1 -0
- package/dist/m/c-CYWLPPUS.js +1 -0
- package/dist/m/c-CZUSOB7V.js +1 -0
- package/dist/m/c-DCFSEJZG.js +1 -0
- package/dist/m/c-DCXOXBF3.js +1 -0
- package/dist/m/c-DFS4XZJV.js +2 -0
- package/dist/m/c-DNENWY4O.js +1 -0
- package/dist/m/c-DNZ5IFM6.js +1 -0
- package/dist/m/c-E4LIR5X5.js +1 -0
- package/dist/m/c-EJTGYSLN.js +1 -0
- package/dist/m/c-EVCQ3BKA.js +1 -0
- package/dist/m/c-EZU56NQV.js +34 -0
- package/dist/m/c-FDNHHZY5.js +1 -0
- package/dist/m/c-FILLTJA7.js +1 -0
- package/dist/m/c-FRLHSY7E.js +1 -0
- package/dist/m/c-FROJUZMV.js +1 -0
- package/dist/m/c-G2JPKCTP.js +1 -0
- package/dist/m/c-GB6TU7WV.js +1 -0
- package/dist/m/c-GFJJEBTU.js +1 -0
- package/dist/m/c-GJAEGGXY.js +1 -0
- package/dist/m/c-GOWBDM6J.js +1 -0
- package/dist/m/c-GQ6F6IDK.js +1 -0
- package/dist/m/c-GSFIGKOP.js +1 -0
- package/dist/m/c-GY422N3Q.js +1 -0
- package/dist/m/c-H4LG442Q.js +1 -0
- package/dist/m/c-HA6INT6J.js +1 -0
- package/dist/m/c-HAWFNBYG.js +1 -0
- package/dist/m/c-HDDEEL77.js +1 -0
- package/dist/m/c-HFKUVACH.js +1 -0
- package/dist/m/c-HHDFCNHV.js +1 -0
- package/dist/m/c-HPURYVZS.js +2 -0
- package/dist/m/c-I2ZATQCD.js +2 -0
- package/dist/m/c-IEM4NTCS.js +1 -0
- package/dist/m/c-IJUK2GPW.js +1 -0
- package/dist/m/c-IW6GGOU2.js +1 -0
- package/dist/m/c-JAX2667S.js +6 -0
- package/dist/m/c-JDCX26UP.js +25 -0
- package/dist/m/c-JRKY44DK.js +1 -0
- package/dist/m/c-JU3NK25H.js +1 -0
- package/dist/m/c-KAU6E43D.js +1 -0
- package/dist/m/c-KBS3UV6B.js +1 -0
- package/dist/m/c-KGVGROZT.js +1 -0
- package/dist/m/c-KI7M4AY3.js +1 -0
- package/dist/m/c-KIPQJX4B.js +1 -0
- package/dist/m/c-KQ6TYJAP.js +135 -0
- package/dist/m/c-KU3ZFADW.js +1 -0
- package/dist/m/c-L4WQSC27.js +1 -0
- package/dist/m/c-LFPHCXY3.js +1 -0
- package/dist/m/c-LRPUO2MX.js +1 -0
- package/dist/m/c-LWLBHLVC.js +3 -0
- package/dist/m/c-LZJPNR2W.js +1 -0
- package/dist/m/c-M2L47S2X.js +7 -0
- package/dist/m/c-MFP2ZWD4.js +1 -0
- package/dist/m/c-MKSQ4Y57.js +1 -0
- package/dist/m/c-MTQHWQC3.js +1 -0
- package/dist/m/c-N326R2JD.js +1 -0
- package/dist/m/c-N7JPUPGC.js +1 -0
- package/dist/m/c-NRPJEVJS.js +1 -0
- package/dist/m/c-NUFFJVVJ.js +1 -0
- package/dist/m/c-NUHP2XDB.js +1 -0
- package/dist/m/c-NUHSPT5P.js +1 -0
- package/dist/m/c-O72HVYSA.js +1 -0
- package/dist/m/c-OCSZ4QGN.js +1 -0
- package/dist/m/c-OIBUTDXI.js +1 -0
- package/dist/m/c-OIDRZKAF.js +1 -0
- package/dist/m/c-OW5QIGG3.js +1 -0
- package/dist/m/c-P7KNDG34.js +1 -0
- package/dist/m/c-PGRCK73F.js +1 -0
- package/dist/m/c-PQ5LK6QE.js +993 -0
- package/dist/m/c-PS5ARVWR.js +9 -0
- package/dist/m/c-PZ4XEPUP.js +2 -0
- package/dist/m/c-Q622NS2Y.js +1 -0
- package/dist/m/c-Q7NQD65V.js +1 -0
- package/dist/m/c-QH63LQOS.js +1 -0
- package/dist/m/c-QHIWXHRO.js +1 -0
- package/dist/m/c-QHWJ5ZIJ.js +1 -0
- package/dist/m/c-QICP2YR5.js +1 -0
- package/dist/m/c-QJ4OM3G6.js +1 -0
- package/dist/m/c-QNKREOL6.js +1 -0
- package/dist/m/c-R3L7ZJGB.js +2 -0
- package/dist/m/c-R7IWVINQ.js +1 -0
- package/dist/m/c-RM7J7BV2.js +1 -0
- package/dist/m/c-RSMOODDU.js +1 -0
- package/dist/m/c-RVMWGXEJ.js +1 -0
- package/dist/m/c-RYUXUUBX.js +1 -0
- package/dist/m/c-S4ZEH46B.js +9 -0
- package/dist/m/c-S7H2DRCB.js +1 -0
- package/dist/m/c-S7LK4KUE.js +1 -0
- package/dist/m/c-SDAXDM2D.js +1 -0
- package/dist/m/c-SGIA4IEQ.js +188 -0
- package/dist/m/c-SMBZG5X4.js +1 -0
- package/dist/m/c-SMGU7RT3.js +235 -0
- package/dist/m/c-SPIORRYS.js +1 -0
- package/dist/m/c-SWLRT66R.js +1 -0
- package/dist/m/c-SZGYW5HT.js +1 -0
- package/dist/m/c-TCCA5BZN.js +2 -0
- package/dist/m/c-TCJTVAFN.js +1 -0
- package/dist/m/c-TJ6WOJL3.js +67 -0
- package/dist/m/c-TQEMVJCS.js +1 -0
- package/dist/m/c-TRS6TM6O.js +1 -0
- package/dist/m/c-TYLXX4NF.js +21 -0
- package/dist/m/c-U62OB4BT.js +1 -0
- package/dist/m/c-U6YWUAR3.js +8 -0
- package/dist/m/c-UDCSU5G5.js +2 -0
- package/dist/m/c-UDUUJPFZ.js +1 -0
- package/dist/m/c-UGHH4MB3.js +1 -0
- package/dist/m/c-UL2YUY52.js +1 -0
- package/dist/m/c-VAK7SG37.js +1 -0
- package/dist/m/c-VDSS4K3Z.js +1 -0
- package/dist/m/c-VE6ZMYMC.js +1 -0
- package/dist/m/c-VG5XIN4T.js +1 -0
- package/dist/m/c-VHM5XQHK.js +1 -0
- package/dist/m/c-VHNSBD2M.js +1 -0
- package/dist/m/c-VI5RMSVY.js +1 -0
- package/dist/m/c-VKEDWYDF.js +1 -0
- package/dist/m/c-VRZN56XY.js +1 -0
- package/dist/m/c-W5MCHEGA.js +1 -0
- package/dist/m/c-WDC5GVKU.js +1 -0
- package/dist/m/c-WN5AY3OZ.js +1 -0
- package/dist/m/c-WNYBLIOZ.js +1 -0
- package/dist/m/c-WRBKEJD4.js +1 -0
- package/dist/m/c-WWXIDH4O.js +28 -0
- package/dist/m/c-WYFDLITL.js +1 -0
- package/dist/m/c-XCNTK76U.js +1 -0
- package/dist/m/c-XMS6DPBR.js +1 -0
- package/dist/m/c-XN6MBKI6.js +67 -0
- package/dist/m/c-XPELB6E5.js +1 -0
- package/dist/m/c-XQXZAO7N.js +1 -0
- package/dist/m/c-Y5HBBIQ4.js +1 -0
- package/dist/m/c-ZE46QDIH.js +1 -0
- package/dist/m/c-ZUCOKOHT.js +1 -0
- package/dist/m/c-ZVPVXTJF.js +4 -0
- package/dist/m/c-ZVRKXKM7.js +1 -0
- package/dist/m/c-ZWIOJGMT.js +1 -0
- package/dist/m/core/admin-attrs-QHOKPJ7W.js +1 -0
- package/dist/m/core/autosave-HRYGUWV6.js +1 -0
- package/dist/m/core/conflict-notice-6MROJN2L.js +1 -0
- package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
- package/dist/m/core/host-permission-FNXVEFNE.js +1 -0
- package/dist/m/core/is-edit-mode-KTWA3O5N.js +1 -0
- package/dist/m/core/persist-JFYRHMRJ.js +3 -0
- package/dist/m/core/recovery-FLTCC2OC.js +1 -0
- package/dist/m/core/save-HQZ4S667.js +1 -0
- package/dist/m/core/save-core-KGQWFUNH.js +1 -0
- package/dist/m/core/snapshot-EGQZNT45.js +1 -0
- package/dist/m/core/unsaved-warning-IDJLD66H.js +1 -0
- package/dist/m/dom/all-APS5ZSKP.js +1 -0
- package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
- package/dist/m/events/index-RN2JNGBV.js +1 -0
- package/dist/m/internals/index-PVXGPUDA.js +1 -0
- package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
- package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
- package/dist/m/loader-S4I7GKXR.js +1 -0
- package/dist/m/manifest-4C419D67.js +1 -0
- package/dist/m/manifest-edit-B077F288.js +1 -0
- package/dist/m/manifest.json +3988 -0
- package/dist/m/options/options-EJMS3K24.js +1 -0
- package/dist/m/page/editor-NNJA4OKD.js +202 -0
- package/dist/m/plugins/ai-edit-QN4PMMNL.js +6 -0
- package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
- package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
- package/dist/m/plugins/demo-RM5IR6AO.js +1 -0
- package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
- package/dist/m/plugins/indicator-URDBBOQO.js +1 -0
- package/dist/m/plugins/live-HFWD4OIH.js +1 -0
- package/dist/m/plugins/menu-3HIEQSI6.js +138 -0
- package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
- package/dist/m/plugins/page-JOKB6XPR.js +2 -0
- package/dist/m/plugins/page-view-NXOJHNW6.js +1 -0
- package/dist/m/plugins/people-SGPJKANK.js +1 -0
- package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
- package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
- package/dist/m/plugins/richclay-NRQDSWX6.js +517 -0
- package/dist/m/plugins/richclay-NRQDSWX6.js.LEGAL.txt +1 -0
- package/dist/m/plugins/sortable-GWT4PWDV.js +1 -0
- package/dist/m/plugins/source-4ZRSAIYM.js +1 -0
- package/dist/m/plugins/undo-VDA5P7FH.js +1 -0
- package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
- package/dist/m/plugins/upgrade-W7FG5VFG.js +1 -0
- package/dist/m/plugins/upload-WLYKK6TL.js +1 -0
- package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +21 -0
- package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +1 -0
- package/dist/m/plugins/wire-4N6GYAF6.js +1 -0
- package/dist/m/reader-core-DXYCURFF.js +68 -0
- package/dist/m/route/route-KOCZSEGN.js +1 -0
- package/dist/m/sync/live-sync-J7VMYTKS.js +1 -0
- package/dist/m/ui/index-3JLVI27O.js +1 -0
- package/dist/m/utils/index-4TJCVLT7.js +1 -0
- package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
- package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
- package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
- package/dist/offline-manifest.json +4034 -0
- package/dist/page/themes/light-1.css +430 -0
- package/dist/server/core.cjs +23748 -22855
- package/dist/server/offline-runtime.cjs +4954 -0
- package/dist/server/offline-runtime.mjs +4923 -0
- package/dist/server/rooms.mjs +9 -0
- package/docs/guide.md +1078 -669
- package/entries/all.js +2 -0
- package/entries/clay-data.js +1 -1
- package/entries/clay-dom.js +2 -0
- package/entries/clay-events.js +2 -0
- package/entries/clay-internals.js +2 -0
- package/entries/clay-options.js +2 -0
- package/entries/clay-router.js +2 -0
- package/entries/clay-ui.js +2 -0
- package/entries/clay-utils.js +2 -0
- package/entries/clay.js +2 -0
- package/entries/sap.js +6 -6
- package/fonts/ibm-plex-mono-LICENSE +93 -0
- package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-LICENSE +93 -0
- package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- package/fonts/ibm-plex.css +71 -0
- package/fonts/inter-LICENSE +93 -0
- package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
- package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-greek-wght-normal.woff2 +0 -0
- package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
- package/fonts/inter.css +62 -0
- package/fonts/newsreader-LICENSE +93 -0
- package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
- package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
- package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
- package/fonts/newsreader.css +26 -0
- package/fonts/work-sans-LICENSE +93 -0
- package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
- package/fonts/work-sans.css +8 -0
- package/package.json +20 -3
- package/packed-contract.json +1 -0
- package/src/attrs/add-from.js +27 -4
- package/src/attrs/save-freeze.js +7 -52
- package/src/core/admin-attrs.js +119 -12
- package/src/core/admin-contenteditable.js +12 -10
- package/src/core/admin-inputs.js +14 -15
- package/src/core/admin-onclick.js +2 -0
- package/src/core/autosave.js +74 -21
- package/src/core/boot-phase.js +2 -1
- package/src/core/conflict-download.js +19 -23
- package/src/core/conflict-notice-ui.js +1173 -0
- package/src/core/conflict-notice.js +140 -697
- package/src/core/download.js +32 -0
- package/src/core/edit-mode.js +147 -14
- package/src/core/etag.js +30 -12
- package/src/core/host-meta.js +90 -8
- package/src/core/host-permission.js +29 -0
- package/src/core/is-edit-mode.js +145 -12
- package/src/core/mode-key.js +125 -0
- package/src/core/mode-lifecycle.js +544 -0
- package/src/core/mode-receipt.js +30 -0
- package/src/core/mode-refusal.js +13 -0
- package/src/core/mode-resources.js +52 -0
- package/src/core/mode-support.js +82 -0
- package/src/core/mode-switch.js +259 -0
- package/src/core/mode-transition.js +112 -0
- package/src/core/page-data-write.js +305 -0
- package/src/core/page-data.js +26 -227
- package/src/core/page-format-wire.js +4 -0
- package/src/core/page-format.js +58 -0
- package/src/core/persist.js +37 -9
- package/src/core/preserve-authored.js +114 -0
- package/src/core/recovery.js +772 -0
- package/src/core/runtime-url.js +105 -0
- package/src/core/save-core.js +113 -12
- package/src/core/save-replace.js +72 -0
- package/src/core/save-size-notice.js +134 -0
- package/src/core/save-size.js +140 -0
- package/src/core/save.js +806 -119
- package/src/core/snapshot.js +159 -11
- package/src/core/source-map.js +157 -9
- package/src/core/unsaved-warning.js +14 -4
- package/src/core/upgrade-run.js +408 -0
- package/src/core/version.js +18 -0
- package/src/lib/assets.js +30 -0
- package/src/lib/autosave-debug-gate.js +50 -0
- package/src/lib/autosave-scheduler.js +140 -0
- package/src/lib/boot-imports.js +8 -0
- package/src/lib/derived.js +3 -1
- package/src/lib/dirty-gate.js +15 -0
- package/src/lib/doc-storage.js +33 -0
- package/src/lib/effect.js +9 -4
- package/src/lib/mutation.js +4 -2
- package/src/lib/offline-runtime.js +226 -0
- package/src/lib/preload.js +48 -0
- package/src/lib/root-attrs.js +1 -1
- package/src/lib/trash.js +18 -0
- package/src/lib/undo/capture-move.js +152 -0
- package/src/live/local-channel.js +61 -0
- package/src/live/local-lane.js +60 -9
- package/src/live/server-lane.js +6 -3
- package/src/loader-logic.js +76 -15
- package/src/loader.js +218 -30
- package/src/page/adopt-boot.js +76 -22
- package/src/page/adopt.js +25 -4
- package/src/page/awareness.js +230 -0
- package/src/page/blocks.js +113 -6
- package/src/page/blocksel.js +20 -4
- package/src/page/board-face.js +13 -0
- package/src/page/board-panel.js +5 -17
- package/src/page/board-style.js +2 -13
- package/src/page/board-view.js +1 -11
- package/src/page/board-views.js +3 -0
- package/src/page/board.js +2 -2
- package/src/page/boot-pack.js +26 -0
- package/src/page/boot.js +237 -0
- package/src/page/chart-tables.js +11 -0
- package/src/page/chart.js +6 -10
- package/src/page/checklist.js +1 -1
- package/src/page/checkpoint-proof.js +289 -0
- package/src/page/chrome.js +431 -63
- package/src/page/csv-import.js +17 -2
- package/src/page/document-title-dom.js +52 -0
- package/src/page/document-title-runtime.js +90 -0
- package/src/page/document-title.js +12 -0
- package/src/page/drag.js +48 -13
- package/src/page/drop.js +2 -1
- package/src/page/editor.js +399 -90
- package/src/page/emoji-data.js +162 -0
- package/src/page/feature-loader.js +72 -0
- package/src/page/features/board.js +21 -0
- package/src/page/features/chart.js +11 -0
- package/src/page/features/code.js +13 -0
- package/src/page/features/dates.js +180 -0
- package/src/page/features/emoji.js +187 -0
- package/src/page/features/media.js +290 -0
- package/src/page/features/mentions.js +188 -0
- package/src/page/features/registry.js +28 -0
- package/src/page/features/table.js +383 -0
- package/src/page/{tool-view.js → features/tool.js} +33 -2
- package/src/page/format-codec.js +215 -0
- package/src/page/format-command.js +9 -0
- package/src/page/format-gate.js +179 -0
- package/src/page/format-host.js +54 -0
- package/src/page/format-level.js +157 -0
- package/src/page/format-native-commands.js +142 -0
- package/src/page/format-native.js +42 -0
- package/src/page/highlight-slot.js +23 -0
- package/src/page/highlight.js +30 -16
- package/src/page/history-intent.js +78 -0
- package/src/page/inline.js +45 -28
- package/src/page/items.js +67 -14
- package/src/page/link.js +1 -0
- package/src/page/markdown.js +112 -3
- package/src/page/media-views.js +3 -0
- package/src/page/media.js +33 -245
- package/src/page/mention-picker.js +99 -0
- package/src/page/merge.js +1 -1
- package/src/page/outline-cache.js +11 -0
- package/src/page/placeholder.js +27 -7
- package/src/page/projection.js +240 -51
- package/src/page/reader-patch.js +306 -0
- package/src/page/recover.js +1 -1
- package/src/page/rich/page-tool.js +1 -4
- package/src/page/runtime-owner.js +27 -0
- package/src/page/save-projection.js +46 -4
- package/src/page/save-states.js +1 -1
- package/src/page/schema.js +47 -0
- package/src/page/table-rect-key.js +3 -0
- package/src/page/table-select.js +4 -3
- package/src/page/table-style.js +1 -1
- package/src/page/table-views.js +3 -0
- package/src/page/table.js +8 -293
- package/src/page/themes/content.css +25 -1
- package/src/page/themes/light-1.css +163 -2
- package/src/page/themes/light-1.source.css +269 -0
- package/src/page/tool-capability.js +3 -0
- package/src/page/tool-views.js +3 -0
- package/src/page/view-hooks.js +32 -0
- package/src/page/view.js +390 -107
- package/src/page/ybind/awareness.js +172 -0
- package/src/page/ybind/binding.js +40 -5
- package/src/page/ybind/bridge.js +50 -47
- package/src/page/ybind/decode.js +52 -19
- package/src/page/ybind/doc.js +27 -2
- package/src/page/ybind/group.js +12 -3
- package/src/page/ybind/index.js +2 -0
- package/src/page/ybind/lineage.js +71 -0
- package/src/page/ybind/marks.js +26 -1
- package/src/page/ybind/migrate.js +48 -13
- package/src/page/ybind/moves.js +32 -21
- package/src/page/ybind/positions.js +50 -0
- package/src/page/ybind/provider.js +97 -24
- package/src/page/ybind/recovery-proof.js +191 -0
- package/src/page/ybind/recovery-replay.js +488 -0
- package/src/page/ybind/tokens.js +15 -6
- package/src/page/ybind/walk.js +10 -3
- package/src/page/ybind/writer.js +48 -4
- package/src/plugins/ai-edit.js +167 -32
- package/src/plugins/bind-text.js +218 -0
- package/src/plugins/cms.js +93 -0
- package/src/plugins/indicator.js +57 -5
- package/src/plugins/live.js +319 -35
- package/src/plugins/menu/claygoo.svg.js +1 -0
- package/src/plugins/menu/seo-head.js +323 -0
- package/src/plugins/menu/seo.js +849 -0
- package/src/plugins/menu.js +425 -0
- package/src/plugins/page-view.js +131 -0
- package/src/plugins/page.js +1131 -270
- package/src/plugins/people.js +158 -31
- package/src/plugins/richclay.js +119 -9
- package/src/plugins/sortable.js +370 -60
- package/src/plugins/source.js +189 -366
- package/src/plugins/undo.js +289 -19
- package/src/plugins/unsent.js +132 -0
- package/src/plugins/upgrade-run.js +1 -0
- package/src/plugins/upgrade.js +210 -0
- package/src/plugins/upload-task.js +571 -0
- package/src/plugins/upload-ui.js +265 -0
- package/src/plugins/upload.js +34 -153
- package/src/plugins/visitor-drafts/inert.js +32 -0
- package/src/plugins/visitor-drafts/intro.js +2 -1
- package/src/plugins/visitor-drafts/owner-chip.js +3 -0
- package/src/plugins/visitor-drafts/prompt.js +2 -2
- package/src/plugins/visitor-drafts-owner.js +85 -0
- package/src/plugins/visitor-drafts.js +31 -42
- package/src/plugins/wire.js +82 -1
- package/src/reader-core.js +26 -0
- package/src/recovery/conflicts.js +287 -0
- package/src/recovery/identity.js +154 -0
- package/src/recovery/store.js +792 -0
- package/src/recovery/undo.js +257 -0
- package/src/server/checkpoint/index.js +68 -4
- package/src/server/offline-runtime/index.js +214 -0
- package/src/server/rooms/index.js +2 -1
- package/src/server/rooms/live-data-rooms.js +2 -2
- package/src/server/rooms/page-admission.js +33 -0
- package/src/server/rooms/page-lineage.js +56 -0
- package/src/server/rooms/page-rebase.js +11 -5
- package/src/server/rooms/page-state.js +2 -0
- package/src/server/rooms/page-write-admission.js +9 -3
- package/src/source/session.js +705 -0
- package/src/sync/conflict-revert.js +5 -4
- package/src/sync/conflicts-edit.js +119 -0
- package/src/sync/conflicts-facade.js +49 -0
- package/src/sync/conflicts.js +99 -1
- package/src/sync/editing-marker.js +37 -6
- package/src/sync/editing-sender.js +8 -1
- package/src/sync/live-sync-edit.js +63 -0
- package/src/sync/live-sync.js +364 -119
- package/src/sync/local-presence.js +187 -0
- package/src/sync/merge-declarations.js +10 -0
- package/src/sync/merge-engine.js +12 -0
- package/src/sync/presence-store.js +412 -0
- package/src/sync/presence-target.js +243 -0
- package/src/sync/presence.js +72 -41
- package/src/sync/saved-handoff.js +200 -0
- package/src/sync/saved-stream.js +544 -0
- package/src/sync/version-guard.js +92 -0
- package/src/sync/wire-profile.js +81 -0
- package/src/ui/bevel-controls.js +2 -8
- package/src/ui/page-scheme.js +7 -0
- package/src/ui/skins/cms.js +1 -1
- package/src/ui/theme-config.js +150 -0
- package/src/ui/theme.js +6 -138
- package/src/ui/toast-lazy.js +11 -0
- package/src/vendor/LICENSES-optional.md +253 -0
- package/src/vendor/highlight.vendor.js +3 -0
- package/src/vendor/hljs-bash.vendor.js +2 -0
- package/src/vendor/hljs-css.vendor.js +2 -0
- package/src/vendor/hljs-javascript.vendor.js +2 -0
- package/src/vendor/hljs-json.vendor.js +2 -0
- package/src/vendor/hljs-python.vendor.js +2 -0
- package/src/vendor/hljs-xml.vendor.js +2 -0
- package/src/vendor/hyper-html-api-data.vendor.js +1 -1
- package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
- package/src/vendor/hyper-morph-identity.vendor.js +2 -0
- package/src/vendor/hyper-morph-json.vendor.js +3 -0
- package/src/vendor/hyper-morph.vendor.js +6 -6
- package/src/vendor/hyper-undo.vendor.js +1 -1
- package/src/vendor/hypercms.vendor.js +169 -84
- package/src/vendor/marked.vendor.js +68 -0
- package/src/vendor/overtype.vendor.js +994 -0
- package/src/vendor/richclay.vendor.js +17 -17
- package/src/vendor/sap-core.vendor.js +5 -5
- package/src/vendor/sap-ownership.vendor.js +11 -7
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as Y from "../../vendor/yjs.vendor.js";
|
|
2
|
+
import { parseModel } from "../projection.js";
|
|
3
|
+
import { blockFromItem } from "../adopt.js";
|
|
4
|
+
import { seedClientId } from "./migrate.js";
|
|
5
|
+
import { entriesOf } from "./parts.js";
|
|
6
|
+
import { digestHex } from "./recovery-proof.js";
|
|
7
|
+
|
|
8
|
+
// The seed client of an article: the Yjs client id seedPageFromArticle would write,
|
|
9
|
+
// without building the seed. Two articles with the same blocks share it.
|
|
10
|
+
export function seedLineage(article) {
|
|
11
|
+
const items = parseModel(article, { seed: article.getAttribute("data-rev") ?? "" });
|
|
12
|
+
if (!items.length) return { client: null };
|
|
13
|
+
const blocks = {};
|
|
14
|
+
items.forEach((item, i) => {
|
|
15
|
+
const block = blockFromItem(item, null);
|
|
16
|
+
block.pos = String(i + 1).padStart(8, "0");
|
|
17
|
+
blocks[item.id] = block;
|
|
18
|
+
});
|
|
19
|
+
return { client: seedClientId(blocks) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function valueClients(value) {
|
|
23
|
+
const clients = new Set();
|
|
24
|
+
for (const [, text] of entriesOf(value?.y ?? {})) {
|
|
25
|
+
try {
|
|
26
|
+
for (const client of Y.decodeStateVector(Y.encodeStateVectorFromUpdateV2(Y.fromBase64(text))).keys()) clients.add(client);
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
29
|
+
return clients;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function sharesLineage(stored, incoming) {
|
|
33
|
+
const theirs = valueClients(stored);
|
|
34
|
+
const mine = valueClients(incoming);
|
|
35
|
+
if (!theirs.size || !mine.size || [...mine].some((client) => theirs.has(client))) return true;
|
|
36
|
+
const kept = new Map(entriesOf(stored?.y ?? {}));
|
|
37
|
+
return entriesOf(incoming?.y ?? {}).some(([key, text]) => kept.get(key) === text);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// May a journal written under `a` be replayed into a page under `b`? Same page
|
|
41
|
+
// generation, and the same base bytes: an externally replaced article is another
|
|
42
|
+
// generation even when it shows the same text.
|
|
43
|
+
export function sameLineage(a, b) {
|
|
44
|
+
if (!a || !b || !a.generation || !b.generation) return false;
|
|
45
|
+
return a.generation === b.generation && a.baseDigest === b.baseDigest;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The lineage a capsule records for its immutable base: the digest the host persists
|
|
49
|
+
// and the state vector the page's own document had at the same moment.
|
|
50
|
+
export async function lineageOf(generation, base) {
|
|
51
|
+
const bytes = base instanceof Uint8Array ? base : new Uint8Array(0);
|
|
52
|
+
return {
|
|
53
|
+
generation,
|
|
54
|
+
baseDigest: await digestHex(bytes),
|
|
55
|
+
baseVector: bytes.length ? Y.encodeStateVectorFromUpdateV2(bytes) : new Uint8Array(0),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The existing ancestry check, with the empty case refused: an update with no client of
|
|
60
|
+
// its own shares lineage with nothing, and neither does a page with no clients.
|
|
61
|
+
export function sharesAncestry(stored, incoming) {
|
|
62
|
+
const theirs = valueClients(stored);
|
|
63
|
+
const mine = valueClients(incoming);
|
|
64
|
+
if (!theirs.size || !mine.size) return false;
|
|
65
|
+
return sharesLineage(stored, incoming);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function seedOnly(doc, client) {
|
|
69
|
+
for (const id of Y.decodeStateVector(Y.encodeStateVector(doc)).keys()) if (id !== client) return false;
|
|
70
|
+
return Y.createDeleteSetFromStructStore(doc.store).clients.size === 0;
|
|
71
|
+
}
|
package/src/page/ybind/marks.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { schema } from "../schema.js";
|
|
2
|
+
import { entriesOfYjs, isOpaque, opaqueMark, yjsFields } from "../format-codec.js";
|
|
3
|
+
import { NATIVE_NAMES, nativeAttrs } from "../format-native.js";
|
|
2
4
|
|
|
3
5
|
export const markName = (k) => k.split("!")[0].split("~")[0];
|
|
4
6
|
export const isCancel = (k) => k.includes("!");
|
|
5
7
|
export const marksAttrs = (marks) => { const o = {}; for (const m of marks) o[m.type.name] = m.attrs && Object.keys(m.attrs).length ? { ...m.attrs } : true; return o; };
|
|
8
|
+
// The Yjs fields a set of editor marks stands for. An opaque mark is not a live
|
|
9
|
+
// key of its own: its named descriptors go under their own names and its compact
|
|
10
|
+
// tokens under the reserved one.
|
|
11
|
+
export const marksFields = (marks) => {
|
|
12
|
+
const o = {};
|
|
13
|
+
for (const m of marks) {
|
|
14
|
+
if (isOpaque(m)) { for (const [key, value] of Object.entries(yjsFields(m.attrs.entries))) o[key] = JSON.parse(JSON.stringify(value)); continue; }
|
|
15
|
+
o[m.type.name] = m.attrs && Object.keys(m.attrs).length ? { ...m.attrs } : true;
|
|
16
|
+
}
|
|
17
|
+
return o;
|
|
18
|
+
};
|
|
6
19
|
export const marksKey = (marks) => marks.map((m) => m.type.name + (m.attrs && Object.keys(m.attrs).length ? JSON.stringify(m.attrs) : "")).sort().join("|");
|
|
7
20
|
export function marksFromAttributes(a, blockType) {
|
|
8
21
|
if (blockType === "code_block") return [];
|
|
@@ -14,5 +27,17 @@ export function marksFromAttributes(a, blockType) {
|
|
|
14
27
|
if (v === null || v === undefined || isCancel(k) || cancelled.has(k)) continue;
|
|
15
28
|
best.set(markName(k), v);
|
|
16
29
|
}
|
|
17
|
-
|
|
30
|
+
const marks = [];
|
|
31
|
+
let unknown = null;
|
|
32
|
+
for (const [n, v] of best) {
|
|
33
|
+
if (schema.marks[n] && (!NATIVE_NAMES.has(n) || nativeAttrs(n, v))) marks.push(schema.marks[n].create(v === true ? null : v));
|
|
34
|
+
else (unknown ??= {})[n] = v;
|
|
35
|
+
}
|
|
36
|
+
// The walk asks this of every text run on every keystroke, and almost none carry a
|
|
37
|
+
// mark from a newer version, so the codec runs only when one is there.
|
|
38
|
+
if (unknown) {
|
|
39
|
+
const opaque = entriesOfYjs(unknown);
|
|
40
|
+
if (opaque.length) marks.push(opaqueMark(opaque));
|
|
41
|
+
}
|
|
42
|
+
return marks;
|
|
18
43
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as Y from "../../vendor/yjs.vendor.js";
|
|
2
2
|
import { schema } from "../schema.js";
|
|
3
3
|
import { nodeFromBlock } from "../model.js";
|
|
4
|
-
import { flatten, marksOf } from "../inline.js";
|
|
4
|
+
import { flatten, markKey, marksOf } from "../inline.js";
|
|
5
5
|
import { ATOM, BREAK, DEFAULTS, blocksOfValues, orderedIds, unitsOf, runsOf, canon, hash64, parseModel } from "../projection.js";
|
|
6
6
|
import { blockFromItem } from "../adopt.js";
|
|
7
7
|
import { createPageDoc, attrKeys, defaultOf, SEED } from "./layout.js";
|
|
8
|
+
import { marksFields } from "./marks.js";
|
|
8
9
|
import { blocksFromYValue } from "./decode.js";
|
|
9
10
|
import { PART, enc } from "./provider.js";
|
|
10
11
|
import { boardKey } from "./board.js";
|
|
@@ -34,13 +35,54 @@ function sourceBlocks(values) {
|
|
|
34
35
|
return blocksOfValues(values);
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
// A mark key as the record keeps it: the codec validates every descriptor, and the
|
|
39
|
+
// known codes and the opaque ones come back in the order the binding writes them.
|
|
40
|
+
const canonicalKey = (key, id) => {
|
|
41
|
+
let marks;
|
|
42
|
+
try { marks = marksOf(schema, key); } catch { return fail(`Invalid mark key on block: ${id}`); }
|
|
43
|
+
return markKey([...marks].sort((a, b) => a.type.rank - b.type.rank));
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const markUnits = (block, id) => {
|
|
47
|
+
const units = unitsOf(block.m, block.c.length);
|
|
48
|
+
if (!units) fail(`Invalid mark runs on block: ${id}`);
|
|
49
|
+
for (let i = 0; i < units.length; i++) {
|
|
50
|
+
const key = units[i];
|
|
51
|
+
if (typeof key !== "string") fail(`Invalid mark key on block: ${id}`);
|
|
52
|
+
if (key.startsWith("@") && (key !== BREAK || block.c[i] !== ATOM)) fail(`Unsupported inline atom on block: ${id}`);
|
|
53
|
+
if (block.t === "code_block" && key) fail(`Unsupported formatting in code block: ${id}`);
|
|
54
|
+
if (!key.startsWith("@")) units[i] = canonicalKey(key, id);
|
|
55
|
+
}
|
|
56
|
+
return units;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// A block type this runtime has no schema for keeps the record it stands for: its
|
|
60
|
+
// own type, its text and marks and every other field a newer peer wrote on it.
|
|
61
|
+
function opaqueRecord(id, block, pos) {
|
|
62
|
+
const out = { t: block.t, pos };
|
|
63
|
+
for (const key of Object.keys(block)) {
|
|
64
|
+
if (key === "t" || key === "pos" || key === "c" || key === "m") continue;
|
|
65
|
+
out[key] = copy(block[key]);
|
|
66
|
+
}
|
|
67
|
+
if (block.c != null && typeof block.c !== "string") fail(`Invalid text on block: ${id}`);
|
|
68
|
+
out.c = block.c ?? "";
|
|
69
|
+
if (typeof block.c === "string" && block.m != null) {
|
|
70
|
+
const units = markUnits(block, id);
|
|
71
|
+
if (units.some(Boolean)) out.m = runsOf(units);
|
|
72
|
+
} else if (block.m != null) fail(`Mark runs require flat text on block: ${id}`);
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
37
76
|
function normalizeBlock(id, block, index) {
|
|
38
|
-
|
|
77
|
+
const known = Object.hasOwn(DEFAULTS, block.t);
|
|
78
|
+
if (!known && (typeof block.t !== "string" || !block.t || block.t === "opaque_block")) fail(`Unsupported hybrid block type: ${block.t}`);
|
|
79
|
+
const pos = String(index + 1).padStart(8, "0");
|
|
80
|
+
if (!known) return opaqueRecord(id, block, pos);
|
|
39
81
|
const type = schema.nodes[block.t];
|
|
40
82
|
const fields = attrKeys(type);
|
|
41
83
|
const allowed = new Set(["t", "pos", "c", "m", ...fields, ...(block.t === "table" ? ["rows"] : [])]);
|
|
42
|
-
|
|
43
|
-
const
|
|
84
|
+
const out = { t: block.t, pos };
|
|
85
|
+
for (const key of Object.keys(block)) if (!allowed.has(key)) out[key] = copy(block[key]);
|
|
44
86
|
for (const key of fields) {
|
|
45
87
|
const value = block[key] === undefined ? defaultOf(type, key) : block[key];
|
|
46
88
|
if (canon(value) !== canon(defaultOf(type, key))) out[key] = copy(value);
|
|
@@ -56,14 +98,7 @@ function normalizeBlock(id, block, index) {
|
|
|
56
98
|
}
|
|
57
99
|
if (block.c != null && typeof block.c !== "string" && !Array.isArray(block.c)) fail(`Invalid text on block: ${id}`);
|
|
58
100
|
if (typeof block.c === "string" && block.m != null) {
|
|
59
|
-
|
|
60
|
-
if (!units) fail(`Invalid mark runs on block: ${id}`);
|
|
61
|
-
for (let i = 0; i < units.length; i++) {
|
|
62
|
-
const key = units[i];
|
|
63
|
-
if (typeof key !== "string") fail(`Invalid mark key on block: ${id}`);
|
|
64
|
-
if (key.startsWith("@") && (key !== BREAK || block.c[i] !== ATOM)) fail(`Unsupported inline atom on block: ${id}`);
|
|
65
|
-
if (block.t === "code_block" && key) fail(`Unsupported formatting in code block: ${id}`);
|
|
66
|
-
}
|
|
101
|
+
markUnits(block, id);
|
|
67
102
|
} else if (block.m != null) fail(`Mark runs require flat text on block: ${id}`);
|
|
68
103
|
const node = nodeFromBlock(schema, id, block);
|
|
69
104
|
node.check();
|
|
@@ -107,7 +142,7 @@ export function buildPageValue(values, clientID) {
|
|
|
107
142
|
if (key === BREAK) {
|
|
108
143
|
for (let i = 0; i < length; i++) pd.flow.insertEmbed(pd.flow.length, { br: 1 }, {});
|
|
109
144
|
} else {
|
|
110
|
-
const attrs = Object.fromEntries(marksOf(schema, key).map((
|
|
145
|
+
const attrs = Object.fromEntries(Object.entries(marksFields(marksOf(schema, key))).map(([name, value]) => [`${name}~${clientID}-m${(op++).toString(36)}`, value && typeof value === "object" ? copy(value) : value]));
|
|
111
146
|
pd.flow.insert(pd.flow.length, c.slice(offset, offset + length), attrs);
|
|
112
147
|
}
|
|
113
148
|
offset += length;
|
package/src/page/ybind/moves.js
CHANGED
|
@@ -59,6 +59,7 @@ const MAX_INDENT = 8;
|
|
|
59
59
|
const ATTR_RULES = {
|
|
60
60
|
indent: (value, block) => block.t === "list_item" && Number.isInteger(value) && value >= 0 && value <= MAX_INDENT,
|
|
61
61
|
open: (value, block) => block.t === "toggle" && typeof value === "boolean",
|
|
62
|
+
parent: (value, block, byId) => value === null || (typeof value === "string" && byId.get(value)?.t === "toggle" && !inSubtree(byId, value, block.id)),
|
|
62
63
|
};
|
|
63
64
|
|
|
64
65
|
const attrChanges = (args, byId) => {
|
|
@@ -69,7 +70,7 @@ const attrChanges = (args, byId) => {
|
|
|
69
70
|
const block = byId.get(id);
|
|
70
71
|
if (!block || !fields || typeof fields !== "object" || Array.isArray(fields)) return null;
|
|
71
72
|
for (const [field, value] of Object.entries(fields)) {
|
|
72
|
-
if (!ATTR_RULES[field] || !ATTR_RULES[field](value, block)) return null;
|
|
73
|
+
if (!ATTR_RULES[field] || !ATTR_RULES[field](value, block, byId)) return null;
|
|
73
74
|
out.push({ id, field, value });
|
|
74
75
|
}
|
|
75
76
|
}
|
|
@@ -118,6 +119,9 @@ export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
|
|
|
118
119
|
const hasParent = args?.parent !== undefined;
|
|
119
120
|
|
|
120
121
|
const current = walk(pd, origins);
|
|
122
|
+
// An opaque block is an operation this runtime must refuse before writing: it
|
|
123
|
+
// has no turn-into, it cannot be carried, and its record is never rewritten.
|
|
124
|
+
const protectedIds = new Set(current.visible.filter((seg) => seg.opaque).map((seg) => seg.id));
|
|
121
125
|
const list = visibleBlocks(pd, current, view);
|
|
122
126
|
const byId = new Map(list.map((block) => [block.id, block]));
|
|
123
127
|
const order = list.map((block) => block.id);
|
|
@@ -126,10 +130,12 @@ export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
|
|
|
126
130
|
for (const id of ids) if (!byId.has(id)) return refuse("missing-source");
|
|
127
131
|
const changes = attrChanges(args, byId);
|
|
128
132
|
if (!changes) return refuse("invalid-attrs");
|
|
133
|
+
const parentChanges = changes.filter((change) => change.field === "parent");
|
|
129
134
|
|
|
130
135
|
const roots = new Set(ids.filter((id) => !ids.some((other) => other !== id && isAncestor(byId, other, id))));
|
|
131
136
|
const selected = list.filter((block) => ids.some((root) => inSubtree(byId, block.id, root)));
|
|
132
137
|
const selectedSet = new Set(selected.map((block) => block.id));
|
|
138
|
+
if (selected.some((block) => protectedIds.has(block.id)) || changes.some((change) => protectedIds.has(change.id))) return refuse("unsupported-format");
|
|
133
139
|
|
|
134
140
|
const segments = new Map(current.visible.map((seg) => [seg.id, seg]));
|
|
135
141
|
for (const block of selected) {
|
|
@@ -174,14 +180,33 @@ export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
|
|
|
174
180
|
const movedIds = selected.map((block) => block.id);
|
|
175
181
|
if (sameOrder && !movedRoots.length && !changes.length) return { status: "unchanged" };
|
|
176
182
|
|
|
183
|
+
const caret = (() => {
|
|
184
|
+
const sel = view?.state.selection;
|
|
185
|
+
if (!(sel instanceof pmState.TextSelection)) return null;
|
|
186
|
+
const at = (pos) => { const $pos = view.state.doc.resolve(pos); return $pos.depth ? { id: $pos.node(1).attrs.id, offset: $pos.parentOffset } : null; };
|
|
187
|
+
const anchor = at(sel.anchor), head = at(sel.head);
|
|
188
|
+
return anchor && head && (selectedSet.has(anchor.id) || selectedSet.has(head.id)) ? { anchor, head } : null;
|
|
189
|
+
})();
|
|
190
|
+
const restoreCaret = () => {
|
|
191
|
+
if (!caret || !view) return;
|
|
192
|
+
const place = ({ id, offset }) => {
|
|
193
|
+
let found = null;
|
|
194
|
+
view.state.doc.forEach((node, pos) => { if (found === null && node.attrs.id === id) found = pos + 1 + Math.min(offset, node.content.size); });
|
|
195
|
+
return found;
|
|
196
|
+
};
|
|
197
|
+
const anchor = place(caret.anchor), head = place(caret.head);
|
|
198
|
+
if (anchor !== null && head !== null) view.dispatch(view.state.tr.setSelection(pmState.TextSelection.create(view.state.doc, anchor, head)).setMeta(REMOTE_META, true).setMeta("addToHistory", false));
|
|
199
|
+
};
|
|
177
200
|
if (sameOrder) {
|
|
178
201
|
um?.stopCapturing();
|
|
179
202
|
pd.ydoc.transact(() => {
|
|
180
|
-
for (const
|
|
181
|
-
for (const
|
|
203
|
+
if (parentChanges.length) for (const change of parentChanges) setParent(pd, segments.get(change.id), change.id, change.value);
|
|
204
|
+
else for (const block of movedRoots) setParent(pd, segments.get(block.id), block.id, parent);
|
|
205
|
+
for (const change of changes) if (change.field !== "parent") setAttr(pd, segments.get(change.id), change.id, change.field, change.value);
|
|
182
206
|
}, LOCAL);
|
|
183
207
|
um?.stopCapturing();
|
|
184
208
|
render();
|
|
209
|
+
restoreCaret();
|
|
185
210
|
return { status: "moved", ids: movedIds };
|
|
186
211
|
}
|
|
187
212
|
|
|
@@ -216,13 +241,6 @@ export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
|
|
|
216
241
|
const baseline = intents.restores.find((restore) => restore.key === winner?.key)?.baseline ?? { type: seg.t, attrs: seg.fields ?? {} };
|
|
217
242
|
promotions.set(seg.sourceKey, { covers: removalsOf.map((removal) => removal.key), baseline: JSON.parse(JSON.stringify(baseline)) });
|
|
218
243
|
}
|
|
219
|
-
const caret = (() => {
|
|
220
|
-
const sel = view?.state.selection;
|
|
221
|
-
if (!(sel instanceof pmState.TextSelection)) return null;
|
|
222
|
-
const at = (pos) => { const $pos = view.state.doc.resolve(pos); return $pos.depth ? { id: $pos.node(1).attrs.id, offset: $pos.parentOffset } : null; };
|
|
223
|
-
const anchor = at(sel.anchor), head = at(sel.head);
|
|
224
|
-
return anchor && head && (selectedSet.has(anchor.id) || selectedSet.has(head.id)) ? { anchor, head } : null;
|
|
225
|
-
})();
|
|
226
244
|
if (!removals.length) throw new Error("The selected blocks have no live physical markers to retire");
|
|
227
245
|
|
|
228
246
|
um?.stopCapturing();
|
|
@@ -270,19 +288,12 @@ export function moveBlocks({ pd, view, um, origins, render }, args = {}) {
|
|
|
270
288
|
pd.gone.set(copyActiveKey(physicalKeyOf(dest[0])), true);
|
|
271
289
|
}
|
|
272
290
|
if (spans.length) origins.recordSpans(spans);
|
|
273
|
-
for (const
|
|
274
|
-
for (const
|
|
291
|
+
if (parentChanges.length) for (const change of parentChanges) setParent(pd, segments.get(change.id), change.id, change.value);
|
|
292
|
+
else for (const block of movedRoots) setParent(pd, segments.get(block.id), block.id, parent);
|
|
293
|
+
for (const change of changes) if (change.field !== "parent") setAttr(pd, segments.get(change.id), change.id, change.field, change.value);
|
|
275
294
|
}, LOCAL);
|
|
276
295
|
um?.stopCapturing();
|
|
277
296
|
render();
|
|
278
|
-
|
|
279
|
-
const place = ({ id, offset }) => {
|
|
280
|
-
let found = null;
|
|
281
|
-
view.state.doc.forEach((node, pos) => { if (found === null && node.attrs.id === id) found = pos + 1 + Math.min(offset, node.content.size); });
|
|
282
|
-
return found;
|
|
283
|
-
};
|
|
284
|
-
const anchor = place(caret.anchor), head = place(caret.head);
|
|
285
|
-
if (anchor !== null && head !== null) view.dispatch(view.state.tr.setSelection(pmState.TextSelection.create(view.state.doc, anchor, head)).setMeta(REMOTE_META, true).setMeta("addToHistory", false));
|
|
286
|
-
}
|
|
297
|
+
restoreCaret();
|
|
287
298
|
return { status: "moved", ids: movedIds };
|
|
288
299
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Where a position sits on the page: the map between a ProseMirror position in
|
|
2
|
+
// the projected document and an index in the page's Yjs `flow`. Extracted from
|
|
3
|
+
// bridge.js so the same functions keep this tab's own caret through a peer's edit
|
|
4
|
+
// and place a peer's caret here, instead of each approximating the other.
|
|
5
|
+
import { tokenIndex, yBefore } from "./tokens.js";
|
|
6
|
+
|
|
7
|
+
export const yIndexAt = (doc, holes, pos, visible) => yBefore(holes, tokenIndex(doc, new Map(), visible)[pos]);
|
|
8
|
+
|
|
9
|
+
export function posAtY(doc, holes, tokens, y, visible = null) {
|
|
10
|
+
let lo = 0, hi = tokens;
|
|
11
|
+
while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (yBefore(holes, mid) <= y) lo = mid; else hi = mid - 1; }
|
|
12
|
+
const idx = tokenIndex(doc, new Map(), visible);
|
|
13
|
+
let first = -1;
|
|
14
|
+
for (let p = 0; p < idx.length; p++) {
|
|
15
|
+
if (idx[p] < lo) continue;
|
|
16
|
+
if (idx[p] > lo) break;
|
|
17
|
+
if (first < 0) first = p;
|
|
18
|
+
if (doc.resolve(p).parent.inlineContent) return p;
|
|
19
|
+
}
|
|
20
|
+
return first < 0 ? doc.content.size : first;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const cellPoint = (doc, pos) => {
|
|
24
|
+
const $pos = doc.resolve(pos);
|
|
25
|
+
if ($pos.depth !== 3 || $pos.parent.type.name !== "table_cell") return null;
|
|
26
|
+
return { table: $pos.node(1).attrs.id, row: $pos.node(2).attrs.id, col: $pos.parent.attrs.col, offset: $pos.parentOffset };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// The cell a point names, as its own range: what an outline draws and what a
|
|
30
|
+
// character offset would pretend to know.
|
|
31
|
+
export const cellRange = (doc, point) => {
|
|
32
|
+
let found = null;
|
|
33
|
+
doc.forEach((table, at) => {
|
|
34
|
+
if (found !== null || table.type.name !== "table" || table.attrs.id !== point.table) return;
|
|
35
|
+
table.forEach((row, rowAt) => {
|
|
36
|
+
if (found !== null || row.attrs.id !== point.row) return;
|
|
37
|
+
row.forEach((cell, cellAt) => {
|
|
38
|
+
if (found === null && cell.attrs.col === point.col) found = { from: at + 2 + rowAt + cellAt, to: at + 2 + rowAt + cellAt + cell.nodeSize, block: point.table, row: point.row, col: point.col };
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
return found;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const cellAt = (doc, point) => {
|
|
46
|
+
const range = cellRange(doc, point);
|
|
47
|
+
if (!range) return null;
|
|
48
|
+
const cell = doc.nodeAt(range.from);
|
|
49
|
+
return range.from + 1 + Math.min(point.offset ?? 0, cell ? cell.content.size : 0);
|
|
50
|
+
};
|
|
@@ -3,6 +3,7 @@ import { toBase64, fromBase64 } from "../../vendor/yjs.vendor.js";
|
|
|
3
3
|
import { NET } from "./layout.js";
|
|
4
4
|
import { ORIGINS_ORIGIN } from "./origins.js";
|
|
5
5
|
import { PART_KEY, BASE64, entriesOf, captureYValue } from "./parts.js";
|
|
6
|
+
import { containsUpdate } from "./recovery-proof.js";
|
|
6
7
|
|
|
7
8
|
export { entriesOf } from "./parts.js";
|
|
8
9
|
|
|
@@ -10,8 +11,11 @@ export const enc = (u) => toBase64(u);
|
|
|
10
11
|
export const dec = (s) => fromBase64(s);
|
|
11
12
|
|
|
12
13
|
export const PART = 12 * 1024;
|
|
14
|
+
|
|
15
|
+
// The origin a replayed journal is applied through. It is offered to the room like any
|
|
16
|
+
// other local work and is never journaled again: a recovery is not this tab's typing.
|
|
17
|
+
export const RECOVERY_ORIGIN = "page-recovery";
|
|
13
18
|
const baseOf = (k) => PART_KEY.exec(k)?.[1] ?? k;
|
|
14
|
-
const sameBytes = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
|
|
15
19
|
|
|
16
20
|
export function yValueOf(ydoc) {
|
|
17
21
|
return { v: 1, g: 0, y: { seed: enc(Y.encodeStateAsUpdateV2(ydoc)) } };
|
|
@@ -75,10 +79,12 @@ export function docFromValue(value, { strict = false } = {}) {
|
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
81
|
|
|
82
|
+
const encoder = typeof TextEncoder === "function" ? new TextEncoder() : null;
|
|
83
|
+
const utf8Bytes = (text) => (encoder ? encoder.encode(text).length : text.length * 2);
|
|
78
84
|
const sameDeletes = (a, b) => Y.equalDeleteSets(Y.createDeleteSetFromStructStore(a.store), Y.createDeleteSetFromStructStore(b.store));
|
|
79
85
|
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; };
|
|
80
86
|
|
|
81
|
-
export function createYProvider({ store, ydoc, compact: limits = {}, setTimer = setTimeout, clearTimer = clearTimeout, onStopped = () => {}, stats = { applied: 0, offered: 0, compactions: 0 } }) {
|
|
87
|
+
export function createYProvider({ store, ydoc, compact: limits = {}, setTimer = setTimeout, clearTimer = clearTimeout, onStopped = () => {}, stats = { applied: 0, offered: 0, compactions: 0 }, journal = null }) {
|
|
82
88
|
const initial = entriesOf(store.value.y);
|
|
83
89
|
const known = new Set();
|
|
84
90
|
// Set when this tab's page can no longer reach the room: "lineage" when the room holds
|
|
@@ -95,6 +101,9 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
95
101
|
const me = ydoc.clientID.toString(36);
|
|
96
102
|
const nextKey = (p) => `${p}${me}-${(counter++).toString(36)}`;
|
|
97
103
|
let timer = null;
|
|
104
|
+
const generation = store.value.g;
|
|
105
|
+
// Keys this tab wrote itself: the updates and offers the room has not confirmed yet.
|
|
106
|
+
const own = new Set();
|
|
98
107
|
|
|
99
108
|
const partsOf = (key, s) => {
|
|
100
109
|
if (s.length <= PART) return [[key, s]];
|
|
@@ -104,13 +113,17 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
104
113
|
const put = (key, bytes) => {
|
|
105
114
|
canCompact = true;
|
|
106
115
|
known.add(key);
|
|
116
|
+
own.add(key);
|
|
107
117
|
const parts = partsOf(key, enc(bytes));
|
|
108
118
|
store.handle.update((data) => { for (const [k, v] of parts) data.y[k] = v; });
|
|
109
119
|
};
|
|
110
120
|
|
|
111
121
|
for (const [k] of initial) known.add(k);
|
|
112
122
|
|
|
123
|
+
// Ahead of the stop guards, and of the room's own delivery: work this tab can no longer
|
|
124
|
+
// offer, and work a refused offer took back, is still work a crash must not lose.
|
|
113
125
|
const onUpdate = (u, origin) => {
|
|
126
|
+
if (origin !== NET && origin !== ORIGINS_ORIGIN && origin !== RECOVERY_ORIGIN) journal?.local(u);
|
|
114
127
|
if (stopped || origin === NET || origin === ORIGINS_ORIGIN) return;
|
|
115
128
|
put(nextKey("u"), u);
|
|
116
129
|
};
|
|
@@ -121,7 +134,11 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
121
134
|
canCompact = true;
|
|
122
135
|
known.add(k);
|
|
123
136
|
stats.applied++;
|
|
124
|
-
|
|
137
|
+
const update = dec(v);
|
|
138
|
+
Y.applyUpdateV2(ydoc, update, NET);
|
|
139
|
+
// A received update can be the prerequisite of a later local insert, so the journal
|
|
140
|
+
// keeps it beside the work it owns, and never counts it as this tab's own.
|
|
141
|
+
journal?.dependency(update);
|
|
125
142
|
};
|
|
126
143
|
|
|
127
144
|
let wantReconcile = false;
|
|
@@ -163,20 +180,70 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
163
180
|
const compact = { keys: 32, room: 3840 * 1024, ...limits };
|
|
164
181
|
let fold = null;
|
|
165
182
|
let quietUntil = 0;
|
|
183
|
+
const COVER_MAX = 256;
|
|
166
184
|
const coverCache = new Map();
|
|
167
185
|
const drop = (keys) => store.handle.update((data) => { for (const k of keys) delete data.y[k]; });
|
|
168
186
|
const keysOf = (auth, bases) => Object.keys(auth).filter((k) => bases.has(baseOf(k)));
|
|
187
|
+
// A key is retired only while what this tab shows under it is still what the proof was
|
|
188
|
+
// made over: a late write under the same key keeps its own entry.
|
|
189
|
+
const retire = (auth, keys) => { const visible = store.value.y ?? {}; drop(keys.filter((k) => k in visible && visible[k] === auth[k])); };
|
|
169
190
|
|
|
191
|
+
// `known` and the coverage memo keep live references only: the authoritative entries,
|
|
192
|
+
// this tab's own unconfirmed writes and the fold in progress. Pruned after a rebase, a
|
|
193
|
+
// fold and a refusal. A key that is forgotten can be decoded again; Yjs is idempotent.
|
|
194
|
+
function prune() {
|
|
195
|
+
const auth = store.authoritative.y ?? {};
|
|
196
|
+
const visible = store.value.y ?? {};
|
|
197
|
+
for (const k of own) if (!(k in visible)) own.delete(k);
|
|
198
|
+
const held = fold ? [...fold.parts.map(([k]) => k), ...fold.inputs.keys()] : [];
|
|
199
|
+
const retained = new Set([...Object.keys(auth), ...own, ...held]);
|
|
200
|
+
for (const key of known) if (!retained.has(key)) known.delete(key);
|
|
201
|
+
const values = new Map(entriesOf(auth));
|
|
202
|
+
for (const [key, proof] of coverCache) if (!retained.has(key) || values.get(key) !== proof.value) coverCache.delete(key);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Whether a fold's merged bytes already carry this input: merging it in again changes
|
|
206
|
+
// neither an insertion nor a deletion. Equal state vectors say nothing about deletes.
|
|
207
|
+
function coveredBy(key, value, foldKey, base) {
|
|
208
|
+
let proof = coverCache.get(key);
|
|
209
|
+
if (proof && proof.value !== value) { coverCache.delete(key); proof = null; }
|
|
210
|
+
if (!proof) {
|
|
211
|
+
if (coverCache.size >= COVER_MAX) coverCache.delete(coverCache.keys().next().value);
|
|
212
|
+
proof = { value, folds: new Map() };
|
|
213
|
+
coverCache.set(key, proof);
|
|
214
|
+
}
|
|
215
|
+
if (!proof.folds.has(foldKey)) proof.folds.set(foldKey, containsUpdate(base, dec(value)));
|
|
216
|
+
return proof.folds.get(foldKey);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// An input the authority no longer has was retired already; one it holds differently
|
|
220
|
+
// was rewritten after the fold was built, and the fold no longer speaks for it.
|
|
221
|
+
const stillHeld = (auth, active) => { for (const [k, v] of active.held) if (auth[k] !== undefined && auth[k] !== v) return false; return true; };
|
|
222
|
+
const coversInputs = (active) => {
|
|
223
|
+
const base = dec(active.merged);
|
|
224
|
+
for (const [k, v] of active.inputs) if (v !== null && !coveredBy(k, v, active.key, base)) return false;
|
|
225
|
+
return true;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// Inputs retire only once the authority holds every proposed part with the identical
|
|
229
|
+
// value, still holds each input as it was captured, and the fold covers every one of
|
|
230
|
+
// them. Anything less keeps the originals; pending confirmation keeps them too.
|
|
170
231
|
function finishFold(auth) {
|
|
171
|
-
|
|
172
|
-
|
|
232
|
+
const active = fold;
|
|
233
|
+
const whole = captureYValue({ v: 1, y: auth }).y;
|
|
234
|
+
const arrived = active.parts.filter(([k, v]) => auth[k] === v && whole[k] === v);
|
|
235
|
+
if (arrived.length !== active.parts.length) {
|
|
236
|
+
if (store.pending.length) return false;
|
|
173
237
|
fold = null;
|
|
238
|
+
if (arrived.length) retire(auth, arrived.map(([k]) => k));
|
|
239
|
+
quietUntil = entriesOf(auth).length + compact.keys;
|
|
240
|
+
prune();
|
|
174
241
|
return true;
|
|
175
242
|
}
|
|
176
|
-
if (store.pending.length) return false;
|
|
177
|
-
drop(fold.parts.filter((k) => k in auth));
|
|
178
|
-
quietUntil = entriesOf(auth).length + compact.keys;
|
|
243
|
+
if (store.pending.length || !stillHeld(auth, active) || !coversInputs(active)) return false;
|
|
179
244
|
fold = null;
|
|
245
|
+
retire(auth, keysOf(auth, active.inputs));
|
|
246
|
+
prune();
|
|
180
247
|
return true;
|
|
181
248
|
}
|
|
182
249
|
|
|
@@ -185,11 +252,7 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
185
252
|
const val = new Map(entries);
|
|
186
253
|
const norm = new Map(folds);
|
|
187
254
|
const normal = (k) => { const v = norm.get(k); if (typeof v === "string") norm.set(k, Y.mergeUpdatesV2([dec(v)])); return norm.get(k); };
|
|
188
|
-
const covered = (k, v, mk) =>
|
|
189
|
-
const c = `${k}|${mk}`;
|
|
190
|
-
if (!coverCache.has(c)) { const b = normal(mk); coverCache.set(c, sameBytes(Y.mergeUpdatesV2([b, dec(v)]), b)); }
|
|
191
|
-
return coverCache.get(c);
|
|
192
|
-
};
|
|
255
|
+
const covered = (k, v, mk) => coveredBy(k, v, mk, normal(mk));
|
|
193
256
|
const gone = new Set();
|
|
194
257
|
for (const [k, v] of entries) {
|
|
195
258
|
if (!crowded && k[0] !== "m") continue;
|
|
@@ -201,31 +264,36 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
201
264
|
}
|
|
202
265
|
}
|
|
203
266
|
if (!gone.size) return false;
|
|
204
|
-
|
|
267
|
+
retire(auth, keysOf(auth, gone));
|
|
205
268
|
return true;
|
|
206
269
|
}
|
|
207
270
|
|
|
208
271
|
function maybeCompact() {
|
|
209
|
-
if (!canCompact) return false;
|
|
272
|
+
if (stopped || !canCompact || store.value.g !== generation) return false;
|
|
210
273
|
const auth = store.authoritative.y;
|
|
211
274
|
if (fold) return finishFold(auth);
|
|
212
275
|
const entries = entriesOf(auth);
|
|
213
276
|
const crowded = entries.length > Math.max(compact.keys, quietUntil);
|
|
214
277
|
if (!crowded && entries.filter(([k]) => k[0] === "m").length < 2) return false;
|
|
215
|
-
if (dropCovered(auth, entries, crowded)) return true;
|
|
278
|
+
if (dropCovered(auth, entries, crowded)) { prune(); return true; }
|
|
216
279
|
if (!crowded) return false;
|
|
217
280
|
const merged = enc(Y.mergeUpdatesV2(entries.map(([, v]) => dec(v))));
|
|
218
|
-
const
|
|
219
|
-
|
|
281
|
+
const key = nextKey("m");
|
|
282
|
+
const parts = partsOf(key, merged);
|
|
283
|
+
const candidate = { ...store.value, y: { ...store.value.y } };
|
|
284
|
+
for (const [k, v] of parts) candidate.y[k] = v;
|
|
285
|
+
if (utf8Bytes(JSON.stringify(candidate)) > compact.room) {
|
|
220
286
|
quietUntil = entries.length + compact.keys;
|
|
221
287
|
return false;
|
|
222
288
|
}
|
|
223
|
-
const key = nextKey("m");
|
|
224
|
-
known.add(key);
|
|
225
|
-
const parts = partsOf(key, merged);
|
|
226
289
|
const captured = captureYValue({ v: 1, y: auth }).y;
|
|
227
290
|
const incompleteFolds = Object.keys(auth).filter((k) => !(k in captured)).map(baseOf).filter((k) => k[0] === "m");
|
|
228
|
-
|
|
291
|
+
const inputs = new Map(entries);
|
|
292
|
+
for (const k of incompleteFolds) inputs.set(k, null);
|
|
293
|
+
const held = new Map();
|
|
294
|
+
for (const k of keysOf(auth, inputs)) if (inputs.get(baseOf(k)) !== null) held.set(k, captured[k] ?? auth[k]);
|
|
295
|
+
fold = { key, parts, merged, inputs, held };
|
|
296
|
+
known.add(key);
|
|
229
297
|
store.handle.update((data) => { for (const [k, v] of parts) data.y[k] = v; });
|
|
230
298
|
stats.compactions++;
|
|
231
299
|
return true;
|
|
@@ -264,6 +332,7 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
264
332
|
} else {
|
|
265
333
|
for (const [k, v] of entriesOf(ev.next.y ?? {})) applyKey(k, v);
|
|
266
334
|
}
|
|
335
|
+
prune();
|
|
267
336
|
schedule(ev.source !== "remote" || !ops, ev.source !== "remote");
|
|
268
337
|
});
|
|
269
338
|
|
|
@@ -272,14 +341,18 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
272
341
|
// the refused one, so they are all taken back, with the parts of updates the room does not
|
|
273
342
|
// hold whole, and the room's page stays one a checkpoint can render.
|
|
274
343
|
const offDelivery = store.onDelivery((event) => {
|
|
275
|
-
if (event.phase !== "refused"
|
|
344
|
+
if (event.phase !== "refused") return;
|
|
345
|
+
if (event.code !== "too-large" || stopped) { prune(); return; }
|
|
276
346
|
stop("too-large");
|
|
277
347
|
const auth = store.authoritative.y ?? {};
|
|
278
348
|
const whole = captureYValue({ v: 1, y: auth }).y;
|
|
279
349
|
const unconfirmed = Object.keys(store.value.y ?? {}).filter((k) => !(k in auth));
|
|
280
350
|
const partial = Object.keys(auth).filter((k) => PART_KEY.test(k) && !(k in whole) && baseOf(k).slice(1).startsWith(`${me}-`));
|
|
281
351
|
if (unconfirmed.length || partial.length) drop([...unconfirmed, ...partial]);
|
|
352
|
+
prune();
|
|
282
353
|
});
|
|
283
354
|
|
|
284
|
-
return { known, stats, settle, reconcile, get reconcilePending() { return reconcilePending; }, get stopped() { return stopped; },
|
|
355
|
+
return { known, stats, settle, reconcile, get reconcilePending() { return reconcilePending; }, get stopped() { return stopped; },
|
|
356
|
+
// Test-only snapshot of the counters and cache sizes; no mutable set leaves the provider.
|
|
357
|
+
_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); } };
|
|
285
358
|
}
|