@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/undo.js
CHANGED
|
@@ -4,25 +4,75 @@
|
|
|
4
4
|
// flag.
|
|
5
5
|
|
|
6
6
|
import { undo } from '../vendor/hyper-undo.vendor.js'
|
|
7
|
-
import { isEditMode } from '../core/is-edit-mode.js'
|
|
7
|
+
import { isEditMode, startsAtImport } from '../core/is-edit-mode.js'
|
|
8
8
|
import { isTabLocalAttr } from '../lib/root-attrs.js'
|
|
9
9
|
import { isDerivedAttr } from '../lib/derived.js'
|
|
10
10
|
import { PERSIST } from '../lib/attr-aliases.js'
|
|
11
|
+
import { isGestureItem, isRuntimeAttribute } from '../lib/undo/capture-move.js'
|
|
12
|
+
import { gateMarkDirty } from '../lib/dirty-gate.js'
|
|
11
13
|
|
|
12
14
|
// Tab-local attributes (ephemeral state, route-active, root library state) are
|
|
13
|
-
// never undone
|
|
14
|
-
//
|
|
15
|
+
// never undone, and neither is what a drag's vendor writes on the item it holds:
|
|
16
|
+
// `draggable` and `style` are the gesture's own bookkeeping, and a click that
|
|
17
|
+
// writes them is not an edit. The shared mutation hub already drops tab-local
|
|
18
|
+
// attributes for the main instance; a scope made with create() runs its own
|
|
19
|
+
// observer, so it is told too.
|
|
20
|
+
// The page's own maintenance attributes, kept outside history by name (a card number a
|
|
21
|
+
// repair rewrites, for example): saved like any attribute, never an edit to undo.
|
|
22
|
+
const pageIgnored = new Set()
|
|
15
23
|
const withTabLocal = (opts = {}) => {
|
|
16
24
|
const own = opts.ignoreAttribute
|
|
17
25
|
return {
|
|
18
26
|
...opts,
|
|
19
|
-
ignoreAttribute: (name, target) => isTabLocalAttr(name, target) || isDerivedAttr(target, name) || (own ? own(name, target) : false),
|
|
27
|
+
ignoreAttribute: (name, target, host) => pageIgnored.has(name) || isTabLocalAttr(name, target) || isDerivedAttr(target, name, host) || isRuntimeAttribute(name, target) || (own ? own(name, target, host) : false),
|
|
28
|
+
isProtected: opts.isProtected || keepsOwnHistory,
|
|
29
|
+
ignoreNode: opts.ignoreNode || isGestureItem,
|
|
20
30
|
}
|
|
21
31
|
}
|
|
22
32
|
|
|
33
|
+
// Saved content that undo does not record (a richclay region, a no-undo block) has a
|
|
34
|
+
// history undo cannot restore, so a parent's undo or redo must not erase it. Chrome
|
|
35
|
+
// that is never saved (editor-ui) loses nothing and is not protected.
|
|
36
|
+
function keepsOwnHistory(node) {
|
|
37
|
+
const policy = window.clay?.region?.resolveRegionPolicy
|
|
38
|
+
if (!policy || node.nodeType !== 1) return false
|
|
39
|
+
const own = policy(node)
|
|
40
|
+
if (own.undoable !== false || own.persist === 'none') return false
|
|
41
|
+
const parent = node.parentElement
|
|
42
|
+
return !parent || policy(parent).undoable !== false
|
|
43
|
+
}
|
|
44
|
+
|
|
23
45
|
const createScope = undo.create.bind(undo)
|
|
24
46
|
undo.create = (opts) => createScope(withTabLocal(opts))
|
|
25
47
|
|
|
48
|
+
// The recording mode is the page's choice, and a session starts a new scope over the
|
|
49
|
+
// page as it stands: remember it here, so an exit and a re-entry come back in the mode
|
|
50
|
+
// the page picked instead of the default one.
|
|
51
|
+
let recording = null
|
|
52
|
+
const setRecording = undo.setRecording.bind(undo)
|
|
53
|
+
undo.setRecording = (mode) => {
|
|
54
|
+
recording = mode ?? null
|
|
55
|
+
return setRecording(mode)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// An authored history action is a user edit, and it writes the DOM while a sync
|
|
59
|
+
// frame's apply may hold both the mutation hub and the gate paused: neither feed
|
|
60
|
+
// records it then, and the apply would take the write into its saved baseline.
|
|
61
|
+
// So every commit, command, undo and redo that changed the document marks the
|
|
62
|
+
// page dirty at the gate's own entry point, whose count is what the capture token
|
|
63
|
+
// is compared against — an explicit mark advances it even while the gate is paused.
|
|
64
|
+
const lastEntryId = () => undo.history.at(-1)?.id ?? null
|
|
65
|
+
|
|
66
|
+
const command = undo.command.bind(undo)
|
|
67
|
+
undo.command = (label, fn) => {
|
|
68
|
+
const receipt = command(label, fn)
|
|
69
|
+
if (receipt) gateMarkDirty()
|
|
70
|
+
return receipt
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Undo and redo mark it in afterReplay, which the engine calls on every path that
|
|
74
|
+
// replays a step: the facade and the keyboard shortcut bound to the engine itself.
|
|
75
|
+
|
|
26
76
|
// A commit inside clay.effect drains the paused hub's records, so the effect's
|
|
27
77
|
// own writes reach autosave and re-trigger it. Warn once; the commit still runs.
|
|
28
78
|
const commit = undo.commit.bind(undo)
|
|
@@ -32,7 +82,10 @@ undo.commit = (label, fn) => {
|
|
|
32
82
|
warnedEffectCommit = true
|
|
33
83
|
console.warn(`clay.effect: undo.commit("${label}") was called inside an effect. Run commands from event handlers, not from an effect.`)
|
|
34
84
|
}
|
|
35
|
-
|
|
85
|
+
const before = lastEntryId()
|
|
86
|
+
const receipt = commit(label, fn)
|
|
87
|
+
if (receipt || lastEntryId() !== before) gateMarkDirty()
|
|
88
|
+
return receipt
|
|
36
89
|
}
|
|
37
90
|
|
|
38
91
|
// Undo and redo restore attributes. A control the user has touched shows its
|
|
@@ -74,23 +127,240 @@ function restoreControls() {
|
|
|
74
127
|
})
|
|
75
128
|
}
|
|
76
129
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
130
|
+
// The engine emits its receipt only after a whole replay applied, so a refused
|
|
131
|
+
// undo or redo never reaches the control restore.
|
|
132
|
+
const afterReplay = (receipt) => {
|
|
133
|
+
if (!receipt?.ok) return
|
|
134
|
+
gateMarkDirty()
|
|
135
|
+
restoreControls()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// A refusal is announced for whoever presents it: the engine's receipt, once.
|
|
139
|
+
const refuse = ({ entryId, label, direction, code }) => {
|
|
140
|
+
document.dispatchEvent(new CustomEvent('clay:undo-refused', {
|
|
141
|
+
detail: { ok: false, code, entryId, label, direction },
|
|
142
|
+
}))
|
|
143
|
+
keepRefused({ ok: false, code, entryId, label, direction })
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// A refused step is kept as recovery before a reload can take it away: the refusal is
|
|
147
|
+
// announced synchronously above and the copy is made behind it, so a store that will
|
|
148
|
+
// not answer can never hold up the receipt. The module is fetched on the first refusal
|
|
149
|
+
// or the first app keep; no reader and no boot pays for it, and nothing it writes is a
|
|
150
|
+
// save.
|
|
151
|
+
let recoveryLoad = null
|
|
152
|
+
|
|
153
|
+
function loadRecovery() {
|
|
154
|
+
recoveryLoad ??= import('../recovery/undo.js').catch((error) => {
|
|
155
|
+
recoveryLoad = null
|
|
156
|
+
console.error('clayjs: the undo recovery module could not be downloaded; a refused step stays in this tab only', error)
|
|
157
|
+
return null
|
|
158
|
+
})
|
|
159
|
+
return recoveryLoad
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function keepRefused(receipt) {
|
|
163
|
+
let written = { ok: false, code: 'unavailable' }
|
|
164
|
+
try {
|
|
165
|
+
const module = await loadRecovery()
|
|
166
|
+
if (module) written = await module.keepRefusal(undo, receipt)
|
|
167
|
+
} catch (_) {
|
|
168
|
+
written = { ok: false, code: 'error' }
|
|
85
169
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
170
|
+
const detail = written?.ok === true
|
|
171
|
+
? { ok: true, entryId: receipt.entryId, direction: receipt.direction }
|
|
172
|
+
: { ok: false, code: written?.code || 'error', entryId: receipt.entryId, direction: receipt.direction }
|
|
173
|
+
document.dispatchEvent(new CustomEvent('clay:undo-kept', { detail }))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// The page's own records on the same channel and in the same store: `keep` stages one,
|
|
177
|
+
// `kept` is what a later session can review, and `dismissKept` is the explicit,
|
|
178
|
+
// revision-checked way one leaves. All three fetch the recovery module on demand.
|
|
179
|
+
const withRecovery = (call, fallback) => (...args) =>
|
|
180
|
+
loadRecovery().then(
|
|
181
|
+
(module) => (module ? module[call](...args) : fallback),
|
|
182
|
+
() => fallback
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
// An app's kept copy is announced like a refused step's, so the review notice shows it
|
|
186
|
+
// in this session instead of only after a reload.
|
|
187
|
+
const keepApp = withRecovery('keep', { ok: false, code: 'unavailable' })
|
|
188
|
+
undo.ignoreAttribute = (name) => {
|
|
189
|
+
const key = String(name || '')
|
|
190
|
+
if (!key) return () => {}
|
|
191
|
+
pageIgnored.add(key)
|
|
192
|
+
return () => { pageIgnored.delete(key) }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
undo.keep = async (spec) => {
|
|
196
|
+
const written = await keepApp(spec)
|
|
197
|
+
if (written?.ok === true) {
|
|
198
|
+
document.dispatchEvent(new CustomEvent('clay:undo-kept', { detail: { ok: true, key: spec?.key == null ? null : `app:${spec.key}` } }))
|
|
90
199
|
}
|
|
200
|
+
return written
|
|
201
|
+
}
|
|
202
|
+
undo.kept = withRecovery('kept', { ok: false, code: 'unavailable', records: [] })
|
|
203
|
+
undo.dismissKept = withRecovery('dismissKept', { ok: false, code: 'unavailable' })
|
|
204
|
+
|
|
205
|
+
// The page's own actions on a kept copy. A provider is asked about every record the
|
|
206
|
+
// review panel draws, and each action it returns becomes a button on that row, so a
|
|
207
|
+
// template offers Keep as new without the panel knowing what a card is. A provider
|
|
208
|
+
// that throws costs its own buttons only, never the row or the copy.
|
|
209
|
+
const reviewProviders = new Set()
|
|
210
|
+
|
|
211
|
+
undo.reviewActions = (fn) => {
|
|
212
|
+
if (typeof fn !== 'function') return () => {}
|
|
213
|
+
reviewProviders.add(fn)
|
|
214
|
+
return () => { reviewProviders.delete(fn) }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
undo.reviewActionsFor = (record) => {
|
|
218
|
+
const actions = []
|
|
219
|
+
for (const fn of reviewProviders) {
|
|
220
|
+
let offered
|
|
221
|
+
try {
|
|
222
|
+
offered = fn(record)
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error('clayjs: a review action could not describe itself; the copy stays reviewable', error)
|
|
225
|
+
continue
|
|
226
|
+
}
|
|
227
|
+
for (const item of Array.isArray(offered) ? offered : []) {
|
|
228
|
+
if (item && typeof item.run === 'function' && item.label) actions.push(item)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return actions
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// One event for whatever presents the history (Undo and Redo buttons, a refusal banner):
|
|
235
|
+
// after every entry, replay, refusal, dismissal and clear, and when a session starts or
|
|
236
|
+
// ends. `undo` and `redo` are the entries the next press would replay, with `refused`
|
|
237
|
+
// set on the one that was just refused.
|
|
238
|
+
const HISTORY_EVENTS = ['commit', 'undo', 'redo', 'refused', 'discard', 'clear']
|
|
239
|
+
const announceChange = () => {
|
|
240
|
+
const live = !!session?.started
|
|
241
|
+
document.dispatchEvent(new CustomEvent('clay:undo-change', {
|
|
242
|
+
detail: {
|
|
243
|
+
canUndo: live && undo.canUndo,
|
|
244
|
+
canRedo: live && undo.canRedo,
|
|
245
|
+
undo: (live && undo.history.at(-1)) || null,
|
|
246
|
+
redo: (live && undo.future?.[0]) || null,
|
|
247
|
+
},
|
|
248
|
+
}))
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// One edit session owns the page's DOM history. A session starts its own over the page as
|
|
252
|
+
// it stands, so nothing an earlier session recorded, and nothing a reader painted while the
|
|
253
|
+
// page was in view, is this session's to undo. The durable recovery records belong to the
|
|
254
|
+
// recovery service and outlive every session; nothing here reads, clears or rewrites them.
|
|
255
|
+
let session = null
|
|
256
|
+
|
|
257
|
+
function ownership() {
|
|
258
|
+
return { started: false, admitted: false, subscriptions: [] }
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const isStale = (context) => {
|
|
262
|
+
if (!context) return false
|
|
263
|
+
if (context.signal?.aborted) return true
|
|
264
|
+
return typeof context.isCurrent === 'function' && !context.isCurrent()
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// The default scope is document.body, so a boot that is still parsing waits for the body
|
|
268
|
+
// to exist before the session starts.
|
|
269
|
+
function bodyReady() {
|
|
270
|
+
if (typeof document === 'undefined') return Promise.resolve()
|
|
271
|
+
if (document.readyState !== 'loading') return Promise.resolve()
|
|
272
|
+
return new Promise((resolve) => document.addEventListener('DOMContentLoaded', resolve, { once: true }))
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// One edit session over the live page. `enable` starts a fresh history and the control
|
|
276
|
+
// restore that follows it; `quiesce` closes recording so the exit barrier's writes are not
|
|
277
|
+
// entries, keeping the history itself; `resume` is the refused exit; `dispose` detaches
|
|
278
|
+
// exactly the subscriptions this session added and takes the history and its keydown
|
|
279
|
+
// binding back, so a later entry starts from nothing.
|
|
280
|
+
function undoLease(record) {
|
|
281
|
+
return {
|
|
282
|
+
enable() {
|
|
283
|
+
if (record.started) return
|
|
284
|
+
record.started = true
|
|
285
|
+
record.admitted = true
|
|
286
|
+
undo.start(withTabLocal({ bindKeys: true, ...(recording ? { recording } : {}) }))
|
|
287
|
+
for (const type of ['undo', 'redo']) {
|
|
288
|
+
undo.on(type, afterReplay)
|
|
289
|
+
record.subscriptions.push({ type, handler: afterReplay })
|
|
290
|
+
}
|
|
291
|
+
undo.on('refused', refuse)
|
|
292
|
+
record.subscriptions.push({ type: 'refused', handler: refuse })
|
|
293
|
+
for (const type of HISTORY_EVENTS) {
|
|
294
|
+
undo.on(type, announceChange)
|
|
295
|
+
record.subscriptions.push({ type, handler: announceChange })
|
|
296
|
+
}
|
|
297
|
+
announceChange()
|
|
298
|
+
},
|
|
299
|
+
quiesce() {
|
|
300
|
+
if (!record.admitted) return
|
|
301
|
+
record.admitted = false
|
|
302
|
+
undo.pause()
|
|
303
|
+
},
|
|
304
|
+
resume() {
|
|
305
|
+
if (record.admitted) return
|
|
306
|
+
record.admitted = true
|
|
307
|
+
undo.resume()
|
|
308
|
+
},
|
|
309
|
+
dispose() {
|
|
310
|
+
record.admitted = false
|
|
311
|
+
if (session === record) session = null
|
|
312
|
+
if (!record.started) return
|
|
313
|
+
record.started = false
|
|
314
|
+
for (const { type, handler } of record.subscriptions.splice(0)) undo.off(type, handler)
|
|
315
|
+
undo.stop()
|
|
316
|
+
announceChange()
|
|
317
|
+
},
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Undo as a mode participant: the history the commit that enters edit starts, and the one
|
|
323
|
+
* the exit takes back.
|
|
324
|
+
*
|
|
325
|
+
* `prepare` waits for the body the default scope needs, and answers with the session's
|
|
326
|
+
* lease; that wait can outlive the session that asked for it, so the generation is asked
|
|
327
|
+
* again once the body exists. `enable` starts the history. `quiesce` stops recording
|
|
328
|
+
* without losing what the session already holds. `dispose` hands back the subscriptions,
|
|
329
|
+
* the history and the keydown binding.
|
|
330
|
+
*/
|
|
331
|
+
export function undoParticipant() {
|
|
332
|
+
return {
|
|
333
|
+
id: 'clay:undo',
|
|
334
|
+
order: 350,
|
|
335
|
+
async prepare(context = {}) {
|
|
336
|
+
if (context.initial === true) return adopt()
|
|
337
|
+
await bodyReady()
|
|
338
|
+
if (isStale(context)) {
|
|
339
|
+
throw Object.assign(new Error('clay:undo prepared for a stale generation'), { code: 'stale-document' })
|
|
340
|
+
}
|
|
341
|
+
return undoLease(session || (session = ownership()))
|
|
342
|
+
},
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// The boot this tab already made, adopted instead of prepared: it is the history `init`
|
|
347
|
+
// below started, or, on a boot that started none, nothing to hand back.
|
|
348
|
+
async function adopt() {
|
|
349
|
+
const lease = await ready
|
|
350
|
+
return { enable() {}, quiesce: () => lease?.quiesce(), resume: () => lease?.resume(), dispose: () => lease?.dispose() }
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Auto-init when the module is imported; the loader awaits `ready` before resolving clay.ready
|
|
354
|
+
async function init() {
|
|
355
|
+
if (!startsAtImport()) return null
|
|
356
|
+
if (typeof document === 'undefined') return null
|
|
357
|
+
const lease = await undoParticipant().prepare()
|
|
358
|
+
lease.enable()
|
|
359
|
+
return lease
|
|
91
360
|
}
|
|
92
361
|
|
|
93
|
-
init()
|
|
362
|
+
const ready = init()
|
|
94
363
|
|
|
95
|
-
export { undo, restoreControls }
|
|
364
|
+
export { undo, restoreControls, init, ready }
|
|
365
|
+
export { undoParticipant as modeParticipant }
|
|
96
366
|
export default undo
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Text typed into chrome and not yet sent (a comment box, a description being edited),
|
|
2
|
+
// kept per record and name, outside the file. A redraw or a reload brings it back.
|
|
3
|
+
//
|
|
4
|
+
// The record a field belongs to is named by an `unsent-key` attribute on its nearest
|
|
5
|
+
// such ancestor, and by the nearest `[data-id]` ancestor when there is none: a view
|
|
6
|
+
// with two draft fields under one record names each scope it wants kept apart. The
|
|
7
|
+
// name is the field's own `unsent` attribute. A draft is pending while the field's
|
|
8
|
+
// value differs from the base it started at, an emptied field included.
|
|
9
|
+
import { registerUnsavedState } from "../lib/unsaved-state.js";
|
|
10
|
+
import { storage } from "../lib/doc-storage.js";
|
|
11
|
+
|
|
12
|
+
const KEY = "unsent";
|
|
13
|
+
const drafts = new Map(Object.entries(storage.session.get(KEY) || {}));
|
|
14
|
+
const bases = new WeakMap();
|
|
15
|
+
const pending = registerUnsavedState({ id: "unsent", isPending: () => drafts.size > 0 });
|
|
16
|
+
|
|
17
|
+
const keyOf = (el) => {
|
|
18
|
+
const scope = el.closest("[unsent-key]");
|
|
19
|
+
const record = (scope && scope.getAttribute("unsent-key")) || el.closest("[data-id]")?.dataset.id;
|
|
20
|
+
const name = el.getAttribute("unsent");
|
|
21
|
+
return record && name ? `${record}|${name}` : null;
|
|
22
|
+
};
|
|
23
|
+
const split = (key) => { const at = key.indexOf("|"); return [key.slice(0, at), key.slice(at + 1)]; };
|
|
24
|
+
|
|
25
|
+
function persist() {
|
|
26
|
+
storage.session.set(KEY, Object.fromEntries(drafts));
|
|
27
|
+
pending.changed();
|
|
28
|
+
document.dispatchEvent(new CustomEvent("clay:unsent-changed", { detail: { count: drafts.size } }));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function onBeforeInput(e) {
|
|
32
|
+
const el = e.target.closest?.("[unsent]");
|
|
33
|
+
if (el && !bases.has(el)) bases.set(el, el.value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function onInput(e) {
|
|
37
|
+
const el = e.target.closest?.("[unsent]");
|
|
38
|
+
const key = el && keyOf(el);
|
|
39
|
+
if (!key) return;
|
|
40
|
+
const base = bases.get(el) ?? drafts.get(key)?.base ?? "";
|
|
41
|
+
if (el.value === base) {
|
|
42
|
+
drafts.delete(key);
|
|
43
|
+
bases.delete(el);
|
|
44
|
+
} else {
|
|
45
|
+
drafts.set(key, { base, text: el.value });
|
|
46
|
+
}
|
|
47
|
+
persist();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function restore(el) {
|
|
51
|
+
const key = keyOf(el);
|
|
52
|
+
const draft = key && drafts.get(key);
|
|
53
|
+
if (!draft) return;
|
|
54
|
+
const current = el.value;
|
|
55
|
+
bases.set(el, draft.base);
|
|
56
|
+
el.value = draft.text;
|
|
57
|
+
el.dispatchEvent(new CustomEvent("clay:unsent-restored", {
|
|
58
|
+
bubbles: true,
|
|
59
|
+
detail: { base: draft.base, current, stale: current !== draft.base },
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const observer = new MutationObserver((records) => {
|
|
64
|
+
for (const record of records) for (const node of record.addedNodes) {
|
|
65
|
+
if (node.nodeType !== 1) continue;
|
|
66
|
+
if (node.matches("[unsent]")) restore(node);
|
|
67
|
+
for (const el of node.querySelectorAll("[unsent]")) restore(el);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
function restoreAll() {
|
|
72
|
+
for (const el of document.querySelectorAll("[unsent]")) restore(el);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The listeners and the observer belong to the page, not to an edit session: a reader
|
|
76
|
+
// repaint while the page is in view has to bring the drafts back too. Installing twice
|
|
77
|
+
// adds nothing, which is what keeps a second entry from recording one keystroke twice.
|
|
78
|
+
let installed = false;
|
|
79
|
+
|
|
80
|
+
function install() {
|
|
81
|
+
if (installed) return;
|
|
82
|
+
installed = true;
|
|
83
|
+
document.addEventListener("beforeinput", onBeforeInput, true);
|
|
84
|
+
document.addEventListener("input", onInput, true);
|
|
85
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
install();
|
|
89
|
+
restoreAll();
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Unsent drafts as a mode participant.
|
|
93
|
+
*
|
|
94
|
+
* Nothing here is the session's to lose: a draft is a revision of one control (its base
|
|
95
|
+
* and its text, per record and name) that the page's bytes cannot show, and the restore
|
|
96
|
+
* that brings it back runs in view as well. So `enable` installs and restores, and
|
|
97
|
+
* `quiesce`, `resume` and `dispose` leave every draft, its base and the pending check
|
|
98
|
+
* exactly where they are — an exit that teardown could acknowledge would report a view
|
|
99
|
+
* while a person's half-written text still waits outside the file.
|
|
100
|
+
*/
|
|
101
|
+
export function unsentParticipant() {
|
|
102
|
+
return {
|
|
103
|
+
id: "clay:unsent",
|
|
104
|
+
order: 320,
|
|
105
|
+
prepare(context = {}) {
|
|
106
|
+
// Adoption asks for a lease over the boot's session, and this module has none to
|
|
107
|
+
// hand back: it installs once at import and keeps every draft.
|
|
108
|
+
if (context.initial === true) {
|
|
109
|
+
return { enable() {}, quiesce() {}, resume() {}, dispose() {} };
|
|
110
|
+
}
|
|
111
|
+
install();
|
|
112
|
+
return {
|
|
113
|
+
enable() {
|
|
114
|
+
install();
|
|
115
|
+
restoreAll();
|
|
116
|
+
},
|
|
117
|
+
quiesce() {},
|
|
118
|
+
resume() {},
|
|
119
|
+
dispose() {},
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export { unsentParticipant as modeParticipant };
|
|
126
|
+
|
|
127
|
+
export const unsent = {
|
|
128
|
+
get size() { return drafts.size; },
|
|
129
|
+
list: () => [...drafts].map(([key, d]) => { const [record, name] = split(key); return { record, name, ...d }; }),
|
|
130
|
+
clear(el) { const key = keyOf(el); if (key && drafts.delete(key)) persist(); bases.delete(el); },
|
|
131
|
+
drop(record, name) { if (drafts.delete(`${record}|${name}`)) persist(); },
|
|
132
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { start, apply, prepare } from "../core/upgrade-run.js";
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// A copy made from a starter template says where it came from: its `hyper-source`
|
|
2
|
+
// and `hyper-version` metas. Once per load, the person who can edit such a copy is
|
|
3
|
+
// asked whether a newer version was published, and gets one offer when it was: an
|
|
4
|
+
// item with a dot in the menu, or a notice with Upgrade and Not now when the page
|
|
5
|
+
// has no menu. Nothing here writes the document; the click lands on
|
|
6
|
+
// `clay:upgrade-requested`, which is where the upgrade itself picks it up.
|
|
7
|
+
//
|
|
8
|
+
// Silent on every other page: no metas (a copy made before templates carried them),
|
|
9
|
+
// a visitor, a draft, no network, a failed fetch, a bad digest or an equal version.
|
|
10
|
+
// The 24-hour cache is hyper-html-api's own; the dotted-version comparison is
|
|
11
|
+
// core/version.js, so the rules engine that upgrades a document stays the click's.
|
|
12
|
+
import { isNewerVersion } from "../core/version.js";
|
|
13
|
+
|
|
14
|
+
const CACHE_PREFIX = "hha:upgrade-check:";
|
|
15
|
+
const TTL_MS = 24 * 60 * 60 * 1000;
|
|
16
|
+
const DISMISSED_KEY = "upgrade-dismissed";
|
|
17
|
+
const NOTICE = "A new version of this template is available.";
|
|
18
|
+
|
|
19
|
+
function readMeta(doc, name) {
|
|
20
|
+
const el = doc.querySelector(`meta[name="${name}"]`);
|
|
21
|
+
const content = el ? el.getAttribute("content") : null;
|
|
22
|
+
return content && content.trim() ? content.trim() : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readCache(key) {
|
|
26
|
+
try {
|
|
27
|
+
const raw = globalThis.localStorage.getItem(key);
|
|
28
|
+
return raw ? JSON.parse(raw) : null;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function writeCache(key, value) {
|
|
35
|
+
try {
|
|
36
|
+
globalThis.localStorage.setItem(key, JSON.stringify(value));
|
|
37
|
+
} catch {
|
|
38
|
+
// No storage (a private window): the next load asks again.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function digestOf(bytes) {
|
|
43
|
+
const subtle = globalThis.crypto && globalThis.crypto.subtle;
|
|
44
|
+
if (!subtle) return null;
|
|
45
|
+
const digest = await subtle.digest("SHA-256", bytes);
|
|
46
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function fetchOrNull(fetchFn, url) {
|
|
50
|
+
try {
|
|
51
|
+
const res = await fetchFn(url, { credentials: "omit" });
|
|
52
|
+
if (!res.ok) return null;
|
|
53
|
+
return res;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// `hyper-source` names the published HTML; its manifest is the same URL as JSON.
|
|
60
|
+
async function latestManifest(sourceHref, currentVersion, { fetchFn, now }) {
|
|
61
|
+
const latestUrl = new URL(sourceHref);
|
|
62
|
+
latestUrl.pathname = latestUrl.pathname.replace(/\.html$/, ".json");
|
|
63
|
+
const cacheKey = `${CACHE_PREFIX}${latestUrl.href}:${currentVersion}`;
|
|
64
|
+
const cached = readCache(cacheKey);
|
|
65
|
+
if (cached && typeof cached.checkedAt === "number" && now - cached.checkedAt < TTL_MS) {
|
|
66
|
+
return { latest: cached.latest, latestUrl };
|
|
67
|
+
}
|
|
68
|
+
const res = await fetchOrNull(fetchFn, latestUrl.href);
|
|
69
|
+
if (!res) return { latest: null, latestUrl };
|
|
70
|
+
let latest = null;
|
|
71
|
+
try {
|
|
72
|
+
latest = await res.json();
|
|
73
|
+
} catch {
|
|
74
|
+
return { latest: null, latestUrl };
|
|
75
|
+
}
|
|
76
|
+
writeCache(cacheKey, { checkedAt: now, latest });
|
|
77
|
+
return { latest, latestUrl };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function findUpdate({
|
|
81
|
+
doc = document,
|
|
82
|
+
loc = location,
|
|
83
|
+
fetchFn = (url, init) => fetch(url, init),
|
|
84
|
+
now = Date.now(),
|
|
85
|
+
} = {}) {
|
|
86
|
+
const clay = (typeof window !== "undefined" && window.clay) || {};
|
|
87
|
+
// A visitor cannot act on the offer, and a draft is not the document the source
|
|
88
|
+
// would replace. Neither fetches.
|
|
89
|
+
if (!clay.canEdit || clay.isDraftMode) return null;
|
|
90
|
+
|
|
91
|
+
const sourceUrl = readMeta(doc, "hyper-source");
|
|
92
|
+
const currentVersion = readMeta(doc, "hyper-version");
|
|
93
|
+
if (!sourceUrl || !currentVersion) return null;
|
|
94
|
+
|
|
95
|
+
let source;
|
|
96
|
+
try {
|
|
97
|
+
source = new URL(sourceUrl, loc ? loc.href : undefined);
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
if (!source.pathname.endsWith(".html")) return null;
|
|
102
|
+
// A pristine published copy points at itself: it is the source, not a copy of one.
|
|
103
|
+
if (loc && source.origin === loc.origin && source.pathname === loc.pathname) return null;
|
|
104
|
+
const latestUrl = new URL(source.href);
|
|
105
|
+
latestUrl.pathname = latestUrl.pathname.replace(/\.html$/, ".json");
|
|
106
|
+
|
|
107
|
+
const { latest } = await latestManifest(source.href, currentVersion, { fetchFn, now });
|
|
108
|
+
if (!latest || typeof latest !== "object") return null;
|
|
109
|
+
if (typeof latest.version !== "string" || !isNewerVersion(latest.version, currentVersion)) return null;
|
|
110
|
+
|
|
111
|
+
const storage = clay.storage && clay.storage.local;
|
|
112
|
+
if (storage && storage.get(DISMISSED_KEY) === latest.version) return null;
|
|
113
|
+
|
|
114
|
+
if (typeof latest.html !== "string" || !latest.html) return null;
|
|
115
|
+
const htmlUrl = new URL(latest.html, latestUrl).href;
|
|
116
|
+
const res = await fetchOrNull(fetchFn, htmlUrl);
|
|
117
|
+
if (!res) return null;
|
|
118
|
+
let bytes;
|
|
119
|
+
try {
|
|
120
|
+
bytes = await res.arrayBuffer();
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const digest = await digestOf(bytes);
|
|
125
|
+
// A manifest whose digest does not match its HTML is a host that served the wrong
|
|
126
|
+
// bytes. Nothing is offered, because nothing can be trusted to be that version.
|
|
127
|
+
if (!digest || digest !== String(latest.sha256 || "").toLowerCase()) return null;
|
|
128
|
+
|
|
129
|
+
return { version: latest.version, html: htmlUrl, sha256: digest, latestUrl: latestUrl.href };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function dismiss(version) {
|
|
133
|
+
const clay = window.clay;
|
|
134
|
+
const storage = clay && clay.storage && clay.storage.local;
|
|
135
|
+
if (storage) storage.set(DISMISSED_KEY, version);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function menuPlanned(clay) {
|
|
139
|
+
return Boolean(clay && Array.isArray(clay.plugins) && clay.plugins.includes("menu"));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function menuReady(clay) {
|
|
143
|
+
return Boolean(clay && clay.menu && typeof clay.menu.addItem === "function");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function whenMenuReady(clay) {
|
|
147
|
+
if (menuReady(clay) || !menuPlanned(clay)) return Promise.resolve();
|
|
148
|
+
return new Promise((resolve) => {
|
|
149
|
+
const onSettled = (event) => {
|
|
150
|
+
if (!event.detail || event.detail.plugin !== "menu") return;
|
|
151
|
+
document.removeEventListener("clay:plugin-ready", onSettled);
|
|
152
|
+
document.removeEventListener("clay:plugin-failed", onSettled);
|
|
153
|
+
resolve();
|
|
154
|
+
};
|
|
155
|
+
document.addEventListener("clay:plugin-ready", onSettled);
|
|
156
|
+
document.addEventListener("clay:plugin-failed", onSettled);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function announce(update, clay = typeof window !== "undefined" ? window.clay : null) {
|
|
161
|
+
const run = () => document.dispatchEvent(new CustomEvent("clay:upgrade-requested", { detail: update }));
|
|
162
|
+
await whenMenuReady(clay);
|
|
163
|
+
if (menuReady(clay)) {
|
|
164
|
+
clay.menu.addItem({ id: "upgrade", label: `Upgrade to version ${update.version}`, badge: true, run });
|
|
165
|
+
return "menu";
|
|
166
|
+
}
|
|
167
|
+
if (clay && typeof clay.toastPersistent === "function") {
|
|
168
|
+
clay.toastPersistent(NOTICE, "info", {
|
|
169
|
+
actions: [
|
|
170
|
+
{ label: "Upgrade", onClick: run },
|
|
171
|
+
{ label: "Not now", onClick: () => dismiss(update.version) },
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
return "notice";
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let started = false;
|
|
180
|
+
|
|
181
|
+
export async function start(options = {}) {
|
|
182
|
+
if (started) return null;
|
|
183
|
+
started = true;
|
|
184
|
+
const clay = window.clay;
|
|
185
|
+
// Toasts and the menu are drawn by satellites that may land after boot settles.
|
|
186
|
+
await Promise.resolve(clay && clay.loaded && clay.loaded.ui).catch(() => {});
|
|
187
|
+
const update = await findUpdate(options).catch(() => null);
|
|
188
|
+
if (!update) return null;
|
|
189
|
+
await announce(update);
|
|
190
|
+
return update;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function replace(html, options) {
|
|
194
|
+
const mod = await import("../core/save-replace.js");
|
|
195
|
+
return mod.replace(html, options);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function onUpgradeRequested(event) {
|
|
199
|
+
import("./upgrade-run.js")
|
|
200
|
+
.then((mod) => mod.start(event.detail))
|
|
201
|
+
.catch((err) => console.error("clayjs: the upgrade could not start:", err));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const bootClay = typeof window !== "undefined" ? window.clay : null;
|
|
205
|
+
if (bootClay && bootClay.canEdit && !bootClay.isDraftMode) {
|
|
206
|
+
document.addEventListener("clay:upgrade-requested", onUpgradeRequested);
|
|
207
|
+
const ready = bootClay.ready;
|
|
208
|
+
if (ready && typeof ready.then === "function") ready.then(() => start(), () => {});
|
|
209
|
+
else start();
|
|
210
|
+
}
|