@panphora/clayjs 1.17.0 → 1.18.1
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-26C55578.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
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Markdown export: the Download Markdown action. It snapshots the live record once, so a
|
|
2
|
+
// peer's edit while the report is open cannot change the file, and it shows what the
|
|
3
|
+
// format could not carry before anything downloads: each block written as semantic HTML
|
|
4
|
+
// and each construct whose meaning goes away, named per block. No parser is fetched to
|
|
5
|
+
// export. The file goes through the page copy's own download helper, nothing here writes,
|
|
6
|
+
// enters history or marks the page saved, and an active composition is waited out rather
|
|
7
|
+
// than forced.
|
|
8
|
+
import { blockMap } from "../model.js";
|
|
9
|
+
import { downloadHtml, documentName } from "../../core/download.js";
|
|
10
|
+
import { exportMarkdown, markdownFileName } from "../export-markdown.js";
|
|
11
|
+
|
|
12
|
+
export function createFeature({ view = null, download = downloadHtml } = {}) {
|
|
13
|
+
if (!view) return { run: () => Promise.resolve({ ok: false, code: "not-editing" }), dispose() {} };
|
|
14
|
+
const doc = view.dom.ownerDocument;
|
|
15
|
+
let session = null;
|
|
16
|
+
let disposed = false;
|
|
17
|
+
|
|
18
|
+
function close() {
|
|
19
|
+
const s = session;
|
|
20
|
+
if (!s) return false;
|
|
21
|
+
session = null;
|
|
22
|
+
for (const [target, type, fn, options] of s.listen) target.removeEventListener(type, fn, options);
|
|
23
|
+
s.panel.remove();
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function cancel() {
|
|
28
|
+
if (!close()) return { ok: false, code: "cancelled" };
|
|
29
|
+
view.focus?.();
|
|
30
|
+
return { ok: true, cancelled: true };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The report: how much was written, every construct kept as HTML and every loss, then
|
|
34
|
+
// Download and Cancel. It uses the editor's own menu markup, so it needs no stylesheet
|
|
35
|
+
// of its own.
|
|
36
|
+
function draw(s) {
|
|
37
|
+
const parts = [];
|
|
38
|
+
const line = (className, text) => {
|
|
39
|
+
const el = doc.createElement("div");
|
|
40
|
+
el.className = className;
|
|
41
|
+
el.textContent = text;
|
|
42
|
+
return el;
|
|
43
|
+
};
|
|
44
|
+
parts.push(line("page-menu-title", "Export as Markdown"));
|
|
45
|
+
const counts = [`${s.report.blocks} block${s.report.blocks === 1 ? "" : "s"}`];
|
|
46
|
+
if (s.report.extensions.length) counts.push(`${s.report.extensions.length} kept as HTML`);
|
|
47
|
+
if (s.report.losses.length) counts.push(`${s.report.losses.length} lost`);
|
|
48
|
+
parts.push(line("page-menu-title page-export-summary", counts.join(" \u00b7 ")));
|
|
49
|
+
const section = (className, label, list) => {
|
|
50
|
+
if (!list.length) return;
|
|
51
|
+
const box = doc.createElement("div");
|
|
52
|
+
box.className = className;
|
|
53
|
+
box.append(line("page-menu-title", label));
|
|
54
|
+
for (const one of list) {
|
|
55
|
+
const row = line("page-export-row", one.name);
|
|
56
|
+
row.dataset.block = one.block ?? "";
|
|
57
|
+
row.dataset.type = one.type ?? "";
|
|
58
|
+
box.append(row);
|
|
59
|
+
}
|
|
60
|
+
parts.push(box);
|
|
61
|
+
};
|
|
62
|
+
section("page-export-extensions", "Kept as HTML", s.report.extensions);
|
|
63
|
+
section("page-export-losses", "Not kept in Markdown", s.report.losses);
|
|
64
|
+
if (!s.report.extensions.length && !s.report.losses.length) parts.push(line("page-export-row", "Everything here is written as Markdown."));
|
|
65
|
+
if (s.notice) {
|
|
66
|
+
const failed = doc.createElement("p");
|
|
67
|
+
failed.className = "page-export-error";
|
|
68
|
+
failed.setAttribute("role", "alert");
|
|
69
|
+
failed.textContent = s.notice;
|
|
70
|
+
parts.push(failed);
|
|
71
|
+
}
|
|
72
|
+
const button = (what, label) => {
|
|
73
|
+
const el = doc.createElement("button");
|
|
74
|
+
el.type = "button";
|
|
75
|
+
el.className = "page-menu-item";
|
|
76
|
+
el.dataset.export = what;
|
|
77
|
+
el.textContent = label;
|
|
78
|
+
el.addEventListener("mousedown", (event) => event.preventDefault());
|
|
79
|
+
el.addEventListener("click", () => (what === "download" ? save() : cancel()));
|
|
80
|
+
return el;
|
|
81
|
+
};
|
|
82
|
+
parts.push(button("download", `Download ${s.name}`), button("cancel", "Cancel"));
|
|
83
|
+
s.panel.replaceChildren(...parts);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function save() {
|
|
87
|
+
const s = session;
|
|
88
|
+
if (!s) return { ok: false, code: "no-export" };
|
|
89
|
+
if (download({ html: s.report.text, name: s.name, type: "text/markdown" })) {
|
|
90
|
+
close();
|
|
91
|
+
view.focus?.();
|
|
92
|
+
return { ok: true, downloaded: true };
|
|
93
|
+
}
|
|
94
|
+
s.notice = "That download could not be made.";
|
|
95
|
+
draw(s);
|
|
96
|
+
return { ok: false, code: "download-failed" };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function key(event) {
|
|
100
|
+
const s = session;
|
|
101
|
+
if (!s || event.isComposing || event.keyCode === 229) return;
|
|
102
|
+
if (event.key === "Escape") {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
event.stopPropagation();
|
|
105
|
+
cancel();
|
|
106
|
+
} else if (event.key === "Enter" && !event.target?.closest?.("[data-export]")) {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
event.stopPropagation();
|
|
109
|
+
save();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function open(report, name) {
|
|
114
|
+
const panel = doc.createElement("div");
|
|
115
|
+
panel.className = "page-menu page-export-preview";
|
|
116
|
+
panel.setAttribute("role", "dialog");
|
|
117
|
+
panel.setAttribute("aria-label", "Export Markdown");
|
|
118
|
+
const s = { report, name, panel, notice: null, listen: [] };
|
|
119
|
+
session = s;
|
|
120
|
+
const listen = (target, type, fn, options) => {
|
|
121
|
+
target.addEventListener(type, fn, options);
|
|
122
|
+
s.listen.push([target, type, fn, options]);
|
|
123
|
+
};
|
|
124
|
+
listen(doc, "keydown", (event) => key(event), true);
|
|
125
|
+
listen(doc, "mousedown", (event) => { if (!s.panel.contains(event.target)) cancel(); }, true);
|
|
126
|
+
(view.dom.parentElement ?? doc.body).appendChild(panel);
|
|
127
|
+
draw(s);
|
|
128
|
+
panel.querySelector('[data-export="download"]')?.focus?.();
|
|
129
|
+
return { ok: true, preview: true };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// A composition is never forced: the export waits for the person to finish it, because
|
|
133
|
+
// the record it reads is the one the composition is still writing into.
|
|
134
|
+
function composed() {
|
|
135
|
+
if (!view.composing) return Promise.resolve();
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
const done = () => {
|
|
138
|
+
view.dom.removeEventListener("compositionend", done);
|
|
139
|
+
resolve();
|
|
140
|
+
};
|
|
141
|
+
view.dom.addEventListener("compositionend", done);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function run() {
|
|
146
|
+
if (disposed || view.isDestroyed) return { ok: false, code: "not-editing" };
|
|
147
|
+
close();
|
|
148
|
+
await composed();
|
|
149
|
+
if (disposed || view.isDestroyed) return { ok: false, code: "not-editing" };
|
|
150
|
+
const snapshot = blockMap(view.state.doc);
|
|
151
|
+
let report;
|
|
152
|
+
try {
|
|
153
|
+
report = exportMarkdown(snapshot);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
return { ok: false, code: "export-failed", error };
|
|
156
|
+
}
|
|
157
|
+
return open(report, markdownFileName(documentName()));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
async run(args) {
|
|
162
|
+
return run(args);
|
|
163
|
+
},
|
|
164
|
+
dispose() {
|
|
165
|
+
disposed = true;
|
|
166
|
+
close();
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// Markdown import: the Insert Markdown action. A person picks a .md file, the action
|
|
2
|
+
// measures it before anything parses it, fetches marked on the first import only, and
|
|
3
|
+
// turns the lexer's tokens into page blocks through the import codec. A preview lists
|
|
4
|
+
// what the schema could not keep, and Insert writes the blocks where the selection was,
|
|
5
|
+
// with fresh block, row and column ids, as one undo entry. The place is found again by
|
|
6
|
+
// block id once the file has been read: a target a peer deleted refuses rather than
|
|
7
|
+
// taking whatever holds its old offset, and one a peer moved keeps the blocks at its own
|
|
8
|
+
// new place.
|
|
9
|
+
import { state } from "../../vendor/prosemirror.vendor.js";
|
|
10
|
+
import { schema } from "../schema.js";
|
|
11
|
+
import { keysBetween, newId, nodeFromBlock, orderedIds } from "../model.js";
|
|
12
|
+
import { asStep } from "../blocksel.js";
|
|
13
|
+
import { composing } from "../markdown.js";
|
|
14
|
+
import { project } from "../projection.js";
|
|
15
|
+
import { requiredPageFormat } from "../format-level.js";
|
|
16
|
+
import { runFormatted } from "../format-command.js";
|
|
17
|
+
import { MAX_BYTES, importMarkdownTokens } from "../import-markdown.js";
|
|
18
|
+
import { registerUnsavedState } from "../../lib/unsaved-state.js";
|
|
19
|
+
|
|
20
|
+
const PREVIEW = "markdown-import-preview";
|
|
21
|
+
const encoder = new TextEncoder();
|
|
22
|
+
const excerpt = (text) => {
|
|
23
|
+
const value = String(text ?? "").replace(/\s+/g, " ").trim();
|
|
24
|
+
return value.length > 60 ? `${value.slice(0, 60)}\u2026` : value;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const readText = (file) => (typeof file.text === "function" ? file.text() : new Promise((resolve, reject) => {
|
|
28
|
+
const reader = new FileReader();
|
|
29
|
+
reader.onload = () => resolve(reader.result);
|
|
30
|
+
reader.onerror = () => reject(reader.error);
|
|
31
|
+
reader.readAsText(file);
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// marked is vendored behind the retryable clay.assets.load("marked"), so a document that
|
|
35
|
+
// never imports Markdown never downloads a parser. A fetch that failed is not kept.
|
|
36
|
+
const assetMarked = () => globalThis.clay?.assets?.load?.("marked") ?? import("../../vendor/marked.vendor.js");
|
|
37
|
+
|
|
38
|
+
const indexOfId = (doc, id) => {
|
|
39
|
+
let found = -1;
|
|
40
|
+
doc.forEach((node, _offset, index) => { if (found < 0 && node.attrs.id === id) found = index; });
|
|
41
|
+
return found;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// The root block the selection is in, and whether the selection is text inside it. A
|
|
45
|
+
// block selection names the block it starts on and takes no text offset.
|
|
46
|
+
function blockAt(st) {
|
|
47
|
+
const $from = st.selection.$from;
|
|
48
|
+
if ($from.depth >= 1) return { node: $from.node(1), at: $from.before(1), textblock: $from.parent.isTextblock };
|
|
49
|
+
const index = Math.min($from.index(0), Math.max(0, st.doc.childCount - 1));
|
|
50
|
+
let at = 0;
|
|
51
|
+
for (let i = 0; i < index; i++) at += st.doc.child(i).nodeSize;
|
|
52
|
+
return { node: st.doc.child(index), at, textblock: false };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// What the import needs of the document: the line it was asked for, and the range of text
|
|
56
|
+
// it was asked at, as offsets inside that line. A peer's move changes where the line sits
|
|
57
|
+
// and never these, so the range is read again against the line found by id.
|
|
58
|
+
function targetOf(st) {
|
|
59
|
+
const { node, at, textblock } = blockAt(st);
|
|
60
|
+
const sel = st.selection;
|
|
61
|
+
const fromOff = textblock ? Math.max(0, sel.from - at - 1) : 0;
|
|
62
|
+
return {
|
|
63
|
+
id: node.attrs.id,
|
|
64
|
+
textblock,
|
|
65
|
+
size: node.isTextblock ? node.content.size : 0,
|
|
66
|
+
fromOff,
|
|
67
|
+
toOff: textblock ? Math.max(fromOff, sel.to - at - 1) : 0,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// A construct the schema cannot express is kept as literal content by the codec, so a
|
|
72
|
+
// model that reaches floor 1 is one a newer page format reads; anything the projector
|
|
73
|
+
// cannot read at all is treated the same way rather than written silently.
|
|
74
|
+
const needsFormat = (blocks) => {
|
|
75
|
+
try {
|
|
76
|
+
return requiredPageFormat(project(blocks)) > 0;
|
|
77
|
+
} catch {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const messageOf = (answer) => {
|
|
83
|
+
if (answer?.code === "page-format-upgrade-required") return "This document needs a newer page format before Markdown can be added. The preview stays open.";
|
|
84
|
+
if (answer?.code === "page-format-unsupported") return "This host cannot record the newer page format this file needs. The preview stays open.";
|
|
85
|
+
if (answer?.code === "stale-target") return "The place this import was started from changed. The preview stays open.";
|
|
86
|
+
if (answer?.code === "empty-import") return "This file holds nothing that can be inserted.";
|
|
87
|
+
return "The Markdown could not be inserted. The preview stays open.";
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export function createFeature({ view = null, load = assetMarked } = {}) {
|
|
91
|
+
if (!view) return { run: () => Promise.resolve({ ok: false, code: "not-editing" }), dispose() {} };
|
|
92
|
+
const doc = view.dom.ownerDocument;
|
|
93
|
+
let session = null;
|
|
94
|
+
let marked = null;
|
|
95
|
+
let disposed = false;
|
|
96
|
+
// An unsent preview holds the mode exit open: the exit asks the unsaved-state registry
|
|
97
|
+
// for its pending names, and this one is listed while the preview waits for a person.
|
|
98
|
+
const unsaved = registerUnsavedState({ id: PREVIEW, isPending: () => session !== null });
|
|
99
|
+
|
|
100
|
+
const markedModule = () => (marked ??= Promise.resolve().then(load).catch((error) => { marked = null; throw error; }));
|
|
101
|
+
|
|
102
|
+
function place() {
|
|
103
|
+
try {
|
|
104
|
+
const box = view.dom.parentElement?.getBoundingClientRect?.() ?? { left: 0, top: 0 };
|
|
105
|
+
const at = view.coordsAtPos(Math.min(view.state.selection.from, view.state.doc.content.size));
|
|
106
|
+
return { x: Math.max(8, at.left - box.left), y: at.bottom + 6 - box.top };
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function close() {
|
|
113
|
+
const s = session;
|
|
114
|
+
if (!s) return false;
|
|
115
|
+
session = null;
|
|
116
|
+
for (const [target, type, fn, options] of s.listen) target.removeEventListener(type, fn, options);
|
|
117
|
+
s.panel.remove();
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function cancel() {
|
|
122
|
+
if (!close()) return { ok: false, code: "cancelled" };
|
|
123
|
+
view.focus?.();
|
|
124
|
+
return { ok: true, cancelled: true };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// The preview: the file's name, how much arrived, the constructs the schema could not
|
|
128
|
+
// express and the addresses it refused, then Insert and Cancel. It is built from the
|
|
129
|
+
// editor's own menu markup, so it needs no stylesheet of its own.
|
|
130
|
+
function draw(s) {
|
|
131
|
+
const parts = [];
|
|
132
|
+
const line = (className, text) => {
|
|
133
|
+
const el = doc.createElement("div");
|
|
134
|
+
el.className = className;
|
|
135
|
+
el.textContent = text;
|
|
136
|
+
return el;
|
|
137
|
+
};
|
|
138
|
+
parts.push(line("page-menu-title", s.error ? "Couldn't read that Markdown" : `Import ${s.name}`));
|
|
139
|
+
if (!s.error) {
|
|
140
|
+
const counts = [`${s.count} block${s.count === 1 ? "" : "s"}`];
|
|
141
|
+
if (s.unsupported.length) counts.push(`${s.unsupported.length} kept as written`);
|
|
142
|
+
if (s.refused.length) counts.push(`${s.refused.length} address${s.refused.length === 1 ? "" : "es"} left out`);
|
|
143
|
+
parts.push(line("page-menu-title page-import-summary", counts.join(" \u00b7 ")));
|
|
144
|
+
}
|
|
145
|
+
const rows = (className, list) => {
|
|
146
|
+
const box = doc.createElement("div");
|
|
147
|
+
box.className = className;
|
|
148
|
+
box.replaceChildren(...list.map((text) => line("page-import-row", text)));
|
|
149
|
+
return box;
|
|
150
|
+
};
|
|
151
|
+
if (s.unsupported.length) parts.push(rows("page-import-unsupported", s.unsupported.map((one) => `${one.type}: ${excerpt(one.text)}`)));
|
|
152
|
+
if (s.refused.length) parts.push(rows("page-import-refused", s.refused.map((one) => `${one.type}: ${one.href}`)));
|
|
153
|
+
const said = s.error ?? s.notice ?? null;
|
|
154
|
+
if (said) {
|
|
155
|
+
const failed = doc.createElement("p");
|
|
156
|
+
failed.className = "page-import-error";
|
|
157
|
+
failed.setAttribute("role", "alert");
|
|
158
|
+
failed.textContent = said;
|
|
159
|
+
parts.push(failed);
|
|
160
|
+
}
|
|
161
|
+
const button = (what, label) => {
|
|
162
|
+
const el = doc.createElement("button");
|
|
163
|
+
el.type = "button";
|
|
164
|
+
el.className = "page-menu-item";
|
|
165
|
+
el.dataset.import = what;
|
|
166
|
+
el.textContent = label;
|
|
167
|
+
el.addEventListener("mousedown", (event) => event.preventDefault());
|
|
168
|
+
el.addEventListener("click", () => (what === "insert" ? submit() : cancel()));
|
|
169
|
+
return el;
|
|
170
|
+
};
|
|
171
|
+
const insert = button("insert", "Insert at the selection");
|
|
172
|
+
insert.disabled = !!s.error;
|
|
173
|
+
parts.push(insert, button("cancel", "Cancel"));
|
|
174
|
+
s.panel.replaceChildren(...parts);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// The one place blocks reach the document, synchronous once the gate has answered. The
|
|
178
|
+
// target is found by id, never by the offset it had: a line a peer deleted has no place
|
|
179
|
+
// here, and one a peer moved keeps the blocks at the line's new place. The blocks go into
|
|
180
|
+
// an empty line in its own place, between the two halves of the line being written in, or
|
|
181
|
+
// after the block that was selected; a closed toggle opens first so what arrives is not
|
|
182
|
+
// hidden inside it. Every block is given its own key here, so the identity plugin has
|
|
183
|
+
// nothing to repair and the whole insert is one undo entry.
|
|
184
|
+
function writeAt(target, blocks) {
|
|
185
|
+
const st = view.state;
|
|
186
|
+
const doc = st.doc;
|
|
187
|
+
const index = indexOfId(doc, target.id);
|
|
188
|
+
if (index < 0) return { ok: false, code: "stale-target" };
|
|
189
|
+
const node = doc.child(index);
|
|
190
|
+
if (target.textblock ? !node.isTextblock || node.content.size !== target.size : node.isTextblock) return { ok: false, code: "stale-target" };
|
|
191
|
+
const nodes = orderedIds(blocks).map((id) => nodeFromBlock(schema, id, { ...blocks[id], pos: null, parent: null }));
|
|
192
|
+
if (!nodes.length) return { ok: false, code: "empty-import" };
|
|
193
|
+
let at = 0;
|
|
194
|
+
for (let i = 0; i < index; i++) at += doc.child(i).nodeSize;
|
|
195
|
+
const next = doc.maybeChild(index + 1);
|
|
196
|
+
const toggle = node.type.name === "toggle";
|
|
197
|
+
const cut = target.textblock && !toggle && node.content.size > 0;
|
|
198
|
+
const replace = cut || (target.textblock && !toggle && node.content.size === 0);
|
|
199
|
+
const fromOff = cut ? Math.min(target.fromOff, node.content.size) : 0;
|
|
200
|
+
const toOff = cut ? Math.min(Math.max(fromOff, target.toOff), node.content.size) : 0;
|
|
201
|
+
const head = cut && fromOff > 0 ? node.type.create({ ...node.attrs }, node.content.cut(0, fromOff)) : null;
|
|
202
|
+
const tailContent = cut ? node.content.cut(toOff) : null;
|
|
203
|
+
const tail = tailContent && tailContent.size ? node.type.create({ ...node.attrs, id: newId(), pos: null }, tailContent) : null;
|
|
204
|
+
const lower = cut && !head ? (index > 0 ? doc.child(index - 1).attrs.pos : null) : node.attrs.pos;
|
|
205
|
+
const keys = keysBetween(lower, next?.attrs.pos ?? null, nodes.length + (tail ? 1 : 0));
|
|
206
|
+
const parent = toggle ? node.attrs.id : node.attrs.parent ?? null;
|
|
207
|
+
const placed = nodes.map((one, i) => one.type.create({ ...one.attrs, pos: keys[i], parent }, one.content, one.marks));
|
|
208
|
+
const last = tail ? tail.type.create({ ...tail.attrs, pos: keys[nodes.length] }, tail.content, tail.marks) : null;
|
|
209
|
+
const tr = st.tr;
|
|
210
|
+
if (replace) tr.replaceWith(at, at + node.nodeSize, [...(head ? [head] : []), ...placed, ...(last ? [last] : [])]);
|
|
211
|
+
else tr.insert(at + node.nodeSize, placed);
|
|
212
|
+
if (toggle && !node.attrs.open && placed.length > 0) tr.setNodeMarkup(at, null, { ...node.attrs, open: true });
|
|
213
|
+
let end = at + (replace ? (head ? head.nodeSize : 0) : node.nodeSize);
|
|
214
|
+
for (const one of placed) end += one.nodeSize;
|
|
215
|
+
tr.setSelection(state.TextSelection.near(tr.doc.resolve(last ? end : Math.max(0, end - 1)), last ? 1 : -1));
|
|
216
|
+
view.dispatch(asStep(tr).scrollIntoView());
|
|
217
|
+
view.focus?.();
|
|
218
|
+
return { ok: true, blocks: placed.length };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Insert: floor 1 is asked for only if the content needs it, and the write happens
|
|
222
|
+
// inside the gate's own apply, so a refusal leaves the document and the preview as they
|
|
223
|
+
// were and the preview says why.
|
|
224
|
+
async function submit() {
|
|
225
|
+
const s = session;
|
|
226
|
+
if (!s || s.error) return s ? { ok: false, code: "no-import" } : { ok: false, code: "not-editing" };
|
|
227
|
+
if (s.notice) {
|
|
228
|
+
s.notice = null;
|
|
229
|
+
draw(s);
|
|
230
|
+
}
|
|
231
|
+
const apply = () => writeAt(s.target, s.blocks);
|
|
232
|
+
const answer = s.floor ? await runFormatted(view, apply) : apply();
|
|
233
|
+
if (answer?.ok) {
|
|
234
|
+
close();
|
|
235
|
+
return answer;
|
|
236
|
+
}
|
|
237
|
+
// The preview keeps the blocks for copying, and a refusal a later answer could lift
|
|
238
|
+
// leaves Insert in place to ask again.
|
|
239
|
+
if (session === s) {
|
|
240
|
+
s.notice = messageOf(answer);
|
|
241
|
+
draw(s);
|
|
242
|
+
}
|
|
243
|
+
return answer ?? { ok: false, code: "import-failed" };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function key(event) {
|
|
247
|
+
const s = session;
|
|
248
|
+
if (!s || composing(event, view)) return;
|
|
249
|
+
if (event.key === "Escape") {
|
|
250
|
+
event.preventDefault();
|
|
251
|
+
event.stopPropagation();
|
|
252
|
+
cancel();
|
|
253
|
+
} else if (event.key === "Enter" && !event.target?.closest?.("[data-import]")) {
|
|
254
|
+
event.preventDefault();
|
|
255
|
+
event.stopPropagation();
|
|
256
|
+
submit();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// The file: the one a caller handed over, or the picker's own, which is removed again
|
|
261
|
+
// whatever the person answers.
|
|
262
|
+
function pick() {
|
|
263
|
+
return new Promise((resolve) => {
|
|
264
|
+
const input = doc.createElement("input");
|
|
265
|
+
input.type = "file";
|
|
266
|
+
input.accept = ".md,.markdown,text/markdown,text/plain";
|
|
267
|
+
input.hidden = true;
|
|
268
|
+
input.className = "page-markdown-input";
|
|
269
|
+
const done = (file) => {
|
|
270
|
+
input.remove();
|
|
271
|
+
resolve(file);
|
|
272
|
+
};
|
|
273
|
+
input.addEventListener("change", () => done(input.files?.[0] ?? null));
|
|
274
|
+
input.addEventListener("cancel", () => done(null));
|
|
275
|
+
(view.dom.parentElement ?? doc.body).appendChild(input);
|
|
276
|
+
input.click();
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function open(s, error = null) {
|
|
281
|
+
s.error = error;
|
|
282
|
+
const panel = doc.createElement("div");
|
|
283
|
+
panel.className = "page-menu page-import-preview";
|
|
284
|
+
panel.setAttribute("role", "dialog");
|
|
285
|
+
panel.setAttribute("aria-label", "Import Markdown");
|
|
286
|
+
s.panel = panel;
|
|
287
|
+
session = s;
|
|
288
|
+
const listen = (target, type, fn, options) => {
|
|
289
|
+
target.addEventListener(type, fn, options);
|
|
290
|
+
s.listen.push([target, type, fn, options]);
|
|
291
|
+
};
|
|
292
|
+
listen(doc, "keydown", (event) => key(event), true);
|
|
293
|
+
listen(doc, "mousedown", (event) => { if (!s.panel.contains(event.target)) cancel(); }, true);
|
|
294
|
+
(view.dom.parentElement ?? doc.body).appendChild(panel);
|
|
295
|
+
draw(s);
|
|
296
|
+
const at = place();
|
|
297
|
+
if (at) {
|
|
298
|
+
panel.style.setProperty("--page-pop-x", `${at.x}px`);
|
|
299
|
+
panel.style.setProperty("--page-pop-y", `${at.y}px`);
|
|
300
|
+
}
|
|
301
|
+
const insert = panel.querySelector('[data-import="insert"]');
|
|
302
|
+
if (insert && !insert.disabled) insert.focus?.();
|
|
303
|
+
return { ok: true, preview: true };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function run(args = {}) {
|
|
307
|
+
if (disposed || view.isDestroyed) return { ok: false, code: "not-editing" };
|
|
308
|
+
close();
|
|
309
|
+
const target = targetOf(view.state);
|
|
310
|
+
const file = args?.file ?? (await pick());
|
|
311
|
+
if (!file) return { ok: false, code: "cancelled" };
|
|
312
|
+
if (typeof file.size === "number" && file.size > MAX_BYTES) return { ok: false, code: "input-too-large" };
|
|
313
|
+
let text;
|
|
314
|
+
try {
|
|
315
|
+
text = await readText(file);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
return { ok: false, code: "unreadable", error };
|
|
318
|
+
}
|
|
319
|
+
if (disposed || view.isDestroyed) return { ok: false, code: "not-editing" };
|
|
320
|
+
if (encoder.encode(text).length > MAX_BYTES) return { ok: false, code: "input-too-large" };
|
|
321
|
+
const s = { target, name: String(file.name ?? "Markdown"), blocks: {}, count: 0, unsupported: [], refused: [], floor: 0, panel: null, error: null, notice: null, listen: [] };
|
|
322
|
+
let parsed;
|
|
323
|
+
try {
|
|
324
|
+
const mod = await markedModule();
|
|
325
|
+
parsed = importMarkdownTokens(new mod.Marked().lexer(text));
|
|
326
|
+
} catch (failure) {
|
|
327
|
+
if (disposed || view.isDestroyed) return { ok: false, code: "not-editing" };
|
|
328
|
+
return open(s, String(failure?.message ?? "The Markdown could not be read."));
|
|
329
|
+
}
|
|
330
|
+
if (disposed || view.isDestroyed) return { ok: false, code: "not-editing" };
|
|
331
|
+
s.blocks = parsed.blocks;
|
|
332
|
+
s.count = Object.keys(parsed.blocks).length;
|
|
333
|
+
s.unsupported = parsed.unsupported;
|
|
334
|
+
s.refused = parsed.refused;
|
|
335
|
+
s.floor = needsFormat(parsed.blocks) ? 1 : 0;
|
|
336
|
+
return open(s);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
async run(args) {
|
|
341
|
+
return run(args);
|
|
342
|
+
},
|
|
343
|
+
dispose() {
|
|
344
|
+
disposed = true;
|
|
345
|
+
close();
|
|
346
|
+
unsaved.dispose();
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
// The image and file blocks: their node views, the bookmark a pending upload draws
|
|
2
|
-
// at a block boundary,
|
|
3
|
-
// takes one of these blocks
|
|
4
|
-
|
|
2
|
+
// at a block boundary, their CSS, and the upload work behind them. Loaded the first
|
|
3
|
+
// time a document has or takes one of these blocks, or on the first media paste,
|
|
4
|
+
// drop, insert or upload.
|
|
5
|
+
import { safeHref, safeFileHref } from "../address.js";
|
|
6
|
+
import { view as pmView } from "../../vendor/prosemirror.vendor.js";
|
|
5
7
|
import { asStep } from "../blocksel.js";
|
|
6
|
-
import {
|
|
8
|
+
import { schema } from "../schema.js";
|
|
9
|
+
import { announce } from "../announce.js";
|
|
10
|
+
import { attrsFor, boundary, live, mediaKey, mediaParts, mediaRuntime, placeTr, removeTr, run, settle, update } from "../media.js";
|
|
7
11
|
import { mediaViews } from "../media-views.js";
|
|
12
|
+
import { addViewHook } from "../view-hooks.js";
|
|
13
|
+
|
|
14
|
+
const { Decoration, DecorationSet } = pmView;
|
|
15
|
+
const N = schema.nodes;
|
|
16
|
+
// The codes an upload is offered a retry for.
|
|
17
|
+
const RETRY = new Set(["timeout", "network", "error", "bad-response"]);
|
|
8
18
|
|
|
9
19
|
export const MEDIA_CSS = `
|
|
10
20
|
.page-editor .page-image { margin-inline: 0; }
|
|
@@ -276,8 +286,50 @@ export class FileView {
|
|
|
276
286
|
ignoreMutation() { return true; }
|
|
277
287
|
}
|
|
278
288
|
|
|
289
|
+
const readDataUrl = (file) => new Promise((ok, no) => { const r = new FileReader(); r.onload = () => ok(r.result); r.onerror = () => no(r.error); r.readAsDataURL(file); });
|
|
290
|
+
|
|
291
|
+
export async function embedUpload(view, id) {
|
|
292
|
+
const it = live(view, id);
|
|
293
|
+
if (!it || it.state !== "embed-choice") return;
|
|
294
|
+
const src = await readDataUrl(it.file);
|
|
295
|
+
if (!mediaRuntime.has(view) || !live(view, id)) return;
|
|
296
|
+
settle(it, view, placeTr(view.state, id, attrsFor(it, src)));
|
|
297
|
+
announce(view, "Saved inside the document");
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function retryUpload(view, id) {
|
|
301
|
+
const it = live(view, id);
|
|
302
|
+
if (!it) return;
|
|
303
|
+
update(view, id, { state: "uploading", percent: 0, code: null, message: null });
|
|
304
|
+
run(view, live(view, id));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function cancelUpload(view, id) {
|
|
308
|
+
const rt = mediaRuntime.get(view);
|
|
309
|
+
const abort = rt?.aborts.get(id);
|
|
310
|
+
const it = live(view, id);
|
|
311
|
+
if (it) settle(it, view, removeTr(view.state, id));
|
|
312
|
+
rt?.aborts.delete(id);
|
|
313
|
+
abort?.abort();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// The bookmark a pending upload draws at its block boundary: the view part keeps it
|
|
317
|
+
// through the same hook the other parts use, so the plugin that takes the paste does
|
|
318
|
+
// not carry it.
|
|
319
|
+
function bookmarks(view, st) {
|
|
320
|
+
const { items } = mediaKey.getState(st);
|
|
321
|
+
const render = mediaViews.get(view) ?? null;
|
|
322
|
+
if (!items.length || !render) return null;
|
|
323
|
+
return DecorationSet.create(st.doc, items.map((it) => Decoration.widget(boundary(st.doc, it.pos), (view) => render(view.dom.ownerDocument, it), {
|
|
324
|
+
side: -1000 + it.order, key: `${it.id}:${it.state}:${it.percent}`, stopEvent: () => true, ignoreSelection: true,
|
|
325
|
+
})));
|
|
326
|
+
}
|
|
327
|
+
|
|
279
328
|
export function createFeature({ view = null } = {}) {
|
|
280
329
|
if (view) mediaViews.set(view, bookmarkDOM);
|
|
330
|
+
const part = { retryUpload, embedUpload, cancelUpload };
|
|
331
|
+
if (view) mediaParts.set(view, part);
|
|
332
|
+
const off = view ? addViewHook(view, { decorations: (st) => bookmarks(view, st) }) : () => {};
|
|
281
333
|
return {
|
|
282
334
|
id: "media",
|
|
283
335
|
nodeViews: {
|
|
@@ -285,6 +337,10 @@ export function createFeature({ view = null } = {}) {
|
|
|
285
337
|
file: (node, editorView) => new FileView(node, editorView),
|
|
286
338
|
},
|
|
287
339
|
css: MEDIA_CSS,
|
|
288
|
-
dispose() {
|
|
340
|
+
dispose() {
|
|
341
|
+
off();
|
|
342
|
+
if (view && mediaViews.get(view) === bookmarkDOM) mediaViews.delete(view);
|
|
343
|
+
if (view && mediaParts.get(view) === part) mediaParts.delete(view);
|
|
344
|
+
},
|
|
289
345
|
};
|
|
290
346
|
}
|
|
@@ -7,13 +7,26 @@ export const FEATURES = {
|
|
|
7
7
|
table: { types: ["table"], views: ["table", "table_cell"], module: "page/features/table.js", load: () => import("./table.js") },
|
|
8
8
|
board: { types: ["board"], module: "page/features/board.js", load: () => import("./board.js") },
|
|
9
9
|
chart: { types: ["chart"], views: ["chart"], module: "page/features/chart.js", load: () => import("./chart.js") },
|
|
10
|
+
checklist: { types: [], views: [], match: (node) => node.type.name === "list_item" && node.attrs.kind === "check", module: "page/features/checklist.js", load: () => import("./checklist.js") },
|
|
10
11
|
code: { types: ["code_block"], module: "page/features/code.js", load: () => import("./code.js") },
|
|
12
|
+
toc: { types: ["toc"], module: "page/features/toc.js", load: () => import("./toc.js") },
|
|
13
|
+
titleIcon: { types: [], views: ["heading"], match: (node) => node.attrs.pageIcon != null || node.attrs.pageCoverSrc != null, module: "page/features/title-icon.js", load: () => import("./title-icon.js") },
|
|
11
14
|
media: { types: ["image", "file"], module: "page/features/media.js", load: () => import("./media.js") },
|
|
12
15
|
tool: { types: ["tool"], module: "page/features/tool.js", boot: ["page/tools.js", "page/tool-data.js"], load: () => import("./tool.js") },
|
|
13
16
|
};
|
|
14
17
|
|
|
15
18
|
export const featureForType = (type) => Object.keys(FEATURES).find((id) => FEATURES[id].types.includes(type)) ?? null;
|
|
16
19
|
|
|
20
|
+
// The row for a whole block: a row may name its blocks with `match` instead of a type,
|
|
21
|
+
// the way the checklist serves only the list items whose kind is check.
|
|
22
|
+
export const featureForNode = (node) => {
|
|
23
|
+
for (const id of Object.keys(FEATURES)) {
|
|
24
|
+
const row = FEATURES[id];
|
|
25
|
+
if (row.match ? row.match(node) : row.types.includes(node.type.name)) return id;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
};
|
|
29
|
+
|
|
17
30
|
// The optional commands: pickers, import and export, which a reader or a plain text
|
|
18
31
|
// editor never needs. They are a table of their own, out of the block-type scan above, so
|
|
19
32
|
// no document preloads one; the page editor fetches a command the first time somebody
|
|
@@ -25,4 +38,9 @@ export const COMMAND_FEATURES = {
|
|
|
25
38
|
mentions: { module: "page/features/mentions.js", load: () => import("./mentions.js") },
|
|
26
39
|
dates: { module: "page/features/dates.js", load: () => import("./dates.js") },
|
|
27
40
|
emoji: { module: "page/features/emoji.js", load: () => import("./emoji.js") },
|
|
41
|
+
titleIcon: { module: "page/features/title-icon.js", load: () => import("./title-icon.js") },
|
|
42
|
+
titleCover: { module: "page/features/title-cover.js", load: () => import("./title-cover.js") },
|
|
43
|
+
exportHtml: { module: "page/features/export-html.js", load: () => import("./export-html.js") },
|
|
44
|
+
importMarkdown: { module: "page/features/import-markdown.js", load: () => import("./import-markdown.js") },
|
|
45
|
+
exportMarkdown: { module: "page/features/export-markdown.js", load: () => import("./export-markdown.js") },
|
|
28
46
|
};
|