@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/plugins/ai-edit.js
CHANGED
|
@@ -41,10 +41,9 @@
|
|
|
41
41
|
* invisible to the mutation system (its status text changes constantly), absent from
|
|
42
42
|
* every snapshot and from what peers receive.
|
|
43
43
|
*/
|
|
44
|
-
import {
|
|
45
|
-
import { mergeTagRecognizers } from "../sync/merge-tags.js";
|
|
44
|
+
import { loadedEngine, mergeEngine } from "../sync/merge-engine.js";
|
|
46
45
|
import Mutation from "../lib/mutation.js";
|
|
47
|
-
import {
|
|
46
|
+
import { startsAtImport } from "../core/is-edit-mode.js";
|
|
48
47
|
import { holdAllSaves, releaseAllSaves } from "../core/save.js";
|
|
49
48
|
import { STRIP_FROM_SAVE, SNAPSHOT_REMOVE_SELECTOR } from "../lib/region-policy.js";
|
|
50
49
|
import { enableContentEditable } from "../core/admin-contenteditable.js";
|
|
@@ -81,7 +80,10 @@ let barMode = null; // "working", "ready", "saving" or "error"
|
|
|
81
80
|
let barLifecycle = null;
|
|
82
81
|
let barCloseTimer = null;
|
|
83
82
|
let panelPointerDown = false; // the last pointerdown began in the panel: its click is not a page click
|
|
84
|
-
|
|
83
|
+
let drafts = new WeakMap(); // compose text a target keeps while the panel is shut
|
|
84
|
+
// A WeakMap cannot be read back, and an exit has to know whether anything typed here is
|
|
85
|
+
// still unsent, so the targets that hold a draft are counted beside it.
|
|
86
|
+
const drafted = new Set();
|
|
85
87
|
let anchorEl = null; // element the panel is currently anchored to
|
|
86
88
|
let chipTarget = null; // unit the hover chip currently points at
|
|
87
89
|
let chipHideTimer = null;
|
|
@@ -92,6 +94,24 @@ let selectionTimer = null;
|
|
|
92
94
|
let anchorRange = null; // the selection the open panel was raised for
|
|
93
95
|
let returnFocus = null; // where focus goes back to when the panel closes
|
|
94
96
|
let helperState = null; // "ready" or "unavailable"
|
|
97
|
+
let listeners = null; // the AbortController every handler one session added listens on
|
|
98
|
+
let observers = []; // the page observers that session opened
|
|
99
|
+
let styleEl = null; // the one stylesheet the chrome needed
|
|
100
|
+
let chromeToken = 0; // the latest init(); a teardown retires the one before it
|
|
101
|
+
|
|
102
|
+
function dropDraft(el) {
|
|
103
|
+
drafts.delete(el);
|
|
104
|
+
drafted.delete(el);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function keepDraft(el, text) {
|
|
108
|
+
if (!text) {
|
|
109
|
+
dropDraft(el);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
drafts.set(el, text);
|
|
113
|
+
drafted.add(el);
|
|
114
|
+
}
|
|
95
115
|
|
|
96
116
|
const HIGHLIGHT = 'clay-ai-edit';
|
|
97
117
|
const OFF_MESSAGE = 'AI editing is turned off. Turn it on from the app’s menu.';
|
|
@@ -190,6 +210,7 @@ function strippedBodyHTML() {
|
|
|
190
210
|
// ---------------------------------------------------------------- morphing
|
|
191
211
|
|
|
192
212
|
function morphTo(el, content, scripts) {
|
|
213
|
+
const { HyperMorph } = loadedEngine();
|
|
193
214
|
return HyperMorph.morph(el, content, { morphStyle: 'outerHTML', restoreFocus: true, scripts });
|
|
194
215
|
}
|
|
195
216
|
|
|
@@ -201,6 +222,7 @@ function morphTo(el, content, scripts) {
|
|
|
201
222
|
// against the snapshot the model saw, so data the user changed mid-flight survives
|
|
202
223
|
// the reply. Rewinds must restore the snapshot exactly, so they disable merging.
|
|
203
224
|
function sessionMorph(s, content, { rewind = false } = {}) {
|
|
225
|
+
const { HyperMorph, mergeTagRecognizers } = loadedEngine();
|
|
204
226
|
// handle:false: a reply's scripts never run, so the preview cannot execute code
|
|
205
227
|
// before Keep, and neither can a rewind.
|
|
206
228
|
const scripts = rewind
|
|
@@ -300,7 +322,7 @@ async function sendRequest(el, comment, quote) {
|
|
|
300
322
|
}
|
|
301
323
|
// The sent comment is this target's draft until the reply is ready: Error, Stop, a
|
|
302
324
|
// refused reply, a morph failure and a host cancellation all leave it in place.
|
|
303
|
-
|
|
325
|
+
keepDraft(el, comment);
|
|
304
326
|
closePanelForSend();
|
|
305
327
|
session.paused = true;
|
|
306
328
|
pauseObservers();
|
|
@@ -321,6 +343,9 @@ async function sendRequest(el, comment, quote) {
|
|
|
321
343
|
session.handle = handle;
|
|
322
344
|
const outcome = await handle.done;
|
|
323
345
|
if (!session || session.handle !== handle) return;
|
|
346
|
+
// The engine is fetched here, where a reply is applied, not at boot.
|
|
347
|
+
await mergeEngine();
|
|
348
|
+
if (!session || session.handle !== handle) return;
|
|
324
349
|
if (outcome.state === 'done') onDone(outcome.result || {});
|
|
325
350
|
else if (outcome.state === 'cancelled') onError('HTML Clay stopped this edit.');
|
|
326
351
|
else onError(outcome.error || 'the helper reported an error');
|
|
@@ -417,7 +442,7 @@ function onDone(payload) {
|
|
|
417
442
|
return;
|
|
418
443
|
}
|
|
419
444
|
positionChrome();
|
|
420
|
-
|
|
445
|
+
dropDraft(session.el); // delivered: the comment is no longer this target's draft
|
|
421
446
|
showBar('ready');
|
|
422
447
|
setBarStatus(readyText(warnings, payload.model), warnings.length ? 'warn' : '');
|
|
423
448
|
}
|
|
@@ -444,7 +469,7 @@ function revertSession() {
|
|
|
444
469
|
session = null;
|
|
445
470
|
if (!s) return;
|
|
446
471
|
try {
|
|
447
|
-
if (s.state === 'requesting' || s.state === 'deciding') {
|
|
472
|
+
if ((s.state === 'requesting' || s.state === 'deciding') && loadedEngine()) {
|
|
448
473
|
sessionMorph(s, s.snapshot, { rewind: true });
|
|
449
474
|
}
|
|
450
475
|
} finally {
|
|
@@ -507,15 +532,16 @@ function marked(el, name, value) {
|
|
|
507
532
|
}
|
|
508
533
|
|
|
509
534
|
function buildChrome() {
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
535
|
+
const { signal } = listeners;
|
|
536
|
+
styleEl = document.createElement('style');
|
|
537
|
+
styleEl.setAttribute('clay', RUNTIME_ONLY);
|
|
538
|
+
styleEl.textContent = `
|
|
513
539
|
[editmode\\:contenteditable][contenteditable]:focus {
|
|
514
540
|
outline: 1px solid #4a4a6a; outline-offset: 4px; border-radius: 2px;
|
|
515
541
|
}
|
|
516
542
|
::highlight(clay-ai-edit) { background-color: ${TOKENS['brass-soft']} !important; }
|
|
517
543
|
`;
|
|
518
|
-
document.head.appendChild(
|
|
544
|
+
document.head.appendChild(styleEl);
|
|
519
545
|
|
|
520
546
|
const scheme = `color-scheme:${pageScheme()}`;
|
|
521
547
|
|
|
@@ -641,24 +667,28 @@ function buildChrome() {
|
|
|
641
667
|
openPanel(document.body, quoteFromSelection(document.body));
|
|
642
668
|
});
|
|
643
669
|
|
|
644
|
-
window.addEventListener('scroll', () => { positionChrome(); hideChip(); }, { passive: true });
|
|
645
|
-
window.addEventListener('resize', () => { placeBubble(); if (panel && !panel.hidden) fitTextarea(); positionChrome(); }, { passive: true });
|
|
670
|
+
window.addEventListener('scroll', () => { positionChrome(); hideChip(); }, { passive: true, signal });
|
|
671
|
+
window.addEventListener('resize', () => { placeBubble(); if (panel && !panel.hidden) fitTextarea(); positionChrome(); }, { passive: true, signal });
|
|
646
672
|
// The CMS toggle can arrive after this chrome, and a docked sidebar slides it left.
|
|
647
673
|
const replace = () => { placeBubble(); positionChrome(); };
|
|
648
|
-
document.addEventListener('hcms:open', () => { replace(); setTimeout(replace, 350); });
|
|
649
|
-
document.addEventListener('hcms:close', () => { replace(); setTimeout(replace, 350); });
|
|
650
|
-
new MutationObserver((records) => {
|
|
674
|
+
document.addEventListener('hcms:open', () => { replace(); setTimeout(replace, 350); }, { signal });
|
|
675
|
+
document.addEventListener('hcms:close', () => { replace(); setTimeout(replace, 350); }, { signal });
|
|
676
|
+
const cmsToggle = new MutationObserver((records) => {
|
|
651
677
|
syncBubble();
|
|
652
678
|
const touched = (nodes) => [...nodes].some((n) => n.nodeType === 1 && n.hasAttribute('data-hcms-toggle-host'));
|
|
653
679
|
if (records.some((r) => touched(r.addedNodes) || touched(r.removedNodes))) replace();
|
|
654
|
-
})
|
|
680
|
+
});
|
|
681
|
+
cmsToggle.observe(document.body, { childList: true });
|
|
682
|
+
observers.push(cmsToggle);
|
|
655
683
|
|
|
656
684
|
// A target removed from the page ends its edit, so the save hold and the paused
|
|
657
685
|
// observers never outlive it. A running bar is no exception: it is torn down with
|
|
658
686
|
// the target it was describing.
|
|
659
|
-
new MutationObserver(() => {
|
|
687
|
+
const targetGone = new MutationObserver(() => {
|
|
660
688
|
if (anchorEl && !anchorEl.isConnected) abandonTarget();
|
|
661
|
-
})
|
|
689
|
+
});
|
|
690
|
+
targetGone.observe(document.body, { childList: true, subtree: true });
|
|
691
|
+
observers.push(targetGone);
|
|
662
692
|
}
|
|
663
693
|
|
|
664
694
|
// The panel's height changes as the box grows and the status line fills, so it is
|
|
@@ -709,6 +739,7 @@ function applyHelperState() {
|
|
|
709
739
|
// The host's toggle can change while the page is open, so each opening asks again.
|
|
710
740
|
function refreshHelperState() {
|
|
711
741
|
helperStateNow().then((state) => {
|
|
742
|
+
if (!panel) return; // the session this asked for has ended
|
|
712
743
|
helperState = state || 'unavailable';
|
|
713
744
|
if (!panel.hidden && !session) applyHelperState();
|
|
714
745
|
else syncBubble();
|
|
@@ -939,8 +970,7 @@ function openPanel(el, quote) {
|
|
|
939
970
|
// panel is opened on that target again.
|
|
940
971
|
function storeDraft() {
|
|
941
972
|
if (!anchorEl) return;
|
|
942
|
-
|
|
943
|
-
else drafts.delete(anchorEl);
|
|
973
|
+
keepDraft(anchorEl, textarea.value);
|
|
944
974
|
}
|
|
945
975
|
|
|
946
976
|
function closePanel() {
|
|
@@ -1023,6 +1053,7 @@ function escapeIsMine(event) {
|
|
|
1023
1053
|
}
|
|
1024
1054
|
|
|
1025
1055
|
function wireInteractions() {
|
|
1056
|
+
const { signal } = listeners;
|
|
1026
1057
|
// Clicks: text units are contenteditable (caret) and figures keep their own
|
|
1027
1058
|
// interactivity, so neither is intercepted, and nothing here prevents the click from
|
|
1028
1059
|
// landing. Any click on the page away from ClayJS chrome closes the box, a click
|
|
@@ -1033,7 +1064,7 @@ function wireInteractions() {
|
|
|
1033
1064
|
// box and ends on the page is still the box's, not a click away from it.
|
|
1034
1065
|
document.addEventListener('pointerdown', (event) => {
|
|
1035
1066
|
panelPointerDown = panel.contains(event.target);
|
|
1036
|
-
}, { capture: true });
|
|
1067
|
+
}, { capture: true, signal });
|
|
1037
1068
|
|
|
1038
1069
|
document.addEventListener('click', (event) => {
|
|
1039
1070
|
const startedInPanel = panelPointerDown;
|
|
@@ -1050,7 +1081,7 @@ function wireInteractions() {
|
|
|
1050
1081
|
} else if (!panel.hidden) {
|
|
1051
1082
|
closePanel();
|
|
1052
1083
|
}
|
|
1053
|
-
});
|
|
1084
|
+
}, { signal });
|
|
1054
1085
|
|
|
1055
1086
|
// Hover chip: follows the unit under the cursor while no panel is open.
|
|
1056
1087
|
let lastMove = 0;
|
|
@@ -1064,7 +1095,7 @@ function wireInteractions() {
|
|
|
1064
1095
|
const unit = unitFrom(event.target);
|
|
1065
1096
|
if (unit) showChipFor(unit);
|
|
1066
1097
|
else scheduleChipHide();
|
|
1067
|
-
});
|
|
1098
|
+
}, { signal });
|
|
1068
1099
|
|
|
1069
1100
|
document.addEventListener('keydown', (event) => {
|
|
1070
1101
|
if (isShortcut(event)) {
|
|
@@ -1094,26 +1125,35 @@ function wireInteractions() {
|
|
|
1094
1125
|
if (barMode === 'error') { closeBar(); return; }
|
|
1095
1126
|
if (barMode) return;
|
|
1096
1127
|
if (!panel.hidden) closePanel();
|
|
1097
|
-
});
|
|
1128
|
+
}, { signal });
|
|
1098
1129
|
|
|
1099
1130
|
document.addEventListener('selectionchange', () => {
|
|
1100
1131
|
clearTimeout(selectionTimer);
|
|
1101
1132
|
selectionTimer = setTimeout(showChipForSelection, 150);
|
|
1102
|
-
});
|
|
1133
|
+
}, { signal });
|
|
1103
1134
|
}
|
|
1104
1135
|
|
|
1105
1136
|
// ---------------------------------------------------------------- boot
|
|
1106
1137
|
|
|
1107
|
-
async function
|
|
1108
|
-
|
|
1138
|
+
async function loadHelper() {
|
|
1139
|
+
const token = ++chromeToken;
|
|
1109
1140
|
helperState = await helperStateNow();
|
|
1110
|
-
|
|
1111
|
-
|
|
1141
|
+
return token === chromeToken;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function build() {
|
|
1112
1145
|
if (!helperState || panel) return;
|
|
1146
|
+
listeners = new AbortController();
|
|
1113
1147
|
buildChrome();
|
|
1114
1148
|
wireInteractions();
|
|
1115
1149
|
}
|
|
1116
1150
|
|
|
1151
|
+
async function init() {
|
|
1152
|
+
if (!startsAtImport() || panel) return; // an owner/editor feature, built once
|
|
1153
|
+
if (!(await loadHelper())) return;
|
|
1154
|
+
build();
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1117
1157
|
// A target the composer can be opened on: a connected content element of this
|
|
1118
1158
|
// document. ClayJS chrome, transient UI, everything the save strips and everything
|
|
1119
1159
|
// that holds no text are not targets.
|
|
@@ -1141,11 +1181,106 @@ async function open(el, { prompt } = {}) {
|
|
|
1141
1181
|
textarea.focus();
|
|
1142
1182
|
return true;
|
|
1143
1183
|
}
|
|
1144
|
-
if (typeof prompt === 'string' && !drafts.has(el))
|
|
1184
|
+
if (typeof prompt === 'string' && !drafts.has(el)) keepDraft(el, prompt);
|
|
1145
1185
|
openPanel(el, null);
|
|
1146
1186
|
return true;
|
|
1147
1187
|
}
|
|
1148
1188
|
|
|
1149
|
-
|
|
1189
|
+
/**
|
|
1190
|
+
* A committed exit: the chrome, every handler this session added and every page
|
|
1191
|
+
* observer go. What the person typed is a draft, and an exit that could not represent it
|
|
1192
|
+
* never gets here, so nothing unsent is thrown away by this.
|
|
1193
|
+
*/
|
|
1194
|
+
function teardown() {
|
|
1195
|
+
chromeToken++;
|
|
1196
|
+
const s = session;
|
|
1197
|
+
session = null;
|
|
1198
|
+
try {
|
|
1199
|
+
s?.handle?.cancel();
|
|
1200
|
+
if (s) {
|
|
1201
|
+
if (s.paused) resumeObservers();
|
|
1202
|
+
if (s.held) releaseAllSaves();
|
|
1203
|
+
}
|
|
1204
|
+
} catch {}
|
|
1205
|
+
clearTimeout(selectionTimer);
|
|
1206
|
+
selectionTimer = null;
|
|
1207
|
+
clearTimeout(chipHideTimer);
|
|
1208
|
+
chipHideTimer = null;
|
|
1209
|
+
clearTimeout(barCloseTimer);
|
|
1210
|
+
barCloseTimer = null;
|
|
1211
|
+
clearHighlight();
|
|
1212
|
+
listeners?.abort();
|
|
1213
|
+
listeners = null;
|
|
1214
|
+
for (const observer of observers) observer.disconnect();
|
|
1215
|
+
observers = [];
|
|
1216
|
+
for (const node of [styleEl, ring, panel, bar, chip, docBubble]) node?.remove();
|
|
1217
|
+
styleEl = ring = panel = textarea = statusEl = pointer = bar = barStatus = null;
|
|
1218
|
+
barStop = barKeep = barRevert = barClose = chip = docBubble = null;
|
|
1219
|
+
buttons = {};
|
|
1220
|
+
barMode = null;
|
|
1221
|
+
barLifecycle = null;
|
|
1222
|
+
anchorEl = null;
|
|
1223
|
+
anchorRange = null;
|
|
1224
|
+
returnFocus = null;
|
|
1225
|
+
pendingSelection = undefined;
|
|
1226
|
+
chipTarget = null;
|
|
1227
|
+
chipSelection = undefined;
|
|
1228
|
+
panelPointerDown = false;
|
|
1229
|
+
helperState = null;
|
|
1230
|
+
drafts = new WeakMap();
|
|
1231
|
+
drafted.clear();
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
/**
|
|
1235
|
+
* ai-edit as a mode lease.
|
|
1236
|
+
*
|
|
1237
|
+
* Preparing this lease asks the host whether it lists a ready helper, which is safe while
|
|
1238
|
+
* the committed mode is still view; the commit that enters edit builds the chrome, inert
|
|
1239
|
+
* until somebody opens it, exactly as `clay.aiEdit.init()` does. What a session adds is
|
|
1240
|
+
* everything still unresolved — a request on the wire, a preview waiting for Keep or
|
|
1241
|
+
* Revert, a comment typed and not sent — so `quiesce` refuses the exit and `resume`
|
|
1242
|
+
* leaves that same preview, comment and selection timer exactly as they were.
|
|
1243
|
+
* `dispose` (after the barrier) stops every editor-only handler and takes the chrome
|
|
1244
|
+
* down, so a save or selection event in view draws nothing. `initial: true` adopts the
|
|
1245
|
+
* chrome the boot's own import built, and asks the host nothing a second time.
|
|
1246
|
+
*/
|
|
1247
|
+
export function aiEditParticipant() {
|
|
1248
|
+
return {
|
|
1249
|
+
id: "clay:ai-edit",
|
|
1250
|
+
order: 950, // after the wire (900): this request is sent through it
|
|
1251
|
+
prepare(context = {}) {
|
|
1252
|
+
const initial = context.initial === true;
|
|
1253
|
+
return {
|
|
1254
|
+
ready: initial ? boot : loadHelper(),
|
|
1255
|
+
enable() {
|
|
1256
|
+
if (!initial) build();
|
|
1257
|
+
},
|
|
1258
|
+
quiesce() {
|
|
1259
|
+
const unsent = (panel && !panel.hidden && textarea.value.trim() !== '') ||
|
|
1260
|
+
[...drafted].some((el) => el.isConnected);
|
|
1261
|
+
const pending = [];
|
|
1262
|
+
if (session) pending.push(session.id);
|
|
1263
|
+
if (barMode === 'saving') pending.push('save');
|
|
1264
|
+
if (unsent) pending.push('prompt');
|
|
1265
|
+
if (!pending.length) return;
|
|
1266
|
+
throw Object.assign(new Error('clay.aiEdit: an edit here is still unresolved'), {
|
|
1267
|
+
code: 'pending-input',
|
|
1268
|
+
pending,
|
|
1269
|
+
});
|
|
1270
|
+
},
|
|
1271
|
+
resume() {},
|
|
1272
|
+
dispose() {
|
|
1273
|
+
teardown();
|
|
1274
|
+
},
|
|
1275
|
+
};
|
|
1276
|
+
},
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// The boot's own answer, which an explicit edit boot's exit adopts rather than re-asking.
|
|
1281
|
+
let boot = null;
|
|
1282
|
+
onDomReady(() => { boot = init().then(() => {}, () => {}); });
|
|
1283
|
+
|
|
1284
|
+
export { aiEditParticipant as modeParticipant };
|
|
1150
1285
|
|
|
1151
1286
|
export const aiEdit = { init, open };
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* clay.bindText — an editor textarea that lives outside the saved records.
|
|
3
|
+
*
|
|
4
|
+
* The textarea sits in a `clay="freeze"` region and edits one record's text
|
|
5
|
+
* element (for example `<pre><code class="entry-body">`). Every input is
|
|
6
|
+
* written into the record at once, so a save captures it as the record's text
|
|
7
|
+
* and a peer's merge of another paragraph keeps both edits. A change to the
|
|
8
|
+
* record from elsewhere (a peer's merge, a restored draft, a JSON API write) is
|
|
9
|
+
* shown in the textarea with the caret kept. During an IME composition nothing
|
|
10
|
+
* is written or shown, and when it ends both edits apply unless they touch the
|
|
11
|
+
* same stretch of text. Nothing here is Journal-specific: OverType, the
|
|
12
|
+
* timestamps and the note arrive as callbacks.
|
|
13
|
+
*
|
|
14
|
+
* The binding answers for its own lifecycle too: `flush()` reports whether the
|
|
15
|
+
* record now holds the text and refuses while a composition is running, `open()`
|
|
16
|
+
* and `close()` honor that refusal, and `dispose()` only detaches once the exit
|
|
17
|
+
* barrier has landed.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// Applies two edits to one text when each is one stretch of it (an IME composition always is) and the
|
|
21
|
+
// stretches do not overlap (two inserts at one point keep both); null when they do.
|
|
22
|
+
export function spliceMerge(base, local, remote) {
|
|
23
|
+
const span = (text) => {
|
|
24
|
+
let pre = 0;
|
|
25
|
+
const max = Math.min(base.length, text.length);
|
|
26
|
+
while (pre < max && base[pre] === text[pre]) pre++;
|
|
27
|
+
let suf = 0;
|
|
28
|
+
while (suf < max - pre && base[base.length - 1 - suf] === text[text.length - 1 - suf]) suf++;
|
|
29
|
+
return { start: pre, end: base.length - suf, text: text.slice(pre, text.length - suf) };
|
|
30
|
+
};
|
|
31
|
+
const l = span(local);
|
|
32
|
+
const r = span(remote);
|
|
33
|
+
if (l.end <= r.start) return base.slice(0, l.start) + l.text + base.slice(l.end, r.start) + r.text + base.slice(r.end);
|
|
34
|
+
if (r.end <= l.start) return base.slice(0, r.start) + r.text + base.slice(r.end, l.start) + l.text + base.slice(l.end);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Where an offset in `before` lands in `after`, when the two differ in one stretch.
|
|
39
|
+
export function mapCaret(before, after, offset) {
|
|
40
|
+
let pre = 0;
|
|
41
|
+
const max = Math.min(before.length, after.length);
|
|
42
|
+
while (pre < max && before[pre] === after[pre]) pre++;
|
|
43
|
+
if (offset <= pre) return offset;
|
|
44
|
+
let suf = 0;
|
|
45
|
+
while (suf < max - pre && before[before.length - 1 - suf] === after[after.length - 1 - suf]) suf++;
|
|
46
|
+
return offset >= before.length - suf ? after.length - (before.length - offset) : after.length - suf;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function bindText(textarea, { onWrite, onShow, onConflict, onGone } = {}) {
|
|
50
|
+
const OBSERVE = { characterData: true, childList: true, subtree: true };
|
|
51
|
+
let field = textarea;
|
|
52
|
+
const ta = () => field;
|
|
53
|
+
let s = null; // { key, code, seen, composing, resolve }
|
|
54
|
+
let disposed = false;
|
|
55
|
+
const observer = new MutationObserver(() => {
|
|
56
|
+
observer.takeRecords();
|
|
57
|
+
if (s && !s.composing && s.code.textContent !== s.seen) reconcile(s.code.textContent);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// New durable input is the lifecycle's to admit, not the binding's: while a transition
|
|
61
|
+
// runs, a keystroke is not new input and is refused. A standalone page has no lifecycle
|
|
62
|
+
// and always admits.
|
|
63
|
+
const admitted = () => window.clay?.modeLifecycle?.canWrite?.() !== false;
|
|
64
|
+
|
|
65
|
+
// The record holds what the textarea holds. That is the only proof a flush landed: a
|
|
66
|
+
// callback that staged a conflict or a gone record has not made the text represented.
|
|
67
|
+
const represented = () => !!(s && s.code.isConnected && s.code.textContent === ta().value);
|
|
68
|
+
|
|
69
|
+
function write(value) {
|
|
70
|
+
const t = s.code.firstChild;
|
|
71
|
+
if (t && t.nodeType === 3 && !t.nextSibling) t.data = value;
|
|
72
|
+
else s.code.textContent = value;
|
|
73
|
+
observer.takeRecords();
|
|
74
|
+
s.seen = value;
|
|
75
|
+
onWrite?.(s.code);
|
|
76
|
+
}
|
|
77
|
+
function show(value) {
|
|
78
|
+
const el = ta();
|
|
79
|
+
const before = el.value;
|
|
80
|
+
const [a, b] = [mapCaret(before, value, el.selectionStart), mapCaret(before, value, el.selectionEnd)];
|
|
81
|
+
el.value = value;
|
|
82
|
+
if (document.activeElement === el) el.setSelectionRange(a, b);
|
|
83
|
+
s.seen = value;
|
|
84
|
+
onShow?.();
|
|
85
|
+
}
|
|
86
|
+
// The record changed while the textarea held text the record had not taken yet.
|
|
87
|
+
// `base` is the text both sides were editing, read before show() moves `seen`: a
|
|
88
|
+
// caller staging the losing side needs what the clash was measured against, not
|
|
89
|
+
// what the textarea holds afterwards. The second argument is additive, so a
|
|
90
|
+
// caller that only wants the typed string ignores it.
|
|
91
|
+
function reconcile(now) {
|
|
92
|
+
const typed = ta().value;
|
|
93
|
+
const base = s.seen;
|
|
94
|
+
const merged = typed === base ? now : spliceMerge(base, typed, now);
|
|
95
|
+
if (merged == null) {
|
|
96
|
+
onConflict?.(typed, { key: s.key, base, remote: now });
|
|
97
|
+
show(now);
|
|
98
|
+
return { ok: false, code: "conflict" };
|
|
99
|
+
}
|
|
100
|
+
if (merged !== now) write(merged);
|
|
101
|
+
show(merged);
|
|
102
|
+
return { ok: true };
|
|
103
|
+
}
|
|
104
|
+
// A record that cannot be resolved hands its text to `onGone` and is released: there is
|
|
105
|
+
// nothing left here that a later write could land in, and holding the detached element
|
|
106
|
+
// would refuse every record switch after it.
|
|
107
|
+
function rebind() {
|
|
108
|
+
const code = s.resolve ? s.resolve() : null;
|
|
109
|
+
if (!code) {
|
|
110
|
+
onGone?.(ta().value, { key: s.key, base: s.seen });
|
|
111
|
+
observer.disconnect();
|
|
112
|
+
s = null;
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
s.code = code;
|
|
116
|
+
observer.disconnect();
|
|
117
|
+
observer.observe(s.code, OBSERVE);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
// `captured` is the composition this binding already holds: it finishes through its own
|
|
121
|
+
// operation, so a closed admission does not throw away what the person was typing.
|
|
122
|
+
function input(captured = false) {
|
|
123
|
+
if (!s) return { ok: true };
|
|
124
|
+
if (s.composing) return { ok: false, code: "composition" };
|
|
125
|
+
if (!captured && !admitted()) return { ok: false, code: "busy" };
|
|
126
|
+
if (!s.code.isConnected && !rebind()) return { ok: false, code: "gone" };
|
|
127
|
+
const now = s.code.textContent;
|
|
128
|
+
if (now !== s.seen) return reconcile(now);
|
|
129
|
+
const value = ta().value;
|
|
130
|
+
if (value !== s.seen) write(value);
|
|
131
|
+
return { ok: true };
|
|
132
|
+
}
|
|
133
|
+
// A composition in progress is not something a flush may end on the person's behalf: it
|
|
134
|
+
// is refused, and `s.composing` stays exactly as it was. Everything else runs the input
|
|
135
|
+
// path above, and its answer says whether the record now holds the text.
|
|
136
|
+
function flush() {
|
|
137
|
+
if (!s) return { ok: true };
|
|
138
|
+
if (s.composing) return { ok: false, code: "composition" };
|
|
139
|
+
const result = input();
|
|
140
|
+
if (!result.ok) return result;
|
|
141
|
+
return represented() ? { ok: true } : { ok: false, code: "conflict" };
|
|
142
|
+
}
|
|
143
|
+
function open(textEl, options = {}) {
|
|
144
|
+
if (disposed) return { ok: false, code: "disposed" };
|
|
145
|
+
const key = options.key;
|
|
146
|
+
if (s && s.key === key) {
|
|
147
|
+
s.resolve = options.resolve;
|
|
148
|
+
if (s.code !== textEl || !s.code.isConnected) {
|
|
149
|
+
if (!rebind()) return { ok: false, code: "gone" };
|
|
150
|
+
return input();
|
|
151
|
+
}
|
|
152
|
+
return { ok: true };
|
|
153
|
+
}
|
|
154
|
+
// A refused flush keeps the open record and its observer: nothing was written, so
|
|
155
|
+
// switching records here would drop the text the refusal was protecting. A record that
|
|
156
|
+
// was gone has already handed its text over and released itself, so there is nothing
|
|
157
|
+
// left to protect and the switch goes ahead.
|
|
158
|
+
const flushed = flush();
|
|
159
|
+
if (!flushed.ok && s) return flushed;
|
|
160
|
+
observer.disconnect();
|
|
161
|
+
s = { key, code: textEl, seen: textEl.textContent, composing: false, resolve: options.resolve };
|
|
162
|
+
observer.observe(textEl, OBSERVE);
|
|
163
|
+
const el = ta();
|
|
164
|
+
el.value = s.seen;
|
|
165
|
+
el.setSelectionRange(0, 0);
|
|
166
|
+
onShow?.();
|
|
167
|
+
return { ok: true };
|
|
168
|
+
}
|
|
169
|
+
function close() {
|
|
170
|
+
const flushed = flush();
|
|
171
|
+
if (!flushed.ok && s) return flushed;
|
|
172
|
+
observer.disconnect();
|
|
173
|
+
s = null;
|
|
174
|
+
return { ok: true };
|
|
175
|
+
}
|
|
176
|
+
// Detaching only. The barrier has already succeeded when a lease disposes the binding, so
|
|
177
|
+
// this never flushes: a composition still in progress or text the record never took keeps
|
|
178
|
+
// the listeners where they are, and the caller is told why.
|
|
179
|
+
function dispose() {
|
|
180
|
+
if (disposed) return { ok: true };
|
|
181
|
+
if (s?.composing) return { ok: false, code: "composition" };
|
|
182
|
+
if (s && !represented()) return { ok: false, code: "pending-input" };
|
|
183
|
+
disposed = true;
|
|
184
|
+
observer.disconnect();
|
|
185
|
+
detach();
|
|
186
|
+
s = null;
|
|
187
|
+
return { ok: true };
|
|
188
|
+
}
|
|
189
|
+
function attach(next) {
|
|
190
|
+
if (disposed || !next || next === field) return;
|
|
191
|
+
detach();
|
|
192
|
+
field = next;
|
|
193
|
+
listen();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function onInput() { input(); }
|
|
197
|
+
function onCompositionStart() { if (s) s.composing = true; }
|
|
198
|
+
function onCompositionEnd() { if (s) { s.composing = false; input(true); } }
|
|
199
|
+
function listen() {
|
|
200
|
+
field.addEventListener("input", onInput);
|
|
201
|
+
field.addEventListener("compositionstart", onCompositionStart);
|
|
202
|
+
field.addEventListener("compositionend", onCompositionEnd);
|
|
203
|
+
}
|
|
204
|
+
function detach() {
|
|
205
|
+
field.removeEventListener("input", onInput);
|
|
206
|
+
field.removeEventListener("compositionstart", onCompositionStart);
|
|
207
|
+
field.removeEventListener("compositionend", onCompositionEnd);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
listen();
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
open, close, flush, dispose,
|
|
214
|
+
current: () => s && s.code,
|
|
215
|
+
attach,
|
|
216
|
+
composing: () => !!(s && s.composing),
|
|
217
|
+
};
|
|
218
|
+
}
|
package/src/plugins/cms.js
CHANGED
|
@@ -4,9 +4,102 @@
|
|
|
4
4
|
import { installSkin } from "../ui/vendor-skin.js";
|
|
5
5
|
import { CSS } from "../ui/skins/cms.js";
|
|
6
6
|
import { CSS as TOGGLE_CSS, ROOTS as TOGGLE_ROOTS } from "../ui/skins/cms-toggle.js";
|
|
7
|
+
import { cms as hypercms } from "../vendor/hypercms.vendor.js";
|
|
7
8
|
|
|
8
9
|
export { cms, default } from "../vendor/hypercms.vendor.js";
|
|
9
10
|
|
|
10
11
|
installSkin("cms", CSS);
|
|
11
12
|
// The toggle takes the page's scheme, so Bevel's face and ink resolve the way the page reads.
|
|
12
13
|
installSkin("cms-toggle", TOGGLE_CSS, { roots: TOGGLE_ROOTS });
|
|
14
|
+
|
|
15
|
+
const fault = (message, code) => Object.assign(new Error(`clay:cms: ${message}`), { code });
|
|
16
|
+
|
|
17
|
+
const isStale = (context) => {
|
|
18
|
+
if (!context) return false;
|
|
19
|
+
if (context.signal?.aborted) return true;
|
|
20
|
+
return typeof context.isCurrent === "function" && !context.isCurrent();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// One edit session over the page the CMS edits, as a ClayJS lease. The source owns the
|
|
24
|
+
// session itself, so every method here is the source's own lifecycle call: `enable`
|
|
25
|
+
// opens and mounts, `resume` is the refused exit over the same session, and `dispose`
|
|
26
|
+
// goes through the CMS's teardown after the barrier.
|
|
27
|
+
//
|
|
28
|
+
// `quiesce` is the one call the lease shapes. The source answers for the work it still
|
|
29
|
+
// holds, an upload in flight or a painted preview, with `{ok:false}` rather than by
|
|
30
|
+
// tearing that work down: an exit refused there keeps the session, its buffers and the
|
|
31
|
+
// upload, and the controller resumes the same lease once the person finishes or
|
|
32
|
+
// discards it. The lease contract carries a refusal as the `code` a controller reports,
|
|
33
|
+
// so the non-ok answer is thrown as `pending-input`.
|
|
34
|
+
function cmsLease(source) {
|
|
35
|
+
let disposed = false;
|
|
36
|
+
return {
|
|
37
|
+
ready: source.ready,
|
|
38
|
+
enable() {
|
|
39
|
+
if (disposed) return;
|
|
40
|
+
source.enable();
|
|
41
|
+
},
|
|
42
|
+
quiesce() {
|
|
43
|
+
if (disposed) return;
|
|
44
|
+
const result = source.quiesce();
|
|
45
|
+
if (result && result.ok === false) {
|
|
46
|
+
throw Object.assign(fault("the CMS still has work to finish", result.code || "pending-input"), {
|
|
47
|
+
pending: result.pending,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
},
|
|
52
|
+
resume() {
|
|
53
|
+
if (disposed) return;
|
|
54
|
+
source.resume();
|
|
55
|
+
},
|
|
56
|
+
dispose() {
|
|
57
|
+
if (disposed) return;
|
|
58
|
+
disposed = true;
|
|
59
|
+
source.dispose();
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The boot this tab made, adopted instead of prepared. A page that opened the CMS itself
|
|
65
|
+
// (`?cms=true`) holds a session the source hands no lease out for, so it cannot be
|
|
66
|
+
// quiesced or torn down through the adapter and the caller keeps its reload instead;
|
|
67
|
+
// a boot that opened nothing has nothing to hand back, and this lease starts nothing.
|
|
68
|
+
function adopt() {
|
|
69
|
+
if (hypercms.isOpen) throw fault("the CMS this boot opened cannot be taken back", "unsupported-plugin");
|
|
70
|
+
const lease = cmsLease(hypercms.prepareModeLease({}));
|
|
71
|
+
return {
|
|
72
|
+
enable() {},
|
|
73
|
+
quiesce: () => lease.quiesce(),
|
|
74
|
+
resume: () => lease.resume(),
|
|
75
|
+
dispose: () => lease.dispose(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The CMS as a mode participant: the session the commit that enters edit opens, and the
|
|
81
|
+
* one the exit destroys.
|
|
82
|
+
*
|
|
83
|
+
* `prepare` is inert by construction, because the source's lease mounts nothing until
|
|
84
|
+
* `enable`; a vendor build without that lifecycle is refused here rather than opened
|
|
85
|
+
* beside the controller, where an exit would have no session to quiesce and no teardown
|
|
86
|
+
* to run. `order` is the source's: the CMS edits the page's own attributes and content,
|
|
87
|
+
* so it enters after the modules that prepare that page for editing, and it leaves
|
|
88
|
+
* first, while its session can still refuse an exit over an upload or a preview.
|
|
89
|
+
*/
|
|
90
|
+
export function cmsParticipant(options = {}) {
|
|
91
|
+
return {
|
|
92
|
+
id: "clay:cms",
|
|
93
|
+
order: 1000,
|
|
94
|
+
async prepare(context = {}) {
|
|
95
|
+
if (typeof hypercms.prepareModeLease !== "function") {
|
|
96
|
+
throw fault("this vendor build has no quiescable session", "unsupported-plugin");
|
|
97
|
+
}
|
|
98
|
+
if (isStale(context)) throw fault("prepared for a stale generation", "stale-document");
|
|
99
|
+
if (context.initial === true) return adopt();
|
|
100
|
+
return cmsLease(hypercms.prepareModeLease(options));
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export { cmsParticipant as modeParticipant };
|