@panphora/clayjs 1.16.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/all.js +1 -1
- package/dist/clay-dom.js +1 -1
- package/dist/clay-events.js +1 -1
- package/dist/clay-internals.js +1 -1
- package/dist/clay-options.js +1 -1
- package/dist/clay-router.js +1 -1
- package/dist/clay-ui.js +1 -1
- package/dist/clay-utils.js +1 -1
- package/dist/clay.js +1 -1
- package/dist/clay.standalone.js +72519 -59839
- package/dist/m/attrs/refetch-on-save-ZUE7PSCI.js +1 -0
- package/dist/m/attrs/save-freeze-LWGF6XUN.js +1 -0
- package/dist/m/c-2GRUM77Q.js +30 -0
- package/dist/m/c-2KIEGNC4.js +1 -0
- package/dist/m/{c-5R4PTRG2.js → c-2KL3BD52.js} +1 -1
- package/dist/m/c-2YYDMLNZ.js +1 -0
- package/dist/m/c-2ZZBC45O.js +1 -0
- package/dist/m/c-372N6H3B.js +1 -0
- package/dist/m/{c-4WKFKEKF.js → c-3G2DGZUP.js} +1 -1
- package/dist/m/c-3LDGIBMN.js +1 -0
- package/dist/m/c-3O7OBDCM.js +1 -0
- package/dist/m/c-4ASQPRQL.js +1 -0
- package/dist/m/c-4TXNLX4K.js +6 -0
- package/dist/m/c-4YU3BR4D.js +6 -0
- package/dist/m/c-5734MF2P.js +1 -0
- package/dist/m/c-57XDY6HC.js +1 -0
- package/dist/m/{c-VFX45IZQ.js → c-5B4YBZXC.js} +1 -1
- package/dist/m/c-5ONTYIUI.js +1 -0
- package/dist/m/c-5OOVFCJM.js +1 -0
- package/dist/m/c-5T6NZHNM.js +1 -0
- package/dist/m/{c-YDH7Q3NY.js → c-5ZQV7KC4.js} +1 -1
- package/dist/m/{c-Z36J63PP.js → c-6DB2OLRJ.js} +1 -1
- package/dist/m/c-6DNRCPNM.js +1 -0
- package/dist/m/c-6UGWFMQC.js +2 -0
- package/dist/m/c-7K4SCQB7.js +1 -0
- package/dist/m/c-7MG5RU63.js +1 -0
- package/dist/m/c-7PRJCA3E.js +1 -0
- package/dist/m/c-A6L3FMUS.js +1 -0
- package/dist/m/c-AMU3BPPR.js +4 -0
- package/dist/m/c-AMYEFA2E.js +1 -0
- package/dist/m/c-AQYCG7QW.js +1 -0
- package/dist/m/c-ATKAOH23.js +1 -0
- package/dist/m/c-B3JM5ZXC.js +1 -0
- package/dist/m/c-B5CS55WR.js +1 -0
- package/dist/m/c-B5UJQT7W.js +1 -0
- package/dist/m/c-B7J4K2AE.js +3 -0
- package/dist/m/c-BHZYSHW4.js +1 -0
- package/dist/m/c-BIJALSRD.js +1 -0
- package/dist/m/c-BJ7YQ5QZ.js +1 -0
- package/dist/m/c-BPF2NSPV.js +1 -0
- package/dist/m/{c-KIPQJX4B.js → c-BRLCTNMC.js} +1 -1
- package/dist/m/c-BUAYFEV5.js +1 -0
- package/dist/m/c-C6IPRG34.js +1 -0
- package/dist/m/{c-2JL6E75N.js → c-CAS3NND4.js} +1 -1
- package/dist/m/{c-4DRMS4QG.js → c-CCQ25GFY.js} +1 -1
- package/dist/m/c-CJFDLD43.js +1 -0
- package/dist/m/c-COPTL5WB.js +1 -0
- package/dist/m/c-D3E4WJ4M.js +166 -0
- package/dist/m/c-D5IKEKXW.js +7 -0
- package/dist/m/c-DHBRA6DL.js +1 -0
- package/dist/m/c-DKLS74XB.js +72 -0
- package/dist/m/c-DVYGFTSK.js +3 -0
- package/dist/m/c-E3ZXARWA.js +1 -0
- package/dist/m/c-EK63G3CG.js +1 -0
- package/dist/m/c-ERQSNR6K.js +13 -0
- package/dist/m/c-EVYAISP3.js +1 -0
- package/dist/m/c-EXKHPOVC.js +3 -0
- package/dist/m/{c-7VGZ7RU5.js → c-FWKHM4E7.js} +1 -1
- package/dist/m/{c-4Q2XTQ2N.js → c-GBKB5WDL.js} +1 -1
- package/dist/m/c-GNDL3TKB.js +1 -0
- package/dist/m/c-GRWMD6O5.js +1 -0
- package/dist/m/c-GRXHHFPO.js +1 -0
- package/dist/m/{c-OYTFP5QN.js → c-GY422N3Q.js} +1 -1
- package/dist/m/c-HAWFNBYG.js +1 -0
- package/dist/m/c-HG2C77BM.js +4 -0
- package/dist/m/c-HHGJBU4D.js +1 -0
- package/dist/m/c-HMV6P4RX.js +1 -0
- package/dist/m/c-HMYWIHFG.js +1 -0
- package/dist/m/c-HT2DKBV2.js +1 -0
- package/dist/m/c-I2SCCUEW.js +1 -0
- package/dist/m/{c-HRDCD3WN.js → c-ICJYHABF.js} +2 -2
- package/dist/m/c-IMZCWYHR.js +7 -0
- package/dist/m/c-IVMD3FEM.js +1 -0
- package/dist/m/c-IVTRVENZ.js +1 -0
- package/dist/m/c-IW6GGOU2.js +1 -0
- package/dist/m/c-JKZJIUIP.js +1 -0
- package/dist/m/{c-NZMNXVF6.js → c-JNLX3RJB.js} +1 -1
- package/dist/m/c-JRKY44DK.js +1 -0
- package/dist/m/c-K3ADYNLU.js +1 -0
- package/dist/m/c-KBS3UV6B.js +1 -0
- package/dist/m/c-KGHVFRR3.js +1 -0
- package/dist/m/c-KJJPV77R.js +1 -0
- package/dist/m/{c-CZUSOB7V.js → c-KNPDOMDY.js} +1 -1
- package/dist/m/{c-5Y6TNX5W.js → c-KP64LT2A.js} +2 -2
- package/dist/m/c-KQ7E3PYY.js +1 -0
- package/dist/m/c-L3F5ZLIV.js +1 -0
- package/dist/m/{c-LR4CGEPT.js → c-L42OLXWM.js} +3 -2
- package/dist/m/c-L5VFATT6.js +1 -0
- package/dist/m/c-L7JL5TL3.js +3 -0
- package/dist/m/c-LKSL4AUJ.js +1 -0
- package/dist/m/c-M6FNQCTQ.js +1 -0
- package/dist/m/c-MCOOHCSG.js +1 -0
- package/dist/m/c-MHI3GYKF.js +1 -0
- package/dist/m/c-MWXFLW4J.js +1 -0
- package/dist/m/c-NEL7HQW3.js +1 -0
- package/dist/m/c-NQJRVJ56.js +1 -0
- package/dist/m/c-NWX3MFT4.js +1 -0
- package/dist/m/{c-HPURYVZS.js → c-OJYM642Q.js} +1 -1
- package/dist/m/c-OKXI52N7.js +1 -0
- package/dist/m/{c-MMUIUUZG.js → c-OPLUZR3A.js} +1 -1
- package/dist/m/{c-J7CA54IP.js → c-OTMKN2OO.js} +1 -1
- package/dist/m/{c-LTGMXXKO.js → c-OYRZ7XG2.js} +1 -1
- package/dist/m/c-P3OFYUMA.js +1 -0
- package/dist/m/c-PLN76GF3.js +6 -0
- package/dist/m/c-PLP4JKFC.js +1 -0
- package/dist/m/c-Q26MME6N.js +1 -0
- package/dist/m/{c-S7H2DRCB.js → c-Q3XOWZEV.js} +1 -1
- package/dist/m/{c-SWRJFCNN.js → c-QIKT6SKM.js} +1 -1
- package/dist/m/c-QIXT3Q7C.js +28 -0
- package/dist/m/c-QO4HNGCQ.js +1 -0
- package/dist/m/c-R27DWQKX.js +1 -0
- package/dist/m/{c-HVYXIS72.js → c-R2XUODVY.js} +1 -1
- package/dist/m/{c-QNKREOL6.js → c-R7LBFMF5.js} +1 -1
- package/dist/m/c-RND3G2S2.js +1 -0
- package/dist/m/c-S2U6NJU4.js +1 -0
- package/dist/m/c-SPZPGD3R.js +1 -0
- package/dist/m/c-SW4YEG2D.js +8 -0
- package/dist/m/c-SZGYW5HT.js +1 -0
- package/dist/m/c-T637T7SN.js +1 -0
- package/dist/m/c-TJAXGIRV.js +1 -0
- package/dist/m/{c-KKWLDOPM.js → c-TNEM3RNM.js} +1 -1
- package/dist/m/c-TTB5EK4S.js +32 -0
- package/dist/m/c-U7SHNWDH.js +1 -0
- package/dist/m/c-UDHIVLYH.js +20 -0
- package/dist/m/{c-BBVGTQG7.js → c-UGNV5MFB.js} +1 -1
- package/dist/m/{c-TQZ2JYHD.js → c-UORXLSD3.js} +1 -1
- package/dist/m/c-UPD33ZPE.js +1 -0
- package/dist/m/c-URSMSFCB.js +1 -0
- package/dist/m/c-UY4QX42B.js +1 -0
- package/dist/m/c-V44KWI2L.js +4 -0
- package/dist/m/c-VG5XIN4T.js +1 -0
- package/dist/m/c-VILENZTD.js +1 -0
- package/dist/m/{c-33HUYEDI.js → c-VJYEVRYK.js} +1 -1
- package/dist/m/c-VR2BJ4AN.js +1 -0
- package/dist/m/c-W4JCVLAB.js +1 -0
- package/dist/m/c-W6DQSHXI.js +135 -0
- package/dist/m/c-WBGWGOM7.js +1 -0
- package/dist/m/{c-4L3BTFRK.js → c-WCXLG3MV.js} +1 -1
- package/dist/m/c-WGW764WX.js +1 -0
- package/dist/m/c-WMRJKQ2V.js +5 -0
- package/dist/m/c-WNTYKCDG.js +1 -0
- package/dist/m/c-WQWIRWO3.js +72 -0
- package/dist/m/c-X5LNHET5.js +1 -0
- package/dist/m/c-X5TSXZYJ.js +2 -0
- package/dist/m/c-XGH4GS2T.js +1 -0
- package/dist/m/c-Y6F2FORL.js +1 -0
- package/dist/m/c-YBKHUAK7.js +10 -0
- package/dist/m/c-YBSS55KU.js +20 -0
- package/dist/m/c-YCLU4UNT.js +2 -0
- package/dist/m/c-YVZ2L7CE.js +1 -0
- package/dist/m/c-ZK4GIZH2.js +1 -0
- package/dist/m/c-ZRXJD4G6.js +1 -0
- package/dist/m/c-ZWBI4HYK.js +1 -0
- package/dist/m/{c-HT33EQRN.js → c-ZWIOJGMT.js} +1 -1
- package/dist/m/core/admin-attrs-YY7T5FGU.js +1 -0
- package/dist/m/core/autosave-S6OMNDCK.js +1 -0
- package/dist/m/core/conflict-notice-IWHMHH43.js +1 -0
- package/dist/m/core/host-permission-6EVJGNPL.js +1 -0
- package/dist/m/core/is-edit-mode-GCX6767E.js +1 -0
- package/dist/m/core/{persist-HKHA66IY.js → persist-UKXV5KLA.js} +1 -1
- package/dist/m/core/recovery-DRFZRJED.js +1 -0
- package/dist/m/core/save-LYY7K6U7.js +1 -0
- package/dist/m/core/save-core-MKRPSEEO.js +1 -0
- package/dist/m/core/snapshot-NU3YM6GR.js +1 -0
- package/dist/m/core/unsaved-warning-7QTKEN7Z.js +1 -0
- package/dist/m/events/index-RN2JNGBV.js +1 -0
- package/dist/m/internals/index-TGLQINN3.js +1 -0
- package/dist/m/lib/cache-bust-MUMFXZK6.js +1 -0
- package/dist/m/loader-3BBNZZGM.js +1 -0
- package/dist/m/manifest-F449FF98.js +1 -0
- package/dist/m/manifest-edit-4396A009.js +1 -0
- package/dist/m/manifest.json +2509 -1474
- package/dist/m/options/options-EJMS3K24.js +1 -0
- package/dist/m/page/editor-6LRB3GE4.js +304 -0
- package/dist/m/plugins/ai-edit-UTV7YO2I.js +6 -0
- package/dist/m/plugins/{cms-HP3LSTPN.js → cms-LRAOZHT7.js} +9 -9
- package/dist/m/plugins/demo-OYO5GCAO.js +1 -0
- package/dist/m/plugins/{derive-UAAA4V4S.js → derive-FRE5JJQE.js} +1 -1
- package/dist/m/plugins/indicator-4VDXWYTD.js +1 -0
- package/dist/m/plugins/live-AD4H6XSL.js +1 -0
- package/dist/m/plugins/menu-M5WW7DBF.js +1 -0
- package/dist/m/plugins/page-MD3T2ZX3.js +2 -0
- package/dist/m/plugins/page-view-PAWVZNFY.js +1 -0
- package/dist/m/plugins/people-JCHVPF7P.js +1 -0
- package/dist/m/plugins/quickcrop-BQ5YYFCH.js +26 -0
- package/dist/m/plugins/{richclay-RC3L2F6X.js → richclay-KLGETK77.js} +2 -2
- package/dist/m/plugins/sortable-4S6SQC3D.js +1 -0
- package/dist/m/plugins/source-TJXOIOYY.js +1 -0
- package/dist/m/plugins/undo-NLPGCJQY.js +1 -0
- package/dist/m/plugins/{unsent-HJFLQJ2O.js → unsent-OA4RGBUT.js} +1 -1
- package/dist/m/plugins/upgrade-HSUNDNJD.js +1 -0
- package/dist/m/plugins/upload-NHGJXZK2.js +1 -0
- package/dist/m/plugins/visitor-drafts-BW43DGY3.js +20 -0
- package/dist/m/plugins/visitor-drafts-owner-7ODQG7JX.js +1 -0
- package/dist/m/plugins/wire-VBWXEXU2.js +1 -0
- package/dist/m/reader-core-4W2KYEWQ.js +77 -0
- package/dist/m/route/route-62CHXL5B.js +1 -0
- package/dist/m/sync/live-sync-MO5DH33W.js +1 -0
- package/dist/m/ui/index-IVWN2QC4.js +1 -0
- package/dist/m/vendor/sap-core.vendor-RN4ZVY4X.js +6 -0
- package/dist/offline-manifest.json +1560 -889
- package/dist/page/themes/light-1.css +33 -8
- package/dist/server/core.cjs +1554 -311
- package/dist/server/offline-runtime.cjs +63 -63
- package/dist/server/offline-runtime.mjs +63 -63
- package/dist/server/page-compaction-worker.cjs +3710 -0
- package/dist/server/rooms.mjs +5 -0
- package/docs/guide.md +653 -101
- package/entries/sap.js +5 -5
- package/package.json +1 -1
- package/src/core/admin-contenteditable.js +5 -2
- package/src/core/admin-inputs.js +1 -0
- package/src/core/admin-onclick.js +2 -1
- package/src/core/boot-phase.js +2 -1
- package/src/core/conflict-download.js +3 -29
- package/src/core/conflict-notice-ui.js +4 -7
- package/src/core/download.js +32 -0
- package/src/core/host-meta.js +56 -0
- package/src/core/is-edit-mode.js +17 -1
- package/src/core/mode-key.js +125 -19
- package/src/core/mode-support.js +5 -0
- package/src/core/mode-transition.js +20 -6
- package/src/core/preserve-authored.js +4 -4
- package/src/core/recovery.js +121 -33
- package/src/core/save-accepted.js +33 -0
- package/src/core/save-core.js +111 -32
- package/src/core/save-job.js +108 -0
- package/src/core/save-prepare.js +53 -0
- package/src/core/save-size-notice.js +3 -1
- package/src/core/save-slices.js +15 -0
- package/src/core/save.js +557 -127
- package/src/core/serialize-steps.js +109 -0
- package/src/core/snapshot.js +209 -40
- package/src/core/source-map.js +83 -38
- package/src/core/stale-host-notice.js +1 -0
- package/src/core/upgrade-run.js +59 -8
- package/src/dom/active-element.js +5 -0
- package/src/lib/assets.js +1 -0
- package/src/lib/autosave-scheduler.js +3 -4
- package/src/lib/slice.js +65 -0
- package/src/lib/uimotion.js +774 -0
- package/src/lib/unsaved-state.js +2 -2
- package/src/live/fast-lane.js +10 -3
- package/src/live/local-lane.js +202 -11
- package/src/live/ops.js +16 -0
- package/src/live/page-compaction.js +162 -0
- package/src/live/server-lane.js +1 -0
- package/src/live/store.js +11 -3
- package/src/loader-logic.js +3 -1
- package/src/loader.js +14 -4
- package/src/page/adopt-boot.js +2 -1
- package/src/page/awareness.js +33 -2
- package/src/page/blocks.js +122 -27
- package/src/page/board-views.js +2 -16
- package/src/page/boot-pack.js +1 -1
- package/src/page/chart-tables.js +29 -0
- package/src/page/chart.js +411 -72
- package/src/page/checklist.js +10 -135
- package/src/page/checkpoint-proof.js +295 -0
- package/src/page/chrome.js +257 -40
- package/src/page/clipboard-shape.js +126 -0
- package/src/page/document-title-dom.js +12 -0
- package/src/page/document-title-runtime.js +15 -1
- package/src/page/document-title.js +4 -1
- package/src/page/drag.js +33 -3
- package/src/page/editor.js +207 -31
- package/src/page/emoji-data.js +162 -0
- package/src/page/export-assets.js +357 -0
- package/src/page/export-html.js +209 -0
- package/src/page/export-markdown.js +361 -0
- package/src/page/features/board.js +12 -3
- package/src/page/features/chart.js +11 -0
- package/src/page/features/checklist.js +428 -0
- package/src/page/features/dates.js +180 -0
- package/src/page/features/emoji.js +187 -0
- package/src/page/features/export-html.js +189 -0
- package/src/page/features/export-markdown.js +169 -0
- package/src/page/features/import-markdown.js +349 -0
- package/src/page/features/media.js +61 -5
- package/src/page/features/mentions.js +188 -0
- package/src/page/features/registry.js +24 -1
- package/src/page/features/table.js +375 -85
- package/src/page/features/title-cover.js +331 -0
- package/src/page/features/title-icon.js +213 -0
- package/src/page/features/toc.js +87 -0
- package/src/page/format-command.js +33 -0
- package/src/page/format-gate.js +68 -18
- package/src/page/format-host.js +71 -0
- package/src/page/format-native-commands.js +158 -0
- package/src/page/format-native.js +42 -0
- package/src/page/image-actions.js +64 -0
- package/src/page/import-markdown.js +262 -0
- package/src/page/inline.js +32 -8
- package/src/page/items.js +141 -11
- package/src/page/markdown.js +30 -2
- package/src/page/media.js +35 -119
- package/src/page/mention-picker.js +99 -0
- package/src/page/merge.js +1 -1
- package/src/page/placeholder.js +14 -1
- package/src/page/projection.js +156 -25
- package/src/page/recover.js +10 -12
- package/src/page/recovery-ui.js +464 -0
- package/src/page/rich/chart.js +63 -7
- package/src/page/save-projection.js +67 -0
- package/src/page/save-states.js +14 -4
- package/src/page/schema.js +52 -3
- package/src/page/table-paste.js +3 -1
- package/src/page/table-style.js +4 -4
- package/src/page/table.js +44 -2
- package/src/page/themes/content.css +25 -1
- package/src/page/themes/light-1.css +33 -8
- package/src/page/themes/light-1.source.css +8 -7
- package/src/page/toc-link.js +31 -0
- package/src/page/toc.js +5 -0
- package/src/page/view-hooks.js +58 -0
- package/src/page/view.js +13 -0
- package/src/page/ybind/binding.js +116 -6
- package/src/page/ybind/bridge.js +187 -20
- package/src/page/ybind/containment.js +162 -0
- package/src/page/ybind/doc.js +17 -9
- package/src/page/ybind/file-debt.js +63 -0
- package/src/page/ybind/layout.js +7 -2
- package/src/page/ybind/marks.js +2 -1
- package/src/page/ybind/moves.js +5 -4
- package/src/page/ybind/origins.js +106 -4
- package/src/page/ybind/parts.js +8 -1
- package/src/page/ybind/provider.js +125 -18
- package/src/page/ybind/recovery-proof.js +32 -9
- package/src/page/ybind/recovery-replay.js +905 -4
- package/src/page/ybind/recovery.js +174 -45
- package/src/page/ybind/table.js +67 -11
- package/src/page/ybind/walk-delta.js +258 -0
- package/src/page/ybind/walk.js +54 -29
- package/src/page/ybind/writer.js +1 -1
- package/src/plugins/ai-edit.js +13 -9
- package/src/plugins/indicator-chip.js +91 -0
- package/src/plugins/indicator.js +13 -80
- package/src/plugins/live.js +126 -15
- package/src/plugins/menu/actions.js +196 -0
- package/src/plugins/menu/people.js +279 -0
- package/src/plugins/menu/placement.js +121 -0
- package/src/plugins/menu/preferences.js +9 -0
- package/src/plugins/menu/seo.js +316 -397
- package/src/plugins/menu/style.js +171 -0
- package/src/plugins/menu/variants.js +296 -0
- package/src/plugins/menu.js +186 -183
- package/src/plugins/page-view.js +3 -2
- package/src/plugins/page.js +362 -43
- package/src/plugins/sortable.js +8 -1
- package/src/plugins/source.js +6 -0
- package/src/plugins/upgrade.js +51 -17
- package/src/plugins/upload-ui.js +12 -17
- package/src/plugins/visitor-drafts/bottom-ui.js +12 -4
- package/src/plugins/visitor-drafts/intro-style.js +7 -2
- package/src/plugins/visitor-drafts/intro.js +7 -4
- package/src/plugins/visitor-drafts/owner-chip.js +8 -12
- package/src/plugins/visitor-drafts/prompt.js +11 -9
- package/src/plugins/visitor-drafts/source.js +4 -3
- package/src/plugins/visitor-drafts/store.js +138 -9
- package/src/plugins/visitor-drafts/ui-memory.js +2 -2
- package/src/plugins/visitor-drafts-owner.js +4 -6
- package/src/plugins/visitor-drafts.js +5 -3
- package/src/recovery/store.js +1 -1
- package/src/route/route.js +4 -0
- package/src/server/checkpoint/index.js +2 -2
- package/src/server/rooms/index.js +5 -1
- package/src/server/rooms/live-data-rooms.js +302 -50
- package/src/server/rooms/page-compaction-worker.js +12 -0
- package/src/server/rooms/page-compactor.js +92 -0
- package/src/server/rooms/page-room-conformance.js +461 -0
- package/src/source/parse-steps.js +94 -0
- package/src/source/session.js +233 -57
- package/src/sync/editing-sender.js +12 -0
- package/src/sync/live-sync-edit.js +22 -2
- package/src/sync/live-sync.js +150 -23
- package/src/sync/page-relay.js +52 -0
- package/src/sync/presence-store.js +44 -4
- package/src/sync/presence.js +6 -3
- package/src/sync/saved-stream.js +28 -14
- package/src/ui/bevel-controls.js +1 -0
- package/src/ui/bevel-dialog.js +5 -2
- package/src/ui/editor-ui.js +77 -0
- package/src/ui/page-colour.js +281 -0
- package/src/ui/toast.js +16 -7
- package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
- package/src/vendor/hyper-undo.vendor.js +1 -1
- package/src/vendor/hypercms.vendor.js +9 -9
- package/src/vendor/parse5.vendor.js +2 -2
- package/src/vendor/sap-core.vendor.js +5 -5
- package/dist/m/attrs/refetch-on-save-NWPPXOCZ.js +0 -1
- package/dist/m/attrs/save-freeze-FUP4Q4X3.js +0 -1
- package/dist/m/c-2M73252O.js +0 -1
- package/dist/m/c-2SPVQSHC.js +0 -1
- package/dist/m/c-35NBGMZB.js +0 -1
- package/dist/m/c-3IQRECYS.js +0 -1
- package/dist/m/c-4B6H4G5V.js +0 -1
- package/dist/m/c-4FFHZM32.js +0 -1
- package/dist/m/c-4YSCRICO.js +0 -1
- package/dist/m/c-54RCDCBU.js +0 -1
- package/dist/m/c-5M4YEKCX.js +0 -4
- package/dist/m/c-5V3MOVJT.js +0 -1
- package/dist/m/c-66O32MV3.js +0 -28
- package/dist/m/c-6HMB24EV.js +0 -1
- package/dist/m/c-7O6AFR65.js +0 -1
- package/dist/m/c-ANQTJ3B5.js +0 -2
- package/dist/m/c-AYVOREBE.js +0 -1
- package/dist/m/c-B2ZXPOS3.js +0 -3
- package/dist/m/c-BMIZKMPV.js +0 -1
- package/dist/m/c-BTDI4Y23.js +0 -1
- package/dist/m/c-CJZSLMVQ.js +0 -1
- package/dist/m/c-DBE6ORSI.js +0 -1
- package/dist/m/c-EDHUR4MX.js +0 -1
- package/dist/m/c-EY6UOM54.js +0 -1
- package/dist/m/c-F3CKJTJS.js +0 -1
- package/dist/m/c-F7Y6FLPY.js +0 -1
- package/dist/m/c-FA4DZVTC.js +0 -1
- package/dist/m/c-G2BUZITR.js +0 -1
- package/dist/m/c-GB3JTV6E.js +0 -1
- package/dist/m/c-GBGWXTX4.js +0 -1
- package/dist/m/c-GHBCCVG4.js +0 -1
- package/dist/m/c-GJAEGGXY.js +0 -1
- package/dist/m/c-GOWBDM6J.js +0 -1
- package/dist/m/c-IB52R237.js +0 -1
- package/dist/m/c-IKWNE4R4.js +0 -1
- package/dist/m/c-IU3IYEU6.js +0 -1
- package/dist/m/c-IZ2EBRAJ.js +0 -1
- package/dist/m/c-KR4UW4KW.js +0 -1
- package/dist/m/c-KY3VJGUA.js +0 -8
- package/dist/m/c-LFPHCXY3.js +0 -1
- package/dist/m/c-LRPUO2MX.js +0 -1
- package/dist/m/c-LXENEBV7.js +0 -1
- package/dist/m/c-NMJRXKPC.js +0 -8
- package/dist/m/c-OIBUTDXI.js +0 -1
- package/dist/m/c-PPOSCAQD.js +0 -1
- package/dist/m/c-QIX5BJYN.js +0 -1
- package/dist/m/c-QQHEX6HA.js +0 -1
- package/dist/m/c-R7RMANJI.js +0 -9
- package/dist/m/c-RI4NT75Y.js +0 -1
- package/dist/m/c-RM7J7BV2.js +0 -1
- package/dist/m/c-RUJKSUQW.js +0 -1
- package/dist/m/c-SGCCC4FW.js +0 -25
- package/dist/m/c-T4VO4OCB.js +0 -1
- package/dist/m/c-TQEMVJCS.js +0 -1
- package/dist/m/c-TR6RMDQ7.js +0 -3
- package/dist/m/c-UKHTLWUW.js +0 -1
- package/dist/m/c-UNN7LWCT.js +0 -1
- package/dist/m/c-UXBMRDZB.js +0 -235
- package/dist/m/c-V7PZZRG7.js +0 -1
- package/dist/m/c-VL52ADDR.js +0 -1
- package/dist/m/c-WNYBLIOZ.js +0 -1
- package/dist/m/c-WWDBYXLK.js +0 -1
- package/dist/m/c-XNNW2CWK.js +0 -1
- package/dist/m/c-Y6USGTS5.js +0 -5
- package/dist/m/c-YG2NJYS6.js +0 -135
- package/dist/m/c-Z4VRC7GU.js +0 -7
- package/dist/m/c-Z6K52OLP.js +0 -19
- package/dist/m/c-ZDY2MDAA.js +0 -21
- package/dist/m/c-ZUCOKOHT.js +0 -1
- package/dist/m/core/admin-attrs-VGNLAUDK.js +0 -1
- package/dist/m/core/autosave-E4PEFPZ6.js +0 -1
- package/dist/m/core/conflict-notice-VQ6Y64PH.js +0 -1
- package/dist/m/core/host-permission-XN3K2IW3.js +0 -1
- package/dist/m/core/is-edit-mode-JXHDVGUL.js +0 -1
- package/dist/m/core/recovery-TBBSICE2.js +0 -1
- package/dist/m/core/save-WUCHTQBE.js +0 -1
- package/dist/m/core/save-core-FJVHT5M3.js +0 -1
- package/dist/m/core/snapshot-F3SWDZMM.js +0 -1
- package/dist/m/core/unsaved-warning-STKBHE6G.js +0 -1
- package/dist/m/events/index-23DPTUT4.js +0 -1
- package/dist/m/internals/index-WOVAP3RU.js +0 -1
- package/dist/m/lib/cache-bust-CGWT6ZJI.js +0 -1
- package/dist/m/loader-HXX6YNH5.js +0 -1
- package/dist/m/manifest-F22DEDAC.js +0 -1
- package/dist/m/manifest-edit-C3C7376B.js +0 -1
- package/dist/m/options/options-2LH76JQ3.js +0 -1
- package/dist/m/page/editor-XUTB2HB2.js +0 -189
- package/dist/m/plugins/ai-edit-ZQ4CGWIP.js +0 -6
- package/dist/m/plugins/demo-ZIBFAA3R.js +0 -1
- package/dist/m/plugins/indicator-PV4YW2ID.js +0 -1
- package/dist/m/plugins/live-NUTTXSBS.js +0 -1
- package/dist/m/plugins/menu-HCKO3DSP.js +0 -138
- package/dist/m/plugins/page-KSUH7G6J.js +0 -2
- package/dist/m/plugins/page-view-W73OW7WI.js +0 -1
- package/dist/m/plugins/people-AJJ32YCG.js +0 -1
- package/dist/m/plugins/quickcrop-QNY3PKLD.js +0 -26
- package/dist/m/plugins/sortable-YRMQX3ZU.js +0 -1
- package/dist/m/plugins/source-KHK5UUSK.js +0 -1
- package/dist/m/plugins/undo-OF2L3CXU.js +0 -1
- package/dist/m/plugins/upgrade-ZNYODP5Y.js +0 -1
- package/dist/m/plugins/upload-XSGNILUY.js +0 -1
- package/dist/m/plugins/visitor-drafts-XK5GW55Q.js +0 -21
- package/dist/m/plugins/visitor-drafts-owner-KSSH75EV.js +0 -1
- package/dist/m/plugins/wire-ZWVWKF76.js +0 -1
- package/dist/m/reader-core-ILWLXVMP.js +0 -68
- package/dist/m/route/route-KOCZSEGN.js +0 -1
- package/dist/m/sync/live-sync-4AUVXVJU.js +0 -1
- package/dist/m/ui/index-EVHKMKBL.js +0 -1
- package/dist/m/vendor/sap-core.vendor-A7BMUYZT.js +0 -6
- /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
- /package/dist/m/plugins/{quickcrop-QNY3PKLD.js.LEGAL.txt → quickcrop-BQ5YYFCH.js.LEGAL.txt} +0 -0
- /package/dist/m/plugins/{richclay-RC3L2F6X.js.LEGAL.txt → richclay-KLGETK77.js.LEGAL.txt} +0 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// Markdown import: the tokens marked's lexer returns become the page's flat block
|
|
2
|
+
// records (model.js), so a file arrives without marked rendering anything. Raw HTML
|
|
3
|
+
// stays inert: a block of it is a preserved block, cleaned of scripts, event handlers
|
|
4
|
+
// and script URLs, and inline markup is literal text. A construct the schema cannot
|
|
5
|
+
// express is kept as a literal code block and named in `unsupported`; a link or
|
|
6
|
+
// image address the editor refuses is left out and named in `refused`, its text
|
|
7
|
+
// kept. Nothing is dropped, and a limit refuses with a code rather than mutating.
|
|
8
|
+
import { schema } from "./schema.js";
|
|
9
|
+
import { markKey, runsOf } from "./inline.js";
|
|
10
|
+
import { keysBetween, newId } from "./model.js";
|
|
11
|
+
import { fragmentCleaner } from "./projection.js";
|
|
12
|
+
import { safeHref, safeSrc } from "./address.js";
|
|
13
|
+
|
|
14
|
+
// HTML in a file someone sent is kept for its formatting, never for what it can load or
|
|
15
|
+
// run: anything that embeds or fetches another document goes, and so does every address
|
|
16
|
+
// that runs script, whichever attribute holds it.
|
|
17
|
+
const EMBEDS = "iframe, frame, frameset, object, embed, applet, meta, base, link, form, style, animate, set, animateMotion, animateTransform, portal";
|
|
18
|
+
const LOADERS = /^(srcdoc|data|formaction|codebase|archive|ping|background|poster|lowsrc|dynsrc)$/i;
|
|
19
|
+
const SCRIPTED = /^\s*(javascript|vbscript|data):/i;
|
|
20
|
+
function cleanImported(html) {
|
|
21
|
+
const t = document.createElement("template");
|
|
22
|
+
t.innerHTML = html;
|
|
23
|
+
for (const el of t.content.querySelectorAll(EMBEDS)) el.remove();
|
|
24
|
+
for (const el of t.content.querySelectorAll("*")) {
|
|
25
|
+
for (const { name, value } of [...el.attributes]) {
|
|
26
|
+
if (LOADERS.test(name) || SCRIPTED.test(value.replace(/[\u0000-\u0020]/g, ""))) el.removeAttribute(name);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return t.innerHTML;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const MAX_BYTES = 2 * 1024 * 1024;
|
|
33
|
+
export const MAX_DEPTH = 32;
|
|
34
|
+
export const MAX_BLOCKS = 10000;
|
|
35
|
+
|
|
36
|
+
export class MarkdownImportError extends Error {
|
|
37
|
+
constructor(code, message) {
|
|
38
|
+
super(message);
|
|
39
|
+
this.name = "MarkdownImportError";
|
|
40
|
+
this.code = code;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const ATOM = "\uFFFC";
|
|
45
|
+
const BREAK = { type: "hard_break" };
|
|
46
|
+
const BLOCK = new Set(["paragraph", "heading", "list", "blockquote", "code", "table", "hr", "html", "def", "space"]);
|
|
47
|
+
const encoder = new TextEncoder();
|
|
48
|
+
const M = schema.marks;
|
|
49
|
+
|
|
50
|
+
const sorted = (marks) => marks.slice().sort((a, b) => a.type.rank - b.type.rank);
|
|
51
|
+
|
|
52
|
+
function added(marks, name, attrs) {
|
|
53
|
+
if (marks.some((mark) => mark.type === M[name])) return marks;
|
|
54
|
+
return sorted([...marks, M[name].create(attrs)]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// One text block's inline content as the record keeps it: c, and m as runs (one
|
|
58
|
+
// key per UTF-16 unit, packed), exactly as flatten and runsOf write it; `parts`
|
|
59
|
+
// splits around an image, which the schema only carries as a block of its own.
|
|
60
|
+
function inlineOf(tokens, { images = true, refused = [], unsupported = [] } = {}) {
|
|
61
|
+
const parts = [];
|
|
62
|
+
let c = "";
|
|
63
|
+
let m = [];
|
|
64
|
+
const flush = () => {
|
|
65
|
+
if (c) parts.push({ c, m });
|
|
66
|
+
c = "";
|
|
67
|
+
m = [];
|
|
68
|
+
};
|
|
69
|
+
const text = (value, marks) => {
|
|
70
|
+
const str = String(value ?? "");
|
|
71
|
+
if (!str) return;
|
|
72
|
+
const key = markKey(sorted(marks));
|
|
73
|
+
c += str;
|
|
74
|
+
for (let i = 0; i < str.length; i++) m.push(key);
|
|
75
|
+
};
|
|
76
|
+
const atom = (json) => { c += ATOM; m.push("@" + JSON.stringify(json)); };
|
|
77
|
+
const walk = (list, marks) => {
|
|
78
|
+
for (const token of list ?? []) {
|
|
79
|
+
const type = token?.type;
|
|
80
|
+
if (typeof type !== "string") throw new MarkdownImportError("malformed-input", "A Markdown token has no type.");
|
|
81
|
+
switch (type) {
|
|
82
|
+
case "text":
|
|
83
|
+
if (Array.isArray(token.tokens)) walk(token.tokens.filter((child) => typeof child?.type === "string" && !BLOCK.has(child.type)), marks);
|
|
84
|
+
else text(token.text, marks);
|
|
85
|
+
break;
|
|
86
|
+
case "escape": text(token.text, marks); break;
|
|
87
|
+
case "strong": walk(token.tokens, added(marks, "strong")); break;
|
|
88
|
+
case "em": walk(token.tokens, added(marks, "em")); break;
|
|
89
|
+
case "del":
|
|
90
|
+
case "strike": walk(token.tokens, added(marks, "strike")); break;
|
|
91
|
+
case "codespan": text(token.text, added(marks, "code")); break;
|
|
92
|
+
case "br": atom(BREAK); break;
|
|
93
|
+
case "link": {
|
|
94
|
+
const href = safeHref(token.href);
|
|
95
|
+
if (href) walk(token.tokens, added(marks, "link", { href }));
|
|
96
|
+
else {
|
|
97
|
+
refused.push({ type: "link", href: String(token.href ?? "") });
|
|
98
|
+
walk(token.tokens, marks);
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case "image": {
|
|
103
|
+
const image = { src: String(token.href ?? ""), alt: String(token.text ?? ""), title: token.title ?? "" };
|
|
104
|
+
if (!images) { text(image.alt, marks); break; }
|
|
105
|
+
flush();
|
|
106
|
+
parts.push({ image });
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
// Raw HTML is inert: its text stays text, never a node or a mark.
|
|
110
|
+
case "html": text(token.text ?? token.raw, marks); break;
|
|
111
|
+
case "checkbox": break;
|
|
112
|
+
default:
|
|
113
|
+
unsupported.push({ type, text: String(token.raw ?? token.text ?? "") });
|
|
114
|
+
text(token.raw ?? token.text, marks);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
walk(tokens, []);
|
|
120
|
+
flush();
|
|
121
|
+
return { parts };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function importMarkdownTokens(tokens) {
|
|
125
|
+
if (!Array.isArray(tokens)) throw new MarkdownImportError("malformed-input", "Markdown import needs the lexer's token list.");
|
|
126
|
+
let size = 0;
|
|
127
|
+
for (const token of tokens) {
|
|
128
|
+
if (!token || typeof token !== "object" || typeof token.type !== "string") throw new MarkdownImportError("malformed-input", "A Markdown token has no type.");
|
|
129
|
+
size += encoder.encode(String(token.raw ?? "")).length;
|
|
130
|
+
}
|
|
131
|
+
if (size > MAX_BYTES) throw new MarkdownImportError("input-too-large", `Markdown import is limited to ${MAX_BYTES} bytes; this file is ${size}.`);
|
|
132
|
+
|
|
133
|
+
const blocks = [];
|
|
134
|
+
const unsupported = [];
|
|
135
|
+
const refused = [];
|
|
136
|
+
const used = new Set();
|
|
137
|
+
const fresh = (prefix) => {
|
|
138
|
+
let id = newId(prefix);
|
|
139
|
+
while (used.has(id)) id = newId(prefix);
|
|
140
|
+
used.add(id);
|
|
141
|
+
return id;
|
|
142
|
+
};
|
|
143
|
+
const push = (value) => {
|
|
144
|
+
if (blocks.length + 1 > MAX_BLOCKS) throw new MarkdownImportError("too-many-blocks", `Markdown import is limited to ${MAX_BLOCKS} blocks.`);
|
|
145
|
+
blocks.push(value);
|
|
146
|
+
};
|
|
147
|
+
const deeper = (ctx) => {
|
|
148
|
+
const depth = ctx.depth + 1;
|
|
149
|
+
if (depth > MAX_DEPTH) throw new MarkdownImportError("nesting-too-deep", `Markdown import is limited to ${MAX_DEPTH} levels of nesting.`);
|
|
150
|
+
return depth;
|
|
151
|
+
};
|
|
152
|
+
const block = (t, attrs, { c, m }) => {
|
|
153
|
+
const value = { t, ...attrs, c };
|
|
154
|
+
if (m.some(Boolean)) value.m = runsOf(m);
|
|
155
|
+
return value;
|
|
156
|
+
};
|
|
157
|
+
const imageOf = (image) => {
|
|
158
|
+
const src = safeSrc(image.src);
|
|
159
|
+
if (src) return { t: "image", src, ...(image.alt ? { alt: image.alt } : {}), ...(image.title ? { caption: image.title } : {}) };
|
|
160
|
+
refused.push({ type: "image", href: image.src });
|
|
161
|
+
return image.alt ? block("paragraph", {}, { c: image.alt, m: [] }) : null;
|
|
162
|
+
};
|
|
163
|
+
const textBlocks = (t, attrs, inline) => {
|
|
164
|
+
for (const part of inline.parts) {
|
|
165
|
+
if (!part.image) { push(block(t, attrs, part)); continue; }
|
|
166
|
+
const value = imageOf(part.image);
|
|
167
|
+
if (value) push(value);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const paragraph = (inline, ctx) => textBlocks(ctx.quote ? "quote" : "paragraph", {}, inline);
|
|
171
|
+
const inline = (tokens, options = {}) => inlineOf(tokens, { refused, unsupported, ...options });
|
|
172
|
+
const literal = (token) => {
|
|
173
|
+
const raw = String(token.raw ?? token.text ?? "");
|
|
174
|
+
unsupported.push({ type: token.type, text: raw });
|
|
175
|
+
push({ t: "code_block", lang: "markdown", c: raw });
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const walk = (list, ctx) => {
|
|
179
|
+
for (const token of list ?? []) {
|
|
180
|
+
const type = token?.type;
|
|
181
|
+
if (typeof type !== "string") throw new MarkdownImportError("malformed-input", "A Markdown token has no type.");
|
|
182
|
+
switch (type) {
|
|
183
|
+
case "space": break;
|
|
184
|
+
case "heading": {
|
|
185
|
+
const level = Math.min(6, Math.max(1, Number.parseInt(token.depth, 10) || 1));
|
|
186
|
+
textBlocks("heading", level === 2 ? {} : { level }, inline(token.tokens, { images: false }));
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
case "paragraph":
|
|
190
|
+
case "text": paragraph(inline(token.tokens ?? [token]), ctx); break;
|
|
191
|
+
case "hr": push({ t: "divider" }); break;
|
|
192
|
+
case "code": push({ t: "code_block", ...(token.lang ? { lang: String(token.lang) } : {}), c: String(token.text ?? "") }); break;
|
|
193
|
+
case "html": {
|
|
194
|
+
const raw = String(token.text ?? token.raw ?? "");
|
|
195
|
+
unsupported.push({ type: "HTML block cleaned", text: raw });
|
|
196
|
+
push({ t: "preserved", html: cleanImported(fragmentCleaner(document).fallback(raw)) });
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case "blockquote": {
|
|
200
|
+
const depth = deeper(ctx);
|
|
201
|
+
walk(token.tokens, { ...ctx, depth, quote: true });
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case "list": {
|
|
205
|
+
const depth = deeper(ctx);
|
|
206
|
+
const level = ctx.indent ?? 0;
|
|
207
|
+
const kind = token.ordered ? "ordered" : "bullet";
|
|
208
|
+
for (const item of token.items ?? []) {
|
|
209
|
+
let own = { c: "", m: [] };
|
|
210
|
+
const rest = [];
|
|
211
|
+
for (const child of item?.tokens ?? []) {
|
|
212
|
+
const childType = child?.type;
|
|
213
|
+
if (childType === "checkbox" || childType === "space") continue;
|
|
214
|
+
if (childType === "text" || childType === "paragraph") {
|
|
215
|
+
if (own.c) own = { c: own.c + ATOM, m: [...own.m, "@" + JSON.stringify(BREAK)] };
|
|
216
|
+
const tokens = childType === "paragraph" ? child.tokens ?? [] : [child];
|
|
217
|
+
for (const part of inline(tokens, { images: false }).parts) {
|
|
218
|
+
own = { c: own.c + part.c, m: [...own.m, ...part.m] };
|
|
219
|
+
}
|
|
220
|
+
rest.push(...(child.tokens ?? []).filter((nested) => typeof nested?.type === "string" && BLOCK.has(nested.type) && nested.type !== "space"));
|
|
221
|
+
} else rest.push(child);
|
|
222
|
+
}
|
|
223
|
+
const itemKind = item?.task ? "check" : kind;
|
|
224
|
+
const attrs = {
|
|
225
|
+
...(itemKind === "bullet" ? {} : { kind: itemKind }),
|
|
226
|
+
...(level ? { indent: level } : {}),
|
|
227
|
+
...(item?.task && item.checked ? { checked: true } : {}),
|
|
228
|
+
};
|
|
229
|
+
push(block("list_item", attrs, own));
|
|
230
|
+
if (rest.length) walk(rest, { ...ctx, depth, indent: level + 1, quote: false });
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
case "table": {
|
|
235
|
+
const header = token.header ?? [];
|
|
236
|
+
if (!header.length) { literal(token); break; }
|
|
237
|
+
const cell = (tokens) => inline(tokens, { images: false }).parts[0] ?? { c: "", m: [] };
|
|
238
|
+
const colIds = header.map(() => fresh("c"));
|
|
239
|
+
const colPos = keysBetween(null, null, header.length);
|
|
240
|
+
const cols = Object.fromEntries(colIds.map((id, i) => [id, { pos: colPos[i], name: cell(header[i]?.tokens).c }]));
|
|
241
|
+
// The header line is the column names, drawn as <thead>; the body holds the data rows.
|
|
242
|
+
const rows = token.rows?.length ? token.rows : [[]];
|
|
243
|
+
const rowPos = keysBetween(null, null, rows.length);
|
|
244
|
+
const values = rows.map((row) => Object.fromEntries(colIds.map((id, i) => {
|
|
245
|
+
const part = cell(row?.[i]?.tokens);
|
|
246
|
+
return [id, part.m.some(Boolean) ? { c: part.c, m: runsOf(part.m) } : part.c];
|
|
247
|
+
})));
|
|
248
|
+
push({ t: "table", cols, rows: Object.fromEntries(values.map((cells, i) => [fresh("r"), { pos: rowPos[i], cells }])) });
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
default: literal(token); break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
walk(tokens, { depth: 0, indent: 0, quote: false });
|
|
257
|
+
|
|
258
|
+
const positions = keysBetween(null, null, blocks.length);
|
|
259
|
+
const out = {};
|
|
260
|
+
blocks.forEach((value, i) => { out[fresh("b")] = { ...value, pos: positions[i] }; });
|
|
261
|
+
return { blocks: out, unsupported, refused };
|
|
262
|
+
}
|
package/src/page/inline.js
CHANGED
|
@@ -14,28 +14,41 @@
|
|
|
14
14
|
// encodeForMerge (replay.js) gives each one a code of its own.
|
|
15
15
|
// A mark this schema has no type for keeps its token or named descriptor
|
|
16
16
|
// instead of throwing (format-codec.js): the unit's key is written back exactly
|
|
17
|
-
// as it was read.
|
|
17
|
+
// as it was read. A mark of this schema with no compact code of its own is
|
|
18
|
+
// written as a named descriptor too, x[name, attrs], so a new mark never takes a
|
|
19
|
+
// letter an older writer already reads.
|
|
18
20
|
import { model } from "../vendor/prosemirror.vendor.js";
|
|
19
21
|
import { descriptors, entriesOf, isOpaque, keyOf, opaqueMark } from "./format-codec.js";
|
|
22
|
+
import { nativeAttrs } from "./format-native.js";
|
|
20
23
|
|
|
21
24
|
export const ATOM = "";
|
|
22
25
|
export const CODES = { strong: "b", em: "i", strike: "s", code: "c", link: "a" };
|
|
26
|
+
const NATIVE = new Set(["underline", "text_color", "highlight"]);
|
|
23
27
|
const NAMES = Object.fromEntries(Object.entries(CODES).map(([name, code]) => [code, name]));
|
|
24
28
|
|
|
25
29
|
export function markKey(marks) {
|
|
26
30
|
let key = "";
|
|
31
|
+
const named = [];
|
|
27
32
|
for (const mark of marks) {
|
|
28
33
|
if (isOpaque(mark)) {
|
|
29
34
|
key += keyOf(mark.attrs.entries);
|
|
30
35
|
continue;
|
|
31
36
|
}
|
|
32
37
|
const code = CODES[mark.type.name];
|
|
33
|
-
if (!code)
|
|
38
|
+
if (!code) {
|
|
39
|
+
// A mark with no compact letter travels as a named descriptor, after the codes
|
|
40
|
+
// and in the codec's order: one of the four formats only with attrs it writes,
|
|
41
|
+
// any other (a mention, a date) with its own attrs.
|
|
42
|
+
const native = nativeAttrs(mark.type.name, mark.attrs);
|
|
43
|
+
if (!native && NATIVE.has(mark.type.name)) throw new TypeError(`page: the mark ${mark.type.name} has no key code`);
|
|
44
|
+
named.push({ name: mark.type.name, attrs: native ?? mark.attrs });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
34
47
|
key += code;
|
|
35
48
|
const names = Object.keys(mark.type.attrs);
|
|
36
49
|
if (names.length) key += JSON.stringify(names.map((name) => mark.attrs[name]));
|
|
37
50
|
}
|
|
38
|
-
return key;
|
|
51
|
+
return named.length ? key + keyOf(named) : key;
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
const parsed = new WeakMap();
|
|
@@ -49,15 +62,26 @@ export function marksOf(schema, key) {
|
|
|
49
62
|
marks = [];
|
|
50
63
|
const opaque = [];
|
|
51
64
|
for (const desc of descriptors(key)) {
|
|
52
|
-
|
|
65
|
+
// A named descriptor this schema does have a type for is that mark, not an opaque
|
|
66
|
+
// entry: one of the four formats only with attrs it writes, any other mark only
|
|
67
|
+
// with attributes its type declares. Anything else stays opaque, so nothing is lost.
|
|
68
|
+
if (desc.code === undefined) {
|
|
69
|
+
const native = nativeAttrs(desc.name, desc.attrs);
|
|
70
|
+
const type = schema.marks[desc.name];
|
|
71
|
+
if (native) marks = type.create(native).addToSet(marks);
|
|
72
|
+
else if (type && !NATIVE.has(desc.name) && Object.keys(desc.attrs ?? {}).every((field) => Object.hasOwn(type.attrs, field))) marks = type.create(desc.attrs).addToSet(marks);
|
|
73
|
+
else opaque.push(desc);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const name = NAMES[desc.code];
|
|
53
77
|
if (!name) {
|
|
54
78
|
opaque.push(desc);
|
|
55
79
|
continue;
|
|
56
80
|
}
|
|
57
|
-
const
|
|
58
|
-
if (!
|
|
59
|
-
const attrs = desc.attrs ? Object.fromEntries(Object.keys(
|
|
60
|
-
marks =
|
|
81
|
+
const coded = schema.marks[name];
|
|
82
|
+
if (!coded) throw new TypeError(`page: unknown mark code ${desc.code} in ${key}`);
|
|
83
|
+
const attrs = desc.attrs ? Object.fromEntries(Object.keys(coded.attrs).map((field, j) => [field, desc.attrs[j]])) : null;
|
|
84
|
+
marks = coded.create(attrs).addToSet(marks);
|
|
61
85
|
}
|
|
62
86
|
if (opaque.length) marks = opaqueMark(entriesOf(opaque)).addToSet(marks);
|
|
63
87
|
cache.set(key, marks);
|
package/src/page/items.js
CHANGED
|
@@ -12,7 +12,9 @@ import { pickFiles, hostPlace } from "./media.js";
|
|
|
12
12
|
import { makeTable, insertTableNode } from "./table.js";
|
|
13
13
|
import { pickCsv } from "./csv-import.js";
|
|
14
14
|
import { makeBoard, insertBoardNode } from "./board.js";
|
|
15
|
-
import {
|
|
15
|
+
import { chartTables } from "./chart-tables.js";
|
|
16
|
+
import { viewFeatures } from "./view-hooks.js";
|
|
17
|
+
import { runFormatted } from "./format-command.js";
|
|
16
18
|
|
|
17
19
|
const { TextSelection } = state;
|
|
18
20
|
const N = schema.nodes;
|
|
@@ -26,21 +28,34 @@ export const BLOCK_TYPES = [
|
|
|
26
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"] },
|
|
27
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"] },
|
|
28
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"] },
|
|
29
34
|
{ key: "callout", label: "Callout", description: "Make a note stand out", icon: "!", type: "callout", aliases: ["note", "aside", "info"] },
|
|
35
|
+
{ key: "quote", label: "Quote", description: "Set text apart as a quotation", icon: "\u201c", type: "quote", hint: "\"", aliases: ["blockquote", "quotation", "cite"] },
|
|
30
36
|
{ key: "code", label: "Code", description: "Code with highlighting", icon: "</>", type: "code_block", hint: "```", aliases: ["pre", "snippet"] },
|
|
31
37
|
];
|
|
32
38
|
|
|
33
39
|
export const INSERT_ITEMS = [
|
|
34
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" },
|
|
35
42
|
{ key: "image", label: "Image", description: "Upload or paste a picture", icon: "▣", aliases: ["img", "picture", "photo"], insert: "image" },
|
|
36
43
|
{ key: "file", label: "File", description: "Attach any file", icon: "⎘", aliases: ["upload", "attachment", "pdf"], insert: "file" },
|
|
37
44
|
{ key: "table", label: "Table", description: "Rows and columns, paste from a spreadsheet", icon: "▦", aliases: ["grid", "spreadsheet"], insert: "table" },
|
|
38
45
|
{ key: "csv", label: "Import CSV…", description: "Turn a CSV file into a table", icon: "⇧", aliases: ["comma separated", "import"], insert: "csv" },
|
|
39
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" },
|
|
40
51
|
];
|
|
41
52
|
|
|
42
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" };
|
|
43
54
|
|
|
55
|
+
// The chart module and its view part load together, before the block draws: a
|
|
56
|
+
// document that never takes a chart never fetches either.
|
|
57
|
+
export const loadChartPart = (view) => (viewFeatures.get(view)?.ensure("chart") ?? Promise.resolve()).catch(() => {}).then(() => (view.isDestroyed ? null : import("./chart.js")));
|
|
58
|
+
|
|
44
59
|
export const MENU_ITEMS = [...BLOCK_TYPES, ...INSERT_ITEMS];
|
|
45
60
|
|
|
46
61
|
export const insertItemsFor = (st, items = MENU_ITEMS) => (chartTables(st.doc).length ? [...items, CHART_ITEM] : items);
|
|
@@ -49,7 +64,8 @@ export const insertItemsFor = (st, items = MENU_ITEMS) => (chartTables(st.doc).l
|
|
|
49
64
|
export function blockTypeOf(node) {
|
|
50
65
|
const t = node.type.name;
|
|
51
66
|
return BLOCK_TYPES.find((b) => b.type === t && (!b.attrs
|
|
52
|
-
|| (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];
|
|
53
69
|
}
|
|
54
70
|
|
|
55
71
|
const startOf = (doc, index) => {
|
|
@@ -93,6 +109,40 @@ export function turnInto(st, index, item) {
|
|
|
93
109
|
return canTurnInto(node, item) ? retype(st.tr, index, item) : REFUSED;
|
|
94
110
|
}
|
|
95
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
|
+
|
|
96
146
|
// A new empty paragraph after the block at `index` and its subtree, at the
|
|
97
147
|
// block's own level, with the caret in it.
|
|
98
148
|
export function insertAfter(st, index, tr = st.tr) {
|
|
@@ -138,6 +188,35 @@ export function insertDivider(st, index, tr = st.tr) {
|
|
|
138
188
|
return tr.setSelection(TextSelection.create(tr.doc, to + 2));
|
|
139
189
|
}
|
|
140
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
|
+
|
|
141
220
|
// The skipped letters between a query and a candidate it can be read out of,
|
|
142
221
|
// or Infinity when it cannot. Only queries of 2 to 32 letters count, so a single
|
|
143
222
|
// letter never drags in half the menu.
|
|
@@ -174,16 +253,8 @@ export function rankItems(items, query) {
|
|
|
174
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);
|
|
175
254
|
}
|
|
176
255
|
|
|
177
|
-
|
|
178
|
-
// the file pickers have their own placement; a block type retypes an empty line
|
|
179
|
-
// and otherwise goes into a new block below the line's whole subtree.
|
|
180
|
-
export function runItem(view, item, index, remove = null) {
|
|
256
|
+
function writeItem(view, item, index, remove) {
|
|
181
257
|
const st = view.state;
|
|
182
|
-
if (item.insert === "image" || item.insert === "file") return pickFiles(view, item.insert === "image" ? "image/*" : "", hostPlace(st, index), remove);
|
|
183
|
-
if (item.insert === "csv") return pickCsv(view, remove);
|
|
184
|
-
if (item.insert === "table") return insertTableNode(st, view.dispatch, makeTable(), remove, null, index);
|
|
185
|
-
if (item.insert === "board") return insertBoardNode(st, view.dispatch, makeBoard(), remove, index);
|
|
186
|
-
if (item.insert === "chart") return chartFromSlash(view, index, remove);
|
|
187
258
|
let tr = st.tr;
|
|
188
259
|
if (remove) tr.delete(remove.from, remove.to);
|
|
189
260
|
const node = tr.doc.child(index);
|
|
@@ -198,3 +269,62 @@ export function runItem(view, item, index, remove = null) {
|
|
|
198
269
|
view.focus();
|
|
199
270
|
return true;
|
|
200
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
|
@@ -124,7 +124,35 @@ export const MARKDOWN_RULES = [
|
|
|
124
124
|
...INLINE_RULES,
|
|
125
125
|
];
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
// The shortest shortcut that quotes a line: `" ` typed at the start of a
|
|
128
|
+
// paragraph. It writes a floor-1 block, so it asks the format gate first and
|
|
129
|
+
// converts only when the gate answered applied; the transaction it dispatches is
|
|
130
|
+
// the input-rule one, recorded on this plugin so Backspace puts the literal `" `
|
|
131
|
+
// back and undo keeps the conversion whole. The typed space is never swallowed:
|
|
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.
|
|
134
|
+
function quoteShortcut(plugin, view, from, to, text) {
|
|
135
|
+
if (text !== " " || from !== to) return false;
|
|
136
|
+
const st = view.state;
|
|
137
|
+
const $from = st.doc.resolve(from);
|
|
138
|
+
const node = $from.parent;
|
|
139
|
+
if ($from.depth !== 1 || node.type !== N.paragraph || $from.parentOffset !== 1 || node.textContent !== '"') return false;
|
|
140
|
+
if (!view.editable || typeof view.runFormatCommand !== "function") return false;
|
|
141
|
+
const plain = () => {
|
|
142
|
+
if (view.isDestroyed || view.state.doc !== st.doc) return;
|
|
143
|
+
view.dispatch(view.state.tr.insertText(" ", from, to));
|
|
144
|
+
};
|
|
145
|
+
const at = $from.before(1);
|
|
146
|
+
import("./format-native-commands.js").then(({ quoteFromSpace }) => quoteFromSpace(plugin, view, { at, from, to }, plain), plain);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const markdownPlugin = () => {
|
|
151
|
+
const plugin = inputrules.inputRules({ rules: MARKDOWN_RULES });
|
|
152
|
+
const rules = plugin.props.handleTextInput;
|
|
153
|
+
plugin.props.handleTextInput = (view, from, to, text) => quoteShortcut(plugin, view, from, to, text) || rules(view, from, to, text);
|
|
154
|
+
return plugin;
|
|
155
|
+
};
|
|
128
156
|
|
|
129
157
|
// "| Name | Owner |" then Enter at the end of a paragraph: the paragraph becomes
|
|
130
158
|
// a table with those column names and one empty row, the caret in its first cell.
|
|
@@ -150,7 +178,7 @@ export function backspaceToParagraph(st, dispatch) {
|
|
|
150
178
|
const { $from, empty } = st.selection;
|
|
151
179
|
if (!empty || $from.depth !== 1 || $from.parentOffset !== 0) return false;
|
|
152
180
|
const n = $from.parent;
|
|
153
|
-
if (!(n.type === N.heading || n.type === N.callout || (n.type === N.code_block && n.content.size === 0))) return false;
|
|
181
|
+
if (!(n.type === N.heading || n.type === N.callout || n.type === N.quote || (n.type === N.code_block && n.content.size === 0))) return false;
|
|
154
182
|
if (dispatch) dispatch(asStep(st.tr.setNodeMarkup($from.before(), N.paragraph, { id: n.attrs.id, pos: n.attrs.pos, parent: n.attrs.parent })));
|
|
155
183
|
return true;
|
|
156
184
|
}
|