@panphora/clayjs 1.15.1 → 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,30 @@
|
|
|
1
|
+
// One-shot proof of the mode this tab was in, written just before a reload and read
|
|
2
|
+
// once, at the top of is-edit-mode.js. It is a hint, like the cookie: the host still
|
|
3
|
+
// authorizes every save.
|
|
4
|
+
import { docKey } from "../lib/doc-storage.js";
|
|
5
|
+
|
|
6
|
+
const key = () => docKey("mode-receipt");
|
|
7
|
+
const MAX_AGE_MS = 30000;
|
|
8
|
+
|
|
9
|
+
function navigationType() {
|
|
10
|
+
try { return performance.getEntriesByType("navigation")[0]?.type ?? null; } catch { return null; }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function takeReceipt() {
|
|
14
|
+
let raw = null;
|
|
15
|
+
try { raw = sessionStorage.getItem(key()); sessionStorage.removeItem(key()); } catch { return null; }
|
|
16
|
+
if (!raw) return null;
|
|
17
|
+
let receipt;
|
|
18
|
+
try { receipt = JSON.parse(raw); } catch { return null; }
|
|
19
|
+
if (receipt?.v !== 1 || receipt.path !== location.pathname) return null;
|
|
20
|
+
if (receipt.mode !== "edit" && receipt.mode !== "view") return null;
|
|
21
|
+
if (Date.now() - receipt.at > MAX_AGE_MS) return null;
|
|
22
|
+
if (!receipt.internal && navigationType() !== "reload") return null;
|
|
23
|
+
return receipt;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function writeReceipt({ mode, canEdit, internal }) {
|
|
27
|
+
try {
|
|
28
|
+
sessionStorage.setItem(key(), JSON.stringify({ v: 1, path: location.pathname, mode, canEdit, at: Date.now(), internal }));
|
|
29
|
+
} catch {}
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// What a refused mode change tells the person who asked. A request that was joined,
|
|
2
|
+
// superseded or dropped because the page moved on says nothing.
|
|
3
|
+
const TEXT = {
|
|
4
|
+
"permission-denied": "You don't have permission to edit this document.",
|
|
5
|
+
"save-failed": "Couldn't save, so this page stays in edit mode.",
|
|
6
|
+
"host-timeout": "The workspace didn't answer, so this page stays as it was.",
|
|
7
|
+
};
|
|
8
|
+
const QUIET = new Set(["busy", "cancelled"]);
|
|
9
|
+
|
|
10
|
+
export function modeRefusalText(result) {
|
|
11
|
+
if (!result || result.ok !== false || QUIET.has(result.code)) return null;
|
|
12
|
+
return TEXT[result.code] || result.message || "Couldn't switch modes.";
|
|
13
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Which authored resource a live switch can start and take back, decided before
|
|
2
|
+
// preparation rather than discovered halfway through it. Which edit module is supported
|
|
3
|
+
// is a separate table (mode-support.js). This module is a dynamic target: a reader never
|
|
4
|
+
// fetches it.
|
|
5
|
+
//
|
|
6
|
+
// A style or a stylesheet link is one attribute, and the existing type flip restores it.
|
|
7
|
+
// A script that has run cannot be un-run, so a document whose served bytes carry an
|
|
8
|
+
// `editmode:resource` script has no reversible entry: its script would have to execute
|
|
9
|
+
// again, and re-executing it on every entry is exactly what the reload path exists to
|
|
10
|
+
// avoid. Such a document keeps the guarded reload.
|
|
11
|
+
import { enableAdminResources, disableAdminResources } from "./admin-resources.js";
|
|
12
|
+
|
|
13
|
+
export const RESOURCE_SELECTOR = '[editmode\\:resource]:is(style, link, script)';
|
|
14
|
+
export const SCRIPT_SELECTOR = 'script[editmode\\:resource]';
|
|
15
|
+
|
|
16
|
+
export function resourceSupport(root = document) {
|
|
17
|
+
const scripts = [...root.querySelectorAll(SCRIPT_SELECTOR)];
|
|
18
|
+
return { reversible: scripts.length === 0, scripts };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// The adapter's lease over the authored resources: a reversible flip for the styles and
|
|
22
|
+
// stylesheets. A document whose script could not be taken back never reaches it, because
|
|
23
|
+
// the caller refuses that document before preparation.
|
|
24
|
+
export function resourcesLease(root = document) {
|
|
25
|
+
return {
|
|
26
|
+
enable() {
|
|
27
|
+
enableAdminResources(root);
|
|
28
|
+
},
|
|
29
|
+
quiesce() {},
|
|
30
|
+
resume() {},
|
|
31
|
+
dispose() {
|
|
32
|
+
disableAdminResources();
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function resourcesParticipant(root = document) {
|
|
38
|
+
return {
|
|
39
|
+
id: "clay:resources",
|
|
40
|
+
order: 100,
|
|
41
|
+
prepare() {
|
|
42
|
+
const { reversible } = resourceSupport(root);
|
|
43
|
+
if (!reversible) {
|
|
44
|
+
throw Object.assign(
|
|
45
|
+
new Error("clay.modeLifecycle: an editmode:resource script cannot be re-run"),
|
|
46
|
+
{ code: "unsupported-plugin" }
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return resourcesLease(root);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Which resolved module a live switch can start and take back, keyed by the path the
|
|
2
|
+
// loader resolves. This module is data only — it imports nothing and evaluates nothing —
|
|
3
|
+
// and it is a dynamic target: a reader never fetches it.
|
|
4
|
+
//
|
|
5
|
+
// A path that is not a row here is unsupported, never assumed supported, so a plugin that
|
|
6
|
+
// arrives through an implication, a default, a `[sap]` root or template metadata is
|
|
7
|
+
// checked exactly like one the URL named. The caller takes the reload path before
|
|
8
|
+
// anything is prepared.
|
|
9
|
+
//
|
|
10
|
+
// "neutral" page-lifetime machinery that acts only under the committed mode or write
|
|
11
|
+
// admission, so a live entry can import it and leave it alone
|
|
12
|
+
// "export" the module's namespace exports `modeParticipant`, prepared as a lease
|
|
13
|
+
// "reader" the view plan's reader, owned by the transition rather than a lease
|
|
14
|
+
// function a reader-loaded module whose participant lives in its edit chunk:
|
|
15
|
+
// `(mod) => Promise<participant>`, so the reader never loads it
|
|
16
|
+
export const MODULE_SUPPORT = {
|
|
17
|
+
// The always wave: state mirrors, a key handler and the facades both modes read.
|
|
18
|
+
"lib/mutation.js": "neutral",
|
|
19
|
+
"core/preserve-authored.js": "neutral",
|
|
20
|
+
"core/edit-mode.js": "neutral",
|
|
21
|
+
"core/mode-key.js": "neutral",
|
|
22
|
+
"core/stale-host-notice.js": "neutral",
|
|
23
|
+
"core/page-data.js": "neutral",
|
|
24
|
+
// Snapshot, save core and the facades over them: functions and transforms read at
|
|
25
|
+
// capture and save time, never at evaluation.
|
|
26
|
+
"core/snapshot.js": "neutral",
|
|
27
|
+
"core/save-core.js": "neutral",
|
|
28
|
+
"core/recovery.js": "neutral",
|
|
29
|
+
"core/unsaved-warning.js": "neutral",
|
|
30
|
+
// These run on a save capture or after a saved event, which only a committed edit
|
|
31
|
+
// produces, and each handler rechecks the mode or admission at event time, so an old
|
|
32
|
+
// edit event is never replayed after an exit.
|
|
33
|
+
"attrs/onaftersave.js": "neutral",
|
|
34
|
+
"attrs/refetch-on-save.js": "neutral",
|
|
35
|
+
"lib/cache-bust.js": "neutral",
|
|
36
|
+
"attrs/save-freeze.js": "neutral",
|
|
37
|
+
"core/persist.js": "neutral",
|
|
38
|
+
"attrs/add-from.js": "neutral",
|
|
39
|
+
"core/etag.js": "neutral",
|
|
40
|
+
// The modules that keep a session and hand the controller a lease.
|
|
41
|
+
"core/save.js": "export",
|
|
42
|
+
"core/conflict-notice.js": "export",
|
|
43
|
+
"core/autosave.js": "export",
|
|
44
|
+
"core/admin-attrs.js": "export",
|
|
45
|
+
"plugins/indicator.js": "export",
|
|
46
|
+
"plugins/richclay.js": "export",
|
|
47
|
+
"plugins/sortable.js": "export",
|
|
48
|
+
"plugins/undo.js": "export",
|
|
49
|
+
"plugins/unsent.js": "export",
|
|
50
|
+
"plugins/upload.js": "export",
|
|
51
|
+
"plugins/people.js": "export",
|
|
52
|
+
"plugins/live.js": "export",
|
|
53
|
+
"plugins/page.js": "export",
|
|
54
|
+
"plugins/visitor-drafts-owner.js": "export",
|
|
55
|
+
// A view boot already loaded these three, and each reads the mode at event time.
|
|
56
|
+
"plugins/menu.js": "neutral",
|
|
57
|
+
"plugins/upgrade.js": "neutral",
|
|
58
|
+
"plugins/derive.js": "neutral",
|
|
59
|
+
// A binding has no import-time work, and its disposal belongs to the application's own
|
|
60
|
+
// participant.
|
|
61
|
+
"plugins/bind-text.js": "neutral",
|
|
62
|
+
// The view plan's reader, owned by the transition rather than a lease: the controller
|
|
63
|
+
// prepares no lease for a `"reader"` row.
|
|
64
|
+
"plugins/page-view.js": "reader",
|
|
65
|
+
// The source map fetches, pairs and installs a save renderer, so it is a session: the
|
|
66
|
+
// lease installs on a live entry and clears the renderer when the page goes back to view.
|
|
67
|
+
"plugins/source.js": "export",
|
|
68
|
+
// Generic sync: the module a reader and an editor both load, and the one whose
|
|
69
|
+
// session starts regardless of mode. Its participant is in the edit chunk.
|
|
70
|
+
"sync/live-sync.js": (mod) => mod.liveSync.editParticipant(),
|
|
71
|
+
// The channel to a local process: its lease holds admission for the edit requests it
|
|
72
|
+
// sends, so a live entry opens it at the commit and an exit closes it again.
|
|
73
|
+
"plugins/wire.js": "export",
|
|
74
|
+
// The AI composer: the lease builds the chrome on a live entry and takes it down again.
|
|
75
|
+
"plugins/ai-edit.js": "export",
|
|
76
|
+
// The CMS: the source owns the session, so the lease that opens it is the one that
|
|
77
|
+
// closes it.
|
|
78
|
+
"plugins/cms.js": "export",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const supportFor = (path) =>
|
|
82
|
+
Object.prototype.hasOwnProperty.call(MODULE_SUPPORT, path) ? MODULE_SUPPORT[path] : null;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// The live switch's decision, and the one lease every session starts with.
|
|
2
|
+
//
|
|
3
|
+
// A document that signs `<html clay-mode-switch="live">` is saying its code can enter and
|
|
4
|
+
// leave edit repeatedly. That claim is checked here, before anything is prepared: every
|
|
5
|
+
// module either of its plans resolves must have a support row and nothing it authored may
|
|
6
|
+
// have to run again, or the document keeps the guarded reload. The same goes for a queued
|
|
7
|
+
// participant that is not one: a lease nobody can call is not a lease.
|
|
8
|
+
//
|
|
9
|
+
// The controller reaches this module, and only the controller: a reader pays for the
|
|
10
|
+
// declaration and nothing else.
|
|
11
|
+
import { peekModeParticipants, registerModeParticipant } from "./is-edit-mode.js";
|
|
12
|
+
import { resourceSupport, resourcesParticipant } from "./mode-resources.js";
|
|
13
|
+
import { supportFor } from "./mode-support.js";
|
|
14
|
+
|
|
15
|
+
let resourcesQueued = false;
|
|
16
|
+
|
|
17
|
+
// A participant queued before boot may be a promise; the controller awaits that one
|
|
18
|
+
// itself. Anything else has to have the shape a lease is prepared from, and its id has to
|
|
19
|
+
// be unique across the queue. Validating never drains: a malformed queue is left as it is.
|
|
20
|
+
const participantsReady = () => {
|
|
21
|
+
const ids = new Set();
|
|
22
|
+
for (const entry of peekModeParticipants()) {
|
|
23
|
+
if (typeof entry?.then === "function") continue;
|
|
24
|
+
const id = entry?.id;
|
|
25
|
+
if (typeof id !== "string" || typeof entry?.prepare !== "function") return false;
|
|
26
|
+
if (ids.has(id)) return false;
|
|
27
|
+
ids.add(id);
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Every module either road resolves: the edit plan with its own core wave, the plugins an
|
|
33
|
+
// implication, a default, a `[sap]` root or template metadata pulled in, and the plan this
|
|
34
|
+
// tab booted. Passing that whole set is the point — a plugin nobody named is checked
|
|
35
|
+
// exactly like one the URL did.
|
|
36
|
+
const resolvedPaths = (handle) => [
|
|
37
|
+
...(handle?.edit?.core ?? []),
|
|
38
|
+
...(handle?.edit?.plugins ?? []),
|
|
39
|
+
...(handle?.edit?.late ?? []),
|
|
40
|
+
...(handle?.view?.plugins ?? []),
|
|
41
|
+
...(handle?.view?.late ?? []),
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Which of the modules this document resolves this release cannot start and take back.
|
|
46
|
+
*
|
|
47
|
+
* @param {{edit?: object, view?: object}} handle - both resolved plans
|
|
48
|
+
* @returns {string[]}
|
|
49
|
+
*/
|
|
50
|
+
export function unsupportedPaths(handle) {
|
|
51
|
+
return [...new Set(resolvedPaths(handle))].filter((path) => supportFor(path) === null);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* May this document switch live, through the plans it resolved?
|
|
56
|
+
*
|
|
57
|
+
* @param {{edit?: object, view?: object}} handle - both resolved plans
|
|
58
|
+
* @returns {boolean}
|
|
59
|
+
*/
|
|
60
|
+
export function liveAllowed(handle) {
|
|
61
|
+
return participantsReady() && unsupportedPaths(handle).length === 0 && resourceSupport().reversible;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The authored styles and stylesheets are the first lease of an entry: `liveAllowed` proved
|
|
65
|
+
// them reversible, so the entry flips them and the exit flips them back. Registered once,
|
|
66
|
+
// through the queue the dispatcher already holds, while the page is idle in view.
|
|
67
|
+
export function openResources() {
|
|
68
|
+
if (resourcesQueued) return;
|
|
69
|
+
resourcesQueued = true;
|
|
70
|
+
registerModeParticipant(resourcesParticipant());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// The abort an entry reports when its own deadline or a newer generation cancelled it:
|
|
74
|
+
// named as the platform names a cancelled import, so a caller can tell it apart from a
|
|
75
|
+
// module that failed.
|
|
76
|
+
const cancelled = () =>
|
|
77
|
+
Object.assign(new Error("clay.modeLifecycle: the import was cancelled"), { name: "AbortError", code: "aborted" });
|
|
78
|
+
|
|
79
|
+
// One deadline covers the whole transition, so the reader's half of it is bounded by the
|
|
80
|
+
// same number the controller uses for everything else.
|
|
81
|
+
const DEADLINE_MS = 15000;
|
|
82
|
+
|
|
83
|
+
const fault = (message, code) => Object.assign(new Error(`clayjs: ${message}`), { code });
|
|
84
|
+
|
|
85
|
+
// The promise, or the entry's cancellation, whichever settles first: an acquisition that
|
|
86
|
+
// never answers is a reload, not a session built on nothing.
|
|
87
|
+
const within = (promise, signal) =>
|
|
88
|
+
new Promise((resolve, reject) => {
|
|
89
|
+
if (signal?.aborted) return reject(cancelled());
|
|
90
|
+
const abort = () => reject(cancelled());
|
|
91
|
+
signal?.addEventListener?.("abort", abort);
|
|
92
|
+
Promise.resolve(promise).then(
|
|
93
|
+
(value) => {
|
|
94
|
+
signal?.removeEventListener?.("abort", abort);
|
|
95
|
+
resolve(value);
|
|
96
|
+
},
|
|
97
|
+
(err) => {
|
|
98
|
+
signal?.removeEventListener?.("abort", abort);
|
|
99
|
+
reject(err);
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// A handoff answers with the file it was built from, never with a promise of one: a
|
|
105
|
+
// reader that represents no bytes has nothing a session can be built on.
|
|
106
|
+
const noFile = (handoff) => !handoff?.represented || handoff.represented.bytes === null;
|
|
107
|
+
|
|
108
|
+
// The instance inside a `sync/live-sync.js` namespace: the module exports the singleton
|
|
109
|
+
// under its own name and as the default, and both name the same object.
|
|
110
|
+
const syncLane = (mod) => (mod && (mod.liveSync ?? mod.default ?? mod)) || null;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Ask the reader a declared document already runs for its half of the switch: the file
|
|
114
|
+
* it represents, the queue it is holding, and the calls that apply, give back and retire
|
|
115
|
+
* them.
|
|
116
|
+
*
|
|
117
|
+
* The page-view reader answers first, because a Document's reader owns the article; a
|
|
118
|
+
* generic-sync document's saved lane answers next. A document with neither has no reader
|
|
119
|
+
* and needs no handoff. A reader that represents no file throws `not-acquired`, which is
|
|
120
|
+
* the reload: nothing has been prepared yet, so the page can still be replaced.
|
|
121
|
+
*
|
|
122
|
+
* @param {object} handle - the loader's handle: `{loaded, modules, attach, ...}`
|
|
123
|
+
* @param {{signal?: AbortSignal}} [context] - the entry's context
|
|
124
|
+
* @returns {Promise<?object>}
|
|
125
|
+
*/
|
|
126
|
+
export async function prepareReader(handle, context = {}) {
|
|
127
|
+
const signal = context.signal;
|
|
128
|
+
const options = { signal, deadline: DEADLINE_MS };
|
|
129
|
+
const pageView = handle?.loaded?.["plugins/page-view.js"];
|
|
130
|
+
if (pageView?.ready) {
|
|
131
|
+
await within(pageView.acquired, signal);
|
|
132
|
+
const handoff = await pageView.prepareHandoff(options);
|
|
133
|
+
if (noFile(handoff)) throw fault("the reader has no file to hand over", "not-acquired");
|
|
134
|
+
return handoff;
|
|
135
|
+
}
|
|
136
|
+
const lane = syncLane(handle?.loaded?.["sync/live-sync.js"]);
|
|
137
|
+
if (lane && !lane.isDestroyed && lane.lane === "saved") {
|
|
138
|
+
const handoff = await lane.prepareHandoff(options);
|
|
139
|
+
if (noFile(handoff)) throw fault("the reader has no file to hand over", "not-acquired");
|
|
140
|
+
return handoff;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Start the reader a view holds, from the file the session left: the page-view reader
|
|
147
|
+
* paints that receipt at once and asks the host for nothing, and the saved lane starts
|
|
148
|
+
* with it, so a second entry has a file without waiting for a frame. Every reader the page
|
|
149
|
+
* ran before the entry starts again, so a page-view reader that follows generic sync's
|
|
150
|
+
* saved lane gets that lane back too. `accepted` null starts the reader as a view boot does.
|
|
151
|
+
*
|
|
152
|
+
* @param {object} handle - the loader's handle
|
|
153
|
+
* @param {?{bytes: string, etag: ?string, seq: ?number}} accepted - the receipt to start from
|
|
154
|
+
*/
|
|
155
|
+
export function startReader(handle, accepted = null) {
|
|
156
|
+
const pageView = handle?.loaded?.["plugins/page-view.js"];
|
|
157
|
+
// The plan this page booted, not what an entry imported: a Document's edit plan
|
|
158
|
+
// carries generic sync as well, and the reader is restarted as the one it was.
|
|
159
|
+
const sync = Array.isArray(handle?.view?.plugins)
|
|
160
|
+
? handle.view.plugins.includes("sync/live-sync.js")
|
|
161
|
+
: Boolean(handle?.loaded?.["sync/live-sync.js"]);
|
|
162
|
+
if (pageView) {
|
|
163
|
+
const start = pageView.restart ?? pageView.start;
|
|
164
|
+
start({ sync, represented: accepted });
|
|
165
|
+
// Every reader the page ran before the entry comes back, not the first one found:
|
|
166
|
+
// with `plugins=page,sync` the page-view reader follows generic sync's saved lane,
|
|
167
|
+
// so that lane is restarted too and the article keeps following other tabs.
|
|
168
|
+
if (!sync) return undefined;
|
|
169
|
+
}
|
|
170
|
+
const lane = syncLane(handle?.loaded?.["sync/live-sync.js"]);
|
|
171
|
+
if (lane) return lane.start(lane.currentFile, { lane: "saved", represented: accepted });
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Import an edit plan's missing modules through the loader's own record and import cache.
|
|
177
|
+
*
|
|
178
|
+
* One path at a time, core then plugins then the late tail, in plan order: the same order
|
|
179
|
+
* boot imports them in, for the same reasons. A path boot already loaded is skipped, so
|
|
180
|
+
* nothing evaluates twice; a path in flight is awaited, so boot's late tail and an entry
|
|
181
|
+
* that overlap share one import; and a rejected thunk is dropped from the cache so the
|
|
182
|
+
* next attempt calls it again. It attaches the edit members once the core has landed and
|
|
183
|
+
* each plugin's member as that plugin lands, exactly as boot does, and it announces
|
|
184
|
+
* nothing: the plugin-ready events belong to a committed entry, not to an import.
|
|
185
|
+
*
|
|
186
|
+
* @param {object} handle - the loader's handle: `{loaded, modules, attach, attachEditCore, loadEditHints, late}`
|
|
187
|
+
* @param {{core?: string[], plugins?: string[], late?: string[]}} plan
|
|
188
|
+
* @param {{signal?: AbortSignal}} [options]
|
|
189
|
+
* @returns {Promise<string[]>} the plugin paths this call newly loaded
|
|
190
|
+
*/
|
|
191
|
+
export async function importPlan(handle, plan, { signal } = {}) {
|
|
192
|
+
const inflight = (handle.inflight ||= new Map());
|
|
193
|
+
const newlyLoaded = [];
|
|
194
|
+
|
|
195
|
+
const stop = () => { if (signal?.aborted) throw cancelled(); };
|
|
196
|
+
|
|
197
|
+
// The loader's own hint fetch, which preloads what it returns against this plan. A failed
|
|
198
|
+
// fetch defers hints, never an import already mapped.
|
|
199
|
+
const hints = handle.loadEditHints?.();
|
|
200
|
+
if (hints) {
|
|
201
|
+
try {
|
|
202
|
+
await hints;
|
|
203
|
+
} catch {}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const importPath = (path) => {
|
|
207
|
+
if (!inflight.has(path)) {
|
|
208
|
+
const promise = Promise.resolve()
|
|
209
|
+
.then(() => handle.modules[path]())
|
|
210
|
+
.catch((err) => {
|
|
211
|
+
inflight.delete(path);
|
|
212
|
+
throw err;
|
|
213
|
+
});
|
|
214
|
+
inflight.set(path, promise);
|
|
215
|
+
}
|
|
216
|
+
return inflight.get(path);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
for (const path of plan.core ?? []) {
|
|
220
|
+
if (handle.loaded[path]) continue;
|
|
221
|
+
stop();
|
|
222
|
+
const mod = await importPath(path);
|
|
223
|
+
handle.loaded[path] = mod;
|
|
224
|
+
mod.install?.();
|
|
225
|
+
}
|
|
226
|
+
// Once, after the core wave: the members those modules offer, added to the facade
|
|
227
|
+
// boot assembled rather than replacing it.
|
|
228
|
+
handle.attachEditCore(handle.loaded);
|
|
229
|
+
|
|
230
|
+
for (const path of plan.plugins ?? []) {
|
|
231
|
+
if (handle.loaded[path]) continue;
|
|
232
|
+
stop();
|
|
233
|
+
const mod = await importPath(path);
|
|
234
|
+
handle.loaded[path] = mod;
|
|
235
|
+
handle.attach(path, mod);
|
|
236
|
+
newlyLoaded.push(path);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const late = (plan.late ?? []).filter((path) => !handle.loaded[path]);
|
|
240
|
+
if (late.length) {
|
|
241
|
+
// Boot's tail may be importing these same paths right now, and it records them in the
|
|
242
|
+
// same table a moment after each import resolves. Waiting for it costs nothing an
|
|
243
|
+
// entry was not already waiting for, and it is what keeps one import per path.
|
|
244
|
+
stop();
|
|
245
|
+
try {
|
|
246
|
+
await handle.late;
|
|
247
|
+
} catch {}
|
|
248
|
+
for (const path of late) {
|
|
249
|
+
if (handle.loaded[path]) continue;
|
|
250
|
+
stop();
|
|
251
|
+
const mod = await importPath(path);
|
|
252
|
+
handle.loaded[path] = mod;
|
|
253
|
+
handle.attach(path, mod);
|
|
254
|
+
newlyLoaded.push(path);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return newlyLoaded;
|
|
259
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// The save barrier: the one write an exit is allowed to make, and the pause around it.
|
|
2
|
+
//
|
|
3
|
+
// A switch has closed input admission before this runs, so an ordinary save cannot go
|
|
4
|
+
// out. The barrier is not an ordinary save: it carries a permit minted for the mode
|
|
5
|
+
// generation it is leaving, it fastens the work no save can write (registered models,
|
|
6
|
+
// uploads, a composer waiting on its person), it writes the page's bytes, and it answers
|
|
7
|
+
// only when the host has accepted bytes that include everything the page still holds.
|
|
8
|
+
//
|
|
9
|
+
// It also takes the document-operation slot, because a flush is only meaningful if
|
|
10
|
+
// nothing else replaces the document while it runs. A caller that already owns that slot
|
|
11
|
+
// (the mode controller's exit) hands its token in, so the barrier does not queue behind
|
|
12
|
+
// its own claim: a standalone call still claims one and releases it.
|
|
13
|
+
//
|
|
14
|
+
// This module is a dynamic target reached from the mode controller: a reader never
|
|
15
|
+
// fetches it.
|
|
16
|
+
import { claimDocumentOperation, releaseDocumentOperation, operationKind, saveFateIsUnknown } from "./save-core.js";
|
|
17
|
+
import { flushSave, isSaveConflicted, openSavePermit, closeSavePermit, saveGeneration } from "./save.js";
|
|
18
|
+
import { flushUnsaved, pendingIds } from "../lib/unsaved-state.js";
|
|
19
|
+
import { bytesUnsaved } from "./unsaved-warning.js";
|
|
20
|
+
|
|
21
|
+
const DEADLINE_MS = 15000;
|
|
22
|
+
const EXPIRED = Symbol("the barrier deadline passed");
|
|
23
|
+
// Two passes at most: the first writes, and a second only covers input that arrived
|
|
24
|
+
// while the first request was on the wire. Anything still unsaved after that refuses.
|
|
25
|
+
const MAX_PASSES = 2;
|
|
26
|
+
|
|
27
|
+
const refusal = (code, extra = {}) => ({ ok: false, code, ...extra });
|
|
28
|
+
|
|
29
|
+
// One wait of the barrier, bounded by its own deadline: a source whose flush never
|
|
30
|
+
// settles is the deadline's business, not an exit that never returns.
|
|
31
|
+
async function bounded(pending, deadline) {
|
|
32
|
+
let timer = null;
|
|
33
|
+
const expired = new Promise((resolve) => {
|
|
34
|
+
timer = setTimeout(() => resolve(EXPIRED), Math.max(0, deadline - Date.now()));
|
|
35
|
+
});
|
|
36
|
+
try {
|
|
37
|
+
return await Promise.race([Promise.resolve(pending), expired]);
|
|
38
|
+
} finally {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { claimDocumentOperation as claimOperation, releaseDocumentOperation as releaseOperation, operationKind };
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Fasten everything this page holds that a save cannot write, then write its bytes.
|
|
47
|
+
*
|
|
48
|
+
* Answers `{ ok: true, state, etag }` only when the host accepted bytes that include
|
|
49
|
+
* every edit and every registered value on the page, under `generation`'s permit. Every
|
|
50
|
+
* other answer is `{ ok: false, code }` from the transition vocabulary: `busy`,
|
|
51
|
+
* `stale-document`, `pending-input`, `conflict`, `save-unknown`, `save-failed`,
|
|
52
|
+
* `timeout`. Nothing about the page's state moves on a refusal, and nothing is aborted:
|
|
53
|
+
* a write already on the wire keeps its own outcome.
|
|
54
|
+
*
|
|
55
|
+
* @param {Object} [options]
|
|
56
|
+
* @param {number} [options.generation] - the mode generation this exit is leaving
|
|
57
|
+
* @param {boolean} [options.keepalive]
|
|
58
|
+
* @param {number} [options.timeoutMs] - the whole barrier, the pending-source flush
|
|
59
|
+
* included, so a source that never settles refuses as `timeout`
|
|
60
|
+
* @param {Object} [options.operation] - the slot its caller already holds; claimed here when absent
|
|
61
|
+
* @returns {Promise<{ok: boolean, code?: string, state?: string, etag?: ?string, pending?: string[]}>}
|
|
62
|
+
*/
|
|
63
|
+
export async function runSaveBarrier({ generation = saveGeneration(), keepalive = false, timeoutMs = DEADLINE_MS, operation = null } = {}) {
|
|
64
|
+
const claimed = operation ? null : claimDocumentOperation("mode");
|
|
65
|
+
if (!operation && !claimed) return refusal("busy");
|
|
66
|
+
const deadline = Date.now() + timeoutMs;
|
|
67
|
+
const permit = openSavePermit(generation);
|
|
68
|
+
try {
|
|
69
|
+
for (let pass = 0; ; pass++) {
|
|
70
|
+
if (generation !== saveGeneration()) return refusal("stale-document");
|
|
71
|
+
if (Date.now() >= deadline) return refusal("timeout");
|
|
72
|
+
|
|
73
|
+
// Work that finishes on its own — a checkpoint, a queued upload — is asked to. A
|
|
74
|
+
// composer waiting on its person has no flush and stays pending: that is a refusal,
|
|
75
|
+
// never a blur or a submit on their behalf.
|
|
76
|
+
const sources = await bounded(flushUnsaved(), deadline);
|
|
77
|
+
if (sources === EXPIRED) return refusal("timeout");
|
|
78
|
+
if (!sources.ok) return refusal("pending-input", { pending: sources.pending });
|
|
79
|
+
if (sources.pending.length) return refusal("pending-input", { pending: sources.pending });
|
|
80
|
+
|
|
81
|
+
// Neither a conflict nor an unresolved fate is a failure a retry fixes.
|
|
82
|
+
if (isSaveConflicted()) return refusal("conflict");
|
|
83
|
+
if (saveFateIsUnknown()) return refusal("save-unknown");
|
|
84
|
+
|
|
85
|
+
let result;
|
|
86
|
+
try {
|
|
87
|
+
result = await flushSave({ keepalive, timeoutMs: Math.max(0, deadline - Date.now()), permit });
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (err?.state === "conflict") return refusal("conflict");
|
|
90
|
+
if (err?.state === "blocked") return refusal("pending-input", { pending: pendingIds() });
|
|
91
|
+
if (saveFateIsUnknown()) return refusal("save-unknown");
|
|
92
|
+
if (Date.now() >= deadline) return refusal("timeout");
|
|
93
|
+
return refusal("save-failed");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (result.state !== "clean" && result.state !== "saved") return refusal("save-failed");
|
|
97
|
+
// The host answered, but an answer is only an exit when nothing is left behind.
|
|
98
|
+
if (isSaveConflicted()) return refusal("conflict");
|
|
99
|
+
if (saveFateIsUnknown()) return refusal("save-unknown");
|
|
100
|
+
const pending = pendingIds();
|
|
101
|
+
if (pending.length) return refusal("pending-input", { pending });
|
|
102
|
+
if (bytesUnsaved()) {
|
|
103
|
+
if (pass + 1 < MAX_PASSES && Date.now() < deadline) continue;
|
|
104
|
+
return refusal("pending-input");
|
|
105
|
+
}
|
|
106
|
+
return { ok: true, state: result.state, etag: result.etag ?? null };
|
|
107
|
+
}
|
|
108
|
+
} finally {
|
|
109
|
+
closeSavePermit(permit);
|
|
110
|
+
if (claimed) releaseDocumentOperation(claimed);
|
|
111
|
+
}
|
|
112
|
+
}
|