@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
|
@@ -1,715 +1,158 @@
|
|
|
1
|
-
import { isEditMode } from "./is-edit-mode.js";
|
|
2
|
-
import { rowOf } from "./conflict-presentation.js";
|
|
3
|
-
import { downloadRecovery } from "./conflict-download.js";
|
|
4
1
|
import onDomReady from "../lib/dom-ready.js";
|
|
5
|
-
import {
|
|
2
|
+
import { isEditMode } from "./is-edit-mode.js";
|
|
6
3
|
import { isSaveConflicted } from "./save.js";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// acknowledges one, and only the ids the panel actually drew.
|
|
17
|
-
//
|
|
18
|
-
// Ledger changes draw on the next frame, not at once: a listener that claims its
|
|
19
|
-
// own records inside clay:sync-applied (Writer) must get there before the first
|
|
20
|
-
// draw, or the bar flashes a count that is not the person's to deal with.
|
|
21
|
-
|
|
22
|
-
export const WARN = "light-dark(#D08A1E, #E3A33F)";
|
|
23
|
-
const ARM_MS = 5000;
|
|
24
|
-
const TOAST_MS = 4000;
|
|
25
|
-
const DOUBLE_PRESS_MS = 500;
|
|
26
|
-
|
|
27
|
-
// The host names what moved the file when it knows. Anything unrecognised falls
|
|
28
|
-
// back to the phrase that is true in every case.
|
|
29
|
-
const SOURCES = {
|
|
30
|
-
"another-tab": "in another tab",
|
|
31
|
-
"another-person": "by someone else",
|
|
32
|
-
"an-agent": "by an agent",
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const MINIMIZE_SVG = '<svg viewBox="0 0 12 12" width="12" height="12" aria-hidden="true"><path d="M2.5 9.5h7" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="square"/></svg>';
|
|
36
|
-
|
|
37
|
-
const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
38
|
-
|
|
39
|
-
const BLOCKED_ROW = "This spot changed again. Review the latest edit or download your copy.";
|
|
40
|
-
const DATA_BLOCKED_ROW = "A data value can't be put back from here. Download your copy to keep yours.";
|
|
41
|
-
|
|
42
|
-
let view = "hidden";
|
|
43
|
-
let listOpen = false;
|
|
44
|
-
let renderedIds = [];
|
|
45
|
-
const seenIds = new Set();
|
|
46
|
-
const freshIds = new Set();
|
|
47
|
-
let refused = null;
|
|
48
|
-
let refusalGeneration = 0;
|
|
49
|
-
let armed = null;
|
|
50
|
-
let armTimer = null;
|
|
51
|
-
let busy = null;
|
|
52
|
-
let keepFailed = false;
|
|
53
|
-
let downloaded = false;
|
|
54
|
-
let downloadFailed = false;
|
|
55
|
-
const blockedRows = new Map();
|
|
56
|
-
let ledgerRevision = 0;
|
|
57
|
-
let frameQueued = false;
|
|
58
|
-
let checkRefusal = false;
|
|
59
|
-
let toastText = null;
|
|
60
|
-
let toastTimer = null;
|
|
61
|
-
let pendingFocus = null;
|
|
62
|
-
let armedAt = 0;
|
|
63
|
-
let armLost = false;
|
|
64
|
-
let keepAttempt = 0;
|
|
65
|
-
let lastAnnounced = null;
|
|
66
|
-
let panelEl = null;
|
|
67
|
-
|
|
68
|
-
let root = null;
|
|
69
|
-
let wellEl = null;
|
|
70
|
-
let keyed = new Map();
|
|
71
|
-
|
|
4
|
+
import { storage } from "../lib/doc-storage.js";
|
|
5
|
+
|
|
6
|
+
// The notice itself lives in conflict-notice-ui.js and is 6.8 KB Brotli: most edit
|
|
7
|
+
// boots have nothing for it to show, so this shell is what every one of them downloads
|
|
8
|
+
// instead. It watches the events that can create something to show and, on the first
|
|
9
|
+
// one, fetches the notice once and hands it that event, which it replays as if it had
|
|
10
|
+
// been listening all along. From then on the notice owns every listener; this file
|
|
11
|
+
// adds no state and draws nothing. Conflicts a reload left in the recovery store have
|
|
12
|
+
// no event to announce them, so a boot that holds some asks for the notice at once.
|
|
72
13
|
const ledger = () => window.clay?.conflicts || null;
|
|
73
|
-
const losses = () => ledger()?.list().filter((r) => !r.claimedBy && r.kind !== "apply-incomplete") ?? [];
|
|
74
|
-
const failures = () => ledger()?.list().filter((r) => !r.claimedBy && r.kind === "apply-incomplete") ?? [];
|
|
75
|
-
|
|
76
|
-
// A refused save goes first: saving is blocked until it is answered.
|
|
77
|
-
function mode() {
|
|
78
|
-
if (refused) return "refused";
|
|
79
|
-
if (losses().length) return "replaced";
|
|
80
|
-
if (failures().length) return "failed";
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function el(tag, rules, text) {
|
|
85
|
-
const node = make(tag, rules, text);
|
|
86
|
-
if (rules[0] === "all:initial") set(node, "color-scheme", "inherit");
|
|
87
|
-
node.setAttribute("clay", RUNTIME_ONLY);
|
|
88
|
-
return node;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function key(node, name) {
|
|
92
|
-
keyed.set(name, node);
|
|
93
|
-
return node;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function schemeOf() {
|
|
97
|
-
const s = getComputedStyle(document.documentElement).colorScheme;
|
|
98
|
-
return s === "light" || s === "dark" ? s : "light dark";
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// A phone keyboard shrinks the visual viewport but leaves fixed elements pinned to
|
|
102
|
-
// the layout viewport, which parks a bottom-anchored bar behind the keyboard at the
|
|
103
|
-
// exact moment somebody is typing. Lift it by the difference instead.
|
|
104
|
-
function place() {
|
|
105
|
-
if (!root) return;
|
|
106
|
-
const vv = window.visualViewport;
|
|
107
|
-
const lift = vv ? Math.max(0, window.innerHeight - vv.height - vv.offsetTop) : 0;
|
|
108
|
-
set(root, "bottom", `calc(${16 + lift}px + env(safe-area-inset-bottom,0px))`);
|
|
109
|
-
if (panelEl) set(panelEl, "max-height", `${Math.max(160, (vv?.height || window.innerHeight) - 32)}px`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function build() {
|
|
113
|
-
root = el("div", [
|
|
114
|
-
"all:initial", "position:fixed", "left:50%", "transform:translateX(-50%)",
|
|
115
|
-
"z-index:2147483001", "display:none", "flex-direction:column", "align-items:center",
|
|
116
|
-
"max-width:calc(100vw - 24px)", `font:14px/1.5 ${FONT_SANS}`, `color:${TOKENS.ink}`,
|
|
117
|
-
"text-align:left",
|
|
118
|
-
]);
|
|
119
|
-
root.setAttribute("data-clay-conflict", "");
|
|
120
|
-
document.body.appendChild(root);
|
|
121
|
-
window.visualViewport?.addEventListener("resize", place);
|
|
122
|
-
window.visualViewport?.addEventListener("scroll", place);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function schedule() {
|
|
126
|
-
if (frameQueued) return;
|
|
127
|
-
frameQueued = true;
|
|
128
|
-
const run = () => { frameQueued = false; render(); };
|
|
129
|
-
if (typeof requestAnimationFrame === "function") requestAnimationFrame(run);
|
|
130
|
-
else setTimeout(run, 16);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function render() {
|
|
134
|
-
if (checkRefusal) {
|
|
135
|
-
checkRefusal = false;
|
|
136
|
-
if (refused && !isSaveConflicted()) refused = null;
|
|
137
|
-
}
|
|
138
|
-
const m = mode();
|
|
139
|
-
if (!m) {
|
|
140
|
-
view = "hidden";
|
|
141
|
-
listOpen = false;
|
|
142
|
-
seenIds.clear();
|
|
143
|
-
freshIds.clear();
|
|
144
|
-
renderedIds = [];
|
|
145
|
-
disarm();
|
|
146
|
-
lastAnnounced = null;
|
|
147
|
-
armLost = false;
|
|
148
|
-
} else if (view === "hidden") {
|
|
149
|
-
view = "bar";
|
|
150
|
-
}
|
|
151
|
-
if (!root) {
|
|
152
|
-
if (view === "hidden" && !toastText) return;
|
|
153
|
-
if (!document.body) return;
|
|
154
|
-
build();
|
|
155
|
-
}
|
|
156
|
-
// A body replaced wholesale takes the root with it.
|
|
157
|
-
if (!root.isConnected && document.body) document.body.appendChild(root);
|
|
158
|
-
|
|
159
|
-
const active = document.activeElement;
|
|
160
|
-
let focusKey = null;
|
|
161
|
-
for (const [name, node] of keyed) if (node === active) focusKey = name;
|
|
162
|
-
const scroll = wellEl ? wellEl.scrollTop : 0;
|
|
163
|
-
keyed = new Map();
|
|
164
|
-
wellEl = null;
|
|
165
|
-
panelEl = null;
|
|
166
|
-
root.replaceChildren();
|
|
167
|
-
set(root, "color-scheme", schemeOf());
|
|
168
|
-
|
|
169
|
-
if (toastText) {
|
|
170
|
-
root.append(drawToast(toastText));
|
|
171
|
-
} else if (view === "bar") {
|
|
172
|
-
root.append(drawBar(m));
|
|
173
|
-
} else if (view === "panel") {
|
|
174
|
-
root.append(drawPanel(m));
|
|
175
|
-
}
|
|
176
|
-
set(root, "display", toastText || view !== "hidden" ? "flex" : "none");
|
|
177
|
-
if (wellEl) wellEl.scrollTop = scroll;
|
|
178
|
-
|
|
179
|
-
const want = pendingFocus || focusKey;
|
|
180
|
-
if (want && keyed.has(want)) keyed.get(want).focus({ preventScroll: true });
|
|
181
|
-
// A request for a control the toast is standing in for waits until the toast goes.
|
|
182
|
-
if (!toastText) pendingFocus = null;
|
|
183
|
-
place();
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function dot() {
|
|
187
|
-
return el("span", ["all:initial", "display:inline-block", "flex:none", "width:8px", "height:8px", "border-radius:50%", `background:${WARN}`]);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function refusedSource() {
|
|
191
|
-
return SOURCES[refused?.changedBy] || null;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function barMessage(m, n) {
|
|
195
|
-
if (m === "refused") {
|
|
196
|
-
const named = refusedSource();
|
|
197
|
-
if (!named && refused.afterTimeout) {
|
|
198
|
-
return "This page changed elsewhere, possibly by your own save that timed out. Your edits here are safe.";
|
|
199
|
-
}
|
|
200
|
-
return `This page changed ${named || "elsewhere"}. Your edits here are safe.`;
|
|
201
|
-
}
|
|
202
|
-
if (m === "failed") return "A sync update did not finish. Your edits here are safe.";
|
|
203
|
-
return `Another edit replaced ${plural(n, "change")}.`;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function titleOf(m, n) {
|
|
207
|
-
if (m === "refused") return `This page changed ${refusedSource() || "elsewhere"}`;
|
|
208
|
-
if (m === "failed") return "A sync update did not finish";
|
|
209
|
-
return `Another edit replaced ${plural(n, "change")}`;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function drawToast(text) {
|
|
213
|
-
const t = bevelSurface("div", ["display:block", `border-left:3px solid ${TOKENS.teal}`, "padding:10px 14px", "width:max-content", "max-width:calc(100vw - 24px)"]);
|
|
214
|
-
t.setAttribute("role", "status");
|
|
215
|
-
t.textContent = text;
|
|
216
|
-
return t;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function drawBar(m) {
|
|
220
|
-
const n = m === "failed" ? failures().length : losses().length;
|
|
221
|
-
const bar = bevelSurface("div", ["display:flex", "flex-wrap:wrap", "align-items:center", "gap:10px 12px", "padding:8px 8px 8px 14px", "width:max-content", "max-width:calc(100vw - 24px)"]);
|
|
222
|
-
// A redraw rebuilds the bar; only a new message is announced again.
|
|
223
|
-
const message = barMessage(m, n);
|
|
224
|
-
if (message !== lastAnnounced) {
|
|
225
|
-
bar.setAttribute("role", m === "refused" ? "alert" : "status");
|
|
226
|
-
lastAnnounced = message;
|
|
227
|
-
}
|
|
228
|
-
bar.append(dot(), bevelText("span", [], message), key(bevelButton("Review", { onClick: openPanel }), "review"));
|
|
229
|
-
return bar;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function action(label, name, { variant = "default", onClick, push = false } = {}) {
|
|
233
|
-
const phone = window.innerWidth < 480;
|
|
234
|
-
const extra = phone
|
|
235
|
-
? ["flex:1 1 100%", ...(variant === "primary" ? ["order:-1"] : [])]
|
|
236
|
-
: (push ? ["margin-left:auto"] : []);
|
|
237
|
-
return key(bevelButton(label, { variant, onClick, extra }), name);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function lede(text) {
|
|
241
|
-
return bevelText("div", ["display:block", "padding:0 16px 12px", `color:${TOKENS["ink-2"]}`, "font-size:13.5px"], text);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function drawPanel(m) {
|
|
245
|
-
const rows = m === "replaced" ? losses() : m === "failed" ? failures() : [];
|
|
246
|
-
const held = m === "refused" ? losses() : rows;
|
|
247
|
-
if (seenIds.size) for (const r of rows) if (!seenIds.has(r.id)) freshIds.add(r.id);
|
|
248
|
-
for (const id of [...freshIds]) if (!rows.some((r) => r.id === id)) freshIds.delete(id);
|
|
249
|
-
|
|
250
|
-
const title = titleOf(m, rows.length);
|
|
251
|
-
const maxHeight = Math.max(160, (window.visualViewport?.height || window.innerHeight) - 32);
|
|
252
|
-
const panel = bevelSurface("div", ["display:flex", "flex-direction:column", "width:520px", "max-width:calc(100vw - 24px)", `max-height:${maxHeight}px`]);
|
|
253
|
-
panelEl = panel;
|
|
254
|
-
panel.setAttribute("role", "dialog");
|
|
255
|
-
panel.setAttribute("aria-label", title);
|
|
256
|
-
|
|
257
|
-
const head = el("div", ["all:initial", "display:flex", "align-items:center", "gap:8px", "padding:12px 12px 8px 16px", `font:14px/1.5 ${FONT_SANS}`, `color:${TOKENS.ink}`]);
|
|
258
|
-
const t = key(bevelText("b", ["font-weight:600", "font-size:14.5px", "outline:none"], title), "title");
|
|
259
|
-
t.setAttribute("tabindex", "-1");
|
|
260
|
-
head.append(dot(), t);
|
|
261
|
-
if (m === "replaced" && freshIds.size) {
|
|
262
|
-
head.append(bevelText("span", [
|
|
263
|
-
`font:600 10.5px/1 ${FONT_MONO}`, "letter-spacing:.12em", "text-transform:uppercase", "padding:3px 6px",
|
|
264
|
-
`color:color-mix(in srgb, ${TOKENS.brass} 80%, ${TOKENS.ink})`, `background:${TOKENS["brass-soft"]}`,
|
|
265
|
-
], `${freshIds.size} new`));
|
|
266
|
-
}
|
|
267
|
-
const minimizeWrap = el("span", ["all:initial", "display:inline-flex", "margin-left:auto"]);
|
|
268
|
-
const minimize = key(bevelIconButton(MINIMIZE_SVG, { label: "Minimize", onClick: minimizePanel }), "minimize");
|
|
269
|
-
minimize.title = "Minimize to the bar. Nothing is accepted until you choose.";
|
|
270
|
-
minimizeWrap.append(minimize);
|
|
271
|
-
head.append(minimizeWrap);
|
|
272
|
-
panel.append(head);
|
|
273
|
-
|
|
274
|
-
if (m === "refused") {
|
|
275
|
-
panel.append(lede("Your edits here are safe and not saved yet; nothing will be overwritten until you choose. Their version is not on this page, so there is no list of edits to compare. Keep yours, which saves over theirs, or accept theirs, which reloads the page and drops your unsaved edits."));
|
|
276
|
-
if (held.length) {
|
|
277
|
-
panel.append(lede(`${plural(held.length, "earlier replaced edit")} ${held.length === 1 ? "is" : "are"} also held in this tab. Download my copy includes them.`));
|
|
278
|
-
}
|
|
279
|
-
if (keepFailed) panel.append(lede("That did not save either. Your edits here are still here."));
|
|
280
|
-
if (armLost) panel.append(lede("Something changed on the page after your first press, so nothing was dropped. Press Accept theirs again if you still want their version."));
|
|
281
|
-
if (armed) {
|
|
282
|
-
const warn = el("div", [
|
|
283
|
-
"all:initial", "display:block", "margin:0 16px 12px", "padding:8px 10px",
|
|
284
|
-
`border-left:3px solid ${WARN}`, `background:color-mix(in srgb, ${WARN} 14%, transparent)`,
|
|
285
|
-
`color:${TOKENS.ink}`, `font:13px/1.5 ${FONT_SANS}`,
|
|
286
|
-
]);
|
|
287
|
-
warn.append(el("b", ["all:initial", "font:inherit", "font-weight:700", "color:inherit"], "This drops your unsaved edits."), document.createTextNode(" Download your copy first if you might want them. Press again to confirm."));
|
|
288
|
-
panel.append(warn);
|
|
289
|
-
}
|
|
290
|
-
} else if (m === "failed") {
|
|
291
|
-
panel.append(lede("Part of an update from elsewhere could not be applied, so this page may be missing some of it. Your edits here are safe. Download a copy of the page as you had it, or accept to keep the page as it is."));
|
|
292
|
-
} else {
|
|
293
|
-
panel.append(lede("The page is showing the other edit. This stays until you choose. You can also save a copy of the page as you had it."));
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const actions = el("div", ["all:initial", "display:flex", "flex-wrap:wrap", "gap:8px", "padding:0 16px 14px"]);
|
|
297
|
-
const dl = action(downloadFailed ? "Try again" : downloaded ? "Downloaded" : "Download my copy", "download", { onClick: onDownload });
|
|
298
|
-
actions.append(dl);
|
|
299
|
-
if (m === "refused") {
|
|
300
|
-
const keep = action(busy === "overwrite" ? "Saving…" : keepFailed ? "Try again" : "Keep mine", "keep", { onClick: onKeep });
|
|
301
|
-
keep.setDisabled(busy === "overwrite");
|
|
302
|
-
actions.append(keep, action(armed ? "Yes, drop my edits" : "Accept theirs", "accept", { variant: "primary", onClick: onRefusedAccept, push: true }));
|
|
303
|
-
} else {
|
|
304
|
-
if (m === "replaced") {
|
|
305
|
-
const pending = ledger().hasPendingApply();
|
|
306
|
-
const put = action(pending ? "Finishing the incoming edit…" : busy === "revert" ? "Putting back…" : "Revert to mine", "revert", { onClick: onRevert });
|
|
307
|
-
put.setDisabled(pending || busy === "revert");
|
|
308
|
-
actions.append(put);
|
|
309
|
-
}
|
|
310
|
-
actions.append(action("Accept theirs", "accept", { variant: "primary", onClick: onAccept, push: true }));
|
|
311
|
-
}
|
|
312
|
-
panel.append(actions);
|
|
313
|
-
|
|
314
|
-
if (m === "replaced") {
|
|
315
|
-
if (!listOpen) {
|
|
316
|
-
const more = el("div", ["all:initial", "display:flex", "align-items:center", "gap:8px", `border-top:1px solid ${TOKENS.line}`, "padding:8px 10px 8px 16px", `color:${TOKENS.muted}`, `font:13px/1.5 ${FONT_SANS}`]);
|
|
317
|
-
const seeWrap = el("span", ["all:initial", "display:inline-flex", "margin-left:auto"]);
|
|
318
|
-
seeWrap.append(key(bevelButton("See the edits", { small: true, onClick: toggleList }), "list"));
|
|
319
|
-
more.append(el("span", ["all:initial", "font:inherit", "color:inherit"], plural(rows.length, "edit")), seeWrap);
|
|
320
|
-
panel.append(more);
|
|
321
|
-
} else {
|
|
322
|
-
panel.append(drawWell(rows));
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
14
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function drawWell(rows) {
|
|
332
|
-
// The sunk ground, not the recess: the recess's lighter blue glares against the panel in dark mode.
|
|
333
|
-
const well = bevelWell(["flex:1 1 auto", "margin:0 16px 12px", "min-height:0", "overflow:auto", `background:${TOKENS.sunk}`, `border:1px solid ${TOKENS["line-2"]}`]);
|
|
334
|
-
const head = el("div", ["all:initial", "position:sticky", "top:0", "z-index:1", "display:flex", "align-items:center", "gap:8px", "padding:8px 12px", `background:${TOKENS.sunk}`, `border-bottom:1px solid ${TOKENS["line-2"]}`, `color:${TOKENS.muted}`, `font:600 10.5px/1 ${FONT_MONO}`, "letter-spacing:.12em", "text-transform:uppercase"]);
|
|
335
|
-
const hideWrap = el("span", ["all:initial", "display:inline-flex", "margin-left:auto"]);
|
|
336
|
-
hideWrap.append(key(bevelButton("Hide", { small: true, variant: "quiet", onClick: toggleList }), "list"));
|
|
337
|
-
head.append(el("span", ["all:initial", "font:inherit", "color:inherit", "letter-spacing:inherit", "text-transform:inherit"], "Replaced edits"), hideWrap);
|
|
338
|
-
well.append(head);
|
|
339
|
-
rows.forEach((r, i) => well.append(buildRow(r, i)));
|
|
340
|
-
wellEl = well;
|
|
341
|
-
return well;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function valueLine(label, value, { mine }) {
|
|
345
|
-
const cells = [bevelText("span", [`color:${TOKENS.faint}`, `font-family:${FONT_SANS}`], label)];
|
|
346
|
-
const box = bevelText("span", [
|
|
347
|
-
"display:-webkit-box", "-webkit-line-clamp:2", "-webkit-box-orient:vertical", "overflow:hidden",
|
|
348
|
-
"overflow-wrap:anywhere",
|
|
349
|
-
...(mine
|
|
350
|
-
? [`color:${TOKENS.ink}`]
|
|
351
|
-
: [`color:${TOKENS.muted}`]),
|
|
352
|
-
]);
|
|
353
|
-
const full = (value.cutBefore ? "…" : "") + value.before + value.hit + value.after + (value.cutAfter ? "…" : "");
|
|
354
|
-
box.title = full;
|
|
355
|
-
if (value.before) box.append(bevelText("span", [`color:${TOKENS.faint}`], (value.cutBefore ? "…" : "") + value.before));
|
|
356
|
-
const hit = bevelText("span", mine
|
|
357
|
-
? [`background:color-mix(in srgb, ${WARN} 18%, transparent)`, "padding:0 4px"]
|
|
358
|
-
: (value.before || value.after
|
|
359
|
-
? ["text-decoration:underline", `text-decoration-color:color-mix(in srgb, ${WARN} 70%, transparent)`, "text-underline-offset:3px"]
|
|
360
|
-
: []), value.hit || "(empty)");
|
|
361
|
-
box.append(hit);
|
|
362
|
-
if (value.after) box.append(bevelText("span", [`color:${TOKENS.faint}`], value.after + (value.cutAfter ? "…" : "")));
|
|
363
|
-
cells.push(box);
|
|
364
|
-
return cells;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function buildRow(record, index) {
|
|
368
|
-
const row = rowOf(record);
|
|
369
|
-
const box = bevelText("div", ["display:block", "padding:8px 12px 10px", ...(index ? [`border-top:1px solid ${TOKENS.line}`] : [])]);
|
|
370
|
-
const where = bevelText("div", ["display:flex", "align-items:center", "gap:4px", "font-size:12.5px", "font-weight:600", `color:${TOKENS["ink-2"]}`, "margin-bottom:4px"]);
|
|
371
|
-
where.append(bevelText("span", [], row.name));
|
|
372
|
-
if (row.target && row.target.isConnected) where.append(key(eyeButton(row), `eye:${record.id}`));
|
|
373
|
-
box.append(where);
|
|
374
|
-
if (row.sentence) box.append(bevelText("div", ["display:block", "font-size:13px", `color:${TOKENS["ink-2"]}`], row.sentence));
|
|
375
|
-
if (blockedRows.has(record.id)) box.append(bevelText("div", ["display:block", "font-size:13px", "margin-top:4px", `color:${WARN}`], blockedRows.get(record.id)));
|
|
376
|
-
if (row.yours) {
|
|
377
|
-
const grid = bevelText("div", ["display:grid", "grid-template-columns:44px 1fr", "gap:3px 10px", "align-items:start", `font:12.5px/1.5 ${FONT_MONO}`, ...(row.sentence ? ["margin-top:4px"] : [])]);
|
|
378
|
-
grid.append(...valueLine("Yours", row.yours, { mine: true }));
|
|
379
|
-
if (row.now) grid.append(...valueLine("Now", row.now, { mine: false }));
|
|
380
|
-
box.append(grid);
|
|
381
|
-
}
|
|
382
|
-
return box;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function eyeButton(row) {
|
|
386
|
-
const base = ["all:initial", "color-scheme:inherit", "box-sizing:border-box", "cursor:pointer", "display:inline-grid", "place-items:center", "width:22px", "height:20px", `color:${TOKENS.ink}`];
|
|
387
|
-
const b = el("button", base);
|
|
388
|
-
b.type = "button";
|
|
389
|
-
b.innerHTML = GLYPHS.conflictEye;
|
|
390
|
-
protectIcon(b.firstElementChild, 16);
|
|
391
|
-
b.setAttribute("aria-label", `Show ${row.name} on page`);
|
|
392
|
-
b.title = "Show on page";
|
|
393
|
-
const paint = (extra) => { b.style.cssText = ""; for (const r of [...base, ...extra]) { const i = r.indexOf(":"); b.style.setProperty(r.slice(0, i), r.slice(i + 1), "important"); } };
|
|
394
|
-
b.addEventListener("pointerenter", () => paint([`background:color-mix(in srgb, ${WARN} 16%, transparent)`]));
|
|
395
|
-
b.addEventListener("pointerleave", () => paint([]));
|
|
396
|
-
b.addEventListener("focus", () => paint([`outline:2px solid ${TOKENS.brass}`, "outline-offset:1px"]));
|
|
397
|
-
b.addEventListener("blur", () => paint([]));
|
|
398
|
-
b.addEventListener("click", () => ring(row.target));
|
|
399
|
-
return b;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// The eye: scroll the element into view and draw a ring over it. The ring is ours
|
|
403
|
-
// and runtime-only; the page's element gets no attribute, class or style, so the
|
|
404
|
-
// dirty gate and the saved bytes see nothing.
|
|
405
|
-
let ringEl = null;
|
|
406
|
-
let ringTimer = null;
|
|
407
|
-
let ringTarget = null;
|
|
408
|
-
|
|
409
|
-
function dropRing() {
|
|
410
|
-
clearTimeout(ringTimer);
|
|
411
|
-
window.removeEventListener("scroll", placeRing, true);
|
|
412
|
-
window.removeEventListener("resize", placeRing);
|
|
413
|
-
ringEl?.remove();
|
|
414
|
-
ringEl = null;
|
|
415
|
-
ringTarget = null;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function placeRing() {
|
|
419
|
-
if (!ringEl || !ringTarget) return;
|
|
420
|
-
const r = ringTarget.getBoundingClientRect();
|
|
421
|
-
set(ringEl, "left", `${r.left}px`);
|
|
422
|
-
set(ringEl, "top", `${r.top}px`);
|
|
423
|
-
set(ringEl, "width", `${r.width}px`);
|
|
424
|
-
set(ringEl, "height", `${r.height}px`);
|
|
425
|
-
}
|
|
15
|
+
let loading = false;
|
|
16
|
+
let notice = null; // the notice module, once it has been fetched
|
|
17
|
+
let listeners = null; // the armed handlers, all of them on one controller
|
|
426
18
|
|
|
427
|
-
function
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const still = !!window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
431
|
-
const behavior = still ? "instant" : "smooth";
|
|
432
|
-
// The notice sits at the bottom of the viewport: centre the target in what is left
|
|
433
|
-
// above it, or the outline lands on the panel instead of the text.
|
|
434
|
-
const top = root?.getBoundingClientRect().top ?? 0;
|
|
435
|
-
const cover = root && top > 0 ? Math.max(0, window.innerHeight - top) : 0;
|
|
436
|
-
if (!cover) {
|
|
437
|
-
target.scrollIntoView?.({ block: "center", behavior });
|
|
438
|
-
} else {
|
|
439
|
-
// Centre first, so a page whose own scroller holds the target still brings it
|
|
440
|
-
// up; then move the window the rest of the way. A target taller than the free
|
|
441
|
-
// space shows its top instead.
|
|
442
|
-
target.scrollIntoView?.({ block: "center", behavior: "instant" });
|
|
443
|
-
const r = target.getBoundingClientRect();
|
|
444
|
-
const free = window.innerHeight - cover;
|
|
445
|
-
const shift = r.height > free - 24 ? r.top - 12 : r.top + r.height / 2 - free / 2;
|
|
446
|
-
window.scrollBy?.({ top: shift, behavior });
|
|
447
|
-
}
|
|
448
|
-
ringTarget = target;
|
|
449
|
-
ringEl = el("div", ["all:initial", "color-scheme:inherit", "position:fixed", "pointer-events:none", "z-index:2147483001", `outline:2px solid ${WARN}`, "outline-offset:3px", `background:color-mix(in srgb, ${WARN} 14%, transparent)`]);
|
|
450
|
-
document.body.appendChild(ringEl);
|
|
451
|
-
placeRing();
|
|
452
|
-
window.addEventListener("scroll", placeRing, true);
|
|
453
|
-
window.addEventListener("resize", placeRing);
|
|
454
|
-
ringTimer = setTimeout(dropRing, 2500);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
function openPanel() {
|
|
458
|
-
view = "panel";
|
|
459
|
-
pendingFocus = "title";
|
|
460
|
-
render();
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
function minimizePanel() {
|
|
464
|
-
dropRing();
|
|
465
|
-
view = "bar";
|
|
19
|
+
function load(trigger) {
|
|
20
|
+
if (loading) return;
|
|
21
|
+
loading = true;
|
|
466
22
|
disarm();
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
function onAccept() {
|
|
479
|
-
const l = ledger();
|
|
480
|
-
const ids = renderedIds.filter((id) => { const r = l?.get(id); return r && !r.claimedBy; });
|
|
481
|
-
if (l && ids.length) l.acknowledge(ids, { reason: "accepted" });
|
|
482
|
-
view = mode() ? "bar" : "hidden";
|
|
483
|
-
pendingFocus = "review";
|
|
484
|
-
schedule();
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Revert to mine. The ids are the rendered ones, copied before anything runs, as
|
|
488
|
-
// Accept reads them. The module that does the work loads on first use, and only
|
|
489
|
-
// here, where the sync plugin has published the ledger. What it could not put back
|
|
490
|
-
// keeps its row, with the reason under it, until the ledger moves on.
|
|
491
|
-
async function onRevert() {
|
|
492
|
-
const l = ledger();
|
|
493
|
-
if (busy || !l || l.hasPendingApply()) return;
|
|
494
|
-
const ids = renderedIds.filter((id) => { const r = l.get(id); return r && !r.claimedBy; });
|
|
495
|
-
if (!ids.length) return;
|
|
496
|
-
busy = "revert";
|
|
497
|
-
render();
|
|
498
|
-
// Focus that lands outside the notice while the revert and its save run is the
|
|
499
|
-
// person moving on; Review takes focus afterwards only if it stayed here.
|
|
500
|
-
let stayed = true;
|
|
501
|
-
const track = (e) => { stayed = !!root?.contains(e.target); };
|
|
502
|
-
document.addEventListener("focus", track, true);
|
|
503
|
-
let result;
|
|
504
|
-
try {
|
|
505
|
-
const { revertConflicts } = await import("../sync/conflict-revert.js");
|
|
506
|
-
result = await revertConflicts(ids);
|
|
507
|
-
} catch (err) {
|
|
508
|
-
console.error("[clay] Revert to mine did not finish", err);
|
|
509
|
-
result = { revertedIds: [], blockedIds: ids, saveResult: null };
|
|
510
|
-
} finally {
|
|
511
|
-
document.removeEventListener("focus", track, true);
|
|
512
|
-
}
|
|
513
|
-
busy = null;
|
|
514
|
-
for (const id of result.blockedIds) blockedRows.set(id, ledger()?.get(id)?.kind === "json" ? DATA_BLOCKED_ROW : BLOCKED_ROW);
|
|
515
|
-
view = mode() ? "bar" : "hidden";
|
|
516
|
-
pendingFocus = stayed ? "review" : null;
|
|
517
|
-
const saving = result.saveResult && (result.saveResult.ok || result.saveResult.msg === "Save already in progress");
|
|
518
|
-
if (result.revertedIds.length && saving) toast(`Put back ${plural(result.revertedIds.length, "change")}. Saving.`);
|
|
519
|
-
else render();
|
|
23
|
+
import("./conflict-notice-ui.js").then(
|
|
24
|
+
(module) => {
|
|
25
|
+
notice = module;
|
|
26
|
+
module.start(trigger);
|
|
27
|
+
},
|
|
28
|
+
(error) => {
|
|
29
|
+
console.error("clayjs: the conflict notice could not be downloaded; it will try again on the next conflict", error);
|
|
30
|
+
loading = false;
|
|
31
|
+
arm();
|
|
32
|
+
}
|
|
33
|
+
);
|
|
520
34
|
}
|
|
521
35
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
downloadFailed = !ok;
|
|
532
|
-
downloaded = ok;
|
|
533
|
-
clearTimeout(downloadedTimer);
|
|
534
|
-
if (ok) downloadedTimer = setTimeout(() => { downloaded = false; schedule(); }, 1200);
|
|
535
|
-
render();
|
|
36
|
+
// Arming twice is arming once, so a boot, an event that arrived while the notice was
|
|
37
|
+
// being fetched and a mode lease can all ask for the same handlers.
|
|
38
|
+
function arm() {
|
|
39
|
+
listeners ||= new AbortController();
|
|
40
|
+
const { signal } = listeners;
|
|
41
|
+
document.addEventListener("clay:sync-conflicts-changed", onLedgerChanged, { signal });
|
|
42
|
+
document.addEventListener("clay:save-conflict", onSaveConflict, { signal });
|
|
43
|
+
document.addEventListener("clay:document-upgraded", onUpgraded, { signal });
|
|
44
|
+
document.addEventListener("clay:undo-kept", onUndoKept, { signal });
|
|
536
45
|
}
|
|
537
46
|
|
|
538
47
|
function disarm() {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
armed = null;
|
|
48
|
+
listeners?.abort();
|
|
49
|
+
listeners = null;
|
|
542
50
|
}
|
|
543
51
|
|
|
544
|
-
|
|
545
|
-
if (
|
|
546
|
-
|
|
547
|
-
busy = "overwrite";
|
|
548
|
-
keepFailed = false;
|
|
549
|
-
disarm();
|
|
550
|
-
render();
|
|
551
|
-
let result;
|
|
552
|
-
try {
|
|
553
|
-
result = await window.clay?.save?.overwrite?.();
|
|
554
|
-
} catch {
|
|
555
|
-
result = { ok: false };
|
|
556
|
-
}
|
|
557
|
-
// A newer attempt, a new refusal or a landed save already settled this one.
|
|
558
|
-
if (attempt !== keepAttempt || busy !== "overwrite") return;
|
|
559
|
-
if (!result?.ok && isSaveConflicted()) {
|
|
560
|
-
// Left showing: the save still has not happened.
|
|
561
|
-
busy = null;
|
|
562
|
-
keepFailed = true;
|
|
563
|
-
}
|
|
564
|
-
schedule();
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// Two presses. The first is a choice, not a confirmation, and this is the one
|
|
568
|
-
// control here that throws away work nothing can bring back. The second only
|
|
569
|
-
// counts if nothing changed in between: a new refusal, a new loss or new typing
|
|
570
|
-
// each disarm it.
|
|
571
|
-
function onRefusedAccept() {
|
|
572
|
-
if (!armed) {
|
|
573
|
-
armed = { refusalGeneration, ledgerRevision, dirtyGeneration: gateCaptureToken().gen };
|
|
574
|
-
armedAt = Date.now();
|
|
575
|
-
armLost = false;
|
|
576
|
-
clearTimeout(armTimer);
|
|
577
|
-
armTimer = setTimeout(() => { disarm(); schedule(); }, ARM_MS);
|
|
578
|
-
render();
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
// The second half of a double click is not a second decision.
|
|
582
|
-
if (Date.now() - armedAt < DOUBLE_PRESS_MS) return;
|
|
583
|
-
const token = armed;
|
|
584
|
-
const current = () =>
|
|
585
|
-
armed === token &&
|
|
586
|
-
token.refusalGeneration === refusalGeneration &&
|
|
587
|
-
token.ledgerRevision === ledgerRevision &&
|
|
588
|
-
token.dirtyGeneration === gateCaptureToken().gen;
|
|
589
|
-
if (!reloadAfterDiscard({ isCurrent: current, reload: () => window.location.reload() })) {
|
|
590
|
-
disarm();
|
|
591
|
-
armLost = true;
|
|
592
|
-
render();
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
export function toast(text) {
|
|
597
|
-
toastText = text;
|
|
598
|
-
clearTimeout(toastTimer);
|
|
599
|
-
toastTimer = setTimeout(() => { toastText = null; schedule(); }, TOAST_MS);
|
|
600
|
-
render();
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
function onLedgerChanged() {
|
|
604
|
-
ledgerRevision++;
|
|
605
|
-
if (armed) disarm();
|
|
606
|
-
for (const id of [...blockedRows.keys()]) if (!ledger()?.get(id)) blockedRows.delete(id);
|
|
607
|
-
schedule();
|
|
52
|
+
function onLedgerChanged(e) {
|
|
53
|
+
if (!ledger()?.size) return;
|
|
54
|
+
load(e);
|
|
608
55
|
}
|
|
609
56
|
|
|
610
57
|
function onSaveConflict(e) {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
58
|
+
load(e);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// The document itself was upgraded somewhere else: the notice is drawn for this
|
|
62
|
+
// too, because it is the one thing on the page that can offer a reload.
|
|
63
|
+
function onUpgraded(e) {
|
|
64
|
+
load(e);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// A refused step this tab kept is offered the way a replaced edit is: the bar with
|
|
68
|
+
// Review, and the panel behind it lists the copy. A keep the store refused stays in
|
|
69
|
+
// memory, so the notice is not raised for it.
|
|
70
|
+
function onUndoKept(e) {
|
|
71
|
+
if (e?.detail?.ok !== true) return;
|
|
72
|
+
load(e);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// A conflict this tab held before a reload lives in the recovery store, not in the
|
|
76
|
+
// ledger. That store keeps one small count key for exactly this question: it is read
|
|
77
|
+
// here, and the module behind it is fetched only when the count says something is
|
|
78
|
+
// stored, so a boot with nothing stored downloads no recovery code. A kept undo step
|
|
79
|
+
// keeps its own count the same way, and the notice it asks for reads both.
|
|
80
|
+
const HINT_KEY = "conflicts";
|
|
81
|
+
const UNDO_HINT_KEY = "undo";
|
|
82
|
+
|
|
83
|
+
const hinted = (name) => storage.local.get(name)?.count > 0;
|
|
84
|
+
|
|
85
|
+
function keptFromEarlier() {
|
|
86
|
+
if (hinted(UNDO_HINT_KEY)) load(null);
|
|
87
|
+
if (!hinted(HINT_KEY)) return;
|
|
88
|
+
import("../recovery/conflicts.js").then(
|
|
89
|
+
(module) => {
|
|
90
|
+
if (!loading && module.pendingConflicts() > 0) load(null);
|
|
91
|
+
},
|
|
92
|
+
() => {}
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// A conflict still unresolved when the page leaves edit would go with the session:
|
|
97
|
+
// saving is blocked until it is answered, and the answer needs the editor. A save the
|
|
98
|
+
// host refused is one of those, and so is a loss the ledger is still holding for this
|
|
99
|
+
// tab. A change the recovery store kept from an earlier session is a copy this page can
|
|
100
|
+
// still hand back, not input it is holding, so it never stands in the way.
|
|
101
|
+
function unresolved() {
|
|
102
|
+
const held = (ledger()?.list?.() ?? []).filter((record) => !record.claimedBy).map((record) => record.id);
|
|
103
|
+
if (held.length) return held;
|
|
104
|
+
return isSaveConflicted() ? ["save"] : [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The lazy notice as a mode lease.
|
|
109
|
+
*
|
|
110
|
+
* The shell and the notice it fetches are editor-only: they exist to answer a conflict,
|
|
111
|
+
* and the answer needs the editing lane. `quiesce` refuses an exit over a conflict that
|
|
112
|
+
* is still unresolved and leaves the notice standing exactly as it is, which is what a
|
|
113
|
+
* refusal has to keep showing; `resume` has nothing to reopen. `initial: true` adopts the
|
|
114
|
+
* shell and the notice the boot already armed, and arms nothing again. `dispose` (after
|
|
115
|
+
* the barrier) takes back the shell's handlers and stops the notice, so a later frame in
|
|
116
|
+
* view draws nothing.
|
|
117
|
+
*/
|
|
118
|
+
export function conflictNoticeParticipant() {
|
|
119
|
+
return {
|
|
120
|
+
id: "clay:conflict-notice",
|
|
121
|
+
order: 960, // last: it reads the save lane's refusal and every peer's losses
|
|
122
|
+
prepare(context = {}) {
|
|
123
|
+
return {
|
|
124
|
+
enable() {
|
|
125
|
+
if (context.initial === true) return;
|
|
126
|
+
if (!notice && !loading) arm();
|
|
127
|
+
},
|
|
128
|
+
quiesce() {
|
|
129
|
+
const pending = unresolved();
|
|
130
|
+
if (!pending.length) return;
|
|
131
|
+
throw Object.assign(new Error("clay.conflict-notice: a conflict here is still unresolved"), {
|
|
132
|
+
code: "conflict",
|
|
133
|
+
pending,
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
resume() {},
|
|
137
|
+
dispose() {
|
|
138
|
+
disarm();
|
|
139
|
+
loading = false;
|
|
140
|
+
notice?.stop?.();
|
|
141
|
+
notice = null;
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
onDomReady(() => {
|
|
695
149
|
if (!isEditMode) return;
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
document.addEventListener("clay:save-conflict-released", onConflictReleased);
|
|
704
|
-
document.addEventListener("keydown", onKeydown);
|
|
705
|
-
document.addEventListener("input", onInput, true);
|
|
706
|
-
document.addEventListener("focus", onFocus, true);
|
|
707
|
-
if (ledger()?.size) schedule();
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
onDomReady(init);
|
|
150
|
+
if (ledger()?.size) load(null);
|
|
151
|
+
else {
|
|
152
|
+
// Armed first, always: a conflict can arrive while the hint is still being read.
|
|
153
|
+
arm();
|
|
154
|
+
keptFromEarlier();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
711
157
|
|
|
712
|
-
export
|
|
713
|
-
state: () => ({ view, listOpen, renderedIds: renderedIds.slice(), refused, armed, busy, ledgerRevision }),
|
|
714
|
-
render,
|
|
715
|
-
};
|
|
158
|
+
export { conflictNoticeParticipant as modeParticipant };
|