@panphora/clayjs 1.15.2 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/THIRD-PARTY-NOTICES.md +16 -0
- package/compat/highlight-11.9.0.min.js +1213 -0
- package/compat/highlight-LICENSE +29 -0
- package/compat/journal-assets.lock.json +22 -0
- package/compat/overtype-2.6.1.min.js +116 -0
- package/compat/overtype-LICENSE +21 -0
- package/dist/all.js +90 -0
- package/dist/clay-dom.js +84 -0
- package/dist/clay-events.js +103 -0
- package/dist/clay-internals.js +85 -0
- package/dist/clay-options.js +93 -0
- package/dist/clay-router.js +98 -0
- package/dist/clay-ui.js +95 -0
- package/dist/clay-utils.js +84 -0
- package/dist/clay.js +146 -0
- package/dist/clay.standalone.js +84291 -57611
- package/dist/m/attrs/add-from-T5LJXFFI.js +1 -0
- package/dist/m/attrs/onaftersave-OKSWEJQF.js +1 -0
- package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
- package/dist/m/attrs/save-freeze-MYNVXREN.js +1 -0
- package/dist/m/c-2AZDZWCK.js +1 -0
- package/dist/m/c-2SPVQSHC.js +1 -0
- package/dist/m/c-2YYDMLNZ.js +1 -0
- package/dist/m/c-336SGU5W.js +1 -0
- package/dist/m/c-33JJ6CBX.js +2 -0
- package/dist/m/c-3GJ4SDE2.js +1 -0
- package/dist/m/c-3H6BZDNJ.js +6 -0
- package/dist/m/c-3OWDMNJR.js +2 -0
- package/dist/m/c-3RUU4TPX.js +91 -0
- package/dist/m/c-3XIRD4NU.js +1 -0
- package/dist/m/c-46XYM55L.js +1 -0
- package/dist/m/c-4DFEJ2PO.js +3 -0
- package/dist/m/c-4JZDZFF6.js +1 -0
- package/dist/m/c-4L3BTFRK.js +1 -0
- package/dist/m/c-4Q2XTQ2N.js +2 -0
- package/dist/m/c-4WKFKEKF.js +1 -0
- package/dist/m/c-56QRZTZH.js +1 -0
- package/dist/m/c-5J4E7O4Y.js +4 -0
- package/dist/m/c-5LKDWQTL.js +19 -0
- package/dist/m/c-5QWQ7A5L.js +1 -0
- package/dist/m/c-5RJFTLSF.js +1 -0
- package/dist/m/c-5V3MOVJT.js +1 -0
- package/dist/m/c-5VYOWDTI.js +1 -0
- package/dist/m/c-5YIHJ3TJ.js +1 -0
- package/dist/m/c-6AZOGAPE.js +1 -0
- package/dist/m/c-6CRUUXOX.js +5 -0
- package/dist/m/c-6HMB24EV.js +1 -0
- package/dist/m/c-6KEVEQ7L.js +1 -0
- package/dist/m/c-6NC5I2TV.js +1 -0
- package/dist/m/c-74MGYWZF.js +1 -0
- package/dist/m/c-7DJDKPGL.js +1 -0
- package/dist/m/c-7OLLJPWP.js +1 -0
- package/dist/m/c-7SWMYKAI.js +1 -0
- package/dist/m/c-A3E3NIMC.js +1 -0
- package/dist/m/c-A7LS5UZW.js +1 -0
- package/dist/m/c-AMSNPOCX.js +1 -0
- package/dist/m/c-AQLFEATB.js +1 -0
- package/dist/m/c-B3JM5ZXC.js +1 -0
- package/dist/m/c-BBVGTQG7.js +1 -0
- package/dist/m/c-BPF2NSPV.js +1 -0
- package/dist/m/c-BQ6LOBEE.js +1 -0
- package/dist/m/c-BTDI4Y23.js +1 -0
- package/dist/m/c-BWFVSBDF.js +1 -0
- package/dist/m/c-BYTKL6UX.js +1 -0
- package/dist/m/c-CFHW5AMY.js +6 -0
- package/dist/m/c-CJ3A7ZKE.js +1 -0
- package/dist/m/c-CQHPGKX2.js +1 -0
- package/dist/m/c-CYWLPPUS.js +1 -0
- package/dist/m/c-CZUSOB7V.js +1 -0
- package/dist/m/c-DCFSEJZG.js +1 -0
- package/dist/m/c-DCXOXBF3.js +1 -0
- package/dist/m/c-DFS4XZJV.js +2 -0
- package/dist/m/c-DNENWY4O.js +1 -0
- package/dist/m/c-DNZ5IFM6.js +1 -0
- package/dist/m/c-E4LIR5X5.js +1 -0
- package/dist/m/c-EJTGYSLN.js +1 -0
- package/dist/m/c-EVCQ3BKA.js +1 -0
- package/dist/m/c-EZU56NQV.js +34 -0
- package/dist/m/c-FDNHHZY5.js +1 -0
- package/dist/m/c-FILLTJA7.js +1 -0
- package/dist/m/c-FRLHSY7E.js +1 -0
- package/dist/m/c-FROJUZMV.js +1 -0
- package/dist/m/c-G2JPKCTP.js +1 -0
- package/dist/m/c-GB6TU7WV.js +1 -0
- package/dist/m/c-GFJJEBTU.js +1 -0
- package/dist/m/c-GJAEGGXY.js +1 -0
- package/dist/m/c-GOWBDM6J.js +1 -0
- package/dist/m/c-GQ6F6IDK.js +1 -0
- package/dist/m/c-GSFIGKOP.js +1 -0
- package/dist/m/c-GY422N3Q.js +1 -0
- package/dist/m/c-H4LG442Q.js +1 -0
- package/dist/m/c-HA6INT6J.js +1 -0
- package/dist/m/c-HAWFNBYG.js +1 -0
- package/dist/m/c-HDDEEL77.js +1 -0
- package/dist/m/c-HFKUVACH.js +1 -0
- package/dist/m/c-HHDFCNHV.js +1 -0
- package/dist/m/c-HPURYVZS.js +2 -0
- package/dist/m/c-I2ZATQCD.js +2 -0
- package/dist/m/c-IEM4NTCS.js +1 -0
- package/dist/m/c-IJUK2GPW.js +1 -0
- package/dist/m/c-IW6GGOU2.js +1 -0
- package/dist/m/c-JAX2667S.js +6 -0
- package/dist/m/c-JDCX26UP.js +25 -0
- package/dist/m/c-JRKY44DK.js +1 -0
- package/dist/m/c-JU3NK25H.js +1 -0
- package/dist/m/c-KAU6E43D.js +1 -0
- package/dist/m/c-KBS3UV6B.js +1 -0
- package/dist/m/c-KGVGROZT.js +1 -0
- package/dist/m/c-KI7M4AY3.js +1 -0
- package/dist/m/c-KIPQJX4B.js +1 -0
- package/dist/m/c-KQ6TYJAP.js +135 -0
- package/dist/m/c-KU3ZFADW.js +1 -0
- package/dist/m/c-L4WQSC27.js +1 -0
- package/dist/m/c-LFPHCXY3.js +1 -0
- package/dist/m/c-LRPUO2MX.js +1 -0
- package/dist/m/c-LWLBHLVC.js +3 -0
- package/dist/m/c-LZJPNR2W.js +1 -0
- package/dist/m/c-M2L47S2X.js +7 -0
- package/dist/m/c-MFP2ZWD4.js +1 -0
- package/dist/m/c-MKSQ4Y57.js +1 -0
- package/dist/m/c-MTQHWQC3.js +1 -0
- package/dist/m/c-N326R2JD.js +1 -0
- package/dist/m/c-N7JPUPGC.js +1 -0
- package/dist/m/c-NRPJEVJS.js +1 -0
- package/dist/m/c-NUFFJVVJ.js +1 -0
- package/dist/m/c-NUHP2XDB.js +1 -0
- package/dist/m/c-NUHSPT5P.js +1 -0
- package/dist/m/c-O72HVYSA.js +1 -0
- package/dist/m/c-OCSZ4QGN.js +1 -0
- package/dist/m/c-OIBUTDXI.js +1 -0
- package/dist/m/c-OIDRZKAF.js +1 -0
- package/dist/m/c-OW5QIGG3.js +1 -0
- package/dist/m/c-P7KNDG34.js +1 -0
- package/dist/m/c-PGRCK73F.js +1 -0
- package/dist/m/c-PQ5LK6QE.js +993 -0
- package/dist/m/c-PS5ARVWR.js +9 -0
- package/dist/m/c-PZ4XEPUP.js +2 -0
- package/dist/m/c-Q622NS2Y.js +1 -0
- package/dist/m/c-Q7NQD65V.js +1 -0
- package/dist/m/c-QH63LQOS.js +1 -0
- package/dist/m/c-QHIWXHRO.js +1 -0
- package/dist/m/c-QHWJ5ZIJ.js +1 -0
- package/dist/m/c-QICP2YR5.js +1 -0
- package/dist/m/c-QJ4OM3G6.js +1 -0
- package/dist/m/c-QNKREOL6.js +1 -0
- package/dist/m/c-R3L7ZJGB.js +2 -0
- package/dist/m/c-R7IWVINQ.js +1 -0
- package/dist/m/c-RM7J7BV2.js +1 -0
- package/dist/m/c-RSMOODDU.js +1 -0
- package/dist/m/c-RVMWGXEJ.js +1 -0
- package/dist/m/c-RYUXUUBX.js +1 -0
- package/dist/m/c-S4ZEH46B.js +9 -0
- package/dist/m/c-S7H2DRCB.js +1 -0
- package/dist/m/c-S7LK4KUE.js +1 -0
- package/dist/m/c-SDAXDM2D.js +1 -0
- package/dist/m/c-SGIA4IEQ.js +188 -0
- package/dist/m/c-SMBZG5X4.js +1 -0
- package/dist/m/c-SMGU7RT3.js +235 -0
- package/dist/m/c-SPIORRYS.js +1 -0
- package/dist/m/c-SWLRT66R.js +1 -0
- package/dist/m/c-SZGYW5HT.js +1 -0
- package/dist/m/c-TCCA5BZN.js +2 -0
- package/dist/m/c-TCJTVAFN.js +1 -0
- package/dist/m/c-TJ6WOJL3.js +67 -0
- package/dist/m/c-TQEMVJCS.js +1 -0
- package/dist/m/c-TRS6TM6O.js +1 -0
- package/dist/m/c-TYLXX4NF.js +21 -0
- package/dist/m/c-U62OB4BT.js +1 -0
- package/dist/m/c-U6YWUAR3.js +8 -0
- package/dist/m/c-UDCSU5G5.js +2 -0
- package/dist/m/c-UDUUJPFZ.js +1 -0
- package/dist/m/c-UGHH4MB3.js +1 -0
- package/dist/m/c-UL2YUY52.js +1 -0
- package/dist/m/c-VAK7SG37.js +1 -0
- package/dist/m/c-VDSS4K3Z.js +1 -0
- package/dist/m/c-VE6ZMYMC.js +1 -0
- package/dist/m/c-VG5XIN4T.js +1 -0
- package/dist/m/c-VHM5XQHK.js +1 -0
- package/dist/m/c-VHNSBD2M.js +1 -0
- package/dist/m/c-VI5RMSVY.js +1 -0
- package/dist/m/c-VKEDWYDF.js +1 -0
- package/dist/m/c-VRZN56XY.js +1 -0
- package/dist/m/c-W5MCHEGA.js +1 -0
- package/dist/m/c-WDC5GVKU.js +1 -0
- package/dist/m/c-WN5AY3OZ.js +1 -0
- package/dist/m/c-WNYBLIOZ.js +1 -0
- package/dist/m/c-WRBKEJD4.js +1 -0
- package/dist/m/c-WWXIDH4O.js +28 -0
- package/dist/m/c-WYFDLITL.js +1 -0
- package/dist/m/c-XCNTK76U.js +1 -0
- package/dist/m/c-XMS6DPBR.js +1 -0
- package/dist/m/c-XN6MBKI6.js +67 -0
- package/dist/m/c-XPELB6E5.js +1 -0
- package/dist/m/c-XQXZAO7N.js +1 -0
- package/dist/m/c-Y5HBBIQ4.js +1 -0
- package/dist/m/c-ZE46QDIH.js +1 -0
- package/dist/m/c-ZUCOKOHT.js +1 -0
- package/dist/m/c-ZVPVXTJF.js +4 -0
- package/dist/m/c-ZVRKXKM7.js +1 -0
- package/dist/m/c-ZWIOJGMT.js +1 -0
- package/dist/m/core/admin-attrs-QHOKPJ7W.js +1 -0
- package/dist/m/core/autosave-HRYGUWV6.js +1 -0
- package/dist/m/core/conflict-notice-6MROJN2L.js +1 -0
- package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
- package/dist/m/core/host-permission-FNXVEFNE.js +1 -0
- package/dist/m/core/is-edit-mode-KTWA3O5N.js +1 -0
- package/dist/m/core/persist-JFYRHMRJ.js +3 -0
- package/dist/m/core/recovery-FLTCC2OC.js +1 -0
- package/dist/m/core/save-HQZ4S667.js +1 -0
- package/dist/m/core/save-core-KGQWFUNH.js +1 -0
- package/dist/m/core/snapshot-EGQZNT45.js +1 -0
- package/dist/m/core/unsaved-warning-IDJLD66H.js +1 -0
- package/dist/m/dom/all-APS5ZSKP.js +1 -0
- package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
- package/dist/m/events/index-RN2JNGBV.js +1 -0
- package/dist/m/internals/index-PVXGPUDA.js +1 -0
- package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
- package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
- package/dist/m/loader-S4I7GKXR.js +1 -0
- package/dist/m/manifest-4C419D67.js +1 -0
- package/dist/m/manifest-edit-B077F288.js +1 -0
- package/dist/m/manifest.json +3988 -0
- package/dist/m/options/options-EJMS3K24.js +1 -0
- package/dist/m/page/editor-NNJA4OKD.js +202 -0
- package/dist/m/plugins/ai-edit-QN4PMMNL.js +6 -0
- package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
- package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
- package/dist/m/plugins/demo-RM5IR6AO.js +1 -0
- package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
- package/dist/m/plugins/indicator-URDBBOQO.js +1 -0
- package/dist/m/plugins/live-HFWD4OIH.js +1 -0
- package/dist/m/plugins/menu-3HIEQSI6.js +138 -0
- package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
- package/dist/m/plugins/page-JOKB6XPR.js +2 -0
- package/dist/m/plugins/page-view-NXOJHNW6.js +1 -0
- package/dist/m/plugins/people-SGPJKANK.js +1 -0
- package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
- package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
- package/dist/m/plugins/richclay-NRQDSWX6.js +517 -0
- package/dist/m/plugins/richclay-NRQDSWX6.js.LEGAL.txt +1 -0
- package/dist/m/plugins/sortable-GWT4PWDV.js +1 -0
- package/dist/m/plugins/source-4ZRSAIYM.js +1 -0
- package/dist/m/plugins/undo-VDA5P7FH.js +1 -0
- package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
- package/dist/m/plugins/upgrade-W7FG5VFG.js +1 -0
- package/dist/m/plugins/upload-WLYKK6TL.js +1 -0
- package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +21 -0
- package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +1 -0
- package/dist/m/plugins/wire-4N6GYAF6.js +1 -0
- package/dist/m/reader-core-DXYCURFF.js +68 -0
- package/dist/m/route/route-KOCZSEGN.js +1 -0
- package/dist/m/sync/live-sync-J7VMYTKS.js +1 -0
- package/dist/m/ui/index-3JLVI27O.js +1 -0
- package/dist/m/utils/index-4TJCVLT7.js +1 -0
- package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
- package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
- package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
- package/dist/offline-manifest.json +4034 -0
- package/dist/page/themes/light-1.css +430 -0
- package/dist/server/core.cjs +23748 -22855
- package/dist/server/offline-runtime.cjs +4954 -0
- package/dist/server/offline-runtime.mjs +4923 -0
- package/dist/server/rooms.mjs +9 -0
- package/docs/guide.md +1078 -669
- package/entries/all.js +2 -0
- package/entries/clay-data.js +1 -1
- package/entries/clay-dom.js +2 -0
- package/entries/clay-events.js +2 -0
- package/entries/clay-internals.js +2 -0
- package/entries/clay-options.js +2 -0
- package/entries/clay-router.js +2 -0
- package/entries/clay-ui.js +2 -0
- package/entries/clay-utils.js +2 -0
- package/entries/clay.js +2 -0
- package/entries/sap.js +6 -6
- package/fonts/ibm-plex-mono-LICENSE +93 -0
- package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-LICENSE +93 -0
- package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- package/fonts/ibm-plex.css +71 -0
- package/fonts/inter-LICENSE +93 -0
- package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
- package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-greek-wght-normal.woff2 +0 -0
- package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
- package/fonts/inter.css +62 -0
- package/fonts/newsreader-LICENSE +93 -0
- package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
- package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
- package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
- package/fonts/newsreader.css +26 -0
- package/fonts/work-sans-LICENSE +93 -0
- package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
- package/fonts/work-sans.css +8 -0
- package/package.json +20 -3
- package/packed-contract.json +1 -0
- package/src/attrs/add-from.js +27 -4
- package/src/attrs/save-freeze.js +7 -52
- package/src/core/admin-attrs.js +119 -12
- package/src/core/admin-contenteditable.js +12 -10
- package/src/core/admin-inputs.js +14 -15
- package/src/core/admin-onclick.js +2 -0
- package/src/core/autosave.js +74 -21
- package/src/core/boot-phase.js +2 -1
- package/src/core/conflict-download.js +19 -23
- package/src/core/conflict-notice-ui.js +1173 -0
- package/src/core/conflict-notice.js +140 -697
- package/src/core/download.js +32 -0
- package/src/core/edit-mode.js +147 -14
- package/src/core/etag.js +30 -12
- package/src/core/host-meta.js +90 -8
- package/src/core/host-permission.js +29 -0
- package/src/core/is-edit-mode.js +145 -12
- package/src/core/mode-key.js +125 -0
- package/src/core/mode-lifecycle.js +544 -0
- package/src/core/mode-receipt.js +30 -0
- package/src/core/mode-refusal.js +13 -0
- package/src/core/mode-resources.js +52 -0
- package/src/core/mode-support.js +82 -0
- package/src/core/mode-switch.js +259 -0
- package/src/core/mode-transition.js +112 -0
- package/src/core/page-data-write.js +305 -0
- package/src/core/page-data.js +26 -227
- package/src/core/page-format-wire.js +4 -0
- package/src/core/page-format.js +58 -0
- package/src/core/persist.js +37 -9
- package/src/core/preserve-authored.js +114 -0
- package/src/core/recovery.js +772 -0
- package/src/core/runtime-url.js +105 -0
- package/src/core/save-core.js +113 -12
- package/src/core/save-replace.js +72 -0
- package/src/core/save-size-notice.js +134 -0
- package/src/core/save-size.js +140 -0
- package/src/core/save.js +806 -119
- package/src/core/snapshot.js +159 -11
- package/src/core/source-map.js +157 -9
- package/src/core/unsaved-warning.js +14 -4
- package/src/core/upgrade-run.js +408 -0
- package/src/core/version.js +18 -0
- package/src/lib/assets.js +30 -0
- package/src/lib/autosave-debug-gate.js +50 -0
- package/src/lib/autosave-scheduler.js +140 -0
- package/src/lib/boot-imports.js +8 -0
- package/src/lib/derived.js +3 -1
- package/src/lib/dirty-gate.js +15 -0
- package/src/lib/doc-storage.js +33 -0
- package/src/lib/effect.js +9 -4
- package/src/lib/mutation.js +4 -2
- package/src/lib/offline-runtime.js +226 -0
- package/src/lib/preload.js +48 -0
- package/src/lib/root-attrs.js +1 -1
- package/src/lib/trash.js +18 -0
- package/src/lib/undo/capture-move.js +152 -0
- package/src/live/local-channel.js +61 -0
- package/src/live/local-lane.js +60 -9
- package/src/live/server-lane.js +6 -3
- package/src/loader-logic.js +76 -15
- package/src/loader.js +218 -30
- package/src/page/adopt-boot.js +76 -22
- package/src/page/adopt.js +25 -4
- package/src/page/awareness.js +230 -0
- package/src/page/blocks.js +113 -6
- package/src/page/blocksel.js +20 -4
- package/src/page/board-face.js +13 -0
- package/src/page/board-panel.js +5 -17
- package/src/page/board-style.js +2 -13
- package/src/page/board-view.js +1 -11
- package/src/page/board-views.js +3 -0
- package/src/page/board.js +2 -2
- package/src/page/boot-pack.js +26 -0
- package/src/page/boot.js +237 -0
- package/src/page/chart-tables.js +11 -0
- package/src/page/chart.js +6 -10
- package/src/page/checklist.js +1 -1
- package/src/page/checkpoint-proof.js +289 -0
- package/src/page/chrome.js +431 -63
- package/src/page/csv-import.js +17 -2
- package/src/page/document-title-dom.js +52 -0
- package/src/page/document-title-runtime.js +90 -0
- package/src/page/document-title.js +12 -0
- package/src/page/drag.js +48 -13
- package/src/page/drop.js +2 -1
- package/src/page/editor.js +399 -90
- package/src/page/emoji-data.js +162 -0
- package/src/page/feature-loader.js +72 -0
- package/src/page/features/board.js +21 -0
- package/src/page/features/chart.js +11 -0
- package/src/page/features/code.js +13 -0
- package/src/page/features/dates.js +180 -0
- package/src/page/features/emoji.js +187 -0
- package/src/page/features/media.js +290 -0
- package/src/page/features/mentions.js +188 -0
- package/src/page/features/registry.js +28 -0
- package/src/page/features/table.js +383 -0
- package/src/page/{tool-view.js → features/tool.js} +33 -2
- package/src/page/format-codec.js +215 -0
- package/src/page/format-command.js +9 -0
- package/src/page/format-gate.js +179 -0
- package/src/page/format-host.js +54 -0
- package/src/page/format-level.js +157 -0
- package/src/page/format-native-commands.js +142 -0
- package/src/page/format-native.js +42 -0
- package/src/page/highlight-slot.js +23 -0
- package/src/page/highlight.js +30 -16
- package/src/page/history-intent.js +78 -0
- package/src/page/inline.js +45 -28
- package/src/page/items.js +67 -14
- package/src/page/link.js +1 -0
- package/src/page/markdown.js +112 -3
- package/src/page/media-views.js +3 -0
- package/src/page/media.js +33 -245
- package/src/page/mention-picker.js +99 -0
- package/src/page/merge.js +1 -1
- package/src/page/outline-cache.js +11 -0
- package/src/page/placeholder.js +27 -7
- package/src/page/projection.js +240 -51
- package/src/page/reader-patch.js +306 -0
- package/src/page/recover.js +1 -1
- package/src/page/rich/page-tool.js +1 -4
- package/src/page/runtime-owner.js +27 -0
- package/src/page/save-projection.js +46 -4
- package/src/page/save-states.js +1 -1
- package/src/page/schema.js +47 -0
- package/src/page/table-rect-key.js +3 -0
- package/src/page/table-select.js +4 -3
- package/src/page/table-style.js +1 -1
- package/src/page/table-views.js +3 -0
- package/src/page/table.js +8 -293
- package/src/page/themes/content.css +25 -1
- package/src/page/themes/light-1.css +163 -2
- package/src/page/themes/light-1.source.css +269 -0
- package/src/page/tool-capability.js +3 -0
- package/src/page/tool-views.js +3 -0
- package/src/page/view-hooks.js +32 -0
- package/src/page/view.js +390 -107
- package/src/page/ybind/awareness.js +172 -0
- package/src/page/ybind/binding.js +40 -5
- package/src/page/ybind/bridge.js +50 -47
- package/src/page/ybind/decode.js +52 -19
- package/src/page/ybind/doc.js +27 -2
- package/src/page/ybind/group.js +12 -3
- package/src/page/ybind/index.js +2 -0
- package/src/page/ybind/lineage.js +71 -0
- package/src/page/ybind/marks.js +26 -1
- package/src/page/ybind/migrate.js +48 -13
- package/src/page/ybind/moves.js +32 -21
- package/src/page/ybind/positions.js +50 -0
- package/src/page/ybind/provider.js +97 -24
- package/src/page/ybind/recovery-proof.js +191 -0
- package/src/page/ybind/recovery-replay.js +488 -0
- package/src/page/ybind/tokens.js +15 -6
- package/src/page/ybind/walk.js +10 -3
- package/src/page/ybind/writer.js +48 -4
- package/src/plugins/ai-edit.js +167 -32
- package/src/plugins/bind-text.js +218 -0
- package/src/plugins/cms.js +93 -0
- package/src/plugins/indicator.js +57 -5
- package/src/plugins/live.js +319 -35
- package/src/plugins/menu/claygoo.svg.js +1 -0
- package/src/plugins/menu/seo-head.js +323 -0
- package/src/plugins/menu/seo.js +849 -0
- package/src/plugins/menu.js +425 -0
- package/src/plugins/page-view.js +131 -0
- package/src/plugins/page.js +1131 -270
- package/src/plugins/people.js +158 -31
- package/src/plugins/richclay.js +119 -9
- package/src/plugins/sortable.js +370 -60
- package/src/plugins/source.js +189 -366
- package/src/plugins/undo.js +289 -19
- package/src/plugins/unsent.js +132 -0
- package/src/plugins/upgrade-run.js +1 -0
- package/src/plugins/upgrade.js +210 -0
- package/src/plugins/upload-task.js +571 -0
- package/src/plugins/upload-ui.js +265 -0
- package/src/plugins/upload.js +34 -153
- package/src/plugins/visitor-drafts/inert.js +32 -0
- package/src/plugins/visitor-drafts/intro.js +2 -1
- package/src/plugins/visitor-drafts/owner-chip.js +3 -0
- package/src/plugins/visitor-drafts/prompt.js +2 -2
- package/src/plugins/visitor-drafts-owner.js +85 -0
- package/src/plugins/visitor-drafts.js +31 -42
- package/src/plugins/wire.js +82 -1
- package/src/reader-core.js +26 -0
- package/src/recovery/conflicts.js +287 -0
- package/src/recovery/identity.js +154 -0
- package/src/recovery/store.js +792 -0
- package/src/recovery/undo.js +257 -0
- package/src/server/checkpoint/index.js +68 -4
- package/src/server/offline-runtime/index.js +214 -0
- package/src/server/rooms/index.js +2 -1
- package/src/server/rooms/live-data-rooms.js +2 -2
- package/src/server/rooms/page-admission.js +33 -0
- package/src/server/rooms/page-lineage.js +56 -0
- package/src/server/rooms/page-rebase.js +11 -5
- package/src/server/rooms/page-state.js +2 -0
- package/src/server/rooms/page-write-admission.js +9 -3
- package/src/source/session.js +705 -0
- package/src/sync/conflict-revert.js +5 -4
- package/src/sync/conflicts-edit.js +119 -0
- package/src/sync/conflicts-facade.js +49 -0
- package/src/sync/conflicts.js +99 -1
- package/src/sync/editing-marker.js +37 -6
- package/src/sync/editing-sender.js +8 -1
- package/src/sync/live-sync-edit.js +63 -0
- package/src/sync/live-sync.js +364 -119
- package/src/sync/local-presence.js +187 -0
- package/src/sync/merge-declarations.js +10 -0
- package/src/sync/merge-engine.js +12 -0
- package/src/sync/presence-store.js +412 -0
- package/src/sync/presence-target.js +243 -0
- package/src/sync/presence.js +72 -41
- package/src/sync/saved-handoff.js +200 -0
- package/src/sync/saved-stream.js +544 -0
- package/src/sync/version-guard.js +92 -0
- package/src/sync/wire-profile.js +81 -0
- package/src/ui/bevel-controls.js +2 -8
- package/src/ui/page-scheme.js +7 -0
- package/src/ui/skins/cms.js +1 -1
- package/src/ui/theme-config.js +150 -0
- package/src/ui/theme.js +6 -138
- package/src/ui/toast-lazy.js +11 -0
- package/src/vendor/LICENSES-optional.md +253 -0
- package/src/vendor/highlight.vendor.js +3 -0
- package/src/vendor/hljs-bash.vendor.js +2 -0
- package/src/vendor/hljs-css.vendor.js +2 -0
- package/src/vendor/hljs-javascript.vendor.js +2 -0
- package/src/vendor/hljs-json.vendor.js +2 -0
- package/src/vendor/hljs-python.vendor.js +2 -0
- package/src/vendor/hljs-xml.vendor.js +2 -0
- package/src/vendor/hyper-html-api-data.vendor.js +1 -1
- package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
- package/src/vendor/hyper-morph-identity.vendor.js +2 -0
- package/src/vendor/hyper-morph-json.vendor.js +3 -0
- package/src/vendor/hyper-morph.vendor.js +6 -6
- package/src/vendor/hyper-undo.vendor.js +1 -1
- package/src/vendor/hypercms.vendor.js +169 -84
- package/src/vendor/marked.vendor.js +68 -0
- package/src/vendor/overtype.vendor.js +994 -0
- package/src/vendor/richclay.vendor.js +17 -17
- package/src/vendor/sap-core.vendor.js +5 -5
- package/src/vendor/sap-ownership.vendor.js +11 -7
package/src/plugins/live.js
CHANGED
|
@@ -8,20 +8,21 @@
|
|
|
8
8
|
import { contains, parseBy, frameSet, descendants, unionRanges, formatBy } from "../live/provenance.js";
|
|
9
9
|
import { createStore } from "../live/store.js";
|
|
10
10
|
import { serializeLive, stampOf, parseStamp, textHash } from "../live/ops.js";
|
|
11
|
-
import { onSnapshot } from "../core/snapshot.js";
|
|
11
|
+
import { capturePageState, onSnapshot } from "../core/snapshot.js";
|
|
12
12
|
import { onSaveAccepted, onSaveAttempt, onSaveOutcome, isSaveInProgress } from "../core/save-core.js";
|
|
13
13
|
import { savePageForce, baselineSettled, isSaveConflicted, autosaveIsSuspended } from "../core/save.js";
|
|
14
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;
|
|
@@ -1000,11 +1235,47 @@ onSaveOutcome(({ captureId, result, attempts, sent }) => {
|
|
|
1000
1235
|
if (plan) startHistoryRecovery(plan, { result, attempts, startedAt: Date.now() });
|
|
1001
1236
|
});
|
|
1002
1237
|
|
|
1238
|
+
// The recovery integration's turn at an accepted save, when one journals this page.
|
|
1239
|
+
// Registered by page code once it holds a `page.yjs` channel, so a page that journals
|
|
1240
|
+
// nothing loads neither the proof builder nor the conditional client, and pays one null
|
|
1241
|
+
// check per save.
|
|
1242
|
+
let pageCheckpoints = null;
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* Hand every accepted save to a page checkpoint integration, or null to stop.
|
|
1246
|
+
*
|
|
1247
|
+
* The integration builds the proof from the capture's pinned page state and the bytes
|
|
1248
|
+
* the host took; nothing here decides what is covered. Its answer is not awaited: a save
|
|
1249
|
+
* that succeeded is saved whether or not a journal can be retired, and a failure there
|
|
1250
|
+
* must never reach the save's own outcome.
|
|
1251
|
+
*
|
|
1252
|
+
* @param {?Function} install - ({capture, state, article, etag}) => unknown
|
|
1253
|
+
*/
|
|
1254
|
+
export function registerPageCheckpoints(install) {
|
|
1255
|
+
pageCheckpoints = install;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function offerCheckpoint(captureId, html) {
|
|
1259
|
+
if (!pageCheckpoints) return;
|
|
1260
|
+
// Read synchronously, before this capture is released: the value is the one its page
|
|
1261
|
+
// tag carried, and the edits that followed it are not in these bytes.
|
|
1262
|
+
const state = capturePageState(captureId);
|
|
1263
|
+
if (!state) return;
|
|
1264
|
+
let article = null;
|
|
1265
|
+
try { article = parseHtml(html).querySelector("article[data-page]")?.innerHTML ?? null; }
|
|
1266
|
+
catch (_) { article = null; }
|
|
1267
|
+
const etag = lastSeenEtag();
|
|
1268
|
+
Promise.resolve()
|
|
1269
|
+
.then(() => pageCheckpoints({ capture: String(captureId), state, article, etag }))
|
|
1270
|
+
.catch(() => {});
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1003
1273
|
// An accepted body confirms the ops of the capture that sent it, and only those.
|
|
1004
1274
|
// A null context (a raw save) cannot consume a captured pin.
|
|
1005
1275
|
function handleAccepted(html, context) {
|
|
1006
1276
|
const captureId = context?.captureId;
|
|
1007
1277
|
if (captureId === null || captureId === undefined) return;
|
|
1278
|
+
offerCheckpoint(captureId, html);
|
|
1008
1279
|
const pin = capturePins.get(captureId);
|
|
1009
1280
|
if (!pin) return;
|
|
1010
1281
|
dropCapture(captureId);
|
|
@@ -1088,6 +1359,8 @@ export function receiveFrame(html, seq = null) {
|
|
|
1088
1359
|
else if (pageTag.getAttribute("live-by") !== by) pageTag.setAttribute("live-by", by);
|
|
1089
1360
|
}
|
|
1090
1361
|
if (entry && !entry.store.ephemeral) {
|
|
1362
|
+
// An old seed is never merged into a newer article before its first write.
|
|
1363
|
+
if (entry.staged) continue;
|
|
1091
1364
|
if (entry.store.handle.status.mode === "file") receive(entry, text, "remote", dropIds);
|
|
1092
1365
|
continue;
|
|
1093
1366
|
}
|
|
@@ -1100,10 +1373,13 @@ document.addEventListener("clay:sync-applied", (event) => receiveFrame(event.det
|
|
|
1100
1373
|
// The last chance to save live changes made since the previous checkpoint. A
|
|
1101
1374
|
// keepalive body is capped near 60 KB; a larger page's save may not finish.
|
|
1102
1375
|
document.addEventListener("visibilitychange", () => {
|
|
1103
|
-
if (document.visibilityState === "hidden") checkpoints.flush();
|
|
1376
|
+
if (document.visibilityState === "hidden" && isEditMode && writerAdmitted()) checkpoints.flush();
|
|
1104
1377
|
});
|
|
1105
1378
|
window.addEventListener("pagehide", () => {
|
|
1106
1379
|
closing = true;
|
|
1380
|
+
// A view, and an edit whose session has closed admission or been released, saves
|
|
1381
|
+
// nothing here: its bytes are the reader's, and the exit's own barrier writes them.
|
|
1382
|
+
if (!isEditMode || !writerAdmitted()) return;
|
|
1107
1383
|
// A hide save still on the wire is answered after the page is gone too.
|
|
1108
1384
|
const latest = unsentCaptureId ?? wireCaptureId;
|
|
1109
1385
|
const pin = checkpoints.flushing && latest !== null ? capturePins.get(latest) : null;
|
|
@@ -1121,6 +1397,14 @@ export function storeOf(name) {
|
|
|
1121
1397
|
return entries.get(name)?.store || null;
|
|
1122
1398
|
}
|
|
1123
1399
|
|
|
1400
|
+
// A caller that opened a store itself is done with it: the next entry reads the tag the
|
|
1401
|
+
// reader now states. Only the store that caller opened is closed; one the app opened
|
|
1402
|
+
// first, or a staged store, stays.
|
|
1403
|
+
export function closeStore(name, store) {
|
|
1404
|
+
const entry = entries.get(name);
|
|
1405
|
+
if (entry && entry.store === store && !entry.staged) entries.delete(name);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1124
1408
|
export function openStores() {
|
|
1125
1409
|
return [...entries.values()].map((entry) => entry.store);
|
|
1126
1410
|
}
|
|
@@ -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>';
|