@panphora/clayjs 1.15.2 → 1.16.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 +83090 -58706
- 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-NWPPXOCZ.js +1 -0
- package/dist/m/attrs/save-freeze-FUP4Q4X3.js +1 -0
- package/dist/m/c-2AZDZWCK.js +1 -0
- package/dist/m/c-2JL6E75N.js +1 -0
- package/dist/m/c-2M73252O.js +1 -0
- package/dist/m/c-2SPVQSHC.js +1 -0
- package/dist/m/c-33HUYEDI.js +1 -0
- package/dist/m/c-33JJ6CBX.js +2 -0
- package/dist/m/c-35NBGMZB.js +1 -0
- package/dist/m/c-3GJ4SDE2.js +1 -0
- package/dist/m/c-3IQRECYS.js +1 -0
- package/dist/m/c-3OWDMNJR.js +2 -0
- package/dist/m/c-46XYM55L.js +1 -0
- package/dist/m/c-4B6H4G5V.js +1 -0
- package/dist/m/c-4DRMS4QG.js +9 -0
- package/dist/m/c-4FFHZM32.js +1 -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-4YSCRICO.js +1 -0
- package/dist/m/c-54RCDCBU.js +1 -0
- package/dist/m/c-5M4YEKCX.js +4 -0
- package/dist/m/c-5R4PTRG2.js +1 -0
- package/dist/m/c-5V3MOVJT.js +1 -0
- package/dist/m/c-5VYOWDTI.js +1 -0
- package/dist/m/c-5Y6TNX5W.js +34 -0
- package/dist/m/c-5YIHJ3TJ.js +1 -0
- package/dist/m/c-66O32MV3.js +28 -0
- package/dist/m/c-6HMB24EV.js +1 -0
- package/dist/m/c-7DJDKPGL.js +1 -0
- package/dist/m/c-7O6AFR65.js +1 -0
- package/dist/m/c-7SWMYKAI.js +1 -0
- package/dist/m/c-7VGZ7RU5.js +1 -0
- package/dist/m/c-A7LS5UZW.js +1 -0
- package/dist/m/c-ANQTJ3B5.js +2 -0
- package/dist/m/c-AQLFEATB.js +1 -0
- package/dist/m/c-AYVOREBE.js +1 -0
- package/dist/m/c-B2ZXPOS3.js +3 -0
- package/dist/m/c-BBVGTQG7.js +1 -0
- package/dist/m/c-BMIZKMPV.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-CJZSLMVQ.js +1 -0
- package/dist/m/c-CYWLPPUS.js +1 -0
- package/dist/m/c-CZUSOB7V.js +1 -0
- package/dist/m/c-DBE6ORSI.js +1 -0
- package/dist/m/c-DCFSEJZG.js +1 -0
- package/dist/m/c-DNZ5IFM6.js +1 -0
- package/dist/m/c-EDHUR4MX.js +1 -0
- package/dist/m/c-EJTGYSLN.js +1 -0
- package/dist/m/c-EY6UOM54.js +1 -0
- package/dist/m/c-F3CKJTJS.js +1 -0
- package/dist/m/c-F7Y6FLPY.js +1 -0
- package/dist/m/c-FA4DZVTC.js +1 -0
- package/dist/m/c-FILLTJA7.js +1 -0
- package/dist/m/c-G2BUZITR.js +1 -0
- package/dist/m/c-GB3JTV6E.js +1 -0
- package/dist/m/c-GBGWXTX4.js +1 -0
- package/dist/m/c-GHBCCVG4.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-H4LG442Q.js +1 -0
- package/dist/m/c-HA6INT6J.js +1 -0
- package/dist/m/c-HPURYVZS.js +2 -0
- package/dist/m/c-HRDCD3WN.js +67 -0
- package/dist/m/c-HT33EQRN.js +1 -0
- package/dist/m/c-HVYXIS72.js +1 -0
- package/dist/m/c-IB52R237.js +1 -0
- package/dist/m/c-IJUK2GPW.js +1 -0
- package/dist/m/c-IKWNE4R4.js +1 -0
- package/dist/m/c-IU3IYEU6.js +1 -0
- package/dist/m/c-IZ2EBRAJ.js +1 -0
- package/dist/m/c-J7CA54IP.js +1 -0
- package/dist/m/c-JAX2667S.js +6 -0
- package/dist/m/c-KI7M4AY3.js +1 -0
- package/dist/m/c-KIPQJX4B.js +1 -0
- package/dist/m/c-KKWLDOPM.js +1 -0
- package/dist/m/c-KR4UW4KW.js +1 -0
- package/dist/m/c-KY3VJGUA.js +8 -0
- package/dist/m/c-L4WQSC27.js +1 -0
- package/dist/m/c-LFPHCXY3.js +1 -0
- package/dist/m/c-LR4CGEPT.js +91 -0
- package/dist/m/c-LRPUO2MX.js +1 -0
- package/dist/m/c-LTGMXXKO.js +1 -0
- package/dist/m/c-LXENEBV7.js +1 -0
- package/dist/m/c-LZJPNR2W.js +1 -0
- package/dist/m/c-MMUIUUZG.js +2 -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-NMJRXKPC.js +8 -0
- package/dist/m/c-NRPJEVJS.js +1 -0
- package/dist/m/c-NUHP2XDB.js +1 -0
- package/dist/m/c-NZMNXVF6.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-OYTFP5QN.js +1 -0
- package/dist/m/c-P7KNDG34.js +1 -0
- package/dist/m/c-PPOSCAQD.js +1 -0
- package/dist/m/c-PQ5LK6QE.js +993 -0
- package/dist/m/c-PZ4XEPUP.js +2 -0
- package/dist/m/c-QHIWXHRO.js +1 -0
- package/dist/m/c-QHWJ5ZIJ.js +1 -0
- package/dist/m/c-QIX5BJYN.js +1 -0
- package/dist/m/c-QNKREOL6.js +1 -0
- package/dist/m/c-QQHEX6HA.js +1 -0
- package/dist/m/c-R7RMANJI.js +9 -0
- package/dist/m/c-RI4NT75Y.js +1 -0
- package/dist/m/c-RM7J7BV2.js +1 -0
- package/dist/m/c-RUJKSUQW.js +1 -0
- package/dist/m/c-RVMWGXEJ.js +1 -0
- package/dist/m/c-S7H2DRCB.js +1 -0
- package/dist/m/c-S7LK4KUE.js +1 -0
- package/dist/m/c-SGCCC4FW.js +25 -0
- package/dist/m/c-SGIA4IEQ.js +188 -0
- package/dist/m/c-SWRJFCNN.js +1 -0
- package/dist/m/c-T4VO4OCB.js +1 -0
- package/dist/m/c-TCCA5BZN.js +2 -0
- package/dist/m/c-TQEMVJCS.js +1 -0
- package/dist/m/c-TQZ2JYHD.js +1 -0
- package/dist/m/c-TR6RMDQ7.js +3 -0
- package/dist/m/c-TRS6TM6O.js +1 -0
- package/dist/m/c-UGHH4MB3.js +1 -0
- package/dist/m/c-UKHTLWUW.js +1 -0
- package/dist/m/c-UL2YUY52.js +1 -0
- package/dist/m/c-UNN7LWCT.js +1 -0
- package/dist/m/c-UXBMRDZB.js +235 -0
- package/dist/m/c-V7PZZRG7.js +1 -0
- package/dist/m/c-VAK7SG37.js +1 -0
- package/dist/m/c-VDSS4K3Z.js +1 -0
- package/dist/m/c-VFX45IZQ.js +1 -0
- package/dist/m/c-VHNSBD2M.js +1 -0
- package/dist/m/c-VKEDWYDF.js +1 -0
- package/dist/m/c-VL52ADDR.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-WWDBYXLK.js +1 -0
- package/dist/m/c-WYFDLITL.js +1 -0
- package/dist/m/c-XN6MBKI6.js +67 -0
- package/dist/m/c-XNNW2CWK.js +1 -0
- package/dist/m/c-XPELB6E5.js +1 -0
- package/dist/m/c-XQXZAO7N.js +1 -0
- package/dist/m/c-Y6USGTS5.js +5 -0
- package/dist/m/c-YDH7Q3NY.js +1 -0
- package/dist/m/c-YG2NJYS6.js +135 -0
- package/dist/m/c-Z36J63PP.js +1 -0
- package/dist/m/c-Z4VRC7GU.js +7 -0
- package/dist/m/c-Z6K52OLP.js +19 -0
- package/dist/m/c-ZDY2MDAA.js +21 -0
- package/dist/m/c-ZE46QDIH.js +1 -0
- package/dist/m/c-ZUCOKOHT.js +1 -0
- package/dist/m/core/admin-attrs-VGNLAUDK.js +1 -0
- package/dist/m/core/autosave-E4PEFPZ6.js +1 -0
- package/dist/m/core/conflict-notice-VQ6Y64PH.js +1 -0
- package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
- package/dist/m/core/host-permission-XN3K2IW3.js +1 -0
- package/dist/m/core/is-edit-mode-JXHDVGUL.js +1 -0
- package/dist/m/core/persist-HKHA66IY.js +3 -0
- package/dist/m/core/recovery-TBBSICE2.js +1 -0
- package/dist/m/core/save-WUCHTQBE.js +1 -0
- package/dist/m/core/save-core-FJVHT5M3.js +1 -0
- package/dist/m/core/snapshot-F3SWDZMM.js +1 -0
- package/dist/m/core/unsaved-warning-STKBHE6G.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-23DPTUT4.js +1 -0
- package/dist/m/internals/index-WOVAP3RU.js +1 -0
- package/dist/m/lib/cache-bust-CGWT6ZJI.js +1 -0
- package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
- package/dist/m/loader-HXX6YNH5.js +1 -0
- package/dist/m/manifest-F22DEDAC.js +1 -0
- package/dist/m/manifest-edit-C3C7376B.js +1 -0
- package/dist/m/manifest.json +3491 -0
- package/dist/m/options/options-2LH76JQ3.js +1 -0
- package/dist/m/page/editor-XUTB2HB2.js +189 -0
- package/dist/m/plugins/ai-edit-ZQ4CGWIP.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-ZIBFAA3R.js +1 -0
- package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
- package/dist/m/plugins/indicator-PV4YW2ID.js +1 -0
- package/dist/m/plugins/live-NUTTXSBS.js +1 -0
- package/dist/m/plugins/menu-HCKO3DSP.js +138 -0
- package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
- package/dist/m/plugins/page-KSUH7G6J.js +2 -0
- package/dist/m/plugins/page-view-W73OW7WI.js +1 -0
- package/dist/m/plugins/people-AJJ32YCG.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-RC3L2F6X.js +517 -0
- package/dist/m/plugins/richclay-RC3L2F6X.js.LEGAL.txt +1 -0
- package/dist/m/plugins/sortable-YRMQX3ZU.js +1 -0
- package/dist/m/plugins/source-KHK5UUSK.js +1 -0
- package/dist/m/plugins/undo-OF2L3CXU.js +1 -0
- package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
- package/dist/m/plugins/upgrade-ZNYODP5Y.js +1 -0
- package/dist/m/plugins/upload-XSGNILUY.js +1 -0
- package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +21 -0
- package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +1 -0
- package/dist/m/plugins/wire-ZWVWKF76.js +1 -0
- package/dist/m/reader-core-ILWLXVMP.js +68 -0
- package/dist/m/route/route-KOCZSEGN.js +1 -0
- package/dist/m/sync/live-sync-4AUVXVJU.js +1 -0
- package/dist/m/ui/index-EVHKMKBL.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 +3800 -0
- package/dist/page/themes/light-1.css +406 -0
- package/dist/server/core.cjs +23662 -22860
- 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/conflict-download.js +22 -0
- package/src/core/conflict-notice-ui.js +1173 -0
- package/src/core/conflict-notice.js +140 -697
- package/src/core/edit-mode.js +147 -14
- package/src/core/etag.js +30 -12
- package/src/core/host-meta.js +72 -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 +109 -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 +805 -118
- package/src/core/snapshot.js +121 -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 +141 -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 +79 -5
- 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 +17 -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/checklist.js +1 -1
- package/src/page/chrome.js +411 -61
- package/src/page/csv-import.js +17 -2
- package/src/page/document-title-dom.js +40 -0
- package/src/page/document-title-runtime.js +76 -0
- package/src/page/document-title.js +9 -0
- package/src/page/drag.js +48 -13
- package/src/page/drop.js +2 -1
- package/src/page/editor.js +330 -86
- package/src/page/feature-loader.js +72 -0
- package/src/page/features/board.js +12 -0
- package/src/page/features/code.js +13 -0
- package/src/page/features/media.js +290 -0
- package/src/page/features/registry.js +23 -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-gate.js +179 -0
- package/src/page/format-level.js +157 -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 +18 -25
- package/src/page/items.js +52 -11
- package/src/page/link.js +1 -0
- package/src/page/markdown.js +86 -1
- package/src/page/media-views.js +3 -0
- package/src/page/media.js +33 -245
- package/src/page/outline-cache.js +11 -0
- package/src/page/placeholder.js +27 -7
- package/src/page/projection.js +196 -50
- 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 +7 -4
- package/src/page/schema.js +14 -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/light-1.css +139 -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.js +390 -107
- package/src/page/ybind/awareness.js +172 -0
- package/src/page/ybind/binding.js +9 -1
- 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 +25 -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 +84 -22
- package/src/page/ybind/recovery-proof.js +191 -0
- package/src/page/ybind/recovery-replay.js +130 -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 +282 -34
- 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 +1022 -275
- 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/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 +535 -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
package/src/page/ybind/marks.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import { schema } from "../schema.js";
|
|
2
|
+
import { entriesOfYjs, isOpaque, opaqueMark, yjsFields } from "../format-codec.js";
|
|
2
3
|
|
|
3
4
|
export const markName = (k) => k.split("!")[0].split("~")[0];
|
|
4
5
|
export const isCancel = (k) => k.includes("!");
|
|
5
6
|
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; };
|
|
7
|
+
// The Yjs fields a set of editor marks stands for. An opaque mark is not a live
|
|
8
|
+
// key of its own: its named descriptors go under their own names and its compact
|
|
9
|
+
// tokens under the reserved one.
|
|
10
|
+
export const marksFields = (marks) => {
|
|
11
|
+
const o = {};
|
|
12
|
+
for (const m of marks) {
|
|
13
|
+
if (isOpaque(m)) { for (const [key, value] of Object.entries(yjsFields(m.attrs.entries))) o[key] = JSON.parse(JSON.stringify(value)); continue; }
|
|
14
|
+
o[m.type.name] = m.attrs && Object.keys(m.attrs).length ? { ...m.attrs } : true;
|
|
15
|
+
}
|
|
16
|
+
return o;
|
|
17
|
+
};
|
|
6
18
|
export const marksKey = (marks) => marks.map((m) => m.type.name + (m.attrs && Object.keys(m.attrs).length ? JSON.stringify(m.attrs) : "")).sort().join("|");
|
|
7
19
|
export function marksFromAttributes(a, blockType) {
|
|
8
20
|
if (blockType === "code_block") return [];
|
|
@@ -14,5 +26,17 @@ export function marksFromAttributes(a, blockType) {
|
|
|
14
26
|
if (v === null || v === undefined || isCancel(k) || cancelled.has(k)) continue;
|
|
15
27
|
best.set(markName(k), v);
|
|
16
28
|
}
|
|
17
|
-
|
|
29
|
+
const marks = [];
|
|
30
|
+
let unknown = null;
|
|
31
|
+
for (const [n, v] of best) {
|
|
32
|
+
if (schema.marks[n]) marks.push(schema.marks[n].create(v === true ? null : v));
|
|
33
|
+
else (unknown ??= {})[n] = v;
|
|
34
|
+
}
|
|
35
|
+
// The walk asks this of every text run on every keystroke, and almost none carry a
|
|
36
|
+
// mark from a newer version, so the codec runs only when one is there.
|
|
37
|
+
if (unknown) {
|
|
38
|
+
const opaque = entriesOfYjs(unknown);
|
|
39
|
+
if (opaque.length) marks.push(opaqueMark(opaque));
|
|
40
|
+
}
|
|
41
|
+
return marks;
|
|
18
42
|
}
|
|
@@ -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
|
|
|
@@ -11,7 +12,6 @@ export const dec = (s) => fromBase64(s);
|
|
|
11
12
|
|
|
12
13
|
export const PART = 12 * 1024;
|
|
13
14
|
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
15
|
|
|
16
16
|
export function yValueOf(ydoc) {
|
|
17
17
|
return { v: 1, g: 0, y: { seed: enc(Y.encodeStateAsUpdateV2(ydoc)) } };
|
|
@@ -75,6 +75,8 @@ export function docFromValue(value, { strict = false } = {}) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
const encoder = typeof TextEncoder === "function" ? new TextEncoder() : null;
|
|
79
|
+
const utf8Bytes = (text) => (encoder ? encoder.encode(text).length : text.length * 2);
|
|
78
80
|
const sameDeletes = (a, b) => Y.equalDeleteSets(Y.createDeleteSetFromStructStore(a.store), Y.createDeleteSetFromStructStore(b.store));
|
|
79
81
|
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
82
|
|
|
@@ -95,6 +97,9 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
95
97
|
const me = ydoc.clientID.toString(36);
|
|
96
98
|
const nextKey = (p) => `${p}${me}-${(counter++).toString(36)}`;
|
|
97
99
|
let timer = null;
|
|
100
|
+
const generation = store.value.g;
|
|
101
|
+
// Keys this tab wrote itself: the updates and offers the room has not confirmed yet.
|
|
102
|
+
const own = new Set();
|
|
98
103
|
|
|
99
104
|
const partsOf = (key, s) => {
|
|
100
105
|
if (s.length <= PART) return [[key, s]];
|
|
@@ -104,6 +109,7 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
104
109
|
const put = (key, bytes) => {
|
|
105
110
|
canCompact = true;
|
|
106
111
|
known.add(key);
|
|
112
|
+
own.add(key);
|
|
107
113
|
const parts = partsOf(key, enc(bytes));
|
|
108
114
|
store.handle.update((data) => { for (const [k, v] of parts) data.y[k] = v; });
|
|
109
115
|
};
|
|
@@ -163,20 +169,70 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
163
169
|
const compact = { keys: 32, room: 3840 * 1024, ...limits };
|
|
164
170
|
let fold = null;
|
|
165
171
|
let quietUntil = 0;
|
|
172
|
+
const COVER_MAX = 256;
|
|
166
173
|
const coverCache = new Map();
|
|
167
174
|
const drop = (keys) => store.handle.update((data) => { for (const k of keys) delete data.y[k]; });
|
|
168
175
|
const keysOf = (auth, bases) => Object.keys(auth).filter((k) => bases.has(baseOf(k)));
|
|
176
|
+
// A key is retired only while what this tab shows under it is still what the proof was
|
|
177
|
+
// made over: a late write under the same key keeps its own entry.
|
|
178
|
+
const retire = (auth, keys) => { const visible = store.value.y ?? {}; drop(keys.filter((k) => k in visible && visible[k] === auth[k])); };
|
|
169
179
|
|
|
180
|
+
// `known` and the coverage memo keep live references only: the authoritative entries,
|
|
181
|
+
// this tab's own unconfirmed writes and the fold in progress. Pruned after a rebase, a
|
|
182
|
+
// fold and a refusal. A key that is forgotten can be decoded again; Yjs is idempotent.
|
|
183
|
+
function prune() {
|
|
184
|
+
const auth = store.authoritative.y ?? {};
|
|
185
|
+
const visible = store.value.y ?? {};
|
|
186
|
+
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
|
+
const values = new Map(entriesOf(auth));
|
|
191
|
+
for (const [key, proof] of coverCache) if (!retained.has(key) || values.get(key) !== proof.value) coverCache.delete(key);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Whether a fold's merged bytes already carry this input: merging it in again changes
|
|
195
|
+
// neither an insertion nor a deletion. Equal state vectors say nothing about deletes.
|
|
196
|
+
function coveredBy(key, value, foldKey, base) {
|
|
197
|
+
let proof = coverCache.get(key);
|
|
198
|
+
if (proof && proof.value !== value) { coverCache.delete(key); proof = null; }
|
|
199
|
+
if (!proof) {
|
|
200
|
+
if (coverCache.size >= COVER_MAX) coverCache.delete(coverCache.keys().next().value);
|
|
201
|
+
proof = { value, folds: new Map() };
|
|
202
|
+
coverCache.set(key, proof);
|
|
203
|
+
}
|
|
204
|
+
if (!proof.folds.has(foldKey)) proof.folds.set(foldKey, containsUpdate(base, dec(value)));
|
|
205
|
+
return proof.folds.get(foldKey);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// An input the authority no longer has was retired already; one it holds differently
|
|
209
|
+
// was rewritten after the fold was built, and the fold no longer speaks for it.
|
|
210
|
+
const stillHeld = (auth, active) => { for (const [k, v] of active.held) if (auth[k] !== undefined && auth[k] !== v) return false; return true; };
|
|
211
|
+
const coversInputs = (active) => {
|
|
212
|
+
const base = dec(active.merged);
|
|
213
|
+
for (const [k, v] of active.inputs) if (v !== null && !coveredBy(k, v, active.key, base)) return false;
|
|
214
|
+
return true;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Inputs retire only once the authority holds every proposed part with the identical
|
|
218
|
+
// value, still holds each input as it was captured, and the fold covers every one of
|
|
219
|
+
// them. Anything less keeps the originals; pending confirmation keeps them too.
|
|
170
220
|
function finishFold(auth) {
|
|
171
|
-
|
|
172
|
-
|
|
221
|
+
const active = fold;
|
|
222
|
+
const whole = captureYValue({ v: 1, y: auth }).y;
|
|
223
|
+
const arrived = active.parts.filter(([k, v]) => auth[k] === v && whole[k] === v);
|
|
224
|
+
if (arrived.length !== active.parts.length) {
|
|
225
|
+
if (store.pending.length) return false;
|
|
173
226
|
fold = null;
|
|
227
|
+
if (arrived.length) retire(auth, arrived.map(([k]) => k));
|
|
228
|
+
quietUntil = entriesOf(auth).length + compact.keys;
|
|
229
|
+
prune();
|
|
174
230
|
return true;
|
|
175
231
|
}
|
|
176
|
-
if (store.pending.length) return false;
|
|
177
|
-
drop(fold.parts.filter((k) => k in auth));
|
|
178
|
-
quietUntil = entriesOf(auth).length + compact.keys;
|
|
232
|
+
if (store.pending.length || !stillHeld(auth, active) || !coversInputs(active)) return false;
|
|
179
233
|
fold = null;
|
|
234
|
+
retire(auth, keysOf(auth, active.inputs));
|
|
235
|
+
prune();
|
|
180
236
|
return true;
|
|
181
237
|
}
|
|
182
238
|
|
|
@@ -185,11 +241,7 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
185
241
|
const val = new Map(entries);
|
|
186
242
|
const norm = new Map(folds);
|
|
187
243
|
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
|
-
};
|
|
244
|
+
const covered = (k, v, mk) => coveredBy(k, v, mk, normal(mk));
|
|
193
245
|
const gone = new Set();
|
|
194
246
|
for (const [k, v] of entries) {
|
|
195
247
|
if (!crowded && k[0] !== "m") continue;
|
|
@@ -201,31 +253,36 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
201
253
|
}
|
|
202
254
|
}
|
|
203
255
|
if (!gone.size) return false;
|
|
204
|
-
|
|
256
|
+
retire(auth, keysOf(auth, gone));
|
|
205
257
|
return true;
|
|
206
258
|
}
|
|
207
259
|
|
|
208
260
|
function maybeCompact() {
|
|
209
|
-
if (!canCompact) return false;
|
|
261
|
+
if (stopped || !canCompact || store.value.g !== generation) return false;
|
|
210
262
|
const auth = store.authoritative.y;
|
|
211
263
|
if (fold) return finishFold(auth);
|
|
212
264
|
const entries = entriesOf(auth);
|
|
213
265
|
const crowded = entries.length > Math.max(compact.keys, quietUntil);
|
|
214
266
|
if (!crowded && entries.filter(([k]) => k[0] === "m").length < 2) return false;
|
|
215
|
-
if (dropCovered(auth, entries, crowded)) return true;
|
|
267
|
+
if (dropCovered(auth, entries, crowded)) { prune(); return true; }
|
|
216
268
|
if (!crowded) return false;
|
|
217
269
|
const merged = enc(Y.mergeUpdatesV2(entries.map(([, v]) => dec(v))));
|
|
218
|
-
const
|
|
219
|
-
|
|
270
|
+
const key = nextKey("m");
|
|
271
|
+
const parts = partsOf(key, merged);
|
|
272
|
+
const candidate = { ...store.value, y: { ...store.value.y } };
|
|
273
|
+
for (const [k, v] of parts) candidate.y[k] = v;
|
|
274
|
+
if (utf8Bytes(JSON.stringify(candidate)) > compact.room) {
|
|
220
275
|
quietUntil = entries.length + compact.keys;
|
|
221
276
|
return false;
|
|
222
277
|
}
|
|
223
|
-
const key = nextKey("m");
|
|
224
|
-
known.add(key);
|
|
225
|
-
const parts = partsOf(key, merged);
|
|
226
278
|
const captured = captureYValue({ v: 1, y: auth }).y;
|
|
227
279
|
const incompleteFolds = Object.keys(auth).filter((k) => !(k in captured)).map(baseOf).filter((k) => k[0] === "m");
|
|
228
|
-
|
|
280
|
+
const inputs = new Map(entries);
|
|
281
|
+
for (const k of incompleteFolds) inputs.set(k, null);
|
|
282
|
+
const held = new Map();
|
|
283
|
+
for (const k of keysOf(auth, inputs)) if (inputs.get(baseOf(k)) !== null) held.set(k, captured[k] ?? auth[k]);
|
|
284
|
+
fold = { key, parts, merged, inputs, held };
|
|
285
|
+
known.add(key);
|
|
229
286
|
store.handle.update((data) => { for (const [k, v] of parts) data.y[k] = v; });
|
|
230
287
|
stats.compactions++;
|
|
231
288
|
return true;
|
|
@@ -264,6 +321,7 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
264
321
|
} else {
|
|
265
322
|
for (const [k, v] of entriesOf(ev.next.y ?? {})) applyKey(k, v);
|
|
266
323
|
}
|
|
324
|
+
prune();
|
|
267
325
|
schedule(ev.source !== "remote" || !ops, ev.source !== "remote");
|
|
268
326
|
});
|
|
269
327
|
|
|
@@ -272,14 +330,18 @@ export function createYProvider({ store, ydoc, compact: limits = {}, setTimer =
|
|
|
272
330
|
// the refused one, so they are all taken back, with the parts of updates the room does not
|
|
273
331
|
// hold whole, and the room's page stays one a checkpoint can render.
|
|
274
332
|
const offDelivery = store.onDelivery((event) => {
|
|
275
|
-
if (event.phase !== "refused"
|
|
333
|
+
if (event.phase !== "refused") return;
|
|
334
|
+
if (event.code !== "too-large" || stopped) { prune(); return; }
|
|
276
335
|
stop("too-large");
|
|
277
336
|
const auth = store.authoritative.y ?? {};
|
|
278
337
|
const whole = captureYValue({ v: 1, y: auth }).y;
|
|
279
338
|
const unconfirmed = Object.keys(store.value.y ?? {}).filter((k) => !(k in auth));
|
|
280
339
|
const partial = Object.keys(auth).filter((k) => PART_KEY.test(k) && !(k in whole) && baseOf(k).slice(1).startsWith(`${me}-`));
|
|
281
340
|
if (unconfirmed.length || partial.length) drop([...unconfirmed, ...partial]);
|
|
341
|
+
prune();
|
|
282
342
|
});
|
|
283
343
|
|
|
284
|
-
return { known, stats, settle, reconcile, get reconcilePending() { return reconcilePending; }, get stopped() { return stopped; },
|
|
344
|
+
return { known, stats, settle, reconcile, get reconcilePending() { return reconcilePending; }, get stopped() { return stopped; },
|
|
345
|
+
// 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); } };
|
|
285
347
|
}
|