@panphora/clayjs 1.17.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 +72474 -62090
- package/dist/m/attrs/{save-freeze-MYNVXREN.js → save-freeze-LWGF6XUN.js} +1 -1
- package/dist/m/c-2GRUM77Q.js +30 -0
- package/dist/m/c-2KIEGNC4.js +1 -0
- package/dist/m/{c-AMSNPOCX.js → c-2KL3BD52.js} +1 -1
- 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-KAU6E43D.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-3XIRD4NU.js → c-5T6NZHNM.js} +1 -1
- package/dist/m/{c-74MGYWZF.js → c-5ZQV7KC4.js} +1 -1
- package/dist/m/{c-7OLLJPWP.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-Y5HBBIQ4.js → c-AMYEFA2E.js} +1 -1
- package/dist/m/c-AQYCG7QW.js +1 -0
- package/dist/m/c-ATKAOH23.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-VHM5XQHK.js → c-BIJALSRD.js} +1 -1
- package/dist/m/c-BJ7YQ5QZ.js +1 -0
- package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
- package/dist/m/{c-VE6ZMYMC.js → c-BUAYFEV5.js} +1 -1
- package/dist/m/c-C6IPRG34.js +1 -0
- package/dist/m/{c-KU3ZFADW.js → c-CAS3NND4.js} +1 -1
- package/dist/m/{c-PS5ARVWR.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-QJ4OM3G6.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-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-TJ6WOJL3.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-WDC5GVKU.js → c-IVTRVENZ.js} +1 -1
- package/dist/m/c-JKZJIUIP.js +1 -0
- package/dist/m/{c-UDUUJPFZ.js → c-JNLX3RJB.js} +1 -1
- package/dist/m/c-K3ADYNLU.js +1 -0
- package/dist/m/c-KGHVFRR3.js +1 -0
- package/dist/m/{c-5QWQ7A5L.js → c-KJJPV77R.js} +1 -1
- package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
- package/dist/m/{c-EZU56NQV.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-3RUU4TPX.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-5RJFTLSF.js → c-NEL7HQW3.js} +1 -1
- package/dist/m/{c-DCXOXBF3.js → c-NQJRVJ56.js} +1 -1
- 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-R3L7ZJGB.js → c-OPLUZR3A.js} +1 -1
- package/dist/m/{c-GSFIGKOP.js → c-OTMKN2OO.js} +1 -1
- package/dist/m/{c-6NC5I2TV.js → c-OYRZ7XG2.js} +1 -1
- package/dist/m/{c-EVCQ3BKA.js → c-P3OFYUMA.js} +1 -1
- 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-FRLHSY7E.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-JU3NK25H.js → c-R27DWQKX.js} +1 -1
- package/dist/m/{c-RSMOODDU.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-Q622NS2Y.js → c-S2U6NJU4.js} +1 -1
- package/dist/m/c-SPZPGD3R.js +1 -0
- package/dist/m/c-SW4YEG2D.js +8 -0
- package/dist/m/c-T637T7SN.js +1 -0
- package/dist/m/c-TJAXGIRV.js +1 -0
- package/dist/m/{c-RYUXUUBX.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-QICP2YR5.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-VILENZTD.js +1 -0
- package/dist/m/{c-HDDEEL77.js → c-VJYEVRYK.js} +1 -1
- package/dist/m/{c-SMBZG5X4.js → c-VR2BJ4AN.js} +1 -1
- 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-I2ZATQCD.js → c-X5TSXZYJ.js} +2 -2
- 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/core/{admin-attrs-QHOKPJ7W.js → admin-attrs-YY7T5FGU.js} +1 -1
- 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-FNXVEFNE.js → host-permission-6EVJGNPL.js} +1 -1
- package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
- package/dist/m/core/{persist-JFYRHMRJ.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/internals/{index-PVXGPUDA.js → index-TGLQINN3.js} +1 -1
- 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 +2111 -1573
- package/dist/m/page/{editor-NNJA4OKD.js → editor-6LRB3GE4.js} +123 -21
- 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-RM5IR6AO.js → demo-OYO5GCAO.js} +1 -1
- 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-SGPJKANK.js → people-JCHVPF7P.js} +1 -1
- package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
- package/dist/m/plugins/{richclay-NRQDSWX6.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-WLYKK6TL.js → upload-NHGJXZK2.js} +1 -1
- 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-4N6GYAF6.js → wire-VBWXEXU2.js} +1 -1
- 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 +1367 -930
- package/dist/page/themes/light-1.css +8 -7
- package/dist/server/core.cjs +1440 -288
- 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/conflict-notice-ui.js +4 -7
- package/src/core/download.js +2 -2
- package/src/core/host-meta.js +38 -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 +106 -31
- 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 +170 -39
- 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/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 +89 -27
- package/src/page/boot-pack.js +1 -1
- package/src/page/chart-tables.js +21 -3
- package/src/page/chart.js +406 -63
- package/src/page/checklist.js +10 -135
- package/src/page/checkpoint-proof.js +7 -1
- package/src/page/chrome.js +250 -51
- package/src/page/clipboard-shape.js +126 -0
- package/src/page/drag.js +33 -3
- package/src/page/editor.js +136 -25
- 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/checklist.js +428 -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/registry.js +18 -0
- package/src/page/features/table.js +373 -83
- 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 +25 -1
- package/src/page/format-gate.js +68 -18
- package/src/page/format-host.js +17 -0
- package/src/page/format-native-commands.js +21 -5
- package/src/page/image-actions.js +64 -0
- package/src/page/import-markdown.js +262 -0
- package/src/page/items.js +135 -17
- package/src/page/markdown.js +6 -2
- package/src/page/media.js +35 -119
- package/src/page/placeholder.js +14 -1
- package/src/page/projection.js +98 -10
- 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 +28 -0
- package/src/page/save-states.js +13 -3
- package/src/page/schema.js +19 -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/light-1.css +8 -7
- 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 +38 -12
- package/src/page/view.js +13 -0
- package/src/page/ybind/binding.js +88 -5
- 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 +1 -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 +113 -17
- package/src/page/ybind/recovery-proof.js +32 -9
- package/src/page/ybind/recovery-replay.js +558 -15
- 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 +89 -14
- 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 +249 -44
- 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 +5 -3
- 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/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 +11 -6
- 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/c-2SPVQSHC.js +0 -1
- package/dist/m/c-336SGU5W.js +0 -1
- package/dist/m/c-3H6BZDNJ.js +0 -6
- package/dist/m/c-4DFEJ2PO.js +0 -3
- package/dist/m/c-56QRZTZH.js +0 -1
- package/dist/m/c-5J4E7O4Y.js +0 -4
- package/dist/m/c-5LKDWQTL.js +0 -19
- package/dist/m/c-5V3MOVJT.js +0 -1
- package/dist/m/c-6AZOGAPE.js +0 -1
- package/dist/m/c-6CRUUXOX.js +0 -5
- package/dist/m/c-6HMB24EV.js +0 -1
- package/dist/m/c-6KEVEQ7L.js +0 -1
- package/dist/m/c-A3E3NIMC.js +0 -1
- package/dist/m/c-BQ6LOBEE.js +0 -1
- package/dist/m/c-BTDI4Y23.js +0 -1
- package/dist/m/c-CJ3A7ZKE.js +0 -1
- package/dist/m/c-CQHPGKX2.js +0 -1
- package/dist/m/c-DFS4XZJV.js +0 -2
- package/dist/m/c-DNENWY4O.js +0 -1
- package/dist/m/c-E4LIR5X5.js +0 -1
- package/dist/m/c-FDNHHZY5.js +0 -1
- package/dist/m/c-FROJUZMV.js +0 -1
- package/dist/m/c-G2JPKCTP.js +0 -1
- package/dist/m/c-GB6TU7WV.js +0 -1
- package/dist/m/c-GFJJEBTU.js +0 -1
- package/dist/m/c-GJAEGGXY.js +0 -1
- package/dist/m/c-GOWBDM6J.js +0 -1
- package/dist/m/c-HFKUVACH.js +0 -1
- package/dist/m/c-HHDFCNHV.js +0 -1
- package/dist/m/c-IEM4NTCS.js +0 -1
- package/dist/m/c-JDCX26UP.js +0 -25
- package/dist/m/c-KGVGROZT.js +0 -1
- package/dist/m/c-KQ6TYJAP.js +0 -135
- package/dist/m/c-LFPHCXY3.js +0 -1
- package/dist/m/c-LRPUO2MX.js +0 -1
- package/dist/m/c-LWLBHLVC.js +0 -3
- package/dist/m/c-M2L47S2X.js +0 -7
- package/dist/m/c-MFP2ZWD4.js +0 -1
- package/dist/m/c-MKSQ4Y57.js +0 -1
- package/dist/m/c-NUFFJVVJ.js +0 -1
- package/dist/m/c-NUHSPT5P.js +0 -1
- package/dist/m/c-O72HVYSA.js +0 -1
- package/dist/m/c-OIBUTDXI.js +0 -1
- package/dist/m/c-OW5QIGG3.js +0 -1
- package/dist/m/c-PGRCK73F.js +0 -1
- package/dist/m/c-Q7NQD65V.js +0 -1
- package/dist/m/c-QH63LQOS.js +0 -1
- package/dist/m/c-R7IWVINQ.js +0 -1
- package/dist/m/c-RM7J7BV2.js +0 -1
- package/dist/m/c-S4ZEH46B.js +0 -9
- package/dist/m/c-SDAXDM2D.js +0 -1
- package/dist/m/c-SMGU7RT3.js +0 -235
- package/dist/m/c-SPIORRYS.js +0 -1
- package/dist/m/c-SWLRT66R.js +0 -1
- package/dist/m/c-TCJTVAFN.js +0 -1
- package/dist/m/c-TQEMVJCS.js +0 -1
- package/dist/m/c-TYLXX4NF.js +0 -21
- package/dist/m/c-U62OB4BT.js +0 -1
- package/dist/m/c-U6YWUAR3.js +0 -8
- package/dist/m/c-UDCSU5G5.js +0 -2
- package/dist/m/c-VI5RMSVY.js +0 -1
- package/dist/m/c-VRZN56XY.js +0 -1
- package/dist/m/c-W5MCHEGA.js +0 -1
- package/dist/m/c-WNYBLIOZ.js +0 -1
- package/dist/m/c-WWXIDH4O.js +0 -28
- package/dist/m/c-XCNTK76U.js +0 -1
- package/dist/m/c-XMS6DPBR.js +0 -1
- package/dist/m/c-ZUCOKOHT.js +0 -1
- package/dist/m/c-ZVPVXTJF.js +0 -4
- package/dist/m/c-ZVRKXKM7.js +0 -1
- package/dist/m/core/autosave-HRYGUWV6.js +0 -1
- package/dist/m/core/conflict-notice-6MROJN2L.js +0 -1
- package/dist/m/core/is-edit-mode-KTWA3O5N.js +0 -1
- package/dist/m/core/recovery-FLTCC2OC.js +0 -1
- package/dist/m/core/save-HQZ4S667.js +0 -1
- package/dist/m/core/save-core-KGQWFUNH.js +0 -1
- package/dist/m/core/snapshot-EGQZNT45.js +0 -1
- package/dist/m/core/unsaved-warning-IDJLD66H.js +0 -1
- package/dist/m/loader-S4I7GKXR.js +0 -1
- package/dist/m/manifest-4C419D67.js +0 -1
- package/dist/m/manifest-edit-B077F288.js +0 -1
- package/dist/m/plugins/ai-edit-QN4PMMNL.js +0 -6
- package/dist/m/plugins/indicator-URDBBOQO.js +0 -1
- package/dist/m/plugins/live-HFWD4OIH.js +0 -1
- package/dist/m/plugins/menu-3HIEQSI6.js +0 -138
- package/dist/m/plugins/page-JOKB6XPR.js +0 -2
- package/dist/m/plugins/page-view-NXOJHNW6.js +0 -1
- package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
- package/dist/m/plugins/sortable-GWT4PWDV.js +0 -1
- package/dist/m/plugins/source-4ZRSAIYM.js +0 -1
- package/dist/m/plugins/undo-VDA5P7FH.js +0 -1
- package/dist/m/plugins/upgrade-W7FG5VFG.js +0 -1
- package/dist/m/plugins/visitor-drafts-LIDY7NH6.js +0 -21
- package/dist/m/plugins/visitor-drafts-owner-ZXCKBZE7.js +0 -1
- package/dist/m/reader-core-DXYCURFF.js +0 -68
- package/dist/m/route/route-KOCZSEGN.js +0 -1
- package/dist/m/sync/live-sync-J7VMYTKS.js +0 -1
- package/dist/m/ui/index-3JLVI27O.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-NRQDSWX6.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
package/src/page/chrome.js
CHANGED
|
@@ -8,15 +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, loadChartPart } 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
15
|
import { pickCsv } from "./csv-import.js";
|
|
17
16
|
import { HISTORY_INTENT, typingIntent } from "./history-intent.js";
|
|
18
17
|
import { composing } from "./markdown.js";
|
|
19
18
|
import { announce } from "./announce.js";
|
|
19
|
+
import { REMOTE } from "./model.js";
|
|
20
20
|
|
|
21
21
|
export { BLOCK_TYPES, blockTypeOf, turnInto, insertAfter } from "./items.js";
|
|
22
22
|
|
|
@@ -73,20 +73,24 @@ function copyBlockLink(id) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
export const ACTION_ITEMS = [
|
|
76
|
-
{ key: "up", label: "Move up", icon: "
|
|
77
|
-
{ key: "down", label: "Move down", icon: "
|
|
78
|
-
{ key: "indent", label: "Indent", icon: "
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{ key: "
|
|
84
|
-
{ key: "
|
|
85
|
-
{ key: "
|
|
86
|
-
{ key: "
|
|
87
|
-
{ key: "
|
|
88
|
-
{ key: "
|
|
89
|
-
{ 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) },
|
|
90
94
|
];
|
|
91
95
|
|
|
92
96
|
const actionsFor = (st) => {
|
|
@@ -111,6 +115,21 @@ const ICONS = {
|
|
|
111
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"/>'),
|
|
112
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"/>'),
|
|
113
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"/>'),
|
|
114
133
|
};
|
|
115
134
|
const TOOLS = [
|
|
116
135
|
["strong", "Bold"],
|
|
@@ -128,6 +147,7 @@ const PHONE_TOOLS = new Set(["strong", "em", "link"]);
|
|
|
128
147
|
const MORE_ACTIONS = new Set(["up", "down", "indent", "outdent"]);
|
|
129
148
|
const PHONE_BAR_WIDTH = 204, PHONE_BAR_HEIGHT = 52, PHONE_INSET = 8;
|
|
130
149
|
const LIST_KINDS = new Set(["bullet", "ordered", "check"]);
|
|
150
|
+
const SETTING_ROWS = [["assignees", "Checklist assignees"], ["dates", "Checklist due dates"]];
|
|
131
151
|
const LINE_TYPES = new Set(["paragraph", "heading", "list_item"]);
|
|
132
152
|
|
|
133
153
|
// The top-level blocks a toolbar command acts on. A nonempty selection's end is
|
|
@@ -219,7 +239,7 @@ function runList(view, kind) {
|
|
|
219
239
|
// The four formats this release adds are commands the format gate may refuse, and
|
|
220
240
|
// a refusal is a notice rather than a change. The menu that offers them, and the
|
|
221
241
|
// 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));
|
|
242
|
+
const nativeMenu = (view, chrome, name) => import("./format-native-commands.js").then((menu) => menu.nativeMenu(view, name, chrome.pickedFrom ?? chrome.more));
|
|
223
243
|
|
|
224
244
|
function runMark(view, name) {
|
|
225
245
|
if (name === "link") return openLinkEditor(view);
|
|
@@ -281,15 +301,28 @@ export const heldSlashRange = (st) => {
|
|
|
281
301
|
return held ? { from: held.from, to: held.to } : null;
|
|
282
302
|
};
|
|
283
303
|
|
|
284
|
-
export function openPageMenu(view, anchor, items) {
|
|
304
|
+
export function openPageMenu(view, anchor, items, { persist = false } = {}) {
|
|
285
305
|
const chrome = chromeFor.get(view);
|
|
286
306
|
if (!chrome) return false;
|
|
287
307
|
chrome.openMenu("external", anchor);
|
|
288
308
|
chrome.menu.externalItems = items;
|
|
309
|
+
chrome.menu.persist = persist;
|
|
289
310
|
chrome.renderMenu();
|
|
290
311
|
return true;
|
|
291
312
|
}
|
|
292
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
|
+
|
|
293
326
|
class Chrome {
|
|
294
327
|
constructor(view, onAiTool) {
|
|
295
328
|
this.view = view;
|
|
@@ -305,18 +338,32 @@ class Chrome {
|
|
|
305
338
|
this.destroyed = false;
|
|
306
339
|
this.probe = null;
|
|
307
340
|
this.roving = null;
|
|
341
|
+
this.remote = false;
|
|
308
342
|
|
|
309
343
|
this.dock = el("div", "page-dock", { role: "toolbar", "aria-label": "Formatting" });
|
|
310
344
|
this.turn = el("button", "page-tool page-tool-turn page-tool-desk", { type: "button", "aria-haspopup": "menu" });
|
|
311
345
|
this.more = el("button", "page-tool page-tool-primary page-tool-more", { type: "button", "aria-label": "More", title: "More", "aria-haspopup": "menu" });
|
|
312
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 = {};
|
|
313
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;
|
|
314
358
|
this.plus = el("button", "page-plus", { type: "button", "aria-label": "Add a block", title: "Add a block" });
|
|
315
359
|
this.plus.innerHTML = ICONS.plus;
|
|
316
360
|
this.grip = el("button", "page-grip", { type: "button", "aria-label": "Block actions", title: "Drag to move, click for actions", "aria-haspopup": "menu" });
|
|
317
361
|
this.grip.innerHTML = ICONS.grip;
|
|
318
362
|
this.bubble = el("div", "page-bubble", { role: "toolbar", "aria-label": "Selection", hidden: "" });
|
|
319
|
-
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);
|
|
320
367
|
this.menuId = `page-menu-${++menuCount}`;
|
|
321
368
|
this.menuEl = el("div", "page-menu", { role: "menu", id: this.menuId, hidden: "" });
|
|
322
369
|
this.scrim = el("div", "page-scrim", { hidden: "" });
|
|
@@ -333,13 +380,23 @@ class Chrome {
|
|
|
333
380
|
}
|
|
334
381
|
this.turn.addEventListener("click", () => this.toggleMenu("turn", this.turn));
|
|
335
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));
|
|
336
386
|
this.dock.addEventListener("keydown", (e) => this.dockKey(e));
|
|
337
387
|
this.plus.addEventListener("click", () => this.fromPlus());
|
|
338
388
|
this.grip.addEventListener("click", (e) => {
|
|
339
389
|
if (e.detail === 0) this.gripAction(this.gutterIndex());
|
|
340
390
|
});
|
|
341
391
|
this.drag = new BlockDrag(view, this.grip, { index: () => this.gutterIndex(), onClick: (i) => this.gripAction(i) });
|
|
342
|
-
|
|
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
|
+
});
|
|
343
400
|
this.menuEl.addEventListener("click", (e) => {
|
|
344
401
|
const row = e.target.closest(".page-menu-item");
|
|
345
402
|
if (row) this.pick(Number(row.dataset.index));
|
|
@@ -360,13 +417,21 @@ class Chrome {
|
|
|
360
417
|
this.setHover(null);
|
|
361
418
|
};
|
|
362
419
|
this.onLeave = () => { this.pendingHover = null; this.setHover(null); };
|
|
363
|
-
this.onResize = () => this.scheduleLayout();
|
|
420
|
+
this.onResize = () => { this.scheduleLayout(); this.placeSettings(); };
|
|
364
421
|
this.onDocDown = (e) => {
|
|
422
|
+
if (this.settingsOpen && ![this.settingsEl, this.settingsAnchor].some((part) => part?.contains(e.target))) this.closeSettings();
|
|
365
423
|
if (!this.menu) return;
|
|
366
|
-
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;
|
|
367
425
|
this.closeMenu();
|
|
368
426
|
};
|
|
427
|
+
this.onSettingsKey = (e) => {
|
|
428
|
+
if (e.key !== "Escape" || !this.settingsOpen) return;
|
|
429
|
+
e.preventDefault();
|
|
430
|
+
e.stopPropagation();
|
|
431
|
+
this.closeSettings(true);
|
|
432
|
+
};
|
|
369
433
|
document.addEventListener("mousedown", this.onDocDown, true);
|
|
434
|
+
document.addEventListener("keydown", this.onSettingsKey, true);
|
|
370
435
|
this.shell.addEventListener("mousemove", this.onMove);
|
|
371
436
|
this.shell.addEventListener("mouseleave", this.onLeave);
|
|
372
437
|
this.shell.addEventListener("keydown", this.onKey);
|
|
@@ -471,7 +536,7 @@ class Chrome {
|
|
|
471
536
|
const m = this.menu;
|
|
472
537
|
if (!m || m.slashAt !== null) return this.view.dom;
|
|
473
538
|
const a = m.anchor;
|
|
474
|
-
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;
|
|
475
540
|
}
|
|
476
541
|
|
|
477
542
|
// A block action from the phone menu acts on the caret's block when nothing
|
|
@@ -526,13 +591,116 @@ class Chrome {
|
|
|
526
591
|
this.openMenu("insert", this.plus, null, null, this.view.state.doc.child(i).attrs.id);
|
|
527
592
|
}
|
|
528
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
|
+
|
|
529
689
|
toggleMenu(mode, anchor) {
|
|
530
690
|
if (this.menu) this.closeMenu();
|
|
531
691
|
else this.openMenu(mode, anchor);
|
|
532
692
|
}
|
|
533
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
|
+
|
|
534
701
|
openMenu(mode, anchor, slashAt = null, ids = null, anchorId = null) {
|
|
535
|
-
this.
|
|
702
|
+
this.closeSettings();
|
|
703
|
+
this.menu = { mode, anchor, slashAt, anchorId, ids: ids ?? (mode === "insert" ? [] : this.selectionIds()), query: "", active: 0, items: [], persist: false };
|
|
536
704
|
anchor.setAttribute("aria-expanded", "true");
|
|
537
705
|
if (slashAt !== null) {
|
|
538
706
|
if (!this.priorAria) this.priorAria = POPUP_ATTRS.map((name) => [name, this.view.dom.getAttribute(name)]);
|
|
@@ -559,17 +727,23 @@ class Chrome {
|
|
|
559
727
|
// Everything the phone toolbar folds away: the rest of the formatting, and the
|
|
560
728
|
// block actions the desktop menu carries. The move commands are the same ones.
|
|
561
729
|
moreItems() {
|
|
562
|
-
const
|
|
563
|
-
|
|
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) })) : [];
|
|
564
733
|
const actions = ACTION_ITEMS.filter((a) => MORE_ACTIONS.has(a.key)).map((a) => ({ ...a, blocks: true }));
|
|
565
734
|
return [
|
|
566
735
|
...tools,
|
|
567
736
|
{ key: "underline", label: "Underline", icon: "U", hint: `${MOD}u`, run: (view, chrome) => nativeMenu(view, chrome, "underline") },
|
|
568
737
|
{ key: "text_color", label: "Text color", icon: "A", hint: "\u203a", run: (view, chrome) => nativeMenu(view, chrome, "text_color") },
|
|
569
738
|
{ key: "highlight", label: "Highlight", icon: "\u25A0", hint: "\u203a", run: (view, chrome) => nativeMenu(view, chrome, "highlight") },
|
|
570
|
-
{ key: "turn", label: "Turn into", icon: "
|
|
571
|
-
{ key: "insert", label: "Insert", icon: "+", run: (view, chrome) => chrome.openMenu("insert", chrome.more, null, null, view.state.doc.child(chrome.blockIndex()).attrs.id) },
|
|
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) },
|
|
572
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") },
|
|
573
747
|
];
|
|
574
748
|
}
|
|
575
749
|
|
|
@@ -626,7 +800,8 @@ class Chrome {
|
|
|
626
800
|
const role = m.mode === "turn" ? "menuitemradio" : typeof checked === "boolean" ? "menuitemcheckbox" : slash ? "option" : "menuitem";
|
|
627
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) } : {}) });
|
|
628
802
|
const icon = el("span", "page-menu-icon");
|
|
629
|
-
icon.
|
|
803
|
+
if (ICONS[b.icon]) icon.innerHTML = ICONS[b.icon];
|
|
804
|
+
else icon.textContent = b.icon || "";
|
|
630
805
|
const label = el("span", "page-menu-label");
|
|
631
806
|
label.textContent = b.label;
|
|
632
807
|
if (b.description) {
|
|
@@ -649,7 +824,7 @@ class Chrome {
|
|
|
649
824
|
this.menuEl.hidden = false;
|
|
650
825
|
this.scrim.hidden = false;
|
|
651
826
|
this.scheduleLayout();
|
|
652
|
-
if (!slash) this.menuEl.querySelectorAll("button")[m.active]?.focus();
|
|
827
|
+
if (!slash) this.menuEl.querySelectorAll("button")[m.active]?.focus({ preventScroll: true });
|
|
653
828
|
}
|
|
654
829
|
|
|
655
830
|
pick(i) {
|
|
@@ -663,6 +838,8 @@ class Chrome {
|
|
|
663
838
|
const wanted = m.mode === "insert" ? (m.anchorId ? [m.anchorId] : []) : m.ids;
|
|
664
839
|
const stale = wanted.some((id) => !id || this.idIndex(id) < 0);
|
|
665
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;
|
|
666
843
|
this.closeMenu();
|
|
667
844
|
if (stale && m.mode !== "external") {
|
|
668
845
|
announce(this.view, TARGET_GONE);
|
|
@@ -675,21 +852,7 @@ class Chrome {
|
|
|
675
852
|
this.view.focus();
|
|
676
853
|
return;
|
|
677
854
|
}
|
|
678
|
-
|
|
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();
|
|
692
|
-
this.view.focus();
|
|
855
|
+
turnBlocks(this.view, item, wanted);
|
|
693
856
|
} else if (m.mode === "actions" || m.mode === "more" || m.mode === "external") {
|
|
694
857
|
if (item.blocks) this.selectCaretBlock(wanted);
|
|
695
858
|
item.run(this.view, this, index >= 0 ? index : this.blockIndex());
|
|
@@ -727,8 +890,13 @@ class Chrome {
|
|
|
727
890
|
if (m.items.length) this.pick(m.active);
|
|
728
891
|
return true;
|
|
729
892
|
}
|
|
893
|
+
if (event.key === "Tab" && m.slashAt === null) {
|
|
894
|
+
this.closeMenu();
|
|
895
|
+
return false;
|
|
896
|
+
}
|
|
730
897
|
if (event.key === "Escape") {
|
|
731
898
|
const target = this.invoker();
|
|
899
|
+
if (m.slashAt !== null) this.dismissedSlash = this.slashKey(m.slashAt);
|
|
732
900
|
this.closeMenu();
|
|
733
901
|
if (target === this.view.dom) this.view.focus();
|
|
734
902
|
else target?.focus?.();
|
|
@@ -836,6 +1004,8 @@ class Chrome {
|
|
|
836
1004
|
const r = m.anchor === this.plus ? (dom?.getBoundingClientRect?.() ?? edBox) : m.anchor.getBoundingClientRect();
|
|
837
1005
|
left = r.left;
|
|
838
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;
|
|
839
1009
|
}
|
|
840
1010
|
out.menu = { x: Math.max(8, Math.min(left - shellBox.left, shellBox.width - 280)), y: top - shellBox.top };
|
|
841
1011
|
}
|
|
@@ -874,22 +1044,46 @@ class Chrome {
|
|
|
874
1044
|
}
|
|
875
1045
|
}
|
|
876
1046
|
|
|
1047
|
+
get settingsAPI() {
|
|
1048
|
+
return globalThis.clay?.page?.settings ?? null;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
877
1051
|
update(view, prev) {
|
|
1052
|
+
const settingsHidden = !this.settingsAPI;
|
|
1053
|
+
if (this.settingsBtn.hidden !== settingsHidden) this.settingsBtn.hidden = settingsHidden;
|
|
878
1054
|
this.view = view;
|
|
879
1055
|
if (this.menu && this.menu.slashAt === null && prev && prev.doc !== view.state.doc) {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
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
|
+
}
|
|
883
1064
|
}
|
|
884
1065
|
const st = view.state;
|
|
885
1066
|
const node = st.doc.child(this.blockIndex());
|
|
886
1067
|
this.turn.textContent = `${blockTypeOf(node).label} ▾`;
|
|
1068
|
+
this.bubbleTurn.textContent = this.turn.textContent;
|
|
887
1069
|
for (const b of this.dock.querySelectorAll("[data-cmd]")) b.setAttribute("aria-pressed", String(toolActive(st, b.dataset.cmd)));
|
|
888
|
-
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());
|
|
889
1072
|
this.bubble.hidden = !showBubble;
|
|
890
1073
|
if (showBubble) for (const b of this.bubble.querySelectorAll("[data-cmd]")) b.setAttribute("aria-pressed", String(toolActive(st, b.dataset.cmd)));
|
|
891
1074
|
this.grip.classList.toggle("is-on", st.selection instanceof BlockSelection);
|
|
892
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
|
+
}
|
|
893
1087
|
this.slashQuery();
|
|
894
1088
|
this.scheduleLayout();
|
|
895
1089
|
}
|
|
@@ -908,7 +1102,8 @@ class Chrome {
|
|
|
908
1102
|
this.win.visualViewport?.removeEventListener("resize", this.onResize);
|
|
909
1103
|
this.win.visualViewport?.removeEventListener("scroll", this.onResize);
|
|
910
1104
|
document.removeEventListener("mousedown", this.onDocDown, true);
|
|
911
|
-
|
|
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();
|
|
912
1107
|
}
|
|
913
1108
|
}
|
|
914
1109
|
|
|
@@ -921,6 +1116,10 @@ export function chromePlugin({ onAiTool = null } = {}) {
|
|
|
921
1116
|
chromeFor.set(view, chrome);
|
|
922
1117
|
return chrome;
|
|
923
1118
|
},
|
|
1119
|
+
filterTransaction(tr) {
|
|
1120
|
+
if (chrome && tr.docChanged) chrome.remote = !!tr.getMeta(REMOTE);
|
|
1121
|
+
return true;
|
|
1122
|
+
},
|
|
924
1123
|
props: {
|
|
925
1124
|
handleKeyDown(view, event) {
|
|
926
1125
|
if (!chrome) return false;
|
|
@@ -946,7 +1145,7 @@ export function chromePlugin({ onAiTool = null } = {}) {
|
|
|
946
1145
|
handleDOMEvents: {
|
|
947
1146
|
keydown(view, event) { return composing(event, view); },
|
|
948
1147
|
blur(view) {
|
|
949
|
-
if (chrome) chrome.bubble.hidden = true;
|
|
1148
|
+
if (chrome && !(chrome.menu && [chrome.bubbleTurn, chrome.bubbleMore].includes(chrome.menu.anchor))) chrome.bubble.hidden = true;
|
|
950
1149
|
return false;
|
|
951
1150
|
},
|
|
952
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;
|