@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
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recovery-proof.js — what a checkpoint covers, and what makes a proof a proof.
|
|
3
|
+
*
|
|
4
|
+
* A checkpoint covers a journaled record only when merging that record's owned bytes
|
|
5
|
+
* into the accepted page state changes neither an insertion nor a deletion. A state
|
|
6
|
+
* vector cannot answer that: inserting `abc` and then deleting `b` leaves the state
|
|
7
|
+
* vector where it was while the bytes carry the deletion. So coverage here is a byte
|
|
8
|
+
* comparison, it is sufficient but not necessary, and malformed input is an error that
|
|
9
|
+
* keeps the record rather than retiring it.
|
|
10
|
+
*
|
|
11
|
+
* A proof is made by the save integration from the capture it saved under and the bytes
|
|
12
|
+
* it read back. No room acknowledgement, `requestCheckpoint()` success, state vector or
|
|
13
|
+
* ETag order becomes a proof, and nothing here asks a host anything: the accepted page
|
|
14
|
+
* state and the receipt arrive in the proof itself.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { encodeStateAsUpdateV2, mergeUpdatesV2 } from "../../vendor/yjs.vendor.js";
|
|
18
|
+
import { docFromValue } from "./provider.js";
|
|
19
|
+
|
|
20
|
+
export class PageProofError extends Error {
|
|
21
|
+
constructor(code, message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "PageProofError";
|
|
24
|
+
this.code = code;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const fail = (code, message) => { throw new PageProofError(code, message); };
|
|
29
|
+
const HEX = /^[0-9a-f]{64}$/;
|
|
30
|
+
const encoder = typeof TextEncoder === "function" ? new TextEncoder() : null;
|
|
31
|
+
const bytesOf = (value) => {
|
|
32
|
+
if (value instanceof Uint8Array) return value;
|
|
33
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
34
|
+
return null;
|
|
35
|
+
};
|
|
36
|
+
const identityKey = (id) => (typeof id === "string" ? id : JSON.stringify(id));
|
|
37
|
+
const copyId = (id) => (Array.isArray(id) ? [...id] : id);
|
|
38
|
+
const copyState = (state) => ({ v: state.v, g: state.g, y: { ...(state.y ?? {}) } });
|
|
39
|
+
|
|
40
|
+
export const equalBytes = (a, b) => a.length === b.length && a.every((value, index) => value === b[index]);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Whether merging `update` into `checkpoint` adds nothing: no insertion, no deletion.
|
|
44
|
+
*
|
|
45
|
+
* @param {Uint8Array} checkpoint - canonical or merged Yjs V2 update bytes
|
|
46
|
+
* @param {Uint8Array} update - the bytes to look for
|
|
47
|
+
* @param {Object} [Yjs] - the vendored Yjs module
|
|
48
|
+
* @returns {boolean}
|
|
49
|
+
*/
|
|
50
|
+
export function containsUpdate(checkpoint, update, Yjs = null) {
|
|
51
|
+
const merge = Yjs?.mergeUpdatesV2 ?? mergeUpdatesV2;
|
|
52
|
+
const canonical = merge([checkpoint]);
|
|
53
|
+
return equalBytes(canonical, merge([canonical, update]));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function digestHex(bytes) {
|
|
57
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
58
|
+
return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The accepted page state as one canonical update, decoded strictly.
|
|
63
|
+
*
|
|
64
|
+
* Pending structs, missing part and a delete set naming an unknown item all refuse:
|
|
65
|
+
* a checkpoint nobody can decode proves nothing about a journaled record.
|
|
66
|
+
*
|
|
67
|
+
* @param {Object} state - a version 1 page value
|
|
68
|
+
* @returns {Uint8Array}
|
|
69
|
+
*/
|
|
70
|
+
export function canonicalPageBytes(state) {
|
|
71
|
+
let doc;
|
|
72
|
+
try {
|
|
73
|
+
doc = docFromValue(state, { strict: true });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
fail("invalid-state", `The accepted page state does not decode: ${error.message}`);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
return encodeStateAsUpdateV2(doc);
|
|
79
|
+
} finally {
|
|
80
|
+
doc.destroy();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The same answer `sameAuthority` gives in recovery/identity.js, written here because the
|
|
85
|
+
// edit bundle already pays for this module through the provider: importing the generic
|
|
86
|
+
// recovery layer from the page proof would split it into a chunk every editor load pays.
|
|
87
|
+
const sameAuthority = (a, b) => Boolean(a && b) && Boolean(a.host && a.documentId && a.principalId) && a.host === b.host && a.documentId === b.documentId && a.principalId === b.principalId;
|
|
88
|
+
const sameGeneration = (a, b) => Boolean(a && b) && typeof a.generation === "string" && Boolean(a.generation) && a.generation === b.generation;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Assemble the proof a checkpoint is retired with.
|
|
92
|
+
*
|
|
93
|
+
* Each named record carries the bytes it owns and the context it was journaled under, so
|
|
94
|
+
* the validator can refuse a record written under another generation without asking the
|
|
95
|
+
* store or a host. `state` must be the exact accepted page state and `article` the final
|
|
96
|
+
* outgoing canonical article; their digests are taken here, over these bytes.
|
|
97
|
+
*
|
|
98
|
+
* @param {Object} options
|
|
99
|
+
* @param {string} options.capture - the capture the save belongs to
|
|
100
|
+
* @param {Array<{id: *, revision: number, update: Uint8Array, context: ?Object}>} options.records
|
|
101
|
+
* @param {{authority: Object, lineage: Object}} options.accepted - the identity and generation it was saved under
|
|
102
|
+
* @param {string} options.article - the final outgoing canonical article
|
|
103
|
+
* @param {Object} options.state - the accepted page state
|
|
104
|
+
* @param {{file: string, pageState: string}} options.receipt - the persisted pair
|
|
105
|
+
* @param {boolean} [options.saved]
|
|
106
|
+
* @returns {Promise<Object>}
|
|
107
|
+
*/
|
|
108
|
+
export async function pageProof({ capture, records = [], accepted, article, state, receipt, saved = true } = {}) {
|
|
109
|
+
const entries = [];
|
|
110
|
+
for (const record of records) {
|
|
111
|
+
if (!record || record.id === undefined || typeof record.revision !== "number") fail("invalid-record", "A proof names each record by id and revision");
|
|
112
|
+
const update = bytesOf(record.update);
|
|
113
|
+
if (!update || !update.length) fail("invalid-record", "A proof needs the bytes each record owns");
|
|
114
|
+
entries.push({ id: copyId(record.id), revision: record.revision, update: update.slice(), context: record.context ?? null });
|
|
115
|
+
}
|
|
116
|
+
if (typeof article !== "string") fail("missing-article", "A proof names the final outgoing article");
|
|
117
|
+
const stateBytes = canonicalPageBytes(state);
|
|
118
|
+
return {
|
|
119
|
+
saved: saved === true,
|
|
120
|
+
capture,
|
|
121
|
+
accepted,
|
|
122
|
+
article,
|
|
123
|
+
state: copyState(state),
|
|
124
|
+
stateDigest: await digestHex(stateBytes),
|
|
125
|
+
articleDigest: await digestHex(encoder.encode(article)),
|
|
126
|
+
receipt,
|
|
127
|
+
records: entries,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Checklist items 1, 2, 4 and 5, over the proof alone. Item 3 is the per-record coverage
|
|
132
|
+
// test below, and the projection of the state onto the article is the producer's to make:
|
|
133
|
+
// a proof whose state is not the one it was made over fails its own digest.
|
|
134
|
+
async function checkProof(proof) {
|
|
135
|
+
if (!proof || typeof proof !== "object" || Array.isArray(proof)) fail("missing-proof", "A page checkpoint needs a proof");
|
|
136
|
+
if (proof.saved !== true) fail("not-saved", "Only a save that succeeded under admission makes a proof");
|
|
137
|
+
if (typeof proof.capture !== "string" || !proof.capture) fail("missing-capture", "A proof belongs to the capture it saved");
|
|
138
|
+
const receipt = proof.receipt;
|
|
139
|
+
if (!receipt || typeof receipt.file !== "string" || !receipt.file || typeof receipt.pageState !== "string" || !receipt.pageState) {
|
|
140
|
+
fail("missing-receipt", "A proof names the persisted file and page-state pair");
|
|
141
|
+
}
|
|
142
|
+
const accepted = proof.accepted;
|
|
143
|
+
if (!accepted || !accepted.authority || !accepted.lineage || typeof accepted.lineage.generation !== "string" || !accepted.lineage.generation) {
|
|
144
|
+
fail("missing-identity", "A proof names the authority and generation it was saved under");
|
|
145
|
+
}
|
|
146
|
+
if (typeof proof.article !== "string") fail("missing-article", "A proof names the final outgoing article");
|
|
147
|
+
if (!Array.isArray(proof.records)) fail("missing-records", "A proof names the records it covers");
|
|
148
|
+
if (typeof proof.stateDigest !== "string" || !HEX.test(proof.stateDigest)) fail("missing-digest", "A proof carries the accepted state digest");
|
|
149
|
+
if (typeof proof.articleDigest !== "string" || !HEX.test(proof.articleDigest)) fail("missing-digest", "A proof carries the final article digest");
|
|
150
|
+
const stateBytes = canonicalPageBytes(proof.state);
|
|
151
|
+
if (await digestHex(stateBytes) !== proof.stateDigest) fail("state-digest-mismatch", "The page state is not the one the proof was made over");
|
|
152
|
+
if (await digestHex(encoder.encode(proof.article)) !== proof.articleDigest) fail("article-digest-mismatch", "The article is not the one the proof was made over");
|
|
153
|
+
const entries = new Map();
|
|
154
|
+
for (const entry of proof.records) {
|
|
155
|
+
if (!entry || entry.id === undefined || typeof entry.revision !== "number") fail("invalid-record", "A proof names each record by id and revision");
|
|
156
|
+
const update = bytesOf(entry.update);
|
|
157
|
+
if (!update || !update.length) fail("invalid-record", "A proof needs the bytes each record owns");
|
|
158
|
+
entries.set(identityKey(entry.id), entry);
|
|
159
|
+
}
|
|
160
|
+
return { stateBytes, entries, accepted };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// A record is covered only by a state that still carries it, saved under the same
|
|
164
|
+
// authority and the same generation it was journaled under.
|
|
165
|
+
function covers(checked, entry) {
|
|
166
|
+
const context = entry.context;
|
|
167
|
+
if (!context || !sameAuthority(context.authority, checked.accepted.authority)) return false;
|
|
168
|
+
if (!sameGeneration(context.lineage, checked.accepted.lineage)) return false;
|
|
169
|
+
const update = bytesOf(entry.update);
|
|
170
|
+
return Boolean(update) && update.length > 0 && containsUpdate(checked.stateBytes, update);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The records a page checkpoint proof covers, and no others.
|
|
175
|
+
*
|
|
176
|
+
* Registered as `validateCheckpoint` on the `page.yjs` channel. It runs before the
|
|
177
|
+
* store's transaction, may only narrow the caller's list, and throws on a proof it
|
|
178
|
+
* cannot decode so that an error keeps every record.
|
|
179
|
+
*
|
|
180
|
+
* @param {Object} proof
|
|
181
|
+
* @param {Array<{id: *, revision: number}>} records - what the caller observed
|
|
182
|
+
* @returns {Promise<Array<{id: *, revision: number}>>}
|
|
183
|
+
*/
|
|
184
|
+
export async function validatePageCheckpoint(proof, records = []) {
|
|
185
|
+
const checked = await checkProof(proof);
|
|
186
|
+
return (records || []).filter((record) => {
|
|
187
|
+
if (!record || record.id === undefined || typeof record.revision !== "number") return false;
|
|
188
|
+
const entry = checked.entries.get(identityKey(record.id));
|
|
189
|
+
return Boolean(entry) && entry.revision === record.revision && covers(checked, entry);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recovery-replay.js — rebuild a page from one journaled capsule, and decide whether it
|
|
3
|
+
* may be replayed at all.
|
|
4
|
+
*
|
|
5
|
+
* A capsule is one immutable base and everything the episode put on top of it: the
|
|
6
|
+
* dependencies it received (a peer update a later local insert was built on) and the
|
|
7
|
+
* local work it owns. Replaying all three into a fresh Y.Doc rebuilds the page as this
|
|
8
|
+
* tab had it, read back through the strict decoder — no room, no host, no store.
|
|
9
|
+
*
|
|
10
|
+
* Admission is a separate question from reconstruction: a journal replays only into the
|
|
11
|
+
* authority, principal and generation it was written under, only while the current page
|
|
12
|
+
* still contains the capsule's base, and only into a page sharing its ancestry.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as Y from "../../vendor/yjs.vendor.js";
|
|
16
|
+
import { toBase64 } from "../../vendor/yjs.vendor.js";
|
|
17
|
+
import { NET } from "./layout.js";
|
|
18
|
+
import { blocksFromYValue } from "./decode.js";
|
|
19
|
+
import { sameAuthority } from "../../recovery/identity.js";
|
|
20
|
+
import { sameLineage, sharesAncestry } from "./lineage.js";
|
|
21
|
+
import { canonicalPageBytes, containsUpdate } from "./recovery-proof.js";
|
|
22
|
+
|
|
23
|
+
export class PageReplayError extends Error {
|
|
24
|
+
constructor(code, message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "PageReplayError";
|
|
27
|
+
this.code = code;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const fail = (code, message) => { throw new PageReplayError(code, message); };
|
|
32
|
+
const identityKey = (id) => (typeof id === "string" ? id : JSON.stringify(id));
|
|
33
|
+
const bytesOf = (value) => {
|
|
34
|
+
if (value instanceof Uint8Array) return value;
|
|
35
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
36
|
+
return null;
|
|
37
|
+
};
|
|
38
|
+
const sameId = (a, b) => identityKey(a) === identityKey(b);
|
|
39
|
+
|
|
40
|
+
function merge(values) {
|
|
41
|
+
const parts = values.map(bytesOf).filter((bytes) => bytes && bytes.length);
|
|
42
|
+
if (!parts.length) return new Uint8Array(0);
|
|
43
|
+
if (parts.length === 1) return parts[0].slice();
|
|
44
|
+
return Y.mergeUpdatesV2(parts);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* One writer's capsule and its batches as three self-contained updates.
|
|
49
|
+
*
|
|
50
|
+
* Batches are folded in by origin, so a duplicate batch or an update already compacted
|
|
51
|
+
* into the capsule changes nothing: Yjs merges by operation identity. A batch naming
|
|
52
|
+
* another capsule is an error rather than another page's bytes.
|
|
53
|
+
*
|
|
54
|
+
* @param {{capsule: Object, batches?: Array<Object>}} options
|
|
55
|
+
* @returns {Object}
|
|
56
|
+
*/
|
|
57
|
+
export function accumulateCapsule({ capsule, batches = [] } = {}) {
|
|
58
|
+
if (!capsule || capsule.kind !== "binary" || !capsule.payload) fail("invalid-capsule", "Replay needs a binary capsule");
|
|
59
|
+
if (capsule.version !== undefined && capsule.version !== 1) fail("unknown-version", `Unknown page recovery capsule version: ${capsule.version}`);
|
|
60
|
+
const dependencies = [capsule.payload.dependencies];
|
|
61
|
+
const owned = [capsule.payload.owned];
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
for (const batch of batches) {
|
|
64
|
+
if (!batch || batch.kind !== "binary-batch" || !batch.payload) fail("invalid-batch", "Replay reads binary batches only");
|
|
65
|
+
if (batch.capsuleId !== undefined && !sameId(batch.capsuleId, capsule.id)) fail("foreign-batch", "A batch belongs to another capsule");
|
|
66
|
+
if (batch.id !== undefined) {
|
|
67
|
+
if (seen.has(identityKey(batch.id))) continue;
|
|
68
|
+
seen.add(identityKey(batch.id));
|
|
69
|
+
}
|
|
70
|
+
if (batch.origin === "dependency") dependencies.push(batch.payload);
|
|
71
|
+
else owned.push(batch.payload);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
id: capsule.id,
|
|
75
|
+
context: capsule.context ?? null,
|
|
76
|
+
revision: capsule.revision ?? 0,
|
|
77
|
+
acknowledged: capsule.acknowledged ?? null,
|
|
78
|
+
base: merge([capsule.payload.base]),
|
|
79
|
+
dependencies: merge(dependencies),
|
|
80
|
+
owned: merge(owned),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The capsule as one page value: the exact bytes a save would carry, so the strict
|
|
85
|
+
// decoder reads the same state a checkpoint would.
|
|
86
|
+
export function replayPageValue({ capsule, batches = [] } = {}) {
|
|
87
|
+
const parts = accumulateCapsule({ capsule, batches });
|
|
88
|
+
const doc = new Y.Doc();
|
|
89
|
+
try {
|
|
90
|
+
for (const bytes of [parts.base, parts.dependencies, parts.owned]) if (bytes.length) Y.applyUpdateV2(doc, bytes, NET);
|
|
91
|
+
if (doc.store.pendingStructs || doc.store.pendingDs) fail("unresolved-update", "The capsule's updates are missing a prerequisite");
|
|
92
|
+
return { v: 1, g: 0, y: { seed: toBase64(Y.encodeStateAsUpdateV2(doc)) } };
|
|
93
|
+
} finally {
|
|
94
|
+
doc.destroy();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The whole page a capsule reconstructs, decoded through the strict decoder.
|
|
100
|
+
*
|
|
101
|
+
* @param {{capsule: Object, batches?: Array<Object>}} options
|
|
102
|
+
* @returns {Object} the block model, by block id
|
|
103
|
+
*/
|
|
104
|
+
export function reconstructPage({ capsule, batches = [] } = {}) {
|
|
105
|
+
return blocksFromYValue(replayPageValue({ capsule, batches }));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The capsule's parts, if this page may replay them.
|
|
110
|
+
*
|
|
111
|
+
* `current` is the page as it is now: its own authority, generation and accepted state.
|
|
112
|
+
* A journal from another authority or principal is never applied, a journal from another
|
|
113
|
+
* generation is a page that was replaced while the tab was away, and a page that no
|
|
114
|
+
* longer contains the capsule's base has nothing to replay it onto.
|
|
115
|
+
*
|
|
116
|
+
* @param {{capsule: Object, batches?: Array<Object>, current: {context: Object, state: Object}}} options
|
|
117
|
+
* @returns {Object} the accumulated parts, ready to apply
|
|
118
|
+
*/
|
|
119
|
+
export function admitCapsule({ capsule, batches = [], current } = {}) {
|
|
120
|
+
const parts = accumulateCapsule({ capsule, batches });
|
|
121
|
+
if (!parts.context?.authority) fail("missing-identity", "A capsule with no authority is never replayed");
|
|
122
|
+
if (!current?.context?.authority) fail("missing-identity", "Replay needs the page's own authority");
|
|
123
|
+
if (!sameAuthority(parts.context.authority, current.context.authority)) fail("foreign-authority", "This journal belongs to another document or account");
|
|
124
|
+
if (!sameLineage(parts.context.lineage, current.context.lineage)) fail("foreign-generation", "This journal belongs to another generation of this page");
|
|
125
|
+
const stateBytes = canonicalPageBytes(current.state);
|
|
126
|
+
if (parts.base.length && !containsUpdate(stateBytes, parts.base)) fail("missing-base", "The page no longer contains the journal's base");
|
|
127
|
+
if (!parts.owned.length) fail("nothing-owned", "This capsule owns no local work");
|
|
128
|
+
if (!sharesAncestry(current.state, { v: 1, g: 0, y: { seed: toBase64(parts.owned) } })) fail("foreign-lineage", "This journal shares no ancestry with this page");
|
|
129
|
+
return parts;
|
|
130
|
+
}
|
package/src/page/ybind/tokens.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { attrKeys, sameJson } from "./layout.js";
|
|
2
2
|
import { blockJson } from "../model.js";
|
|
3
|
-
import { marksKey,
|
|
3
|
+
import { marksKey, marksFields } from "./marks.js";
|
|
4
4
|
import { createProjectionMap } from "./projection-map.js";
|
|
5
5
|
|
|
6
6
|
export const implicitFlags = (doc, visible) => {
|
|
@@ -19,7 +19,16 @@ export const implicitFlags = (doc, visible) => {
|
|
|
19
19
|
});
|
|
20
20
|
return out;
|
|
21
21
|
};
|
|
22
|
-
const
|
|
22
|
+
const opaqueOf = (node) => node.attrs.raw ?? null;
|
|
23
|
+
const pickAttrs = (node) => {
|
|
24
|
+
const raw = opaqueOf(node);
|
|
25
|
+
if (raw) { const o = { ...raw }; delete o.t; delete o.pos; return o; }
|
|
26
|
+
const o = {};
|
|
27
|
+
for (const k of attrKeys(node.type)) o[k] = node.attrs[k];
|
|
28
|
+
if (node.type.name === "table") o.rows = blockJson(node).rows;
|
|
29
|
+
return o;
|
|
30
|
+
};
|
|
31
|
+
const typeOfNode = (node) => opaqueOf(node)?.t ?? node.type.name;
|
|
23
32
|
|
|
24
33
|
export function tokensOf(doc, alias = new Map(), visible = null) {
|
|
25
34
|
const toks = [];
|
|
@@ -27,9 +36,9 @@ export function tokensOf(doc, alias = new Map(), visible = null) {
|
|
|
27
36
|
doc.forEach((block, _o, index) => {
|
|
28
37
|
const implicit = flags[index] && !alias.has(block.attrs.id);
|
|
29
38
|
const brk = visible ? visible[index]?.brk ?? null : null;
|
|
30
|
-
if (!implicit) toks.push({ k: "b", id: flags[index] && alias.has(block.attrs.id) ? alias.get(block.attrs.id) : block.attrs.id, t: block
|
|
39
|
+
if (!implicit) toks.push({ k: "b", id: flags[index] && alias.has(block.attrs.id) ? alias.get(block.attrs.id) : block.attrs.id, t: typeOfNode(block), attrs: pickAttrs(block), brk });
|
|
31
40
|
if (block.isTextblock) block.content.forEach((n) => {
|
|
32
|
-
if (n.isText) { const ms = block
|
|
41
|
+
if (n.isText) { const ms = typeOfNode(block) === "code_block" ? [] : n.marks; const mk = marksKey(ms), ma = marksFields(ms); for (let i = 0; i < n.text.length; i++) toks.push({ k: "c", ch: n.text[i], mk, ma }); }
|
|
33
42
|
else if (n.type.name === "hard_break") toks.push({ k: "h" });
|
|
34
43
|
});
|
|
35
44
|
});
|
|
@@ -74,8 +83,8 @@ export function textTokenRange(doc, from, to) {
|
|
|
74
83
|
const a = Math.max(0, startOffset - cursor), b = Math.min(node.nodeSize, endOffset - cursor);
|
|
75
84
|
if (a < b) {
|
|
76
85
|
if (node.isText) {
|
|
77
|
-
const marks = block
|
|
78
|
-
const mk = marksKey(marks), ma =
|
|
86
|
+
const marks = typeOfNode(block) === "code_block" ? [] : node.marks;
|
|
87
|
+
const mk = marksKey(marks), ma = marksFields(marks);
|
|
79
88
|
for (let i = a; i < b; i++) tokens.push({ k: "c", ch: node.text[i], mk, ma });
|
|
80
89
|
} else if (node.type.name === "hard_break") tokens.push({ k: "h" });
|
|
81
90
|
}
|
package/src/page/ybind/walk.js
CHANGED
|
@@ -144,14 +144,21 @@ function walkFlow(pd, index) {
|
|
|
144
144
|
const root = recoveredRoot(sourceKey, removalsBySource.get(sourceKey) ?? [], intents);
|
|
145
145
|
const id = `recovered:${sourceKey}`;
|
|
146
146
|
const t = root ? root.type : "paragraph";
|
|
147
|
-
|
|
147
|
+
const opaque = !Object.hasOwn(schema.nodes, t);
|
|
148
|
+
cur = { kind: "b", id, baseId: id, brk: { client: it.id.client, clock: it.id.clock }, implicit: false, runs: [], len: 1, atom: !opaque && !schema.nodes[t]?.isTextblock, opaque, recovered: true, restored: !moved, sourceKey, t, fields: root ? root.attrs : null };
|
|
148
149
|
segs.push(cur);
|
|
149
150
|
prevId = id;
|
|
150
151
|
};
|
|
151
152
|
const open = (id, brk, implicit) => {
|
|
152
153
|
cur = { kind: "b", id, baseId: id, brk, implicit, runs: [], len: implicit ? 0 : 1, atom: false, items: !implicit && (markerCount.get(id) ?? 0) > 1 ? [] : null };
|
|
153
|
-
if (!implicit) {
|
|
154
|
-
|
|
154
|
+
if (!implicit) {
|
|
155
|
+
cur.t = typeOf(id);
|
|
156
|
+
// A type this runtime has no schema for still owns the text and marks up to
|
|
157
|
+
// the next block marker: it is not an atom whose following text becomes an
|
|
158
|
+
// implicit paragraph, and it is never rewritten as one.
|
|
159
|
+
cur.opaque = !Object.hasOwn(schema.nodes, cur.t);
|
|
160
|
+
cur.atom = !cur.opaque && !schema.nodes[cur.t]?.isTextblock;
|
|
161
|
+
} else cur.t = "paragraph";
|
|
155
162
|
segs.push(cur);
|
|
156
163
|
prevId = id;
|
|
157
164
|
};
|
package/src/page/ybind/writer.js
CHANGED
|
@@ -8,10 +8,23 @@ import { resolveTableFields, tableSeed, writeTable } from "./table.js";
|
|
|
8
8
|
import { resolveBoardFields, writeBoard } from "./board.js";
|
|
9
9
|
import { createOriginIndex } from "./origins.js";
|
|
10
10
|
|
|
11
|
+
const FIELD = (value) => (value === true ? {} : value);
|
|
12
|
+
const sameValue = (a, b) => sameJson(FIELD(a), FIELD(b));
|
|
13
|
+
|
|
11
14
|
export function setAttrs(pd, id, t, attrs) {
|
|
12
15
|
let m = pd.attrs.get(id);
|
|
13
16
|
if (!m) { m = new Y.Map(); pd.attrs.set(id, m); }
|
|
14
17
|
const type = schema.nodes[t];
|
|
18
|
+
// A block type this runtime has no schema for keeps its original record: no
|
|
19
|
+
// known field list to normalize it against, and never a paragraph in its place.
|
|
20
|
+
if (!type) {
|
|
21
|
+
if (m.get("t") !== t) m.set("t", t);
|
|
22
|
+
for (const [k, v] of Object.entries(attrs ?? {})) {
|
|
23
|
+
if (k === "t" || k === "pos") continue;
|
|
24
|
+
if (!sameJson(m.has(k) ? m.get(k) : undefined, v)) m.set(k, v);
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
15
28
|
const table = t === "table" && m.get("t") === "table" && !recoveredSource(id);
|
|
16
29
|
const board = t === "board" && !recoveredSource(id);
|
|
17
30
|
const before = table ? resolveTableFields(pd.tableFields, id, tableSeed(pd, id)) : board ? resolveBoardFields(pd.boardFields, id) : null;
|
|
@@ -46,6 +59,31 @@ export function liveKeys(ctx) {
|
|
|
46
59
|
return out;
|
|
47
60
|
}
|
|
48
61
|
|
|
62
|
+
// The live keys this runtime has no schema mark for, including the reserved
|
|
63
|
+
// compact-code bridge. A command never cancels them: they stay byte for byte
|
|
64
|
+
// until something that understands them rewrites them.
|
|
65
|
+
export function opaqueKeys(ctx) {
|
|
66
|
+
const cancelled = new Set();
|
|
67
|
+
for (const [k, v] of ctx) if (isCancel(k) && v) cancelled.add(k.split("!")[0]);
|
|
68
|
+
const out = [];
|
|
69
|
+
for (const [k, v] of ctx) if (!isCancel(k) && v !== null && v !== undefined && !cancelled.has(k) && !schema.marks[markName(k)]) out.push([k, v]);
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// The attributes an inserted unit carries over from its neighbours: every known
|
|
74
|
+
// key as before, and an opaque key only when the editor's mark set still names
|
|
75
|
+
// it, so text at a span boundary does not inherit a format nobody can read.
|
|
76
|
+
export function baseAttributes(ctx, want = {}) {
|
|
77
|
+
const out = {};
|
|
78
|
+
let opaque = false;
|
|
79
|
+
for (const [k, v] of ctx) if (isCancel(k) || schema.marks[markName(k)]) out[k] = v;
|
|
80
|
+
for (const [k, v] of opaqueKeys(ctx)) {
|
|
81
|
+
opaque = true;
|
|
82
|
+
if (want[markName(k)] !== undefined) out[k] = v;
|
|
83
|
+
}
|
|
84
|
+
return { attrs: out, opaque };
|
|
85
|
+
}
|
|
86
|
+
|
|
49
87
|
export function runAttributes(pd, ctx, want) {
|
|
50
88
|
const rid = mintOp(pd);
|
|
51
89
|
const changes = {};
|
|
@@ -54,12 +92,17 @@ export function runAttributes(pd, ctx, want) {
|
|
|
54
92
|
for (const [k, v] of have) {
|
|
55
93
|
const n = markName(k);
|
|
56
94
|
const w = want[n];
|
|
57
|
-
if (w !== undefined &&
|
|
95
|
+
if (w !== undefined && sameValue(w, v)) kept.add(n);
|
|
58
96
|
else changes[`${k}!${rid}`] = true;
|
|
59
97
|
}
|
|
98
|
+
for (const [k, v] of opaqueKeys(ctx)) {
|
|
99
|
+
const n = markName(k);
|
|
100
|
+
if (want[n] !== undefined && sameValue(want[n], v)) kept.add(n);
|
|
101
|
+
}
|
|
60
102
|
for (const [n, w] of Object.entries(want)) if (!kept.has(n)) changes[`${n}~${mintOp(pd)}`] = w;
|
|
61
|
-
|
|
62
|
-
|
|
103
|
+
const base = baseAttributes(ctx, want);
|
|
104
|
+
if (!Object.keys(changes).length && !base.opaque) return undefined;
|
|
105
|
+
return { ...base.attrs, ...changes };
|
|
63
106
|
}
|
|
64
107
|
|
|
65
108
|
export const CHUNK_CHARS = 2000;
|
|
@@ -119,7 +162,7 @@ function insertChunk(pd, yIdx, toks, fences = null) {
|
|
|
119
162
|
i += run.length;
|
|
120
163
|
run = "";
|
|
121
164
|
};
|
|
122
|
-
const embed = (e) => { const attrs =
|
|
165
|
+
const embed = (e) => { const attrs = baseAttributes(ctxAt(pd, i)).attrs; beforeFence(pd, i, fences, () => pd.flow.insertEmbed(i, e, attrs)); i += 1; };
|
|
123
166
|
for (const t of toks) {
|
|
124
167
|
if (t.k === "c") { if (runMk !== null && t.mk !== runMk) flush(); runMk = t.mk; runMa = t.ma; run += t.ch; }
|
|
125
168
|
else if (t.k === "h") { flush(); runMk = null; embed({ br: 1 }); }
|
|
@@ -249,6 +292,7 @@ export function applyRegion(ctx, ta, oldToks, newToks, oldSurvives = null) {
|
|
|
249
292
|
else insertTokens(pd, map.toPhysical(e.at, "before"), e.i.toks, ctx.tx, ctx.fences);
|
|
250
293
|
}
|
|
251
294
|
for (const [ob, nb] of attrs) ctx.tx(() => {
|
|
295
|
+
if (!schema.nodes[nb.t]) return;
|
|
252
296
|
const source = ctx.recovered.get(ob.id);
|
|
253
297
|
if (source) {
|
|
254
298
|
if (ob.t !== nb.t) pd.gone.set(attrKey(source, "t"), nb.t);
|