@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
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
const DATA_WRITE = "data-write";
|
|
2
|
+
const WRITE_HOLD_REASON = "Waiting for the page data write to finish";
|
|
3
|
+
const SYNC_WAIT_MS = 12000;
|
|
4
|
+
const HTTP_WAIT_MS = 30000;
|
|
5
|
+
|
|
6
|
+
function refusal(code, message, details) {
|
|
7
|
+
const error = new Error(message);
|
|
8
|
+
error.code = code;
|
|
9
|
+
error.details = details;
|
|
10
|
+
return error;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function responseBody(response) {
|
|
14
|
+
if (typeof response.json === "function") return response.json();
|
|
15
|
+
const text = await response.text();
|
|
16
|
+
return text ? JSON.parse(text) : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function responseError(response) {
|
|
20
|
+
let body = {};
|
|
21
|
+
try {
|
|
22
|
+
body = (await responseBody(response)) || {};
|
|
23
|
+
} catch {}
|
|
24
|
+
|
|
25
|
+
const message = body.message || body.error || response.statusText || `Request failed (${response.status})`;
|
|
26
|
+
const error = new Error(message);
|
|
27
|
+
error.status = response.status;
|
|
28
|
+
error.error = body.error;
|
|
29
|
+
error.details = body.details;
|
|
30
|
+
return error;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function responseEtag(response) {
|
|
34
|
+
const value = response.headers?.get?.("ETag");
|
|
35
|
+
return typeof value === "string" && value ? value : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function dataUrl(rules, location) {
|
|
39
|
+
const base = `${location.origin}${location.pathname}`;
|
|
40
|
+
if (rules !== undefined) return `${base}?data=${encodeURIComponent(JSON.stringify(rules))}`;
|
|
41
|
+
return `${location.origin}/_/api${location.pathname}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function connected(sync) {
|
|
45
|
+
return !!sync && !sync.isDestroyed && sync.sse?.readyState === 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function syncWaiter(eventTarget, timeoutMs) {
|
|
49
|
+
const seen = [];
|
|
50
|
+
let expected = null;
|
|
51
|
+
let resolvePending = null;
|
|
52
|
+
let timer = null;
|
|
53
|
+
|
|
54
|
+
const matchesExact = (detail) =>
|
|
55
|
+
detail?.source === "disk" && (expected === null || detail.etag === expected);
|
|
56
|
+
|
|
57
|
+
const matchesPending = (detail) =>
|
|
58
|
+
detail?.source === "disk" && (detail.etag == null || expected === null || detail.etag === expected);
|
|
59
|
+
|
|
60
|
+
const cleanup = () => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
eventTarget.removeEventListener("clay:sync-applied", onApplied);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const onApplied = (event) => {
|
|
66
|
+
seen.push(event.detail);
|
|
67
|
+
if (!resolvePending || !matchesPending(event.detail)) return;
|
|
68
|
+
const resolve = resolvePending;
|
|
69
|
+
resolvePending = null;
|
|
70
|
+
cleanup();
|
|
71
|
+
resolve(true);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
eventTarget.addEventListener("clay:sync-applied", onApplied);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
waitFor(etag) {
|
|
78
|
+
expected = etag;
|
|
79
|
+
if (seen.some(matchesExact)) {
|
|
80
|
+
cleanup();
|
|
81
|
+
return Promise.resolve(true);
|
|
82
|
+
}
|
|
83
|
+
return new Promise((resolve) => {
|
|
84
|
+
resolvePending = resolve;
|
|
85
|
+
timer = setTimeout(() => {
|
|
86
|
+
resolvePending = null;
|
|
87
|
+
cleanup();
|
|
88
|
+
resolve(false);
|
|
89
|
+
}, timeoutMs);
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
cancel: cleanup,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function httpDeadline(timeoutMs) {
|
|
97
|
+
const controller = new AbortController();
|
|
98
|
+
let rejectDeadline;
|
|
99
|
+
const expired = new Promise((_, reject) => {
|
|
100
|
+
rejectDeadline = reject;
|
|
101
|
+
});
|
|
102
|
+
const timer = setTimeout(() => {
|
|
103
|
+
const error = new Error("The page data write timed out.");
|
|
104
|
+
error.name = "TimeoutError";
|
|
105
|
+
controller.abort(error);
|
|
106
|
+
rejectDeadline(error);
|
|
107
|
+
}, timeoutMs);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
signal: controller.signal,
|
|
111
|
+
waitFor(promise) {
|
|
112
|
+
return Promise.race([promise, expired]);
|
|
113
|
+
},
|
|
114
|
+
cancel() {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function defaultWriteModules() {
|
|
121
|
+
const [save, saveCore, etag, warning, editMode] = await Promise.all([
|
|
122
|
+
import("./save.js"),
|
|
123
|
+
import("./save-core.js"),
|
|
124
|
+
import("./etag.js"),
|
|
125
|
+
import("./unsaved-warning.js"),
|
|
126
|
+
import("./is-edit-mode.js"),
|
|
127
|
+
]);
|
|
128
|
+
return { ...save, ...saveCore, ...etag, ...warning, ...editMode };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function draftRefusal() {
|
|
132
|
+
const { isDraftMode } = await import("./is-edit-mode.js");
|
|
133
|
+
if (!isDraftMode) return null;
|
|
134
|
+
return refusal("DataUnavailableInDraft", "Page data is kept in this browser for this visitor.");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function createPageDataWrite({
|
|
138
|
+
supports,
|
|
139
|
+
token,
|
|
140
|
+
fetchImpl,
|
|
141
|
+
getLocation,
|
|
142
|
+
eventTarget,
|
|
143
|
+
getLiveSync,
|
|
144
|
+
loadWriteModules = defaultWriteModules,
|
|
145
|
+
syncTimeoutMs = SYNC_WAIT_MS,
|
|
146
|
+
httpTimeoutMs = HTTP_WAIT_MS,
|
|
147
|
+
}) {
|
|
148
|
+
let writeRunning = false;
|
|
149
|
+
let writeOutcomeUnknown = false;
|
|
150
|
+
|
|
151
|
+
function assertWriteState(modules, { checkHold = true } = {}) {
|
|
152
|
+
// A save session with admission closed is a page between modes. Its own exit is the
|
|
153
|
+
// only write that may go out until it commits or resumes, and page data is not it.
|
|
154
|
+
if (modules.savesAdmitted && !modules.savesAdmitted()) {
|
|
155
|
+
throw refusal("ModeTransition", "The page is switching modes. Write page data again when it has settled.");
|
|
156
|
+
}
|
|
157
|
+
if (writeOutcomeUnknown) {
|
|
158
|
+
throw refusal("DataWriteOutcomeUnknown", "A previous page data write has an unknown outcome. Reload before writing again.");
|
|
159
|
+
}
|
|
160
|
+
if (writeRunning) {
|
|
161
|
+
throw refusal("DataWriteInProgress", "Another page data write is already running.");
|
|
162
|
+
}
|
|
163
|
+
if (modules.isSaveInProgress()) {
|
|
164
|
+
throw refusal("SaveInProgress", "The page is already being saved.");
|
|
165
|
+
}
|
|
166
|
+
if (modules.saveFateIsUnknown()) {
|
|
167
|
+
throw refusal("SaveOutcomeUnknown", "The page's last save has an unknown outcome.");
|
|
168
|
+
}
|
|
169
|
+
if (checkHold && (modules.savesHeld() || modules.isSaveConflicted())) {
|
|
170
|
+
throw refusal("SavesHeld", "The page cannot write data while another save hold is active.");
|
|
171
|
+
}
|
|
172
|
+
if (modules.hasUnsavedChanges()) {
|
|
173
|
+
throw refusal("UnsavedChanges", "Save or discard the page's unsaved changes before writing data.");
|
|
174
|
+
}
|
|
175
|
+
if (!connected(getLiveSync())) {
|
|
176
|
+
throw refusal("SyncRequired", "Page data writes require the sync plugin to be connected.");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function waitForBaseline(modules) {
|
|
181
|
+
if (modules.baselineSettled()) return;
|
|
182
|
+
await new Promise((resolve) => eventTarget.addEventListener("clay:baseline-settled", resolve, { once: true }));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function writeData(values, { rules, ifMatch } = {}) {
|
|
186
|
+
const draft = await draftRefusal();
|
|
187
|
+
if (draft) throw draft;
|
|
188
|
+
|
|
189
|
+
if (!(await supports(DATA_WRITE))) {
|
|
190
|
+
throw refusal("DataWriteUnsupported", "This host does not support writing page data.");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const modules = await loadWriteModules();
|
|
194
|
+
if (!modules.isEditMode) {
|
|
195
|
+
throw refusal("DataWriteUnavailable", "Page data writes require an editable page.");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await waitForBaseline(modules);
|
|
199
|
+
assertWriteState(modules);
|
|
200
|
+
|
|
201
|
+
let match = ifMatch;
|
|
202
|
+
if (match == null) match = modules.lastSeenEtag() || await modules.seedEtag();
|
|
203
|
+
if (typeof match !== "string" || match === "") {
|
|
204
|
+
throw refusal("DataWriteVersionRequired", "Page data writes require a trustworthy document ETag.");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
assertWriteState(modules);
|
|
208
|
+
writeRunning = true;
|
|
209
|
+
modules.holdAllSaves(WRITE_HOLD_REASON);
|
|
210
|
+
|
|
211
|
+
let waiter = null;
|
|
212
|
+
try {
|
|
213
|
+
if (modules.isSaveInProgress()) {
|
|
214
|
+
throw refusal("SaveInProgress", "The page is already being saved.");
|
|
215
|
+
}
|
|
216
|
+
if (modules.saveFateIsUnknown()) {
|
|
217
|
+
throw refusal("SaveOutcomeUnknown", "The page's last save has an unknown outcome.");
|
|
218
|
+
}
|
|
219
|
+
if (modules.isSaveConflicted()) {
|
|
220
|
+
throw refusal("SavesHeld", "The page cannot write data while another save hold is active.");
|
|
221
|
+
}
|
|
222
|
+
if (modules.hasUnsavedChanges()) {
|
|
223
|
+
throw refusal("UnsavedChanges", "Save or discard the page's unsaved changes before writing data.");
|
|
224
|
+
}
|
|
225
|
+
// Rechecked immediately before the POST: a mode switch can close admission while
|
|
226
|
+
// this write was waiting for the sync and the stamp.
|
|
227
|
+
if (modules.savesAdmitted && !modules.savesAdmitted()) {
|
|
228
|
+
throw refusal("ModeTransition", "The page is switching modes. Write page data again when it has settled.");
|
|
229
|
+
}
|
|
230
|
+
if (!connected(getLiveSync())) {
|
|
231
|
+
throw refusal("SyncRequired", "Page data writes require the sync plugin to be connected.");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const headers = {
|
|
235
|
+
"Content-Type": "application/json",
|
|
236
|
+
"If-Match": match,
|
|
237
|
+
};
|
|
238
|
+
const currentToken = token();
|
|
239
|
+
if (currentToken) headers["Save-Token"] = currentToken;
|
|
240
|
+
|
|
241
|
+
const body = JSON.stringify(values);
|
|
242
|
+
waiter = syncWaiter(eventTarget, syncTimeoutMs);
|
|
243
|
+
const deadline = httpDeadline(httpTimeoutMs);
|
|
244
|
+
|
|
245
|
+
let response;
|
|
246
|
+
try {
|
|
247
|
+
response = await deadline.waitFor(fetchImpl(dataUrl(rules, getLocation()), {
|
|
248
|
+
method: "POST",
|
|
249
|
+
credentials: "same-origin",
|
|
250
|
+
redirect: "error",
|
|
251
|
+
headers,
|
|
252
|
+
body,
|
|
253
|
+
signal: deadline.signal,
|
|
254
|
+
}));
|
|
255
|
+
} catch (cause) {
|
|
256
|
+
deadline.cancel();
|
|
257
|
+
writeOutcomeUnknown = true;
|
|
258
|
+
throw Object.assign(
|
|
259
|
+
refusal("DataWriteOutcomeUnknown", "The page data write has an unknown outcome. Do not retry automatically."),
|
|
260
|
+
{ cause, unknownOutcome: true }
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
let data;
|
|
265
|
+
try {
|
|
266
|
+
if (!response.ok) throw await deadline.waitFor(responseError(response));
|
|
267
|
+
data = await deadline.waitFor(responseBody(response));
|
|
268
|
+
} catch (cause) {
|
|
269
|
+
if (cause?.status) throw cause;
|
|
270
|
+
writeOutcomeUnknown = true;
|
|
271
|
+
throw Object.assign(
|
|
272
|
+
refusal("DataWriteOutcomeUnknown", "The page data was written, but its response could not be read."),
|
|
273
|
+
{ cause, unknownOutcome: true }
|
|
274
|
+
);
|
|
275
|
+
} finally {
|
|
276
|
+
deadline.cancel();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const etag = responseEtag(response);
|
|
280
|
+
if (etag && etag === modules.lastSeenEtag()) {
|
|
281
|
+
waiter.cancel();
|
|
282
|
+
waiter = null;
|
|
283
|
+
return { data, etag };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const applied = await waiter.waitFor(etag);
|
|
287
|
+
waiter = null;
|
|
288
|
+
if (!applied) {
|
|
289
|
+
return {
|
|
290
|
+
data,
|
|
291
|
+
etag,
|
|
292
|
+
pageUpdatePending: true,
|
|
293
|
+
message: "Written, page update pending.",
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return { data, etag };
|
|
297
|
+
} finally {
|
|
298
|
+
waiter?.cancel();
|
|
299
|
+
writeRunning = false;
|
|
300
|
+
modules.releaseAllSaves({ reason: WRITE_HOLD_REASON });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return { writeData };
|
|
305
|
+
}
|
package/src/core/page-data.js
CHANGED
|
@@ -3,9 +3,6 @@ import { saveToken } from "./host-attrs.js";
|
|
|
3
3
|
|
|
4
4
|
const DATA_READ = "data-read";
|
|
5
5
|
const DATA_WRITE = "data-write";
|
|
6
|
-
const WRITE_HOLD_REASON = "Waiting for the page data write to finish";
|
|
7
|
-
const SYNC_WAIT_MS = 12000;
|
|
8
|
-
const HTTP_WAIT_MS = 30000;
|
|
9
6
|
|
|
10
7
|
function refusal(code, message, details) {
|
|
11
8
|
const error = new Error(message);
|
|
@@ -45,93 +42,6 @@ function dataUrl(rules, location) {
|
|
|
45
42
|
return `${location.origin}/_/api${location.pathname}`;
|
|
46
43
|
}
|
|
47
44
|
|
|
48
|
-
function connected(sync) {
|
|
49
|
-
return !!sync && !sync.isDestroyed && sync.sse?.readyState === 1;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function syncWaiter(eventTarget, timeoutMs) {
|
|
53
|
-
const seen = [];
|
|
54
|
-
let expected = null;
|
|
55
|
-
let resolvePending = null;
|
|
56
|
-
let timer = null;
|
|
57
|
-
|
|
58
|
-
const matchesExact = (detail) =>
|
|
59
|
-
detail?.source === "disk" && (expected === null || detail.etag === expected);
|
|
60
|
-
|
|
61
|
-
const matchesPending = (detail) =>
|
|
62
|
-
detail?.source === "disk" && (detail.etag == null || expected === null || detail.etag === expected);
|
|
63
|
-
|
|
64
|
-
const cleanup = () => {
|
|
65
|
-
clearTimeout(timer);
|
|
66
|
-
eventTarget.removeEventListener("clay:sync-applied", onApplied);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const onApplied = (event) => {
|
|
70
|
-
seen.push(event.detail);
|
|
71
|
-
if (!resolvePending || !matchesPending(event.detail)) return;
|
|
72
|
-
const resolve = resolvePending;
|
|
73
|
-
resolvePending = null;
|
|
74
|
-
cleanup();
|
|
75
|
-
resolve(true);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
eventTarget.addEventListener("clay:sync-applied", onApplied);
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
waitFor(etag) {
|
|
82
|
-
expected = etag;
|
|
83
|
-
if (seen.some(matchesExact)) {
|
|
84
|
-
cleanup();
|
|
85
|
-
return Promise.resolve(true);
|
|
86
|
-
}
|
|
87
|
-
return new Promise((resolve) => {
|
|
88
|
-
resolvePending = resolve;
|
|
89
|
-
timer = setTimeout(() => {
|
|
90
|
-
resolvePending = null;
|
|
91
|
-
cleanup();
|
|
92
|
-
resolve(false);
|
|
93
|
-
}, timeoutMs);
|
|
94
|
-
});
|
|
95
|
-
},
|
|
96
|
-
cancel: cleanup,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function httpDeadline(timeoutMs) {
|
|
101
|
-
const controller = new AbortController();
|
|
102
|
-
let rejectDeadline;
|
|
103
|
-
const expired = new Promise((_, reject) => {
|
|
104
|
-
rejectDeadline = reject;
|
|
105
|
-
});
|
|
106
|
-
const timer = setTimeout(() => {
|
|
107
|
-
const error = new Error("The page data write timed out.");
|
|
108
|
-
error.name = "TimeoutError";
|
|
109
|
-
controller.abort(error);
|
|
110
|
-
rejectDeadline(error);
|
|
111
|
-
}, timeoutMs);
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
signal: controller.signal,
|
|
115
|
-
waitFor(promise) {
|
|
116
|
-
return Promise.race([promise, expired]);
|
|
117
|
-
},
|
|
118
|
-
cancel() {
|
|
119
|
-
clearTimeout(timer);
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function defaultWriteModules() {
|
|
125
|
-
const [save, saveCore, etag, warning, editMode] = await Promise.all([
|
|
126
|
-
import("./save.js"),
|
|
127
|
-
import("./save-core.js"),
|
|
128
|
-
import("./etag.js"),
|
|
129
|
-
import("./unsaved-warning.js"),
|
|
130
|
-
import("./is-edit-mode.js"),
|
|
131
|
-
]);
|
|
132
|
-
return { ...save, ...saveCore, ...etag, ...warning, ...editMode };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
45
|
async function draftRefusal() {
|
|
136
46
|
const { isDraftMode } = await import("./is-edit-mode.js");
|
|
137
47
|
if (!isDraftMode) return null;
|
|
@@ -144,13 +54,12 @@ export function createPageDataApi({
|
|
|
144
54
|
fetchImpl = (...args) => fetch(...args),
|
|
145
55
|
getLocation = () => window.location,
|
|
146
56
|
eventTarget = document,
|
|
147
|
-
loadWriteModules
|
|
148
|
-
syncTimeoutMs
|
|
149
|
-
httpTimeoutMs
|
|
57
|
+
loadWriteModules,
|
|
58
|
+
syncTimeoutMs,
|
|
59
|
+
httpTimeoutMs,
|
|
150
60
|
} = {}) {
|
|
151
61
|
let liveSync = null;
|
|
152
|
-
let
|
|
153
|
-
let writeOutcomeUnknown = false;
|
|
62
|
+
let writeEngine = null;
|
|
154
63
|
|
|
155
64
|
function setLiveSync(sync) {
|
|
156
65
|
liveSync = sync;
|
|
@@ -180,36 +89,28 @@ export function createPageDataApi({
|
|
|
180
89
|
return { data: await responseBody(response), etag: responseEtag(response) };
|
|
181
90
|
}
|
|
182
91
|
|
|
183
|
-
function
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (!connected(liveSync)) {
|
|
203
|
-
throw refusal("SyncRequired", "Page data writes require the sync plugin to be connected.");
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async function waitForBaseline(modules) {
|
|
208
|
-
if (modules.baselineSettled()) return;
|
|
209
|
-
await new Promise((resolve) => eventTarget.addEventListener("clay:baseline-settled", resolve, { once: true }));
|
|
92
|
+
function loadWriteEngine() {
|
|
93
|
+
if (writeEngine) return writeEngine;
|
|
94
|
+
|
|
95
|
+
writeEngine = import("./page-data-write.js").then(({ createPageDataWrite }) => createPageDataWrite({
|
|
96
|
+
supports,
|
|
97
|
+
token,
|
|
98
|
+
fetchImpl,
|
|
99
|
+
getLocation,
|
|
100
|
+
eventTarget,
|
|
101
|
+
getLiveSync: () => liveSync,
|
|
102
|
+
loadWriteModules,
|
|
103
|
+
syncTimeoutMs,
|
|
104
|
+
httpTimeoutMs,
|
|
105
|
+
})).catch((error) => {
|
|
106
|
+
writeEngine = null;
|
|
107
|
+
throw error;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return writeEngine;
|
|
210
111
|
}
|
|
211
112
|
|
|
212
|
-
async function writeData(values,
|
|
113
|
+
async function writeData(values, options) {
|
|
213
114
|
const draft = await draftRefusal();
|
|
214
115
|
if (draft) throw draft;
|
|
215
116
|
|
|
@@ -217,110 +118,8 @@ export function createPageDataApi({
|
|
|
217
118
|
throw refusal("DataWriteUnsupported", "This host does not support writing page data.");
|
|
218
119
|
}
|
|
219
120
|
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
throw refusal("DataWriteUnavailable", "Page data writes require an editable page.");
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
await waitForBaseline(modules);
|
|
226
|
-
assertWriteState(modules);
|
|
227
|
-
|
|
228
|
-
let match = ifMatch;
|
|
229
|
-
if (match == null) match = modules.lastSeenEtag() || await modules.seedEtag();
|
|
230
|
-
if (typeof match !== "string" || match === "") {
|
|
231
|
-
throw refusal("DataWriteVersionRequired", "Page data writes require a trustworthy document ETag.");
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
assertWriteState(modules);
|
|
235
|
-
writeRunning = true;
|
|
236
|
-
modules.holdAllSaves(WRITE_HOLD_REASON);
|
|
237
|
-
|
|
238
|
-
let waiter = null;
|
|
239
|
-
try {
|
|
240
|
-
if (modules.isSaveInProgress()) {
|
|
241
|
-
throw refusal("SaveInProgress", "The page is already being saved.");
|
|
242
|
-
}
|
|
243
|
-
if (modules.saveFateIsUnknown()) {
|
|
244
|
-
throw refusal("SaveOutcomeUnknown", "The page's last save has an unknown outcome.");
|
|
245
|
-
}
|
|
246
|
-
if (modules.isSaveConflicted()) {
|
|
247
|
-
throw refusal("SavesHeld", "The page cannot write data while another save hold is active.");
|
|
248
|
-
}
|
|
249
|
-
if (modules.hasUnsavedChanges()) {
|
|
250
|
-
throw refusal("UnsavedChanges", "Save or discard the page's unsaved changes before writing data.");
|
|
251
|
-
}
|
|
252
|
-
if (!connected(liveSync)) {
|
|
253
|
-
throw refusal("SyncRequired", "Page data writes require the sync plugin to be connected.");
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const headers = {
|
|
257
|
-
"Content-Type": "application/json",
|
|
258
|
-
"If-Match": match,
|
|
259
|
-
};
|
|
260
|
-
const currentToken = token();
|
|
261
|
-
if (currentToken) headers["Save-Token"] = currentToken;
|
|
262
|
-
|
|
263
|
-
const body = JSON.stringify(values);
|
|
264
|
-
waiter = syncWaiter(eventTarget, syncTimeoutMs);
|
|
265
|
-
const deadline = httpDeadline(httpTimeoutMs);
|
|
266
|
-
|
|
267
|
-
let response;
|
|
268
|
-
try {
|
|
269
|
-
response = await deadline.waitFor(fetchImpl(dataUrl(rules, getLocation()), {
|
|
270
|
-
method: "POST",
|
|
271
|
-
credentials: "same-origin",
|
|
272
|
-
redirect: "error",
|
|
273
|
-
headers,
|
|
274
|
-
body,
|
|
275
|
-
signal: deadline.signal,
|
|
276
|
-
}));
|
|
277
|
-
} catch (cause) {
|
|
278
|
-
deadline.cancel();
|
|
279
|
-
writeOutcomeUnknown = true;
|
|
280
|
-
throw Object.assign(
|
|
281
|
-
refusal("DataWriteOutcomeUnknown", "The page data write has an unknown outcome. Do not retry automatically."),
|
|
282
|
-
{ cause, unknownOutcome: true }
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
let data;
|
|
287
|
-
try {
|
|
288
|
-
if (!response.ok) throw await deadline.waitFor(responseError(response));
|
|
289
|
-
data = await deadline.waitFor(responseBody(response));
|
|
290
|
-
} catch (cause) {
|
|
291
|
-
if (cause?.status) throw cause;
|
|
292
|
-
writeOutcomeUnknown = true;
|
|
293
|
-
throw Object.assign(
|
|
294
|
-
refusal("DataWriteOutcomeUnknown", "The page data was written, but its response could not be read."),
|
|
295
|
-
{ cause, unknownOutcome: true }
|
|
296
|
-
);
|
|
297
|
-
} finally {
|
|
298
|
-
deadline.cancel();
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const etag = responseEtag(response);
|
|
302
|
-
if (etag && etag === modules.lastSeenEtag()) {
|
|
303
|
-
waiter.cancel();
|
|
304
|
-
waiter = null;
|
|
305
|
-
return { data, etag };
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const applied = await waiter.waitFor(etag);
|
|
309
|
-
waiter = null;
|
|
310
|
-
if (!applied) {
|
|
311
|
-
return {
|
|
312
|
-
data,
|
|
313
|
-
etag,
|
|
314
|
-
pageUpdatePending: true,
|
|
315
|
-
message: "Written, page update pending.",
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
return { data, etag };
|
|
319
|
-
} finally {
|
|
320
|
-
waiter?.cancel();
|
|
321
|
-
writeRunning = false;
|
|
322
|
-
modules.releaseAllSaves({ reason: WRITE_HOLD_REASON });
|
|
323
|
-
}
|
|
121
|
+
const engine = await loadWriteEngine();
|
|
122
|
+
return engine.writeData(values, options);
|
|
324
123
|
}
|
|
325
124
|
|
|
326
125
|
return { readData, writeData, setLiveSync };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// The document format floor as the save wire carries it: the canonical
|
|
2
|
+
// <meta name="clay-page-format" content="1">
|
|
3
|
+
// marker (absence is floor 0, a floor only rises) and the `X-Clay-Page-Format`
|
|
4
|
+
// capability header every page write declares. Core code because every save sends
|
|
5
|
+
// the header; what content needs which floor lives in `page/format-level.js`.
|
|
6
|
+
|
|
7
|
+
import { PAGE_FORMAT } from "./page-format-wire.js";
|
|
8
|
+
export { PAGE_FORMAT, PAGE_FORMAT_HEADER } from "./page-format-wire.js";
|
|
9
|
+
export const PAGE_FORMAT_META = "clay-page-format";
|
|
10
|
+
export const PAGE_FORMAT_EXTENSION = "page-format-v1";
|
|
11
|
+
export const PAGE_FORMAT_UPGRADE = "page-format-upgrade-required";
|
|
12
|
+
|
|
13
|
+
// Every helper here is a plain function with no top-level work, so a client that
|
|
14
|
+
// imports one of them carries only that one.
|
|
15
|
+
const level = (value) => (Number.isSafeInteger(value) && value > 0 ? value : 0);
|
|
16
|
+
|
|
17
|
+
const metaTag = (html) => /<meta\b[^>]*\bname\s*=\s*["']?clay-page-format["']?[^>]*>/i.exec(html);
|
|
18
|
+
|
|
19
|
+
// A capability as a request declares it: a missing or unreadable header is no
|
|
20
|
+
// capability at all, so it can never authorize a write.
|
|
21
|
+
export function formatCapability(value) {
|
|
22
|
+
if (typeof value === "number") return level(value);
|
|
23
|
+
if (typeof value !== "string" || !/^\s*\d+\s*$/.test(value)) return 0;
|
|
24
|
+
return level(Number(value));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The floor a document carries. Absent or unreadable metadata is floor 0.
|
|
28
|
+
export function pageFormatFloor(html) {
|
|
29
|
+
if (typeof html !== "string") return 0;
|
|
30
|
+
const tag = metaTag(html);
|
|
31
|
+
if (!tag) return 0;
|
|
32
|
+
const content = /\bcontent\s*=\s*["']?(\d+)/i.exec(tag[0]);
|
|
33
|
+
return content ? level(Number(content[1])) : 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// The same document with its floor marker set to `floor`, or with every marker
|
|
37
|
+
// gone at 0. Canonical by construction, so it is idempotent: a document already
|
|
38
|
+
// at that floor comes back unchanged.
|
|
39
|
+
export function withPageFormatFloor(html, floor) {
|
|
40
|
+
const want = level(floor);
|
|
41
|
+
if (typeof html !== "string" || !want) return html;
|
|
42
|
+
const tag = metaTag(html);
|
|
43
|
+
if (tag && pageFormatFloor(tag[0]) === want) return html;
|
|
44
|
+
const marker = `<meta name="${PAGE_FORMAT_META}" content="${want}">`;
|
|
45
|
+
if (tag) return html.slice(0, tag.index) + marker + html.slice(tag.index + tag[0].length);
|
|
46
|
+
const at = /<\/head\s*>/i.exec(html) || /<body\b/i.exec(html);
|
|
47
|
+
return at ? html.slice(0, at.index) + marker + html.slice(at.index) : marker + html;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// The floor this tab last saw the document at. A save goes out with its capture
|
|
51
|
+
// repaired to this floor, so a capture taken before activation cannot drop the
|
|
52
|
+
// marker by omitting it.
|
|
53
|
+
let known = 0;
|
|
54
|
+
export const knownPageFormatFloor = () => known;
|
|
55
|
+
export function notePageFormatFloor(value) {
|
|
56
|
+
known = Math.max(known, level(value));
|
|
57
|
+
return known;
|
|
58
|
+
}
|