@panphora/clayjs 1.15.2 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/THIRD-PARTY-NOTICES.md +16 -0
- package/compat/highlight-11.9.0.min.js +1213 -0
- package/compat/highlight-LICENSE +29 -0
- package/compat/journal-assets.lock.json +22 -0
- package/compat/overtype-2.6.1.min.js +116 -0
- package/compat/overtype-LICENSE +21 -0
- package/dist/all.js +90 -0
- package/dist/clay-dom.js +84 -0
- package/dist/clay-events.js +103 -0
- package/dist/clay-internals.js +85 -0
- package/dist/clay-options.js +93 -0
- package/dist/clay-router.js +98 -0
- package/dist/clay-ui.js +95 -0
- package/dist/clay-utils.js +84 -0
- package/dist/clay.js +146 -0
- package/dist/clay.standalone.js +84291 -57611
- package/dist/m/attrs/add-from-T5LJXFFI.js +1 -0
- package/dist/m/attrs/onaftersave-OKSWEJQF.js +1 -0
- package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
- package/dist/m/attrs/save-freeze-MYNVXREN.js +1 -0
- package/dist/m/c-2AZDZWCK.js +1 -0
- package/dist/m/c-2SPVQSHC.js +1 -0
- package/dist/m/c-2YYDMLNZ.js +1 -0
- package/dist/m/c-336SGU5W.js +1 -0
- package/dist/m/c-33JJ6CBX.js +2 -0
- package/dist/m/c-3GJ4SDE2.js +1 -0
- package/dist/m/c-3H6BZDNJ.js +6 -0
- package/dist/m/c-3OWDMNJR.js +2 -0
- package/dist/m/c-3RUU4TPX.js +91 -0
- package/dist/m/c-3XIRD4NU.js +1 -0
- package/dist/m/c-46XYM55L.js +1 -0
- package/dist/m/c-4DFEJ2PO.js +3 -0
- package/dist/m/c-4JZDZFF6.js +1 -0
- package/dist/m/c-4L3BTFRK.js +1 -0
- package/dist/m/c-4Q2XTQ2N.js +2 -0
- package/dist/m/c-4WKFKEKF.js +1 -0
- package/dist/m/c-56QRZTZH.js +1 -0
- package/dist/m/c-5J4E7O4Y.js +4 -0
- package/dist/m/c-5LKDWQTL.js +19 -0
- package/dist/m/c-5QWQ7A5L.js +1 -0
- package/dist/m/c-5RJFTLSF.js +1 -0
- package/dist/m/c-5V3MOVJT.js +1 -0
- package/dist/m/c-5VYOWDTI.js +1 -0
- package/dist/m/c-5YIHJ3TJ.js +1 -0
- package/dist/m/c-6AZOGAPE.js +1 -0
- package/dist/m/c-6CRUUXOX.js +5 -0
- package/dist/m/c-6HMB24EV.js +1 -0
- package/dist/m/c-6KEVEQ7L.js +1 -0
- package/dist/m/c-6NC5I2TV.js +1 -0
- package/dist/m/c-74MGYWZF.js +1 -0
- package/dist/m/c-7DJDKPGL.js +1 -0
- package/dist/m/c-7OLLJPWP.js +1 -0
- package/dist/m/c-7SWMYKAI.js +1 -0
- package/dist/m/c-A3E3NIMC.js +1 -0
- package/dist/m/c-A7LS5UZW.js +1 -0
- package/dist/m/c-AMSNPOCX.js +1 -0
- package/dist/m/c-AQLFEATB.js +1 -0
- package/dist/m/c-B3JM5ZXC.js +1 -0
- package/dist/m/c-BBVGTQG7.js +1 -0
- package/dist/m/c-BPF2NSPV.js +1 -0
- package/dist/m/c-BQ6LOBEE.js +1 -0
- package/dist/m/c-BTDI4Y23.js +1 -0
- package/dist/m/c-BWFVSBDF.js +1 -0
- package/dist/m/c-BYTKL6UX.js +1 -0
- package/dist/m/c-CFHW5AMY.js +6 -0
- package/dist/m/c-CJ3A7ZKE.js +1 -0
- package/dist/m/c-CQHPGKX2.js +1 -0
- package/dist/m/c-CYWLPPUS.js +1 -0
- package/dist/m/c-CZUSOB7V.js +1 -0
- package/dist/m/c-DCFSEJZG.js +1 -0
- package/dist/m/c-DCXOXBF3.js +1 -0
- package/dist/m/c-DFS4XZJV.js +2 -0
- package/dist/m/c-DNENWY4O.js +1 -0
- package/dist/m/c-DNZ5IFM6.js +1 -0
- package/dist/m/c-E4LIR5X5.js +1 -0
- package/dist/m/c-EJTGYSLN.js +1 -0
- package/dist/m/c-EVCQ3BKA.js +1 -0
- package/dist/m/c-EZU56NQV.js +34 -0
- package/dist/m/c-FDNHHZY5.js +1 -0
- package/dist/m/c-FILLTJA7.js +1 -0
- package/dist/m/c-FRLHSY7E.js +1 -0
- package/dist/m/c-FROJUZMV.js +1 -0
- package/dist/m/c-G2JPKCTP.js +1 -0
- package/dist/m/c-GB6TU7WV.js +1 -0
- package/dist/m/c-GFJJEBTU.js +1 -0
- package/dist/m/c-GJAEGGXY.js +1 -0
- package/dist/m/c-GOWBDM6J.js +1 -0
- package/dist/m/c-GQ6F6IDK.js +1 -0
- package/dist/m/c-GSFIGKOP.js +1 -0
- package/dist/m/c-GY422N3Q.js +1 -0
- package/dist/m/c-H4LG442Q.js +1 -0
- package/dist/m/c-HA6INT6J.js +1 -0
- package/dist/m/c-HAWFNBYG.js +1 -0
- package/dist/m/c-HDDEEL77.js +1 -0
- package/dist/m/c-HFKUVACH.js +1 -0
- package/dist/m/c-HHDFCNHV.js +1 -0
- package/dist/m/c-HPURYVZS.js +2 -0
- package/dist/m/c-I2ZATQCD.js +2 -0
- package/dist/m/c-IEM4NTCS.js +1 -0
- package/dist/m/c-IJUK2GPW.js +1 -0
- package/dist/m/c-IW6GGOU2.js +1 -0
- package/dist/m/c-JAX2667S.js +6 -0
- package/dist/m/c-JDCX26UP.js +25 -0
- package/dist/m/c-JRKY44DK.js +1 -0
- package/dist/m/c-JU3NK25H.js +1 -0
- package/dist/m/c-KAU6E43D.js +1 -0
- package/dist/m/c-KBS3UV6B.js +1 -0
- package/dist/m/c-KGVGROZT.js +1 -0
- package/dist/m/c-KI7M4AY3.js +1 -0
- package/dist/m/c-KIPQJX4B.js +1 -0
- package/dist/m/c-KQ6TYJAP.js +135 -0
- package/dist/m/c-KU3ZFADW.js +1 -0
- package/dist/m/c-L4WQSC27.js +1 -0
- package/dist/m/c-LFPHCXY3.js +1 -0
- package/dist/m/c-LRPUO2MX.js +1 -0
- package/dist/m/c-LWLBHLVC.js +3 -0
- package/dist/m/c-LZJPNR2W.js +1 -0
- package/dist/m/c-M2L47S2X.js +7 -0
- package/dist/m/c-MFP2ZWD4.js +1 -0
- package/dist/m/c-MKSQ4Y57.js +1 -0
- package/dist/m/c-MTQHWQC3.js +1 -0
- package/dist/m/c-N326R2JD.js +1 -0
- package/dist/m/c-N7JPUPGC.js +1 -0
- package/dist/m/c-NRPJEVJS.js +1 -0
- package/dist/m/c-NUFFJVVJ.js +1 -0
- package/dist/m/c-NUHP2XDB.js +1 -0
- package/dist/m/c-NUHSPT5P.js +1 -0
- package/dist/m/c-O72HVYSA.js +1 -0
- package/dist/m/c-OCSZ4QGN.js +1 -0
- package/dist/m/c-OIBUTDXI.js +1 -0
- package/dist/m/c-OIDRZKAF.js +1 -0
- package/dist/m/c-OW5QIGG3.js +1 -0
- package/dist/m/c-P7KNDG34.js +1 -0
- package/dist/m/c-PGRCK73F.js +1 -0
- package/dist/m/c-PQ5LK6QE.js +993 -0
- package/dist/m/c-PS5ARVWR.js +9 -0
- package/dist/m/c-PZ4XEPUP.js +2 -0
- package/dist/m/c-Q622NS2Y.js +1 -0
- package/dist/m/c-Q7NQD65V.js +1 -0
- package/dist/m/c-QH63LQOS.js +1 -0
- package/dist/m/c-QHIWXHRO.js +1 -0
- package/dist/m/c-QHWJ5ZIJ.js +1 -0
- package/dist/m/c-QICP2YR5.js +1 -0
- package/dist/m/c-QJ4OM3G6.js +1 -0
- package/dist/m/c-QNKREOL6.js +1 -0
- package/dist/m/c-R3L7ZJGB.js +2 -0
- package/dist/m/c-R7IWVINQ.js +1 -0
- package/dist/m/c-RM7J7BV2.js +1 -0
- package/dist/m/c-RSMOODDU.js +1 -0
- package/dist/m/c-RVMWGXEJ.js +1 -0
- package/dist/m/c-RYUXUUBX.js +1 -0
- package/dist/m/c-S4ZEH46B.js +9 -0
- package/dist/m/c-S7H2DRCB.js +1 -0
- package/dist/m/c-S7LK4KUE.js +1 -0
- package/dist/m/c-SDAXDM2D.js +1 -0
- package/dist/m/c-SGIA4IEQ.js +188 -0
- package/dist/m/c-SMBZG5X4.js +1 -0
- package/dist/m/c-SMGU7RT3.js +235 -0
- package/dist/m/c-SPIORRYS.js +1 -0
- package/dist/m/c-SWLRT66R.js +1 -0
- package/dist/m/c-SZGYW5HT.js +1 -0
- package/dist/m/c-TCCA5BZN.js +2 -0
- package/dist/m/c-TCJTVAFN.js +1 -0
- package/dist/m/c-TJ6WOJL3.js +67 -0
- package/dist/m/c-TQEMVJCS.js +1 -0
- package/dist/m/c-TRS6TM6O.js +1 -0
- package/dist/m/c-TYLXX4NF.js +21 -0
- package/dist/m/c-U62OB4BT.js +1 -0
- package/dist/m/c-U6YWUAR3.js +8 -0
- package/dist/m/c-UDCSU5G5.js +2 -0
- package/dist/m/c-UDUUJPFZ.js +1 -0
- package/dist/m/c-UGHH4MB3.js +1 -0
- package/dist/m/c-UL2YUY52.js +1 -0
- package/dist/m/c-VAK7SG37.js +1 -0
- package/dist/m/c-VDSS4K3Z.js +1 -0
- package/dist/m/c-VE6ZMYMC.js +1 -0
- package/dist/m/c-VG5XIN4T.js +1 -0
- package/dist/m/c-VHM5XQHK.js +1 -0
- package/dist/m/c-VHNSBD2M.js +1 -0
- package/dist/m/c-VI5RMSVY.js +1 -0
- package/dist/m/c-VKEDWYDF.js +1 -0
- package/dist/m/c-VRZN56XY.js +1 -0
- package/dist/m/c-W5MCHEGA.js +1 -0
- package/dist/m/c-WDC5GVKU.js +1 -0
- package/dist/m/c-WN5AY3OZ.js +1 -0
- package/dist/m/c-WNYBLIOZ.js +1 -0
- package/dist/m/c-WRBKEJD4.js +1 -0
- package/dist/m/c-WWXIDH4O.js +28 -0
- package/dist/m/c-WYFDLITL.js +1 -0
- package/dist/m/c-XCNTK76U.js +1 -0
- package/dist/m/c-XMS6DPBR.js +1 -0
- package/dist/m/c-XN6MBKI6.js +67 -0
- package/dist/m/c-XPELB6E5.js +1 -0
- package/dist/m/c-XQXZAO7N.js +1 -0
- package/dist/m/c-Y5HBBIQ4.js +1 -0
- package/dist/m/c-ZE46QDIH.js +1 -0
- package/dist/m/c-ZUCOKOHT.js +1 -0
- package/dist/m/c-ZVPVXTJF.js +4 -0
- package/dist/m/c-ZVRKXKM7.js +1 -0
- package/dist/m/c-ZWIOJGMT.js +1 -0
- package/dist/m/core/admin-attrs-QHOKPJ7W.js +1 -0
- package/dist/m/core/autosave-HRYGUWV6.js +1 -0
- package/dist/m/core/conflict-notice-6MROJN2L.js +1 -0
- package/dist/m/core/draft-mode-WSQAT67S.js +1 -0
- package/dist/m/core/host-permission-FNXVEFNE.js +1 -0
- package/dist/m/core/is-edit-mode-KTWA3O5N.js +1 -0
- package/dist/m/core/persist-JFYRHMRJ.js +3 -0
- package/dist/m/core/recovery-FLTCC2OC.js +1 -0
- package/dist/m/core/save-HQZ4S667.js +1 -0
- package/dist/m/core/save-core-KGQWFUNH.js +1 -0
- package/dist/m/core/snapshot-EGQZNT45.js +1 -0
- package/dist/m/core/unsaved-warning-IDJLD66H.js +1 -0
- package/dist/m/dom/all-APS5ZSKP.js +1 -0
- package/dist/m/dom/dom-helpers-L7OSR6S4.js +1 -0
- package/dist/m/events/index-RN2JNGBV.js +1 -0
- package/dist/m/internals/index-PVXGPUDA.js +1 -0
- package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
- package/dist/m/lib/region-policy-SB2AWVG5.js +1 -0
- package/dist/m/loader-S4I7GKXR.js +1 -0
- package/dist/m/manifest-4C419D67.js +1 -0
- package/dist/m/manifest-edit-B077F288.js +1 -0
- package/dist/m/manifest.json +3988 -0
- package/dist/m/options/options-EJMS3K24.js +1 -0
- package/dist/m/page/editor-NNJA4OKD.js +202 -0
- package/dist/m/plugins/ai-edit-QN4PMMNL.js +6 -0
- package/dist/m/plugins/bind-text-NX46N5UV.js +1 -0
- package/dist/m/plugins/cms-HP3LSTPN.js +2293 -0
- package/dist/m/plugins/demo-RM5IR6AO.js +1 -0
- package/dist/m/plugins/derive-UAAA4V4S.js +1 -0
- package/dist/m/plugins/indicator-URDBBOQO.js +1 -0
- package/dist/m/plugins/live-HFWD4OIH.js +1 -0
- package/dist/m/plugins/menu-3HIEQSI6.js +138 -0
- package/dist/m/plugins/message-LQ6EDH4V.js +1 -0
- package/dist/m/plugins/page-JOKB6XPR.js +2 -0
- package/dist/m/plugins/page-view-NXOJHNW6.js +1 -0
- package/dist/m/plugins/people-SGPJKANK.js +1 -0
- package/dist/m/plugins/quickcrop-QNY3PKLD.js +26 -0
- package/dist/m/plugins/quickcrop-QNY3PKLD.js.LEGAL.txt +7 -0
- package/dist/m/plugins/richclay-NRQDSWX6.js +517 -0
- package/dist/m/plugins/richclay-NRQDSWX6.js.LEGAL.txt +1 -0
- package/dist/m/plugins/sortable-GWT4PWDV.js +1 -0
- package/dist/m/plugins/source-4ZRSAIYM.js +1 -0
- package/dist/m/plugins/undo-VDA5P7FH.js +1 -0
- package/dist/m/plugins/unsent-HJFLQJ2O.js +1 -0
- package/dist/m/plugins/upgrade-W7FG5VFG.js +1 -0
- package/dist/m/plugins/upload-WLYKK6TL.js +1 -0
- package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +21 -0
- package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +1 -0
- package/dist/m/plugins/wire-4N6GYAF6.js +1 -0
- package/dist/m/reader-core-DXYCURFF.js +68 -0
- package/dist/m/route/route-KOCZSEGN.js +1 -0
- package/dist/m/sync/live-sync-J7VMYTKS.js +1 -0
- package/dist/m/ui/index-3JLVI27O.js +1 -0
- package/dist/m/utils/index-4TJCVLT7.js +1 -0
- package/dist/m/vendor/Sortable.vendor-A755ITLN.js +3 -0
- package/dist/m/vendor/Sortable.vendor-A755ITLN.js.LEGAL.txt +1 -0
- package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +6 -0
- package/dist/offline-manifest.json +4034 -0
- package/dist/page/themes/light-1.css +430 -0
- package/dist/server/core.cjs +23748 -22855
- package/dist/server/offline-runtime.cjs +4954 -0
- package/dist/server/offline-runtime.mjs +4923 -0
- package/dist/server/rooms.mjs +9 -0
- package/docs/guide.md +1078 -669
- package/entries/all.js +2 -0
- package/entries/clay-data.js +1 -1
- package/entries/clay-dom.js +2 -0
- package/entries/clay-events.js +2 -0
- package/entries/clay-internals.js +2 -0
- package/entries/clay-options.js +2 -0
- package/entries/clay-router.js +2 -0
- package/entries/clay-ui.js +2 -0
- package/entries/clay-utils.js +2 -0
- package/entries/clay.js +2 -0
- package/entries/sap.js +6 -6
- package/fonts/ibm-plex-mono-LICENSE +93 -0
- package/fonts/ibm-plex-mono-latin-300-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-400-italic.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-LICENSE +93 -0
- package/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- package/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- package/fonts/ibm-plex.css +71 -0
- package/fonts/inter-LICENSE +93 -0
- package/fonts/inter-cyrillic-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-cyrillic-wght-normal.woff2 +0 -0
- package/fonts/inter-greek-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-greek-wght-normal.woff2 +0 -0
- package/fonts/inter-latin-ext-wght-normal.woff2 +0 -0
- package/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/fonts/inter-vietnamese-wght-normal.woff2 +0 -0
- package/fonts/inter.css +62 -0
- package/fonts/newsreader-LICENSE +93 -0
- package/fonts/newsreader-latin-ext-opsz-normal.woff2 +0 -0
- package/fonts/newsreader-latin-opsz-normal.woff2 +0 -0
- package/fonts/newsreader-vietnamese-opsz-normal.woff2 +0 -0
- package/fonts/newsreader.css +26 -0
- package/fonts/work-sans-LICENSE +93 -0
- package/fonts/work-sans-latin-wght-normal.woff2 +0 -0
- package/fonts/work-sans.css +8 -0
- package/package.json +20 -3
- package/packed-contract.json +1 -0
- package/src/attrs/add-from.js +27 -4
- package/src/attrs/save-freeze.js +7 -52
- package/src/core/admin-attrs.js +119 -12
- package/src/core/admin-contenteditable.js +12 -10
- package/src/core/admin-inputs.js +14 -15
- package/src/core/admin-onclick.js +2 -0
- package/src/core/autosave.js +74 -21
- package/src/core/boot-phase.js +2 -1
- package/src/core/conflict-download.js +19 -23
- package/src/core/conflict-notice-ui.js +1173 -0
- package/src/core/conflict-notice.js +140 -697
- package/src/core/download.js +32 -0
- package/src/core/edit-mode.js +147 -14
- package/src/core/etag.js +30 -12
- package/src/core/host-meta.js +90 -8
- package/src/core/host-permission.js +29 -0
- package/src/core/is-edit-mode.js +145 -12
- package/src/core/mode-key.js +125 -0
- package/src/core/mode-lifecycle.js +544 -0
- package/src/core/mode-receipt.js +30 -0
- package/src/core/mode-refusal.js +13 -0
- package/src/core/mode-resources.js +52 -0
- package/src/core/mode-support.js +82 -0
- package/src/core/mode-switch.js +259 -0
- package/src/core/mode-transition.js +112 -0
- package/src/core/page-data-write.js +305 -0
- package/src/core/page-data.js +26 -227
- package/src/core/page-format-wire.js +4 -0
- package/src/core/page-format.js +58 -0
- package/src/core/persist.js +37 -9
- package/src/core/preserve-authored.js +114 -0
- package/src/core/recovery.js +772 -0
- package/src/core/runtime-url.js +105 -0
- package/src/core/save-core.js +113 -12
- package/src/core/save-replace.js +72 -0
- package/src/core/save-size-notice.js +134 -0
- package/src/core/save-size.js +140 -0
- package/src/core/save.js +806 -119
- package/src/core/snapshot.js +159 -11
- package/src/core/source-map.js +157 -9
- package/src/core/unsaved-warning.js +14 -4
- package/src/core/upgrade-run.js +408 -0
- package/src/core/version.js +18 -0
- package/src/lib/assets.js +30 -0
- package/src/lib/autosave-debug-gate.js +50 -0
- package/src/lib/autosave-scheduler.js +140 -0
- package/src/lib/boot-imports.js +8 -0
- package/src/lib/derived.js +3 -1
- package/src/lib/dirty-gate.js +15 -0
- package/src/lib/doc-storage.js +33 -0
- package/src/lib/effect.js +9 -4
- package/src/lib/mutation.js +4 -2
- package/src/lib/offline-runtime.js +226 -0
- package/src/lib/preload.js +48 -0
- package/src/lib/root-attrs.js +1 -1
- package/src/lib/trash.js +18 -0
- package/src/lib/undo/capture-move.js +152 -0
- package/src/live/local-channel.js +61 -0
- package/src/live/local-lane.js +60 -9
- package/src/live/server-lane.js +6 -3
- package/src/loader-logic.js +76 -15
- package/src/loader.js +218 -30
- package/src/page/adopt-boot.js +76 -22
- package/src/page/adopt.js +25 -4
- package/src/page/awareness.js +230 -0
- package/src/page/blocks.js +113 -6
- package/src/page/blocksel.js +20 -4
- package/src/page/board-face.js +13 -0
- package/src/page/board-panel.js +5 -17
- package/src/page/board-style.js +2 -13
- package/src/page/board-view.js +1 -11
- package/src/page/board-views.js +3 -0
- package/src/page/board.js +2 -2
- package/src/page/boot-pack.js +26 -0
- package/src/page/boot.js +237 -0
- package/src/page/chart-tables.js +11 -0
- package/src/page/chart.js +6 -10
- package/src/page/checklist.js +1 -1
- package/src/page/checkpoint-proof.js +289 -0
- package/src/page/chrome.js +431 -63
- package/src/page/csv-import.js +17 -2
- package/src/page/document-title-dom.js +52 -0
- package/src/page/document-title-runtime.js +90 -0
- package/src/page/document-title.js +12 -0
- package/src/page/drag.js +48 -13
- package/src/page/drop.js +2 -1
- package/src/page/editor.js +399 -90
- package/src/page/emoji-data.js +162 -0
- package/src/page/feature-loader.js +72 -0
- package/src/page/features/board.js +21 -0
- package/src/page/features/chart.js +11 -0
- package/src/page/features/code.js +13 -0
- package/src/page/features/dates.js +180 -0
- package/src/page/features/emoji.js +187 -0
- package/src/page/features/media.js +290 -0
- package/src/page/features/mentions.js +188 -0
- package/src/page/features/registry.js +28 -0
- package/src/page/features/table.js +383 -0
- package/src/page/{tool-view.js → features/tool.js} +33 -2
- package/src/page/format-codec.js +215 -0
- package/src/page/format-command.js +9 -0
- package/src/page/format-gate.js +179 -0
- package/src/page/format-host.js +54 -0
- package/src/page/format-level.js +157 -0
- package/src/page/format-native-commands.js +142 -0
- package/src/page/format-native.js +42 -0
- package/src/page/highlight-slot.js +23 -0
- package/src/page/highlight.js +30 -16
- package/src/page/history-intent.js +78 -0
- package/src/page/inline.js +45 -28
- package/src/page/items.js +67 -14
- package/src/page/link.js +1 -0
- package/src/page/markdown.js +112 -3
- package/src/page/media-views.js +3 -0
- package/src/page/media.js +33 -245
- package/src/page/mention-picker.js +99 -0
- package/src/page/merge.js +1 -1
- package/src/page/outline-cache.js +11 -0
- package/src/page/placeholder.js +27 -7
- package/src/page/projection.js +240 -51
- package/src/page/reader-patch.js +306 -0
- package/src/page/recover.js +1 -1
- package/src/page/rich/page-tool.js +1 -4
- package/src/page/runtime-owner.js +27 -0
- package/src/page/save-projection.js +46 -4
- package/src/page/save-states.js +1 -1
- package/src/page/schema.js +47 -0
- package/src/page/table-rect-key.js +3 -0
- package/src/page/table-select.js +4 -3
- package/src/page/table-style.js +1 -1
- package/src/page/table-views.js +3 -0
- package/src/page/table.js +8 -293
- package/src/page/themes/content.css +25 -1
- package/src/page/themes/light-1.css +163 -2
- package/src/page/themes/light-1.source.css +269 -0
- package/src/page/tool-capability.js +3 -0
- package/src/page/tool-views.js +3 -0
- package/src/page/view-hooks.js +32 -0
- package/src/page/view.js +390 -107
- package/src/page/ybind/awareness.js +172 -0
- package/src/page/ybind/binding.js +40 -5
- package/src/page/ybind/bridge.js +50 -47
- package/src/page/ybind/decode.js +52 -19
- package/src/page/ybind/doc.js +27 -2
- package/src/page/ybind/group.js +12 -3
- package/src/page/ybind/index.js +2 -0
- package/src/page/ybind/lineage.js +71 -0
- package/src/page/ybind/marks.js +26 -1
- package/src/page/ybind/migrate.js +48 -13
- package/src/page/ybind/moves.js +32 -21
- package/src/page/ybind/positions.js +50 -0
- package/src/page/ybind/provider.js +97 -24
- package/src/page/ybind/recovery-proof.js +191 -0
- package/src/page/ybind/recovery-replay.js +488 -0
- package/src/page/ybind/tokens.js +15 -6
- package/src/page/ybind/walk.js +10 -3
- package/src/page/ybind/writer.js +48 -4
- package/src/plugins/ai-edit.js +167 -32
- package/src/plugins/bind-text.js +218 -0
- package/src/plugins/cms.js +93 -0
- package/src/plugins/indicator.js +57 -5
- package/src/plugins/live.js +319 -35
- package/src/plugins/menu/claygoo.svg.js +1 -0
- package/src/plugins/menu/seo-head.js +323 -0
- package/src/plugins/menu/seo.js +849 -0
- package/src/plugins/menu.js +425 -0
- package/src/plugins/page-view.js +131 -0
- package/src/plugins/page.js +1131 -270
- package/src/plugins/people.js +158 -31
- package/src/plugins/richclay.js +119 -9
- package/src/plugins/sortable.js +370 -60
- package/src/plugins/source.js +189 -366
- package/src/plugins/undo.js +289 -19
- package/src/plugins/unsent.js +132 -0
- package/src/plugins/upgrade-run.js +1 -0
- package/src/plugins/upgrade.js +210 -0
- package/src/plugins/upload-task.js +571 -0
- package/src/plugins/upload-ui.js +265 -0
- package/src/plugins/upload.js +34 -153
- package/src/plugins/visitor-drafts/inert.js +32 -0
- package/src/plugins/visitor-drafts/intro.js +2 -1
- package/src/plugins/visitor-drafts/owner-chip.js +3 -0
- package/src/plugins/visitor-drafts/prompt.js +2 -2
- package/src/plugins/visitor-drafts-owner.js +85 -0
- package/src/plugins/visitor-drafts.js +31 -42
- package/src/plugins/wire.js +82 -1
- package/src/reader-core.js +26 -0
- package/src/recovery/conflicts.js +287 -0
- package/src/recovery/identity.js +154 -0
- package/src/recovery/store.js +792 -0
- package/src/recovery/undo.js +257 -0
- package/src/server/checkpoint/index.js +68 -4
- package/src/server/offline-runtime/index.js +214 -0
- package/src/server/rooms/index.js +2 -1
- package/src/server/rooms/live-data-rooms.js +2 -2
- package/src/server/rooms/page-admission.js +33 -0
- package/src/server/rooms/page-lineage.js +56 -0
- package/src/server/rooms/page-rebase.js +11 -5
- package/src/server/rooms/page-state.js +2 -0
- package/src/server/rooms/page-write-admission.js +9 -3
- package/src/source/session.js +705 -0
- package/src/sync/conflict-revert.js +5 -4
- package/src/sync/conflicts-edit.js +119 -0
- package/src/sync/conflicts-facade.js +49 -0
- package/src/sync/conflicts.js +99 -1
- package/src/sync/editing-marker.js +37 -6
- package/src/sync/editing-sender.js +8 -1
- package/src/sync/live-sync-edit.js +63 -0
- package/src/sync/live-sync.js +364 -119
- package/src/sync/local-presence.js +187 -0
- package/src/sync/merge-declarations.js +10 -0
- package/src/sync/merge-engine.js +12 -0
- package/src/sync/presence-store.js +412 -0
- package/src/sync/presence-target.js +243 -0
- package/src/sync/presence.js +72 -41
- package/src/sync/saved-handoff.js +200 -0
- package/src/sync/saved-stream.js +544 -0
- package/src/sync/version-guard.js +92 -0
- package/src/sync/wire-profile.js +81 -0
- package/src/ui/bevel-controls.js +2 -8
- package/src/ui/page-scheme.js +7 -0
- package/src/ui/skins/cms.js +1 -1
- package/src/ui/theme-config.js +150 -0
- package/src/ui/theme.js +6 -138
- package/src/ui/toast-lazy.js +11 -0
- package/src/vendor/LICENSES-optional.md +253 -0
- package/src/vendor/highlight.vendor.js +3 -0
- package/src/vendor/hljs-bash.vendor.js +2 -0
- package/src/vendor/hljs-css.vendor.js +2 -0
- package/src/vendor/hljs-javascript.vendor.js +2 -0
- package/src/vendor/hljs-json.vendor.js +2 -0
- package/src/vendor/hljs-python.vendor.js +2 -0
- package/src/vendor/hljs-xml.vendor.js +2 -0
- package/src/vendor/hyper-html-api-data.vendor.js +1 -1
- package/src/vendor/hyper-html-api-upgrade.vendor.js +5 -0
- package/src/vendor/hyper-morph-identity.vendor.js +2 -0
- package/src/vendor/hyper-morph-json.vendor.js +3 -0
- package/src/vendor/hyper-morph.vendor.js +6 -6
- package/src/vendor/hyper-undo.vendor.js +1 -1
- package/src/vendor/hypercms.vendor.js +169 -84
- package/src/vendor/marked.vendor.js +68 -0
- package/src/vendor/overtype.vendor.js +994 -0
- package/src/vendor/richclay.vendor.js +17 -17
- package/src/vendor/sap-core.vendor.js +5 -5
- package/src/vendor/sap-ownership.vendor.js +11 -7
package/src/page/chrome.js
CHANGED
|
@@ -5,15 +5,18 @@
|
|
|
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";
|
|
15
|
-
import { insertChartAfterTable } from "./chart.js";
|
|
14
|
+
import { BLOCK_TYPES, MENU_ITEMS, blockTypeOf, turnInto, insertAfter, rankItems, runItem, retype, canTurnInto, insertItemsFor, loadChartPart } from "./items.js";
|
|
15
|
+
import toast from "../ui/toast-lazy.js";
|
|
16
16
|
import { pickCsv } from "./csv-import.js";
|
|
17
|
+
import { HISTORY_INTENT, typingIntent } from "./history-intent.js";
|
|
18
|
+
import { composing } from "./markdown.js";
|
|
19
|
+
import { announce } from "./announce.js";
|
|
17
20
|
|
|
18
21
|
export { BLOCK_TYPES, blockTypeOf, turnInto, insertAfter } from "./items.js";
|
|
19
22
|
|
|
@@ -24,7 +27,7 @@ const N = schema.nodes;
|
|
|
24
27
|
const MOD = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform) ? "⌘" : "Ctrl+";
|
|
25
28
|
|
|
26
29
|
const selectedRoots = (st) => {
|
|
27
|
-
const ents =
|
|
30
|
+
const ents = outlineOf(st.doc);
|
|
28
31
|
const { lo, hi } = st.selection.span(st.doc);
|
|
29
32
|
return ents.slice(lo, hi + 1).filter((e) => e.up === ents[lo].up);
|
|
30
33
|
};
|
|
@@ -32,6 +35,30 @@ const selectedRoots = (st) => {
|
|
|
32
35
|
const imageId = (view, index) => view.state.doc.child(index).attrs.id;
|
|
33
36
|
const blockStart = (doc, index) => { let at = 0; for (let i = 0; i < index; i++) at += doc.child(i).nodeSize; return at; };
|
|
34
37
|
|
|
38
|
+
// The editor attributes a menu lends it while it is open. A slash menu puts them
|
|
39
|
+
// back as it found them; every other menu clears them.
|
|
40
|
+
const POPUP_ATTRS = ["aria-haspopup", "aria-controls", "aria-activedescendant"];
|
|
41
|
+
const TARGET_GONE = "That block is no longer available.";
|
|
42
|
+
const CANNOT_CONVERT = "This block holds content from a newer version, so it can't be converted here.";
|
|
43
|
+
|
|
44
|
+
// The part of the window the person can see: the visual viewport when the
|
|
45
|
+
// browser has one, so a keyboard or a pinch does not hide the bar.
|
|
46
|
+
export function phoneViewport(win) {
|
|
47
|
+
const v = win.visualViewport;
|
|
48
|
+
return v ? { left: v.offsetLeft, top: v.offsetTop, width: v.width, height: v.height }
|
|
49
|
+
: { left: 0, top: 0, width: win.innerWidth, height: win.innerHeight };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function phoneBarBox(v, width, height, safeBottom = 0) {
|
|
53
|
+
const inset = PHONE_INSET;
|
|
54
|
+
const w = Math.min(width, Math.max(0, v.width - inset * 2));
|
|
55
|
+
return {
|
|
56
|
+
left: v.left + (v.width - w) / 2,
|
|
57
|
+
top: Math.max(v.top + inset, v.top + v.height - height - safeBottom - inset),
|
|
58
|
+
width: w,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
35
62
|
export function blockLink(id, loc = globalThis.location) {
|
|
36
63
|
return `${loc.origin}${loc.pathname}${loc.search}#${encodeURIComponent(id)}`;
|
|
37
64
|
}
|
|
@@ -58,7 +85,7 @@ export const ACTION_ITEMS = [
|
|
|
58
85
|
{ key: "small", label: "Small", icon: "▫", only: "image", run: (view, chrome, index) => setImageAttrs(view, imageId(view, index), { width: 240 }) },
|
|
59
86
|
{ key: "medium", label: "Medium", icon: "◻", only: "image", run: (view, chrome, index) => setImageAttrs(view, imageId(view, index), { width: 480 }) },
|
|
60
87
|
{ key: "full", label: "Full width", icon: "⬜", only: "image", run: (view, chrome, index) => setImageAttrs(view, imageId(view, index), { width: null }) },
|
|
61
|
-
{ key: "chart", label: "Chart this table", icon: "📊", only: "table", run: (view, chrome, index) =>
|
|
88
|
+
{ key: "chart", label: "Chart this table", icon: "📊", only: "table", run: (view, chrome, index) => { const at = blockStart(view.state.doc, index); return loadChartPart(view).then(({ insertChartAfterTable }) => insertChartAfterTable(view, at)); } },
|
|
62
89
|
{ key: "csv", label: "Import CSV…", icon: "⇧", only: "table", focus: false, run: (view, chrome, index) => pickCsv(view, null, view.state.doc.child(index).attrs.id) },
|
|
63
90
|
];
|
|
64
91
|
|
|
@@ -83,6 +110,7 @@ const ICONS = {
|
|
|
83
110
|
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
111
|
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
112
|
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"/>'),
|
|
113
|
+
more: svg('<path stroke-width="2.2" d="M3 8h.01M8 8h.01M13 8h.01"/>'),
|
|
86
114
|
};
|
|
87
115
|
const TOOLS = [
|
|
88
116
|
["strong", "Bold"],
|
|
@@ -94,6 +122,11 @@ const TOOLS = [
|
|
|
94
122
|
["check", "Checklist"],
|
|
95
123
|
["code", "Code"],
|
|
96
124
|
];
|
|
125
|
+
// The four controls a phone toolbar keeps: bold, italic, link and the menu that
|
|
126
|
+
// holds everything else. Wider screens show every tool.
|
|
127
|
+
const PHONE_TOOLS = new Set(["strong", "em", "link"]);
|
|
128
|
+
const MORE_ACTIONS = new Set(["up", "down", "indent", "outdent"]);
|
|
129
|
+
const PHONE_BAR_WIDTH = 204, PHONE_BAR_HEIGHT = 52, PHONE_INSET = 8;
|
|
97
130
|
const LIST_KINDS = new Set(["bullet", "ordered", "check"]);
|
|
98
131
|
const LINE_TYPES = new Set(["paragraph", "heading", "list_item"]);
|
|
99
132
|
|
|
@@ -173,11 +206,21 @@ function runList(view, kind) {
|
|
|
173
206
|
const st = view.state;
|
|
174
207
|
const off = listActive(st, kind);
|
|
175
208
|
const item = BLOCK_TYPES.find((b) => b.key === (off ? "text" : kind));
|
|
209
|
+
const idx = listable(st).filter((i) => off || !isKind(st.doc.child(i), kind));
|
|
210
|
+
if (idx.some((i) => !canTurnInto(st.doc.child(i), item))) {
|
|
211
|
+
announce(view, CANNOT_CONVERT);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
176
214
|
const tr = st.tr;
|
|
177
|
-
for (const i of
|
|
215
|
+
for (const i of idx) retype(tr, i, item);
|
|
178
216
|
if (tr.docChanged) view.dispatch(asStep(tr));
|
|
179
217
|
}
|
|
180
218
|
|
|
219
|
+
// The four formats this release adds are commands the format gate may refuse, and
|
|
220
|
+
// a refusal is a notice rather than a change. The menu that offers them, and the
|
|
221
|
+
// commands behind it, load the first time one is chosen (format-native-commands.js).
|
|
222
|
+
const nativeMenu = (view, chrome, name) => import("./format-native-commands.js").then((menu) => menu.nativeMenu(view, name, chrome.more));
|
|
223
|
+
|
|
181
224
|
function runMark(view, name) {
|
|
182
225
|
if (name === "link") return openLinkEditor(view);
|
|
183
226
|
const st = view.state;
|
|
@@ -185,6 +228,7 @@ function runMark(view, name) {
|
|
|
185
228
|
const lines = name === "code" && !code ? wholeLines(st) : null;
|
|
186
229
|
if (LIST_KINDS.has(name)) runList(view, name);
|
|
187
230
|
else if (code) view.dispatch(asStep(codeLinesTr(st, code)).scrollIntoView());
|
|
231
|
+
else if (lines && lines.some((i) => !canTurnInto(st.doc.child(i), BLOCK_TYPES.find((b) => b.key === "code")))) announce(view, CANNOT_CONVERT);
|
|
188
232
|
else if (lines) view.dispatch(asStep(codeBlockTr(st, lines)).scrollIntoView());
|
|
189
233
|
else commands.toggleMark(M[name])(st, view.dispatch);
|
|
190
234
|
view.focus();
|
|
@@ -198,7 +242,7 @@ const el = (tag, cls, attrs = {}) => {
|
|
|
198
242
|
};
|
|
199
243
|
|
|
200
244
|
function toolButton(name, label) {
|
|
201
|
-
const b = el("button", "page-tool"
|
|
245
|
+
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
246
|
b.innerHTML = ICONS[name];
|
|
203
247
|
return b;
|
|
204
248
|
}
|
|
@@ -208,6 +252,35 @@ let menuCount = 0;
|
|
|
208
252
|
export const chromeKey = new PluginKey("page-chrome");
|
|
209
253
|
const chromeFor = new WeakMap();
|
|
210
254
|
|
|
255
|
+
// A range a slash menu chose, held in plugin state so it follows the document
|
|
256
|
+
// while the action it belongs to waits for code to load.
|
|
257
|
+
export const slashRangeKey = new PluginKey("page-slash-range");
|
|
258
|
+
const slashRangeState = {
|
|
259
|
+
init: () => null,
|
|
260
|
+
apply(tr, value) {
|
|
261
|
+
let held = value;
|
|
262
|
+
if (held && tr.docChanged) {
|
|
263
|
+
const from = tr.mapping.map(held.from, 1), to = tr.mapping.map(held.to, -1);
|
|
264
|
+
held = from <= to && tr.doc.textBetween(from, to) === held.text ? { ...held, from, to } : null;
|
|
265
|
+
}
|
|
266
|
+
const m = tr.getMeta(slashRangeKey);
|
|
267
|
+
if (m !== undefined) held = m;
|
|
268
|
+
return held;
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export const slashRangePlugin = () => new Plugin({ key: slashRangeKey, state: slashRangeState });
|
|
273
|
+
|
|
274
|
+
export const holdSlashRange = (view, range) => {
|
|
275
|
+
const text = range ? view.state.doc.textBetween(range.from, range.to) : null;
|
|
276
|
+
view.dispatch(view.state.tr.setMeta(slashRangeKey, range ? { ...range, text } : null));
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export const heldSlashRange = (st) => {
|
|
280
|
+
const held = slashRangeKey.getState(st);
|
|
281
|
+
return held ? { from: held.from, to: held.to } : null;
|
|
282
|
+
};
|
|
283
|
+
|
|
211
284
|
export function openPageMenu(view, anchor, items) {
|
|
212
285
|
const chrome = chromeFor.get(view);
|
|
213
286
|
if (!chrome) return false;
|
|
@@ -223,12 +296,21 @@ class Chrome {
|
|
|
223
296
|
this.onAiTool = onAiTool;
|
|
224
297
|
this.shell = view.dom.parentElement;
|
|
225
298
|
this.menu = null;
|
|
299
|
+
this.priorAria = null;
|
|
226
300
|
this.movedTimer = 0;
|
|
227
301
|
this.hover = null;
|
|
302
|
+
this.pendingHover = null;
|
|
303
|
+
this.win = view.dom.ownerDocument.defaultView ?? globalThis;
|
|
304
|
+
this.frame = null;
|
|
305
|
+
this.destroyed = false;
|
|
306
|
+
this.probe = null;
|
|
307
|
+
this.roving = null;
|
|
228
308
|
|
|
229
309
|
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.
|
|
310
|
+
this.turn = el("button", "page-tool page-tool-turn page-tool-desk", { type: "button", "aria-haspopup": "menu" });
|
|
311
|
+
this.more = el("button", "page-tool page-tool-primary page-tool-more", { type: "button", "aria-label": "More", title: "More", "aria-haspopup": "menu" });
|
|
312
|
+
this.more.innerHTML = ICONS.more;
|
|
313
|
+
this.dock.append(this.turn, el("span", "page-tool-sep page-tool-desk"), ...TOOLS.map(([n, l]) => toolButton(n, l)), this.more);
|
|
232
314
|
this.plus = el("button", "page-plus", { type: "button", "aria-label": "Add a block", title: "Add a block" });
|
|
233
315
|
this.plus.innerHTML = ICONS.plus;
|
|
234
316
|
this.grip = el("button", "page-grip", { type: "button", "aria-label": "Block actions", title: "Drag to move, click for actions", "aria-haspopup": "menu" });
|
|
@@ -250,6 +332,8 @@ class Chrome {
|
|
|
250
332
|
});
|
|
251
333
|
}
|
|
252
334
|
this.turn.addEventListener("click", () => this.toggleMenu("turn", this.turn));
|
|
335
|
+
this.more.addEventListener("click", () => this.toggleMenu("more", this.more));
|
|
336
|
+
this.dock.addEventListener("keydown", (e) => this.dockKey(e));
|
|
253
337
|
this.plus.addEventListener("click", () => this.fromPlus());
|
|
254
338
|
this.grip.addEventListener("click", (e) => {
|
|
255
339
|
if (e.detail === 0) this.gripAction(this.gutterIndex());
|
|
@@ -272,10 +356,11 @@ class Chrome {
|
|
|
272
356
|
this.onKey = () => {
|
|
273
357
|
clearTimeout(this.movedTimer);
|
|
274
358
|
this.shell.classList.remove("is-moved");
|
|
359
|
+
this.pendingHover = null;
|
|
275
360
|
this.setHover(null);
|
|
276
361
|
};
|
|
277
|
-
this.onLeave = () => this.setHover(null);
|
|
278
|
-
this.onResize = () => this.
|
|
362
|
+
this.onLeave = () => { this.pendingHover = null; this.setHover(null); };
|
|
363
|
+
this.onResize = () => this.scheduleLayout();
|
|
279
364
|
this.onDocDown = (e) => {
|
|
280
365
|
if (!this.menu) return;
|
|
281
366
|
if ([this.menuEl, this.plus, this.grip, this.turn].some((part) => part.contains(e.target))) return;
|
|
@@ -285,7 +370,10 @@ class Chrome {
|
|
|
285
370
|
this.shell.addEventListener("mousemove", this.onMove);
|
|
286
371
|
this.shell.addEventListener("mouseleave", this.onLeave);
|
|
287
372
|
this.shell.addEventListener("keydown", this.onKey);
|
|
288
|
-
|
|
373
|
+
this.win.addEventListener("resize", this.onResize);
|
|
374
|
+
this.win.addEventListener("scroll", this.onResize, true);
|
|
375
|
+
this.win.visualViewport?.addEventListener("resize", this.onResize);
|
|
376
|
+
this.win.visualViewport?.addEventListener("scroll", this.onResize);
|
|
289
377
|
this.update(view, null);
|
|
290
378
|
}
|
|
291
379
|
|
|
@@ -295,11 +383,107 @@ class Chrome {
|
|
|
295
383
|
return Math.min(st.doc.resolve(pos).index(0), st.doc.childCount - 1);
|
|
296
384
|
}
|
|
297
385
|
|
|
386
|
+
isPhone() {
|
|
387
|
+
return !!this.win?.matchMedia?.("(max-width: 560px)").matches;
|
|
388
|
+
}
|
|
389
|
+
|
|
298
390
|
gutterIndex() {
|
|
299
|
-
if (this.
|
|
391
|
+
if (this.isPhone()) return this.blockIndex();
|
|
392
|
+
this.settleHover();
|
|
300
393
|
return Math.min(this.hover ?? this.blockIndex(), this.view.state.doc.childCount - 1);
|
|
301
394
|
}
|
|
302
395
|
|
|
396
|
+
dockControls() {
|
|
397
|
+
const all = [...this.dock.querySelectorAll("button")].filter((b) => this.rendered(b));
|
|
398
|
+
return this.isPhone() ? all.filter((b) => b.classList.contains("page-tool-primary")) : all;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Every transaction asks whether the roving control is still usable: one style read for
|
|
402
|
+
// that control, not one for every button in the dock.
|
|
403
|
+
inRoving(control) {
|
|
404
|
+
if (!control || !this.dock.contains(control)) return false;
|
|
405
|
+
if (this.isPhone() && !control.classList.contains("page-tool-primary")) return false;
|
|
406
|
+
return this.rendered(control);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
rendered(control) {
|
|
410
|
+
if (control.hidden) return false;
|
|
411
|
+
const style = this.win?.getComputedStyle?.(control);
|
|
412
|
+
return !style || style.display !== "none";
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
setRoving(target) {
|
|
416
|
+
this.roving = target;
|
|
417
|
+
const roving = new Set(this.dockControls());
|
|
418
|
+
for (const b of this.dock.querySelectorAll("button")) b.tabIndex = b === target && roving.has(b) ? 0 : -1;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// The block selection's own Escape still runs first, and then the keyboard
|
|
422
|
+
// leaves the writing surface for the toolbar.
|
|
423
|
+
leaveToToolbar() {
|
|
424
|
+
queueMicrotask(() => {
|
|
425
|
+
if (!this.view.isDestroyed) this.focusToolbar();
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
focusToolbar() {
|
|
430
|
+
const [first] = this.dockControls();
|
|
431
|
+
if (!first) return false;
|
|
432
|
+
this.setRoving(first);
|
|
433
|
+
first.focus();
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Left and Right walk the toolbar; Escape hands the keyboard back to the
|
|
438
|
+
// editor. Tab is left alone, so it leaves the toolbar.
|
|
439
|
+
dockKey(event) {
|
|
440
|
+
if (event.key === "Escape") {
|
|
441
|
+
event.preventDefault();
|
|
442
|
+
this.view.focus();
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (event.key !== "ArrowRight" && event.key !== "ArrowLeft") return;
|
|
446
|
+
const list = this.dockControls();
|
|
447
|
+
const at = list.indexOf(event.target);
|
|
448
|
+
if (at < 0) return;
|
|
449
|
+
event.preventDefault();
|
|
450
|
+
const next = list[(at + (event.key === "ArrowRight" ? 1 : -1) + list.length) % list.length];
|
|
451
|
+
this.setRoving(next);
|
|
452
|
+
next.focus();
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// The blocks a menu's commands act on, by id, so a peer's edit between opening
|
|
456
|
+
// the menu and running the command cannot retarget them.
|
|
457
|
+
selectionIds() {
|
|
458
|
+
const st = this.view.state;
|
|
459
|
+
return blocksIn(st).map((i) => st.doc.child(i).attrs.id);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
idIndex(id) {
|
|
463
|
+
let found = -1;
|
|
464
|
+
this.view.state.doc.forEach((n, _, i) => { if (n.attrs.id === id) found = i; });
|
|
465
|
+
return found;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Where Escape sends focus: the control that opened the menu when it can hold
|
|
469
|
+
// focus, otherwise the editor.
|
|
470
|
+
invoker() {
|
|
471
|
+
const m = this.menu;
|
|
472
|
+
if (!m || m.slashAt !== null) return this.view.dom;
|
|
473
|
+
const a = m.anchor;
|
|
474
|
+
return a && a !== this.view.dom && (a.tagName === "BUTTON" || a.hasAttribute("tabindex")) ? a : this.view.dom;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// A block action from the phone menu acts on the caret's block when nothing
|
|
478
|
+
// more is selected, as the desktop command does.
|
|
479
|
+
selectCaretBlock(ids = null) {
|
|
480
|
+
const st = this.view.state;
|
|
481
|
+
if (st.selection instanceof BlockSelection) return;
|
|
482
|
+
const found = (ids ?? []).map((id) => this.idIndex(id)).filter((i) => i >= 0);
|
|
483
|
+
const first = found.length ? found[0] : this.blockIndex();
|
|
484
|
+
this.view.dispatch(st.tr.setSelection(selectBlocks(st.doc, first, found.length ? found[found.length - 1] : first)));
|
|
485
|
+
}
|
|
486
|
+
|
|
303
487
|
gripAction(index) {
|
|
304
488
|
if (this.menu) this.closeMenu();
|
|
305
489
|
else this.openActions(this.grip, index);
|
|
@@ -307,25 +491,39 @@ class Chrome {
|
|
|
307
491
|
|
|
308
492
|
trackHover(event) {
|
|
309
493
|
if (this.menu || this.drag.state || [this.plus, this.grip, this.dock, this.bubble].some((part) => part.contains(event.target))) return;
|
|
494
|
+
this.pendingHover = { clientY: event.clientY };
|
|
495
|
+
this.scheduleLayout();
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// The pointer's block, read once per frame from the newest move.
|
|
499
|
+
hoverAt(clientY) {
|
|
310
500
|
const view = this.view;
|
|
311
|
-
const hit = view.posAtCoords({ left: view.dom.getBoundingClientRect().left + 2, top:
|
|
501
|
+
const hit = view.posAtCoords({ left: view.dom.getBoundingClientRect().left + 2, top: clientY });
|
|
312
502
|
if (!hit) return;
|
|
313
503
|
const doc = view.state.doc;
|
|
314
|
-
const ents =
|
|
504
|
+
const ents = outlineOf(doc);
|
|
315
505
|
let i = Math.min(doc.resolve(hit.inside >= 0 ? hit.inside : hit.pos).index(0), ents.length - 1);
|
|
316
506
|
while (i > 0 && ents[i].hidden) i--;
|
|
317
|
-
this.
|
|
507
|
+
this.hover = i;
|
|
318
508
|
}
|
|
319
509
|
|
|
320
510
|
setHover(i) {
|
|
321
511
|
if (i === this.hover) return;
|
|
322
512
|
this.hover = i;
|
|
323
|
-
this.
|
|
513
|
+
this.scheduleLayout();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
settleHover() {
|
|
517
|
+
if (!this.pendingHover) return;
|
|
518
|
+
const { clientY } = this.pendingHover;
|
|
519
|
+
this.pendingHover = null;
|
|
520
|
+
this.hoverAt(clientY);
|
|
324
521
|
}
|
|
325
522
|
|
|
326
523
|
fromPlus() {
|
|
327
524
|
this.view.focus();
|
|
328
|
-
|
|
525
|
+
const i = this.gutterIndex();
|
|
526
|
+
this.openMenu("insert", this.plus, null, null, this.view.state.doc.child(i).attrs.id);
|
|
329
527
|
}
|
|
330
528
|
|
|
331
529
|
toggleMenu(mode, anchor) {
|
|
@@ -333,10 +531,13 @@ class Chrome {
|
|
|
333
531
|
else this.openMenu(mode, anchor);
|
|
334
532
|
}
|
|
335
533
|
|
|
336
|
-
openMenu(mode, anchor, slashAt = null,
|
|
337
|
-
this.menu = { mode, anchor, slashAt,
|
|
534
|
+
openMenu(mode, anchor, slashAt = null, ids = null, anchorId = null) {
|
|
535
|
+
this.menu = { mode, anchor, slashAt, anchorId, ids: ids ?? (mode === "insert" ? [] : this.selectionIds()), query: "", active: 0, items: [] };
|
|
338
536
|
anchor.setAttribute("aria-expanded", "true");
|
|
339
|
-
|
|
537
|
+
if (slashAt !== null) {
|
|
538
|
+
if (!this.priorAria) this.priorAria = POPUP_ATTRS.map((name) => [name, this.view.dom.getAttribute(name)]);
|
|
539
|
+
this.view.dom.setAttribute("aria-haspopup", "listbox");
|
|
540
|
+
} else this.view.dom.setAttribute("aria-haspopup", "menu");
|
|
340
541
|
this.view.dom.setAttribute("aria-controls", this.menuId);
|
|
341
542
|
this.renderMenu();
|
|
342
543
|
}
|
|
@@ -346,32 +547,68 @@ class Chrome {
|
|
|
346
547
|
this.menu = null;
|
|
347
548
|
this.menuEl.hidden = true;
|
|
348
549
|
this.scrim.hidden = true;
|
|
349
|
-
|
|
550
|
+
if (this.priorAria) {
|
|
551
|
+
for (const [name, value] of this.priorAria) {
|
|
552
|
+
if (value === null) this.view.dom.removeAttribute(name);
|
|
553
|
+
else this.view.dom.setAttribute(name, value);
|
|
554
|
+
}
|
|
555
|
+
this.priorAria = null;
|
|
556
|
+
} else for (const name of POPUP_ATTRS) this.view.dom.removeAttribute(name);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Everything the phone toolbar folds away: the rest of the formatting, and the
|
|
560
|
+
// block actions the desktop menu carries. The move commands are the same ones.
|
|
561
|
+
moreItems() {
|
|
562
|
+
const tools = TOOLS.filter(([name]) => !PHONE_TOOLS.has(name))
|
|
563
|
+
.map(([name, label]) => ({ key: name, label, icon: ICONS[name], run: (view) => runMark(view, name) }));
|
|
564
|
+
const actions = ACTION_ITEMS.filter((a) => MORE_ACTIONS.has(a.key)).map((a) => ({ ...a, blocks: true }));
|
|
565
|
+
return [
|
|
566
|
+
...tools,
|
|
567
|
+
{ key: "underline", label: "Underline", icon: "U", hint: `${MOD}u`, run: (view, chrome) => nativeMenu(view, chrome, "underline") },
|
|
568
|
+
{ key: "text_color", label: "Text color", icon: "A", hint: "\u203a", run: (view, chrome) => nativeMenu(view, chrome, "text_color") },
|
|
569
|
+
{ key: "highlight", label: "Highlight", icon: "\u25A0", hint: "\u203a", run: (view, chrome) => nativeMenu(view, chrome, "highlight") },
|
|
570
|
+
{ key: "turn", label: "Turn into", icon: "\u21bb", run: (view, chrome) => chrome.openMenu("turn", chrome.more) },
|
|
571
|
+
{ key: "insert", label: "Insert", icon: "+", run: (view, chrome) => chrome.openMenu("insert", chrome.more, null, null, view.state.doc.child(chrome.blockIndex()).attrs.id) },
|
|
572
|
+
...actions,
|
|
573
|
+
];
|
|
350
574
|
}
|
|
351
575
|
|
|
352
576
|
menuItems(m) {
|
|
353
577
|
if (m.mode === "external") return m.externalItems || [];
|
|
354
578
|
if (m.mode === "actions") return actionsFor(this.view.state);
|
|
579
|
+
if (m.mode === "more") return this.moreItems();
|
|
355
580
|
const items = m.mode === "turn" ? BLOCK_TYPES : insertItemsFor(this.view.state, this.onAiTool
|
|
356
581
|
? [...MENU_ITEMS, { key: "ai", label: "Make a tool with AI…", icon: "✦", aliases: ["tool", "generate"], insert: "ai" }]
|
|
357
582
|
: MENU_ITEMS);
|
|
358
583
|
return rankItems(items, m.query);
|
|
359
584
|
}
|
|
360
585
|
|
|
586
|
+
// The types the blocks in the selection are now. A mixed selection has more
|
|
587
|
+
// than one and so checks no row.
|
|
588
|
+
currentTypes() {
|
|
589
|
+
const st = this.view.state;
|
|
590
|
+
return new Set(blocksIn(st).map((i) => blockTypeOf(st.doc.child(i)).key));
|
|
591
|
+
}
|
|
592
|
+
|
|
361
593
|
openActions(anchor = this.plus, at = null) {
|
|
362
594
|
const st = this.view.state;
|
|
363
|
-
const ents =
|
|
595
|
+
const ents = outlineOf(st.doc);
|
|
364
596
|
const sel = st.selection;
|
|
365
597
|
const inside = at !== null && sel instanceof BlockSelection && sel.span(st.doc).lo <= at && at <= sel.span(st.doc).hi;
|
|
366
598
|
const { lo, hi } = at === null || inside ? currentSpan(st, ents) : siblingSpan(ents, at, at);
|
|
367
599
|
this.view.dispatch(st.tr.setSelection(selectBlocks(st.doc, lo, hi, ents)));
|
|
368
|
-
|
|
600
|
+
const ids = [];
|
|
601
|
+
for (let i = lo; i <= hi; i++) ids.push(st.doc.child(i).attrs.id);
|
|
602
|
+
this.openMenu("actions", anchor, null, ids);
|
|
369
603
|
}
|
|
370
604
|
|
|
371
605
|
renderMenu() {
|
|
372
606
|
const m = this.menu;
|
|
373
607
|
m.items = this.menuItems(m);
|
|
374
608
|
m.active = Math.min(m.active, Math.max(0, m.items.length - 1));
|
|
609
|
+
const slash = m.slashAt !== null;
|
|
610
|
+
const current = m.mode === "turn" ? this.currentTypes() : null;
|
|
611
|
+
this.menuEl.setAttribute("role", slash ? "listbox" : "menu");
|
|
375
612
|
const nodes = [];
|
|
376
613
|
if (m.mode === "turn") {
|
|
377
614
|
const title = el("div", "page-menu-title");
|
|
@@ -385,7 +622,9 @@ class Chrome {
|
|
|
385
622
|
}
|
|
386
623
|
m.items.forEach((b, i) => {
|
|
387
624
|
const active = i === m.active;
|
|
388
|
-
const
|
|
625
|
+
const checked = current ? current.size === 1 && b.key === [...current][0] : (typeof b.checked === "boolean" ? b.checked : null);
|
|
626
|
+
const role = m.mode === "turn" ? "menuitemradio" : typeof checked === "boolean" ? "menuitemcheckbox" : slash ? "option" : "menuitem";
|
|
627
|
+
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
628
|
const icon = el("span", "page-menu-icon");
|
|
390
629
|
icon.textContent = b.icon || "";
|
|
391
630
|
const label = el("span", "page-menu-label");
|
|
@@ -405,12 +644,12 @@ class Chrome {
|
|
|
405
644
|
nodes.push(row);
|
|
406
645
|
});
|
|
407
646
|
this.menuEl.replaceChildren(...nodes);
|
|
408
|
-
if (m.items.length) this.view.dom.setAttribute("aria-activedescendant", `${this.menuId}-${m.active}`);
|
|
647
|
+
if (slash && m.items.length) this.view.dom.setAttribute("aria-activedescendant", `${this.menuId}-${m.active}`);
|
|
409
648
|
else this.view.dom.removeAttribute("aria-activedescendant");
|
|
410
649
|
this.menuEl.hidden = false;
|
|
411
650
|
this.scrim.hidden = false;
|
|
412
|
-
this.
|
|
413
|
-
if (
|
|
651
|
+
this.scheduleLayout();
|
|
652
|
+
if (!slash) this.menuEl.querySelectorAll("button")[m.active]?.focus();
|
|
414
653
|
}
|
|
415
654
|
|
|
416
655
|
pick(i) {
|
|
@@ -419,34 +658,68 @@ class Chrome {
|
|
|
419
658
|
if (!item) return;
|
|
420
659
|
const st = this.view.state;
|
|
421
660
|
const remove = m.slashAt !== null && st.selection.head >= m.slashAt ? { from: m.slashAt - 1, to: st.selection.head } : null;
|
|
661
|
+
// Resolve what the menu chose against the current document: a block a peer
|
|
662
|
+
// removed cancels the command instead of handing it whatever took its place.
|
|
663
|
+
const wanted = m.mode === "insert" ? (m.anchorId ? [m.anchorId] : []) : m.ids;
|
|
664
|
+
const stale = wanted.some((id) => !id || this.idIndex(id) < 0);
|
|
665
|
+
const index = wanted.length ? this.idIndex(wanted[0]) : -1;
|
|
422
666
|
this.closeMenu();
|
|
667
|
+
if (stale && m.mode !== "external") {
|
|
668
|
+
announce(this.view, TARGET_GONE);
|
|
669
|
+
this.view.focus();
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
423
672
|
if (m.mode === "turn") {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
|
|
673
|
+
if (wanted.some((id) => !canTurnInto(st.doc.child(this.idIndex(id)), item))) {
|
|
674
|
+
announce(this.view, CANNOT_CONVERT);
|
|
675
|
+
this.view.focus();
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const turn = () => {
|
|
679
|
+
const tr = this.view.state.tr;
|
|
680
|
+
for (const id of wanted) {
|
|
681
|
+
const at = this.idIndex(id);
|
|
682
|
+
if (at >= 0) retype(tr, at, item);
|
|
683
|
+
}
|
|
684
|
+
if (tr.docChanged) this.view.dispatch(asStep(tr).scrollIntoView());
|
|
685
|
+
return true;
|
|
686
|
+
};
|
|
687
|
+
// A block type from a later format asks the gate before it writes.
|
|
688
|
+
if (item.type === "quote") {
|
|
689
|
+
const view = this.view;
|
|
690
|
+
import("./format-native-commands.js").then((menu) => menu.gateTurn(view, turn));
|
|
691
|
+
} else turn();
|
|
430
692
|
this.view.focus();
|
|
431
|
-
} else if (m.mode === "actions" || m.mode === "external") {
|
|
432
|
-
item.
|
|
693
|
+
} else if (m.mode === "actions" || m.mode === "more" || m.mode === "external") {
|
|
694
|
+
if (item.blocks) this.selectCaretBlock(wanted);
|
|
695
|
+
item.run(this.view, this, index >= 0 ? index : this.blockIndex());
|
|
433
696
|
if (!this.menu && item.focus !== false) this.view.focus();
|
|
434
697
|
} else {
|
|
435
698
|
if (item.insert === "ai") this.onAiTool(this.view, remove);
|
|
436
|
-
else runItem(this.view, item,
|
|
699
|
+
else runItem(this.view, item, index >= 0 ? index : this.blockIndex(), remove);
|
|
437
700
|
}
|
|
438
701
|
}
|
|
439
702
|
|
|
440
703
|
// Keys while the menu is open; anything else falls through to the editor.
|
|
441
704
|
key(event) {
|
|
442
|
-
if (event
|
|
705
|
+
if (composing(event, this.view)) return false;
|
|
443
706
|
const m = this.menu;
|
|
444
|
-
if (!m)
|
|
707
|
+
if (!m) {
|
|
708
|
+
if (event.key === "Escape" && !event.metaKey && !event.ctrlKey && !event.altKey
|
|
709
|
+
&& this.view.state.selection instanceof BlockSelection) this.leaveToToolbar();
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
if ((event.key === "Backspace" || event.key === "Delete") && m.mode === "actions"
|
|
713
|
+
&& this.view.state.selection instanceof BlockSelection) {
|
|
714
|
+
this.closeMenu();
|
|
715
|
+
this.view.focus();
|
|
716
|
+
deleteBlocks(this.view.state, this.view.dispatch);
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
445
719
|
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
446
720
|
if (m.items.length) {
|
|
447
721
|
m.active = (m.active + (event.key === "ArrowDown" ? 1 : -1) + m.items.length) % m.items.length;
|
|
448
722
|
this.renderMenu();
|
|
449
|
-
if (m.mode === "external") this.menuEl.querySelectorAll("button")[m.active]?.focus();
|
|
450
723
|
}
|
|
451
724
|
return true;
|
|
452
725
|
}
|
|
@@ -454,7 +727,13 @@ class Chrome {
|
|
|
454
727
|
if (m.items.length) this.pick(m.active);
|
|
455
728
|
return true;
|
|
456
729
|
}
|
|
457
|
-
if (event.key === "Escape") {
|
|
730
|
+
if (event.key === "Escape") {
|
|
731
|
+
const target = this.invoker();
|
|
732
|
+
this.closeMenu();
|
|
733
|
+
if (target === this.view.dom) this.view.focus();
|
|
734
|
+
else target?.focus?.();
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
458
737
|
return false;
|
|
459
738
|
}
|
|
460
739
|
|
|
@@ -473,33 +752,78 @@ class Chrome {
|
|
|
473
752
|
this.renderMenu();
|
|
474
753
|
}
|
|
475
754
|
|
|
476
|
-
|
|
755
|
+
scheduleLayout() {
|
|
756
|
+
if (this.frame !== null || this.destroyed) return;
|
|
757
|
+
this.frame = this.win.requestAnimationFrame(() => {
|
|
758
|
+
this.frame = null;
|
|
759
|
+
if (this.destroyed) return;
|
|
760
|
+
const layout = this.measureLayout();
|
|
761
|
+
this.paintLayout(layout);
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// The safe-area inset the browser reports, read through one hidden probe.
|
|
766
|
+
safeBottom() {
|
|
767
|
+
if (!this.probe) {
|
|
768
|
+
this.probe = el("div", "page-safe-probe");
|
|
769
|
+
this.shell.append(this.probe);
|
|
770
|
+
}
|
|
771
|
+
return parseFloat(this.win.getComputedStyle(this.probe).paddingBottom) || 0;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// A menu stays inside the visible viewport, above the bar when there is room.
|
|
775
|
+
menuBox(bar) {
|
|
776
|
+
const v = phoneViewport(this.win);
|
|
777
|
+
const maxHeight = Math.max(0, v.height - PHONE_INSET * 2);
|
|
778
|
+
const width = Math.min(280, Math.max(0, v.width - PHONE_INSET * 2));
|
|
779
|
+
const applied = this.menuEl.style.getPropertyValue("--page-pop-max");
|
|
780
|
+
this.menuEl.style.removeProperty("--page-pop-max");
|
|
781
|
+
const natural = this.menuEl.offsetHeight || 0;
|
|
782
|
+
if (applied) this.menuEl.style.setProperty("--page-pop-max", applied);
|
|
783
|
+
const height = Math.min(natural, maxHeight);
|
|
784
|
+
const left = Math.max(v.left + PHONE_INSET, Math.min(bar.left, v.left + v.width - PHONE_INSET - width));
|
|
785
|
+
let top = bar.top - 6 - height;
|
|
786
|
+
if (top < v.top + PHONE_INSET) top = Math.min(bar.top + bar.height + 6, v.top + v.height - PHONE_INSET - height);
|
|
787
|
+
return { left, top, width, maxHeight };
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Every geometry read the chrome needs, taken in one pass for the frame.
|
|
791
|
+
measureLayout() {
|
|
477
792
|
const view = this.view;
|
|
478
793
|
const st = view.state;
|
|
794
|
+
this.settleHover();
|
|
479
795
|
const shellBox = this.shell.getBoundingClientRect();
|
|
480
796
|
const edBox = view.dom.getBoundingClientRect();
|
|
481
797
|
const i = this.gutterIndex();
|
|
482
798
|
let at = 0;
|
|
483
799
|
for (let k = 0; k < i; k++) at += st.doc.child(k).nodeSize;
|
|
484
800
|
const dom = view.nodeDOM(at);
|
|
801
|
+
const out = { textLeft: edBox.left - shellBox.left, gutter: null, bubble: null, menu: null, bar: null, desk: false };
|
|
485
802
|
if (dom && dom.getBoundingClientRect) {
|
|
486
803
|
const r = dom.getBoundingClientRect();
|
|
487
804
|
const lineH = parseFloat(getComputedStyle(dom).lineHeight) || r.height;
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
805
|
+
out.gutter = { top: r.top - shellBox.top + Math.min(r.height, lineH) / 2, left: r.left - shellBox.left };
|
|
806
|
+
}
|
|
807
|
+
const phone = this.isPhone();
|
|
808
|
+
// The fixed toolbar sits inside the visible viewport, so an on-screen keyboard
|
|
809
|
+
// or a pan cannot carry it off screen.
|
|
810
|
+
if (phone) {
|
|
811
|
+
const v = phoneViewport(this.win);
|
|
812
|
+
const height = this.dock.offsetHeight || PHONE_BAR_HEIGHT;
|
|
813
|
+
out.bar = { ...phoneBarBox(v, this.dock.offsetWidth || PHONE_BAR_WIDTH, height, this.safeBottom()), height };
|
|
814
|
+
} else {
|
|
815
|
+
out.desk = !!this.dock.style.left;
|
|
492
816
|
}
|
|
493
|
-
this.plus.style.setProperty("--page-text-left", `${edBox.left - shellBox.left}px`);
|
|
494
|
-
|
|
495
817
|
const sel = st.selection;
|
|
496
|
-
if (!this.bubble.hidden) {
|
|
818
|
+
if (!this.bubble.hidden && !phone) {
|
|
497
819
|
const a = view.coordsAtPos(sel.from), b = view.coordsAtPos(sel.to, -1);
|
|
498
820
|
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`);
|
|
821
|
+
out.bubble = { x: mid - shellBox.left, y: Math.min(a.top, b.top) - shellBox.top };
|
|
501
822
|
}
|
|
502
|
-
if (this.menu) {
|
|
823
|
+
if (this.menu && phone) {
|
|
824
|
+
const box = this.menuBox(out.bar);
|
|
825
|
+
out.menu = { x: box.left, y: box.top, width: box.width, maxHeight: box.maxHeight };
|
|
826
|
+
} else if (this.menu) {
|
|
503
827
|
const m = this.menu;
|
|
504
828
|
let left, top;
|
|
505
829
|
if (m.slashAt !== null) {
|
|
@@ -507,20 +831,56 @@ class Chrome {
|
|
|
507
831
|
left = c.left;
|
|
508
832
|
top = c.bottom + 6;
|
|
509
833
|
const height = this.menuEl.offsetHeight;
|
|
510
|
-
if (top + height >
|
|
834
|
+
if (top + height > this.win.innerHeight - 8 && c.top - 6 - height >= 8) top = c.top - 6 - height;
|
|
511
835
|
} else {
|
|
512
836
|
const r = m.anchor === this.plus ? (dom?.getBoundingClientRect?.() ?? edBox) : m.anchor.getBoundingClientRect();
|
|
513
837
|
left = r.left;
|
|
514
838
|
top = r.bottom + 6;
|
|
515
839
|
}
|
|
516
|
-
|
|
517
|
-
|
|
840
|
+
out.menu = { x: Math.max(8, Math.min(left - shellBox.left, shellBox.width - 280)), y: top - shellBox.top };
|
|
841
|
+
}
|
|
842
|
+
return out;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// The placement style writes, from the frame's measurements alone.
|
|
846
|
+
paintLayout(layout) {
|
|
847
|
+
if (layout.bar) {
|
|
848
|
+
this.dock.style.left = `${layout.bar.left}px`;
|
|
849
|
+
this.dock.style.top = `${layout.bar.top}px`;
|
|
850
|
+
this.dock.style.width = `${layout.bar.width}px`;
|
|
851
|
+
this.grip.style.setProperty("--page-grip-lift", `${layout.bar.height}px`);
|
|
852
|
+
} else {
|
|
853
|
+
if (layout.desk) for (const side of ["left", "top", "width"]) this.dock.style.removeProperty(side);
|
|
854
|
+
this.grip.style.removeProperty("--page-grip-lift");
|
|
855
|
+
}
|
|
856
|
+
if (layout.gutter) {
|
|
857
|
+
for (const part of [this.plus, this.grip]) {
|
|
858
|
+
part.style.setProperty("--page-plus-top", `${layout.gutter.top}px`);
|
|
859
|
+
part.style.setProperty("--page-plus-left", `${layout.gutter.left}px`);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
this.plus.style.setProperty("--page-text-left", `${layout.textLeft}px`);
|
|
863
|
+
if (layout.bubble) {
|
|
864
|
+
this.bubble.style.setProperty("--page-pop-x", `${layout.bubble.x}px`);
|
|
865
|
+
this.bubble.style.setProperty("--page-pop-y", `${layout.bubble.y}px`);
|
|
866
|
+
}
|
|
867
|
+
if (layout.menu) {
|
|
868
|
+
this.menuEl.style.setProperty("--page-pop-x", `${layout.menu.x}px`);
|
|
869
|
+
this.menuEl.style.setProperty("--page-pop-y", `${layout.menu.y}px`);
|
|
870
|
+
if (layout.menu.width !== undefined) {
|
|
871
|
+
this.menuEl.style.setProperty("--page-pop-width", `${layout.menu.width}px`);
|
|
872
|
+
this.menuEl.style.setProperty("--page-pop-max", `${layout.menu.maxHeight}px`);
|
|
873
|
+
}
|
|
518
874
|
}
|
|
519
875
|
}
|
|
520
876
|
|
|
521
877
|
update(view, prev) {
|
|
522
878
|
this.view = view;
|
|
523
|
-
if (this.menu && this.menu.slashAt === null && prev && prev.doc !== view.state.doc)
|
|
879
|
+
if (this.menu && this.menu.slashAt === null && prev && prev.doc !== view.state.doc) {
|
|
880
|
+
const hadFocus = this.menuEl.contains(document.activeElement);
|
|
881
|
+
this.closeMenu();
|
|
882
|
+
if (hadFocus) view.focus();
|
|
883
|
+
}
|
|
524
884
|
const st = view.state;
|
|
525
885
|
const node = st.doc.child(this.blockIndex());
|
|
526
886
|
this.turn.textContent = `${blockTypeOf(node).label} ▾`;
|
|
@@ -529,19 +889,26 @@ class Chrome {
|
|
|
529
889
|
this.bubble.hidden = !showBubble;
|
|
530
890
|
if (showBubble) for (const b of this.bubble.querySelectorAll("[data-cmd]")) b.setAttribute("aria-pressed", String(toolActive(st, b.dataset.cmd)));
|
|
531
891
|
this.grip.classList.toggle("is-on", st.selection instanceof BlockSelection);
|
|
892
|
+
if (!this.inRoving(this.roving)) this.setRoving(this.dockControls()[0]);
|
|
532
893
|
this.slashQuery();
|
|
533
|
-
this.
|
|
894
|
+
this.scheduleLayout();
|
|
534
895
|
}
|
|
535
896
|
|
|
536
897
|
destroy() {
|
|
898
|
+
this.destroyed = true;
|
|
899
|
+
if (this.frame !== null) this.win.cancelAnimationFrame(this.frame);
|
|
900
|
+
this.frame = null;
|
|
537
901
|
clearTimeout(this.movedTimer);
|
|
538
902
|
this.drag.destroy();
|
|
539
903
|
this.shell.removeEventListener("mousemove", this.onMove);
|
|
540
904
|
this.shell.removeEventListener("mouseleave", this.onLeave);
|
|
541
905
|
this.shell.removeEventListener("keydown", this.onKey);
|
|
542
|
-
|
|
906
|
+
this.win.removeEventListener("resize", this.onResize);
|
|
907
|
+
this.win.removeEventListener("scroll", this.onResize, true);
|
|
908
|
+
this.win.visualViewport?.removeEventListener("resize", this.onResize);
|
|
909
|
+
this.win.visualViewport?.removeEventListener("scroll", this.onResize);
|
|
543
910
|
document.removeEventListener("mousedown", this.onDocDown, true);
|
|
544
|
-
for (const part of [this.dock, this.plus, this.grip, this.bubble, this.scrim, this.menuEl]) part
|
|
911
|
+
for (const part of [this.dock, this.plus, this.grip, this.bubble, this.scrim, this.menuEl, this.probe]) part?.remove();
|
|
545
912
|
}
|
|
546
913
|
}
|
|
547
914
|
|
|
@@ -571,12 +938,13 @@ export function chromePlugin({ onAiTool = null } = {}) {
|
|
|
571
938
|
const $from = view.state.doc.resolve(from);
|
|
572
939
|
if ($from.depth !== 1 || !$from.parent.isTextblock || $from.parent.type.spec.code) return inserted;
|
|
573
940
|
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
|
-
|
|
941
|
+
if (!inserted) view.dispatch(view.state.tr.insertText("/", from, to).setMeta(HISTORY_INTENT, typingIntent(view.state, from, to, "/")));
|
|
942
|
+
const at = Math.min(view.state.doc.resolve(from).index(0), view.state.doc.childCount - 1);
|
|
943
|
+
chrome.openMenu("insert", chrome.plus, from + 1, null, view.state.doc.child(at).attrs.id);
|
|
576
944
|
return true;
|
|
577
945
|
},
|
|
578
946
|
handleDOMEvents: {
|
|
579
|
-
keydown(view, event) { return event
|
|
947
|
+
keydown(view, event) { return composing(event, view); },
|
|
580
948
|
blur(view) {
|
|
581
949
|
if (chrome) chrome.bubble.hidden = true;
|
|
582
950
|
return false;
|