@panphora/clayjs 1.15.2 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/THIRD-PARTY-NOTICES.md +16 -0
- package/compat/highlight-11.9.0.min.js +1213 -0
- package/compat/highlight-LICENSE +29 -0
- package/compat/journal-assets.lock.json +22 -0
- package/compat/overtype-2.6.1.min.js +116 -0
- package/compat/overtype-LICENSE +21 -0
- package/dist/all.js +90 -0
- package/dist/clay-dom.js +84 -0
- package/dist/clay-events.js +103 -0
- package/dist/clay-internals.js +85 -0
- package/dist/clay-options.js +93 -0
- package/dist/clay-router.js +98 -0
- package/dist/clay-ui.js +95 -0
- package/dist/clay-utils.js +84 -0
- package/dist/clay.js +146 -0
- package/dist/clay.standalone.js +83090 -58706
- package/dist/m/attrs/add-from-T5LJXFFI.js +1 -0
- package/dist/m/attrs/onaftersave-OKSWEJQF.js +1 -0
- package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +1 -0
- package/dist/m/attrs/save-freeze-FUP4Q4X3.js +1 -0
- package/dist/m/c-2AZDZWCK.js +1 -0
- package/dist/m/c-2JL6E75N.js +1 -0
- package/dist/m/c-2M73252O.js +1 -0
- package/dist/m/c-2SPVQSHC.js +1 -0
- package/dist/m/c-33HUYEDI.js +1 -0
- package/dist/m/c-33JJ6CBX.js +2 -0
- package/dist/m/c-35NBGMZB.js +1 -0
- package/dist/m/c-3GJ4SDE2.js +1 -0
- package/dist/m/c-3IQRECYS.js +1 -0
- package/dist/m/c-3OWDMNJR.js +2 -0
- package/dist/m/c-46XYM55L.js +1 -0
- package/dist/m/c-4B6H4G5V.js +1 -0
- package/dist/m/c-4DRMS4QG.js +9 -0
- package/dist/m/c-4FFHZM32.js +1 -0
- package/dist/m/c-4JZDZFF6.js +1 -0
- package/dist/m/c-4L3BTFRK.js +1 -0
- package/dist/m/c-4Q2XTQ2N.js +2 -0
- package/dist/m/c-4WKFKEKF.js +1 -0
- package/dist/m/c-4YSCRICO.js +1 -0
- package/dist/m/c-54RCDCBU.js +1 -0
- package/dist/m/c-5M4YEKCX.js +4 -0
- package/dist/m/c-5R4PTRG2.js +1 -0
- package/dist/m/c-5V3MOVJT.js +1 -0
- package/dist/m/c-5VYOWDTI.js +1 -0
- package/dist/m/c-5Y6TNX5W.js +34 -0
- package/dist/m/c-5YIHJ3TJ.js +1 -0
- package/dist/m/c-66O32MV3.js +28 -0
- package/dist/m/c-6HMB24EV.js +1 -0
- package/dist/m/c-7DJDKPGL.js +1 -0
- package/dist/m/c-7O6AFR65.js +1 -0
- package/dist/m/c-7SWMYKAI.js +1 -0
- package/dist/m/c-7VGZ7RU5.js +1 -0
- package/dist/m/c-A7LS5UZW.js +1 -0
- package/dist/m/c-ANQTJ3B5.js +2 -0
- package/dist/m/c-AQLFEATB.js +1 -0
- package/dist/m/c-AYVOREBE.js +1 -0
- package/dist/m/c-B2ZXPOS3.js +3 -0
- package/dist/m/c-BBVGTQG7.js +1 -0
- package/dist/m/c-BMIZKMPV.js +1 -0
- package/dist/m/c-BTDI4Y23.js +1 -0
- package/dist/m/c-BWFVSBDF.js +1 -0
- package/dist/m/c-BYTKL6UX.js +1 -0
- package/dist/m/c-CFHW5AMY.js +6 -0
- package/dist/m/c-CJZSLMVQ.js +1 -0
- package/dist/m/c-CYWLPPUS.js +1 -0
- package/dist/m/c-CZUSOB7V.js +1 -0
- package/dist/m/c-DBE6ORSI.js +1 -0
- package/dist/m/c-DCFSEJZG.js +1 -0
- package/dist/m/c-DNZ5IFM6.js +1 -0
- package/dist/m/c-EDHUR4MX.js +1 -0
- package/dist/m/c-EJTGYSLN.js +1 -0
- package/dist/m/c-EY6UOM54.js +1 -0
- package/dist/m/c-F3CKJTJS.js +1 -0
- package/dist/m/c-F7Y6FLPY.js +1 -0
- package/dist/m/c-FA4DZVTC.js +1 -0
- package/dist/m/c-FILLTJA7.js +1 -0
- package/dist/m/c-G2BUZITR.js +1 -0
- package/dist/m/c-GB3JTV6E.js +1 -0
- package/dist/m/c-GBGWXTX4.js +1 -0
- package/dist/m/c-GHBCCVG4.js +1 -0
- package/dist/m/c-GJAEGGXY.js +1 -0
- package/dist/m/c-GOWBDM6J.js +1 -0
- package/dist/m/c-GQ6F6IDK.js +1 -0
- package/dist/m/c-H4LG442Q.js +1 -0
- package/dist/m/c-HA6INT6J.js +1 -0
- package/dist/m/c-HPURYVZS.js +2 -0
- package/dist/m/c-HRDCD3WN.js +67 -0
- package/dist/m/c-HT33EQRN.js +1 -0
- package/dist/m/c-HVYXIS72.js +1 -0
- package/dist/m/c-IB52R237.js +1 -0
- package/dist/m/c-IJUK2GPW.js +1 -0
- package/dist/m/c-IKWNE4R4.js +1 -0
- package/dist/m/c-IU3IYEU6.js +1 -0
- package/dist/m/c-IZ2EBRAJ.js +1 -0
- package/dist/m/c-J7CA54IP.js +1 -0
- package/dist/m/c-JAX2667S.js +6 -0
- package/dist/m/c-KI7M4AY3.js +1 -0
- package/dist/m/c-KIPQJX4B.js +1 -0
- package/dist/m/c-KKWLDOPM.js +1 -0
- package/dist/m/c-KR4UW4KW.js +1 -0
- package/dist/m/c-KY3VJGUA.js +8 -0
- package/dist/m/c-L4WQSC27.js +1 -0
- package/dist/m/c-LFPHCXY3.js +1 -0
- package/dist/m/c-LR4CGEPT.js +91 -0
- package/dist/m/c-LRPUO2MX.js +1 -0
- package/dist/m/c-LTGMXXKO.js +1 -0
- package/dist/m/c-LXENEBV7.js +1 -0
- package/dist/m/c-LZJPNR2W.js +1 -0
- package/dist/m/c-MMUIUUZG.js +2 -0
- package/dist/m/c-MTQHWQC3.js +1 -0
- package/dist/m/c-N326R2JD.js +1 -0
- package/dist/m/c-N7JPUPGC.js +1 -0
- package/dist/m/c-NMJRXKPC.js +8 -0
- package/dist/m/c-NRPJEVJS.js +1 -0
- package/dist/m/c-NUHP2XDB.js +1 -0
- package/dist/m/c-NZMNXVF6.js +1 -0
- package/dist/m/c-OCSZ4QGN.js +1 -0
- package/dist/m/c-OIBUTDXI.js +1 -0
- package/dist/m/c-OIDRZKAF.js +1 -0
- package/dist/m/c-OYTFP5QN.js +1 -0
- package/dist/m/c-P7KNDG34.js +1 -0
- package/dist/m/c-PPOSCAQD.js +1 -0
- package/dist/m/c-PQ5LK6QE.js +993 -0
- package/dist/m/c-PZ4XEPUP.js +2 -0
- package/dist/m/c-QHIWXHRO.js +1 -0
- package/dist/m/c-QHWJ5ZIJ.js +1 -0
- package/dist/m/c-QIX5BJYN.js +1 -0
- package/dist/m/c-QNKREOL6.js +1 -0
- package/dist/m/c-QQHEX6HA.js +1 -0
- package/dist/m/c-R7RMANJI.js +9 -0
- package/dist/m/c-RI4NT75Y.js +1 -0
- package/dist/m/c-RM7J7BV2.js +1 -0
- package/dist/m/c-RUJKSUQW.js +1 -0
- package/dist/m/c-RVMWGXEJ.js +1 -0
- package/dist/m/c-S7H2DRCB.js +1 -0
- package/dist/m/c-S7LK4KUE.js +1 -0
- package/dist/m/c-SGCCC4FW.js +25 -0
- package/dist/m/c-SGIA4IEQ.js +188 -0
- package/dist/m/c-SWRJFCNN.js +1 -0
- package/dist/m/c-T4VO4OCB.js +1 -0
- package/dist/m/c-TCCA5BZN.js +2 -0
- package/dist/m/c-TQEMVJCS.js +1 -0
- package/dist/m/c-TQZ2JYHD.js +1 -0
- package/dist/m/c-TR6RMDQ7.js +3 -0
- package/dist/m/c-TRS6TM6O.js +1 -0
- package/dist/m/c-UGHH4MB3.js +1 -0
- package/dist/m/c-UKHTLWUW.js +1 -0
- package/dist/m/c-UL2YUY52.js +1 -0
- package/dist/m/c-UNN7LWCT.js +1 -0
- package/dist/m/c-UXBMRDZB.js +235 -0
- package/dist/m/c-V7PZZRG7.js +1 -0
- package/dist/m/c-VAK7SG37.js +1 -0
- package/dist/m/c-VDSS4K3Z.js +1 -0
- package/dist/m/c-VFX45IZQ.js +1 -0
- package/dist/m/c-VHNSBD2M.js +1 -0
- package/dist/m/c-VKEDWYDF.js +1 -0
- package/dist/m/c-VL52ADDR.js +1 -0
- package/dist/m/c-WN5AY3OZ.js +1 -0
- package/dist/m/c-WNYBLIOZ.js +1 -0
- package/dist/m/c-WRBKEJD4.js +1 -0
- package/dist/m/c-WWDBYXLK.js +1 -0
- package/dist/m/c-WYFDLITL.js +1 -0
- package/dist/m/c-XN6MBKI6.js +67 -0
- package/dist/m/c-XNNW2CWK.js +1 -0
- package/dist/m/c-XPELB6E5.js +1 -0
- package/dist/m/c-XQXZAO7N.js +1 -0
- package/dist/m/c-Y6USGTS5.js +5 -0
- package/dist/m/c-YDH7Q3NY.js +1 -0
- package/dist/m/c-YG2NJYS6.js +135 -0
- package/dist/m/c-Z36J63PP.js +1 -0
- package/dist/m/c-Z4VRC7GU.js +7 -0
- package/dist/m/c-Z6K52OLP.js +19 -0
- package/dist/m/c-ZDY2MDAA.js +21 -0
- package/dist/m/c-ZE46QDIH.js +1 -0
- package/dist/m/c-ZUCOKOHT.js +1 -0
- package/dist/m/core/admin-attrs-VGNLAUDK.js +1 -0
- package/dist/m/core/autosave-E4PEFPZ6.js +1 -0
- package/dist/m/core/conflict-notice-VQ6Y64PH.js +1 -0
- package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
- package/dist/m/core/host-permission-XN3K2IW3.js +1 -0
- package/dist/m/core/is-edit-mode-JXHDVGUL.js +1 -0
- package/dist/m/core/persist-HKHA66IY.js +3 -0
- package/dist/m/core/recovery-TBBSICE2.js +1 -0
- package/dist/m/core/save-WUCHTQBE.js +1 -0
- package/dist/m/core/save-core-FJVHT5M3.js +1 -0
- package/dist/m/core/snapshot-F3SWDZMM.js +1 -0
- package/dist/m/core/unsaved-warning-STKBHE6G.js +1 -0
- package/dist/m/dom/all-APS5ZSKP.js +1 -0
- package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
- package/dist/m/events/index-23DPTUT4.js +1 -0
- package/dist/m/internals/index-WOVAP3RU.js +1 -0
- package/dist/m/lib/cache-bust-CGWT6ZJI.js +1 -0
- package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
- package/dist/m/loader-HXX6YNH5.js +1 -0
- package/dist/m/manifest-F22DEDAC.js +1 -0
- package/dist/m/manifest-edit-C3C7376B.js +1 -0
- package/dist/m/manifest.json +3491 -0
- package/dist/m/options/options-2LH76JQ3.js +1 -0
- package/dist/m/page/editor-XUTB2HB2.js +189 -0
- package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +6 -0
- package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
- package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
- package/dist/m/plugins/demo-ZIBFAA3R.js +1 -0
- package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
- package/dist/m/plugins/indicator-PV4YW2ID.js +1 -0
- package/dist/m/plugins/live-NUTTXSBS.js +1 -0
- package/dist/m/plugins/menu-HCKO3DSP.js +138 -0
- package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
- package/dist/m/plugins/page-KSUH7G6J.js +2 -0
- package/dist/m/plugins/page-view-W73OW7WI.js +1 -0
- package/dist/m/plugins/people-AJJ32YCG.js +1 -0
- package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
- package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
- package/dist/m/plugins/richclay-RC3L2F6X.js +517 -0
- package/dist/m/plugins/richclay-RC3L2F6X.js.LEGAL.txt +1 -0
- package/dist/m/plugins/sortable-YRMQX3ZU.js +1 -0
- package/dist/m/plugins/source-KHK5UUSK.js +1 -0
- package/dist/m/plugins/undo-OF2L3CXU.js +1 -0
- package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
- package/dist/m/plugins/upgrade-ZNYODP5Y.js +1 -0
- package/dist/m/plugins/upload-XSGNILUY.js +1 -0
- package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +21 -0
- package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +1 -0
- package/dist/m/plugins/wire-ZWVWKF76.js +1 -0
- package/dist/m/reader-core-ILWLXVMP.js +68 -0
- package/dist/m/route/route-KOCZSEGN.js +1 -0
- package/dist/m/sync/live-sync-4AUVXVJU.js +1 -0
- package/dist/m/ui/index-EVHKMKBL.js +1 -0
- package/dist/m/utils/index-4TJCVLT7.js +1 -0
- package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
- package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
- package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
- package/dist/offline-manifest.json +3800 -0
- package/dist/page/themes/light-1.css +406 -0
- package/dist/server/core.cjs +23662 -22860
- package/dist/server/offline-runtime.cjs +4954 -0
- package/dist/server/offline-runtime.mjs +4923 -0
- package/dist/server/rooms.mjs +9 -0
- package/docs/guide.md +1078 -669
- package/entries/all.js +2 -0
- package/entries/clay-data.js +1 -1
- package/entries/clay-dom.js +2 -0
- package/entries/clay-events.js +2 -0
- package/entries/clay-internals.js +2 -0
- package/entries/clay-options.js +2 -0
- package/entries/clay-router.js +2 -0
- package/entries/clay-ui.js +2 -0
- package/entries/clay-utils.js +2 -0
- package/entries/clay.js +2 -0
- package/entries/sap.js +6 -6
- package/fonts/ibm-plex-mono-LICENSE +93 -0
- package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-LICENSE +93 -0
- package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- package/fonts/ibm-plex.css +71 -0
- package/fonts/inter-LICENSE +93 -0
- package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
- package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-greek-wght-normal.woff2 +0 -0
- package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
- package/fonts/inter.css +62 -0
- package/fonts/newsreader-LICENSE +93 -0
- package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
- package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
- package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
- package/fonts/newsreader.css +26 -0
- package/fonts/work-sans-LICENSE +93 -0
- package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
- package/fonts/work-sans.css +8 -0
- package/package.json +20 -3
- package/packed-contract.json +1 -0
- package/src/attrs/add-from.js +27 -4
- package/src/attrs/save-freeze.js +7 -52
- package/src/core/admin-attrs.js +119 -12
- package/src/core/admin-contenteditable.js +12 -10
- package/src/core/admin-inputs.js +14 -15
- package/src/core/admin-onclick.js +2 -0
- package/src/core/autosave.js +74 -21
- package/src/core/conflict-download.js +22 -0
- package/src/core/conflict-notice-ui.js +1173 -0
- package/src/core/conflict-notice.js +140 -697
- package/src/core/edit-mode.js +147 -14
- package/src/core/etag.js +30 -12
- package/src/core/host-meta.js +72 -8
- package/src/core/host-permission.js +29 -0
- package/src/core/is-edit-mode.js +145 -12
- package/src/core/mode-key.js +125 -0
- package/src/core/mode-lifecycle.js +544 -0
- package/src/core/mode-receipt.js +30 -0
- package/src/core/mode-refusal.js +13 -0
- package/src/core/mode-resources.js +52 -0
- package/src/core/mode-support.js +82 -0
- package/src/core/mode-switch.js +259 -0
- package/src/core/mode-transition.js +112 -0
- package/src/core/page-data-write.js +305 -0
- package/src/core/page-data.js +26 -227
- package/src/core/page-format-wire.js +4 -0
- package/src/core/page-format.js +58 -0
- package/src/core/persist.js +37 -9
- package/src/core/preserve-authored.js +114 -0
- package/src/core/recovery.js +772 -0
- package/src/core/runtime-url.js +105 -0
- package/src/core/save-core.js +109 -12
- package/src/core/save-replace.js +72 -0
- package/src/core/save-size-notice.js +134 -0
- package/src/core/save-size.js +140 -0
- package/src/core/save.js +805 -118
- package/src/core/snapshot.js +121 -11
- package/src/core/source-map.js +157 -9
- package/src/core/unsaved-warning.js +14 -4
- package/src/core/upgrade-run.js +408 -0
- package/src/core/version.js +18 -0
- package/src/lib/assets.js +30 -0
- package/src/lib/autosave-debug-gate.js +50 -0
- package/src/lib/autosave-scheduler.js +141 -0
- package/src/lib/boot-imports.js +8 -0
- package/src/lib/derived.js +3 -1
- package/src/lib/dirty-gate.js +15 -0
- package/src/lib/doc-storage.js +33 -0
- package/src/lib/effect.js +9 -4
- package/src/lib/mutation.js +4 -2
- package/src/lib/offline-runtime.js +226 -0
- package/src/lib/preload.js +48 -0
- package/src/lib/root-attrs.js +1 -1
- package/src/lib/trash.js +18 -0
- package/src/lib/undo/capture-move.js +152 -0
- package/src/live/local-channel.js +61 -0
- package/src/live/local-lane.js +60 -9
- package/src/live/server-lane.js +6 -3
- package/src/loader-logic.js +76 -15
- package/src/loader.js +218 -30
- package/src/page/adopt-boot.js +76 -22
- package/src/page/adopt.js +25 -4
- package/src/page/awareness.js +230 -0
- package/src/page/blocks.js +79 -5
- package/src/page/blocksel.js +20 -4
- package/src/page/board-face.js +13 -0
- package/src/page/board-panel.js +5 -17
- package/src/page/board-style.js +2 -13
- package/src/page/board-view.js +1 -11
- package/src/page/board-views.js +17 -0
- package/src/page/board.js +2 -2
- package/src/page/boot-pack.js +26 -0
- package/src/page/boot.js +237 -0
- package/src/page/checklist.js +1 -1
- package/src/page/chrome.js +411 -61
- package/src/page/csv-import.js +17 -2
- package/src/page/document-title-dom.js +40 -0
- package/src/page/document-title-runtime.js +76 -0
- package/src/page/document-title.js +9 -0
- package/src/page/drag.js +48 -13
- package/src/page/drop.js +2 -1
- package/src/page/editor.js +330 -86
- package/src/page/feature-loader.js +72 -0
- package/src/page/features/board.js +12 -0
- package/src/page/features/code.js +13 -0
- package/src/page/features/media.js +290 -0
- package/src/page/features/registry.js +23 -0
- package/src/page/features/table.js +383 -0
- package/src/page/{tool-view.js → features/tool.js} +33 -2
- package/src/page/format-codec.js +215 -0
- package/src/page/format-gate.js +179 -0
- package/src/page/format-level.js +157 -0
- package/src/page/highlight-slot.js +23 -0
- package/src/page/highlight.js +30 -16
- package/src/page/history-intent.js +78 -0
- package/src/page/inline.js +18 -25
- package/src/page/items.js +52 -11
- package/src/page/link.js +1 -0
- package/src/page/markdown.js +86 -1
- package/src/page/media-views.js +3 -0
- package/src/page/media.js +33 -245
- package/src/page/outline-cache.js +11 -0
- package/src/page/placeholder.js +27 -7
- package/src/page/projection.js +196 -50
- package/src/page/reader-patch.js +306 -0
- package/src/page/recover.js +1 -1
- package/src/page/rich/page-tool.js +1 -4
- package/src/page/runtime-owner.js +27 -0
- package/src/page/save-projection.js +7 -4
- package/src/page/schema.js +14 -0
- package/src/page/table-rect-key.js +3 -0
- package/src/page/table-select.js +4 -3
- package/src/page/table-style.js +1 -1
- package/src/page/table-views.js +3 -0
- package/src/page/table.js +8 -293
- package/src/page/themes/light-1.css +139 -2
- package/src/page/themes/light-1.source.css +269 -0
- package/src/page/tool-capability.js +3 -0
- package/src/page/tool-views.js +3 -0
- package/src/page/view.js +390 -107
- package/src/page/ybind/awareness.js +172 -0
- package/src/page/ybind/binding.js +9 -1
- package/src/page/ybind/bridge.js +50 -47
- package/src/page/ybind/decode.js +52 -19
- package/src/page/ybind/doc.js +27 -2
- package/src/page/ybind/group.js +12 -3
- package/src/page/ybind/index.js +2 -0
- package/src/page/ybind/lineage.js +71 -0
- package/src/page/ybind/marks.js +25 -1
- package/src/page/ybind/migrate.js +48 -13
- package/src/page/ybind/moves.js +32 -21
- package/src/page/ybind/positions.js +50 -0
- package/src/page/ybind/provider.js +84 -22
- package/src/page/ybind/recovery-proof.js +191 -0
- package/src/page/ybind/recovery-replay.js +130 -0
- package/src/page/ybind/tokens.js +15 -6
- package/src/page/ybind/walk.js +10 -3
- package/src/page/ybind/writer.js +48 -4
- package/src/plugins/ai-edit.js +167 -32
- package/src/plugins/bind-text.js +218 -0
- package/src/plugins/cms.js +93 -0
- package/src/plugins/indicator.js +57 -5
- package/src/plugins/live.js +282 -34
- package/src/plugins/menu/claygoo.svg.js +1 -0
- package/src/plugins/menu/seo-head.js +323 -0
- package/src/plugins/menu/seo.js +849 -0
- package/src/plugins/menu.js +425 -0
- package/src/plugins/page-view.js +131 -0
- package/src/plugins/page.js +1022 -275
- package/src/plugins/people.js +158 -31
- package/src/plugins/richclay.js +119 -9
- package/src/plugins/sortable.js +370 -60
- package/src/plugins/source.js +189 -366
- package/src/plugins/undo.js +289 -19
- package/src/plugins/unsent.js +132 -0
- package/src/plugins/upgrade-run.js +1 -0
- package/src/plugins/upgrade.js +210 -0
- package/src/plugins/upload-task.js +571 -0
- package/src/plugins/upload-ui.js +265 -0
- package/src/plugins/upload.js +34 -153
- package/src/plugins/visitor-drafts/inert.js +32 -0
- package/src/plugins/visitor-drafts/owner-chip.js +3 -0
- package/src/plugins/visitor-drafts/prompt.js +2 -2
- package/src/plugins/visitor-drafts-owner.js +85 -0
- package/src/plugins/visitor-drafts.js +31 -42
- package/src/plugins/wire.js +82 -1
- package/src/reader-core.js +26 -0
- package/src/recovery/conflicts.js +287 -0
- package/src/recovery/identity.js +154 -0
- package/src/recovery/store.js +792 -0
- package/src/recovery/undo.js +257 -0
- package/src/server/checkpoint/index.js +68 -4
- package/src/server/offline-runtime/index.js +214 -0
- package/src/server/rooms/index.js +2 -1
- package/src/server/rooms/live-data-rooms.js +2 -2
- package/src/server/rooms/page-admission.js +33 -0
- package/src/server/rooms/page-lineage.js +56 -0
- package/src/server/rooms/page-rebase.js +11 -5
- package/src/server/rooms/page-state.js +2 -0
- package/src/server/rooms/page-write-admission.js +9 -3
- package/src/source/session.js +705 -0
- package/src/sync/conflict-revert.js +5 -4
- package/src/sync/conflicts-edit.js +119 -0
- package/src/sync/conflicts-facade.js +49 -0
- package/src/sync/conflicts.js +99 -1
- package/src/sync/editing-marker.js +37 -6
- package/src/sync/editing-sender.js +8 -1
- package/src/sync/live-sync-edit.js +63 -0
- package/src/sync/live-sync.js +364 -119
- package/src/sync/local-presence.js +187 -0
- package/src/sync/merge-declarations.js +10 -0
- package/src/sync/merge-engine.js +12 -0
- package/src/sync/presence-store.js +412 -0
- package/src/sync/presence-target.js +243 -0
- package/src/sync/presence.js +72 -41
- package/src/sync/saved-handoff.js +200 -0
- package/src/sync/saved-stream.js +535 -0
- package/src/sync/version-guard.js +92 -0
- package/src/sync/wire-profile.js +81 -0
- package/src/ui/bevel-controls.js +2 -8
- package/src/ui/page-scheme.js +7 -0
- package/src/ui/skins/cms.js +1 -1
- package/src/ui/theme-config.js +150 -0
- package/src/ui/theme.js +6 -138
- package/src/ui/toast-lazy.js +11 -0
- package/src/vendor/LICENSES-optional.md +253 -0
- package/src/vendor/highlight.vendor.js +3 -0
- package/src/vendor/hljs-bash.vendor.js +2 -0
- package/src/vendor/hljs-css.vendor.js +2 -0
- package/src/vendor/hljs-javascript.vendor.js +2 -0
- package/src/vendor/hljs-json.vendor.js +2 -0
- package/src/vendor/hljs-python.vendor.js +2 -0
- package/src/vendor/hljs-xml.vendor.js +2 -0
- package/src/vendor/hyper-html-api-data.vendor.js +1 -1
- package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
- package/src/vendor/hyper-morph-identity.vendor.js +2 -0
- package/src/vendor/hyper-morph-json.vendor.js +3 -0
- package/src/vendor/hyper-morph.vendor.js +6 -6
- package/src/vendor/hyper-undo.vendor.js +1 -1
- package/src/vendor/hypercms.vendor.js +169 -84
- package/src/vendor/marked.vendor.js +68 -0
- package/src/vendor/overtype.vendor.js +994 -0
- package/src/vendor/richclay.vendor.js +17 -17
- package/src/vendor/sap-core.vendor.js +5 -5
- package/src/vendor/sap-ownership.vendor.js +11 -7
|
@@ -399,6 +399,7 @@ function planText(rec, apply) {
|
|
|
399
399
|
const subject = rec.recovery.subject;
|
|
400
400
|
const live = liveOf(subject, apply, subject.nodeType);
|
|
401
401
|
if (!live) return null;
|
|
402
|
+
const box = live.nodeType === 3 && live.parentNode?.localName === "textarea" ? live.parentNode : live;
|
|
402
403
|
const local = t.local.text.slice(t.local.start, t.local.end);
|
|
403
404
|
if (STRUCTURAL.test(t.merged.text.slice(t.merged.start, t.merged.end)) || local.includes(BREAK)) return null;
|
|
404
405
|
const op = { kind: "text", id: rec.id, ticket: apply.ticket, key: subject.key, fragment: t.local.fragment, encoding: t.encoding, local, ids: new Set(), range: document.createRange() };
|
|
@@ -411,12 +412,12 @@ function planText(rec, apply) {
|
|
|
411
412
|
}
|
|
412
413
|
// A textarea shows its value, which typing moves away from its child text: the
|
|
413
414
|
// spot is found in the value and both are written.
|
|
414
|
-
if (
|
|
415
|
+
if (box.localName === "textarea") {
|
|
415
416
|
if (op.encoding !== "plain") return null;
|
|
416
|
-
const span = rebase(t.merged.text,
|
|
417
|
+
const span = rebase(t.merged.text, box.value, t.merged.start, t.merged.end);
|
|
417
418
|
if (!span) return null;
|
|
418
|
-
op.range.selectNodeContents(
|
|
419
|
-
return { ...op, root:
|
|
419
|
+
op.range.selectNodeContents(box);
|
|
420
|
+
return { ...op, root: box, field: box, scope: box, f: { text: box.value }, s: span.s, e: span.e, before: box.value, bs: span.s, be: span.e };
|
|
420
421
|
}
|
|
421
422
|
const f = project(live);
|
|
422
423
|
const from = scopeStart(t, live, f);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The edit-only half of the conflict ledger.
|
|
3
|
+
*
|
|
4
|
+
* `conflicts.js` is the one conflict module a reader and an editor both load, and a
|
|
5
|
+
* reader never records a conflict: it has nothing unsaved for an incoming frame to
|
|
6
|
+
* win over. So the copy a reload has to keep, and the store it goes to, live here.
|
|
7
|
+
* `conflicts.js` starts this fetch as it evaluates in edit mode, and hands it the
|
|
8
|
+
* material it cannot rebuild; a conflict that arrives before it lands is queued
|
|
9
|
+
* there and drains the moment it does.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { TAB_LOCAL_ROOT_ATTRS } from '../lib/root-attrs.js';
|
|
13
|
+
import { serializeRecoveryClone } from '../core/snapshot.js';
|
|
14
|
+
import { rowOf } from '../core/conflict-presentation.js';
|
|
15
|
+
|
|
16
|
+
// The recovery half of this module is fetched, not imported: no recovery code is in
|
|
17
|
+
// the boot graph, and a page that never loses anything never downloads it. The copy
|
|
18
|
+
// itself is made synchronously below and queued here, so what has to be preserved is
|
|
19
|
+
// preserved before anything is released; the queue drains the moment the module
|
|
20
|
+
// arrives. Until it does, the live record is the only copy, which is where this
|
|
21
|
+
// started.
|
|
22
|
+
const DURABLE_QUEUE_MAX = 20;
|
|
23
|
+
let durable = null;
|
|
24
|
+
let loadingDurable = false;
|
|
25
|
+
const durableQueue = [];
|
|
26
|
+
|
|
27
|
+
function loadDurable() {
|
|
28
|
+
if (durable || loadingDurable) return;
|
|
29
|
+
loadingDurable = true;
|
|
30
|
+
import('../recovery/conflicts.js').then(
|
|
31
|
+
(module) => {
|
|
32
|
+
durable = module;
|
|
33
|
+
loadingDurable = false;
|
|
34
|
+
drainDurable();
|
|
35
|
+
},
|
|
36
|
+
(error) => {
|
|
37
|
+
loadingDurable = false;
|
|
38
|
+
console.error('clayjs: the conflict recovery module could not be downloaded; conflicts stay in this tab only', error);
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function drainDurable() {
|
|
44
|
+
if (!durable) {
|
|
45
|
+
loadDurable();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
while (durableQueue.length) {
|
|
49
|
+
const spec = durableQueue.shift();
|
|
50
|
+
try {
|
|
51
|
+
durable.recordConflicts(spec);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.error('clayjs: the conflict could not be stored for a later reload; it is still here', err);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The losing text a reload has to keep, as the notice already words it. Everything
|
|
59
|
+
// here is plain text: the row's own name, the sentence that explains it, and the two
|
|
60
|
+
// sides, with the rest of the copy in the stored root.
|
|
61
|
+
function conflictEntry(rec, context) {
|
|
62
|
+
let row = null;
|
|
63
|
+
try {
|
|
64
|
+
row = rowOf(rec);
|
|
65
|
+
} catch (_) {}
|
|
66
|
+
const full = (side) => (!side ? '' : (side.cutBefore ? '\u2026' : '') + side.before + side.hit + side.after + (side.cutAfter ? '\u2026' : ''));
|
|
67
|
+
return {
|
|
68
|
+
id: String(rec.id),
|
|
69
|
+
kind: String(rec.kind ?? ''),
|
|
70
|
+
key: rec.recovery?.key == null ? '' : String(rec.recovery.key),
|
|
71
|
+
name: row?.name ? String(row.name) : '',
|
|
72
|
+
sentence: row?.sentence ? String(row.sentence) : '',
|
|
73
|
+
yours: full(row?.yours),
|
|
74
|
+
now: full(row?.now),
|
|
75
|
+
context,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The pre-apply root as the bytes a save would have written, with the root
|
|
80
|
+
// attributes this response and this tab own removed first: a recovery copy never
|
|
81
|
+
// carries a save token, a file id or this library's own UI state.
|
|
82
|
+
function recoveryHtml(apply) {
|
|
83
|
+
try {
|
|
84
|
+
const source = apply.recoveryRoot ?? apply.root;
|
|
85
|
+
const clone = source?.cloneNode?.(true);
|
|
86
|
+
if (!clone) return null;
|
|
87
|
+
for (const name of TAB_LOCAL_ROOT_ATTRS) clone.removeAttribute(name);
|
|
88
|
+
return serializeRecoveryClone(clone, { prepared: apply.domain === 'save' });
|
|
89
|
+
} catch (err) {
|
|
90
|
+
console.error('clayjs: the conflict recovery page could not be serialized; the conflict text is still stored', err);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Copy the material before anything is released, and never let the store's failure
|
|
96
|
+
// reach the ledger: a quota or a blocked database leaves the live record exactly
|
|
97
|
+
// where it was. Losing the copy to this queue is bounded, never silent.
|
|
98
|
+
export function keep({ apply, records }) {
|
|
99
|
+
try {
|
|
100
|
+
durableQueue.push({
|
|
101
|
+
apply: { id: apply.id, source: apply.source, domain: apply.domain, seq: apply.seq, etag: apply.etag, ticket: apply.ticket },
|
|
102
|
+
root: recoveryHtml(apply),
|
|
103
|
+
conflicts: records.map(({ rec, context }) => conflictEntry(rec, context)),
|
|
104
|
+
});
|
|
105
|
+
if (durableQueue.length > DURABLE_QUEUE_MAX) durableQueue.shift();
|
|
106
|
+
drainDurable();
|
|
107
|
+
} catch (err) {
|
|
108
|
+
console.error('clayjs: the conflict could not be stored for a later reload; it is still here', err);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// An acknowledged conflict leaves the store with the ledger record it came from:
|
|
113
|
+
// nothing here reads it back, and a store that cannot be reached just keeps it.
|
|
114
|
+
export function dismiss(ids) {
|
|
115
|
+
import('../recovery/conflicts.js').then(
|
|
116
|
+
(module) => module.dismissConflicts(ids),
|
|
117
|
+
() => {}
|
|
118
|
+
).catch(() => {});
|
|
119
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// clay.conflicts before the conflict ledger has loaded. No merge has happened, so
|
|
2
|
+
// there are no conflicts: reads answer empty, writes have nothing to act on, and a
|
|
3
|
+
// context resolver registered now is handed to the ledger when it loads. Once loaded,
|
|
4
|
+
// every member delegates, so the object a page holds never changes.
|
|
5
|
+
let ledger = null;
|
|
6
|
+
let loading = null;
|
|
7
|
+
const resolvers = new Map();
|
|
8
|
+
|
|
9
|
+
export function loadConflicts() {
|
|
10
|
+
loading ??= Promise.all([import('./conflicts.js'), import('./conflict-footprints.js')]).then(([c, f]) => {
|
|
11
|
+
ledger = { ...c, beginLineageCapture: f.beginLineageCapture };
|
|
12
|
+
// The unregister the real registry handed back, so a resolver the page drops
|
|
13
|
+
// before or after the download leaves the ledger's registry alike.
|
|
14
|
+
for (const resolver of [...resolvers.keys()]) resolvers.set(resolver, c.registerContext(resolver));
|
|
15
|
+
return ledger;
|
|
16
|
+
});
|
|
17
|
+
return loading;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const conflictLedger = () => ledger;
|
|
21
|
+
|
|
22
|
+
export const conflicts = {
|
|
23
|
+
get size() { return ledger ? ledger.conflicts.size : 0; },
|
|
24
|
+
list() { return ledger ? ledger.conflicts.list() : []; },
|
|
25
|
+
get(id) { return ledger ? ledger.conflicts.get(id) : null; },
|
|
26
|
+
acknowledge(ids, options = {}) {
|
|
27
|
+
if (ledger) return ledger.conflicts.acknowledge(ids, options);
|
|
28
|
+
if (!Array.isArray(ids)) throw new TypeError('clay.conflicts.acknowledge needs an array of ids');
|
|
29
|
+
return { removedIds: [], remainingIds: [] };
|
|
30
|
+
},
|
|
31
|
+
claim(ids, options = {}) {
|
|
32
|
+
if (ledger) return ledger.conflicts.claim(ids, options);
|
|
33
|
+
if (!options.owner) throw new TypeError('clay.conflicts.claim needs an owner');
|
|
34
|
+
return { release() {} };
|
|
35
|
+
},
|
|
36
|
+
registerContext(resolver) {
|
|
37
|
+
if (ledger) return ledger.conflicts.registerContext(resolver);
|
|
38
|
+
if (typeof resolver !== 'function') throw new TypeError('clay.conflicts.registerContext needs a resolver function');
|
|
39
|
+
resolvers.set(resolver, null);
|
|
40
|
+
return () => {
|
|
41
|
+
const registered = resolvers.get(resolver);
|
|
42
|
+
resolvers.delete(resolver);
|
|
43
|
+
if (registered) registered();
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
contextFor(rec, apply) { return ledger ? ledger.conflicts.contextFor(rec, apply) : null; },
|
|
47
|
+
recoveryOf(id) { return ledger ? ledger.conflicts.recoveryOf(id) : null; },
|
|
48
|
+
hasPendingApply() { return ledger ? ledger.conflicts.hasPendingApply() : false; },
|
|
49
|
+
};
|
package/src/sync/conflicts.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { registerUnsavedState } from '../lib/unsaved-state.js';
|
|
2
2
|
import { releaseProtection } from './conflict-footprints.js';
|
|
3
|
+
import { isEditMode } from '../core/is-edit-mode.js';
|
|
3
4
|
|
|
4
5
|
// What incoming frames won over this tab's unsaved edits, kept for the life of
|
|
5
6
|
// the document until the person decides. Nothing here is read by a save, a
|
|
@@ -13,8 +14,32 @@ let counter = 0;
|
|
|
13
14
|
const records = new Map();
|
|
14
15
|
const applies = new Map();
|
|
15
16
|
const pending = new Set();
|
|
17
|
+
const resolvers = new Set();
|
|
16
18
|
let registration = null;
|
|
17
19
|
|
|
20
|
+
// The recording half is edit-only, like live sync's: a reader has nothing unsaved to
|
|
21
|
+
// lose. Edit mode starts the fetch as this file evaluates; a loss that arrives first
|
|
22
|
+
// waits in the queue below.
|
|
23
|
+
const EDIT_QUEUE_MAX = 20;
|
|
24
|
+
let edit = null;
|
|
25
|
+
let editLoad = null;
|
|
26
|
+
const editQueue = [];
|
|
27
|
+
|
|
28
|
+
export function loadEdit() {
|
|
29
|
+
editLoad ??= import('./conflicts-edit.js').then(
|
|
30
|
+
(module) => {
|
|
31
|
+
edit = module;
|
|
32
|
+
while (editQueue.length) edit.keep(editQueue.shift());
|
|
33
|
+
},
|
|
34
|
+
(error) => {
|
|
35
|
+
editLoad = null;
|
|
36
|
+
console.error('clayjs: the conflict recovery module could not be downloaded; conflicts stay in this tab only', error);
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (isEditMode) loadEdit();
|
|
42
|
+
|
|
18
43
|
function nextId(prefix = '') {
|
|
19
44
|
return `${nonce}:${prefix}${++counter}`;
|
|
20
45
|
}
|
|
@@ -45,12 +70,61 @@ function isLoss(c) {
|
|
|
45
70
|
return r.localLost !== false;
|
|
46
71
|
}
|
|
47
72
|
|
|
73
|
+
// The page names a conflict when it can: a record id, a label and a field, from the
|
|
74
|
+
// app's own registered resolver. What comes back is a name and an action for the
|
|
75
|
+
// notice, never a claim, and never a replacement for the generic ones.
|
|
76
|
+
const CONTEXT_FIELDS = ['kind', 'id', 'label', 'field'];
|
|
77
|
+
|
|
78
|
+
// What can be stored: the identity, the label and the field. An `open` action is a
|
|
79
|
+
// function bound to this page's tree and stays on the live record.
|
|
80
|
+
function contextFields(value) {
|
|
81
|
+
if (!value || typeof value !== 'object') return null;
|
|
82
|
+
const context = {};
|
|
83
|
+
for (const field of CONTEXT_FIELDS) {
|
|
84
|
+
const found = value[field];
|
|
85
|
+
if (typeof found === 'string' && found) context[field] = found;
|
|
86
|
+
else if (typeof found === 'number' && Number.isFinite(found)) context[field] = String(found);
|
|
87
|
+
}
|
|
88
|
+
return Object.keys(context).length ? context : null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function contextOf(value) {
|
|
92
|
+
const context = contextFields(value);
|
|
93
|
+
if (!context) return null;
|
|
94
|
+
if (typeof value.open === 'function') context.open = value.open;
|
|
95
|
+
return context;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The newest registration answers first: an app that changes its own resolver gets
|
|
99
|
+
// the new one without having to unregister the old.
|
|
100
|
+
function contextFor(rec, apply) {
|
|
101
|
+
for (const resolver of [...resolvers].reverse()) {
|
|
102
|
+
let answer;
|
|
103
|
+
try {
|
|
104
|
+
answer = resolver(rec, apply);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error('clayjs: a conflict context resolver threw; the conflict stays generic', err);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const context = contextOf(answer);
|
|
110
|
+
if (context) return context;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function registerContext(resolver) {
|
|
116
|
+
if (typeof resolver !== 'function') throw new TypeError('clay.conflicts.registerContext needs a resolver function');
|
|
117
|
+
resolvers.add(resolver);
|
|
118
|
+
return () => resolvers.delete(resolver);
|
|
119
|
+
}
|
|
120
|
+
|
|
48
121
|
function install(apply, raw) {
|
|
49
122
|
const id = nextId();
|
|
50
123
|
const rec = Object.assign(raw[0], {
|
|
51
124
|
id, applyId: apply.id, source: apply.source, ticket: apply.ticket,
|
|
52
125
|
rawReports: raw, claimedBy: null,
|
|
53
126
|
});
|
|
127
|
+
rec.context = contextFor(rec, apply);
|
|
54
128
|
records.set(id, rec);
|
|
55
129
|
apply.ids.add(id);
|
|
56
130
|
return id;
|
|
@@ -91,6 +165,19 @@ function foldInsertions(groups) {
|
|
|
91
165
|
return [...folded.values()];
|
|
92
166
|
}
|
|
93
167
|
|
|
168
|
+
// What the store may keep of a record's context, read now, while the record still
|
|
169
|
+
// knows it: the identity, the label and the field, never the resolver's action, which
|
|
170
|
+
// is a function bound to this page's tree and stays on the live record.
|
|
171
|
+
function keepConflicts(apply, ids) {
|
|
172
|
+
const kept = ids.map((id) => records.get(id)).filter(Boolean);
|
|
173
|
+
if (!kept.length) return;
|
|
174
|
+
const spec = { apply, records: kept.map((rec) => ({ rec, context: contextFields(rec.context) })) };
|
|
175
|
+
if (edit) return edit.keep(spec);
|
|
176
|
+
editQueue.push(spec);
|
|
177
|
+
if (editQueue.length > EDIT_QUEUE_MAX) editQueue.shift();
|
|
178
|
+
loadEdit();
|
|
179
|
+
}
|
|
180
|
+
|
|
94
181
|
export function completeApply(applyId, conflicts, { ticket }) {
|
|
95
182
|
const apply = applies.get(applyId);
|
|
96
183
|
if (!apply) return [];
|
|
@@ -103,6 +190,7 @@ export function completeApply(applyId, conflicts, { ticket }) {
|
|
|
103
190
|
groups.get(key).push(c);
|
|
104
191
|
}
|
|
105
192
|
const ids = foldInsertions([...groups.values()]).map((raw) => install(apply, raw));
|
|
193
|
+
if (ids.length) keepConflicts(apply, ids);
|
|
106
194
|
pending.delete(applyId);
|
|
107
195
|
if (!ids.length) applies.delete(applyId);
|
|
108
196
|
if (ids.length) changed({ added: ids.map((id) => records.get(id)) });
|
|
@@ -117,6 +205,7 @@ export function failApply(applyId, error, { ticket = 0 } = {}) {
|
|
|
117
205
|
pending.delete(applyId);
|
|
118
206
|
const id = install(apply, [{ kind: 'apply-incomplete', detail: null, error: String(error) }]);
|
|
119
207
|
records.get(id).rawReports = [];
|
|
208
|
+
keepConflicts(apply, [id]);
|
|
120
209
|
changed({ added: [records.get(id)] });
|
|
121
210
|
return [id];
|
|
122
211
|
}
|
|
@@ -147,7 +236,13 @@ export const conflicts = {
|
|
|
147
236
|
if (!apply.ids.size) applies.delete(rec.applyId);
|
|
148
237
|
}
|
|
149
238
|
}
|
|
150
|
-
if (removedIds.length)
|
|
239
|
+
if (removedIds.length) {
|
|
240
|
+
changed({ removedIds, reason });
|
|
241
|
+
import('./conflicts-edit.js').then(
|
|
242
|
+
(module) => module.dismiss(removedIds),
|
|
243
|
+
() => {}
|
|
244
|
+
);
|
|
245
|
+
}
|
|
151
246
|
return { removedIds, remainingIds: [...records.keys()] };
|
|
152
247
|
},
|
|
153
248
|
claim(ids, { owner }) {
|
|
@@ -178,6 +273,9 @@ export const conflicts = {
|
|
|
178
273
|
},
|
|
179
274
|
};
|
|
180
275
|
},
|
|
276
|
+
registerContext,
|
|
277
|
+
contextFor,
|
|
278
|
+
|
|
181
279
|
recoveryOf(id) {
|
|
182
280
|
const rec = records.get(id);
|
|
183
281
|
return rec ? applies.get(rec.applyId) || null : null;
|
|
@@ -14,18 +14,37 @@
|
|
|
14
14
|
import { hostSupports } from '../core/host-meta.js';
|
|
15
15
|
import { make, set } from '../lib/hostile-css.js';
|
|
16
16
|
import { personColor } from '../lib/person-face.js';
|
|
17
|
-
import { RUNTIME_ONLY } from '../ui/bevel-controls.js';
|
|
18
|
-
import { FONT_MONO } from '../ui/bevel.js';
|
|
19
17
|
import { EDITING_PATH, elementAt } from './editing-path.js';
|
|
20
18
|
|
|
21
19
|
const FACE = '#FFFCF6';
|
|
22
20
|
const RESET = ['all:initial', 'color-scheme:inherit', 'direction:ltr', 'unicode-bidi:isolate'];
|
|
23
21
|
const CHIP_HEIGHT = 18;
|
|
24
22
|
const GAP = 3;
|
|
25
|
-
|
|
23
|
+
|
|
24
|
+
// The marking token and the chip face are the Bevel subset, a first-use download: a
|
|
25
|
+
// reader with nobody else on the page never draws a marker, so it never fetches them.
|
|
26
|
+
let ui = null;
|
|
27
|
+
let uiLoad = null;
|
|
28
|
+
|
|
29
|
+
function loadUI() {
|
|
30
|
+
uiLoad ??= Promise.all([import('../ui/bevel-controls.js'), import('../ui/bevel.js')])
|
|
31
|
+
.then(([controls, bevel]) => (ui = {
|
|
32
|
+
RUNTIME_ONLY: controls.RUNTIME_ONLY,
|
|
33
|
+
CHIP_FONT: `600 11px/16px ${bevel.FONT_MONO}`,
|
|
34
|
+
}))
|
|
35
|
+
.catch((error) => {
|
|
36
|
+
uiLoad = null;
|
|
37
|
+
throw error;
|
|
38
|
+
});
|
|
39
|
+
return uiLoad;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// A frame never rejects its way back to the stream: a chunk that failed to arrive
|
|
43
|
+
// leaves the block unmarked and the next frame asks again.
|
|
44
|
+
const uiReady = () => loadUI().then(() => true, () => false);
|
|
26
45
|
|
|
27
46
|
function runtimeRoot(el) {
|
|
28
|
-
el.setAttribute('clay', RUNTIME_ONLY);
|
|
47
|
+
el.setAttribute('clay', ui.RUNTIME_ONLY);
|
|
29
48
|
return el;
|
|
30
49
|
}
|
|
31
50
|
|
|
@@ -59,7 +78,7 @@ class EditingMarker {
|
|
|
59
78
|
async update(frame) {
|
|
60
79
|
this.editors = editorsOf(frame);
|
|
61
80
|
if (!(await this.supported())) return;
|
|
62
|
-
this.render();
|
|
81
|
+
await this.render();
|
|
63
82
|
}
|
|
64
83
|
|
|
65
84
|
clear() {
|
|
@@ -79,7 +98,19 @@ class EditingMarker {
|
|
|
79
98
|
});
|
|
80
99
|
}
|
|
81
100
|
|
|
101
|
+
// A synchronous draw while the chunk is here, as it always was, and one draw
|
|
102
|
+
// queued for when it arrives on the first frame that needs it.
|
|
82
103
|
render() {
|
|
104
|
+
if (ui) {
|
|
105
|
+
this.paint();
|
|
106
|
+
return Promise.resolve();
|
|
107
|
+
}
|
|
108
|
+
return uiReady().then((ok) => {
|
|
109
|
+
if (ok) this.paint();
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
paint() {
|
|
83
114
|
const groups = new Map();
|
|
84
115
|
for (const editor of this.editors) {
|
|
85
116
|
const element = elementAt(editor.path);
|
|
@@ -130,7 +161,7 @@ class EditingMarker {
|
|
|
130
161
|
const chip = runtimeRoot(make('span', [
|
|
131
162
|
...RESET,
|
|
132
163
|
'box-sizing:border-box', `height:${CHIP_HEIGHT}px`, 'padding:1px 6px',
|
|
133
|
-
`font:${CHIP_FONT}`, 'letter-spacing:.02em', 'white-space:nowrap',
|
|
164
|
+
`font:${ui.CHIP_FONT}`, 'letter-spacing:.02em', 'white-space:nowrap',
|
|
134
165
|
'max-width:180px', 'overflow:hidden', 'text-overflow:ellipsis',
|
|
135
166
|
`background-color:${personColor(editor.id)}`, `color:${FACE}`,
|
|
136
167
|
`box-shadow:0 0 0 1px ${FACE}`,
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { blockOf, pathOf } from './editing-path.js';
|
|
2
|
+
import { presence } from './presence-store.js';
|
|
2
3
|
|
|
3
4
|
const INTERVAL_MS = 250;
|
|
4
5
|
const RETRY_MS = 2000;
|
|
5
6
|
const EVENTS = ['focusin', 'focusout', 'selectionchange', 'clay:sync-applied', 'clay:workspace-background', 'clay:workspace-foreground'];
|
|
6
7
|
|
|
8
|
+
// The default is the facade's generic half: a page that publishes a target does
|
|
9
|
+
// not post it itself, and an owner that attached a transport is the only thing that
|
|
10
|
+
// does. The marker stands aside while an explicit page target owns the session, so
|
|
11
|
+
// one document never has two senders talking at once.
|
|
12
|
+
const publish = (target) => presence.setGeneric(target);
|
|
13
|
+
|
|
7
14
|
export class EditingSender {
|
|
8
|
-
constructor({ send, now = () => Date.now() }) {
|
|
15
|
+
constructor({ send = publish, now = () => Date.now() }) {
|
|
9
16
|
this.send = send;
|
|
10
17
|
this.now = now;
|
|
11
18
|
this.others = false;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The edit-only half of live sync.
|
|
3
|
+
*
|
|
4
|
+
* `live-sync.js` is the one sync module a reader and an editor both load. The
|
|
5
|
+
* names below are the ones it reaches only in edit mode (or whose view-mode
|
|
6
|
+
* answer is a constant), so they live in this separate chunk: live-sync loads it
|
|
7
|
+
* with `await import()` on its edit path, and a view-mode tab never fetches the
|
|
8
|
+
* save, admin or editing-sender graph behind it.
|
|
9
|
+
*/
|
|
10
|
+
import { loadEdit as loadConflictsEdit } from "./conflicts.js";
|
|
11
|
+
import { presence } from "./presence-store.js";
|
|
12
|
+
|
|
13
|
+
export { savePageThrottled, setLastSavedBaselines, setUnsavedChanges, getLastSavedBytes, conflictResolvedBySync, isSaveConflicted, baselineSettled, schedulePaintHeal } from "../core/save.js";
|
|
14
|
+
export { enableContentEditable } from "../core/admin-contenteditable.js";
|
|
15
|
+
export { enableOnClick } from "../core/admin-onclick.js";
|
|
16
|
+
export { enableAdminInputs } from "../core/admin-inputs.js";
|
|
17
|
+
export { enableAdminResources } from "../core/admin-resources.js";
|
|
18
|
+
export { EditingSender } from "./editing-sender.js";
|
|
19
|
+
|
|
20
|
+
// The lifecycle participant a live switch prepares for generic sync. `sync` is the
|
|
21
|
+
// one LiveSync instance the page owns: the saved lane it follows as a reader is what
|
|
22
|
+
// an entry stops, and the live lane it starts is what an exit disposes. The exit's
|
|
23
|
+
// saved lane is the controller's to restart, not this lease's.
|
|
24
|
+
export function syncParticipant(sync) {
|
|
25
|
+
// The outgoing target goes before the editor is taken down, so a caret this tab
|
|
26
|
+
// published cannot outlive the session that published it. A page target is
|
|
27
|
+
// released with the ownership it came with; the generic marker is cleared
|
|
28
|
+
// outright. Neither falls back to a positional marker: the exit publishes nobody.
|
|
29
|
+
const quiesce = () => {
|
|
30
|
+
presence.setGeneric(null);
|
|
31
|
+
presence.releaseTarget();
|
|
32
|
+
return sync.lane === "live" && sync._morphInFlight ? { ok: false, code: "pending-input" } : { ok: true };
|
|
33
|
+
};
|
|
34
|
+
const dispose = () => sync.stop();
|
|
35
|
+
return {
|
|
36
|
+
id: "clay:sync",
|
|
37
|
+
order: 50,
|
|
38
|
+
async prepare(context) {
|
|
39
|
+
await loadConflictsEdit();
|
|
40
|
+
// Adoption: the explicit initial edit started this lane as the module
|
|
41
|
+
// evaluated, so the lease covers it and starts nothing new.
|
|
42
|
+
if (context?.initial) return { quiesce, dispose };
|
|
43
|
+
let started = false;
|
|
44
|
+
return {
|
|
45
|
+
quiesce,
|
|
46
|
+
// The commit: the lane a reader was following stops, and the same stream
|
|
47
|
+
// slot opens on the live lane.
|
|
48
|
+
enable() {
|
|
49
|
+
const file = sync.currentFile;
|
|
50
|
+
sync.stop();
|
|
51
|
+
sync.start(file, { lane: "live" });
|
|
52
|
+
started = true;
|
|
53
|
+
},
|
|
54
|
+
// A refused entry disposes what it prepared as well, and until the commit this
|
|
55
|
+
// lane was the reader's: stopping it would take the saved lane the switch gives
|
|
56
|
+
// back with it.
|
|
57
|
+
dispose: () => {
|
|
58
|
+
if (started) sync.stop();
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|