@panphora/clayjs 1.16.0 → 1.18.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/dist/all.js +1 -1
- package/dist/clay-dom.js +1 -1
- package/dist/clay-events.js +1 -1
- package/dist/clay-internals.js +1 -1
- package/dist/clay-options.js +1 -1
- package/dist/clay-router.js +1 -1
- package/dist/clay-ui.js +1 -1
- package/dist/clay-utils.js +1 -1
- package/dist/clay.js +1 -1
- package/dist/clay.standalone.js +72519 -59839
- package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
- package/dist/m/attrs/save-freeze-LWGF6XUN.js +1 -0
- package/dist/m/c-2GRUM77Q.js +30 -0
- package/dist/m/c-2KIEGNC4.js +1 -0
- package/dist/m/{c-5R4PTRG2.js → c-2KL3BD52.js} +1 -1
- package/dist/m/c-2YYDMLNZ.js +1 -0
- package/dist/m/c-2ZZBC45O.js +1 -0
- package/dist/m/c-372N6H3B.js +1 -0
- package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
- package/dist/m/c-3LDGIBMN.js +1 -0
- package/dist/m/c-3O7OBDCM.js +1 -0
- package/dist/m/c-4ASQPRQL.js +1 -0
- package/dist/m/c-4TXNLX4K.js +6 -0
- package/dist/m/c-4YU3BR4D.js +6 -0
- package/dist/m/c-5734MF2P.js +1 -0
- package/dist/m/c-57XDY6HC.js +1 -0
- package/dist/m/{c-VFX45IZQ.js → c-5B4YBZXC.js} +1 -1
- package/dist/m/c-5ONTYIUI.js +1 -0
- package/dist/m/c-5OOVFCJM.js +1 -0
- package/dist/m/c-5T6NZHNM.js +1 -0
- package/dist/m/{c-YDH7Q3NY.js → c-5ZQV7KC4.js} +1 -1
- package/dist/m/{c-Z36J63PP.js → c-6DB2OLRJ.js} +1 -1
- package/dist/m/c-6DNRCPNM.js +1 -0
- package/dist/m/c-6UGWFMQC.js +2 -0
- package/dist/m/c-7K4SCQB7.js +1 -0
- package/dist/m/c-7MG5RU63.js +1 -0
- package/dist/m/c-7PRJCA3E.js +1 -0
- package/dist/m/c-A6L3FMUS.js +1 -0
- package/dist/m/c-AMU3BPPR.js +4 -0
- package/dist/m/c-AMYEFA2E.js +1 -0
- package/dist/m/c-AQYCG7QW.js +1 -0
- package/dist/m/c-ATKAOH23.js +1 -0
- package/dist/m/c-B3JM5ZXC.js +1 -0
- package/dist/m/c-B5CS55WR.js +1 -0
- package/dist/m/c-B5UJQT7W.js +1 -0
- package/dist/m/c-B7J4K2AE.js +3 -0
- package/dist/m/c-BHZYSHW4.js +1 -0
- package/dist/m/c-BIJALSRD.js +1 -0
- package/dist/m/c-BJ7YQ5QZ.js +1 -0
- package/dist/m/c-BPF2NSPV.js +1 -0
- package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
- package/dist/m/c-BUAYFEV5.js +1 -0
- package/dist/m/c-C6IPRG34.js +1 -0
- package/dist/m/{c-2JL6E75N.js → c-CAS3NND4.js} +1 -1
- package/dist/m/{c-4DRMS4QG.js → c-CCQ25GFY.js} +1 -1
- package/dist/m/c-CJFDLD43.js +1 -0
- package/dist/m/c-COPTL5WB.js +1 -0
- package/dist/m/c-D3E4WJ4M.js +166 -0
- package/dist/m/c-D5IKEKXW.js +7 -0
- package/dist/m/c-DHBRA6DL.js +1 -0
- package/dist/m/c-DKLS74XB.js +72 -0
- package/dist/m/c-DVYGFTSK.js +3 -0
- package/dist/m/c-E3ZXARWA.js +1 -0
- package/dist/m/c-EK63G3CG.js +1 -0
- package/dist/m/c-ERQSNR6K.js +13 -0
- package/dist/m/c-EVYAISP3.js +1 -0
- package/dist/m/c-EXKHPOVC.js +3 -0
- package/dist/m/{c-7VGZ7RU5.js → c-FWKHM4E7.js} +1 -1
- package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
- package/dist/m/c-GNDL3TKB.js +1 -0
- package/dist/m/c-GRWMD6O5.js +1 -0
- package/dist/m/c-GRXHHFPO.js +1 -0
- package/dist/m/{c-OYTFP5QN.js → c-GY422N3Q.js} +1 -1
- package/dist/m/c-HAWFNBYG.js +1 -0
- package/dist/m/c-HG2C77BM.js +4 -0
- package/dist/m/c-HHGJBU4D.js +1 -0
- package/dist/m/c-HMV6P4RX.js +1 -0
- package/dist/m/c-HMYWIHFG.js +1 -0
- package/dist/m/c-HT2DKBV2.js +1 -0
- package/dist/m/c-I2SCCUEW.js +1 -0
- package/dist/m/{c-HRDCD3WN.js → c-ICJYHABF.js} +2 -2
- package/dist/m/c-IMZCWYHR.js +7 -0
- package/dist/m/c-IVMD3FEM.js +1 -0
- package/dist/m/c-IVTRVENZ.js +1 -0
- package/dist/m/c-IW6GGOU2.js +1 -0
- package/dist/m/c-JKZJIUIP.js +1 -0
- package/dist/m/{c-NZMNXVF6.js → c-JNLX3RJB.js} +1 -1
- package/dist/m/c-JRKY44DK.js +1 -0
- package/dist/m/c-K3ADYNLU.js +1 -0
- package/dist/m/c-KBS3UV6B.js +1 -0
- package/dist/m/c-KGHVFRR3.js +1 -0
- package/dist/m/c-KJJPV77R.js +1 -0
- package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
- package/dist/m/{c-5Y6TNX5W.js → c-KP64LT2A.js} +2 -2
- package/dist/m/c-KQ7E3PYY.js +1 -0
- package/dist/m/c-L3F5ZLIV.js +1 -0
- package/dist/m/{c-LR4CGEPT.js → c-L42OLXWM.js} +3 -2
- package/dist/m/c-L5VFATT6.js +1 -0
- package/dist/m/c-L7JL5TL3.js +3 -0
- package/dist/m/c-LKSL4AUJ.js +1 -0
- package/dist/m/c-M6FNQCTQ.js +1 -0
- package/dist/m/c-MCOOHCSG.js +1 -0
- package/dist/m/c-MHI3GYKF.js +1 -0
- package/dist/m/c-MWXFLW4J.js +1 -0
- package/dist/m/c-NEL7HQW3.js +1 -0
- package/dist/m/c-NQJRVJ56.js +1 -0
- package/dist/m/c-NWX3MFT4.js +1 -0
- package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
- package/dist/m/c-OKXI52N7.js +1 -0
- package/dist/m/{c-MMUIUUZG.js → c-OPLUZR3A.js} +1 -1
- package/dist/m/{c-J7CA54IP.js → c-OTMKN2OO.js} +1 -1
- package/dist/m/{c-LTGMXXKO.js → c-OYRZ7XG2.js} +1 -1
- package/dist/m/c-P3OFYUMA.js +1 -0
- package/dist/m/c-PLN76GF3.js +6 -0
- package/dist/m/c-PLP4JKFC.js +1 -0
- package/dist/m/c-Q26MME6N.js +1 -0
- package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
- package/dist/m/{c-SWRJFCNN.js → c-QIKT6SKM.js} +1 -1
- package/dist/m/c-QIXT3Q7C.js +28 -0
- package/dist/m/c-QO4HNGCQ.js +1 -0
- package/dist/m/c-R27DWQKX.js +1 -0
- package/dist/m/{c-HVYXIS72.js → c-R2XUODVY.js} +1 -1
- package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
- package/dist/m/c-RND3G2S2.js +1 -0
- package/dist/m/c-S2U6NJU4.js +1 -0
- package/dist/m/c-SPZPGD3R.js +1 -0
- package/dist/m/c-SW4YEG2D.js +8 -0
- package/dist/m/c-SZGYW5HT.js +1 -0
- package/dist/m/c-T637T7SN.js +1 -0
- package/dist/m/c-TJAXGIRV.js +1 -0
- package/dist/m/{c-KKWLDOPM.js → c-TNEM3RNM.js} +1 -1
- package/dist/m/c-TTB5EK4S.js +32 -0
- package/dist/m/c-U7SHNWDH.js +1 -0
- package/dist/m/c-UDHIVLYH.js +20 -0
- package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
- package/dist/m/{c-TQZ2JYHD.js → c-UORXLSD3.js} +1 -1
- package/dist/m/c-UPD33ZPE.js +1 -0
- package/dist/m/c-URSMSFCB.js +1 -0
- package/dist/m/c-UY4QX42B.js +1 -0
- package/dist/m/c-V44KWI2L.js +4 -0
- package/dist/m/c-VG5XIN4T.js +1 -0
- package/dist/m/c-VILENZTD.js +1 -0
- package/dist/m/{c-33HUYEDI.js → c-VJYEVRYK.js} +1 -1
- package/dist/m/c-VR2BJ4AN.js +1 -0
- package/dist/m/c-W4JCVLAB.js +1 -0
- package/dist/m/c-W6DQSHXI.js +135 -0
- package/dist/m/c-WBGWGOM7.js +1 -0
- package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
- package/dist/m/c-WGW764WX.js +1 -0
- package/dist/m/c-WMRJKQ2V.js +5 -0
- package/dist/m/c-WNTYKCDG.js +1 -0
- package/dist/m/c-WQWIRWO3.js +72 -0
- package/dist/m/c-X5LNHET5.js +1 -0
- package/dist/m/c-X5TSXZYJ.js +2 -0
- package/dist/m/c-XGH4GS2T.js +1 -0
- package/dist/m/c-Y6F2FORL.js +1 -0
- package/dist/m/c-YBKHUAK7.js +10 -0
- package/dist/m/c-YBSS55KU.js +20 -0
- package/dist/m/c-YCLU4UNT.js +2 -0
- package/dist/m/c-YVZ2L7CE.js +1 -0
- package/dist/m/c-ZK4GIZH2.js +1 -0
- package/dist/m/c-ZRXJD4G6.js +1 -0
- package/dist/m/c-ZWBI4HYK.js +1 -0
- package/dist/m/{c-HT33EQRN.js → c-ZWIOJGMT.js} +1 -1
- package/dist/m/core/admin-attrs-YY7T5FGU.js +1 -0
- package/dist/m/core/autosave-S6OMNDCK.js +1 -0
- package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
- package/dist/m/core/host-permission-6EVJGNPL.js +1 -0
- package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
- package/dist/m/core/{persist-HKHA66IY.js → persist-UKXV5KLA.js} +1 -1
- package/dist/m/core/recovery-DRFZRJED.js +1 -0
- package/dist/m/core/save-LYY7K6U7.js +1 -0
- package/dist/m/core/save-core-MKRPSEEO.js +1 -0
- package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
- package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
- package/dist/m/events/index-RN2JNGBV.js +1 -0
- package/dist/m/internals/index-TGLQINN3.js +1 -0
- package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
- package/dist/m/loader-3BBNZZGM.js +1 -0
- package/dist/m/manifest-F449FF98.js +1 -0
- package/dist/m/manifest-edit-4396A009.js +1 -0
- package/dist/m/manifest.json +2509 -1474
- package/dist/m/options/options-EJMS3K24.js +1 -0
- package/dist/m/page/editor-6LRB3GE4.js +304 -0
- package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
- package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
- package/dist/m/plugins/demo-OYO5GCAO.js +1 -0
- package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
- package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
- package/dist/m/plugins/live-AD4H6XSL.js +1 -0
- package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
- package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
- package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
- package/dist/m/plugins/people-JCHVPF7P.js +1 -0
- package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
- package/dist/m/plugins/{richclay-RC3L2F6X.js → richclay-KLGETK77.js} +2 -2
- package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
- package/dist/m/plugins/source-TJXOIOYY.js +1 -0
- package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
- package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
- package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
- package/dist/m/plugins/upload-NHGJXZK2.js +1 -0
- package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
- package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
- package/dist/m/plugins/wire-VBWXEXU2.js +1 -0
- package/dist/m/reader-core-4W2KYEWQ.js +77 -0
- package/dist/m/route/route-62CHXL5B.js +1 -0
- package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
- package/dist/m/ui/index-IVWN2QC4.js +1 -0
- package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
- package/dist/offline-manifest.json +1560 -889
- package/dist/page/themes/light-1.css +33 -8
- package/dist/server/core.cjs +1554 -311
- package/dist/server/offline-runtime.cjs +63 -63
- package/dist/server/offline-runtime.mjs +63 -63
- package/dist/server/page-compaction-worker.cjs +3710 -0
- package/dist/server/rooms.mjs +5 -0
- package/docs/guide.md +653 -101
- package/entries/sap.js +5 -5
- package/package.json +1 -1
- package/src/core/admin-contenteditable.js +5 -2
- package/src/core/admin-inputs.js +1 -0
- package/src/core/admin-onclick.js +2 -1
- package/src/core/boot-phase.js +2 -1
- package/src/core/conflict-download.js +3 -29
- package/src/core/conflict-notice-ui.js +4 -7
- package/src/core/download.js +32 -0
- package/src/core/host-meta.js +56 -0
- package/src/core/is-edit-mode.js +17 -1
- package/src/core/mode-key.js +125 -19
- package/src/core/mode-support.js +5 -0
- package/src/core/mode-transition.js +20 -6
- package/src/core/preserve-authored.js +4 -4
- package/src/core/recovery.js +121 -33
- package/src/core/save-accepted.js +33 -0
- package/src/core/save-core.js +111 -32
- package/src/core/save-job.js +108 -0
- package/src/core/save-prepare.js +53 -0
- package/src/core/save-size-notice.js +3 -1
- package/src/core/save-slices.js +15 -0
- package/src/core/save.js +557 -127
- package/src/core/serialize-steps.js +109 -0
- package/src/core/snapshot.js +209 -40
- package/src/core/source-map.js +83 -38
- package/src/core/stale-host-notice.js +1 -0
- package/src/core/upgrade-run.js +59 -8
- package/src/dom/active-element.js +5 -0
- package/src/lib/assets.js +1 -0
- package/src/lib/autosave-scheduler.js +3 -4
- package/src/lib/slice.js +65 -0
- package/src/lib/uimotion.js +774 -0
- package/src/lib/unsaved-state.js +2 -2
- package/src/live/fast-lane.js +10 -3
- package/src/live/local-lane.js +202 -11
- package/src/live/ops.js +16 -0
- package/src/live/page-compaction.js +162 -0
- package/src/live/server-lane.js +1 -0
- package/src/live/store.js +11 -3
- package/src/loader-logic.js +3 -1
- package/src/loader.js +14 -4
- package/src/page/adopt-boot.js +2 -1
- package/src/page/awareness.js +33 -2
- package/src/page/blocks.js +122 -27
- package/src/page/board-views.js +2 -16
- package/src/page/boot-pack.js +1 -1
- package/src/page/chart-tables.js +29 -0
- package/src/page/chart.js +411 -72
- package/src/page/checklist.js +10 -135
- package/src/page/checkpoint-proof.js +295 -0
- package/src/page/chrome.js +257 -40
- package/src/page/clipboard-shape.js +126 -0
- package/src/page/document-title-dom.js +12 -0
- package/src/page/document-title-runtime.js +15 -1
- package/src/page/document-title.js +4 -1
- package/src/page/drag.js +33 -3
- package/src/page/editor.js +207 -31
- package/src/page/emoji-data.js +162 -0
- package/src/page/export-assets.js +357 -0
- package/src/page/export-html.js +209 -0
- package/src/page/export-markdown.js +361 -0
- package/src/page/features/board.js +12 -3
- package/src/page/features/chart.js +11 -0
- package/src/page/features/checklist.js +428 -0
- package/src/page/features/dates.js +180 -0
- package/src/page/features/emoji.js +187 -0
- package/src/page/features/export-html.js +189 -0
- package/src/page/features/export-markdown.js +169 -0
- package/src/page/features/import-markdown.js +349 -0
- package/src/page/features/media.js +61 -5
- package/src/page/features/mentions.js +188 -0
- package/src/page/features/registry.js +24 -1
- package/src/page/features/table.js +375 -85
- package/src/page/features/title-cover.js +331 -0
- package/src/page/features/title-icon.js +213 -0
- package/src/page/features/toc.js +87 -0
- package/src/page/format-command.js +33 -0
- package/src/page/format-gate.js +68 -18
- package/src/page/format-host.js +71 -0
- package/src/page/format-native-commands.js +158 -0
- package/src/page/format-native.js +42 -0
- package/src/page/image-actions.js +64 -0
- package/src/page/import-markdown.js +262 -0
- package/src/page/inline.js +32 -8
- package/src/page/items.js +141 -11
- package/src/page/markdown.js +30 -2
- package/src/page/media.js +35 -119
- package/src/page/mention-picker.js +99 -0
- package/src/page/merge.js +1 -1
- package/src/page/placeholder.js +14 -1
- package/src/page/projection.js +156 -25
- package/src/page/recover.js +10 -12
- package/src/page/recovery-ui.js +464 -0
- package/src/page/rich/chart.js +63 -7
- package/src/page/save-projection.js +67 -0
- package/src/page/save-states.js +14 -4
- package/src/page/schema.js +52 -3
- package/src/page/table-paste.js +3 -1
- package/src/page/table-style.js +4 -4
- package/src/page/table.js +44 -2
- package/src/page/themes/content.css +25 -1
- package/src/page/themes/light-1.css +33 -8
- package/src/page/themes/light-1.source.css +8 -7
- package/src/page/toc-link.js +31 -0
- package/src/page/toc.js +5 -0
- package/src/page/view-hooks.js +58 -0
- package/src/page/view.js +13 -0
- package/src/page/ybind/binding.js +116 -6
- package/src/page/ybind/bridge.js +187 -20
- package/src/page/ybind/containment.js +162 -0
- package/src/page/ybind/doc.js +17 -9
- package/src/page/ybind/file-debt.js +63 -0
- package/src/page/ybind/layout.js +7 -2
- package/src/page/ybind/marks.js +2 -1
- package/src/page/ybind/moves.js +5 -4
- package/src/page/ybind/origins.js +106 -4
- package/src/page/ybind/parts.js +8 -1
- package/src/page/ybind/provider.js +125 -18
- package/src/page/ybind/recovery-proof.js +32 -9
- package/src/page/ybind/recovery-replay.js +905 -4
- package/src/page/ybind/recovery.js +174 -45
- package/src/page/ybind/table.js +67 -11
- package/src/page/ybind/walk-delta.js +258 -0
- package/src/page/ybind/walk.js +54 -29
- package/src/page/ybind/writer.js +1 -1
- package/src/plugins/ai-edit.js +13 -9
- package/src/plugins/indicator-chip.js +91 -0
- package/src/plugins/indicator.js +13 -80
- package/src/plugins/live.js +126 -15
- package/src/plugins/menu/actions.js +196 -0
- package/src/plugins/menu/people.js +279 -0
- package/src/plugins/menu/placement.js +121 -0
- package/src/plugins/menu/preferences.js +9 -0
- package/src/plugins/menu/seo.js +316 -397
- package/src/plugins/menu/style.js +171 -0
- package/src/plugins/menu/variants.js +296 -0
- package/src/plugins/menu.js +186 -183
- package/src/plugins/page-view.js +3 -2
- package/src/plugins/page.js +362 -43
- package/src/plugins/sortable.js +8 -1
- package/src/plugins/source.js +6 -0
- package/src/plugins/upgrade.js +51 -17
- package/src/plugins/upload-ui.js +12 -17
- package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
- package/src/plugins/visitor-drafts/intro-style.js +7 -2
- package/src/plugins/visitor-drafts/intro.js +7 -4
- package/src/plugins/visitor-drafts/owner-chip.js +8 -12
- package/src/plugins/visitor-drafts/prompt.js +11 -9
- package/src/plugins/visitor-drafts/source.js +4 -3
- package/src/plugins/visitor-drafts/store.js +138 -9
- package/src/plugins/visitor-drafts/ui-memory.js +2 -2
- package/src/plugins/visitor-drafts-owner.js +4 -6
- package/src/plugins/visitor-drafts.js +5 -3
- package/src/recovery/store.js +1 -1
- package/src/route/route.js +4 -0
- package/src/server/checkpoint/index.js +2 -2
- package/src/server/rooms/index.js +5 -1
- package/src/server/rooms/live-data-rooms.js +302 -50
- package/src/server/rooms/page-compaction-worker.js +12 -0
- package/src/server/rooms/page-compactor.js +92 -0
- package/src/server/rooms/page-room-conformance.js +461 -0
- package/src/source/parse-steps.js +94 -0
- package/src/source/session.js +233 -57
- package/src/sync/editing-sender.js +12 -0
- package/src/sync/live-sync-edit.js +22 -2
- package/src/sync/live-sync.js +150 -23
- package/src/sync/page-relay.js +52 -0
- package/src/sync/presence-store.js +44 -4
- package/src/sync/presence.js +6 -3
- package/src/sync/saved-stream.js +28 -14
- package/src/ui/bevel-controls.js +1 -0
- package/src/ui/bevel-dialog.js +5 -2
- package/src/ui/editor-ui.js +77 -0
- package/src/ui/page-colour.js +281 -0
- package/src/ui/toast.js +16 -7
- package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
- package/src/vendor/hyper-undo.vendor.js +1 -1
- package/src/vendor/hypercms.vendor.js +9 -9
- package/src/vendor/parse5.vendor.js +2 -2
- package/src/vendor/sap-core.vendor.js +5 -5
- package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +0 -1
- package/dist/m/attrs/save-freeze-FUP4Q4X3.js +0 -1
- package/dist/m/c-2M73252O.js +0 -1
- package/dist/m/c-2SPVQSHC.js +0 -1
- package/dist/m/c-35NBGMZB.js +0 -1
- package/dist/m/c-3IQRECYS.js +0 -1
- package/dist/m/c-4B6H4G5V.js +0 -1
- package/dist/m/c-4FFHZM32.js +0 -1
- package/dist/m/c-4YSCRICO.js +0 -1
- package/dist/m/c-54RCDCBU.js +0 -1
- package/dist/m/c-5M4YEKCX.js +0 -4
- package/dist/m/c-5V3MOVJT.js +0 -1
- package/dist/m/c-66O32MV3.js +0 -28
- package/dist/m/c-6HMB24EV.js +0 -1
- package/dist/m/c-7O6AFR65.js +0 -1
- package/dist/m/c-ANQTJ3B5.js +0 -2
- package/dist/m/c-AYVOREBE.js +0 -1
- package/dist/m/c-B2ZXPOS3.js +0 -3
- package/dist/m/c-BMIZKMPV.js +0 -1
- package/dist/m/c-BTDI4Y23.js +0 -1
- package/dist/m/c-CJZSLMVQ.js +0 -1
- package/dist/m/c-DBE6ORSI.js +0 -1
- package/dist/m/c-EDHUR4MX.js +0 -1
- package/dist/m/c-EY6UOM54.js +0 -1
- package/dist/m/c-F3CKJTJS.js +0 -1
- package/dist/m/c-F7Y6FLPY.js +0 -1
- package/dist/m/c-FA4DZVTC.js +0 -1
- package/dist/m/c-G2BUZITR.js +0 -1
- package/dist/m/c-GB3JTV6E.js +0 -1
- package/dist/m/c-GBGWXTX4.js +0 -1
- package/dist/m/c-GHBCCVG4.js +0 -1
- package/dist/m/c-GJAEGGXY.js +0 -1
- package/dist/m/c-GOWBDM6J.js +0 -1
- package/dist/m/c-IB52R237.js +0 -1
- package/dist/m/c-IKWNE4R4.js +0 -1
- package/dist/m/c-IU3IYEU6.js +0 -1
- package/dist/m/c-IZ2EBRAJ.js +0 -1
- package/dist/m/c-KR4UW4KW.js +0 -1
- package/dist/m/c-KY3VJGUA.js +0 -8
- package/dist/m/c-LFPHCXY3.js +0 -1
- package/dist/m/c-LRPUO2MX.js +0 -1
- package/dist/m/c-LXENEBV7.js +0 -1
- package/dist/m/c-NMJRXKPC.js +0 -8
- package/dist/m/c-OIBUTDXI.js +0 -1
- package/dist/m/c-PPOSCAQD.js +0 -1
- package/dist/m/c-QIX5BJYN.js +0 -1
- package/dist/m/c-QQHEX6HA.js +0 -1
- package/dist/m/c-R7RMANJI.js +0 -9
- package/dist/m/c-RI4NT75Y.js +0 -1
- package/dist/m/c-RM7J7BV2.js +0 -1
- package/dist/m/c-RUJKSUQW.js +0 -1
- package/dist/m/c-SGCCC4FW.js +0 -25
- package/dist/m/c-T4VO4OCB.js +0 -1
- package/dist/m/c-TQEMVJCS.js +0 -1
- package/dist/m/c-TR6RMDQ7.js +0 -3
- package/dist/m/c-UKHTLWUW.js +0 -1
- package/dist/m/c-UNN7LWCT.js +0 -1
- package/dist/m/c-UXBMRDZB.js +0 -235
- package/dist/m/c-V7PZZRG7.js +0 -1
- package/dist/m/c-VL52ADDR.js +0 -1
- package/dist/m/c-WNYBLIOZ.js +0 -1
- package/dist/m/c-WWDBYXLK.js +0 -1
- package/dist/m/c-XNNW2CWK.js +0 -1
- package/dist/m/c-Y6USGTS5.js +0 -5
- package/dist/m/c-YG2NJYS6.js +0 -135
- package/dist/m/c-Z4VRC7GU.js +0 -7
- package/dist/m/c-Z6K52OLP.js +0 -19
- package/dist/m/c-ZDY2MDAA.js +0 -21
- package/dist/m/c-ZUCOKOHT.js +0 -1
- package/dist/m/core/admin-attrs-VGNLAUDK.js +0 -1
- package/dist/m/core/autosave-E4PEFPZ6.js +0 -1
- package/dist/m/core/conflict-notice-VQ6Y64PH.js +0 -1
- package/dist/m/core/host-permission-XN3K2IW3.js +0 -1
- package/dist/m/core/is-edit-mode-JXHDVGUL.js +0 -1
- package/dist/m/core/recovery-TBBSICE2.js +0 -1
- package/dist/m/core/save-WUCHTQBE.js +0 -1
- package/dist/m/core/save-core-FJVHT5M3.js +0 -1
- package/dist/m/core/snapshot-F3SWDZMM.js +0 -1
- package/dist/m/core/unsaved-warning-STKBHE6G.js +0 -1
- package/dist/m/events/index-23DPTUT4.js +0 -1
- package/dist/m/internals/index-WOVAP3RU.js +0 -1
- package/dist/m/lib/cache-bust-CGWT6ZJI.js +0 -1
- package/dist/m/loader-HXX6YNH5.js +0 -1
- package/dist/m/manifest-F22DEDAC.js +0 -1
- package/dist/m/manifest-edit-C3C7376B.js +0 -1
- package/dist/m/options/options-2LH76JQ3.js +0 -1
- package/dist/m/page/editor-XUTB2HB2.js +0 -189
- package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +0 -6
- package/dist/m/plugins/demo-ZIBFAA3R.js +0 -1
- package/dist/m/plugins/indicator-PV4YW2ID.js +0 -1
- package/dist/m/plugins/live-NUTTXSBS.js +0 -1
- package/dist/m/plugins/menu-HCKO3DSP.js +0 -138
- package/dist/m/plugins/page-KSUH7G6J.js +0 -2
- package/dist/m/plugins/page-view-W73OW7WI.js +0 -1
- package/dist/m/plugins/people-AJJ32YCG.js +0 -1
- package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
- package/dist/m/plugins/sortable-YRMQX3ZU.js +0 -1
- package/dist/m/plugins/source-KHK5UUSK.js +0 -1
- package/dist/m/plugins/undo-OF2L3CXU.js +0 -1
- package/dist/m/plugins/upgrade-ZNYODP5Y.js +0 -1
- package/dist/m/plugins/upload-XSGNILUY.js +0 -1
- package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +0 -21
- package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +0 -1
- package/dist/m/plugins/wire-ZWVWKF76.js +0 -1
- package/dist/m/reader-core-ILWLXVMP.js +0 -68
- package/dist/m/route/route-KOCZSEGN.js +0 -1
- package/dist/m/sync/live-sync-4AUVXVJU.js +0 -1
- package/dist/m/ui/index-EVHKMKBL.js +0 -1
- package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
- /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
- /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
- /package/dist/m/plugins/{richclay-RC3L2F6X.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
package/src/page/chrome.js
CHANGED
|
@@ -8,16 +8,15 @@ import { schema } from "./schema.js";
|
|
|
8
8
|
import { BlockSelection, siblingSpan, moveBlock, indentBlocks, outdentBlocks } from "./blocks.js";
|
|
9
9
|
import { outlineOf } from "./outline-cache.js";
|
|
10
10
|
import { currentSpan, selectBlocks, duplicateBlocks, deleteBlocks, asStep, typeIntoBlocks } from "./blocksel.js";
|
|
11
|
-
import { openAltEditor, setImageAttrs } from "./media.js";
|
|
12
11
|
import { BlockDrag } from "./drag.js";
|
|
13
12
|
import { openLinkEditor } from "./link.js";
|
|
14
|
-
import { BLOCK_TYPES, MENU_ITEMS, blockTypeOf, turnInto, insertAfter, rankItems, runItem, retype, canTurnInto, insertItemsFor } from "./items.js";
|
|
13
|
+
import { BLOCK_TYPES, MENU_ITEMS, blockTypeOf, turnBlocks, turnInto, insertAfter, rankItems, runItem, retype, canTurnInto, insertItemsFor, loadChartPart, runMenuCommand } from "./items.js";
|
|
15
14
|
import toast from "../ui/toast-lazy.js";
|
|
16
|
-
import { insertChartAfterTable } from "./chart.js";
|
|
17
15
|
import { pickCsv } from "./csv-import.js";
|
|
18
16
|
import { HISTORY_INTENT, typingIntent } from "./history-intent.js";
|
|
19
17
|
import { composing } from "./markdown.js";
|
|
20
18
|
import { announce } from "./announce.js";
|
|
19
|
+
import { REMOTE } from "./model.js";
|
|
21
20
|
|
|
22
21
|
export { BLOCK_TYPES, blockTypeOf, turnInto, insertAfter } from "./items.js";
|
|
23
22
|
|
|
@@ -74,20 +73,24 @@ function copyBlockLink(id) {
|
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
export const ACTION_ITEMS = [
|
|
77
|
-
{ key: "up", label: "Move up", icon: "
|
|
78
|
-
{ key: "down", label: "Move down", icon: "
|
|
79
|
-
{ key: "indent", label: "Indent", icon: "
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
{ key: "
|
|
85
|
-
{ key: "
|
|
86
|
-
{ key: "
|
|
87
|
-
{ key: "
|
|
88
|
-
{ key: "
|
|
89
|
-
{ key: "
|
|
90
|
-
{ key: "
|
|
76
|
+
{ key: "up", label: "Move up", icon: "up", hint: `${MOD}⇧↑`, run: (view) => moveBlock(-1)(view.state, view.dispatch) },
|
|
77
|
+
{ key: "down", label: "Move down", icon: "down", hint: `${MOD}⇧↓`, run: (view) => moveBlock(1)(view.state, view.dispatch) },
|
|
78
|
+
{ key: "indent", label: "Indent", icon: "indent", hint: "Tab", run: (view) => {
|
|
79
|
+
const before = view.state.doc;
|
|
80
|
+
indentBlocks(view.state, view.dispatch);
|
|
81
|
+
if (view.state.doc === before) { announce(view, "Indent works on list items and inside toggles."); toast("Indent works on list items and inside toggles."); }
|
|
82
|
+
} },
|
|
83
|
+
{ key: "outdent", label: "Outdent", icon: "outdent", hint: "⇧Tab", run: (view) => outdentBlocks(view.state, view.dispatch) },
|
|
84
|
+
{ key: "duplicate", label: "Duplicate", icon: "duplicate", hint: `${MOD}D`, run: (view) => duplicateBlocks(view.state, view.dispatch) },
|
|
85
|
+
{ key: "delete", label: "Delete", icon: "trash", hint: "Del", run: (view) => deleteBlocks(view.state, view.dispatch) },
|
|
86
|
+
{ key: "turn", label: "Turn into", icon: "turn", hint: "›", only: "text", run: (view, chrome) => chrome.openMenu("turn", chrome.plus) },
|
|
87
|
+
{ key: "link", label: "Copy link", icon: "link", run: (view, chrome, index) => copyBlockLink(view.state.doc.child(index).attrs.id) },
|
|
88
|
+
{ key: "alt", label: "Alt text…", icon: "alt", only: "image", focus: false, run: (view, chrome, index) => { const id = imageId(view, index); return import("./image-actions.js").then((part) => part.openAltEditor(view, id)); } },
|
|
89
|
+
{ key: "small", label: "Small", icon: "small", only: "image", run: (view, chrome, index) => { const id = imageId(view, index); return import("./image-actions.js").then((part) => part.setImageAttrs(view, id, { width: 240 })); } },
|
|
90
|
+
{ key: "medium", label: "Medium", icon: "medium", only: "image", run: (view, chrome, index) => { const id = imageId(view, index); return import("./image-actions.js").then((part) => part.setImageAttrs(view, id, { width: 480 })); } },
|
|
91
|
+
{ key: "full", label: "Full width", icon: "full", only: "image", run: (view, chrome, index) => { const id = imageId(view, index); return import("./image-actions.js").then((part) => part.setImageAttrs(view, id, { width: null })); } },
|
|
92
|
+
{ key: "chart", label: "Chart this table", icon: "chart", only: "table", run: (view, chrome, index) => { const at = blockStart(view.state.doc, index); return loadChartPart(view).then(({ insertChartAfterTable }) => insertChartAfterTable(view, at)); } },
|
|
93
|
+
{ key: "csv", label: "Import CSV…", icon: "csv", only: "table", focus: false, run: (view, chrome, index) => pickCsv(view, null, view.state.doc.child(index).attrs.id) },
|
|
91
94
|
];
|
|
92
95
|
|
|
93
96
|
const actionsFor = (st) => {
|
|
@@ -112,6 +115,21 @@ const ICONS = {
|
|
|
112
115
|
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"/>'),
|
|
113
116
|
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
117
|
more: svg('<path stroke-width="2.2" d="M3 8h.01M8 8h.01M13 8h.01"/>'),
|
|
118
|
+
gear: svg('<g transform="scale(0.6667)" stroke-width="2.25"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></g>'),
|
|
119
|
+
close: svg('<path d="M4 4l8 8M12 4l-8 8"/>'),
|
|
120
|
+
up: svg('<path d="M8 13V3.5M4 7.5 8 3.5l4 4"/>'),
|
|
121
|
+
down: svg('<path d="M8 3v9.5M4 8.5l4 4 4-4"/>'),
|
|
122
|
+
indent: svg('<path d="M2.5 3.5h11M7 8h6.5M2.5 12.5h11M2.5 6l2.5 2-2.5 2"/>'),
|
|
123
|
+
outdent: svg('<path d="M2.5 3.5h11M7 8h6.5M2.5 12.5h11M5 6 2.5 8 5 10"/>'),
|
|
124
|
+
duplicate: svg('<rect x="5.5" y="5.5" width="8" height="8" rx="1.5"/><path d="M10.5 3.5v-.5A1.5 1.5 0 0 0 9 1.5H4A1.5 1.5 0 0 0 2.5 3v5A1.5 1.5 0 0 0 4 9.5h.5"/>'),
|
|
125
|
+
trash: svg('<path d="M3 4.5h10M6.5 4.5V3h3v1.5M4.5 4.5l.6 8.5h5.8l.6-8.5"/>'),
|
|
126
|
+
turn: svg('<path d="M3 5.5h9.5M10 3l2.5 2.5L10 8M13 10.5H3.5M6 8l-2.5 2.5L6 13"/>'),
|
|
127
|
+
chart: svg('<path d="M3 13V3M3 13h10.5M6 10.5V8M9 10.5V5.5M12 10.5V7"/>'),
|
|
128
|
+
csv: svg('<path d="M4 1.5h5L12.5 5v9.5h-8.5zM9 1.5V5h3.5M8 7.5v5M6 10.5l2 2 2-2"/>'),
|
|
129
|
+
alt: svg('<path d="M2.5 12.5 5.5 4l3 8.5M3.6 9.5h3.8M10 12.5V7.5M10 8.5c.4-.7 1.1-1 2-1h.5"/>'),
|
|
130
|
+
small: svg('<rect x="5" y="5" width="6" height="6" rx="1"/>'),
|
|
131
|
+
medium: svg('<rect x="3.5" y="3.5" width="9" height="9" rx="1.2"/>'),
|
|
132
|
+
full: svg('<rect x="1.5" y="3.5" width="13" height="9" rx="1.2"/>'),
|
|
115
133
|
};
|
|
116
134
|
const TOOLS = [
|
|
117
135
|
["strong", "Bold"],
|
|
@@ -129,6 +147,7 @@ const PHONE_TOOLS = new Set(["strong", "em", "link"]);
|
|
|
129
147
|
const MORE_ACTIONS = new Set(["up", "down", "indent", "outdent"]);
|
|
130
148
|
const PHONE_BAR_WIDTH = 204, PHONE_BAR_HEIGHT = 52, PHONE_INSET = 8;
|
|
131
149
|
const LIST_KINDS = new Set(["bullet", "ordered", "check"]);
|
|
150
|
+
const SETTING_ROWS = [["assignees", "Checklist assignees"], ["dates", "Checklist due dates"]];
|
|
132
151
|
const LINE_TYPES = new Set(["paragraph", "heading", "list_item"]);
|
|
133
152
|
|
|
134
153
|
// The top-level blocks a toolbar command acts on. A nonempty selection's end is
|
|
@@ -217,6 +236,11 @@ function runList(view, kind) {
|
|
|
217
236
|
if (tr.docChanged) view.dispatch(asStep(tr));
|
|
218
237
|
}
|
|
219
238
|
|
|
239
|
+
// The four formats this release adds are commands the format gate may refuse, and
|
|
240
|
+
// a refusal is a notice rather than a change. The menu that offers them, and the
|
|
241
|
+
// commands behind it, load the first time one is chosen (format-native-commands.js).
|
|
242
|
+
const nativeMenu = (view, chrome, name) => import("./format-native-commands.js").then((menu) => menu.nativeMenu(view, name, chrome.pickedFrom ?? chrome.more));
|
|
243
|
+
|
|
220
244
|
function runMark(view, name) {
|
|
221
245
|
if (name === "link") return openLinkEditor(view);
|
|
222
246
|
const st = view.state;
|
|
@@ -277,15 +301,28 @@ export const heldSlashRange = (st) => {
|
|
|
277
301
|
return held ? { from: held.from, to: held.to } : null;
|
|
278
302
|
};
|
|
279
303
|
|
|
280
|
-
export function openPageMenu(view, anchor, items) {
|
|
304
|
+
export function openPageMenu(view, anchor, items, { persist = false } = {}) {
|
|
281
305
|
const chrome = chromeFor.get(view);
|
|
282
306
|
if (!chrome) return false;
|
|
283
307
|
chrome.openMenu("external", anchor);
|
|
284
308
|
chrome.menu.externalItems = items;
|
|
309
|
+
chrome.menu.persist = persist;
|
|
285
310
|
chrome.renderMenu();
|
|
286
311
|
return true;
|
|
287
312
|
}
|
|
288
313
|
|
|
314
|
+
// A menu whose rows name their targets by id outlives a render of the control that
|
|
315
|
+
// opened it: the control that takes the old one's place keeps the popup open.
|
|
316
|
+
export function reanchorPageMenu(view, from, to) {
|
|
317
|
+
const chrome = chromeFor.get(view);
|
|
318
|
+
const menu = chrome?.menu;
|
|
319
|
+
if (!menu || menu.anchor !== from || !to || to === from) return false;
|
|
320
|
+
menu.anchor = to;
|
|
321
|
+
to.setAttribute("aria-expanded", "true");
|
|
322
|
+
view.dom.setAttribute("aria-controls", chrome.menuId);
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
|
|
289
326
|
class Chrome {
|
|
290
327
|
constructor(view, onAiTool) {
|
|
291
328
|
this.view = view;
|
|
@@ -301,18 +338,32 @@ class Chrome {
|
|
|
301
338
|
this.destroyed = false;
|
|
302
339
|
this.probe = null;
|
|
303
340
|
this.roving = null;
|
|
341
|
+
this.remote = false;
|
|
304
342
|
|
|
305
343
|
this.dock = el("div", "page-dock", { role: "toolbar", "aria-label": "Formatting" });
|
|
306
344
|
this.turn = el("button", "page-tool page-tool-turn page-tool-desk", { type: "button", "aria-haspopup": "menu" });
|
|
307
345
|
this.more = el("button", "page-tool page-tool-primary page-tool-more", { type: "button", "aria-label": "More", title: "More", "aria-haspopup": "menu" });
|
|
308
346
|
this.more.innerHTML = ICONS.more;
|
|
347
|
+
this.settingsBtn = el("button", "page-tool page-tool-desk page-tool-settings", { type: "button", "aria-label": "Document settings", title: "Document settings", "aria-haspopup": "dialog", "aria-expanded": "false" });
|
|
348
|
+
this.settingsBtn.innerHTML = ICONS.gear;
|
|
349
|
+
this.settingsEl = null;
|
|
350
|
+
this.settingsOpen = false;
|
|
351
|
+
this.settingsAnchor = null;
|
|
352
|
+
this.settingSwitches = {};
|
|
309
353
|
this.dock.append(this.turn, el("span", "page-tool-sep page-tool-desk"), ...TOOLS.map(([n, l]) => toolButton(n, l)), this.more);
|
|
354
|
+
// The loader publishes the page facade when its module arrives, which can be after
|
|
355
|
+
// this toolbar is built, so the gear asks for the settings each time it is drawn.
|
|
356
|
+
this.dock.insertBefore(this.settingsBtn, this.more);
|
|
357
|
+
this.settingsBtn.hidden = !this.settingsAPI;
|
|
310
358
|
this.plus = el("button", "page-plus", { type: "button", "aria-label": "Add a block", title: "Add a block" });
|
|
311
359
|
this.plus.innerHTML = ICONS.plus;
|
|
312
360
|
this.grip = el("button", "page-grip", { type: "button", "aria-label": "Block actions", title: "Drag to move, click for actions", "aria-haspopup": "menu" });
|
|
313
361
|
this.grip.innerHTML = ICONS.grip;
|
|
314
362
|
this.bubble = el("div", "page-bubble", { role: "toolbar", "aria-label": "Selection", hidden: "" });
|
|
315
|
-
this.
|
|
363
|
+
this.bubbleTurn = el("button", "page-tool page-tool-turn", { type: "button", "aria-haspopup": "menu" });
|
|
364
|
+
this.bubbleMore = el("button", "page-tool page-tool-primary page-tool-more", { type: "button", "aria-label": "More", title: "More", "aria-haspopup": "menu" });
|
|
365
|
+
this.bubbleMore.innerHTML = ICONS.more;
|
|
366
|
+
this.bubble.append(this.bubbleTurn, el("span", "page-tool-sep"), ...TOOLS.map(([n, l]) => toolButton(n, l)), this.bubbleMore);
|
|
316
367
|
this.menuId = `page-menu-${++menuCount}`;
|
|
317
368
|
this.menuEl = el("div", "page-menu", { role: "menu", id: this.menuId, hidden: "" });
|
|
318
369
|
this.scrim = el("div", "page-scrim", { hidden: "" });
|
|
@@ -329,13 +380,23 @@ class Chrome {
|
|
|
329
380
|
}
|
|
330
381
|
this.turn.addEventListener("click", () => this.toggleMenu("turn", this.turn));
|
|
331
382
|
this.more.addEventListener("click", () => this.toggleMenu("more", this.more));
|
|
383
|
+
this.settingsBtn.addEventListener("click", () => this.toggleSettings());
|
|
384
|
+
this.bubbleTurn.addEventListener("click", () => this.toggleMenu("turn", this.bubbleTurn));
|
|
385
|
+
this.bubbleMore.addEventListener("click", () => this.toggleMenu("more", this.bubbleMore));
|
|
332
386
|
this.dock.addEventListener("keydown", (e) => this.dockKey(e));
|
|
333
387
|
this.plus.addEventListener("click", () => this.fromPlus());
|
|
334
388
|
this.grip.addEventListener("click", (e) => {
|
|
335
389
|
if (e.detail === 0) this.gripAction(this.gutterIndex());
|
|
336
390
|
});
|
|
337
391
|
this.drag = new BlockDrag(view, this.grip, { index: () => this.gutterIndex(), onClick: (i) => this.gripAction(i) });
|
|
338
|
-
|
|
392
|
+
// A tap that opens a menu ends in a click over the scrim it just showed: only a press
|
|
393
|
+
// that began on the scrim closes it.
|
|
394
|
+
let scrimPressed = false;
|
|
395
|
+
this.scrim.addEventListener("pointerdown", () => { scrimPressed = true; });
|
|
396
|
+
this.scrim.addEventListener("click", () => {
|
|
397
|
+
if (scrimPressed) this.closeMenu();
|
|
398
|
+
scrimPressed = false;
|
|
399
|
+
});
|
|
339
400
|
this.menuEl.addEventListener("click", (e) => {
|
|
340
401
|
const row = e.target.closest(".page-menu-item");
|
|
341
402
|
if (row) this.pick(Number(row.dataset.index));
|
|
@@ -356,13 +417,21 @@ class Chrome {
|
|
|
356
417
|
this.setHover(null);
|
|
357
418
|
};
|
|
358
419
|
this.onLeave = () => { this.pendingHover = null; this.setHover(null); };
|
|
359
|
-
this.onResize = () => this.scheduleLayout();
|
|
420
|
+
this.onResize = () => { this.scheduleLayout(); this.placeSettings(); };
|
|
360
421
|
this.onDocDown = (e) => {
|
|
422
|
+
if (this.settingsOpen && ![this.settingsEl, this.settingsAnchor].some((part) => part?.contains(e.target))) this.closeSettings();
|
|
361
423
|
if (!this.menu) return;
|
|
362
|
-
if ([this.menuEl, this.plus, this.grip, this.turn].some((part) => part.contains(e.target))) return;
|
|
424
|
+
if ([this.menuEl, this.plus, this.grip, this.turn, this.bubbleTurn, this.bubbleMore].some((part) => part.contains(e.target))) return;
|
|
363
425
|
this.closeMenu();
|
|
364
426
|
};
|
|
427
|
+
this.onSettingsKey = (e) => {
|
|
428
|
+
if (e.key !== "Escape" || !this.settingsOpen) return;
|
|
429
|
+
e.preventDefault();
|
|
430
|
+
e.stopPropagation();
|
|
431
|
+
this.closeSettings(true);
|
|
432
|
+
};
|
|
365
433
|
document.addEventListener("mousedown", this.onDocDown, true);
|
|
434
|
+
document.addEventListener("keydown", this.onSettingsKey, true);
|
|
366
435
|
this.shell.addEventListener("mousemove", this.onMove);
|
|
367
436
|
this.shell.addEventListener("mouseleave", this.onLeave);
|
|
368
437
|
this.shell.addEventListener("keydown", this.onKey);
|
|
@@ -467,7 +536,7 @@ class Chrome {
|
|
|
467
536
|
const m = this.menu;
|
|
468
537
|
if (!m || m.slashAt !== null) return this.view.dom;
|
|
469
538
|
const a = m.anchor;
|
|
470
|
-
return a && a !== this.view.dom && (a.tagName === "BUTTON" || a.hasAttribute("tabindex")) ? a : this.view.dom;
|
|
539
|
+
return a && a !== this.view.dom && a.isConnected && (a.tagName === "BUTTON" || a.hasAttribute("tabindex")) ? a : this.view.dom;
|
|
471
540
|
}
|
|
472
541
|
|
|
473
542
|
// A block action from the phone menu acts on the caret's block when nothing
|
|
@@ -522,13 +591,116 @@ class Chrome {
|
|
|
522
591
|
this.openMenu("insert", this.plus, null, null, this.view.state.doc.child(i).attrs.id);
|
|
523
592
|
}
|
|
524
593
|
|
|
594
|
+
// The gear's panel: built once, filled from the document's own settings, and
|
|
595
|
+
// placed under the control that opened it.
|
|
596
|
+
buildSettings() {
|
|
597
|
+
if (this.settingsEl) return this.settingsEl;
|
|
598
|
+
const panel = el("div", "page-settings", { role: "dialog", "aria-label": "Document settings", hidden: "" });
|
|
599
|
+
const head = el("div", "page-settings-head");
|
|
600
|
+
const title = el("span", "page-settings-title");
|
|
601
|
+
title.textContent = "Document settings";
|
|
602
|
+
const close = el("button", "page-settings-close", { type: "button", "aria-label": "Close settings" });
|
|
603
|
+
close.innerHTML = ICONS.close;
|
|
604
|
+
close.addEventListener("click", () => this.closeSettings(true));
|
|
605
|
+
head.append(title, close);
|
|
606
|
+
const rows = SETTING_ROWS.map(([key, label]) => {
|
|
607
|
+
const row = el("div", "page-settings-row");
|
|
608
|
+
const name = el("span", "page-settings-name", { id: `${this.menuId}-set-${key}` });
|
|
609
|
+
name.textContent = label;
|
|
610
|
+
const sw = el("button", "page-settings-switch", { type: "button", role: "switch", "aria-checked": "true", "aria-labelledby": name.id, "data-setting": key });
|
|
611
|
+
sw.append(el("span", "page-settings-knob"));
|
|
612
|
+
sw.addEventListener("click", () => this.flipSetting(key));
|
|
613
|
+
this.settingSwitches[key] = sw;
|
|
614
|
+
row.append(name, sw);
|
|
615
|
+
return row;
|
|
616
|
+
});
|
|
617
|
+
const note = el("div", "page-settings-note");
|
|
618
|
+
note.textContent = "Hidden values stay saved and come back when turned on.";
|
|
619
|
+
panel.append(head, ...rows, note);
|
|
620
|
+
this.settingsEl = panel;
|
|
621
|
+
this.shell.append(panel);
|
|
622
|
+
return panel;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
flipSetting(key) {
|
|
626
|
+
const sw = this.settingSwitches[key];
|
|
627
|
+
const on = sw.getAttribute("aria-checked") !== "true";
|
|
628
|
+
sw.setAttribute("aria-checked", String(on));
|
|
629
|
+
Promise.resolve().then(() => this.settingsAPI.set(key, on)).catch(() => {
|
|
630
|
+
sw.setAttribute("aria-checked", String(!on));
|
|
631
|
+
toast("That setting could not be saved.", "error");
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
openSettings(anchor = this.settingsBtn) {
|
|
636
|
+
if (!this.settingsAPI) return;
|
|
637
|
+
if (this.menu) this.closeMenu();
|
|
638
|
+
const panel = this.buildSettings();
|
|
639
|
+
const now = this.settingsAPI.read();
|
|
640
|
+
for (const [key, sw] of Object.entries(this.settingSwitches)) sw.setAttribute("aria-checked", String(now[key] !== false));
|
|
641
|
+
this.settingsOpen = true;
|
|
642
|
+
this.settingsAnchor = anchor;
|
|
643
|
+
anchor.setAttribute("aria-expanded", "true");
|
|
644
|
+
panel.hidden = false;
|
|
645
|
+
this.placeSettings();
|
|
646
|
+
panel.querySelector(".page-settings-close")?.focus({ preventScroll: true });
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
closeSettings(restoreFocus = false) {
|
|
650
|
+
if (!this.settingsOpen) return;
|
|
651
|
+
const anchor = this.settingsAnchor;
|
|
652
|
+
this.settingsOpen = false;
|
|
653
|
+
this.settingsAnchor = null;
|
|
654
|
+
if (this.settingsEl) this.settingsEl.hidden = true;
|
|
655
|
+
anchor?.setAttribute("aria-expanded", "false");
|
|
656
|
+
if (restoreFocus && anchor?.isConnected) anchor.focus({ preventScroll: true });
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
toggleSettings() {
|
|
660
|
+
if (this.settingsOpen) this.closeSettings(true);
|
|
661
|
+
else this.openSettings(this.settingsBtn);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// The panel's right edge sits on its control's right edge, under it, and it
|
|
665
|
+
// flips above the control when there is no room below.
|
|
666
|
+
placeSettings() {
|
|
667
|
+
const panel = this.settingsEl;
|
|
668
|
+
if (!panel || panel.hidden || !this.settingsAnchor) return;
|
|
669
|
+
const r = this.settingsAnchor.getBoundingClientRect();
|
|
670
|
+
const width = panel.offsetWidth || 264;
|
|
671
|
+
const height = panel.offsetHeight || 0;
|
|
672
|
+
if (this.isPhone()) {
|
|
673
|
+
const v = phoneViewport(this.win);
|
|
674
|
+
const x = Math.max(v.left + PHONE_INSET, Math.min(r.right - width, v.left + v.width - PHONE_INSET - width));
|
|
675
|
+
let y = r.top - 6 - height;
|
|
676
|
+
if (y < v.top + PHONE_INSET) y = Math.min(r.bottom + 6, v.top + v.height - PHONE_INSET - height);
|
|
677
|
+
panel.style.setProperty("--page-pop-x", `${x}px`);
|
|
678
|
+
panel.style.setProperty("--page-pop-y", `${Math.max(v.top + PHONE_INSET, y)}px`);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
const shellBox = this.shell.getBoundingClientRect();
|
|
682
|
+
const x = Math.max(8, Math.min(r.right - shellBox.left - width, Math.max(8, shellBox.width - width - 8)));
|
|
683
|
+
let y = r.bottom - shellBox.top + 6;
|
|
684
|
+
if (y + height > this.win.innerHeight - 8 && r.top - shellBox.top - 6 - height >= 8) y = r.top - shellBox.top - 6 - height;
|
|
685
|
+
panel.style.setProperty("--page-pop-x", `${x}px`);
|
|
686
|
+
panel.style.setProperty("--page-pop-y", `${y}px`);
|
|
687
|
+
}
|
|
688
|
+
|
|
525
689
|
toggleMenu(mode, anchor) {
|
|
526
690
|
if (this.menu) this.closeMenu();
|
|
527
691
|
else this.openMenu(mode, anchor);
|
|
528
692
|
}
|
|
529
693
|
|
|
694
|
+
// A slash dismissed with Escape stays text: it is known by its block and the text
|
|
695
|
+
// before it, so typing after it does not bring the menu back.
|
|
696
|
+
slashKey(slashAt) {
|
|
697
|
+
const $at = this.view.state.doc.resolve(slashAt);
|
|
698
|
+
return `${$at.parent.attrs.id}\u0000${this.view.state.doc.textBetween($at.start(), slashAt - 1)}`;
|
|
699
|
+
}
|
|
700
|
+
|
|
530
701
|
openMenu(mode, anchor, slashAt = null, ids = null, anchorId = null) {
|
|
531
|
-
this.
|
|
702
|
+
this.closeSettings();
|
|
703
|
+
this.menu = { mode, anchor, slashAt, anchorId, ids: ids ?? (mode === "insert" ? [] : this.selectionIds()), query: "", active: 0, items: [], persist: false };
|
|
532
704
|
anchor.setAttribute("aria-expanded", "true");
|
|
533
705
|
if (slashAt !== null) {
|
|
534
706
|
if (!this.priorAria) this.priorAria = POPUP_ATTRS.map((name) => [name, this.view.dom.getAttribute(name)]);
|
|
@@ -555,14 +727,23 @@ class Chrome {
|
|
|
555
727
|
// Everything the phone toolbar folds away: the rest of the formatting, and the
|
|
556
728
|
// block actions the desktop menu carries. The move commands are the same ones.
|
|
557
729
|
moreItems() {
|
|
558
|
-
const
|
|
559
|
-
|
|
730
|
+
const phone = this.isPhone();
|
|
731
|
+
const tools = phone ? TOOLS.filter(([name]) => !PHONE_TOOLS.has(name))
|
|
732
|
+
.map(([name, label]) => ({ key: name, label, icon: name, run: (view) => runMark(view, name) })) : [];
|
|
560
733
|
const actions = ACTION_ITEMS.filter((a) => MORE_ACTIONS.has(a.key)).map((a) => ({ ...a, blocks: true }));
|
|
561
734
|
return [
|
|
562
735
|
...tools,
|
|
563
|
-
{ key: "
|
|
564
|
-
{ key: "
|
|
736
|
+
{ key: "underline", label: "Underline", icon: "U", hint: `${MOD}u`, run: (view, chrome) => nativeMenu(view, chrome, "underline") },
|
|
737
|
+
{ key: "text_color", label: "Text color", icon: "A", hint: "\u203a", run: (view, chrome) => nativeMenu(view, chrome, "text_color") },
|
|
738
|
+
{ key: "highlight", label: "Highlight", icon: "\u25A0", hint: "\u203a", run: (view, chrome) => nativeMenu(view, chrome, "highlight") },
|
|
739
|
+
...(phone ? [{ key: "turn", label: "Turn into", icon: "turn", run: (view, chrome) => chrome.openMenu("turn", chrome.more) }] : []),
|
|
740
|
+
{ key: "insert", label: "Insert", icon: "+", run: (view, chrome) => chrome.openMenu("insert", chrome.pickedFrom ?? chrome.more, null, null, view.state.doc.child(chrome.blockIndex()).attrs.id) },
|
|
565
741
|
...actions,
|
|
742
|
+
...(phone && this.settingsAPI ? [{ key: "settings", label: "Document settings", icon: "gear", focus: false, run: (view, chrome) => chrome.openSettings(chrome.more) }] : []),
|
|
743
|
+
{ key: "titleIcon", label: "Page icon", icon: "\u263A", run: (view) => runMenuCommand(view, "titleIcon") },
|
|
744
|
+
{ key: "titleCover", label: "Add cover", icon: "\u25A4", run: (view) => runMenuCommand(view, "titleCover") },
|
|
745
|
+
{ key: "exportHtml", label: "Export reading copy", icon: "\u21E9", run: (view) => runMenuCommand(view, "exportHtml") },
|
|
746
|
+
{ key: "exportMarkdown", label: "Export as Markdown", icon: "M\u21E9", run: (view) => runMenuCommand(view, "exportMarkdown") },
|
|
566
747
|
];
|
|
567
748
|
}
|
|
568
749
|
|
|
@@ -619,7 +800,8 @@ class Chrome {
|
|
|
619
800
|
const role = m.mode === "turn" ? "menuitemradio" : typeof checked === "boolean" ? "menuitemcheckbox" : slash ? "option" : "menuitem";
|
|
620
801
|
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) } : {}) });
|
|
621
802
|
const icon = el("span", "page-menu-icon");
|
|
622
|
-
icon.
|
|
803
|
+
if (ICONS[b.icon]) icon.innerHTML = ICONS[b.icon];
|
|
804
|
+
else icon.textContent = b.icon || "";
|
|
623
805
|
const label = el("span", "page-menu-label");
|
|
624
806
|
label.textContent = b.label;
|
|
625
807
|
if (b.description) {
|
|
@@ -642,7 +824,7 @@ class Chrome {
|
|
|
642
824
|
this.menuEl.hidden = false;
|
|
643
825
|
this.scrim.hidden = false;
|
|
644
826
|
this.scheduleLayout();
|
|
645
|
-
if (!slash) this.menuEl.querySelectorAll("button")[m.active]?.focus();
|
|
827
|
+
if (!slash) this.menuEl.querySelectorAll("button")[m.active]?.focus({ preventScroll: true });
|
|
646
828
|
}
|
|
647
829
|
|
|
648
830
|
pick(i) {
|
|
@@ -656,6 +838,8 @@ class Chrome {
|
|
|
656
838
|
const wanted = m.mode === "insert" ? (m.anchorId ? [m.anchorId] : []) : m.ids;
|
|
657
839
|
const stale = wanted.some((id) => !id || this.idIndex(id) < 0);
|
|
658
840
|
const index = wanted.length ? this.idIndex(wanted[0]) : -1;
|
|
841
|
+
// A submenu a row opens (text color, highlight) anchors where this menu was opened.
|
|
842
|
+
this.pickedFrom = m.anchor;
|
|
659
843
|
this.closeMenu();
|
|
660
844
|
if (stale && m.mode !== "external") {
|
|
661
845
|
announce(this.view, TARGET_GONE);
|
|
@@ -668,10 +852,7 @@ class Chrome {
|
|
|
668
852
|
this.view.focus();
|
|
669
853
|
return;
|
|
670
854
|
}
|
|
671
|
-
|
|
672
|
-
for (const id of wanted) retype(tr, this.idIndex(id), item);
|
|
673
|
-
this.view.dispatch(asStep(tr).scrollIntoView());
|
|
674
|
-
this.view.focus();
|
|
855
|
+
turnBlocks(this.view, item, wanted);
|
|
675
856
|
} else if (m.mode === "actions" || m.mode === "more" || m.mode === "external") {
|
|
676
857
|
if (item.blocks) this.selectCaretBlock(wanted);
|
|
677
858
|
item.run(this.view, this, index >= 0 ? index : this.blockIndex());
|
|
@@ -709,8 +890,13 @@ class Chrome {
|
|
|
709
890
|
if (m.items.length) this.pick(m.active);
|
|
710
891
|
return true;
|
|
711
892
|
}
|
|
893
|
+
if (event.key === "Tab" && m.slashAt === null) {
|
|
894
|
+
this.closeMenu();
|
|
895
|
+
return false;
|
|
896
|
+
}
|
|
712
897
|
if (event.key === "Escape") {
|
|
713
898
|
const target = this.invoker();
|
|
899
|
+
if (m.slashAt !== null) this.dismissedSlash = this.slashKey(m.slashAt);
|
|
714
900
|
this.closeMenu();
|
|
715
901
|
if (target === this.view.dom) this.view.focus();
|
|
716
902
|
else target?.focus?.();
|
|
@@ -818,6 +1004,8 @@ class Chrome {
|
|
|
818
1004
|
const r = m.anchor === this.plus ? (dom?.getBoundingClientRect?.() ?? edBox) : m.anchor.getBoundingClientRect();
|
|
819
1005
|
left = r.left;
|
|
820
1006
|
top = r.bottom + 6;
|
|
1007
|
+
const height = this.menuEl.offsetHeight;
|
|
1008
|
+
if (top + height > this.win.innerHeight - 8 && r.top - 6 - height >= 8) top = r.top - 6 - height;
|
|
821
1009
|
}
|
|
822
1010
|
out.menu = { x: Math.max(8, Math.min(left - shellBox.left, shellBox.width - 280)), y: top - shellBox.top };
|
|
823
1011
|
}
|
|
@@ -856,22 +1044,46 @@ class Chrome {
|
|
|
856
1044
|
}
|
|
857
1045
|
}
|
|
858
1046
|
|
|
1047
|
+
get settingsAPI() {
|
|
1048
|
+
return globalThis.clay?.page?.settings ?? null;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
859
1051
|
update(view, prev) {
|
|
1052
|
+
const settingsHidden = !this.settingsAPI;
|
|
1053
|
+
if (this.settingsBtn.hidden !== settingsHidden) this.settingsBtn.hidden = settingsHidden;
|
|
860
1054
|
this.view = view;
|
|
861
1055
|
if (this.menu && this.menu.slashAt === null && prev && prev.doc !== view.state.doc) {
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (
|
|
1056
|
+
// A menu whose rows resolve their targets by id, from a control the page keeps,
|
|
1057
|
+
// is still the right menu after a peer's edit.
|
|
1058
|
+
if (this.menu.persist && this.menu.anchor?.isConnected) this.scheduleLayout();
|
|
1059
|
+
else {
|
|
1060
|
+
const hadFocus = this.menuEl.contains(document.activeElement);
|
|
1061
|
+
this.closeMenu();
|
|
1062
|
+
if (hadFocus) view.focus();
|
|
1063
|
+
}
|
|
865
1064
|
}
|
|
866
1065
|
const st = view.state;
|
|
867
1066
|
const node = st.doc.child(this.blockIndex());
|
|
868
1067
|
this.turn.textContent = `${blockTypeOf(node).label} ▾`;
|
|
1068
|
+
this.bubbleTurn.textContent = this.turn.textContent;
|
|
869
1069
|
for (const b of this.dock.querySelectorAll("[data-cmd]")) b.setAttribute("aria-pressed", String(toolActive(st, b.dataset.cmd)));
|
|
870
|
-
const
|
|
1070
|
+
const bubbleMenu = !!this.menu && [this.bubbleTurn, this.bubbleMore].includes(this.menu.anchor);
|
|
1071
|
+
const showBubble = bubbleMenu || (!st.selection.empty && st.selection instanceof TextSelection && view.hasFocus());
|
|
871
1072
|
this.bubble.hidden = !showBubble;
|
|
872
1073
|
if (showBubble) for (const b of this.bubble.querySelectorAll("[data-cmd]")) b.setAttribute("aria-pressed", String(toolActive(st, b.dataset.cmd)));
|
|
873
1074
|
this.grip.classList.toggle("is-on", st.selection instanceof BlockSelection);
|
|
874
1075
|
if (!this.inRoving(this.roving)) this.setRoving(this.dockControls()[0]);
|
|
1076
|
+
// A slash word typed before the caret left the block reopens when its own word is
|
|
1077
|
+
// edited again, not when the caret merely returns to it.
|
|
1078
|
+
if (!this.menu && prev && prev.doc !== st.doc && !this.remote && view.hasFocus() && st.selection.empty) {
|
|
1079
|
+
const $head = st.selection.$head;
|
|
1080
|
+
if ($head.depth === 1 && $head.parent.isTextblock && !$head.parent.type.spec.code) {
|
|
1081
|
+
const before = st.doc.textBetween($head.start(), $head.pos);
|
|
1082
|
+
const match = /(?:^|\s)\/([a-z]*)$/i.exec(before);
|
|
1083
|
+
const slashAt = match ? $head.start() + match.index + match[0].indexOf("/") + 1 : null;
|
|
1084
|
+
if (match && this.slashKey(slashAt) !== this.dismissedSlash) this.openMenu("insert", this.plus, slashAt, null, $head.parent.attrs.id);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
875
1087
|
this.slashQuery();
|
|
876
1088
|
this.scheduleLayout();
|
|
877
1089
|
}
|
|
@@ -890,7 +1102,8 @@ class Chrome {
|
|
|
890
1102
|
this.win.visualViewport?.removeEventListener("resize", this.onResize);
|
|
891
1103
|
this.win.visualViewport?.removeEventListener("scroll", this.onResize);
|
|
892
1104
|
document.removeEventListener("mousedown", this.onDocDown, true);
|
|
893
|
-
|
|
1105
|
+
document.removeEventListener("keydown", this.onSettingsKey, true);
|
|
1106
|
+
for (const part of [this.dock, this.plus, this.grip, this.bubble, this.scrim, this.menuEl, this.settingsEl, this.probe]) part?.remove();
|
|
894
1107
|
}
|
|
895
1108
|
}
|
|
896
1109
|
|
|
@@ -903,6 +1116,10 @@ export function chromePlugin({ onAiTool = null } = {}) {
|
|
|
903
1116
|
chromeFor.set(view, chrome);
|
|
904
1117
|
return chrome;
|
|
905
1118
|
},
|
|
1119
|
+
filterTransaction(tr) {
|
|
1120
|
+
if (chrome && tr.docChanged) chrome.remote = !!tr.getMeta(REMOTE);
|
|
1121
|
+
return true;
|
|
1122
|
+
},
|
|
906
1123
|
props: {
|
|
907
1124
|
handleKeyDown(view, event) {
|
|
908
1125
|
if (!chrome) return false;
|
|
@@ -928,7 +1145,7 @@ export function chromePlugin({ onAiTool = null } = {}) {
|
|
|
928
1145
|
handleDOMEvents: {
|
|
929
1146
|
keydown(view, event) { return composing(event, view); },
|
|
930
1147
|
blur(view) {
|
|
931
|
-
if (chrome) chrome.bubble.hidden = true;
|
|
1148
|
+
if (chrome && !(chrome.menu && [chrome.bubbleTurn, chrome.bubbleMore].includes(chrome.menu.anchor))) chrome.bubble.hidden = true;
|
|
932
1149
|
return false;
|
|
933
1150
|
},
|
|
934
1151
|
},
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Lists and tables on the clipboard in the saved article's shape, so another app
|
|
2
|
+
// gets real lists and a header row and a Document reads them back as the same
|
|
3
|
+
// blocks. A copy nests list items as <ul>/<ol> by indent, leads a check item with
|
|
4
|
+
// its checkbox and names a table's columns in a <thead>; a toggle's own list items
|
|
5
|
+
// keep the editor's flat form, which carries their toggle. On paste,
|
|
6
|
+
// flattenPastedHTML turns the lists back into items and moves the slice's open
|
|
7
|
+
// depths (data-pm-slice, left on the dropped <ul>) to the first block, and the
|
|
8
|
+
// table rule in blocks.js reads a table through table() and rows() below; a
|
|
9
|
+
// Document's own table skips pasteTable's grid path for it. The text flavor is
|
|
10
|
+
// markdown list lines (parseListText reads them) and tab-separated table rows.
|
|
11
|
+
// blocks.js loads this module when an editor mounts, off the input-ready path.
|
|
12
|
+
import { model } from "../vendor/prosemirror.vendor.js";
|
|
13
|
+
import { schema } from "./schema.js";
|
|
14
|
+
import { keysBetween, newId, orderedIds } from "./model.js";
|
|
15
|
+
import { clipboardParser } from "./blocks.js";
|
|
16
|
+
|
|
17
|
+
const N = schema.nodes;
|
|
18
|
+
const parentOf = (n) => n.attrs.parent ?? null;
|
|
19
|
+
|
|
20
|
+
const copiedCols = (table) => {
|
|
21
|
+
const used = new Set();
|
|
22
|
+
table.forEach((row) => row.forEach((cell) => used.add(cell.attrs.col)));
|
|
23
|
+
return orderedIds(table.attrs.cols).filter((id) => used.has(id));
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function tableHead(table, dom) {
|
|
27
|
+
const doc = dom.ownerDocument;
|
|
28
|
+
const tr = doc.createElement("thead").appendChild(doc.createElement("tr"));
|
|
29
|
+
for (const id of copiedCols(table)) {
|
|
30
|
+
const col = table.attrs.cols[id], th = tr.appendChild(doc.createElement("th"));
|
|
31
|
+
th.setAttribute("data-col", id);
|
|
32
|
+
th.setAttribute("scope", "col");
|
|
33
|
+
if (col.kind) th.setAttribute("data-kind", col.kind);
|
|
34
|
+
if (col.width) { th.setAttribute("data-width", String(col.width)); th.style.width = `${col.width}px`; }
|
|
35
|
+
th.textContent = col.name ?? "";
|
|
36
|
+
}
|
|
37
|
+
dom.prepend(tr.parentNode);
|
|
38
|
+
if (table.attrs.sort) dom.setAttribute("data-sort", JSON.stringify(table.attrs.sort));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The serializer's top-level elements, one per block of the fragment, rewritten in place.
|
|
42
|
+
export function copy(fragment, doms) {
|
|
43
|
+
let lists = [];
|
|
44
|
+
fragment.forEach((n, _, i) => {
|
|
45
|
+
const dom = doms[i];
|
|
46
|
+
if (!dom || dom.nodeType !== 1) return;
|
|
47
|
+
if (n.type === N.table) tableHead(n, dom);
|
|
48
|
+
if (n.type !== N.list_item || parentOf(n) !== null) { lists = []; return; }
|
|
49
|
+
const doc = dom.ownerDocument, { indent, kind } = n.attrs, tag = kind === "ordered" ? "OL" : "UL";
|
|
50
|
+
while (lists.length && lists.at(-1).indent > indent) lists.pop();
|
|
51
|
+
if (lists.at(-1)?.indent === indent && lists.at(-1).list.tagName !== tag) lists.pop();
|
|
52
|
+
if (!lists.length || lists.at(-1).indent < indent) {
|
|
53
|
+
const list = doc.createElement(tag);
|
|
54
|
+
if (lists.length) lists.at(-1).li.append(list); else dom.before(list);
|
|
55
|
+
lists.push({ indent, list, li: null });
|
|
56
|
+
}
|
|
57
|
+
const li = doc.createElement("li");
|
|
58
|
+
if (kind === "check") li.innerHTML = `<input type="checkbox" disabled${n.attrs.checked ? " checked" : ""}>`;
|
|
59
|
+
li.append(...dom.childNodes);
|
|
60
|
+
lists.at(-1).list.append(li);
|
|
61
|
+
lists.at(-1).li = li;
|
|
62
|
+
dom.remove();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const quoteCell = (text) => (/[\t\n\r"]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text);
|
|
67
|
+
|
|
68
|
+
export function text(slice) {
|
|
69
|
+
if (!slice.content.firstChild?.type.spec.group?.includes("block")) return null;
|
|
70
|
+
let out = "", runs = [], prevList = false;
|
|
71
|
+
slice.content.forEach((n, _, i) => {
|
|
72
|
+
const list = n.type === N.list_item;
|
|
73
|
+
let line = n.textBetween(0, n.content.size, "\n\n");
|
|
74
|
+
if (list) {
|
|
75
|
+
const { indent, kind } = n.attrs;
|
|
76
|
+
while (runs.length && runs.at(-1).indent > indent) runs.pop();
|
|
77
|
+
if (runs.at(-1)?.indent === indent && runs.at(-1).kind !== kind) runs.pop();
|
|
78
|
+
if (!runs.length || runs.at(-1).indent < indent) runs.push({ indent, kind, n: 0 });
|
|
79
|
+
const run = runs.at(-1);
|
|
80
|
+
const marker = kind === "ordered" ? `${++run.n}.` : kind === "check" ? `- [${n.attrs.checked ? "x" : " "}]` : "-";
|
|
81
|
+
line = `${" ".repeat(runs.length - 1)}${marker} ${line}`;
|
|
82
|
+
} else runs = [];
|
|
83
|
+
if (n.type === N.table) {
|
|
84
|
+
const rows = [copiedCols(n).map((id) => quoteCell(n.attrs.cols[id].name ?? ""))];
|
|
85
|
+
n.forEach((row) => { const cells = []; row.forEach((cell) => cells.push(quoteCell(cell.textContent))); rows.push(cells); });
|
|
86
|
+
line = rows.map((row) => row.join("\t")).join("\n");
|
|
87
|
+
}
|
|
88
|
+
out += (i ? (prevList && list ? "\n" : "\n\n") : "") + line;
|
|
89
|
+
prevList = list;
|
|
90
|
+
});
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// A pasted table: its header row (a <thead> row, or a first row of <th>) names
|
|
95
|
+
// the columns, and a copied one keeps each column's id, kind and width; every
|
|
96
|
+
// other row is a row of cells. Rows get their ids and order keys from identityPlugin.
|
|
97
|
+
const tables = new WeakMap();
|
|
98
|
+
function pastedTable(dom) {
|
|
99
|
+
if (tables.has(dom)) return tables.get(dom);
|
|
100
|
+
const cellsOf = (tr) => [...tr.children].filter((c) => c.tagName === "TD" || c.tagName === "TH");
|
|
101
|
+
const trs = [...dom.querySelectorAll("tr")].filter((tr) => tr.closest("table") === dom);
|
|
102
|
+
const head = trs.find((tr) => tr.parentElement.tagName === "THEAD") ?? (trs.length > 1 && cellsOf(trs[0]).every((c) => c.tagName === "TH") ? trs[0] : null);
|
|
103
|
+
const body = trs.filter((tr) => tr !== head);
|
|
104
|
+
const names = head ? cellsOf(head) : [];
|
|
105
|
+
const width = Math.max(1, names.length, ...body.map((tr) => cellsOf(tr).length));
|
|
106
|
+
const keys = keysBetween(null, null, width);
|
|
107
|
+
const ids = [], cols = {};
|
|
108
|
+
for (let i = 0; i < width; i++) {
|
|
109
|
+
const th = names[i];
|
|
110
|
+
let id = th?.getAttribute("data-col");
|
|
111
|
+
if (!id || Object.hasOwn(cols, id)) id = newId("c");
|
|
112
|
+
const kind = th?.getAttribute("data-kind"), w = Math.round(Number(th?.getAttribute("data-width")));
|
|
113
|
+
cols[id] = { pos: keys[i], name: th?.textContent.replace(/\s+/g, " ").trim() || `Column ${i + 1}`, ...(kind ? { kind } : {}), ...(w > 0 ? { width: w } : {}) };
|
|
114
|
+
ids.push(id);
|
|
115
|
+
}
|
|
116
|
+
const cell = (td, col) => (td ? clipboardParser.parse(td, { topNode: N.table_cell.create({ col }) }) : N.table_cell.create({ col }));
|
|
117
|
+
const rows = (body.length ? body : [null]).map((tr) => N.table_row.create(null, ids.map((col, i) => cell(tr && cellsOf(tr)[i], col))));
|
|
118
|
+
let sort = null;
|
|
119
|
+
try { sort = JSON.parse(dom.getAttribute("data-sort")); } catch {}
|
|
120
|
+
const found = { cols, sort: sort && ids.includes(sort.col) && (sort.dir === "asc" || sort.dir === "desc") ? { col: sort.col, dir: sort.dir } : null, rows: model.Fragment.from(rows) };
|
|
121
|
+
tables.set(dom, found);
|
|
122
|
+
return found;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const table = (dom) => ({ cols: pastedTable(dom).cols, sort: pastedTable(dom).sort, parent: dom.getAttribute("data-page-parent") || null });
|
|
126
|
+
export const rows = (dom) => pastedTable(dom).rows;
|