@panphora/clayjs 1.15.2 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/THIRD-PARTY-NOTICES.md +16 -0
- package/compat/highlight-11.9.0.min.js +1213 -0
- package/compat/highlight-LICENSE +29 -0
- package/compat/journal-assets.lock.json +22 -0
- package/compat/overtype-2.6.1.min.js +116 -0
- package/compat/overtype-LICENSE +21 -0
- package/dist/all.js +90 -0
- package/dist/clay-dom.js +84 -0
- package/dist/clay-events.js +103 -0
- package/dist/clay-internals.js +85 -0
- package/dist/clay-options.js +93 -0
- package/dist/clay-router.js +98 -0
- package/dist/clay-ui.js +95 -0
- package/dist/clay-utils.js +84 -0
- package/dist/clay.js +146 -0
- package/dist/clay.standalone.js +83090 -58706
- package/dist/m/attrs/add-from-T5LJXFFI.js +1 -0
- package/dist/m/attrs/onaftersave-OKSWEJQF.js +1 -0
- package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +1 -0
- package/dist/m/attrs/save-freeze-FUP4Q4X3.js +1 -0
- package/dist/m/c-2AZDZWCK.js +1 -0
- package/dist/m/c-2JL6E75N.js +1 -0
- package/dist/m/c-2M73252O.js +1 -0
- package/dist/m/c-2SPVQSHC.js +1 -0
- package/dist/m/c-33HUYEDI.js +1 -0
- package/dist/m/c-33JJ6CBX.js +2 -0
- package/dist/m/c-35NBGMZB.js +1 -0
- package/dist/m/c-3GJ4SDE2.js +1 -0
- package/dist/m/c-3IQRECYS.js +1 -0
- package/dist/m/c-3OWDMNJR.js +2 -0
- package/dist/m/c-46XYM55L.js +1 -0
- package/dist/m/c-4B6H4G5V.js +1 -0
- package/dist/m/c-4DRMS4QG.js +9 -0
- package/dist/m/c-4FFHZM32.js +1 -0
- package/dist/m/c-4JZDZFF6.js +1 -0
- package/dist/m/c-4L3BTFRK.js +1 -0
- package/dist/m/c-4Q2XTQ2N.js +2 -0
- package/dist/m/c-4WKFKEKF.js +1 -0
- package/dist/m/c-4YSCRICO.js +1 -0
- package/dist/m/c-54RCDCBU.js +1 -0
- package/dist/m/c-5M4YEKCX.js +4 -0
- package/dist/m/c-5R4PTRG2.js +1 -0
- package/dist/m/c-5V3MOVJT.js +1 -0
- package/dist/m/c-5VYOWDTI.js +1 -0
- package/dist/m/c-5Y6TNX5W.js +34 -0
- package/dist/m/c-5YIHJ3TJ.js +1 -0
- package/dist/m/c-66O32MV3.js +28 -0
- package/dist/m/c-6HMB24EV.js +1 -0
- package/dist/m/c-7DJDKPGL.js +1 -0
- package/dist/m/c-7O6AFR65.js +1 -0
- package/dist/m/c-7SWMYKAI.js +1 -0
- package/dist/m/c-7VGZ7RU5.js +1 -0
- package/dist/m/c-A7LS5UZW.js +1 -0
- package/dist/m/c-ANQTJ3B5.js +2 -0
- package/dist/m/c-AQLFEATB.js +1 -0
- package/dist/m/c-AYVOREBE.js +1 -0
- package/dist/m/c-B2ZXPOS3.js +3 -0
- package/dist/m/c-BBVGTQG7.js +1 -0
- package/dist/m/c-BMIZKMPV.js +1 -0
- package/dist/m/c-BTDI4Y23.js +1 -0
- package/dist/m/c-BWFVSBDF.js +1 -0
- package/dist/m/c-BYTKL6UX.js +1 -0
- package/dist/m/c-CFHW5AMY.js +6 -0
- package/dist/m/c-CJZSLMVQ.js +1 -0
- package/dist/m/c-CYWLPPUS.js +1 -0
- package/dist/m/c-CZUSOB7V.js +1 -0
- package/dist/m/c-DBE6ORSI.js +1 -0
- package/dist/m/c-DCFSEJZG.js +1 -0
- package/dist/m/c-DNZ5IFM6.js +1 -0
- package/dist/m/c-EDHUR4MX.js +1 -0
- package/dist/m/c-EJTGYSLN.js +1 -0
- package/dist/m/c-EY6UOM54.js +1 -0
- package/dist/m/c-F3CKJTJS.js +1 -0
- package/dist/m/c-F7Y6FLPY.js +1 -0
- package/dist/m/c-FA4DZVTC.js +1 -0
- package/dist/m/c-FILLTJA7.js +1 -0
- package/dist/m/c-G2BUZITR.js +1 -0
- package/dist/m/c-GB3JTV6E.js +1 -0
- package/dist/m/c-GBGWXTX4.js +1 -0
- package/dist/m/c-GHBCCVG4.js +1 -0
- package/dist/m/c-GJAEGGXY.js +1 -0
- package/dist/m/c-GOWBDM6J.js +1 -0
- package/dist/m/c-GQ6F6IDK.js +1 -0
- package/dist/m/c-H4LG442Q.js +1 -0
- package/dist/m/c-HA6INT6J.js +1 -0
- package/dist/m/c-HPURYVZS.js +2 -0
- package/dist/m/c-HRDCD3WN.js +67 -0
- package/dist/m/c-HT33EQRN.js +1 -0
- package/dist/m/c-HVYXIS72.js +1 -0
- package/dist/m/c-IB52R237.js +1 -0
- package/dist/m/c-IJUK2GPW.js +1 -0
- package/dist/m/c-IKWNE4R4.js +1 -0
- package/dist/m/c-IU3IYEU6.js +1 -0
- package/dist/m/c-IZ2EBRAJ.js +1 -0
- package/dist/m/c-J7CA54IP.js +1 -0
- package/dist/m/c-JAX2667S.js +6 -0
- package/dist/m/c-KI7M4AY3.js +1 -0
- package/dist/m/c-KIPQJX4B.js +1 -0
- package/dist/m/c-KKWLDOPM.js +1 -0
- package/dist/m/c-KR4UW4KW.js +1 -0
- package/dist/m/c-KY3VJGUA.js +8 -0
- package/dist/m/c-L4WQSC27.js +1 -0
- package/dist/m/c-LFPHCXY3.js +1 -0
- package/dist/m/c-LR4CGEPT.js +91 -0
- package/dist/m/c-LRPUO2MX.js +1 -0
- package/dist/m/c-LTGMXXKO.js +1 -0
- package/dist/m/c-LXENEBV7.js +1 -0
- package/dist/m/c-LZJPNR2W.js +1 -0
- package/dist/m/c-MMUIUUZG.js +2 -0
- package/dist/m/c-MTQHWQC3.js +1 -0
- package/dist/m/c-N326R2JD.js +1 -0
- package/dist/m/c-N7JPUPGC.js +1 -0
- package/dist/m/c-NMJRXKPC.js +8 -0
- package/dist/m/c-NRPJEVJS.js +1 -0
- package/dist/m/c-NUHP2XDB.js +1 -0
- package/dist/m/c-NZMNXVF6.js +1 -0
- package/dist/m/c-OCSZ4QGN.js +1 -0
- package/dist/m/c-OIBUTDXI.js +1 -0
- package/dist/m/c-OIDRZKAF.js +1 -0
- package/dist/m/c-OYTFP5QN.js +1 -0
- package/dist/m/c-P7KNDG34.js +1 -0
- package/dist/m/c-PPOSCAQD.js +1 -0
- package/dist/m/c-PQ5LK6QE.js +993 -0
- package/dist/m/c-PZ4XEPUP.js +2 -0
- package/dist/m/c-QHIWXHRO.js +1 -0
- package/dist/m/c-QHWJ5ZIJ.js +1 -0
- package/dist/m/c-QIX5BJYN.js +1 -0
- package/dist/m/c-QNKREOL6.js +1 -0
- package/dist/m/c-QQHEX6HA.js +1 -0
- package/dist/m/c-R7RMANJI.js +9 -0
- package/dist/m/c-RI4NT75Y.js +1 -0
- package/dist/m/c-RM7J7BV2.js +1 -0
- package/dist/m/c-RUJKSUQW.js +1 -0
- package/dist/m/c-RVMWGXEJ.js +1 -0
- package/dist/m/c-S7H2DRCB.js +1 -0
- package/dist/m/c-S7LK4KUE.js +1 -0
- package/dist/m/c-SGCCC4FW.js +25 -0
- package/dist/m/c-SGIA4IEQ.js +188 -0
- package/dist/m/c-SWRJFCNN.js +1 -0
- package/dist/m/c-T4VO4OCB.js +1 -0
- package/dist/m/c-TCCA5BZN.js +2 -0
- package/dist/m/c-TQEMVJCS.js +1 -0
- package/dist/m/c-TQZ2JYHD.js +1 -0
- package/dist/m/c-TR6RMDQ7.js +3 -0
- package/dist/m/c-TRS6TM6O.js +1 -0
- package/dist/m/c-UGHH4MB3.js +1 -0
- package/dist/m/c-UKHTLWUW.js +1 -0
- package/dist/m/c-UL2YUY52.js +1 -0
- package/dist/m/c-UNN7LWCT.js +1 -0
- package/dist/m/c-UXBMRDZB.js +235 -0
- package/dist/m/c-V7PZZRG7.js +1 -0
- package/dist/m/c-VAK7SG37.js +1 -0
- package/dist/m/c-VDSS4K3Z.js +1 -0
- package/dist/m/c-VFX45IZQ.js +1 -0
- package/dist/m/c-VHNSBD2M.js +1 -0
- package/dist/m/c-VKEDWYDF.js +1 -0
- package/dist/m/c-VL52ADDR.js +1 -0
- package/dist/m/c-WN5AY3OZ.js +1 -0
- package/dist/m/c-WNYBLIOZ.js +1 -0
- package/dist/m/c-WRBKEJD4.js +1 -0
- package/dist/m/c-WWDBYXLK.js +1 -0
- package/dist/m/c-WYFDLITL.js +1 -0
- package/dist/m/c-XN6MBKI6.js +67 -0
- package/dist/m/c-XNNW2CWK.js +1 -0
- package/dist/m/c-XPELB6E5.js +1 -0
- package/dist/m/c-XQXZAO7N.js +1 -0
- package/dist/m/c-Y6USGTS5.js +5 -0
- package/dist/m/c-YDH7Q3NY.js +1 -0
- package/dist/m/c-YG2NJYS6.js +135 -0
- package/dist/m/c-Z36J63PP.js +1 -0
- package/dist/m/c-Z4VRC7GU.js +7 -0
- package/dist/m/c-Z6K52OLP.js +19 -0
- package/dist/m/c-ZDY2MDAA.js +21 -0
- package/dist/m/c-ZE46QDIH.js +1 -0
- package/dist/m/c-ZUCOKOHT.js +1 -0
- package/dist/m/core/admin-attrs-VGNLAUDK.js +1 -0
- package/dist/m/core/autosave-E4PEFPZ6.js +1 -0
- package/dist/m/core/conflict-notice-VQ6Y64PH.js +1 -0
- package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
- package/dist/m/core/host-permission-XN3K2IW3.js +1 -0
- package/dist/m/core/is-edit-mode-JXHDVGUL.js +1 -0
- package/dist/m/core/persist-HKHA66IY.js +3 -0
- package/dist/m/core/recovery-TBBSICE2.js +1 -0
- package/dist/m/core/save-WUCHTQBE.js +1 -0
- package/dist/m/core/save-core-FJVHT5M3.js +1 -0
- package/dist/m/core/snapshot-F3SWDZMM.js +1 -0
- package/dist/m/core/unsaved-warning-STKBHE6G.js +1 -0
- package/dist/m/dom/all-APS5ZSKP.js +1 -0
- package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
- package/dist/m/events/index-23DPTUT4.js +1 -0
- package/dist/m/internals/index-WOVAP3RU.js +1 -0
- package/dist/m/lib/cache-bust-CGWT6ZJI.js +1 -0
- package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
- package/dist/m/loader-HXX6YNH5.js +1 -0
- package/dist/m/manifest-F22DEDAC.js +1 -0
- package/dist/m/manifest-edit-C3C7376B.js +1 -0
- package/dist/m/manifest.json +3491 -0
- package/dist/m/options/options-2LH76JQ3.js +1 -0
- package/dist/m/page/editor-XUTB2HB2.js +189 -0
- package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +6 -0
- package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
- package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
- package/dist/m/plugins/demo-ZIBFAA3R.js +1 -0
- package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
- package/dist/m/plugins/indicator-PV4YW2ID.js +1 -0
- package/dist/m/plugins/live-NUTTXSBS.js +1 -0
- package/dist/m/plugins/menu-HCKO3DSP.js +138 -0
- package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
- package/dist/m/plugins/page-KSUH7G6J.js +2 -0
- package/dist/m/plugins/page-view-W73OW7WI.js +1 -0
- package/dist/m/plugins/people-AJJ32YCG.js +1 -0
- package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
- package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
- package/dist/m/plugins/richclay-RC3L2F6X.js +517 -0
- package/dist/m/plugins/richclay-RC3L2F6X.js.LEGAL.txt +1 -0
- package/dist/m/plugins/sortable-YRMQX3ZU.js +1 -0
- package/dist/m/plugins/source-KHK5UUSK.js +1 -0
- package/dist/m/plugins/undo-OF2L3CXU.js +1 -0
- package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
- package/dist/m/plugins/upgrade-ZNYODP5Y.js +1 -0
- package/dist/m/plugins/upload-XSGNILUY.js +1 -0
- package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +21 -0
- package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +1 -0
- package/dist/m/plugins/wire-ZWVWKF76.js +1 -0
- package/dist/m/reader-core-ILWLXVMP.js +68 -0
- package/dist/m/route/route-KOCZSEGN.js +1 -0
- package/dist/m/sync/live-sync-4AUVXVJU.js +1 -0
- package/dist/m/ui/index-EVHKMKBL.js +1 -0
- package/dist/m/utils/index-4TJCVLT7.js +1 -0
- package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
- package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
- package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
- package/dist/offline-manifest.json +3800 -0
- package/dist/page/themes/light-1.css +406 -0
- package/dist/server/core.cjs +23662 -22860
- package/dist/server/offline-runtime.cjs +4954 -0
- package/dist/server/offline-runtime.mjs +4923 -0
- package/dist/server/rooms.mjs +9 -0
- package/docs/guide.md +1078 -669
- package/entries/all.js +2 -0
- package/entries/clay-data.js +1 -1
- package/entries/clay-dom.js +2 -0
- package/entries/clay-events.js +2 -0
- package/entries/clay-internals.js +2 -0
- package/entries/clay-options.js +2 -0
- package/entries/clay-router.js +2 -0
- package/entries/clay-ui.js +2 -0
- package/entries/clay-utils.js +2 -0
- package/entries/clay.js +2 -0
- package/entries/sap.js +6 -6
- package/fonts/ibm-plex-mono-LICENSE +93 -0
- package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-LICENSE +93 -0
- package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- package/fonts/ibm-plex.css +71 -0
- package/fonts/inter-LICENSE +93 -0
- package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
- package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-greek-wght-normal.woff2 +0 -0
- package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
- package/fonts/inter.css +62 -0
- package/fonts/newsreader-LICENSE +93 -0
- package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
- package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
- package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
- package/fonts/newsreader.css +26 -0
- package/fonts/work-sans-LICENSE +93 -0
- package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
- package/fonts/work-sans.css +8 -0
- package/package.json +20 -3
- package/packed-contract.json +1 -0
- package/src/attrs/add-from.js +27 -4
- package/src/attrs/save-freeze.js +7 -52
- package/src/core/admin-attrs.js +119 -12
- package/src/core/admin-contenteditable.js +12 -10
- package/src/core/admin-inputs.js +14 -15
- package/src/core/admin-onclick.js +2 -0
- package/src/core/autosave.js +74 -21
- package/src/core/conflict-download.js +22 -0
- package/src/core/conflict-notice-ui.js +1173 -0
- package/src/core/conflict-notice.js +140 -697
- package/src/core/edit-mode.js +147 -14
- package/src/core/etag.js +30 -12
- package/src/core/host-meta.js +72 -8
- package/src/core/host-permission.js +29 -0
- package/src/core/is-edit-mode.js +145 -12
- package/src/core/mode-key.js +125 -0
- package/src/core/mode-lifecycle.js +544 -0
- package/src/core/mode-receipt.js +30 -0
- package/src/core/mode-refusal.js +13 -0
- package/src/core/mode-resources.js +52 -0
- package/src/core/mode-support.js +82 -0
- package/src/core/mode-switch.js +259 -0
- package/src/core/mode-transition.js +112 -0
- package/src/core/page-data-write.js +305 -0
- package/src/core/page-data.js +26 -227
- package/src/core/page-format-wire.js +4 -0
- package/src/core/page-format.js +58 -0
- package/src/core/persist.js +37 -9
- package/src/core/preserve-authored.js +114 -0
- package/src/core/recovery.js +772 -0
- package/src/core/runtime-url.js +105 -0
- package/src/core/save-core.js +109 -12
- package/src/core/save-replace.js +72 -0
- package/src/core/save-size-notice.js +134 -0
- package/src/core/save-size.js +140 -0
- package/src/core/save.js +805 -118
- package/src/core/snapshot.js +121 -11
- package/src/core/source-map.js +157 -9
- package/src/core/unsaved-warning.js +14 -4
- package/src/core/upgrade-run.js +408 -0
- package/src/core/version.js +18 -0
- package/src/lib/assets.js +30 -0
- package/src/lib/autosave-debug-gate.js +50 -0
- package/src/lib/autosave-scheduler.js +141 -0
- package/src/lib/boot-imports.js +8 -0
- package/src/lib/derived.js +3 -1
- package/src/lib/dirty-gate.js +15 -0
- package/src/lib/doc-storage.js +33 -0
- package/src/lib/effect.js +9 -4
- package/src/lib/mutation.js +4 -2
- package/src/lib/offline-runtime.js +226 -0
- package/src/lib/preload.js +48 -0
- package/src/lib/root-attrs.js +1 -1
- package/src/lib/trash.js +18 -0
- package/src/lib/undo/capture-move.js +152 -0
- package/src/live/local-channel.js +61 -0
- package/src/live/local-lane.js +60 -9
- package/src/live/server-lane.js +6 -3
- package/src/loader-logic.js +76 -15
- package/src/loader.js +218 -30
- package/src/page/adopt-boot.js +76 -22
- package/src/page/adopt.js +25 -4
- package/src/page/awareness.js +230 -0
- package/src/page/blocks.js +79 -5
- package/src/page/blocksel.js +20 -4
- package/src/page/board-face.js +13 -0
- package/src/page/board-panel.js +5 -17
- package/src/page/board-style.js +2 -13
- package/src/page/board-view.js +1 -11
- package/src/page/board-views.js +17 -0
- package/src/page/board.js +2 -2
- package/src/page/boot-pack.js +26 -0
- package/src/page/boot.js +237 -0
- package/src/page/checklist.js +1 -1
- package/src/page/chrome.js +411 -61
- package/src/page/csv-import.js +17 -2
- package/src/page/document-title-dom.js +40 -0
- package/src/page/document-title-runtime.js +76 -0
- package/src/page/document-title.js +9 -0
- package/src/page/drag.js +48 -13
- package/src/page/drop.js +2 -1
- package/src/page/editor.js +330 -86
- package/src/page/feature-loader.js +72 -0
- package/src/page/features/board.js +12 -0
- package/src/page/features/code.js +13 -0
- package/src/page/features/media.js +290 -0
- package/src/page/features/registry.js +23 -0
- package/src/page/features/table.js +383 -0
- package/src/page/{tool-view.js → features/tool.js} +33 -2
- package/src/page/format-codec.js +215 -0
- package/src/page/format-gate.js +179 -0
- package/src/page/format-level.js +157 -0
- package/src/page/highlight-slot.js +23 -0
- package/src/page/highlight.js +30 -16
- package/src/page/history-intent.js +78 -0
- package/src/page/inline.js +18 -25
- package/src/page/items.js +52 -11
- package/src/page/link.js +1 -0
- package/src/page/markdown.js +86 -1
- package/src/page/media-views.js +3 -0
- package/src/page/media.js +33 -245
- package/src/page/outline-cache.js +11 -0
- package/src/page/placeholder.js +27 -7
- package/src/page/projection.js +196 -50
- package/src/page/reader-patch.js +306 -0
- package/src/page/recover.js +1 -1
- package/src/page/rich/page-tool.js +1 -4
- package/src/page/runtime-owner.js +27 -0
- package/src/page/save-projection.js +7 -4
- package/src/page/schema.js +14 -0
- package/src/page/table-rect-key.js +3 -0
- package/src/page/table-select.js +4 -3
- package/src/page/table-style.js +1 -1
- package/src/page/table-views.js +3 -0
- package/src/page/table.js +8 -293
- package/src/page/themes/light-1.css +139 -2
- package/src/page/themes/light-1.source.css +269 -0
- package/src/page/tool-capability.js +3 -0
- package/src/page/tool-views.js +3 -0
- package/src/page/view.js +390 -107
- package/src/page/ybind/awareness.js +172 -0
- package/src/page/ybind/binding.js +9 -1
- package/src/page/ybind/bridge.js +50 -47
- package/src/page/ybind/decode.js +52 -19
- package/src/page/ybind/doc.js +27 -2
- package/src/page/ybind/group.js +12 -3
- package/src/page/ybind/index.js +2 -0
- package/src/page/ybind/lineage.js +71 -0
- package/src/page/ybind/marks.js +25 -1
- package/src/page/ybind/migrate.js +48 -13
- package/src/page/ybind/moves.js +32 -21
- package/src/page/ybind/positions.js +50 -0
- package/src/page/ybind/provider.js +84 -22
- package/src/page/ybind/recovery-proof.js +191 -0
- package/src/page/ybind/recovery-replay.js +130 -0
- package/src/page/ybind/tokens.js +15 -6
- package/src/page/ybind/walk.js +10 -3
- package/src/page/ybind/writer.js +48 -4
- package/src/plugins/ai-edit.js +167 -32
- package/src/plugins/bind-text.js +218 -0
- package/src/plugins/cms.js +93 -0
- package/src/plugins/indicator.js +57 -5
- package/src/plugins/live.js +282 -34
- package/src/plugins/menu/claygoo.svg.js +1 -0
- package/src/plugins/menu/seo-head.js +323 -0
- package/src/plugins/menu/seo.js +849 -0
- package/src/plugins/menu.js +425 -0
- package/src/plugins/page-view.js +131 -0
- package/src/plugins/page.js +1022 -275
- package/src/plugins/people.js +158 -31
- package/src/plugins/richclay.js +119 -9
- package/src/plugins/sortable.js +370 -60
- package/src/plugins/source.js +189 -366
- package/src/plugins/undo.js +289 -19
- package/src/plugins/unsent.js +132 -0
- package/src/plugins/upgrade-run.js +1 -0
- package/src/plugins/upgrade.js +210 -0
- package/src/plugins/upload-task.js +571 -0
- package/src/plugins/upload-ui.js +265 -0
- package/src/plugins/upload.js +34 -153
- package/src/plugins/visitor-drafts/inert.js +32 -0
- package/src/plugins/visitor-drafts/owner-chip.js +3 -0
- package/src/plugins/visitor-drafts/prompt.js +2 -2
- package/src/plugins/visitor-drafts-owner.js +85 -0
- package/src/plugins/visitor-drafts.js +31 -42
- package/src/plugins/wire.js +82 -1
- package/src/reader-core.js +26 -0
- package/src/recovery/conflicts.js +287 -0
- package/src/recovery/identity.js +154 -0
- package/src/recovery/store.js +792 -0
- package/src/recovery/undo.js +257 -0
- package/src/server/checkpoint/index.js +68 -4
- package/src/server/offline-runtime/index.js +214 -0
- package/src/server/rooms/index.js +2 -1
- package/src/server/rooms/live-data-rooms.js +2 -2
- package/src/server/rooms/page-admission.js +33 -0
- package/src/server/rooms/page-lineage.js +56 -0
- package/src/server/rooms/page-rebase.js +11 -5
- package/src/server/rooms/page-state.js +2 -0
- package/src/server/rooms/page-write-admission.js +9 -3
- package/src/source/session.js +705 -0
- package/src/sync/conflict-revert.js +5 -4
- package/src/sync/conflicts-edit.js +119 -0
- package/src/sync/conflicts-facade.js +49 -0
- package/src/sync/conflicts.js +99 -1
- package/src/sync/editing-marker.js +37 -6
- package/src/sync/editing-sender.js +8 -1
- package/src/sync/live-sync-edit.js +63 -0
- package/src/sync/live-sync.js +364 -119
- package/src/sync/local-presence.js +187 -0
- package/src/sync/merge-declarations.js +10 -0
- package/src/sync/merge-engine.js +12 -0
- package/src/sync/presence-store.js +412 -0
- package/src/sync/presence-target.js +243 -0
- package/src/sync/presence.js +72 -41
- package/src/sync/saved-handoff.js +200 -0
- package/src/sync/saved-stream.js +535 -0
- package/src/sync/version-guard.js +92 -0
- package/src/sync/wire-profile.js +81 -0
- package/src/ui/bevel-controls.js +2 -8
- package/src/ui/page-scheme.js +7 -0
- package/src/ui/skins/cms.js +1 -1
- package/src/ui/theme-config.js +150 -0
- package/src/ui/theme.js +6 -138
- package/src/ui/toast-lazy.js +11 -0
- package/src/vendor/LICENSES-optional.md +253 -0
- package/src/vendor/highlight.vendor.js +3 -0
- package/src/vendor/hljs-bash.vendor.js +2 -0
- package/src/vendor/hljs-css.vendor.js +2 -0
- package/src/vendor/hljs-javascript.vendor.js +2 -0
- package/src/vendor/hljs-json.vendor.js +2 -0
- package/src/vendor/hljs-python.vendor.js +2 -0
- package/src/vendor/hljs-xml.vendor.js +2 -0
- package/src/vendor/hyper-html-api-data.vendor.js +1 -1
- package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
- package/src/vendor/hyper-morph-identity.vendor.js +2 -0
- package/src/vendor/hyper-morph-json.vendor.js +3 -0
- package/src/vendor/hyper-morph.vendor.js +6 -6
- package/src/vendor/hyper-undo.vendor.js +1 -1
- package/src/vendor/hypercms.vendor.js +169 -84
- package/src/vendor/marked.vendor.js +68 -0
- package/src/vendor/overtype.vendor.js +994 -0
- package/src/vendor/richclay.vendor.js +17 -17
- package/src/vendor/sap-core.vendor.js +5 -5
- package/src/vendor/sap-ownership.vendor.js +11 -7
package/src/plugins/indicator.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { startsAtImport } from "../core/is-edit-mode.js";
|
|
2
2
|
import onDomReady from "../lib/dom-ready.js";
|
|
3
3
|
import { set, style } from "../lib/hostile-css.js";
|
|
4
4
|
import { bevelSurface, pageScheme } from "../ui/bevel-controls.js";
|
|
@@ -79,13 +79,65 @@ function show(state) {
|
|
|
79
79
|
if (!STICKY.has(state)) hideTimer = setTimeout(() => { set(node, "opacity", "0"); }, 2200);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
let listeners = null;
|
|
83
|
+
|
|
84
|
+
// Arming is idempotent: the plugin's own boot and a mode lease both ask for the same
|
|
85
|
+
// listeners, and one exit must be able to take all of them back.
|
|
86
|
+
function arm() {
|
|
84
87
|
saveFeedback.chip = true;
|
|
88
|
+
if (listeners) return;
|
|
89
|
+
listeners = new AbortController();
|
|
90
|
+
const { signal } = listeners;
|
|
85
91
|
for (const state of Object.keys(LABELS)) {
|
|
86
|
-
document.addEventListener("clay:save-" + state, () => show(state));
|
|
92
|
+
document.addEventListener("clay:save-" + state, () => show(state), { signal });
|
|
87
93
|
}
|
|
88
|
-
document.addEventListener("clay:page-save-state", (event) => show(event.detail?.state));
|
|
94
|
+
document.addEventListener("clay:page-save-state", (event) => show(event.detail?.state), { signal });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function disarm() {
|
|
98
|
+
listeners?.abort();
|
|
99
|
+
listeners = null;
|
|
100
|
+
clearTimeout(hideTimer);
|
|
101
|
+
hideTimer = null;
|
|
102
|
+
el?.remove();
|
|
103
|
+
el = null;
|
|
89
104
|
}
|
|
90
105
|
|
|
106
|
+
function init() {
|
|
107
|
+
if (!startsAtImport()) return;
|
|
108
|
+
arm();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The save chip as a mode lease.
|
|
113
|
+
*
|
|
114
|
+
* The chip owns no input: it reports a save somebody else is making, so an exit never
|
|
115
|
+
* waits on it. `quiesce` leaves the chip and its fade timer exactly as they were, which
|
|
116
|
+
* is what a refusal has to keep showing; `resume` has nothing to reopen. `initial: true`
|
|
117
|
+
* adopts the chip the boot already armed and arms nothing again. `dispose` (after the
|
|
118
|
+
* barrier) takes the chip, its listeners and its timer down, so a save event in view
|
|
119
|
+
* draws nothing.
|
|
120
|
+
*/
|
|
121
|
+
export function indicatorParticipant() {
|
|
122
|
+
return {
|
|
123
|
+
id: "clay:indicator",
|
|
124
|
+
order: 700,
|
|
125
|
+
prepare(context = {}) {
|
|
126
|
+
return {
|
|
127
|
+
enable() {
|
|
128
|
+
if (context.initial === true) return;
|
|
129
|
+
arm();
|
|
130
|
+
},
|
|
131
|
+
quiesce() {},
|
|
132
|
+
resume() {},
|
|
133
|
+
dispose() {
|
|
134
|
+
disarm();
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { indicatorParticipant as modeParticipant };
|
|
142
|
+
|
|
91
143
|
onDomReady(init);
|
package/src/plugins/live.js
CHANGED
|
@@ -15,13 +15,14 @@ import { withKeepalive } from "../core/save-core.js";
|
|
|
15
15
|
import { isEditMode } from "../core/is-edit-mode.js";
|
|
16
16
|
import { registerUnsavedState } from "../lib/unsaved-state.js";
|
|
17
17
|
import { createCheckpoints } from "../live/checkpoint.js";
|
|
18
|
-
import {
|
|
18
|
+
import { parseJsonRelaxed } from "../vendor/hyper-morph-json.vendor.js";
|
|
19
19
|
import * as hostMeta from "../core/host-meta.js";
|
|
20
20
|
import { createFastLane } from "../live/fast-lane.js";
|
|
21
21
|
import { createSaveHistoryRecovery, RECOVERY_WINDOW_MS } from "../live/save-history.js";
|
|
22
22
|
import { serverTransport } from "../live/server-lane.js";
|
|
23
23
|
import { localTransport, localLaneSupported } from "../live/local-lane.js";
|
|
24
24
|
import { captureYValue } from "../page/ybind/parts.js";
|
|
25
|
+
import { lastSeenEtag, recordEtag } from "../core/etag.js";
|
|
25
26
|
|
|
26
27
|
const NAME = /^[A-Za-z0-9_.:-]{1,64}$/;
|
|
27
28
|
const entries = new Map();
|
|
@@ -406,17 +407,25 @@ function sortedKeys(value) {
|
|
|
406
407
|
|
|
407
408
|
function canonical(text) {
|
|
408
409
|
try {
|
|
409
|
-
return JSON.stringify(sortedKeys(
|
|
410
|
+
return JSON.stringify(sortedKeys(parseJsonRelaxed(text)));
|
|
410
411
|
} catch {
|
|
411
412
|
return null;
|
|
412
413
|
}
|
|
413
414
|
}
|
|
414
415
|
|
|
416
|
+
// The writer session a mode lease owns (see liveWriterParticipant). Null until one is
|
|
417
|
+
// prepared, and null again once it is disposed: a page whose loader has not registered
|
|
418
|
+
// a writer keeps the old rule, one lane per edit-mode tab. While a session has closed
|
|
419
|
+
// admission (entry preparing, or an exit draining what this tab still holds) no local
|
|
420
|
+
// op is framed and no checkpoint saves.
|
|
421
|
+
let writerSession = null;
|
|
422
|
+
const writerAdmitted = () => !writerSession || writerSession.admitted;
|
|
423
|
+
|
|
415
424
|
// Saved stores with changes no accepted save holds yet. Only an edit-mode tab
|
|
416
425
|
// saves. In file mode that is this tab's unconfirmed ops; in fast mode the room
|
|
417
426
|
// says the file does not reach its latest change, and only the writer saves.
|
|
418
427
|
function unsavedLiveChanges() {
|
|
419
|
-
if (!isEditMode) return false;
|
|
428
|
+
if (!isEditMode || !writerAdmitted()) return false;
|
|
420
429
|
for (const entry of entries.values()) {
|
|
421
430
|
if (entry.store.ephemeral) continue;
|
|
422
431
|
if (entry.store.handle.status.mode === "fast") {
|
|
@@ -437,7 +446,7 @@ function fileMoved() {
|
|
|
437
446
|
|
|
438
447
|
const checkpoints = createCheckpoints({
|
|
439
448
|
needsSave: unsavedLiveChanges,
|
|
440
|
-
blocked: () => !baselineSettled() || isSaveConflicted() || autosaveIsSuspended() || historyPending(),
|
|
449
|
+
blocked: () => !writerAdmitted() || !baselineSettled() || isSaveConflicted() || autosaveIsSuspended() || historyPending(),
|
|
441
450
|
save: () => savePageForce().finally(fileMoved),
|
|
442
451
|
saveNow: () => withKeepalive(true, () => savePageForce()),
|
|
443
452
|
});
|
|
@@ -446,6 +455,9 @@ const checkpoints = createCheckpoints({
|
|
|
446
455
|
// clay.live() call in an edit-mode tab.
|
|
447
456
|
let fast = null;
|
|
448
457
|
let laneRequested = false;
|
|
458
|
+
// Bumped by every request and every release, so an answer that arrives after its
|
|
459
|
+
// session was released starts nothing.
|
|
460
|
+
let laneRequest = 0;
|
|
449
461
|
let serverLane = null;
|
|
450
462
|
// The host merges exact save provenance (live-by v2); without it a save that
|
|
451
463
|
// needs provenance is refused rather than risk applying an edit twice.
|
|
@@ -455,6 +467,144 @@ let local = null;
|
|
|
455
467
|
// forward): only a closing page's save is reported to the local room at capture.
|
|
456
468
|
let closing = false;
|
|
457
469
|
|
|
470
|
+
// A listener for the store behind a session: a reader's tag, and the values other
|
|
471
|
+
// tabs saved to it, keep following the file.
|
|
472
|
+
function sessionStores() {
|
|
473
|
+
return [...entries.values()].filter((entry) => !entry.store.ephemeral);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// What this tab still holds that the page's own bytes cannot carry: ops a store in a
|
|
477
|
+
// room has not had acknowledged. A reader's own change is not one of them.
|
|
478
|
+
function liveWriterPending() {
|
|
479
|
+
if (!writerSession || writerSession.admitted || !fast || fast.status.stopped) return false;
|
|
480
|
+
for (const entry of sessionStores()) {
|
|
481
|
+
if (entry.store.handle.status.mode === "fast" && entry.store.pending.length > 0) return true;
|
|
482
|
+
}
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Frame what the lane has not sent and wait for the room to take it. This is the one
|
|
487
|
+
// transfer that lets the writer be released: a checkpoint writes the room's value, so
|
|
488
|
+
// an unacknowledged op of this tab has to travel through the room, not the file.
|
|
489
|
+
export async function drainLiveWriter({ timeoutMs = 5000 } = {}) {
|
|
490
|
+
const deadline = Date.now() + timeoutMs;
|
|
491
|
+
const frame = () => {
|
|
492
|
+
for (const entry of sessionStores()) {
|
|
493
|
+
if (entry.store.handle.status.mode !== "fast") continue;
|
|
494
|
+
if (entry.store.pending.length > 0) fast?.release(entry.store.name);
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
frame();
|
|
498
|
+
while (liveWriterPending()) {
|
|
499
|
+
if (Date.now() >= deadline) return { ok: false, code: "timeout" };
|
|
500
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
501
|
+
frame();
|
|
502
|
+
}
|
|
503
|
+
return { ok: true };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
registerUnsavedState({ id: "live:writer", isPending: liveWriterPending, flush: drainLiveWriter });
|
|
507
|
+
|
|
508
|
+
// The lane a session starts from: the reader's own ops are not this session's edits,
|
|
509
|
+
// and no position a previous session's lane reported is carried into this one.
|
|
510
|
+
function resetWriterSession() {
|
|
511
|
+
for (const entry of entries.values()) {
|
|
512
|
+
entry.framesSince = false;
|
|
513
|
+
const ids = entry.store.pendingEntries().map((item) => item.id);
|
|
514
|
+
if (ids.length) entry.store.rebase(entry.store.authoritative, { source: "state", dropIds: ids });
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Give back everything the lane holds: the transport's subscription (its stream
|
|
519
|
+
// handlers and its local channel), the room position, and the request for a lane, so
|
|
520
|
+
// the next session starts a fresh one.
|
|
521
|
+
function releaseWriter() {
|
|
522
|
+
const lane = fast;
|
|
523
|
+
fast = null;
|
|
524
|
+
laneRequested = false;
|
|
525
|
+
laneRequest++;
|
|
526
|
+
serverLane = null;
|
|
527
|
+
local = null;
|
|
528
|
+
provenanceHost = false;
|
|
529
|
+
// The gates went with the lane: a second entry must not read this released one as ready.
|
|
530
|
+
laneReady = null;
|
|
531
|
+
writerReady = null;
|
|
532
|
+
settleLane = null;
|
|
533
|
+
settleWriter = null;
|
|
534
|
+
lane?.stop();
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* clay.live as a mode participant: the store's writer, started by the commit that
|
|
539
|
+
* enters edit and released by the one that leaves it.
|
|
540
|
+
*
|
|
541
|
+
* `prepare` closes admission and puts the stores back on the reader's value, so
|
|
542
|
+
* nothing a reader changed can be uploaded as this person's edit. `enable` starts
|
|
543
|
+
* the tab's lane. `quiesce` closes admission again and drains the ops the room has
|
|
544
|
+
* not acknowledged, so a refused exit resumes the very same lane. `dispose` (after the
|
|
545
|
+
* barrier) stops the lane and forgets its subscription, its room and its send queue.
|
|
546
|
+
*/
|
|
547
|
+
export function liveWriterParticipant() {
|
|
548
|
+
return {
|
|
549
|
+
id: "clay:live",
|
|
550
|
+
order: 200,
|
|
551
|
+
prepare(context = {}) {
|
|
552
|
+
// An adoption is the session this tab's edit boot started: the exit takes that one
|
|
553
|
+
// back, and the entry asks for no second lane.
|
|
554
|
+
if (context.initial === true) {
|
|
555
|
+
if (!writerSession) writerSession = { admitted: true };
|
|
556
|
+
return writerLease(writerSession, { adopted: true });
|
|
557
|
+
}
|
|
558
|
+
freshLaneGates();
|
|
559
|
+
// The lane's transport opens with the preparation, not with the commit: the page
|
|
560
|
+
// (order 400) waits on the room while it prepares, and it prepares before any
|
|
561
|
+
// enable runs. Admission is still the commit's, so what a person does is what
|
|
562
|
+
// reaches the room.
|
|
563
|
+
const session = { admitted: false, wantsLane: true };
|
|
564
|
+
writerSession = session;
|
|
565
|
+
resetWriterSession();
|
|
566
|
+
ensureLane();
|
|
567
|
+
return writerLease(session);
|
|
568
|
+
},
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function releaseHeld() {
|
|
573
|
+
for (const entry of sessionStores()) {
|
|
574
|
+
if (entry.store.pending.length > 0) fast?.release(entry.store.name);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function writerLease(session, { adopted = false } = {}) {
|
|
579
|
+
return {
|
|
580
|
+
enable() {
|
|
581
|
+
if (adopted || writerSession !== session) return;
|
|
582
|
+
session.admitted = true;
|
|
583
|
+
ensureLane();
|
|
584
|
+
// A lane opened at preparation may already hold ops the page made before the
|
|
585
|
+
// commit (an adoption's), which onLocal kept back while admission was closed.
|
|
586
|
+
releaseHeld();
|
|
587
|
+
},
|
|
588
|
+
quiesce() {
|
|
589
|
+
if (writerSession !== session) return;
|
|
590
|
+
session.admitted = false;
|
|
591
|
+
return drainLiveWriter();
|
|
592
|
+
},
|
|
593
|
+
resume() {
|
|
594
|
+
if (writerSession !== session) return;
|
|
595
|
+
session.admitted = true;
|
|
596
|
+
releaseHeld();
|
|
597
|
+
},
|
|
598
|
+
dispose() {
|
|
599
|
+
if (writerSession !== session) return;
|
|
600
|
+
writerSession = null;
|
|
601
|
+
releaseWriter();
|
|
602
|
+
},
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export { liveWriterParticipant as modeParticipant };
|
|
607
|
+
|
|
458
608
|
// The page plugin's one-shot gates: the fast lane's setup, and the room's
|
|
459
609
|
// writer election. Both resolve null when this tab has no lane at all.
|
|
460
610
|
let laneReady = null;
|
|
@@ -462,11 +612,15 @@ let writerReady = null;
|
|
|
462
612
|
let settleLane = null;
|
|
463
613
|
let settleWriter = null;
|
|
464
614
|
|
|
615
|
+
// A fresh pair for a new session: the page plugin awaits these, and the ones a released
|
|
616
|
+
// session settled answer for a lane that no longer exists.
|
|
617
|
+
function freshLaneGates() {
|
|
618
|
+
laneReady = new Promise((resolve) => { settleLane = resolve; });
|
|
619
|
+
writerReady = new Promise((resolve) => { settleWriter = resolve; });
|
|
620
|
+
}
|
|
621
|
+
|
|
465
622
|
function laneGates() {
|
|
466
|
-
if (!laneReady)
|
|
467
|
-
laneReady = new Promise((resolve) => { settleLane = resolve; });
|
|
468
|
-
writerReady = new Promise((resolve) => { settleWriter = resolve; });
|
|
469
|
-
}
|
|
623
|
+
if (!laneReady) freshLaneGates();
|
|
470
624
|
return { laneReady, writerReady };
|
|
471
625
|
}
|
|
472
626
|
|
|
@@ -597,7 +751,7 @@ function seedLocalRoom() {
|
|
|
597
751
|
continue;
|
|
598
752
|
}
|
|
599
753
|
try {
|
|
600
|
-
const value =
|
|
754
|
+
const value = parseJsonRelaxed(tag.element.textContent);
|
|
601
755
|
if (value !== null && typeof value === "object") values.set(name, { value, ephemeral: tag.ephemeral });
|
|
602
756
|
} catch {}
|
|
603
757
|
}
|
|
@@ -606,17 +760,21 @@ function seedLocalRoom() {
|
|
|
606
760
|
|
|
607
761
|
function ensureLane() {
|
|
608
762
|
laneGates();
|
|
609
|
-
if (laneRequested || !isEditMode) return;
|
|
763
|
+
if (laneRequested || !(writerSession ? writerSession.admitted || writerSession.wantsLane : isEditMode)) return;
|
|
610
764
|
laneRequested = true;
|
|
765
|
+
const request = ++laneRequest;
|
|
611
766
|
hostMeta.hostSupports("live-data").then(
|
|
612
767
|
async (supported) => {
|
|
613
768
|
const provenance = supported && (await hostMeta.hostSupports("live-provenance"));
|
|
769
|
+
if (request !== laneRequest) return;
|
|
614
770
|
serverLane = supported;
|
|
615
771
|
provenanceHost = provenance;
|
|
616
772
|
if (supported) {
|
|
617
773
|
fast = createFastLane(laneOptions(serverTransport()));
|
|
618
774
|
} else if (localLaneSupported()) {
|
|
619
|
-
|
|
775
|
+
// The document's own floor picks the channel scope: a floor-1 document
|
|
776
|
+
// never shares the lane a tab that cannot read it is on.
|
|
777
|
+
local = localTransport({ seed: seedLocalRoom, floor: document.querySelector('meta[name="clay-page-format"]')?.getAttribute("content") });
|
|
620
778
|
fast = createFastLane(laneOptions(local));
|
|
621
779
|
} else {
|
|
622
780
|
settleLane?.(null);
|
|
@@ -707,7 +865,7 @@ export function serializeLines(value) {
|
|
|
707
865
|
// already holds (its frames that landed before the value's stamp).
|
|
708
866
|
function hashOfText(text) {
|
|
709
867
|
try {
|
|
710
|
-
return textHash(serializeLive(
|
|
868
|
+
return textHash(serializeLive(parseJsonRelaxed(text)));
|
|
711
869
|
} catch {
|
|
712
870
|
return null;
|
|
713
871
|
}
|
|
@@ -716,28 +874,16 @@ function hashOfText(text) {
|
|
|
716
874
|
function receive(entry, text, source, dropIds) {
|
|
717
875
|
let value;
|
|
718
876
|
try {
|
|
719
|
-
value =
|
|
877
|
+
value = parseJsonRelaxed(text);
|
|
720
878
|
} catch {
|
|
721
879
|
return;
|
|
722
880
|
}
|
|
723
881
|
if (entry.store.rebase(value, { source, ...(dropIds?.length && { dropIds }) })) entry.framesSince = true;
|
|
724
882
|
}
|
|
725
883
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
const tag = liveTags().get(name);
|
|
730
|
-
if (!tag) throw new Error(`clay.live: this page has no <script type="application/json" merge="${name}" clay="live"> tag`);
|
|
731
|
-
const text = tag.element.textContent;
|
|
732
|
-
let value;
|
|
733
|
-
try {
|
|
734
|
-
value = HyperMorph.parseJsonRelaxed(text);
|
|
735
|
-
} catch (err) {
|
|
736
|
-
throw new SyntaxError(`clay.live("${name}"): the tag's JSON does not parse: ${err.message}`);
|
|
737
|
-
}
|
|
738
|
-
const store = createStore({ name, value, ephemeral: tag.ephemeral });
|
|
739
|
-
const layout = tag.element.getAttribute("live-format") === "lines" ? "lines" : text.includes("\n") ? "pretty" : "line";
|
|
740
|
-
const entry = { store, layout, framesSince: false, last: 0, landed: 0, leftAt: lineageOf(tag.element), captureOps: new Map(), withheld: false, reading: false, timer: null, recovery: null, historicalRefreshPending: false, refreshTimer: null };
|
|
884
|
+
// One open store and what the plugin follows about it. A staged store has no lane.
|
|
885
|
+
function openEntry(name, store, { layout = "line", leftAt = null, staged = null } = {}) {
|
|
886
|
+
const entry = { store, layout, framesSince: false, last: 0, landed: 0, leftAt, captureOps: new Map(), withheld: false, reading: false, timer: null, recovery: null, historicalRefreshPending: false, refreshTimer: null, staged };
|
|
741
887
|
entries.set(name, entry);
|
|
742
888
|
store.onDelivery((event) => {
|
|
743
889
|
if (event.code === "receipt-expired") document.dispatchEvent(new CustomEvent("clay:live-notice", {
|
|
@@ -756,14 +902,79 @@ export function live(name) {
|
|
|
756
902
|
settle(entry);
|
|
757
903
|
if (store.handle.status.mode === "file" && ["undone", "refused", "decided"].includes(event.cause)) entry.leftAt = null;
|
|
758
904
|
});
|
|
759
|
-
if (!
|
|
760
|
-
store.onLocal((ops) => fast?.local(name, ops, store.mark));
|
|
905
|
+
if (!store.ephemeral) store.onLocal(() => checkpoints.changed());
|
|
906
|
+
store.onLocal((ops) => { if (!entry.staged && writerAdmitted()) fast?.local(name, ops, store.mark); });
|
|
761
907
|
store.handle.on("change", () => {
|
|
762
908
|
if (!store.ephemeral && store.handle.status.mode === "fast") checkpoints.changed();
|
|
763
909
|
});
|
|
764
|
-
|
|
910
|
+
if (!staged) {
|
|
911
|
+
fast?.storeOpened(name);
|
|
912
|
+
ensureLane();
|
|
913
|
+
}
|
|
914
|
+
return entry;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
export function live(name) {
|
|
918
|
+
const existing = entries.get(name);
|
|
919
|
+
if (existing) return existing.store.handle;
|
|
920
|
+
const tag = liveTags().get(name);
|
|
921
|
+
if (!tag) throw new Error(`clay.live: this page has no <script type="application/json" merge="${name}" clay="live"> tag`);
|
|
922
|
+
const text = tag.element.textContent;
|
|
923
|
+
let value;
|
|
924
|
+
try {
|
|
925
|
+
value = parseJsonRelaxed(text);
|
|
926
|
+
} catch (err) {
|
|
927
|
+
throw new SyntaxError(`clay.live("${name}"): the tag's JSON does not parse: ${err.message}`);
|
|
928
|
+
}
|
|
929
|
+
const store = createStore({ name, value, ephemeral: tag.ephemeral });
|
|
930
|
+
const layout = tag.element.getAttribute("live-format") === "lines" ? "lines" : text.includes("\n") ? "pretty" : "line";
|
|
931
|
+
const entry = openEntry(name, store, { layout, leftAt: lineageOf(tag.element) });
|
|
932
|
+
return entry.store.handle;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// A page whose file carries no state of its own yet: the seed in memory, no tag in
|
|
936
|
+
// the document, no request, until its first genuine op is written by the ordinary
|
|
937
|
+
// save. Only an accepted write opens its room.
|
|
938
|
+
export function stageStore(name, { value, represented = null, signal = null } = {}) {
|
|
939
|
+
const existing = entries.get(name);
|
|
940
|
+
if (existing && !existing.staged) throw new Error(`clay.live("${name}"): the store is open in a room`);
|
|
941
|
+
const entry = existing ?? openEntry(name, createStore({ name, value, ephemeral: false }), { staged: { represented, signal } });
|
|
942
|
+
return {
|
|
943
|
+
handle: entry.store.handle,
|
|
944
|
+
isPending: () => !!entry.staged && entry.store.pending.length > 0,
|
|
945
|
+
flush: (options = {}) => flushStaged(entry, options),
|
|
946
|
+
dispose() {
|
|
947
|
+
if (entry.staged && entries.get(name) === entry) entries.delete(name);
|
|
948
|
+
},
|
|
949
|
+
// The session that staged this store is over, whether or not its first write was
|
|
950
|
+
// accepted: the room keeps the value, and the next session stages its own from the
|
|
951
|
+
// bytes it represents.
|
|
952
|
+
close() {
|
|
953
|
+
if (entries.get(name) === entry) entries.delete(name);
|
|
954
|
+
},
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// The first write, through the save this tab would make anyway.
|
|
959
|
+
async function flushStaged(entry, { timeoutMs = 10_000 } = {}) {
|
|
960
|
+
const staged = entry.staged;
|
|
961
|
+
if (!staged || !entry.store.pending.length) return { ok: true };
|
|
962
|
+
if (staged.conflict) return { ok: false, code: staged.conflict };
|
|
963
|
+
const at = staged.represented?.etag || null;
|
|
964
|
+
const held = lastSeenEtag();
|
|
965
|
+
if (at && held && at !== held) return { ok: false, code: "stale-document" };
|
|
966
|
+
if (at && !held) recordEtag(at);
|
|
967
|
+
const result = await requestCheckpoint({ timeoutMs });
|
|
968
|
+
return result.ok ? { ok: true } : { ok: false, code: staged.conflict ?? result.code ?? null };
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function activateStaged(entry) {
|
|
972
|
+
if (!entry.staged) return;
|
|
973
|
+
entry.staged = null;
|
|
974
|
+
// Its file had no tag and the live page never gets one: every later capture builds it.
|
|
975
|
+
entry.tagless = true;
|
|
976
|
+
fast?.storeOpened(entry.store.name);
|
|
765
977
|
ensureLane();
|
|
766
|
-
return store.handle;
|
|
767
978
|
}
|
|
768
979
|
|
|
769
980
|
// Every capture carries each saved store's current value. A never-saved store
|
|
@@ -773,7 +984,19 @@ onSnapshot((clone, { forSave, requestRefresh }) => {
|
|
|
773
984
|
const tags = liveTags(clone, { warn: false });
|
|
774
985
|
for (const entry of entries.values()) {
|
|
775
986
|
if (entry.store.ephemeral) continue;
|
|
776
|
-
|
|
987
|
+
let tag = tags.get(entry.store.name);
|
|
988
|
+
// A staged store reaches the file here, where the boot would have put its tag, and
|
|
989
|
+
// so does one that started that way, on every save after its first.
|
|
990
|
+
if (!tag && (entry.staged || entry.tagless)) {
|
|
991
|
+
const article = clone.querySelector("article[data-page]");
|
|
992
|
+
if (!article) continue;
|
|
993
|
+
const element = article.ownerDocument.createElement("script");
|
|
994
|
+
element.setAttribute("type", "application/json");
|
|
995
|
+
element.setAttribute("merge", entry.store.name);
|
|
996
|
+
element.setAttribute("clay", "live");
|
|
997
|
+
article.parentNode.insertBefore(element, article);
|
|
998
|
+
tag = { name: entry.store.name, ephemeral: false, element };
|
|
999
|
+
}
|
|
777
1000
|
if (!tag) continue;
|
|
778
1001
|
// A recovering store's own operations are not written again while the answer
|
|
779
1002
|
// about them is still open, and not before the value the file holds now has
|
|
@@ -936,6 +1159,7 @@ function confirmPin(html, pin) {
|
|
|
936
1159
|
} else {
|
|
937
1160
|
entry.store.confirm(saved.mark);
|
|
938
1161
|
entry.leftAt = saved.lineage && tag.element.getAttribute("live-by") === saved.by ? saved.lineage : { disk: true };
|
|
1162
|
+
activateStaged(entry);
|
|
939
1163
|
}
|
|
940
1164
|
settle(entry);
|
|
941
1165
|
}
|
|
@@ -992,6 +1216,17 @@ onSaveOutcome(({ captureId, result, attempts, sent }) => {
|
|
|
992
1216
|
if (result?.ok) return;
|
|
993
1217
|
const pin = capturePins.get(captureId);
|
|
994
1218
|
if (!pin) return;
|
|
1219
|
+
const stagedEntry = [...pin.keys()].map((name) => entries.get(name)).find((entry) => entry?.staged);
|
|
1220
|
+
// Nothing was written: the staged store is clean and nothing follows.
|
|
1221
|
+
if (stagedEntry && result.code === "page-nothing-to-write") {
|
|
1222
|
+
stagedEntry.store.confirm(stagedEntry.store.mark);
|
|
1223
|
+
dropCapture(captureId);
|
|
1224
|
+
releaseBody();
|
|
1225
|
+
settle(stagedEntry);
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
// This tab's value does not descend from the stored document: never send it again.
|
|
1229
|
+
if (stagedEntry && result.code === "page-lineage-stale") stagedEntry.staged.conflict = "conflict";
|
|
995
1230
|
dropCapture(captureId);
|
|
996
1231
|
const landed = mayHaveLanded(result, sent);
|
|
997
1232
|
const plan = landed ? historyPlan(captureId) : null;
|
|
@@ -1088,6 +1323,8 @@ export function receiveFrame(html, seq = null) {
|
|
|
1088
1323
|
else if (pageTag.getAttribute("live-by") !== by) pageTag.setAttribute("live-by", by);
|
|
1089
1324
|
}
|
|
1090
1325
|
if (entry && !entry.store.ephemeral) {
|
|
1326
|
+
// An old seed is never merged into a newer article before its first write.
|
|
1327
|
+
if (entry.staged) continue;
|
|
1091
1328
|
if (entry.store.handle.status.mode === "file") receive(entry, text, "remote", dropIds);
|
|
1092
1329
|
continue;
|
|
1093
1330
|
}
|
|
@@ -1100,10 +1337,13 @@ document.addEventListener("clay:sync-applied", (event) => receiveFrame(event.det
|
|
|
1100
1337
|
// The last chance to save live changes made since the previous checkpoint. A
|
|
1101
1338
|
// keepalive body is capped near 60 KB; a larger page's save may not finish.
|
|
1102
1339
|
document.addEventListener("visibilitychange", () => {
|
|
1103
|
-
if (document.visibilityState === "hidden") checkpoints.flush();
|
|
1340
|
+
if (document.visibilityState === "hidden" && isEditMode && writerAdmitted()) checkpoints.flush();
|
|
1104
1341
|
});
|
|
1105
1342
|
window.addEventListener("pagehide", () => {
|
|
1106
1343
|
closing = true;
|
|
1344
|
+
// A view, and an edit whose session has closed admission or been released, saves
|
|
1345
|
+
// nothing here: its bytes are the reader's, and the exit's own barrier writes them.
|
|
1346
|
+
if (!isEditMode || !writerAdmitted()) return;
|
|
1107
1347
|
// A hide save still on the wire is answered after the page is gone too.
|
|
1108
1348
|
const latest = unsentCaptureId ?? wireCaptureId;
|
|
1109
1349
|
const pin = checkpoints.flushing && latest !== null ? capturePins.get(latest) : null;
|
|
@@ -1121,6 +1361,14 @@ export function storeOf(name) {
|
|
|
1121
1361
|
return entries.get(name)?.store || null;
|
|
1122
1362
|
}
|
|
1123
1363
|
|
|
1364
|
+
// A caller that opened a store itself is done with it: the next entry reads the tag the
|
|
1365
|
+
// reader now states. Only the store that caller opened is closed; one the app opened
|
|
1366
|
+
// first, or a staged store, stays.
|
|
1367
|
+
export function closeStore(name, store) {
|
|
1368
|
+
const entry = entries.get(name);
|
|
1369
|
+
if (entry && entry.store === store && !entry.staged) entries.delete(name);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1124
1372
|
export function openStores() {
|
|
1125
1373
|
return [...entries.values()].map((entry) => entry.store);
|
|
1126
1374
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-45.87 -57.96 100.26 60.98" fill="currentColor" aria-hidden="true" focusable="false"><path fill-rule="evenodd" d="M44.35 -4.56Q40.03 -0.52 34.69 -0.39Q29.34 -0.25 24.81 -0.14Q20.27 -0.02 15.6 0Q10.93 0.02 6.41 0Q1.89 -0.02 -2.79 -0.02Q-7.48 -0.02 -12.11 -0.14Q-16.73 -0.25 -21.64 -0.27Q-26.55 -0.3 -31.72 -2.25Q-36.88 -4.2 -39.87 -9.12Q-42.87 -14.03 -42.8 -19.61Q-42.74 -25.18 -40.43 -29.94Q-38.12 -34.69 -34.82 -38.78Q-31.52 -42.87 -27.25 -45.6Q-22.98 -48.33 -18.35 -50.59Q-13.73 -52.85 -8.7 -53.88Q-3.68 -54.91 1.32 -54.93Q6.32 -54.96 11.44 -54.56Q16.55 -54.16 21.35 -52.2Q26.16 -50.24 30.69 -48.01Q35.22 -45.78 38.99 -42.18Q42.76 -38.59 45.67 -34.37Q48.59 -30.15 49.99 -24.87Q51.39 -19.59 50.04 -14.09Q48.68 -8.6 44.35 -4.56ZM0.66 -30.49C0.5 -28.73 -0.88 -27.42 -2.42 -27.57C-3.96 -27.71 -5.08 -29.25 -4.91 -31.01C-4.75 -32.77 -3.36 -34.08 -1.83 -33.94C-0.29 -33.79 0.83 -32.25 0.66 -30.49ZM13.61 -29.28C13.44 -27.52 12.06 -26.21 10.52 -26.35C8.98 -26.5 7.87 -28.04 8.03 -29.8C8.2 -31.56 9.58 -32.87 11.12 -32.73C12.66 -32.58 13.77 -31.04 13.61 -29.28ZM-1.23 -17.71Q-0.62 -17.35 -0.03 -17.08Q0.56 -16.81 1.15 -16.54Q1.73 -16.28 2.4 -16Q3.07 -15.72 3.87 -15.6Q4.66 -15.48 5.4 -15.55Q6.13 -15.62 6.77 -15.79Q7.41 -15.96 7.97 -16.23Q8.53 -16.5 9 -16.91Q9.46 -17.32 9.73 -17.9Q9.99 -18.47 10.08 -19.12Q10.17 -19.77 10.08 -20.42Q9.99 -21.06 9.45 -21.32Q8.91 -21.58 8.27 -21.38Q7.63 -21.17 6.99 -20.91Q6.36 -20.65 5.7 -20.61Q5.04 -20.56 4.33 -20.73Q3.62 -20.9 2.92 -21.14Q2.23 -21.39 1.54 -21.68Q0.85 -21.97 0.16 -22.27Q-0.52 -22.56 -1.22 -22.79Q-1.92 -23.03 -2.49 -22.61Q-3.05 -22.19 -3.23 -21.5Q-3.4 -20.81 -3.2 -20.09Q-3.01 -19.38 -3.02 -19.15Q-3.02 -18.93 -2.42 -18.5Q-1.83 -18.06 -1.23 -17.71Z"/></svg>';
|