@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/items.js
CHANGED
|
@@ -14,6 +14,7 @@ import { pickCsv } from "./csv-import.js";
|
|
|
14
14
|
import { makeBoard, insertBoardNode } from "./board.js";
|
|
15
15
|
import { chartTables } from "./chart-tables.js";
|
|
16
16
|
import { viewFeatures } from "./view-hooks.js";
|
|
17
|
+
import { runFormatted } from "./format-command.js";
|
|
17
18
|
|
|
18
19
|
const { TextSelection } = state;
|
|
19
20
|
const N = schema.nodes;
|
|
@@ -27,6 +28,9 @@ export const BLOCK_TYPES = [
|
|
|
27
28
|
{ key: "ordered", label: "Numbered list", description: "A list with numbers", icon: "1.", type: "list_item", attrs: { kind: "ordered" }, hint: "1.", aliases: ["ol", "num", "numbered", "ordered"] },
|
|
28
29
|
{ key: "check", label: "Checklist", description: "Tasks to tick off", icon: "☐", type: "list_item", attrs: { kind: "check" }, hint: "[]", aliases: ["todo", "to-do", "task", "check", "checkbox"] },
|
|
29
30
|
{ key: "toggle", label: "Toggle", description: "Hide details under a line", icon: "▸", type: "toggle", attrs: { open: true }, hint: ">", aliases: ["collapse", "details", "fold"] },
|
|
31
|
+
{ key: "toggle-h1", label: "Toggle heading 1", description: "Collapsible heading", icon: "H1", type: "toggle", attrs: { open: true, level: 1 }, aliases: ["toggle heading"] },
|
|
32
|
+
{ key: "toggle-h2", label: "Toggle heading 2", description: "Collapsible heading", icon: "H2", type: "toggle", attrs: { open: true, level: 2 }, aliases: ["toggle heading"] },
|
|
33
|
+
{ key: "toggle-h3", label: "Toggle heading 3", description: "Collapsible heading", icon: "H3", type: "toggle", attrs: { open: true, level: 3 }, aliases: ["toggle heading"] },
|
|
30
34
|
{ key: "callout", label: "Callout", description: "Make a note stand out", icon: "!", type: "callout", aliases: ["note", "aside", "info"] },
|
|
31
35
|
{ key: "quote", label: "Quote", description: "Set text apart as a quotation", icon: "\u201c", type: "quote", hint: "\"", aliases: ["blockquote", "quotation", "cite"] },
|
|
32
36
|
{ key: "code", label: "Code", description: "Code with highlighting", icon: "</>", type: "code_block", hint: "```", aliases: ["pre", "snippet"] },
|
|
@@ -34,18 +38,23 @@ export const BLOCK_TYPES = [
|
|
|
34
38
|
|
|
35
39
|
export const INSERT_ITEMS = [
|
|
36
40
|
{ key: "divider", label: "Divider", description: "A line between sections", icon: "—", hint: "---", aliases: ["hr", "line", "separator", "rule"], insert: "divider" },
|
|
41
|
+
{ key: "toc", label: "Table of contents", description: "Links to this page's headings", icon: "≡", aliases: ["toc", "contents", "outline", "index"], insert: "toc" },
|
|
37
42
|
{ key: "image", label: "Image", description: "Upload or paste a picture", icon: "▣", aliases: ["img", "picture", "photo"], insert: "image" },
|
|
38
43
|
{ key: "file", label: "File", description: "Attach any file", icon: "⎘", aliases: ["upload", "attachment", "pdf"], insert: "file" },
|
|
39
44
|
{ key: "table", label: "Table", description: "Rows and columns, paste from a spreadsheet", icon: "▦", aliases: ["grid", "spreadsheet"], insert: "table" },
|
|
40
45
|
{ key: "csv", label: "Import CSV…", description: "Turn a CSV file into a table", icon: "⇧", aliases: ["comma separated", "import"], insert: "csv" },
|
|
41
46
|
{ key: "board", label: "Board", description: "Cards in columns, like a kanban", icon: "▥", aliases: ["kanban", "cards", "columns", "status"], insert: "board" },
|
|
47
|
+
{ key: "mention", label: "Mention", description: "Name someone on the page", icon: "@", aliases: ["person", "people", "assign"], command: "mentions" },
|
|
48
|
+
{ key: "date", label: "Date", description: "Today, tomorrow or a calendar day", icon: "▧", aliases: ["calendar", "today", "tomorrow", "due"], command: "dates" },
|
|
49
|
+
{ key: "emoji", label: "Emoji", description: "Insert one glyph", icon: "☺", aliases: ["smiley", "glyph", "symbol"], command: "emoji" },
|
|
50
|
+
{ key: "import-markdown", label: "Import Markdown…", description: "Turn a Markdown file into blocks", icon: "M↓", aliases: ["md", "markdown", "import"], command: "importMarkdown" },
|
|
42
51
|
];
|
|
43
52
|
|
|
44
53
|
export const CHART_ITEM = { key: "chart", label: "Chart", description: "A bar or line chart from a table", icon: "📊", aliases: ["graph", "plot", "bar", "line"], insert: "chart" };
|
|
45
54
|
|
|
46
55
|
// The chart module and its view part load together, before the block draws: a
|
|
47
56
|
// document that never takes a chart never fetches either.
|
|
48
|
-
export const loadChartPart = (view) => (viewFeatures.get(view)?.ensure("chart") ?? Promise.resolve()).catch(() => {}).then(() => import("./chart.js"));
|
|
57
|
+
export const loadChartPart = (view) => (viewFeatures.get(view)?.ensure("chart") ?? Promise.resolve()).catch(() => {}).then(() => (view.isDestroyed ? null : import("./chart.js")));
|
|
49
58
|
|
|
50
59
|
export const MENU_ITEMS = [...BLOCK_TYPES, ...INSERT_ITEMS];
|
|
51
60
|
|
|
@@ -55,7 +64,8 @@ export const insertItemsFor = (st, items = MENU_ITEMS) => (chartTables(st.doc).l
|
|
|
55
64
|
export function blockTypeOf(node) {
|
|
56
65
|
const t = node.type.name;
|
|
57
66
|
return BLOCK_TYPES.find((b) => b.type === t && (!b.attrs
|
|
58
|
-
|| (t === "heading"
|
|
67
|
+
|| (t === "heading" || t === "toggle" ? (b.attrs.level ?? null) === (node.attrs.level ?? null)
|
|
68
|
+
: t === "list_item" ? b.attrs.kind === node.attrs.kind : true))) ?? BLOCK_TYPES[0];
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
const startOf = (doc, index) => {
|
|
@@ -99,6 +109,40 @@ export function turnInto(st, index, item) {
|
|
|
99
109
|
return canTurnInto(node, item) ? retype(st.tr, index, item) : REFUSED;
|
|
100
110
|
}
|
|
101
111
|
|
|
112
|
+
// A toggle heading writes a `level` only a newer page format reads, so its first
|
|
113
|
+
// write asks the format gate.
|
|
114
|
+
export const isToggleHeading = (item) => item.type === "toggle" && item.attrs?.level != null;
|
|
115
|
+
|
|
116
|
+
const indexOfId = (doc, id) => {
|
|
117
|
+
let found = -1;
|
|
118
|
+
doc.forEach((n, _, i) => { if (n.attrs.id === id) found = i; });
|
|
119
|
+
return found;
|
|
120
|
+
};
|
|
121
|
+
const stale = { ok: false, code: "stale-target" };
|
|
122
|
+
|
|
123
|
+
// Turn every block named by `ids` into `item`. A block type from a later format,
|
|
124
|
+
// a quote or a toggle heading, asks the format gate first; after it answers the
|
|
125
|
+
// blocks are found again by id: one a peer moved away refuses instead of handing
|
|
126
|
+
// the type to whatever took its place.
|
|
127
|
+
export function turnBlocks(view, item, ids) {
|
|
128
|
+
const at = () => ids.map((id) => indexOfId(view.state.doc, id));
|
|
129
|
+
const write = () => {
|
|
130
|
+
const found = at();
|
|
131
|
+
if (found.includes(-1)) return stale;
|
|
132
|
+
if (found.some((i) => !canTurnInto(view.state.doc.child(i), item))) return REFUSED;
|
|
133
|
+
const tr = view.state.tr;
|
|
134
|
+
for (const i of found) retype(tr, i, item);
|
|
135
|
+
view.dispatch(asStep(tr).scrollIntoView());
|
|
136
|
+
view.focus();
|
|
137
|
+
return { ok: true };
|
|
138
|
+
};
|
|
139
|
+
if (isToggleHeading(item)) return runFormatted(view, write);
|
|
140
|
+
// A block type from a later format, like the turn into a quote, asks the gate
|
|
141
|
+
// before it writes, and a refusal is a notice rather than a change.
|
|
142
|
+
if (item.type === "quote") return import("./format-native-commands.js").then((menu) => (view.isDestroyed ? false : menu.gateTurn(view, write)));
|
|
143
|
+
return write();
|
|
144
|
+
}
|
|
145
|
+
|
|
102
146
|
// A new empty paragraph after the block at `index` and its subtree, at the
|
|
103
147
|
// block's own level, with the caret in it.
|
|
104
148
|
export function insertAfter(st, index, tr = st.tr) {
|
|
@@ -144,6 +188,35 @@ export function insertDivider(st, index, tr = st.tr) {
|
|
|
144
188
|
return tr.setSelection(TextSelection.create(tr.doc, to + 2));
|
|
145
189
|
}
|
|
146
190
|
|
|
191
|
+
// The empty paragraph at `index` becomes a table of contents that keeps its id, pos and
|
|
192
|
+
// parent, followed by a new empty paragraph holding the caret.
|
|
193
|
+
export function replaceWithToc(tr, index) {
|
|
194
|
+
const at = startOf(tr.doc, index);
|
|
195
|
+
const node = tr.doc.child(index);
|
|
196
|
+
const next = tr.doc.maybeChild(index + 1);
|
|
197
|
+
const { id, pos, parent } = node.attrs;
|
|
198
|
+
tr.replaceWith(at, at + node.nodeSize, [
|
|
199
|
+
N.toc.create({ id, pos, parent, maxLevel: 3 }),
|
|
200
|
+
N.paragraph.create({ id: newId(), pos: keyAfter(pos, next ? next.attrs.pos : null), parent }),
|
|
201
|
+
]);
|
|
202
|
+
return tr.setSelection(TextSelection.create(tr.doc, at + 2));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Insert a table of contents for the block at `index`: in place of an empty paragraph,
|
|
206
|
+
// otherwise after the block's whole subtree at the block's own level.
|
|
207
|
+
export function insertToc(st, index, tr = st.tr) {
|
|
208
|
+
const node = tr.doc.child(index);
|
|
209
|
+
if (node.type === N.paragraph && node.content.size === 0) return replaceWithToc(tr, index);
|
|
210
|
+
const ents = outline(tr.doc), end = ents[index].subEnd;
|
|
211
|
+
const to = ents[end].end, next = ents[end + 1]?.node.attrs.pos ?? null;
|
|
212
|
+
const pos = keyAfter(ents[end].node.attrs.pos, next), parent = node.attrs.parent ?? null;
|
|
213
|
+
tr.insert(to, [
|
|
214
|
+
N.toc.create({ id: newId(), pos, parent, maxLevel: 3 }),
|
|
215
|
+
N.paragraph.create({ id: newId(), pos: keyAfter(pos, next), parent }),
|
|
216
|
+
]);
|
|
217
|
+
return tr.setSelection(TextSelection.create(tr.doc, to + 2));
|
|
218
|
+
}
|
|
219
|
+
|
|
147
220
|
// The skipped letters between a query and a candidate it can be read out of,
|
|
148
221
|
// or Infinity when it cannot. Only queries of 2 to 32 letters count, so a single
|
|
149
222
|
// letter never drags in half the menu.
|
|
@@ -180,22 +253,8 @@ export function rankItems(items, query) {
|
|
|
180
253
|
return items.map((it, order) => ({ it, order, rank: rank(it) })).filter((x) => x.rank).sort((a, b) => a.rank[0] - b.rank[0] || a.rank[1] - b.rank[1] || a.order - b.order).map((x) => x.it);
|
|
181
254
|
}
|
|
182
255
|
|
|
183
|
-
|
|
184
|
-
// the file pickers have their own placement; a block type retypes an empty line
|
|
185
|
-
// and otherwise goes into a new block below the line's whole subtree. A block
|
|
186
|
-
// type from a later format asks the format gate before it writes anything.
|
|
187
|
-
export function runItem(view, item, index, remove = null) {
|
|
188
|
-
if (item.type !== "quote") return insertItem(view, item, index, remove);
|
|
189
|
-
return import("./format-native-commands.js").then(({ runFormat }) => runFormat(view, () => insertItem(view, item, index, remove)));
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function insertItem(view, item, index, remove = null) {
|
|
256
|
+
function writeItem(view, item, index, remove) {
|
|
193
257
|
const st = view.state;
|
|
194
|
-
if (item.insert === "image" || item.insert === "file") return pickFiles(view, item.insert === "image" ? "image/*" : "", hostPlace(st, index), remove);
|
|
195
|
-
if (item.insert === "csv") return pickCsv(view, remove);
|
|
196
|
-
if (item.insert === "table") return insertTableNode(st, view.dispatch, makeTable(), remove, null, index);
|
|
197
|
-
if (item.insert === "board") return insertBoardNode(st, view.dispatch, makeBoard(), remove, index);
|
|
198
|
-
if (item.insert === "chart") return loadChartPart(view).then(({ chartFromSlash }) => chartFromSlash(view, index, remove));
|
|
199
258
|
let tr = st.tr;
|
|
200
259
|
if (remove) tr.delete(remove.from, remove.to);
|
|
201
260
|
const node = tr.doc.child(index);
|
|
@@ -210,3 +269,62 @@ function insertItem(view, item, index, remove = null) {
|
|
|
210
269
|
view.focus();
|
|
211
270
|
return true;
|
|
212
271
|
}
|
|
272
|
+
|
|
273
|
+
// A table of contents is a block only a newer page format reads, so its first write asks
|
|
274
|
+
// the format gate; the block the pick was made on is found again after it answers, so one
|
|
275
|
+
// a peer moved away refuses instead of handing the toc its place.
|
|
276
|
+
export function runTocItem(view, index, remove = null) {
|
|
277
|
+
const st = view.state;
|
|
278
|
+
const id = st.doc.maybeChild(index)?.attrs.id ?? null;
|
|
279
|
+
const del = remove && { ...remove, text: st.doc.textBetween(remove.from, remove.to) };
|
|
280
|
+
return runFormatted(view, () => {
|
|
281
|
+
const now = view.state;
|
|
282
|
+
const at = id === null ? -1 : indexOfId(now.doc, id);
|
|
283
|
+
if (at < 0 || (del && now.doc.textBetween(del.from, del.to) !== del.text)) return stale;
|
|
284
|
+
let tr = del ? now.tr.delete(del.from, del.to) : now.tr;
|
|
285
|
+
tr = insertToc(now, indexOfId(tr.doc, id), tr);
|
|
286
|
+
view.dispatch(asStep(tr).scrollIntoView());
|
|
287
|
+
view.focus();
|
|
288
|
+
return { ok: true };
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// The menus ask for an optional command through the editor's own entry point, at the moment
|
|
293
|
+
// somebody picks the row: importing it here keeps this module plain data, which the block
|
|
294
|
+
// menus, the Markdown rules and the editor all share, while the command still loads only on
|
|
295
|
+
// first use. `id` is the row it has in COMMAND_FEATURES.
|
|
296
|
+
export const runMenuCommand = (view, id, args) => import("./editor.js").then(({ runPageCommand }) => (view.isDestroyed ? { ok: false, code: "not-editing" } : runPageCommand(view, id, args)));
|
|
297
|
+
|
|
298
|
+
// A row whose work is an optional command: the typed query goes with the pick, and the
|
|
299
|
+
// command is asked for by its id.
|
|
300
|
+
function runCommandItem(view, id, remove) {
|
|
301
|
+
if (remove) view.dispatch(view.state.tr.delete(remove.from, remove.to));
|
|
302
|
+
return runMenuCommand(view, id, {});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// What a pick does in the insert menu for the block at `index`. A divider and
|
|
306
|
+
// the file pickers have their own placement; a block type retypes an empty line
|
|
307
|
+
// and otherwise goes into a new block below the line's whole subtree. A block
|
|
308
|
+
// type from a later format asks the format gate before it writes anything, and
|
|
309
|
+
// a toggle heading or a quote is found again after it answers: the block and the
|
|
310
|
+
// typed query that moved or changed refuse as a stale target.
|
|
311
|
+
export function runItem(view, item, index, remove = null) {
|
|
312
|
+
const st = view.state;
|
|
313
|
+
if (item.insert === "image" || item.insert === "file") return pickFiles(view, item.insert === "image" ? "image/*" : "", hostPlace(st, index), remove);
|
|
314
|
+
if (item.insert === "csv") return pickCsv(view, remove);
|
|
315
|
+
if (item.insert === "table") return insertTableNode(st, view.dispatch, makeTable(), remove, null, index);
|
|
316
|
+
if (item.insert === "board") return insertBoardNode(st, view.dispatch, makeBoard(), remove, index);
|
|
317
|
+
if (item.insert === "chart") return loadChartPart(view).then((part) => (part && !view.isDestroyed ? part.chartFromSlash(view, index, remove) : false));
|
|
318
|
+
if (item.insert === "toc") return runTocItem(view, index, remove);
|
|
319
|
+
if (item.command) return runCommandItem(view, item.command, remove);
|
|
320
|
+
if (!isToggleHeading(item) && item.type !== "quote") return writeItem(view, item, index, remove);
|
|
321
|
+
const id = st.doc.maybeChild(index)?.attrs.id ?? null;
|
|
322
|
+
const del = remove && { ...remove, text: st.doc.textBetween(remove.from, remove.to) };
|
|
323
|
+
return runFormatted(view, () => {
|
|
324
|
+
const now = view.state;
|
|
325
|
+
const at = id === null ? -1 : indexOfId(now.doc, id);
|
|
326
|
+
// The typed query is found again too: a line that changed underneath refuses.
|
|
327
|
+
if (at < 0 || (del && now.doc.textBetween(del.from, del.to) !== del.text)) return stale;
|
|
328
|
+
return writeItem(view, item, at, del);
|
|
329
|
+
});
|
|
330
|
+
}
|
package/src/page/markdown.js
CHANGED
|
@@ -129,7 +129,8 @@ export const MARKDOWN_RULES = [
|
|
|
129
129
|
// converts only when the gate answered applied; the transaction it dispatches is
|
|
130
130
|
// the input-rule one, recorded on this plugin so Backspace puts the literal `" `
|
|
131
131
|
// back and undo keeps the conversion whole. The typed space is never swallowed:
|
|
132
|
-
// when the format cannot be written
|
|
132
|
+
// when the format cannot be written the space is inserted as itself, and a line
|
|
133
|
+
// that changed while the gate waited takes no write at all.
|
|
133
134
|
function quoteShortcut(plugin, view, from, to, text) {
|
|
134
135
|
if (text !== " " || from !== to) return false;
|
|
135
136
|
const st = view.state;
|
|
@@ -137,7 +138,10 @@ function quoteShortcut(plugin, view, from, to, text) {
|
|
|
137
138
|
const node = $from.parent;
|
|
138
139
|
if ($from.depth !== 1 || node.type !== N.paragraph || $from.parentOffset !== 1 || node.textContent !== '"') return false;
|
|
139
140
|
if (!view.editable || typeof view.runFormatCommand !== "function") return false;
|
|
140
|
-
const plain = () => {
|
|
141
|
+
const plain = () => {
|
|
142
|
+
if (view.isDestroyed || view.state.doc !== st.doc) return;
|
|
143
|
+
view.dispatch(view.state.tr.insertText(" ", from, to));
|
|
144
|
+
};
|
|
141
145
|
const at = $from.before(1);
|
|
142
146
|
import("./format-native-commands.js").then(({ quoteFromSpace }) => quoteFromSpace(plugin, view, { at, from, to }, plain), plain);
|
|
143
147
|
return true;
|
package/src/page/media.js
CHANGED
|
@@ -3,25 +3,31 @@
|
|
|
3
3
|
// Nothing reaches the record until the host has answered with a URL; then one
|
|
4
4
|
// transaction inserts the block at the bookmark. A cancelled or failed upload
|
|
5
5
|
// leaves no trace in the record.
|
|
6
|
-
|
|
6
|
+
//
|
|
7
|
+
// The paste, the drop, the picker and the upload itself are here, so a file lands
|
|
8
|
+
// where it was dropped without waiting for anything. What an upload can then offer
|
|
9
|
+
// (retry, embedding, the alt text and width the chrome sets) is in the media view
|
|
10
|
+
// part, which loads with the first paste, drop, insert or upload.
|
|
11
|
+
import { state } from "../vendor/prosemirror.vendor.js";
|
|
7
12
|
import { schema } from "./schema.js";
|
|
8
13
|
import { outline } from "./blocks.js";
|
|
9
14
|
import { newId } from "./model.js";
|
|
10
15
|
import { asStep } from "./blocksel.js";
|
|
11
16
|
import { announce } from "./announce.js";
|
|
12
17
|
import { safeFileHref } from "./address.js";
|
|
13
|
-
import { mediaViews } from "./media-views.js";
|
|
14
18
|
|
|
15
19
|
const { Plugin, PluginKey } = state;
|
|
16
|
-
const { Decoration, DecorationSet } = pmView;
|
|
17
20
|
const N = schema.nodes;
|
|
18
21
|
|
|
19
22
|
export const mediaKey = new PluginKey("page-media");
|
|
20
23
|
export const EMBED_MAX = 1.5 * 1024 * 1024;
|
|
21
|
-
export const
|
|
22
|
-
const runtime = new WeakMap();
|
|
24
|
+
export const mediaRuntime = new WeakMap();
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
// The view part hands its upload actions back here when it installs, so the
|
|
27
|
+
// bookmark's own buttons reach them without this module importing it.
|
|
28
|
+
export const mediaParts = new WeakMap();
|
|
29
|
+
|
|
30
|
+
export const boundary = (doc, pos) => {
|
|
25
31
|
const $p = doc.resolve(Math.max(0, Math.min(pos, doc.content.size)));
|
|
26
32
|
return $p.depth === 0 ? $p.pos : $p.after(1);
|
|
27
33
|
};
|
|
@@ -109,31 +115,30 @@ export function insertFiles(view, files, place = hostPlace(view.state, view.stat
|
|
|
109
115
|
return true;
|
|
110
116
|
}
|
|
111
117
|
|
|
112
|
-
const live = (view, id) => mediaKey.getState(view.state).items.find((x) => x.id === id);
|
|
113
|
-
const update = (view, id, patch) => view.dispatch(view.state.tr.setMeta(mediaKey, { update: { id, ...patch } }));
|
|
114
|
-
const
|
|
115
|
-
const attrsFor = (it, src) => (it.kind === "image" ? { src, alt: "", caption: "" } : { src, name: it.name, size: it.file.size ?? null, mime: it.file.type || null });
|
|
118
|
+
export const live = (view, id) => mediaKey.getState(view.state).items.find((x) => x.id === id);
|
|
119
|
+
export const update = (view, id, patch) => view.dispatch(view.state.tr.setMeta(mediaKey, { update: { id, ...patch } }));
|
|
120
|
+
export const attrsFor = (it, src) => (it.kind === "image" ? { src, alt: "", caption: "" } : { src, name: it.name, size: it.file.size ?? null, mime: it.file.type || null });
|
|
116
121
|
|
|
117
|
-
function settle(it, view, tr) {
|
|
122
|
+
export function settle(it, view, tr) {
|
|
118
123
|
if (it.preview) URL.revokeObjectURL?.(it.preview);
|
|
119
124
|
if (tr) view.dispatch(tr);
|
|
120
125
|
}
|
|
121
126
|
|
|
122
|
-
async function run(view, it) {
|
|
123
|
-
const rt =
|
|
127
|
+
export async function run(view, it) {
|
|
128
|
+
const rt = mediaRuntime.get(view);
|
|
124
129
|
const abort = new AbortController();
|
|
125
130
|
rt.aborts.set(it.id, abort);
|
|
126
131
|
let last = -5;
|
|
127
132
|
const onProgress = ({ percent }) => {
|
|
128
133
|
if (percent - last < 5 && percent !== 100) return;
|
|
129
134
|
last = percent;
|
|
130
|
-
if (
|
|
135
|
+
if (mediaRuntime.has(view) && live(view, it.id)) update(view, it.id, { percent });
|
|
131
136
|
};
|
|
132
137
|
let res;
|
|
133
138
|
try { res = await rt.upload(it.file, { onProgress, signal: abort.signal }); }
|
|
134
139
|
catch { res = { code: "network", msg: "Upload failed" }; }
|
|
135
140
|
rt.aborts.delete(it.id);
|
|
136
|
-
if (!
|
|
141
|
+
if (!mediaRuntime.has(view) || !live(view, it.id)) return;
|
|
137
142
|
if (res?.ok) {
|
|
138
143
|
const src = safeFileHref(res.uploads?.[0]?.url);
|
|
139
144
|
if (src) {
|
|
@@ -159,46 +164,19 @@ async function run(view, it) {
|
|
|
159
164
|
announce(view, `${it.name}: ${message}`);
|
|
160
165
|
}
|
|
161
166
|
|
|
162
|
-
export async function embedUpload(view, id) {
|
|
163
|
-
const it = live(view, id);
|
|
164
|
-
if (!it || it.state !== "embed-choice") return;
|
|
165
|
-
const src = await readDataUrl(it.file);
|
|
166
|
-
if (!runtime.has(view) || !live(view, id)) return;
|
|
167
|
-
settle(it, view, placeTr(view.state, id, attrsFor(it, src)));
|
|
168
|
-
announce(view, "Saved inside the document");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export function retryUpload(view, id) {
|
|
172
|
-
const it = live(view, id);
|
|
173
|
-
if (!it) return;
|
|
174
|
-
update(view, id, { state: "uploading", percent: 0, code: null, message: null });
|
|
175
|
-
run(view, live(view, id));
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export function cancelUpload(view, id) {
|
|
179
|
-
const rt = runtime.get(view);
|
|
180
|
-
const abort = rt?.aborts.get(id);
|
|
181
|
-
const it = live(view, id);
|
|
182
|
-
if (it) settle(it, view, removeTr(view.state, id));
|
|
183
|
-
rt?.aborts.delete(id);
|
|
184
|
-
abort?.abort();
|
|
185
|
-
}
|
|
186
|
-
|
|
187
167
|
const htmlHasText = (html) => html.replace(/<(meta|style|img)\b[^>]*>/gi, "").replace(/<[^>]+>/g, "").trim() !== "";
|
|
188
168
|
|
|
189
169
|
export function mediaPlugin({ upload, features = null }) {
|
|
190
|
-
let editorView = null;
|
|
191
170
|
// Only the bookmark widget waits for the view part: the upload and its block
|
|
192
171
|
// are core, and the block draws through the schema until the part arrives.
|
|
193
172
|
const showBookmark = (view) => {
|
|
194
|
-
if (!features || features.has("media") || !
|
|
173
|
+
if (!features || features.has("media") || !mediaRuntime.has(view)) return;
|
|
195
174
|
features.ensure("media").catch(() => {});
|
|
196
175
|
};
|
|
197
176
|
return new Plugin({
|
|
198
177
|
key: mediaKey,
|
|
199
178
|
state: mediaState,
|
|
200
179
|
view(view) {
|
|
201
|
-
editorView = view;
|
|
202
180
|
const doc = view.dom.ownerDocument;
|
|
203
181
|
const input = doc.createElement("input");
|
|
204
182
|
input.type = "file";
|
|
@@ -206,7 +184,7 @@ export function mediaPlugin({ upload, features = null }) {
|
|
|
206
184
|
input.hidden = true;
|
|
207
185
|
input.className = "page-file-input";
|
|
208
186
|
view.dom.parentElement.append(input);
|
|
209
|
-
|
|
187
|
+
mediaRuntime.set(view, { upload, aborts: new Map(), input });
|
|
210
188
|
input.addEventListener("change", () => {
|
|
211
189
|
const files = [...(input.files ?? [])];
|
|
212
190
|
const picker = mediaKey.getState(view.state).picker;
|
|
@@ -230,18 +208,18 @@ export function mediaPlugin({ upload, features = null }) {
|
|
|
230
208
|
const b = e.target.closest?.(".page-upload-btn");
|
|
231
209
|
if (!b) return;
|
|
232
210
|
e.preventDefault();
|
|
211
|
+
const part = mediaParts.get(view);
|
|
233
212
|
const id = b.dataset.upload;
|
|
234
|
-
if (b.dataset.act === "retry") retryUpload(view, id);
|
|
235
|
-
else if (b.dataset.act === "embed") embedUpload(view, id);
|
|
236
|
-
else cancelUpload(view, id);
|
|
213
|
+
if (b.dataset.act === "retry") part?.retryUpload(view, id);
|
|
214
|
+
else if (b.dataset.act === "embed") part?.embedUpload(view, id);
|
|
215
|
+
else part?.cancelUpload(view, id);
|
|
237
216
|
};
|
|
238
217
|
view.dom.addEventListener("click", onClick);
|
|
239
218
|
return {
|
|
240
219
|
destroy() {
|
|
241
220
|
view.dom.removeEventListener("click", onClick);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
runtime.delete(view);
|
|
221
|
+
const rt = mediaRuntime.get(view);
|
|
222
|
+
mediaRuntime.delete(view);
|
|
245
223
|
for (const it of mediaKey.getState(view.state).items) if (it.preview) URL.revokeObjectURL?.(it.preview);
|
|
246
224
|
for (const a of rt.aborts.values()) a.abort();
|
|
247
225
|
input.remove();
|
|
@@ -249,14 +227,6 @@ export function mediaPlugin({ upload, features = null }) {
|
|
|
249
227
|
};
|
|
250
228
|
},
|
|
251
229
|
props: {
|
|
252
|
-
decorations(st) {
|
|
253
|
-
const { items } = mediaKey.getState(st);
|
|
254
|
-
const render = editorView ? mediaViews.get(editorView) : null;
|
|
255
|
-
if (!items.length || !render) return null;
|
|
256
|
-
return DecorationSet.create(st.doc, items.map((it) => Decoration.widget(boundary(st.doc, it.pos), (view) => render(view.dom.ownerDocument, it), {
|
|
257
|
-
side: -1000 + it.order, key: `${it.id}:${it.state}:${it.percent}`, stopEvent: () => true, ignoreSelection: true,
|
|
258
|
-
})));
|
|
259
|
-
},
|
|
260
230
|
handlePaste(view, event) {
|
|
261
231
|
const data = event.clipboardData;
|
|
262
232
|
const files = [...(data?.files ?? [])];
|
|
@@ -287,72 +257,18 @@ export function mediaPlugin({ upload, features = null }) {
|
|
|
287
257
|
});
|
|
288
258
|
}
|
|
289
259
|
|
|
290
|
-
export function pickFiles(view, accept, place = hostPlace(view.state, view.state.selection.$head.index(0)), remove = null) {
|
|
291
|
-
const rt = runtime.get(view);
|
|
292
|
-
if (!rt) return false;
|
|
293
|
-
const picker = { place, remove: remove && { ...remove, text: view.state.doc.textBetween(remove.from, remove.to) } };
|
|
294
|
-
view.dispatch(view.state.tr.setMeta(mediaKey, { picker }));
|
|
295
|
-
rt.input.accept = accept;
|
|
296
|
-
rt.input.click();
|
|
297
|
-
return true;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
260
|
const indexOfId = (doc, id) => {
|
|
301
261
|
let found = -1;
|
|
302
262
|
doc.forEach((n, _, i) => { if (n.attrs.id === id) found = i; });
|
|
303
263
|
return found;
|
|
304
264
|
};
|
|
305
265
|
|
|
306
|
-
export function
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
view.dispatch(asStep(st.tr.setNodeMarkup(at, null, { ...node.attrs, ...patch })));
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export function openAltEditor(view, id) {
|
|
318
|
-
const index = indexOfId(view.state.doc, id);
|
|
319
|
-
if (index < 0) return false;
|
|
320
|
-
const shell = view.dom.parentElement;
|
|
321
|
-
const doc = shell.ownerDocument;
|
|
322
|
-
let at = 0;
|
|
323
|
-
for (let i = 0; i < index; i++) at += view.state.doc.child(i).nodeSize;
|
|
324
|
-
const box = doc.createElement("div");
|
|
325
|
-
box.className = "page-link";
|
|
326
|
-
box.dataset.mode = "alt";
|
|
327
|
-
const input = doc.createElement("input");
|
|
328
|
-
input.className = "page-link-input";
|
|
329
|
-
input.type = "text";
|
|
330
|
-
input.value = view.state.doc.child(index).attrs.alt || "";
|
|
331
|
-
input.placeholder = "Describe the image";
|
|
332
|
-
input.setAttribute("aria-label", "Alt text");
|
|
333
|
-
box.append(input);
|
|
334
|
-
const rect = view.nodeDOM(at)?.getBoundingClientRect?.();
|
|
335
|
-
const shellBox = shell.getBoundingClientRect();
|
|
336
|
-
if (rect) {
|
|
337
|
-
box.style.setProperty("--page-pop-x", `${rect.left - shellBox.left}px`);
|
|
338
|
-
box.style.setProperty("--page-pop-y", `${rect.bottom - shellBox.top + 6}px`);
|
|
339
|
-
}
|
|
340
|
-
shell.append(box);
|
|
341
|
-
let done = false;
|
|
342
|
-
const close = (commit, refocus = true) => {
|
|
343
|
-
if (done) return;
|
|
344
|
-
done = true;
|
|
345
|
-
const value = input.value.trim();
|
|
346
|
-
box.remove();
|
|
347
|
-
const i = indexOfId(view.state.doc, id);
|
|
348
|
-
if (commit && i >= 0 && value !== view.state.doc.child(i).attrs.alt) setImageAttrs(view, id, { alt: value });
|
|
349
|
-
if (refocus) view.focus();
|
|
350
|
-
};
|
|
351
|
-
input.addEventListener("keydown", (e) => {
|
|
352
|
-
if (e.key === "Enter") { e.preventDefault(); close(true); }
|
|
353
|
-
else if (e.key === "Escape") { e.preventDefault(); e.stopPropagation(); close(false); }
|
|
354
|
-
});
|
|
355
|
-
input.addEventListener("blur", () => close(true, false));
|
|
356
|
-
input.focus();
|
|
266
|
+
export function pickFiles(view, accept, place = hostPlace(view.state, view.state.selection.$head.index(0)), remove = null) {
|
|
267
|
+
const rt = mediaRuntime.get(view);
|
|
268
|
+
if (!rt) return false;
|
|
269
|
+
const picker = { place, remove: remove && { ...remove, text: view.state.doc.textBetween(remove.from, remove.to) } };
|
|
270
|
+
view.dispatch(view.state.tr.setMeta(mediaKey, { picker }));
|
|
271
|
+
rt.input.accept = accept;
|
|
272
|
+
rt.input.click();
|
|
357
273
|
return true;
|
|
358
274
|
}
|
package/src/page/placeholder.js
CHANGED
|
@@ -5,6 +5,7 @@ const { Decoration, DecorationSet } = pmView;
|
|
|
5
5
|
|
|
6
6
|
export const PLACEHOLDER_TEXT = "Type / for blocks";
|
|
7
7
|
export const TITLE_PLACEHOLDER = "Untitled";
|
|
8
|
+
export const CHECK_PLACEHOLDER = "To do";
|
|
8
9
|
|
|
9
10
|
function titlePlaceholder(doc) {
|
|
10
11
|
let done = false;
|
|
@@ -18,11 +19,23 @@ function titlePlaceholder(doc) {
|
|
|
18
19
|
return decoration;
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
// An empty check item keeps its tags, so the line it leaves is the whole click area;
|
|
23
|
+
// the words say where to click.
|
|
24
|
+
function checkPlaceholders(doc) {
|
|
25
|
+
const decorations = [];
|
|
26
|
+
doc.forEach((node, offset) => {
|
|
27
|
+
if (node.type !== schema.nodes.list_item || node.attrs.kind !== "check") return;
|
|
28
|
+
if (node.content.size || node.attrs.checked) return;
|
|
29
|
+
decorations.push(Decoration.node(offset, offset + node.nodeSize, { class: "page-check-placeholder", "data-placeholder": CHECK_PLACEHOLDER }));
|
|
30
|
+
});
|
|
31
|
+
return decorations;
|
|
32
|
+
}
|
|
33
|
+
|
|
21
34
|
export function placeholderPlugin(text = PLACEHOLDER_TEXT) {
|
|
22
35
|
return new state.Plugin({
|
|
23
36
|
props: {
|
|
24
37
|
decorations(st) {
|
|
25
|
-
const decorations =
|
|
38
|
+
const decorations = checkPlaceholders(st.doc);
|
|
26
39
|
const title = titlePlaceholder(st.doc);
|
|
27
40
|
if (title) decorations.push(title);
|
|
28
41
|
const { selection } = st;
|