@panphora/clayjs 1.15.1 → 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
package/src/core/edit-mode.js
CHANGED
|
@@ -1,28 +1,161 @@
|
|
|
1
1
|
// Edit mode system - combines the editmode toggle with page type setting.
|
|
2
|
-
// Loads in BOTH view and edit mode:
|
|
2
|
+
// Loads in BOTH view and edit mode: setMode must exist in view mode
|
|
3
3
|
// (it's the way IN), and setPageTypeOnPageLoad marks <html> in either mode.
|
|
4
|
-
import { isDraftMode, isEditMode, isOwner } from "./is-edit-mode.js";
|
|
4
|
+
import { canEdit, isDraftMode, isEditMode, isOwner, mode, modeState, registerModeParticipant } from "./is-edit-mode.js";
|
|
5
|
+
import { writeReceipt } from "./mode-receipt.js";
|
|
5
6
|
import onDomReady from "../lib/dom-ready.js";
|
|
6
|
-
import { addDocumentTransform } from "./snapshot.js";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
let leaving = false;
|
|
9
|
+
|
|
10
|
+
// The public registration. The built-ins own the `clay:` prefix (`clay:resources` and
|
|
11
|
+
// every module participant), so an application may not take an id from that space.
|
|
12
|
+
const register = (participant) => {
|
|
13
|
+
if (typeof participant?.id === "string" && participant.id.startsWith("clay:")) {
|
|
14
|
+
throw new TypeError(`clay.modeLifecycle: the id "${participant.id}" is reserved`);
|
|
15
|
+
}
|
|
16
|
+
return registerModeParticipant(participant);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const modeLifecycle = {
|
|
20
|
+
register,
|
|
21
|
+
canWrite: () => modeState().phase === "idle" && isEditMode,
|
|
22
|
+
get state() {
|
|
23
|
+
return Object.freeze(modeState());
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// The lazy dispatcher. The controller is a dynamic target, fetched by an explicit
|
|
28
|
+
// initial edit or the first live switch — never because a live declaration, a menu or
|
|
29
|
+
// a participant exists — and a rejected fetch is retried on the next ask.
|
|
30
|
+
let controller = null;
|
|
31
|
+
|
|
32
|
+
const fetchController = () => (controller ||= import("./mode-lifecycle.js").catch(() => (controller = null)));
|
|
33
|
+
|
|
34
|
+
// A live switch is a contract the document signs, not a hint. The loader records both
|
|
35
|
+
// resolved plans here only when `<html clay-mode-switch="live">` asked for one; the
|
|
36
|
+
// decision behind it and the controller that runs the transaction are a dynamic target,
|
|
37
|
+
// fetched by the first switch and never by the declaration itself.
|
|
38
|
+
let liveSwitch = null;
|
|
39
|
+
|
|
40
|
+
export const noteLiveSwitch = (boot) => {
|
|
41
|
+
liveSwitch = boot;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export async function activateInitialMode() {
|
|
45
|
+
if (!isEditMode) return;
|
|
46
|
+
try {
|
|
47
|
+
await (await fetchController()).activateInitial();
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error("clayjs: the mode lifecycle failed to start:", err);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// One mode request at a time, whoever ends up handling it (the workspace shell, the live
|
|
54
|
+
// controller or a reload): asking for the same mode joins it, and asking for the other one
|
|
55
|
+
// is busy until it settles.
|
|
56
|
+
let pending = null;
|
|
57
|
+
|
|
58
|
+
export async function setMode(next) {
|
|
59
|
+
if (next !== "edit" && next !== "view") throw new TypeError(`clay.setMode: unknown mode "${next}"`);
|
|
60
|
+
if (pending) return pending.target === next ? pending.promise : { ok: false, mode, code: "busy" };
|
|
61
|
+
if (next === mode && !controller) return { ok: true, mode, changed: false };
|
|
62
|
+
if (isDraftMode) return { ok: false, mode, code: "not-allowed" };
|
|
63
|
+
const promise = request(next);
|
|
64
|
+
pending = { target: next, promise };
|
|
65
|
+
try {
|
|
66
|
+
return await promise;
|
|
67
|
+
} finally {
|
|
68
|
+
if (pending?.promise === promise) pending = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function request(next) {
|
|
73
|
+
// In a workspace frame the shell owns the frame and decides permission, so a mode change
|
|
74
|
+
// is its reload, not ours: hand it over before anything local starts. Only a shell that
|
|
75
|
+
// does not take part ("host-unsupported") leaves the switch to this document.
|
|
76
|
+
const host = typeof window !== "undefined" ? window.clay?._requestHostMode : undefined;
|
|
77
|
+
if (typeof host === "function" && next !== mode) {
|
|
78
|
+
let answer;
|
|
79
|
+
try {
|
|
80
|
+
answer = await host(next);
|
|
81
|
+
} catch {
|
|
82
|
+
return { ok: false, mode, code: "host-failed" };
|
|
83
|
+
}
|
|
84
|
+
if (answer?.reason !== "host-unsupported") {
|
|
85
|
+
if (!answer?.ok) return { ok: false, mode, code: answer?.reason || "host-refused" };
|
|
86
|
+
leaving = true;
|
|
87
|
+
return { ok: true, mode: next, changed: true, reloading: true };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (next === "edit" && mode !== "edit") return enterEdit();
|
|
91
|
+
if (!canEdit && !(next === "view" && mode === "edit")) return { ok: false, mode, code: "not-allowed" };
|
|
92
|
+
return switchTo(next);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function enterEdit() {
|
|
96
|
+
const asked = location.pathname;
|
|
97
|
+
const { checkHostPermission } = await import("./host-permission.js");
|
|
98
|
+
const writable = await checkHostPermission({ fresh: true });
|
|
99
|
+
// The page moved to another document while the host answered: this request was for the old one.
|
|
100
|
+
if (location.pathname !== asked) return { ok: false, mode, code: "cancelled" };
|
|
101
|
+
if (writable === false) return { ok: false, mode, code: "permission-denied" };
|
|
102
|
+
if (!canEdit) return { ok: false, mode, code: "not-allowed" };
|
|
103
|
+
return switchTo("edit");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function switchTo(next) {
|
|
107
|
+
if (liveSwitch) {
|
|
108
|
+
// A fetch that fails keeps the old way in: the guarded reload below.
|
|
109
|
+
const loaded = await fetchController();
|
|
110
|
+
const live = loaded && (await loaded.switchMode(next, liveSwitch));
|
|
111
|
+
if (live) return live;
|
|
112
|
+
}
|
|
113
|
+
if (next === mode) return { ok: true, mode, changed: false };
|
|
114
|
+
if (mode === "edit") {
|
|
115
|
+
let result;
|
|
116
|
+
try {
|
|
117
|
+
result = await window.clay?.save?.flush?.();
|
|
118
|
+
} catch (err) {
|
|
119
|
+
return { ok: false, mode, code: "save-failed", state: err?.state };
|
|
120
|
+
}
|
|
121
|
+
if (result && result.state && result.state !== "saved" && result.state !== "clean") {
|
|
122
|
+
return { ok: false, mode, code: "save-failed", state: result.state };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
leaving = true;
|
|
126
|
+
writeReceipt({ mode: next, canEdit, internal: true });
|
|
127
|
+
location.reload();
|
|
128
|
+
return { ok: true, mode: next, changed: true, reloading: true };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function openEditParam() {
|
|
9
132
|
const url = new URL(window.location.href);
|
|
10
|
-
|
|
11
|
-
url.searchParams.set('editmode', newMode);
|
|
133
|
+
url.searchParams.set('editmode', 'true');
|
|
12
134
|
window.location.href = url.toString();
|
|
13
135
|
}
|
|
14
136
|
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
137
|
+
export const toggleEditMode = () =>
|
|
138
|
+
isDraftMode ? openEditParam() : setMode(mode === "edit" ? "view" : "edit");
|
|
139
|
+
|
|
140
|
+
function keepModeOnReload() {
|
|
141
|
+
if (!canEdit || isDraftMode) return;
|
|
142
|
+
window.addEventListener("pagehide", () => { if (!leaving) writeReceipt({ mode, canEdit, internal: false }); });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function markCanEdit() {
|
|
146
|
+
if (canEdit) {
|
|
147
|
+
document.documentElement.setAttribute("canedit", "true");
|
|
148
|
+
} else {
|
|
149
|
+
document.documentElement.removeAttribute("canedit");
|
|
150
|
+
}
|
|
20
151
|
}
|
|
21
152
|
|
|
22
153
|
export function setPageTypeOnPageLoad () {
|
|
23
154
|
onDomReady(() => {
|
|
24
155
|
document.documentElement.setAttribute("editmode", isEditMode ? "true" : "false");
|
|
25
156
|
document.documentElement.setAttribute("pageowner", isOwner ? "true" : "false");
|
|
157
|
+
markCanEdit();
|
|
158
|
+
document.addEventListener("clay:capabilitychange", markCanEdit);
|
|
26
159
|
if (isDraftMode) {
|
|
27
160
|
document.documentElement.setAttribute("draftmode", "true");
|
|
28
161
|
} else {
|
|
@@ -32,12 +165,12 @@ export function setPageTypeOnPageLoad () {
|
|
|
32
165
|
}
|
|
33
166
|
|
|
34
167
|
function init() {
|
|
35
|
-
setViewerPageTypeBeforeSave();
|
|
36
168
|
setPageTypeOnPageLoad();
|
|
169
|
+
keepModeOnReload();
|
|
37
170
|
}
|
|
38
171
|
|
|
39
172
|
// Auto-init when module is imported
|
|
40
173
|
init();
|
|
41
174
|
|
|
42
|
-
export { init, isEditMode, isOwner };
|
|
43
|
-
export default { init, toggleEditMode, isEditMode, isOwner };
|
|
175
|
+
export { init, isEditMode, isOwner, canEdit, mode };
|
|
176
|
+
export default { init, setMode, toggleEditMode, isEditMode, isOwner, canEdit, mode };
|
package/src/core/etag.js
CHANGED
|
@@ -103,6 +103,25 @@ export function forgetEtag() {
|
|
|
103
103
|
recordEtag(null);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
// Only a real answer (spec is a number) says what the host supports: a refresh
|
|
107
|
+
// that failed comes back as a bare host with no extensions, and must not switch
|
|
108
|
+
// conditional saves off for the rest of the page's life.
|
|
109
|
+
function adoptHostExtensions(meta) {
|
|
110
|
+
if (typeof meta.spec === "number") conditional = meta.extensions.includes("conditional");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Learn from discovery whether this host offers conditional saves. The stamp is
|
|
115
|
+
* left alone: a session that only wants the capability must not move the version
|
|
116
|
+
* it is about to save against.
|
|
117
|
+
*
|
|
118
|
+
* @returns {Promise<boolean>}
|
|
119
|
+
*/
|
|
120
|
+
export async function learnConditionalSaves() {
|
|
121
|
+
adoptHostExtensions(await hostMeta());
|
|
122
|
+
return conditional;
|
|
123
|
+
}
|
|
124
|
+
|
|
106
125
|
/**
|
|
107
126
|
* Learn from discovery whether this host offers conditional saves, and what the
|
|
108
127
|
* document's current stamp is.
|
|
@@ -124,10 +143,7 @@ export function forgetEtag() {
|
|
|
124
143
|
export async function seedEtag({ fresh = false, clearIfMissing = fresh } = {}) {
|
|
125
144
|
const at = generation;
|
|
126
145
|
const meta = await hostMeta({ fresh });
|
|
127
|
-
|
|
128
|
-
// that failed comes back as a bare host with no extensions, and must not switch
|
|
129
|
-
// conditional saves off for the rest of the page's life.
|
|
130
|
-
if (typeof meta.spec === "number") conditional = meta.extensions.includes("conditional");
|
|
146
|
+
adoptHostExtensions(meta);
|
|
131
147
|
|
|
132
148
|
if (generation !== at) return lastSeen;
|
|
133
149
|
|
|
@@ -184,11 +200,13 @@ document.addEventListener("clay:sync-applied", (event) => {
|
|
|
184
200
|
// Edit mode goes further: the stamp it SAVES with has to move too, and the host is the
|
|
185
201
|
// only source for the value, so it forgets the one it holds and asks again with the
|
|
186
202
|
// overwrite flags. A view-mode tab never saves, so its save stamp can stay where it is.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
203
|
+
// The mode is read when the frame arrives rather than when the listener is installed: a
|
|
204
|
+
// live entry imports this module in view, and this listener has to move the stamp once
|
|
205
|
+
// the edit session it belongs to has committed.
|
|
206
|
+
document.addEventListener("clay:sync-applied", (event) => {
|
|
207
|
+
if (!isEditMode) return;
|
|
208
|
+
if (event.detail?.source !== "disk") return;
|
|
209
|
+
if (typeof event.detail.etag === "string" && event.detail.etag) return;
|
|
210
|
+
forgetEtag();
|
|
211
|
+
seedEtag({ fresh: true });
|
|
212
|
+
});
|
package/src/core/host-meta.js
CHANGED
|
@@ -27,8 +27,20 @@ const META_TIMEOUT_MS = 6000;
|
|
|
27
27
|
// a host that failed to say who you are has not said you are nobody.
|
|
28
28
|
const outcomes = new WeakMap();
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
// The path each answer was fetched for, captured when that fetch started. A document block
|
|
31
|
+
// describes one document, and a reply that lands after a route change answers for the one
|
|
32
|
+
// that asked.
|
|
33
|
+
const paths = new WeakMap();
|
|
34
|
+
|
|
35
|
+
// Which fetch produced each answer, counted from 1 in the order the fetches started. An
|
|
36
|
+
// answer that lands late is still older than one that started later.
|
|
37
|
+
const orders = new WeakMap();
|
|
38
|
+
let fetches = 0;
|
|
39
|
+
|
|
40
|
+
function settle(outcome, meta = bareHost(), asked = null, order = null) {
|
|
31
41
|
outcomes.set(meta, outcome);
|
|
42
|
+
if (asked !== null) paths.set(meta, asked);
|
|
43
|
+
if (order !== null) orders.set(meta, order);
|
|
32
44
|
return meta;
|
|
33
45
|
}
|
|
34
46
|
|
|
@@ -42,6 +54,26 @@ export function hostMetaOutcome(meta) {
|
|
|
42
54
|
return outcomes.get(meta) ?? "none";
|
|
43
55
|
}
|
|
44
56
|
|
|
57
|
+
/**
|
|
58
|
+
* The path the fetch behind `meta` started on, or undefined when there is none.
|
|
59
|
+
*
|
|
60
|
+
* @param {Object} meta - an answer from hostMeta()
|
|
61
|
+
* @returns {string|undefined}
|
|
62
|
+
*/
|
|
63
|
+
export function hostMetaPath(meta) {
|
|
64
|
+
return paths.get(meta);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Which fetch produced `meta`, counting from 1 in the order the fetches started, or 0 when none did.
|
|
69
|
+
*
|
|
70
|
+
* @param {Object} meta - an answer from hostMeta()
|
|
71
|
+
* @returns {number}
|
|
72
|
+
*/
|
|
73
|
+
export function hostMetaOrder(meta) {
|
|
74
|
+
return orders.get(meta) ?? 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
45
77
|
// The bare-core-host answer, which is also every failure's answer.
|
|
46
78
|
function bareHost() {
|
|
47
79
|
return { spec: null, extensions: [], document: null };
|
|
@@ -101,6 +133,36 @@ export async function hostSupports(name) {
|
|
|
101
133
|
return meta.extensions.includes(name);
|
|
102
134
|
}
|
|
103
135
|
|
|
136
|
+
/**
|
|
137
|
+
* The page format this host enforces: 1 when it announced `page-format-v1`,
|
|
138
|
+
* otherwise 0.
|
|
139
|
+
*
|
|
140
|
+
* A host announces that extension only once its floor enforcement and tolerant
|
|
141
|
+
* checkpoint renderer are installed, so a host that cannot hold the floor keeps
|
|
142
|
+
* the newer writers off. The answer comes from the memoized discovery: one
|
|
143
|
+
* answer serves every asker under a loaded document.
|
|
144
|
+
*
|
|
145
|
+
* @returns {Promise<number>}
|
|
146
|
+
*/
|
|
147
|
+
export async function hostPageFormat() {
|
|
148
|
+
const meta = await hostMeta();
|
|
149
|
+
return meta.extensions.includes("page-format-v1") ? 1 : 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Whether this host keeps a version history a person can go back to.
|
|
154
|
+
*
|
|
155
|
+
* The `document` block names it: absent means no history, which is the answer every
|
|
156
|
+
* host gave before the field existed. Read through discovery like every other
|
|
157
|
+
* capability, never inferred from the hostname or a probed route.
|
|
158
|
+
*
|
|
159
|
+
* @returns {Promise<boolean>}
|
|
160
|
+
*/
|
|
161
|
+
export async function hostVersions() {
|
|
162
|
+
const meta = await hostMeta();
|
|
163
|
+
return meta.document?.versions === true;
|
|
164
|
+
}
|
|
165
|
+
|
|
104
166
|
/** Drop the memoized answer. Tests only. */
|
|
105
167
|
export function resetHostMeta() {
|
|
106
168
|
inFlight = null;
|
|
@@ -183,8 +245,10 @@ export async function querySaveOutcome({ saveId, requestHash, signal } = {}) {
|
|
|
183
245
|
}
|
|
184
246
|
|
|
185
247
|
async function fetchMeta() {
|
|
248
|
+
const asked = location.pathname;
|
|
249
|
+
const order = ++fetches;
|
|
186
250
|
// A file opened from disk has no host to ask, and fetch would only throw.
|
|
187
|
-
if (!/^https?:$/.test(window.location.protocol)) return settle("none");
|
|
251
|
+
if (!/^https?:$/.test(window.location.protocol)) return settle("none", bareHost(), asked, order);
|
|
188
252
|
|
|
189
253
|
const token = saveToken();
|
|
190
254
|
// A host that mints tokens answers discovery per token, because on a sandboxed
|
|
@@ -211,25 +275,25 @@ async function fetchMeta() {
|
|
|
211
275
|
redirect: "manual",
|
|
212
276
|
signal: controller.signal
|
|
213
277
|
});
|
|
214
|
-
if (!res.ok) return settle(res.status >= 500 ? "failed" : "none");
|
|
278
|
+
if (!res.ok) return settle(res.status >= 500 ? "failed" : "none", bareHost(), asked, order);
|
|
215
279
|
const text = await res.text();
|
|
216
|
-
if (!text) return settle("none");
|
|
280
|
+
if (!text) return settle("none", bareHost(), asked, order);
|
|
217
281
|
|
|
218
282
|
let body;
|
|
219
283
|
try {
|
|
220
284
|
body = JSON.parse(text);
|
|
221
285
|
} catch (_) {
|
|
222
|
-
return settle("none");
|
|
286
|
+
return settle("none", bareHost(), asked, order);
|
|
223
287
|
}
|
|
224
|
-
if (!body || typeof body !== "object" || typeof body.spec !== "number") return settle("none");
|
|
288
|
+
if (!body || typeof body !== "object" || typeof body.spec !== "number") return settle("none", bareHost(), asked, order);
|
|
225
289
|
|
|
226
290
|
return settle("ok", {
|
|
227
291
|
spec: body.spec,
|
|
228
292
|
extensions: Array.isArray(body.extensions) ? body.extensions : [],
|
|
229
293
|
document: body.document && typeof body.document === "object" ? body.document : null
|
|
230
|
-
});
|
|
294
|
+
}, asked, order);
|
|
231
295
|
} catch (_) {
|
|
232
|
-
return settle("failed");
|
|
296
|
+
return settle("failed", bareHost(), asked, order);
|
|
233
297
|
} finally {
|
|
234
298
|
clearTimeout(timeoutId);
|
|
235
299
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { hostMeta, hostMetaOrder, hostMetaOutcome, hostMetaPath } from "./host-meta.js";
|
|
2
|
+
import { noteHostWritable } from "./is-edit-mode.js";
|
|
3
|
+
|
|
4
|
+
let accepted = null;
|
|
5
|
+
let committed = 0;
|
|
6
|
+
|
|
7
|
+
// A permission answer counts only for the document it was fetched for and only when discovery
|
|
8
|
+
// answered: a failed refresh, or an answer with no document block, is not evidence either way,
|
|
9
|
+
// and leaves the last accepted answer standing. A host that offers no discovery at all ("none")
|
|
10
|
+
// is no answer, so the hints apply again. An older answer never overwrites a newer one.
|
|
11
|
+
export async function checkHostPermission({ fresh = false } = {}) {
|
|
12
|
+
const asked = location.pathname;
|
|
13
|
+
let meta = await hostMeta({ fresh });
|
|
14
|
+
if (location.pathname !== asked) return accepted;
|
|
15
|
+
if (hostMetaPath(meta) !== asked) {
|
|
16
|
+
meta = await hostMeta({ fresh: true });
|
|
17
|
+
if (location.pathname !== asked || hostMetaPath(meta) !== asked) return accepted;
|
|
18
|
+
}
|
|
19
|
+
const outcome = hostMetaOutcome(meta);
|
|
20
|
+
if (outcome === "failed" || (outcome === "ok" && !meta.document)) return accepted;
|
|
21
|
+
const writable = outcome === "ok" ? meta.document.writable : undefined;
|
|
22
|
+
// An answer from a fetch older than the one already committed is out of date.
|
|
23
|
+
const order = hostMetaOrder(meta);
|
|
24
|
+
if (order < committed) return accepted;
|
|
25
|
+
committed = order;
|
|
26
|
+
accepted = typeof writable === "boolean" ? writable : null;
|
|
27
|
+
noteHostWritable(accepted);
|
|
28
|
+
return accepted;
|
|
29
|
+
}
|
package/src/core/is-edit-mode.js
CHANGED
|
@@ -2,10 +2,14 @@ import cookie from "../lib/cookie.js";
|
|
|
2
2
|
import query from "../lib/query.js";
|
|
3
3
|
import { hasSaveToken, servedStaleToken, servedSiteVisibility } from "./host-attrs.js";
|
|
4
4
|
import { draftEligible } from "./draft-mode.js";
|
|
5
|
+
import { takeReceipt } from "./mode-receipt.js";
|
|
5
6
|
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
7
|
+
// Two answers come out of here, both decided once, at module evaluation: may this
|
|
8
|
+
// person edit the document (`canEdit`), and does this tab start in edit (`mode`).
|
|
9
|
+
//
|
|
10
|
+
// canEdit is the edit-mode ladder, read as permission. An explicit ?editmode=true|false
|
|
11
|
+
// URL param wins, then an authored `viewonly` attribute on <html> (a tool page that must
|
|
12
|
+
// never save its runtime state, even on a host that hands it a save token), then an
|
|
9
13
|
// opt-in window.clayEditMode global (with the legacy window.__hyperclayEditMode
|
|
10
14
|
// still honored as a fallback for older standalone embedders — htmlclay itself
|
|
11
15
|
// uses its injected save token plus the admin cookie, not this global), then
|
|
@@ -16,6 +20,13 @@ import { draftEligible } from "./draft-mode.js";
|
|
|
16
20
|
// comes before ?editmode=true unless the owner cookie is present, and before the
|
|
17
21
|
// owner cookie otherwise.
|
|
18
22
|
//
|
|
23
|
+
// mode is the narrower question: permission alone no longer opens a document for
|
|
24
|
+
// editing, so an owner reads every document the way a visitor does. A tab starts
|
|
25
|
+
// editing only when something explicit asks for it: a receipt this tab left for its
|
|
26
|
+
// own reload (the person's choice in this tab, newer than the URL it was served with),
|
|
27
|
+
// an ?editmode=true param, window.clayEditMode = true, or an authored
|
|
28
|
+
// <html opens-in="edit"> for a tool page. clay.setMode('edit') is the way in.
|
|
29
|
+
//
|
|
19
30
|
// The token sits above the cookie because it is the stronger claim and often the
|
|
20
31
|
// only one visible: the host minted it for this response and nothing else, while
|
|
21
32
|
// the cookie is ambient. A sandboxed document cannot read the cookie at all, so
|
|
@@ -52,23 +63,145 @@ const isDraftMode =
|
|
|
52
63
|
forcedEditMode == null &&
|
|
53
64
|
!hasSaveToken();
|
|
54
65
|
|
|
55
|
-
const
|
|
56
|
-
|
|
66
|
+
const receipt = typeof window !== "undefined" ? takeReceipt() : null;
|
|
67
|
+
const opensInEdit =
|
|
68
|
+
typeof document !== "undefined" && document.documentElement.getAttribute("opens-in") === "edit";
|
|
69
|
+
|
|
70
|
+
// May this person edit here? The old rule, read as permission.
|
|
71
|
+
const hostCanEdit = query.editmode
|
|
72
|
+
? query.editmode === "true" || (!isViewOnlyDocument && !servedStaleToken() && (forcedEditMode ?? (hasSaveToken() || ownerCookie)))
|
|
73
|
+
: isViewOnlyDocument || servedStaleToken()
|
|
74
|
+
? false
|
|
75
|
+
: forcedEditMode != null
|
|
76
|
+
? forcedEditMode
|
|
77
|
+
: hasSaveToken() || ownerCookie;
|
|
78
|
+
const keptCanEdit =
|
|
79
|
+
receipt?.canEdit === true && forcedEditMode !== false && !servedStaleToken() &&
|
|
80
|
+
!(isViewOnlyDocument && query.editmode !== "true");
|
|
81
|
+
const hintCanEdit = hostCanEdit || keptCanEdit;
|
|
82
|
+
// What the document or the URL itself rules out; a host's yes never overrides these.
|
|
83
|
+
const authoredRestriction = isDraftMode || servedStaleToken() || forcedEditMode === false ||
|
|
84
|
+
query.editmode === "false" || (isViewOnlyDocument && query.editmode !== "true");
|
|
85
|
+
let canEdit = hintCanEdit;
|
|
86
|
+
|
|
87
|
+
// The host's own answer to "may this caller save here": false takes edit away, true grants
|
|
88
|
+
// it unless the document rules it out, and no answer leaves the hints in charge.
|
|
89
|
+
export function noteHostWritable(writable) {
|
|
90
|
+
const next = writable === false ? false : writable === true ? hintCanEdit || !authoredRestriction : hintCanEdit;
|
|
91
|
+
if (next === canEdit) return;
|
|
92
|
+
canEdit = next;
|
|
93
|
+
if (typeof document !== "undefined") document.dispatchEvent(new Event("clay:capabilitychange"));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Does this tab start editing? Only when something explicit asks for it.
|
|
97
|
+
const startsEditing = receipt
|
|
98
|
+
? receipt.mode === "edit"
|
|
57
99
|
: query.editmode
|
|
58
|
-
? query.editmode === "true"
|
|
59
|
-
:
|
|
60
|
-
? false
|
|
61
|
-
: forcedEditMode != null
|
|
62
|
-
? forcedEditMode
|
|
63
|
-
: hasSaveToken() || ownerCookie;
|
|
100
|
+
? query.editmode === "true"
|
|
101
|
+
: forcedEditMode === true || opensInEdit;
|
|
64
102
|
|
|
103
|
+
// Live bindings, not constants: the lifecycle controller commits a transition by
|
|
104
|
+
// rewriting them here, and every facade reads them through a getter, so a boolean
|
|
105
|
+
// copied onto `clay` at boot can never go stale.
|
|
106
|
+
let isEditMode = isDraftMode ? true : canEdit && startsEditing;
|
|
107
|
+
let mode = isDraftMode ? "draft" : isEditMode ? "edit" : "view";
|
|
65
108
|
const isOwner = ownerCookie;
|
|
66
109
|
|
|
110
|
+
// The rest of `clay.modeLifecycle.state`: where edits land, which transition is
|
|
111
|
+
// running, and why the last one refused. The controller is the only writer.
|
|
112
|
+
const lifecycle = {
|
|
113
|
+
phase: "idle",
|
|
114
|
+
destination: isDraftMode ? "draft" : "host",
|
|
115
|
+
generation: 0,
|
|
116
|
+
reason: null,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export function commitModeFacts(next) {
|
|
120
|
+
if (next.mode !== undefined) mode = next.mode;
|
|
121
|
+
if (next.isEditMode !== undefined) {
|
|
122
|
+
isEditMode = next.isEditMode;
|
|
123
|
+
if (typeof window !== "undefined") window.__hyperclayEditMode = isEditMode;
|
|
124
|
+
}
|
|
125
|
+
if (next.canEdit !== undefined) canEdit = next.canEdit;
|
|
126
|
+
for (const key in lifecycle) if (next[key] !== undefined) lifecycle[key] = next[key];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const modeState = () => ({ mode, ...lifecycle });
|
|
130
|
+
|
|
131
|
+
// A module that starts a session when it evaluates belongs to the session that was idle when
|
|
132
|
+
// it landed: during any transition the committed mode is still the one being left, so a late
|
|
133
|
+
// import would start an editor no exit holds a lease over.
|
|
134
|
+
export function startsAtImport() {
|
|
135
|
+
return isEditMode && modeState().phase === "idle";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// The one registration queue, on `window.clay`: the API, a preboot push and a later raw
|
|
139
|
+
// push all land in the array the controller drains. Non-enumerable, because it is a
|
|
140
|
+
// handoff and not a member of the facade a page boots with.
|
|
141
|
+
const queue = (() => {
|
|
142
|
+
if (typeof window === "undefined" || !window.clay) return [];
|
|
143
|
+
if (window.clay.modeParticipants) return window.clay.modeParticipants;
|
|
144
|
+
Object.defineProperty(window.clay, "modeParticipants", { value: [], writable: true, configurable: true });
|
|
145
|
+
return window.clay.modeParticipants;
|
|
146
|
+
})();
|
|
147
|
+
const registered = new Set();
|
|
148
|
+
|
|
149
|
+
// The readiness check reads the queue without draining it. A registration taken back also
|
|
150
|
+
// leaves the controller's roster, but only when the controller is already loaded.
|
|
151
|
+
let forgetFromRoster = () => {};
|
|
152
|
+
export const noteParticipantForget = (fn) => (forgetFromRoster = fn);
|
|
153
|
+
export const peekModeParticipants = () => queue.slice();
|
|
154
|
+
|
|
155
|
+
const refuse = (message) => {
|
|
156
|
+
throw new TypeError(`clay.modeLifecycle: ${message}`);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const refuseUnlessIdleInView = () => {
|
|
160
|
+
if (lifecycle.phase !== "idle" || isEditMode) refuse("the page is not idle in view");
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// A blocked page is a view with a reason: the transition that refused is over and the page
|
|
164
|
+
// is not idle only so the refusal can be read. A participant that arrives then is recorded
|
|
165
|
+
// for the next entry, which is the answer the block already gave, so it is not a second
|
|
166
|
+
// refusal on top of it.
|
|
167
|
+
const refuseUnlessAnEntryCanTakeIt = () => {
|
|
168
|
+
if (lifecycle.phase === "blocked" && !isEditMode) return;
|
|
169
|
+
refuseUnlessIdleInView();
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export function takeModeParticipants() {
|
|
173
|
+
registered.clear();
|
|
174
|
+
return queue.splice(0);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function registerModeParticipant(participant) {
|
|
178
|
+
const id = participant && participant.id;
|
|
179
|
+
if (typeof id !== "string" || !id || typeof participant.prepare !== "function") refuse("a participant needs an id and a prepare()");
|
|
180
|
+
if (registered.has(id)) refuse(`duplicate participant id "${id}"`);
|
|
181
|
+
refuseUnlessAnEntryCanTakeIt();
|
|
182
|
+
registered.add(id);
|
|
183
|
+
queue.push(participant);
|
|
184
|
+
// `live`, not `registered`: a drain clears that set while the roster keeps the
|
|
185
|
+
// participant, so an unregister after the first drain still has to reach the roster.
|
|
186
|
+
let live = true;
|
|
187
|
+
return () => {
|
|
188
|
+
if (!live) return;
|
|
189
|
+
refuseUnlessAnEntryCanTakeIt();
|
|
190
|
+
live = false;
|
|
191
|
+
registered.delete(id);
|
|
192
|
+
const index = queue.indexOf(participant);
|
|
193
|
+
if (index !== -1) queue.splice(index, 1);
|
|
194
|
+
forgetFromRoster(id);
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
67
198
|
const siteVisibility = servedSiteVisibility;
|
|
68
199
|
|
|
69
200
|
export {
|
|
70
201
|
isDraftMode,
|
|
71
202
|
isEditMode,
|
|
72
203
|
isOwner,
|
|
73
|
-
siteVisibility
|
|
204
|
+
siteVisibility,
|
|
205
|
+
canEdit,
|
|
206
|
+
mode
|
|
74
207
|
}
|