@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,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recovery-ui.js — the review a page shows for journaled work it may not replay.
|
|
3
|
+
*
|
|
4
|
+
* A capsule this page cannot apply (another authority, another generation, a newer
|
|
5
|
+
* external file, a foreign lineage) is still the person's work, so it is drawn here as
|
|
6
|
+
* the page it reconstructs, can be downloaded, and can be recovered as a copy: fresh
|
|
7
|
+
* block ids, inserted through the page's own document command, never a blind merge.
|
|
8
|
+
* Text typed during a composition that never became a transaction is offered the same
|
|
9
|
+
* way, as text.
|
|
10
|
+
*
|
|
11
|
+
* Loaded lazily, only when there is something to review: a page with nothing held aside
|
|
12
|
+
* pays for none of this, and a reader pays for it never.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { model, state as pmState } from "../vendor/prosemirror.vendor.js";
|
|
16
|
+
import { mountEditorUI } from "../ui/editor-ui.js";
|
|
17
|
+
import { schema } from "./schema.js";
|
|
18
|
+
import { keysBetween, newId, nodeFromBlock } from "./model.js";
|
|
19
|
+
import { downloadStored } from "../core/conflict-download.js";
|
|
20
|
+
import {
|
|
21
|
+
imeEntries,
|
|
22
|
+
openImeShadow,
|
|
23
|
+
pageCopyChannel,
|
|
24
|
+
pageImeChannel,
|
|
25
|
+
reconstructPage,
|
|
26
|
+
recoverAsCopy,
|
|
27
|
+
} from "./ybind/recovery-replay.js";
|
|
28
|
+
|
|
29
|
+
export const REVIEW_TEXT = "Work from an earlier session could not be put back. Read it, download it, or recover it as a copy.";
|
|
30
|
+
|
|
31
|
+
// The control and its panel sit in one shadow root inside the page shell, so the page's
|
|
32
|
+
// CSS cannot restyle them. The page's --page-* tokens still reach them, and they keep
|
|
33
|
+
// the page's font and colour.
|
|
34
|
+
const STYLES = `
|
|
35
|
+
:host { font: inherit !important; color: inherit !important; }
|
|
36
|
+
.page-recovery-root { display: contents; }
|
|
37
|
+
.page-recovery { position: fixed; right: 16px; bottom: 16px; z-index: 20; min-height: 36px; padding: 0 14px; font: inherit; font-size: 14px; color: var(--page-ink); background: var(--page-pop-bg); border: 1px solid var(--page-chrome-line-rest); border-radius: 999px; cursor: pointer; }
|
|
38
|
+
.page-recovery[hidden], .page-recovery-panel[hidden] { display: none; }
|
|
39
|
+
.page-recovery-panel { position: fixed; right: 16px; bottom: 64px; z-index: 21; width: min(420px, calc(100vw - 32px)); max-height: 60vh; overflow-y: auto; color: var(--page-ink); background: var(--page-pop-bg); border: 1px solid var(--page-chrome-line-rest); border-radius: var(--page-chrome-radius); padding: 8px; box-sizing: border-box; }
|
|
40
|
+
.page-recovery-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 4px 6px 8px; font-weight: 600; }
|
|
41
|
+
.page-recovery-note { margin: 0 6px 8px; font-size: 13px; opacity: .72; }
|
|
42
|
+
.page-recovery-failure { margin: 0 6px 8px; font-size: 13px; color: var(--page-warn, #b25b00); }
|
|
43
|
+
.page-recovery-row { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; align-items: center; min-height: 44px; padding: 8px 6px; border-top: 1px solid var(--page-chrome-line-rest); }
|
|
44
|
+
.page-recovery-meta { grid-column: 1; font-size: 12px; opacity: .72; }
|
|
45
|
+
.page-recovery-text { grid-column: 1; overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
46
|
+
.page-recovery-row button, .page-recovery-head button { grid-column: 2; grid-row: 1 / span 2; min-height: 36px; padding: 0 12px; font: inherit; font-size: 14px; color: inherit; background: transparent; border: 1px solid var(--page-chrome-line-rest); border-radius: var(--page-chrome-radius); cursor: pointer; }
|
|
47
|
+
.page-recovery-row.is-busy button { opacity: .6; cursor: progress; }
|
|
48
|
+
@media (max-width: 560px) {
|
|
49
|
+
.page-recovery { top: 12px; bottom: auto; min-height: 44px; }
|
|
50
|
+
.page-recovery-panel { left: 0; right: 0; bottom: 0; width: 100%; max-height: 70vh; border-radius: var(--page-chrome-radius) var(--page-chrome-radius) 0 0; }
|
|
51
|
+
.page-recovery-row button, .page-recovery-head button { min-height: 44px; }
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
const PREVIEW_LIMIT = 280;
|
|
56
|
+
|
|
57
|
+
const el = (doc, tag, className, attrs = {}) => {
|
|
58
|
+
const node = doc.createElement(tag);
|
|
59
|
+
if (className) node.className = className;
|
|
60
|
+
for (const [key, value] of Object.entries(attrs)) node.setAttribute(key, value);
|
|
61
|
+
return node;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
const ordered = (blocks) =>
|
|
66
|
+
Object.keys(blocks).sort((a, b) => {
|
|
67
|
+
const left = typeof blocks[a]?.pos === "string" ? blocks[a].pos : "";
|
|
68
|
+
const right = typeof blocks[b]?.pos === "string" ? blocks[b].pos : "";
|
|
69
|
+
if (left !== right) return left < right ? -1 : 1;
|
|
70
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* One reconstructed page as a copy: fresh block ids, and every parent remapped to its
|
|
75
|
+
* parent's new id, so a toggle's body arrives with its toggle.
|
|
76
|
+
*
|
|
77
|
+
* @param {Object} blocks - a reconstructed page, by block id
|
|
78
|
+
* @returns {Array<Object>} block records carrying their own id and order key
|
|
79
|
+
*/
|
|
80
|
+
export function copyBlocks(blocks) {
|
|
81
|
+
const order = ordered(blocks ?? {});
|
|
82
|
+
const ids = new Map(order.map((id) => [id, newId()]));
|
|
83
|
+
const keys = keysBetween(null, null, order.length);
|
|
84
|
+
return order.map((id, at) => {
|
|
85
|
+
const block = blocks[id];
|
|
86
|
+
const parent = typeof block?.parent === "string" ? ids.get(block.parent) ?? null : null;
|
|
87
|
+
return { ...block, id: ids.get(id), pos: keys[at], parent };
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The nodes a copy of one reconstructed page is inserted as.
|
|
93
|
+
*
|
|
94
|
+
* A block this runtime cannot build keeps its text as a paragraph, so no writing is
|
|
95
|
+
* lost to a newer format.
|
|
96
|
+
*
|
|
97
|
+
* @param {Object} blocks - a reconstructed page, by block id
|
|
98
|
+
* @returns {Array<Object>} ProseMirror nodes
|
|
99
|
+
*/
|
|
100
|
+
export function copyNodes(blocks) {
|
|
101
|
+
const nodes = [];
|
|
102
|
+
for (const block of copyBlocks(blocks)) {
|
|
103
|
+
let node = null;
|
|
104
|
+
try {
|
|
105
|
+
node = nodeFromBlock(schema, block.id, block);
|
|
106
|
+
} catch (_) {
|
|
107
|
+
node = null;
|
|
108
|
+
}
|
|
109
|
+
if (!node && typeof block.c === "string" && block.c) {
|
|
110
|
+
node = schema.nodes.paragraph.create({ id: block.id, pos: block.pos, parent: block.parent }, schema.text(block.c));
|
|
111
|
+
}
|
|
112
|
+
if (node) nodes.push(node);
|
|
113
|
+
}
|
|
114
|
+
return nodes;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Insert one reconstructed page at the end of the document, through the normal command.
|
|
119
|
+
*
|
|
120
|
+
* A single transaction the page turns into its own ops: journaled, offered to the room
|
|
121
|
+
* and undoable, exactly as typing it would be.
|
|
122
|
+
*
|
|
123
|
+
* @param {Object} view - the live editor view
|
|
124
|
+
* @param {Object} blocks - a reconstructed page, by block id
|
|
125
|
+
* @returns {{ok: boolean, code?: string, ids?: Array<string>}}
|
|
126
|
+
*/
|
|
127
|
+
export function insertCopy(view, blocks) {
|
|
128
|
+
if (!view || !view.state) return { ok: false, code: "no-editor" };
|
|
129
|
+
const nodes = copyNodes(blocks ?? {});
|
|
130
|
+
if (!nodes.length) return { ok: false, code: "nothing-to-copy" };
|
|
131
|
+
const state = view.state;
|
|
132
|
+
const tr = state.tr.insert(state.doc.content.size, model.Fragment.from(nodes));
|
|
133
|
+
tr.setSelection(pmState.Selection.near(tr.doc.resolve(tr.doc.content.size - 1)));
|
|
134
|
+
view.dispatch(tr.scrollIntoView());
|
|
135
|
+
return { ok: true, ids: nodes.map((node) => node.attrs.id) };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const preview = (doc, text) => {
|
|
139
|
+
const box = el(doc, "div", "page-recovery-text");
|
|
140
|
+
box.textContent = text.length > PREVIEW_LIMIT ? `${text.slice(0, PREVIEW_LIMIT)}…` : text;
|
|
141
|
+
return box;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
async function copyText(text) {
|
|
145
|
+
try {
|
|
146
|
+
await navigator.clipboard.writeText(text);
|
|
147
|
+
return true;
|
|
148
|
+
} catch (_) {}
|
|
149
|
+
const field = globalThis.document?.createElement("textarea");
|
|
150
|
+
if (!field) return false;
|
|
151
|
+
field.value = text;
|
|
152
|
+
field.style.setProperty("position", "fixed", "important");
|
|
153
|
+
field.style.setProperty("opacity", "0", "important");
|
|
154
|
+
globalThis.document.body?.append(field);
|
|
155
|
+
field.select();
|
|
156
|
+
let ok = false;
|
|
157
|
+
try {
|
|
158
|
+
ok = globalThis.document.execCommand?.("copy") === true;
|
|
159
|
+
} catch (_) {}
|
|
160
|
+
field.remove();
|
|
161
|
+
return ok;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The review panel for journaled work this page may not replay.
|
|
166
|
+
*
|
|
167
|
+
* @param {Object} options
|
|
168
|
+
* @param {Element} options.mount - where the button and panel live
|
|
169
|
+
* @param {Function} [options.getView] - the live editor view, or null
|
|
170
|
+
* @param {Function} [options.getJournal] - this session's page journal, or null
|
|
171
|
+
* @param {?Object} [options.service] - `clay.recovery`
|
|
172
|
+
* @param {Function} [options.isCurrent] - whether this session still owns the page
|
|
173
|
+
* @param {Function} [options.onCopied] - (row, receipt) after a copy reached this device
|
|
174
|
+
* @param {Function} [options.onCovered] - (row) when a save proved the copy is in the page
|
|
175
|
+
* @returns {Object}
|
|
176
|
+
*/
|
|
177
|
+
export function createRecoveryReview({
|
|
178
|
+
mount,
|
|
179
|
+
getView = () => null,
|
|
180
|
+
getJournal = () => null,
|
|
181
|
+
service = globalThis.window?.clay?.recovery ?? null,
|
|
182
|
+
isCurrent = () => true,
|
|
183
|
+
onCopied = () => {},
|
|
184
|
+
onCovered = () => {},
|
|
185
|
+
} = {}) {
|
|
186
|
+
const doc = mount.ownerDocument;
|
|
187
|
+
const button = el(doc, "button", "page-recovery", { type: "button", "aria-haspopup": "dialog", "aria-expanded": "false" });
|
|
188
|
+
const panel = el(doc, "div", "page-recovery-panel", { role: "dialog", "aria-label": "Recover work" });
|
|
189
|
+
button.hidden = true;
|
|
190
|
+
panel.hidden = true;
|
|
191
|
+
const content = el(doc, "div", "page-recovery-root");
|
|
192
|
+
content.append(button, panel);
|
|
193
|
+
const ui = mountEditorUI({ name: "page-recovery", content, css: [STYLES], parent: mount });
|
|
194
|
+
|
|
195
|
+
let rows = [];
|
|
196
|
+
let journaled = [];
|
|
197
|
+
let failure = null;
|
|
198
|
+
let copied = new Set();
|
|
199
|
+
const busy = new Set();
|
|
200
|
+
let copies = null;
|
|
201
|
+
let shadow = null;
|
|
202
|
+
let destroyed = false;
|
|
203
|
+
|
|
204
|
+
const total = () => rows.length + journaled.length;
|
|
205
|
+
|
|
206
|
+
const ensureCopyChannel = () => {
|
|
207
|
+
if (copies) return copies;
|
|
208
|
+
try {
|
|
209
|
+
copies = pageCopyChannel(service);
|
|
210
|
+
} catch (_) {
|
|
211
|
+
copies = null;
|
|
212
|
+
}
|
|
213
|
+
return copies;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const ensureShadow = () => {
|
|
217
|
+
if (shadow) return shadow;
|
|
218
|
+
try {
|
|
219
|
+
shadow = openImeShadow({ channel: pageImeChannel(service), key: getJournal()?.key ?? "" });
|
|
220
|
+
} catch (_) {
|
|
221
|
+
shadow = null;
|
|
222
|
+
}
|
|
223
|
+
return shadow;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const close = () => {
|
|
227
|
+
panel.hidden = true;
|
|
228
|
+
button.setAttribute("aria-expanded", "false");
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const download = () => {
|
|
232
|
+
const edits = [];
|
|
233
|
+
for (const row of rows) edits.push({ id: row.id, kind: "recovered-page", name: row.message, yours: row.text, now: "", sentence: row.message });
|
|
234
|
+
const composing = "Text typed while composing never reached the page.";
|
|
235
|
+
for (const row of journaled) edits.push({ id: row.id, kind: "composing", name: "Typed text", yours: row.text, now: "", sentence: composing });
|
|
236
|
+
if (!downloadStored({ html: null, edits })) failure = "That download could not be made.";
|
|
237
|
+
draw();
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Recover as a copy. Nothing of the capsule's own operations is merged: the page is
|
|
241
|
+
// inserted as a local edit with fresh ids, and the destination record the journal
|
|
242
|
+
// commits is what the transfer marker names.
|
|
243
|
+
async function recover(row) {
|
|
244
|
+
if (!row || busy.has(row.id) || copied.has(row.id)) return { ok: false, code: "busy" };
|
|
245
|
+
busy.add(row.id);
|
|
246
|
+
failure = null;
|
|
247
|
+
draw();
|
|
248
|
+
try {
|
|
249
|
+
if (!isCurrent()) return { ok: false, code: "stale" };
|
|
250
|
+
const journal = getJournal();
|
|
251
|
+
const channel = journal?.channel ?? null;
|
|
252
|
+
const marks = ensureCopyChannel();
|
|
253
|
+
const result = await recoverAsCopy({
|
|
254
|
+
channel,
|
|
255
|
+
copies: marks,
|
|
256
|
+
source: { key: journal?.key ?? "", id: row.id, revision: row.revision },
|
|
257
|
+
insert: async () => {
|
|
258
|
+
if (!isCurrent()) throw new Error("stale");
|
|
259
|
+
const view = getView();
|
|
260
|
+
if (!view) throw new Error("no-editor");
|
|
261
|
+
const waiting = journal?.next ? journal.next() : null;
|
|
262
|
+
const blocks = row.blocks ?? reconstructPage({ capsule: row.capsule, batches: row.batches ?? [] });
|
|
263
|
+
const inserted = insertCopy(view, blocks);
|
|
264
|
+
if (!inserted.ok) throw new Error(inserted.code);
|
|
265
|
+
if (!waiting) throw new Error("no-journal");
|
|
266
|
+
const record = await waiting;
|
|
267
|
+
if (!record) throw new Error("not-stored");
|
|
268
|
+
return { key: journal?.key ?? "", id: record.id, revision: record.revision };
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
if (!result.ok && !isCurrent()) return { ok: false, code: "stale" };
|
|
272
|
+
if (result.ok) {
|
|
273
|
+
copied.add(row.id);
|
|
274
|
+
onCopied(row, result);
|
|
275
|
+
} else if (result.code === "insert-failed") {
|
|
276
|
+
failure = "That copy could not be stored on this device. It is still here to try again.";
|
|
277
|
+
} else if (result.code === "already-copied") {
|
|
278
|
+
copied.add(row.id);
|
|
279
|
+
failure = "This work was already recovered as a copy.";
|
|
280
|
+
} else if (result.code === "covered") {
|
|
281
|
+
onCovered(row);
|
|
282
|
+
} else if (result.code !== "stale" && result.code !== "busy") {
|
|
283
|
+
failure = "Recovery is unavailable here. Download the copy instead.";
|
|
284
|
+
}
|
|
285
|
+
return result;
|
|
286
|
+
} finally {
|
|
287
|
+
busy.delete(row.id);
|
|
288
|
+
if (!destroyed) draw();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function dismiss(row) {
|
|
293
|
+
if (!row || !shadow) return { ok: false, code: "unavailable" };
|
|
294
|
+
const record = journaled.find((entry) => entry.id === row.id);
|
|
295
|
+
if (!record) return { ok: false, code: "unknown" };
|
|
296
|
+
try {
|
|
297
|
+
const receipt = await shadow.channel.dismiss(record.id, { revision: record.revision });
|
|
298
|
+
if (receipt?.ok) journaled = journaled.filter((entry) => entry.id !== record.id);
|
|
299
|
+
draw();
|
|
300
|
+
return receipt;
|
|
301
|
+
} catch (_) {
|
|
302
|
+
return { ok: false, code: "error" };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const rowButton = (label, onClick) => {
|
|
307
|
+
const node = el(doc, "button", "", { type: "button" });
|
|
308
|
+
node.textContent = label;
|
|
309
|
+
node.addEventListener("click", onClick);
|
|
310
|
+
return node;
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
function draw() {
|
|
314
|
+
panel.replaceChildren();
|
|
315
|
+
const head = el(doc, "div", "page-recovery-head");
|
|
316
|
+
head.append(doc.createTextNode("Recover work"));
|
|
317
|
+
head.append(rowButton("Download", download));
|
|
318
|
+
const done = rowButton("Close", () => { close(); button.focus(); });
|
|
319
|
+
head.append(done);
|
|
320
|
+
const note = el(doc, "p", "page-recovery-note");
|
|
321
|
+
note.textContent = REVIEW_TEXT;
|
|
322
|
+
panel.append(head, note);
|
|
323
|
+
if (failure) {
|
|
324
|
+
const line = el(doc, "p", "page-recovery-failure");
|
|
325
|
+
line.textContent = failure;
|
|
326
|
+
panel.append(line);
|
|
327
|
+
}
|
|
328
|
+
for (const row of rows) {
|
|
329
|
+
const item = el(doc, "div", "page-recovery-row", { "data-capsule": String(row.id) });
|
|
330
|
+
if (busy.has(row.id)) item.classList.add("is-busy");
|
|
331
|
+
const meta = el(doc, "div", "page-recovery-meta");
|
|
332
|
+
meta.textContent = `${row.message}${copied.has(row.id) ? " · Recovered as a copy" : ""}`;
|
|
333
|
+
const action = rowButton("Recover as a copy", () => recover(row));
|
|
334
|
+
action.disabled = busy.has(row.id) || copied.has(row.id);
|
|
335
|
+
item.append(meta, preview(doc, row.text || "(no text)"), action);
|
|
336
|
+
panel.append(item);
|
|
337
|
+
}
|
|
338
|
+
for (const row of journaled) {
|
|
339
|
+
const item = el(doc, "div", "page-recovery-row", { "data-composing": row.blockId });
|
|
340
|
+
const meta = el(doc, "div", "page-recovery-meta");
|
|
341
|
+
meta.textContent = "Text typed while composing. It never reached the page.";
|
|
342
|
+
item.append(meta, preview(doc, row.text));
|
|
343
|
+
const actions = el(doc, "div", "");
|
|
344
|
+
actions.append(
|
|
345
|
+
rowButton("Copy", async () => { if (!(await copyText(row.text))) failure = "Could not copy. Select the text instead."; draw(); }),
|
|
346
|
+
rowButton("Dismiss", () => dismiss(row)),
|
|
347
|
+
);
|
|
348
|
+
item.append(actions);
|
|
349
|
+
panel.append(item);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const open = () => {
|
|
354
|
+
if (!total()) return false;
|
|
355
|
+
panel.hidden = false;
|
|
356
|
+
button.setAttribute("aria-expanded", "true");
|
|
357
|
+
draw();
|
|
358
|
+
panel.querySelector("button")?.focus();
|
|
359
|
+
return true;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
button.addEventListener("click", () => (panel.hidden ? open() : close()));
|
|
363
|
+
panel.addEventListener("keydown", (event) => {
|
|
364
|
+
if (event.key !== "Escape") return;
|
|
365
|
+
event.stopPropagation();
|
|
366
|
+
close();
|
|
367
|
+
button.focus();
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// A composition is never forced into a transaction: its block's visible text is
|
|
371
|
+
// shadowed while it is active, and retired on composition end only when a journaled
|
|
372
|
+
// update already carries that text.
|
|
373
|
+
const composing = new Map();
|
|
374
|
+
const blockOf = (event) => {
|
|
375
|
+
const target = event?.target;
|
|
376
|
+
const element = target?.closest?.("[data-id]") ?? null;
|
|
377
|
+
const id = element?.getAttribute?.("data-id") ?? null;
|
|
378
|
+
return { blockId: id, text: element?.textContent ?? "" };
|
|
379
|
+
};
|
|
380
|
+
const onCompose = (event) => {
|
|
381
|
+
const found = blockOf(event);
|
|
382
|
+
if (!found.blockId) return;
|
|
383
|
+
composing.set(found.blockId, found.text);
|
|
384
|
+
ensureShadow()?.keep(found);
|
|
385
|
+
};
|
|
386
|
+
const onComposed = (event) => {
|
|
387
|
+
const found = blockOf(event);
|
|
388
|
+
const kept = composing.get(found.blockId);
|
|
389
|
+
composing.delete(found.blockId);
|
|
390
|
+
const holder = ensureShadow();
|
|
391
|
+
if (!holder) return;
|
|
392
|
+
const journal = getJournal();
|
|
393
|
+
const text = found.text || kept || "";
|
|
394
|
+
const covered = journal?.covers ? journal.covers(found.blockId, text) : false;
|
|
395
|
+
holder.clear({ blockId: found.blockId, text, covered }).then((receipt) => {
|
|
396
|
+
if (receipt?.ok) refresh();
|
|
397
|
+
}, () => {});
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Attach the composition listeners to the editing surface.
|
|
402
|
+
*
|
|
403
|
+
* @param {Element} root
|
|
404
|
+
* @returns {Function} detach
|
|
405
|
+
*/
|
|
406
|
+
function watch(root) {
|
|
407
|
+
if (!root) return () => {};
|
|
408
|
+
root.addEventListener("compositionstart", onCompose);
|
|
409
|
+
root.addEventListener("compositionupdate", onCompose);
|
|
410
|
+
root.addEventListener("compositionend", onComposed);
|
|
411
|
+
return () => {
|
|
412
|
+
root.removeEventListener("compositionstart", onCompose);
|
|
413
|
+
root.removeEventListener("compositionupdate", onCompose);
|
|
414
|
+
root.removeEventListener("compositionend", onComposed);
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// What the store holds for this page: composition shadows it kept, offered as text.
|
|
419
|
+
async function refresh() {
|
|
420
|
+
const holder = ensureShadow();
|
|
421
|
+
if (!holder || destroyed) return journaled;
|
|
422
|
+
let listed = null;
|
|
423
|
+
try {
|
|
424
|
+
listed = await holder.list();
|
|
425
|
+
} catch (_) {
|
|
426
|
+
listed = null;
|
|
427
|
+
}
|
|
428
|
+
if (destroyed) return journaled;
|
|
429
|
+
journaled = listed?.ok ? imeEntries(listed.records) : [];
|
|
430
|
+
update({ rows, refreshText: false });
|
|
431
|
+
return journaled;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function update({ rows: next = rows, refreshText = true } = {}) {
|
|
435
|
+
rows = (next || []).map((row) => ({ ...row }));
|
|
436
|
+
const count = total();
|
|
437
|
+
button.hidden = count === 0;
|
|
438
|
+
button.textContent = `Recover work (${count})`;
|
|
439
|
+
if (!count) close();
|
|
440
|
+
else if (!panel.hidden) draw();
|
|
441
|
+
if (refreshText) refresh();
|
|
442
|
+
return rows;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return {
|
|
446
|
+
open,
|
|
447
|
+
close,
|
|
448
|
+
download,
|
|
449
|
+
recover,
|
|
450
|
+
dismiss,
|
|
451
|
+
refresh,
|
|
452
|
+
watch,
|
|
453
|
+
update,
|
|
454
|
+
get count() { return total(); },
|
|
455
|
+
get rows() { return rows; },
|
|
456
|
+
get journaled() { return journaled; },
|
|
457
|
+
destroy() {
|
|
458
|
+
destroyed = true;
|
|
459
|
+
ui.remove();
|
|
460
|
+
shadow = null;
|
|
461
|
+
copies = null;
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
}
|
package/src/page/rich/chart.js
CHANGED
|
@@ -16,13 +16,16 @@ function ticks(min, max, count) {
|
|
|
16
16
|
return out.length ? out : [Number(min.toFixed(2)), Number(max.toFixed(2))];
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const missing = (chart, reason) => ({ state: "missing", reason, sourceId: chart.source ?? null, title: chart.title ?? "" });
|
|
20
|
+
|
|
19
21
|
export function chartModel(chart, table) {
|
|
20
|
-
if (!table) return
|
|
22
|
+
if (!table) return missing(chart, "table");
|
|
21
23
|
const cols = table.cols ?? [];
|
|
22
24
|
const rows = table.rows ?? [];
|
|
23
25
|
const x = cols.find((col) => col.id === chart.x);
|
|
24
|
-
if (!x || !chart.series?.length || chart.series.some((id) => !cols.some((col) => col.id === id))) return
|
|
25
|
-
const
|
|
26
|
+
if (!x || !chart.series?.length || chart.series.some((id) => !cols.some((col) => col.id === id))) return missing(chart, "column");
|
|
27
|
+
const plotted = chart.type === "pie" ? chart.series.slice(0, 1) : chart.series;
|
|
28
|
+
const series = plotted.map((id, index) => {
|
|
26
29
|
const col = cols.find((item) => item.id === id);
|
|
27
30
|
const points = rows.map((row, rowIndex) => ({
|
|
28
31
|
id: `${chart.id}:${row.id}:${id}`, rowId: row.id, colId: id, rowIndex,
|
|
@@ -45,8 +48,64 @@ export function chartModel(chart, table) {
|
|
|
45
48
|
};
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
const PIE = { width: 560, height: 232, cx: 150, cy: 116, radius: 84 };
|
|
52
|
+
const PIE_COLOURS = 6;
|
|
53
|
+
const pieHeight = (rows) => Math.max(PIE.height, 54 + (rows - 1) * 36 + 24);
|
|
54
|
+
|
|
55
|
+
function chartCaption(series) {
|
|
56
|
+
const valid = series.points.filter((point) => point.value !== null);
|
|
57
|
+
return `${series.name}: ${valid.length ? `${fmt(valid[0].value)} to ${fmt(valid.at(-1).value)}` : "no values"}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function pieSlices(model) {
|
|
61
|
+
const series = model.series[0];
|
|
62
|
+
if (!series) return [];
|
|
63
|
+
const positive = series.points.filter((point) => point.value !== null && point.value > 0);
|
|
64
|
+
const total = positive.reduce((sum, point) => sum + point.value, 0);
|
|
65
|
+
if (!total) return [];
|
|
66
|
+
const points = positive.length <= PIE_COLOURS ? positive : [
|
|
67
|
+
...positive.slice(0, PIE_COLOURS - 1),
|
|
68
|
+
{ id: `${positive[0].id.split(":")[0]}:other`, xLabel: "Other", value: positive.slice(PIE_COLOURS - 1).reduce((sum, point) => sum + point.value, 0), highlight: positive.slice(PIE_COLOURS - 1).some((point) => point.highlight) },
|
|
69
|
+
];
|
|
70
|
+
let angle = -Math.PI / 2;
|
|
71
|
+
return points.map((point, index) => {
|
|
72
|
+
const fraction = point.value / total;
|
|
73
|
+
const slice = { point, color: `var(--chart-${index % 6 + 1})`, fraction, percent: Math.round(fraction * 100), from: angle, to: angle + fraction * Math.PI * 2 };
|
|
74
|
+
angle = slice.to;
|
|
75
|
+
return slice;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function renderPie(model, slices, width) {
|
|
80
|
+
const box = pieHeight(slices.length);
|
|
81
|
+
const height = fmt(width * box / PIE.width);
|
|
82
|
+
const caption = model.series[0] ? chartCaption(model.series[0]) : "no values";
|
|
83
|
+
const parts = [`<svg xmlns="http://www.w3.org/2000/svg" role="img" width="${width}" height="${height}" viewBox="0 0 ${PIE.width} ${box}">`, `<title>${esc(model.title)}</title>`, `<desc>${esc(caption)}</desc>`];
|
|
84
|
+
if (!slices.length) parts.push(`<text x="${PIE.width / 2}" y="${PIE.cy}" text-anchor="middle">No positive values to chart</text>`);
|
|
85
|
+
slices.forEach((slice) => {
|
|
86
|
+
const point = ` data-point="${esc(slice.point.id)}"${slice.point.highlight ? ' data-highlight="true"' : ""}`;
|
|
87
|
+
if (slice.fraction >= .999) {
|
|
88
|
+
parts.push(`<circle${point} cx="${PIE.cx}" cy="${PIE.cy}" r="${PIE.radius}" fill="${slice.color}"/>`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const x1 = fmt(PIE.cx + PIE.radius * Math.cos(slice.from));
|
|
92
|
+
const y1 = fmt(PIE.cy + PIE.radius * Math.sin(slice.from));
|
|
93
|
+
const x2 = fmt(PIE.cx + PIE.radius * Math.cos(slice.to));
|
|
94
|
+
const y2 = fmt(PIE.cy + PIE.radius * Math.sin(slice.to));
|
|
95
|
+
parts.push(`<path${point} d="M ${PIE.cx} ${PIE.cy} L ${x1} ${y1} A ${PIE.radius} ${PIE.radius} 0 ${slice.fraction > .5 ? 1 : 0} 1 ${x2} ${y2} Z" fill="${slice.color}" stroke="var(--page-bg)" stroke-width="2"/>`);
|
|
96
|
+
});
|
|
97
|
+
slices.forEach((slice, index) => {
|
|
98
|
+
const yy = fmt(54 + index * 36);
|
|
99
|
+
const point = ` data-point="${esc(slice.point.id)}"${slice.point.highlight ? ' data-highlight="true"' : ""}`;
|
|
100
|
+
parts.push(`<g${point}><rect x="290" y="${fmt(54 + index * 36 - 10)}" width="11" height="11" fill="${slice.color}"/><text x="310" y="${yy}">${esc(slice.point.xLabel)}</text><text x="530" y="${yy}" text-anchor="end">${fmt(slice.point.value)} (${slice.percent}%)</text></g>`);
|
|
101
|
+
});
|
|
102
|
+
parts.push("</svg>");
|
|
103
|
+
return parts.join("");
|
|
104
|
+
}
|
|
105
|
+
|
|
48
106
|
export function renderChartSvg(model, { width = 640, height = 320, narrow = false } = {}) {
|
|
49
107
|
if (model.state === "missing") return `<svg xmlns="http://www.w3.org/2000/svg" role="img" width="${width}" height="80" viewBox="0 0 ${width} 80"><title>Chart source missing</title><text x="12" y="44">Chart source missing</text></svg>`;
|
|
108
|
+
if (model.type === "pie") return renderPie(model, pieSlices(model), width);
|
|
50
109
|
const margin = { left: 52, right: 72, top: 28, bottom: 48 };
|
|
51
110
|
const plotW = Math.max(1, width - margin.left - margin.right);
|
|
52
111
|
const plotH = Math.max(1, height - margin.top - margin.bottom);
|
|
@@ -55,10 +114,7 @@ export function renderChartSvg(model, { width = 640, height = 320, narrow = fals
|
|
|
55
114
|
const n = model.xLabels.length;
|
|
56
115
|
const x = (index) => margin.left + (n <= 1 ? plotW / 2 : index * plotW / (n - 1));
|
|
57
116
|
const parts = [`<svg xmlns="http://www.w3.org/2000/svg" role="img" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">`, `<title>${esc(model.title)}</title>`];
|
|
58
|
-
const caption = model.series.map((
|
|
59
|
-
const valid = item.points.filter((point) => point.value !== null);
|
|
60
|
-
return `${item.name}: ${valid.length ? `${fmt(valid[0].value)} to ${fmt(valid.at(-1).value)}` : "no values"}`;
|
|
61
|
-
}).join("; ");
|
|
117
|
+
const caption = model.series.map(chartCaption).join("; ");
|
|
62
118
|
parts.push(`<desc>${esc(caption)}</desc>`);
|
|
63
119
|
for (const tick of narrow ? model.narrowTicks : model.yTicks) {
|
|
64
120
|
const yy = fmt(y(tick));
|
|
@@ -53,4 +53,71 @@ export function projectInto(clone) {
|
|
|
53
53
|
if (!customTitle(clone)) writeTitle(clone, title);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* The page state a final save clone carries, or null.
|
|
58
|
+
*
|
|
59
|
+
* One page tag is one page state: a Yjs page never has two, a tag holding plain block
|
|
60
|
+
* data is a page's own content rather than one, and a tag that does not parse is not one
|
|
61
|
+
* either. The value is copied, because the store keeps writing to its own after the
|
|
62
|
+
* capture and a checkpoint must prove the bytes this capture sent.
|
|
63
|
+
*
|
|
64
|
+
* Registered with core/snapshot only by the recovery integration, so a page that
|
|
65
|
+
* journals nothing never reads it.
|
|
66
|
+
*
|
|
67
|
+
* @param {HTMLElement} root - the save clone, after every transform
|
|
68
|
+
* @returns {?Object}
|
|
69
|
+
*/
|
|
70
|
+
export function pageStateOfClone(root) {
|
|
71
|
+
const tags = pageTags(root);
|
|
72
|
+
if (tags.length !== 1) return null;
|
|
73
|
+
let value;
|
|
74
|
+
try { value = parseJsonRelaxed(tags[0].textContent); }
|
|
75
|
+
catch { return null; }
|
|
76
|
+
const { v, g, y } = value ?? {};
|
|
77
|
+
if (v !== 1 || !y || typeof y !== "object" || Array.isArray(y)) return null;
|
|
78
|
+
return { v, g, y: { ...y } };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The canonical article one page state projects to.
|
|
83
|
+
*
|
|
84
|
+
* The same projection the save writes into the capture's article, run over the state a
|
|
85
|
+
* capture pinned or a host handed back. A state whose projection is not the article
|
|
86
|
+
* that was accepted is not a proof of those bytes, whichever transform changed it.
|
|
87
|
+
*
|
|
88
|
+
* @param {Object|string} state - a page value, or the page tag text holding one
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
export function articleOfPageState(state) {
|
|
92
|
+
return renderFromTags([typeof state === "string" ? state : JSON.stringify(state)]).html;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The page state a save the host accepted carries, or null.
|
|
97
|
+
*
|
|
98
|
+
* A request body is not a capture: whoever answers it may have been a host that rewrote the
|
|
99
|
+
* bytes, an older save answering after this tab typed again, or something that is not a page
|
|
100
|
+
* at all. So the state has to be there, has to decode, and has to be the one the accepted
|
|
101
|
+
* article projects — otherwise these bytes prove nothing about the input this tab holds.
|
|
102
|
+
*
|
|
103
|
+
* @param {string} html - the exact body the host accepted
|
|
104
|
+
* @returns {?Object}
|
|
105
|
+
*/
|
|
106
|
+
export function acceptedPageState(html) {
|
|
107
|
+
if (typeof html !== "string") return null;
|
|
108
|
+
try {
|
|
109
|
+
const root = new DOMParser().parseFromString(html, "text/html");
|
|
110
|
+
const state = pageStateOfClone(root);
|
|
111
|
+
const article = root.querySelector(ARTICLE);
|
|
112
|
+
if (!state || !article) return null;
|
|
113
|
+
// Compare as parsed markup: the projection writes some empty attributes bare,
|
|
114
|
+
// and a parsed article serializes every attribute with a value.
|
|
115
|
+
const rendered = root.createElement("article");
|
|
116
|
+
rendered.innerHTML = articleOfPageState(state);
|
|
117
|
+
return article.innerHTML === rendered.innerHTML ? state : null;
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
56
123
|
addDocumentTransform(projectInto);
|
package/src/page/save-states.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { captureForSaveAndComparison } from "../core/snapshot.js";
|
|
2
|
-
import { downloadHtml, documentName } from "../core/
|
|
2
|
+
import { downloadHtml, documentName } from "../core/download.js";
|
|
3
3
|
|
|
4
4
|
// One plain state per save refusal code, so the indicator, the toast and the
|
|
5
5
|
// page plugin all read the same answer. `saved` is the only happy one; every
|
|
@@ -27,8 +27,18 @@ const SAVE_STATE_BY_CODE = Object.freeze({
|
|
|
27
27
|
"save-history-refresh-required": "blocked",
|
|
28
28
|
"provenance-unsupported": "blocked",
|
|
29
29
|
"receipt-expired": "blocked",
|
|
30
|
+
"page-full": "blocked",
|
|
31
|
+
"compaction-failed": "blocked",
|
|
30
32
|
});
|
|
31
33
|
|
|
34
|
+
// Where a person's typing lives, never a stronger claim than the evidence behind it.
|
|
35
|
+
const pageStateFor = ({ unavailable, pending, journaled, acknowledged, checkpointed } = {}) =>
|
|
36
|
+
unavailable ? "unavailable"
|
|
37
|
+
: pending > 0 ? "pending"
|
|
38
|
+
: journaled > 0 ? "journaled"
|
|
39
|
+
: acknowledged > 0 ? "acknowledged"
|
|
40
|
+
: checkpointed ? "checkpointed" : null;
|
|
41
|
+
|
|
32
42
|
const BLOCKED_TEXT = "Can't save here. Your edits are still in this tab.";
|
|
33
43
|
const RETRY_TEXT = "Still trying to save. Your edits are safe in this tab.";
|
|
34
44
|
|
|
@@ -58,7 +68,7 @@ function withoutPageTag(html) {
|
|
|
58
68
|
// page projection already applied by the live store's snapshot listener, and
|
|
59
69
|
// without the page editor's merge state, which no saved file or download carries.
|
|
60
70
|
function downloadPageCopy() {
|
|
61
|
-
const { forSave } = captureForSaveAndComparison();
|
|
71
|
+
const { forSave } = captureForSaveAndComparison({ emitForSync: false });
|
|
62
72
|
return downloadHtml({ html: withoutPageTag(forSave), name: `${documentName()}.html` });
|
|
63
73
|
}
|
|
64
74
|
|
|
@@ -71,7 +81,7 @@ function downloadSnapshot(forSave) {
|
|
|
71
81
|
// the projected article, and only its text is asked for.
|
|
72
82
|
async function copyPageText() {
|
|
73
83
|
try {
|
|
74
|
-
const { forSave } = captureForSaveAndComparison();
|
|
84
|
+
const { forSave } = captureForSaveAndComparison({ emitForSync: false });
|
|
75
85
|
const doc = new DOMParser().parseFromString(forSave, "text/html");
|
|
76
86
|
const articles = doc.querySelectorAll("article[data-page]");
|
|
77
87
|
if (articles.length !== 1) return false;
|
|
@@ -82,4 +92,4 @@ async function copyPageText() {
|
|
|
82
92
|
}
|
|
83
93
|
}
|
|
84
94
|
|
|
85
|
-
export { SAVE_STATE_BY_CODE, BLOCKED_TEXT, RETRY_TEXT, saveStateFor, downloadPageCopy, downloadSnapshot, copyPageText };
|
|
95
|
+
export { SAVE_STATE_BY_CODE, BLOCKED_TEXT, RETRY_TEXT, pageStateFor, saveStateFor, downloadPageCopy, downloadSnapshot, copyPageText };
|