@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/page/chrome.js
CHANGED
|
@@ -5,15 +5,19 @@
|
|
|
5
5
|
// --page-* tokens in editor.js (contract: default-page/v1/look/THEME-CONTRACT.md).
|
|
6
6
|
import { state, commands } from "../vendor/prosemirror.vendor.js";
|
|
7
7
|
import { schema } from "./schema.js";
|
|
8
|
-
import { BlockSelection,
|
|
8
|
+
import { BlockSelection, siblingSpan, moveBlock, indentBlocks, outdentBlocks } from "./blocks.js";
|
|
9
|
+
import { outlineOf } from "./outline-cache.js";
|
|
9
10
|
import { currentSpan, selectBlocks, duplicateBlocks, deleteBlocks, asStep, typeIntoBlocks } from "./blocksel.js";
|
|
10
11
|
import { openAltEditor, setImageAttrs } from "./media.js";
|
|
11
12
|
import { BlockDrag } from "./drag.js";
|
|
12
13
|
import { openLinkEditor } from "./link.js";
|
|
13
|
-
import { BLOCK_TYPES, MENU_ITEMS, blockTypeOf, turnInto, insertAfter, rankItems, runItem, retype, insertItemsFor } from "./items.js";
|
|
14
|
-
import toast from "../ui/toast.js";
|
|
14
|
+
import { BLOCK_TYPES, MENU_ITEMS, blockTypeOf, turnInto, insertAfter, rankItems, runItem, retype, canTurnInto, insertItemsFor } from "./items.js";
|
|
15
|
+
import toast from "../ui/toast-lazy.js";
|
|
15
16
|
import { insertChartAfterTable } from "./chart.js";
|
|
16
17
|
import { pickCsv } from "./csv-import.js";
|
|
18
|
+
import { HISTORY_INTENT, typingIntent } from "./history-intent.js";
|
|
19
|
+
import { composing } from "./markdown.js";
|
|
20
|
+
import { announce } from "./announce.js";
|
|
17
21
|
|
|
18
22
|
export { BLOCK_TYPES, blockTypeOf, turnInto, insertAfter } from "./items.js";
|
|
19
23
|
|
|
@@ -24,7 +28,7 @@ const N = schema.nodes;
|
|
|
24
28
|
const MOD = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform) ? "⌘" : "Ctrl+";
|
|
25
29
|
|
|
26
30
|
const selectedRoots = (st) => {
|
|
27
|
-
const ents =
|
|
31
|
+
const ents = outlineOf(st.doc);
|
|
28
32
|
const { lo, hi } = st.selection.span(st.doc);
|
|
29
33
|
return ents.slice(lo, hi + 1).filter((e) => e.up === ents[lo].up);
|
|
30
34
|
};
|
|
@@ -32,6 +36,30 @@ const selectedRoots = (st) => {
|
|
|
32
36
|
const imageId = (view, index) => view.state.doc.child(index).attrs.id;
|
|
33
37
|
const blockStart = (doc, index) => { let at = 0; for (let i = 0; i < index; i++) at += doc.child(i).nodeSize; return at; };
|
|
34
38
|
|
|
39
|
+
// The editor attributes a menu lends it while it is open. A slash menu puts them
|
|
40
|
+
// back as it found them; every other menu clears them.
|
|
41
|
+
const POPUP_ATTRS = ["aria-haspopup", "aria-controls", "aria-activedescendant"];
|
|
42
|
+
const TARGET_GONE = "That block is no longer available.";
|
|
43
|
+
const CANNOT_CONVERT = "This block holds content from a newer version, so it can't be converted here.";
|
|
44
|
+
|
|
45
|
+
// The part of the window the person can see: the visual viewport when the
|
|
46
|
+
// browser has one, so a keyboard or a pinch does not hide the bar.
|
|
47
|
+
export function phoneViewport(win) {
|
|
48
|
+
const v = win.visualViewport;
|
|
49
|
+
return v ? { left: v.offsetLeft, top: v.offsetTop, width: v.width, height: v.height }
|
|
50
|
+
: { left: 0, top: 0, width: win.innerWidth, height: win.innerHeight };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function phoneBarBox(v, width, height, safeBottom = 0) {
|
|
54
|
+
const inset = PHONE_INSET;
|
|
55
|
+
const w = Math.min(width, Math.max(0, v.width - inset * 2));
|
|
56
|
+
return {
|
|
57
|
+
left: v.left + (v.width - w) / 2,
|
|
58
|
+
top: Math.max(v.top + inset, v.top + v.height - height - safeBottom - inset),
|
|
59
|
+
width: w,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
35
63
|
export function blockLink(id, loc = globalThis.location) {
|
|
36
64
|
return `${loc.origin}${loc.pathname}${loc.search}#${encodeURIComponent(id)}`;
|
|
37
65
|
}
|
|
@@ -83,6 +111,7 @@ const ICONS = {
|
|
|
83
111
|
bullet: svg('<path d="M6.5 4.5h7M6.5 8h7M6.5 11.5h7"/><path stroke-width="2.4" d="M3 4.5h.01M3 8h.01M3 11.5h.01"/>'),
|
|
84
112
|
ordered: svg('<path d="M7 4.5h6.5M7 8h6.5M7 11.5h6.5M2.5 3.5l1-.5v3M2.5 9.3c.2-.5.6-.8 1.1-.8.6 0 1 .4 1 .9 0 .9-2.1 1.4-2.1 2.6h2.1"/>'),
|
|
85
113
|
check: svg('<rect x="2.5" y="2.5" width="11" height="11" rx="2.5"/><path d="M5.5 8.2l1.8 1.8 3.4-4"/>'),
|
|
114
|
+
more: svg('<path stroke-width="2.2" d="M3 8h.01M8 8h.01M13 8h.01"/>'),
|
|
86
115
|
};
|
|
87
116
|
const TOOLS = [
|
|
88
117
|
["strong", "Bold"],
|
|
@@ -94,6 +123,11 @@ const TOOLS = [
|
|
|
94
123
|
["check", "Checklist"],
|
|
95
124
|
["code", "Code"],
|
|
96
125
|
];
|
|
126
|
+
// The four controls a phone toolbar keeps: bold, italic, link and the menu that
|
|
127
|
+
// holds everything else. Wider screens show every tool.
|
|
128
|
+
const PHONE_TOOLS = new Set(["strong", "em", "link"]);
|
|
129
|
+
const MORE_ACTIONS = new Set(["up", "down", "indent", "outdent"]);
|
|
130
|
+
const PHONE_BAR_WIDTH = 204, PHONE_BAR_HEIGHT = 52, PHONE_INSET = 8;
|
|
97
131
|
const LIST_KINDS = new Set(["bullet", "ordered", "check"]);
|
|
98
132
|
const LINE_TYPES = new Set(["paragraph", "heading", "list_item"]);
|
|
99
133
|
|
|
@@ -173,8 +207,13 @@ function runList(view, kind) {
|
|
|
173
207
|
const st = view.state;
|
|
174
208
|
const off = listActive(st, kind);
|
|
175
209
|
const item = BLOCK_TYPES.find((b) => b.key === (off ? "text" : kind));
|
|
210
|
+
const idx = listable(st).filter((i) => off || !isKind(st.doc.child(i), kind));
|
|
211
|
+
if (idx.some((i) => !canTurnInto(st.doc.child(i), item))) {
|
|
212
|
+
announce(view, CANNOT_CONVERT);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
176
215
|
const tr = st.tr;
|
|
177
|
-
for (const i of
|
|
216
|
+
for (const i of idx) retype(tr, i, item);
|
|
178
217
|
if (tr.docChanged) view.dispatch(asStep(tr));
|
|
179
218
|
}
|
|
180
219
|
|
|
@@ -185,6 +224,7 @@ function runMark(view, name) {
|
|
|
185
224
|
const lines = name === "code" && !code ? wholeLines(st) : null;
|
|
186
225
|
if (LIST_KINDS.has(name)) runList(view, name);
|
|
187
226
|
else if (code) view.dispatch(asStep(codeLinesTr(st, code)).scrollIntoView());
|
|
227
|
+
else if (lines && lines.some((i) => !canTurnInto(st.doc.child(i), BLOCK_TYPES.find((b) => b.key === "code")))) announce(view, CANNOT_CONVERT);
|
|
188
228
|
else if (lines) view.dispatch(asStep(codeBlockTr(st, lines)).scrollIntoView());
|
|
189
229
|
else commands.toggleMark(M[name])(st, view.dispatch);
|
|
190
230
|
view.focus();
|
|
@@ -198,7 +238,7 @@ const el = (tag, cls, attrs = {}) => {
|
|
|
198
238
|
};
|
|
199
239
|
|
|
200
240
|
function toolButton(name, label) {
|
|
201
|
-
const b = el("button", "page-tool"
|
|
241
|
+
const b = el("button", `page-tool${PHONE_TOOLS.has(name) ? " page-tool-primary" : " page-tool-desk"}`, { type: "button", "data-cmd": name, "aria-label": label, title: label });
|
|
202
242
|
b.innerHTML = ICONS[name];
|
|
203
243
|
return b;
|
|
204
244
|
}
|
|
@@ -208,6 +248,35 @@ let menuCount = 0;
|
|
|
208
248
|
export const chromeKey = new PluginKey("page-chrome");
|
|
209
249
|
const chromeFor = new WeakMap();
|
|
210
250
|
|
|
251
|
+
// A range a slash menu chose, held in plugin state so it follows the document
|
|
252
|
+
// while the action it belongs to waits for code to load.
|
|
253
|
+
export const slashRangeKey = new PluginKey("page-slash-range");
|
|
254
|
+
const slashRangeState = {
|
|
255
|
+
init: () => null,
|
|
256
|
+
apply(tr, value) {
|
|
257
|
+
let held = value;
|
|
258
|
+
if (held && tr.docChanged) {
|
|
259
|
+
const from = tr.mapping.map(held.from, 1), to = tr.mapping.map(held.to, -1);
|
|
260
|
+
held = from <= to && tr.doc.textBetween(from, to) === held.text ? { ...held, from, to } : null;
|
|
261
|
+
}
|
|
262
|
+
const m = tr.getMeta(slashRangeKey);
|
|
263
|
+
if (m !== undefined) held = m;
|
|
264
|
+
return held;
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export const slashRangePlugin = () => new Plugin({ key: slashRangeKey, state: slashRangeState });
|
|
269
|
+
|
|
270
|
+
export const holdSlashRange = (view, range) => {
|
|
271
|
+
const text = range ? view.state.doc.textBetween(range.from, range.to) : null;
|
|
272
|
+
view.dispatch(view.state.tr.setMeta(slashRangeKey, range ? { ...range, text } : null));
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
export const heldSlashRange = (st) => {
|
|
276
|
+
const held = slashRangeKey.getState(st);
|
|
277
|
+
return held ? { from: held.from, to: held.to } : null;
|
|
278
|
+
};
|
|
279
|
+
|
|
211
280
|
export function openPageMenu(view, anchor, items) {
|
|
212
281
|
const chrome = chromeFor.get(view);
|
|
213
282
|
if (!chrome) return false;
|
|
@@ -223,12 +292,21 @@ class Chrome {
|
|
|
223
292
|
this.onAiTool = onAiTool;
|
|
224
293
|
this.shell = view.dom.parentElement;
|
|
225
294
|
this.menu = null;
|
|
295
|
+
this.priorAria = null;
|
|
226
296
|
this.movedTimer = 0;
|
|
227
297
|
this.hover = null;
|
|
298
|
+
this.pendingHover = null;
|
|
299
|
+
this.win = view.dom.ownerDocument.defaultView ?? globalThis;
|
|
300
|
+
this.frame = null;
|
|
301
|
+
this.destroyed = false;
|
|
302
|
+
this.probe = null;
|
|
303
|
+
this.roving = null;
|
|
228
304
|
|
|
229
305
|
this.dock = el("div", "page-dock", { role: "toolbar", "aria-label": "Formatting" });
|
|
230
|
-
this.turn = el("button", "page-tool page-tool-turn", { type: "button", "aria-haspopup": "menu" });
|
|
231
|
-
this.
|
|
306
|
+
this.turn = el("button", "page-tool page-tool-turn page-tool-desk", { type: "button", "aria-haspopup": "menu" });
|
|
307
|
+
this.more = el("button", "page-tool page-tool-primary page-tool-more", { type: "button", "aria-label": "More", title: "More", "aria-haspopup": "menu" });
|
|
308
|
+
this.more.innerHTML = ICONS.more;
|
|
309
|
+
this.dock.append(this.turn, el("span", "page-tool-sep page-tool-desk"), ...TOOLS.map(([n, l]) => toolButton(n, l)), this.more);
|
|
232
310
|
this.plus = el("button", "page-plus", { type: "button", "aria-label": "Add a block", title: "Add a block" });
|
|
233
311
|
this.plus.innerHTML = ICONS.plus;
|
|
234
312
|
this.grip = el("button", "page-grip", { type: "button", "aria-label": "Block actions", title: "Drag to move, click for actions", "aria-haspopup": "menu" });
|
|
@@ -250,6 +328,8 @@ class Chrome {
|
|
|
250
328
|
});
|
|
251
329
|
}
|
|
252
330
|
this.turn.addEventListener("click", () => this.toggleMenu("turn", this.turn));
|
|
331
|
+
this.more.addEventListener("click", () => this.toggleMenu("more", this.more));
|
|
332
|
+
this.dock.addEventListener("keydown", (e) => this.dockKey(e));
|
|
253
333
|
this.plus.addEventListener("click", () => this.fromPlus());
|
|
254
334
|
this.grip.addEventListener("click", (e) => {
|
|
255
335
|
if (e.detail === 0) this.gripAction(this.gutterIndex());
|
|
@@ -272,10 +352,11 @@ class Chrome {
|
|
|
272
352
|
this.onKey = () => {
|
|
273
353
|
clearTimeout(this.movedTimer);
|
|
274
354
|
this.shell.classList.remove("is-moved");
|
|
355
|
+
this.pendingHover = null;
|
|
275
356
|
this.setHover(null);
|
|
276
357
|
};
|
|
277
|
-
this.onLeave = () => this.setHover(null);
|
|
278
|
-
this.onResize = () => this.
|
|
358
|
+
this.onLeave = () => { this.pendingHover = null; this.setHover(null); };
|
|
359
|
+
this.onResize = () => this.scheduleLayout();
|
|
279
360
|
this.onDocDown = (e) => {
|
|
280
361
|
if (!this.menu) return;
|
|
281
362
|
if ([this.menuEl, this.plus, this.grip, this.turn].some((part) => part.contains(e.target))) return;
|
|
@@ -285,7 +366,10 @@ class Chrome {
|
|
|
285
366
|
this.shell.addEventListener("mousemove", this.onMove);
|
|
286
367
|
this.shell.addEventListener("mouseleave", this.onLeave);
|
|
287
368
|
this.shell.addEventListener("keydown", this.onKey);
|
|
288
|
-
|
|
369
|
+
this.win.addEventListener("resize", this.onResize);
|
|
370
|
+
this.win.addEventListener("scroll", this.onResize, true);
|
|
371
|
+
this.win.visualViewport?.addEventListener("resize", this.onResize);
|
|
372
|
+
this.win.visualViewport?.addEventListener("scroll", this.onResize);
|
|
289
373
|
this.update(view, null);
|
|
290
374
|
}
|
|
291
375
|
|
|
@@ -295,11 +379,107 @@ class Chrome {
|
|
|
295
379
|
return Math.min(st.doc.resolve(pos).index(0), st.doc.childCount - 1);
|
|
296
380
|
}
|
|
297
381
|
|
|
382
|
+
isPhone() {
|
|
383
|
+
return !!this.win?.matchMedia?.("(max-width: 560px)").matches;
|
|
384
|
+
}
|
|
385
|
+
|
|
298
386
|
gutterIndex() {
|
|
299
|
-
if (this.
|
|
387
|
+
if (this.isPhone()) return this.blockIndex();
|
|
388
|
+
this.settleHover();
|
|
300
389
|
return Math.min(this.hover ?? this.blockIndex(), this.view.state.doc.childCount - 1);
|
|
301
390
|
}
|
|
302
391
|
|
|
392
|
+
dockControls() {
|
|
393
|
+
const all = [...this.dock.querySelectorAll("button")].filter((b) => this.rendered(b));
|
|
394
|
+
return this.isPhone() ? all.filter((b) => b.classList.contains("page-tool-primary")) : all;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Every transaction asks whether the roving control is still usable: one style read for
|
|
398
|
+
// that control, not one for every button in the dock.
|
|
399
|
+
inRoving(control) {
|
|
400
|
+
if (!control || !this.dock.contains(control)) return false;
|
|
401
|
+
if (this.isPhone() && !control.classList.contains("page-tool-primary")) return false;
|
|
402
|
+
return this.rendered(control);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
rendered(control) {
|
|
406
|
+
if (control.hidden) return false;
|
|
407
|
+
const style = this.win?.getComputedStyle?.(control);
|
|
408
|
+
return !style || style.display !== "none";
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
setRoving(target) {
|
|
412
|
+
this.roving = target;
|
|
413
|
+
const roving = new Set(this.dockControls());
|
|
414
|
+
for (const b of this.dock.querySelectorAll("button")) b.tabIndex = b === target && roving.has(b) ? 0 : -1;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// The block selection's own Escape still runs first, and then the keyboard
|
|
418
|
+
// leaves the writing surface for the toolbar.
|
|
419
|
+
leaveToToolbar() {
|
|
420
|
+
queueMicrotask(() => {
|
|
421
|
+
if (!this.view.isDestroyed) this.focusToolbar();
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
focusToolbar() {
|
|
426
|
+
const [first] = this.dockControls();
|
|
427
|
+
if (!first) return false;
|
|
428
|
+
this.setRoving(first);
|
|
429
|
+
first.focus();
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Left and Right walk the toolbar; Escape hands the keyboard back to the
|
|
434
|
+
// editor. Tab is left alone, so it leaves the toolbar.
|
|
435
|
+
dockKey(event) {
|
|
436
|
+
if (event.key === "Escape") {
|
|
437
|
+
event.preventDefault();
|
|
438
|
+
this.view.focus();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (event.key !== "ArrowRight" && event.key !== "ArrowLeft") return;
|
|
442
|
+
const list = this.dockControls();
|
|
443
|
+
const at = list.indexOf(event.target);
|
|
444
|
+
if (at < 0) return;
|
|
445
|
+
event.preventDefault();
|
|
446
|
+
const next = list[(at + (event.key === "ArrowRight" ? 1 : -1) + list.length) % list.length];
|
|
447
|
+
this.setRoving(next);
|
|
448
|
+
next.focus();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// The blocks a menu's commands act on, by id, so a peer's edit between opening
|
|
452
|
+
// the menu and running the command cannot retarget them.
|
|
453
|
+
selectionIds() {
|
|
454
|
+
const st = this.view.state;
|
|
455
|
+
return blocksIn(st).map((i) => st.doc.child(i).attrs.id);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
idIndex(id) {
|
|
459
|
+
let found = -1;
|
|
460
|
+
this.view.state.doc.forEach((n, _, i) => { if (n.attrs.id === id) found = i; });
|
|
461
|
+
return found;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Where Escape sends focus: the control that opened the menu when it can hold
|
|
465
|
+
// focus, otherwise the editor.
|
|
466
|
+
invoker() {
|
|
467
|
+
const m = this.menu;
|
|
468
|
+
if (!m || m.slashAt !== null) return this.view.dom;
|
|
469
|
+
const a = m.anchor;
|
|
470
|
+
return a && a !== this.view.dom && (a.tagName === "BUTTON" || a.hasAttribute("tabindex")) ? a : this.view.dom;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// A block action from the phone menu acts on the caret's block when nothing
|
|
474
|
+
// more is selected, as the desktop command does.
|
|
475
|
+
selectCaretBlock(ids = null) {
|
|
476
|
+
const st = this.view.state;
|
|
477
|
+
if (st.selection instanceof BlockSelection) return;
|
|
478
|
+
const found = (ids ?? []).map((id) => this.idIndex(id)).filter((i) => i >= 0);
|
|
479
|
+
const first = found.length ? found[0] : this.blockIndex();
|
|
480
|
+
this.view.dispatch(st.tr.setSelection(selectBlocks(st.doc, first, found.length ? found[found.length - 1] : first)));
|
|
481
|
+
}
|
|
482
|
+
|
|
303
483
|
gripAction(index) {
|
|
304
484
|
if (this.menu) this.closeMenu();
|
|
305
485
|
else this.openActions(this.grip, index);
|
|
@@ -307,25 +487,39 @@ class Chrome {
|
|
|
307
487
|
|
|
308
488
|
trackHover(event) {
|
|
309
489
|
if (this.menu || this.drag.state || [this.plus, this.grip, this.dock, this.bubble].some((part) => part.contains(event.target))) return;
|
|
490
|
+
this.pendingHover = { clientY: event.clientY };
|
|
491
|
+
this.scheduleLayout();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// The pointer's block, read once per frame from the newest move.
|
|
495
|
+
hoverAt(clientY) {
|
|
310
496
|
const view = this.view;
|
|
311
|
-
const hit = view.posAtCoords({ left: view.dom.getBoundingClientRect().left + 2, top:
|
|
497
|
+
const hit = view.posAtCoords({ left: view.dom.getBoundingClientRect().left + 2, top: clientY });
|
|
312
498
|
if (!hit) return;
|
|
313
499
|
const doc = view.state.doc;
|
|
314
|
-
const ents =
|
|
500
|
+
const ents = outlineOf(doc);
|
|
315
501
|
let i = Math.min(doc.resolve(hit.inside >= 0 ? hit.inside : hit.pos).index(0), ents.length - 1);
|
|
316
502
|
while (i > 0 && ents[i].hidden) i--;
|
|
317
|
-
this.
|
|
503
|
+
this.hover = i;
|
|
318
504
|
}
|
|
319
505
|
|
|
320
506
|
setHover(i) {
|
|
321
507
|
if (i === this.hover) return;
|
|
322
508
|
this.hover = i;
|
|
323
|
-
this.
|
|
509
|
+
this.scheduleLayout();
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
settleHover() {
|
|
513
|
+
if (!this.pendingHover) return;
|
|
514
|
+
const { clientY } = this.pendingHover;
|
|
515
|
+
this.pendingHover = null;
|
|
516
|
+
this.hoverAt(clientY);
|
|
324
517
|
}
|
|
325
518
|
|
|
326
519
|
fromPlus() {
|
|
327
520
|
this.view.focus();
|
|
328
|
-
|
|
521
|
+
const i = this.gutterIndex();
|
|
522
|
+
this.openMenu("insert", this.plus, null, null, this.view.state.doc.child(i).attrs.id);
|
|
329
523
|
}
|
|
330
524
|
|
|
331
525
|
toggleMenu(mode, anchor) {
|
|
@@ -333,10 +527,13 @@ class Chrome {
|
|
|
333
527
|
else this.openMenu(mode, anchor);
|
|
334
528
|
}
|
|
335
529
|
|
|
336
|
-
openMenu(mode, anchor, slashAt = null,
|
|
337
|
-
this.menu = { mode, anchor, slashAt,
|
|
530
|
+
openMenu(mode, anchor, slashAt = null, ids = null, anchorId = null) {
|
|
531
|
+
this.menu = { mode, anchor, slashAt, anchorId, ids: ids ?? (mode === "insert" ? [] : this.selectionIds()), query: "", active: 0, items: [] };
|
|
338
532
|
anchor.setAttribute("aria-expanded", "true");
|
|
339
|
-
|
|
533
|
+
if (slashAt !== null) {
|
|
534
|
+
if (!this.priorAria) this.priorAria = POPUP_ATTRS.map((name) => [name, this.view.dom.getAttribute(name)]);
|
|
535
|
+
this.view.dom.setAttribute("aria-haspopup", "listbox");
|
|
536
|
+
} else this.view.dom.setAttribute("aria-haspopup", "menu");
|
|
340
537
|
this.view.dom.setAttribute("aria-controls", this.menuId);
|
|
341
538
|
this.renderMenu();
|
|
342
539
|
}
|
|
@@ -346,32 +543,65 @@ class Chrome {
|
|
|
346
543
|
this.menu = null;
|
|
347
544
|
this.menuEl.hidden = true;
|
|
348
545
|
this.scrim.hidden = true;
|
|
349
|
-
|
|
546
|
+
if (this.priorAria) {
|
|
547
|
+
for (const [name, value] of this.priorAria) {
|
|
548
|
+
if (value === null) this.view.dom.removeAttribute(name);
|
|
549
|
+
else this.view.dom.setAttribute(name, value);
|
|
550
|
+
}
|
|
551
|
+
this.priorAria = null;
|
|
552
|
+
} else for (const name of POPUP_ATTRS) this.view.dom.removeAttribute(name);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Everything the phone toolbar folds away: the rest of the formatting, and the
|
|
556
|
+
// block actions the desktop menu carries. The move commands are the same ones.
|
|
557
|
+
moreItems() {
|
|
558
|
+
const tools = TOOLS.filter(([name]) => !PHONE_TOOLS.has(name))
|
|
559
|
+
.map(([name, label]) => ({ key: name, label, icon: ICONS[name], run: (view) => runMark(view, name) }));
|
|
560
|
+
const actions = ACTION_ITEMS.filter((a) => MORE_ACTIONS.has(a.key)).map((a) => ({ ...a, blocks: true }));
|
|
561
|
+
return [
|
|
562
|
+
...tools,
|
|
563
|
+
{ key: "turn", label: "Turn into", icon: "\u21bb", run: (view, chrome) => chrome.openMenu("turn", chrome.more) },
|
|
564
|
+
{ key: "insert", label: "Insert", icon: "+", run: (view, chrome) => chrome.openMenu("insert", chrome.more, null, null, view.state.doc.child(chrome.blockIndex()).attrs.id) },
|
|
565
|
+
...actions,
|
|
566
|
+
];
|
|
350
567
|
}
|
|
351
568
|
|
|
352
569
|
menuItems(m) {
|
|
353
570
|
if (m.mode === "external") return m.externalItems || [];
|
|
354
571
|
if (m.mode === "actions") return actionsFor(this.view.state);
|
|
572
|
+
if (m.mode === "more") return this.moreItems();
|
|
355
573
|
const items = m.mode === "turn" ? BLOCK_TYPES : insertItemsFor(this.view.state, this.onAiTool
|
|
356
574
|
? [...MENU_ITEMS, { key: "ai", label: "Make a tool with AI…", icon: "✦", aliases: ["tool", "generate"], insert: "ai" }]
|
|
357
575
|
: MENU_ITEMS);
|
|
358
576
|
return rankItems(items, m.query);
|
|
359
577
|
}
|
|
360
578
|
|
|
579
|
+
// The types the blocks in the selection are now. A mixed selection has more
|
|
580
|
+
// than one and so checks no row.
|
|
581
|
+
currentTypes() {
|
|
582
|
+
const st = this.view.state;
|
|
583
|
+
return new Set(blocksIn(st).map((i) => blockTypeOf(st.doc.child(i)).key));
|
|
584
|
+
}
|
|
585
|
+
|
|
361
586
|
openActions(anchor = this.plus, at = null) {
|
|
362
587
|
const st = this.view.state;
|
|
363
|
-
const ents =
|
|
588
|
+
const ents = outlineOf(st.doc);
|
|
364
589
|
const sel = st.selection;
|
|
365
590
|
const inside = at !== null && sel instanceof BlockSelection && sel.span(st.doc).lo <= at && at <= sel.span(st.doc).hi;
|
|
366
591
|
const { lo, hi } = at === null || inside ? currentSpan(st, ents) : siblingSpan(ents, at, at);
|
|
367
592
|
this.view.dispatch(st.tr.setSelection(selectBlocks(st.doc, lo, hi, ents)));
|
|
368
|
-
|
|
593
|
+
const ids = [];
|
|
594
|
+
for (let i = lo; i <= hi; i++) ids.push(st.doc.child(i).attrs.id);
|
|
595
|
+
this.openMenu("actions", anchor, null, ids);
|
|
369
596
|
}
|
|
370
597
|
|
|
371
598
|
renderMenu() {
|
|
372
599
|
const m = this.menu;
|
|
373
600
|
m.items = this.menuItems(m);
|
|
374
601
|
m.active = Math.min(m.active, Math.max(0, m.items.length - 1));
|
|
602
|
+
const slash = m.slashAt !== null;
|
|
603
|
+
const current = m.mode === "turn" ? this.currentTypes() : null;
|
|
604
|
+
this.menuEl.setAttribute("role", slash ? "listbox" : "menu");
|
|
375
605
|
const nodes = [];
|
|
376
606
|
if (m.mode === "turn") {
|
|
377
607
|
const title = el("div", "page-menu-title");
|
|
@@ -385,7 +615,9 @@ class Chrome {
|
|
|
385
615
|
}
|
|
386
616
|
m.items.forEach((b, i) => {
|
|
387
617
|
const active = i === m.active;
|
|
388
|
-
const
|
|
618
|
+
const checked = current ? current.size === 1 && b.key === [...current][0] : (typeof b.checked === "boolean" ? b.checked : null);
|
|
619
|
+
const role = m.mode === "turn" ? "menuitemradio" : typeof checked === "boolean" ? "menuitemcheckbox" : slash ? "option" : "menuitem";
|
|
620
|
+
const row = el("button", `page-menu-item${active ? " is-active" : ""}`, { type: "button", role, tabindex: !slash && active ? "0" : "-1", id: `${this.menuId}-${i}`, "data-index": String(i), ...(role === "menuitemradio" ? {} : { "aria-selected": active ? "true" : "false" }), ...(typeof checked === "boolean" ? { "aria-checked": String(checked) } : {}) });
|
|
389
621
|
const icon = el("span", "page-menu-icon");
|
|
390
622
|
icon.textContent = b.icon || "";
|
|
391
623
|
const label = el("span", "page-menu-label");
|
|
@@ -405,12 +637,12 @@ class Chrome {
|
|
|
405
637
|
nodes.push(row);
|
|
406
638
|
});
|
|
407
639
|
this.menuEl.replaceChildren(...nodes);
|
|
408
|
-
if (m.items.length) this.view.dom.setAttribute("aria-activedescendant", `${this.menuId}-${m.active}`);
|
|
640
|
+
if (slash && m.items.length) this.view.dom.setAttribute("aria-activedescendant", `${this.menuId}-${m.active}`);
|
|
409
641
|
else this.view.dom.removeAttribute("aria-activedescendant");
|
|
410
642
|
this.menuEl.hidden = false;
|
|
411
643
|
this.scrim.hidden = false;
|
|
412
|
-
this.
|
|
413
|
-
if (
|
|
644
|
+
this.scheduleLayout();
|
|
645
|
+
if (!slash) this.menuEl.querySelectorAll("button")[m.active]?.focus();
|
|
414
646
|
}
|
|
415
647
|
|
|
416
648
|
pick(i) {
|
|
@@ -419,34 +651,57 @@ class Chrome {
|
|
|
419
651
|
if (!item) return;
|
|
420
652
|
const st = this.view.state;
|
|
421
653
|
const remove = m.slashAt !== null && st.selection.head >= m.slashAt ? { from: m.slashAt - 1, to: st.selection.head } : null;
|
|
654
|
+
// Resolve what the menu chose against the current document: a block a peer
|
|
655
|
+
// removed cancels the command instead of handing it whatever took its place.
|
|
656
|
+
const wanted = m.mode === "insert" ? (m.anchorId ? [m.anchorId] : []) : m.ids;
|
|
657
|
+
const stale = wanted.some((id) => !id || this.idIndex(id) < 0);
|
|
658
|
+
const index = wanted.length ? this.idIndex(wanted[0]) : -1;
|
|
422
659
|
this.closeMenu();
|
|
660
|
+
if (stale && m.mode !== "external") {
|
|
661
|
+
announce(this.view, TARGET_GONE);
|
|
662
|
+
this.view.focus();
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
423
665
|
if (m.mode === "turn") {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
|
|
666
|
+
if (wanted.some((id) => !canTurnInto(st.doc.child(this.idIndex(id)), item))) {
|
|
667
|
+
announce(this.view, CANNOT_CONVERT);
|
|
668
|
+
this.view.focus();
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
const tr = st.tr;
|
|
672
|
+
for (const id of wanted) retype(tr, this.idIndex(id), item);
|
|
673
|
+
this.view.dispatch(asStep(tr).scrollIntoView());
|
|
430
674
|
this.view.focus();
|
|
431
|
-
} else if (m.mode === "actions" || m.mode === "external") {
|
|
432
|
-
item.
|
|
675
|
+
} else if (m.mode === "actions" || m.mode === "more" || m.mode === "external") {
|
|
676
|
+
if (item.blocks) this.selectCaretBlock(wanted);
|
|
677
|
+
item.run(this.view, this, index >= 0 ? index : this.blockIndex());
|
|
433
678
|
if (!this.menu && item.focus !== false) this.view.focus();
|
|
434
679
|
} else {
|
|
435
680
|
if (item.insert === "ai") this.onAiTool(this.view, remove);
|
|
436
|
-
else runItem(this.view, item,
|
|
681
|
+
else runItem(this.view, item, index >= 0 ? index : this.blockIndex(), remove);
|
|
437
682
|
}
|
|
438
683
|
}
|
|
439
684
|
|
|
440
685
|
// Keys while the menu is open; anything else falls through to the editor.
|
|
441
686
|
key(event) {
|
|
442
|
-
if (event
|
|
687
|
+
if (composing(event, this.view)) return false;
|
|
443
688
|
const m = this.menu;
|
|
444
|
-
if (!m)
|
|
689
|
+
if (!m) {
|
|
690
|
+
if (event.key === "Escape" && !event.metaKey && !event.ctrlKey && !event.altKey
|
|
691
|
+
&& this.view.state.selection instanceof BlockSelection) this.leaveToToolbar();
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
if ((event.key === "Backspace" || event.key === "Delete") && m.mode === "actions"
|
|
695
|
+
&& this.view.state.selection instanceof BlockSelection) {
|
|
696
|
+
this.closeMenu();
|
|
697
|
+
this.view.focus();
|
|
698
|
+
deleteBlocks(this.view.state, this.view.dispatch);
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
445
701
|
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
446
702
|
if (m.items.length) {
|
|
447
703
|
m.active = (m.active + (event.key === "ArrowDown" ? 1 : -1) + m.items.length) % m.items.length;
|
|
448
704
|
this.renderMenu();
|
|
449
|
-
if (m.mode === "external") this.menuEl.querySelectorAll("button")[m.active]?.focus();
|
|
450
705
|
}
|
|
451
706
|
return true;
|
|
452
707
|
}
|
|
@@ -454,7 +709,13 @@ class Chrome {
|
|
|
454
709
|
if (m.items.length) this.pick(m.active);
|
|
455
710
|
return true;
|
|
456
711
|
}
|
|
457
|
-
if (event.key === "Escape") {
|
|
712
|
+
if (event.key === "Escape") {
|
|
713
|
+
const target = this.invoker();
|
|
714
|
+
this.closeMenu();
|
|
715
|
+
if (target === this.view.dom) this.view.focus();
|
|
716
|
+
else target?.focus?.();
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
458
719
|
return false;
|
|
459
720
|
}
|
|
460
721
|
|
|
@@ -473,33 +734,78 @@ class Chrome {
|
|
|
473
734
|
this.renderMenu();
|
|
474
735
|
}
|
|
475
736
|
|
|
476
|
-
|
|
737
|
+
scheduleLayout() {
|
|
738
|
+
if (this.frame !== null || this.destroyed) return;
|
|
739
|
+
this.frame = this.win.requestAnimationFrame(() => {
|
|
740
|
+
this.frame = null;
|
|
741
|
+
if (this.destroyed) return;
|
|
742
|
+
const layout = this.measureLayout();
|
|
743
|
+
this.paintLayout(layout);
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// The safe-area inset the browser reports, read through one hidden probe.
|
|
748
|
+
safeBottom() {
|
|
749
|
+
if (!this.probe) {
|
|
750
|
+
this.probe = el("div", "page-safe-probe");
|
|
751
|
+
this.shell.append(this.probe);
|
|
752
|
+
}
|
|
753
|
+
return parseFloat(this.win.getComputedStyle(this.probe).paddingBottom) || 0;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// A menu stays inside the visible viewport, above the bar when there is room.
|
|
757
|
+
menuBox(bar) {
|
|
758
|
+
const v = phoneViewport(this.win);
|
|
759
|
+
const maxHeight = Math.max(0, v.height - PHONE_INSET * 2);
|
|
760
|
+
const width = Math.min(280, Math.max(0, v.width - PHONE_INSET * 2));
|
|
761
|
+
const applied = this.menuEl.style.getPropertyValue("--page-pop-max");
|
|
762
|
+
this.menuEl.style.removeProperty("--page-pop-max");
|
|
763
|
+
const natural = this.menuEl.offsetHeight || 0;
|
|
764
|
+
if (applied) this.menuEl.style.setProperty("--page-pop-max", applied);
|
|
765
|
+
const height = Math.min(natural, maxHeight);
|
|
766
|
+
const left = Math.max(v.left + PHONE_INSET, Math.min(bar.left, v.left + v.width - PHONE_INSET - width));
|
|
767
|
+
let top = bar.top - 6 - height;
|
|
768
|
+
if (top < v.top + PHONE_INSET) top = Math.min(bar.top + bar.height + 6, v.top + v.height - PHONE_INSET - height);
|
|
769
|
+
return { left, top, width, maxHeight };
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Every geometry read the chrome needs, taken in one pass for the frame.
|
|
773
|
+
measureLayout() {
|
|
477
774
|
const view = this.view;
|
|
478
775
|
const st = view.state;
|
|
776
|
+
this.settleHover();
|
|
479
777
|
const shellBox = this.shell.getBoundingClientRect();
|
|
480
778
|
const edBox = view.dom.getBoundingClientRect();
|
|
481
779
|
const i = this.gutterIndex();
|
|
482
780
|
let at = 0;
|
|
483
781
|
for (let k = 0; k < i; k++) at += st.doc.child(k).nodeSize;
|
|
484
782
|
const dom = view.nodeDOM(at);
|
|
783
|
+
const out = { textLeft: edBox.left - shellBox.left, gutter: null, bubble: null, menu: null, bar: null, desk: false };
|
|
485
784
|
if (dom && dom.getBoundingClientRect) {
|
|
486
785
|
const r = dom.getBoundingClientRect();
|
|
487
786
|
const lineH = parseFloat(getComputedStyle(dom).lineHeight) || r.height;
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
787
|
+
out.gutter = { top: r.top - shellBox.top + Math.min(r.height, lineH) / 2, left: r.left - shellBox.left };
|
|
788
|
+
}
|
|
789
|
+
const phone = this.isPhone();
|
|
790
|
+
// The fixed toolbar sits inside the visible viewport, so an on-screen keyboard
|
|
791
|
+
// or a pan cannot carry it off screen.
|
|
792
|
+
if (phone) {
|
|
793
|
+
const v = phoneViewport(this.win);
|
|
794
|
+
const height = this.dock.offsetHeight || PHONE_BAR_HEIGHT;
|
|
795
|
+
out.bar = { ...phoneBarBox(v, this.dock.offsetWidth || PHONE_BAR_WIDTH, height, this.safeBottom()), height };
|
|
796
|
+
} else {
|
|
797
|
+
out.desk = !!this.dock.style.left;
|
|
492
798
|
}
|
|
493
|
-
this.plus.style.setProperty("--page-text-left", `${edBox.left - shellBox.left}px`);
|
|
494
|
-
|
|
495
799
|
const sel = st.selection;
|
|
496
|
-
if (!this.bubble.hidden) {
|
|
800
|
+
if (!this.bubble.hidden && !phone) {
|
|
497
801
|
const a = view.coordsAtPos(sel.from), b = view.coordsAtPos(sel.to, -1);
|
|
498
802
|
const mid = (Math.min(a.left, b.left) + Math.max(a.right, b.right)) / 2;
|
|
499
|
-
|
|
500
|
-
this.bubble.style.setProperty("--page-pop-y", `${Math.min(a.top, b.top) - shellBox.top}px`);
|
|
803
|
+
out.bubble = { x: mid - shellBox.left, y: Math.min(a.top, b.top) - shellBox.top };
|
|
501
804
|
}
|
|
502
|
-
if (this.menu) {
|
|
805
|
+
if (this.menu && phone) {
|
|
806
|
+
const box = this.menuBox(out.bar);
|
|
807
|
+
out.menu = { x: box.left, y: box.top, width: box.width, maxHeight: box.maxHeight };
|
|
808
|
+
} else if (this.menu) {
|
|
503
809
|
const m = this.menu;
|
|
504
810
|
let left, top;
|
|
505
811
|
if (m.slashAt !== null) {
|
|
@@ -507,20 +813,56 @@ class Chrome {
|
|
|
507
813
|
left = c.left;
|
|
508
814
|
top = c.bottom + 6;
|
|
509
815
|
const height = this.menuEl.offsetHeight;
|
|
510
|
-
if (top + height >
|
|
816
|
+
if (top + height > this.win.innerHeight - 8 && c.top - 6 - height >= 8) top = c.top - 6 - height;
|
|
511
817
|
} else {
|
|
512
818
|
const r = m.anchor === this.plus ? (dom?.getBoundingClientRect?.() ?? edBox) : m.anchor.getBoundingClientRect();
|
|
513
819
|
left = r.left;
|
|
514
820
|
top = r.bottom + 6;
|
|
515
821
|
}
|
|
516
|
-
|
|
517
|
-
|
|
822
|
+
out.menu = { x: Math.max(8, Math.min(left - shellBox.left, shellBox.width - 280)), y: top - shellBox.top };
|
|
823
|
+
}
|
|
824
|
+
return out;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// The placement style writes, from the frame's measurements alone.
|
|
828
|
+
paintLayout(layout) {
|
|
829
|
+
if (layout.bar) {
|
|
830
|
+
this.dock.style.left = `${layout.bar.left}px`;
|
|
831
|
+
this.dock.style.top = `${layout.bar.top}px`;
|
|
832
|
+
this.dock.style.width = `${layout.bar.width}px`;
|
|
833
|
+
this.grip.style.setProperty("--page-grip-lift", `${layout.bar.height}px`);
|
|
834
|
+
} else {
|
|
835
|
+
if (layout.desk) for (const side of ["left", "top", "width"]) this.dock.style.removeProperty(side);
|
|
836
|
+
this.grip.style.removeProperty("--page-grip-lift");
|
|
837
|
+
}
|
|
838
|
+
if (layout.gutter) {
|
|
839
|
+
for (const part of [this.plus, this.grip]) {
|
|
840
|
+
part.style.setProperty("--page-plus-top", `${layout.gutter.top}px`);
|
|
841
|
+
part.style.setProperty("--page-plus-left", `${layout.gutter.left}px`);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
this.plus.style.setProperty("--page-text-left", `${layout.textLeft}px`);
|
|
845
|
+
if (layout.bubble) {
|
|
846
|
+
this.bubble.style.setProperty("--page-pop-x", `${layout.bubble.x}px`);
|
|
847
|
+
this.bubble.style.setProperty("--page-pop-y", `${layout.bubble.y}px`);
|
|
848
|
+
}
|
|
849
|
+
if (layout.menu) {
|
|
850
|
+
this.menuEl.style.setProperty("--page-pop-x", `${layout.menu.x}px`);
|
|
851
|
+
this.menuEl.style.setProperty("--page-pop-y", `${layout.menu.y}px`);
|
|
852
|
+
if (layout.menu.width !== undefined) {
|
|
853
|
+
this.menuEl.style.setProperty("--page-pop-width", `${layout.menu.width}px`);
|
|
854
|
+
this.menuEl.style.setProperty("--page-pop-max", `${layout.menu.maxHeight}px`);
|
|
855
|
+
}
|
|
518
856
|
}
|
|
519
857
|
}
|
|
520
858
|
|
|
521
859
|
update(view, prev) {
|
|
522
860
|
this.view = view;
|
|
523
|
-
if (this.menu && this.menu.slashAt === null && prev && prev.doc !== view.state.doc)
|
|
861
|
+
if (this.menu && this.menu.slashAt === null && prev && prev.doc !== view.state.doc) {
|
|
862
|
+
const hadFocus = this.menuEl.contains(document.activeElement);
|
|
863
|
+
this.closeMenu();
|
|
864
|
+
if (hadFocus) view.focus();
|
|
865
|
+
}
|
|
524
866
|
const st = view.state;
|
|
525
867
|
const node = st.doc.child(this.blockIndex());
|
|
526
868
|
this.turn.textContent = `${blockTypeOf(node).label} ▾`;
|
|
@@ -529,19 +871,26 @@ class Chrome {
|
|
|
529
871
|
this.bubble.hidden = !showBubble;
|
|
530
872
|
if (showBubble) for (const b of this.bubble.querySelectorAll("[data-cmd]")) b.setAttribute("aria-pressed", String(toolActive(st, b.dataset.cmd)));
|
|
531
873
|
this.grip.classList.toggle("is-on", st.selection instanceof BlockSelection);
|
|
874
|
+
if (!this.inRoving(this.roving)) this.setRoving(this.dockControls()[0]);
|
|
532
875
|
this.slashQuery();
|
|
533
|
-
this.
|
|
876
|
+
this.scheduleLayout();
|
|
534
877
|
}
|
|
535
878
|
|
|
536
879
|
destroy() {
|
|
880
|
+
this.destroyed = true;
|
|
881
|
+
if (this.frame !== null) this.win.cancelAnimationFrame(this.frame);
|
|
882
|
+
this.frame = null;
|
|
537
883
|
clearTimeout(this.movedTimer);
|
|
538
884
|
this.drag.destroy();
|
|
539
885
|
this.shell.removeEventListener("mousemove", this.onMove);
|
|
540
886
|
this.shell.removeEventListener("mouseleave", this.onLeave);
|
|
541
887
|
this.shell.removeEventListener("keydown", this.onKey);
|
|
542
|
-
|
|
888
|
+
this.win.removeEventListener("resize", this.onResize);
|
|
889
|
+
this.win.removeEventListener("scroll", this.onResize, true);
|
|
890
|
+
this.win.visualViewport?.removeEventListener("resize", this.onResize);
|
|
891
|
+
this.win.visualViewport?.removeEventListener("scroll", this.onResize);
|
|
543
892
|
document.removeEventListener("mousedown", this.onDocDown, true);
|
|
544
|
-
for (const part of [this.dock, this.plus, this.grip, this.bubble, this.scrim, this.menuEl]) part
|
|
893
|
+
for (const part of [this.dock, this.plus, this.grip, this.bubble, this.scrim, this.menuEl, this.probe]) part?.remove();
|
|
545
894
|
}
|
|
546
895
|
}
|
|
547
896
|
|
|
@@ -571,12 +920,13 @@ export function chromePlugin({ onAiTool = null } = {}) {
|
|
|
571
920
|
const $from = view.state.doc.resolve(from);
|
|
572
921
|
if ($from.depth !== 1 || !$from.parent.isTextblock || $from.parent.type.spec.code) return inserted;
|
|
573
922
|
if ($from.parentOffset > 0 && !/\s/.test(view.state.doc.textBetween(from - 1, from, "", "\n"))) return inserted;
|
|
574
|
-
if (!inserted) view.dispatch(view.state.tr.insertText("/", from, to));
|
|
575
|
-
|
|
923
|
+
if (!inserted) view.dispatch(view.state.tr.insertText("/", from, to).setMeta(HISTORY_INTENT, typingIntent(view.state, from, to, "/")));
|
|
924
|
+
const at = Math.min(view.state.doc.resolve(from).index(0), view.state.doc.childCount - 1);
|
|
925
|
+
chrome.openMenu("insert", chrome.plus, from + 1, null, view.state.doc.child(at).attrs.id);
|
|
576
926
|
return true;
|
|
577
927
|
},
|
|
578
928
|
handleDOMEvents: {
|
|
579
|
-
keydown(view, event) { return event
|
|
929
|
+
keydown(view, event) { return composing(event, view); },
|
|
580
930
|
blur(view) {
|
|
581
931
|
if (chrome) chrome.bubble.hidden = true;
|
|
582
932
|
return false;
|