@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
package/src/page/ybind/bridge.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import * as Y from "../../vendor/yjs.vendor.js";
|
|
2
2
|
import { REMOTE_META, LOCAL, HISTORY_META, attrKeys, sameJson, parseKey, mintOp } from "./layout.js";
|
|
3
3
|
import { walk, hashText } from "./walk.js";
|
|
4
|
+
import { captureDelta, rewalk, walkSignature } from "./walk-delta.js";
|
|
4
5
|
import { docOf } from "./doc.js";
|
|
5
6
|
import { localGroup } from "./group.js";
|
|
6
7
|
import { yBefore, yAfter } from "./tokens.js";
|
|
7
8
|
import { yIndexAt, posAtY, cellPoint, cellAt } from "./positions.js";
|
|
8
9
|
import { state as pmState } from "../../vendor/prosemirror.vendor.js";
|
|
9
|
-
import { repairSiblings } from "../model.js";
|
|
10
|
+
import { repairSiblings, isKey } from "../model.js";
|
|
10
11
|
import { createOriginIndex, installUndoOrigins } from "./origins.js";
|
|
11
|
-
import { reduceRecovery, canonicalRevision, restoreKey, physicalKeyOf, authorKey, RECOVERED_PREFIX, parsePhysicalKey, parseGoneIntents } from "./recovery.js";
|
|
12
|
+
import { reduceRecovery, reduceRecoverable, canonicalRevision, restoreKey, physicalKeyOf, authorKey, RECOVERED_PREFIX, parsePhysicalKey, parseGoneIntents } from "./recovery.js";
|
|
12
13
|
import { moveBlocks } from "./moves.js";
|
|
13
14
|
import { createBoardCommands } from "./board-commands.js";
|
|
14
15
|
import { continuesGroup, intentOf } from "../history-intent.js";
|
|
@@ -20,6 +21,7 @@ export function sameBareShape(a, b) {
|
|
|
20
21
|
if (a.childCount !== b.childCount) return false;
|
|
21
22
|
for (let i = 0; i < a.childCount; i++) {
|
|
22
23
|
const left = a.child(i), right = b.child(i);
|
|
24
|
+
if (left === right) continue;
|
|
23
25
|
if (left.type !== right.type) return false;
|
|
24
26
|
if (attrKeys(left.type).some((key) => !sameJson(left.attrs[key], right.attrs[key]))) return false;
|
|
25
27
|
if ((left.isTextblock || left.type.name === "table") && !left.content.eq(right.content)) return false;
|
|
@@ -88,11 +90,46 @@ export function keepViewPos(derived, current = null) {
|
|
|
88
90
|
}
|
|
89
91
|
if (pos === block.attrs.pos && content === block.content) return block;
|
|
90
92
|
changed = true;
|
|
91
|
-
|
|
93
|
+
const attrs = { ...block.attrs, pos };
|
|
94
|
+
// The view's own node, when it is the node this would build: a block a full walk
|
|
95
|
+
// derived again unchanged keeps the object the view holds.
|
|
96
|
+
const prev = previous.get(block.attrs.id);
|
|
97
|
+
if (prev && content === block.content && prev.hasMarkup(block.type, attrs, block.marks) && prev.content.eq(content)) return prev;
|
|
98
|
+
return block.type.create(attrs, content, block.marks);
|
|
92
99
|
});
|
|
93
100
|
return changed ? derived.type.create(derived.attrs, placed) : derived;
|
|
94
101
|
}
|
|
95
102
|
|
|
103
|
+
const orderedSiblings = (node) => {
|
|
104
|
+
const ids = new Set();
|
|
105
|
+
let previous = null;
|
|
106
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
107
|
+
const { id, pos } = node.child(i).attrs;
|
|
108
|
+
if (!id || ids.has(id) || !isKey(pos) || (previous !== null && previous >= pos)) return false;
|
|
109
|
+
ids.add(id);
|
|
110
|
+
previous = pos;
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const sameAttrsBesidePos = (left, right) => {
|
|
116
|
+
for (const key in left.attrs) if (key !== "pos" && left.attrs[key] !== right.attrs[key]) return false;
|
|
117
|
+
return true;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export function keepsView(current, derived) {
|
|
121
|
+
if (current.childCount !== derived.childCount || !orderedSiblings(current)) return false;
|
|
122
|
+
for (let i = 0; i < current.childCount; i++) {
|
|
123
|
+
const left = current.child(i), right = derived.child(i);
|
|
124
|
+
if (left === right) continue;
|
|
125
|
+
const markup = left.type === right.type && left.marks === right.marks && sameAttrsBesidePos(left, right);
|
|
126
|
+
if (!markup && !left.hasMarkup(right.type, { ...right.attrs, pos: left.attrs.pos }, right.marks)) return false;
|
|
127
|
+
if (!left.content.eq(right.content)) return false;
|
|
128
|
+
if (left.type.name === "table" && !orderedSiblings(left)) return false;
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
96
133
|
const freezeDeep = (value) => {
|
|
97
134
|
if (value && typeof value === "object") {
|
|
98
135
|
for (const key of Object.keys(value)) freezeDeep(value[key]);
|
|
@@ -123,8 +160,16 @@ const newestLocalMarker = (pd, blockId) => {
|
|
|
123
160
|
|
|
124
161
|
export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], authorToken = null, stats = { groups: 0, mismatch: 0, renders: 0, first: null }, captureTimeout = 500 }) {
|
|
125
162
|
let view = null;
|
|
126
|
-
let w =
|
|
163
|
+
let w = null;
|
|
164
|
+
let docVersion = 0, walkedAt = -1;
|
|
165
|
+
// The one transaction since the last walk, when there was exactly one and it changed
|
|
166
|
+
// text inside one live block (walk-delta.js); any other history forces a full walk.
|
|
167
|
+
let delta = null, deltas = 0;
|
|
168
|
+
// A view node per walk segment it was proven equal to, so an unchanged block is not
|
|
169
|
+
// derived again and comes back as the same object the view holds.
|
|
170
|
+
const nodes = new WeakMap();
|
|
127
171
|
const origins = createOriginIndex(pd.origins);
|
|
172
|
+
w = walk(pd, origins);
|
|
128
173
|
const um = new Y.UndoManager([pd.flow, pd.attrs, pd.gone, pd.tableFields, pd.boardFields], {
|
|
129
174
|
trackedOrigins: new Set([LOCAL]),
|
|
130
175
|
captureTimeout,
|
|
@@ -159,12 +204,22 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
|
|
|
159
204
|
const authors = [...owners].filter(Boolean).sort();
|
|
160
205
|
return { kind: "recovered-change", source: record.source, revision: record.revision, blockId: record.blockId, author: authors[0] ?? null, authors };
|
|
161
206
|
};
|
|
162
|
-
const
|
|
163
|
-
if (recoveryMemo && recoveryMemo.version === recoveryVersion) return recoveryMemo.snapshot;
|
|
164
|
-
const reduced = reduceRecovery(pd, { index: origins });
|
|
207
|
+
const snapshotOf = (reduced) => {
|
|
165
208
|
const recoverable = reduced.list.filter((record) => record.recoverable).map((record) => detached(record));
|
|
166
209
|
const notices = detached(recoverable.map((record) => personalNotice(record)).filter((notice) => notice && !dismissed.has(`${notice.source}\u0000${notice.revision}`)));
|
|
167
|
-
|
|
210
|
+
return Object.freeze({ recoverable: Object.freeze(recoverable), notices: Object.freeze(notices) });
|
|
211
|
+
};
|
|
212
|
+
const proveSnapshot = (snapshot, current) => {
|
|
213
|
+
const fresh = JSON.stringify(snapshotOf(reduceRecovery(pd, { index: origins, walked: current })));
|
|
214
|
+
if (fresh === JSON.stringify(snapshot)) { stats.provedRecoveries = (stats.provedRecoveries ?? 0) + 1; return; }
|
|
215
|
+
stats.walkMismatch = (stats.walkMismatch ?? 0) + 1;
|
|
216
|
+
throw new Error("Recovery snapshot built from the displacing sources diverged from a full reduction");
|
|
217
|
+
};
|
|
218
|
+
const recoveryState = () => {
|
|
219
|
+
if (recoveryMemo && recoveryMemo.version === recoveryVersion) return recoveryMemo.snapshot;
|
|
220
|
+
const current = walkedAt === docVersion ? w : undefined;
|
|
221
|
+
const snapshot = snapshotOf(reduceRecoverable(pd, { index: origins, walked: current }));
|
|
222
|
+
if (stats.proveWalks) proveSnapshot(snapshot, current);
|
|
168
223
|
recoveryMemo = { version: recoveryVersion, snapshot };
|
|
169
224
|
return snapshot;
|
|
170
225
|
};
|
|
@@ -182,10 +237,75 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
|
|
|
182
237
|
recoverySignature = signature;
|
|
183
238
|
for (const listener of [...recoveryListeners]) listener(snapshot);
|
|
184
239
|
};
|
|
240
|
+
const walked = (result) => { w = result; walkedAt = docVersion; delta = null; deltas = 0; };
|
|
241
|
+
const currentWalk = () => (walkedAt === docVersion ? w : null);
|
|
242
|
+
const proveWalk = (next) => {
|
|
243
|
+
const a = walkSignature(pd, next), b = walkSignature(pd, walk(pd));
|
|
244
|
+
if (a === b) { stats.provedWalks = (stats.provedWalks ?? 0) + 1; return; }
|
|
245
|
+
stats.walkMismatch = (stats.walkMismatch ?? 0) + 1;
|
|
246
|
+
throw new Error(`Incremental walk diverged from the full walk:\n${a}\n---\n${b}`);
|
|
247
|
+
};
|
|
248
|
+
// Every walk the bridge takes runs after Yjs has settled (onSettled), when the index's
|
|
249
|
+
// observer has seen every origin record; the proof walks again with a fresh index that
|
|
250
|
+
// reads the boundary and alias keys from the map itself.
|
|
251
|
+
const intentsSignature = (intents) => JSON.stringify([intents.removals, intents.restores, intents.textDeletes, [...intents.copyActive], [...intents.attrs].map(([key, fields]) => [key, [...fields]])]);
|
|
252
|
+
const proveIntents = () => {
|
|
253
|
+
if (intentsSignature(parseGoneIntents(pd.gone)) === intentsSignature(parseGoneIntents(pd.gone, { kept: false }))) { stats.provedIntents = (stats.provedIntents ?? 0) + 1; return; }
|
|
254
|
+
stats.walkMismatch = (stats.walkMismatch ?? 0) + 1;
|
|
255
|
+
throw new Error("Removal records kept across writes diverged from a fresh parse of the gone map");
|
|
256
|
+
};
|
|
257
|
+
const proveIndexWalk = (full) => {
|
|
258
|
+
proveIntents();
|
|
259
|
+
const fresh = createOriginIndex(pd.origins);
|
|
260
|
+
let b;
|
|
261
|
+
try { b = walkSignature(pd, walk(pd, { resolve: fresh.resolve, boundaryOf: fresh.boundaryOf })); } finally { fresh.dispose(); }
|
|
262
|
+
const a = walkSignature(pd, full);
|
|
263
|
+
if (a === b) { stats.provedIndexWalks = (stats.provedIndexWalks ?? 0) + 1; return; }
|
|
264
|
+
stats.walkMismatch = (stats.walkMismatch ?? 0) + 1;
|
|
265
|
+
throw new Error(`A walk with the kept origin index diverged from one with a fresh index:\n${a}\n---\n${b}`);
|
|
266
|
+
};
|
|
267
|
+
const proveRecovery = () => {
|
|
268
|
+
const fresh = JSON.stringify(snapshotOf(reduceRecovery(pd, { index: origins, walked: w })));
|
|
269
|
+
if (fresh === JSON.stringify(recoveryState())) return;
|
|
270
|
+
stats.walkMismatch = (stats.walkMismatch ?? 0) + 1;
|
|
271
|
+
throw new Error("Recovery snapshot kept across an incremental walk diverged from a fresh one");
|
|
272
|
+
};
|
|
273
|
+
// Walks from the start, or re-walks the one block the only transaction since the last
|
|
274
|
+
// walk changed. Returns that transaction's delta when the walk was incremental.
|
|
275
|
+
const walkNow = () => {
|
|
276
|
+
const change = deltas === 1 ? delta : null;
|
|
277
|
+
const next = !change ? null : change.marker ? rewalk(pd, w, change.marker, change) : w;
|
|
278
|
+
if (!next) {
|
|
279
|
+
const full = walk(pd, origins);
|
|
280
|
+
if (stats.proveWalks) proveIndexWalk(full);
|
|
281
|
+
walked(full);
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
if (change.fields.size) for (const seg of next.visible) if (change.fields.has(seg.baseId)) nodes.delete(seg);
|
|
285
|
+
stats.incrementalWalks = (stats.incrementalWalks ?? 0) + 1;
|
|
286
|
+
if (stats.proveWalks) proveWalk(next);
|
|
287
|
+
walked(next);
|
|
288
|
+
return change;
|
|
289
|
+
};
|
|
290
|
+
const adopt = (doc) => {
|
|
291
|
+
if (doc.childCount !== w.visible.length) return;
|
|
292
|
+
for (let i = 0; i < doc.childCount; i++) if (!nodes.has(w.visible[i])) nodes.set(w.visible[i], doc.child(i));
|
|
293
|
+
};
|
|
294
|
+
const forget = (before, after) => {
|
|
295
|
+
if (before.childCount !== w.visible.length) return;
|
|
296
|
+
const d = docDiff(before, after);
|
|
297
|
+
if (!d) return;
|
|
298
|
+
const from = before.resolve(d.start).index(0), to = Math.min(before.childCount - 1, before.resolve(d.endA).index(0));
|
|
299
|
+
for (let i = from; i <= to; i++) nodes.delete(w.visible[i]);
|
|
300
|
+
};
|
|
185
301
|
const refresh = () => {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
302
|
+
const change = walkNow();
|
|
303
|
+
// Text this tab inserted into one live block has fresh ids, so it is no copy's source
|
|
304
|
+
// and the recovery snapshot stands. A deletion can displace a moved copy's source, and
|
|
305
|
+
// a peer's late insert can be the source of a hidden copy's displaced run.
|
|
306
|
+
if (!change || change.deleted || change.fields.size || (change.remote && w.hidden.length)) bumpRecovery();
|
|
307
|
+
else if (stats.proveWalks) proveRecovery();
|
|
308
|
+
return docOf(pd, w, nodes);
|
|
189
309
|
};
|
|
190
310
|
let saved = null;
|
|
191
311
|
let lastIntent = null;
|
|
@@ -197,7 +317,7 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
|
|
|
197
317
|
saved = null;
|
|
198
318
|
const derived = keepViewPos(fresh ?? refresh(), view.state.doc);
|
|
199
319
|
const d = docDiff(view.state.doc, derived);
|
|
200
|
-
if (!d) return;
|
|
320
|
+
if (!d) { adopt(view.state.doc); return; }
|
|
201
321
|
const tr = view.state.tr.replace(d.start, d.endA, derived.slice(d.start, d.endB)).setMeta(REMOTE_META, true).setMeta("addToHistory", false);
|
|
202
322
|
if (own) tr.setMeta("yundo", true);
|
|
203
323
|
if (keep) {
|
|
@@ -226,6 +346,27 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
|
|
|
226
346
|
if (a !== null && a !== undefined && h !== null && h !== undefined) tr.setSelection(pmState.TextSelection.create(tr.doc, a, h));
|
|
227
347
|
}
|
|
228
348
|
view.dispatch(tr);
|
|
349
|
+
adopt(view.state.doc);
|
|
350
|
+
};
|
|
351
|
+
// A transaction that wrote nothing (an update the tab already had, Yjs's formatting
|
|
352
|
+
// cleanup after a remote insert) leaves the walk as it was: it is not a delta, it is
|
|
353
|
+
// never rendered, and a walk current before it stays current after it.
|
|
354
|
+
const began = new WeakMap();
|
|
355
|
+
const idle = new WeakSet();
|
|
356
|
+
const unchanged = (t) => {
|
|
357
|
+
if (t.changed.size || t.deleteSet.clients.size) return false;
|
|
358
|
+
for (const [client, clock] of t.afterState) if ((t.beforeState.get(client) ?? 0) !== clock) return false;
|
|
359
|
+
return true;
|
|
360
|
+
};
|
|
361
|
+
const stale = (t) => { began.set(t, docVersion); docVersion++; };
|
|
362
|
+
const onTransaction = (t) => {
|
|
363
|
+
if (unchanged(t)) {
|
|
364
|
+
idle.add(t);
|
|
365
|
+
if (walkedAt === began.get(t) && docVersion === walkedAt + 1) walkedAt = docVersion;
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
deltas++;
|
|
369
|
+
delta = deltas === 1 ? captureDelta(pd, t, w) : null;
|
|
229
370
|
};
|
|
230
371
|
const onBefore = (t) => {
|
|
231
372
|
if (groupOpen && !joining && t.origin === LOCAL) { um.stopCapturing(); groupOpen = false; }
|
|
@@ -245,12 +386,24 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
|
|
|
245
386
|
};
|
|
246
387
|
const onAfter = (t) => {
|
|
247
388
|
if (t.origin !== LOCAL) { lastIntent = null; groupOpen = false; }
|
|
389
|
+
};
|
|
390
|
+
// One render per batch Yjs settles (a transaction and those its observers queued), after
|
|
391
|
+
// its garbage collection and merges, so the walk it leaves is current for the next keystroke.
|
|
392
|
+
const onSettled = (doc, transactions) => {
|
|
393
|
+
let changed = false, local = false, own = false;
|
|
394
|
+
for (const t of transactions) {
|
|
395
|
+
if (idle.has(t)) continue;
|
|
396
|
+
changed = true;
|
|
397
|
+
if (t.origin === LOCAL) local = true;
|
|
398
|
+
else if (t.origin === um) own = true;
|
|
399
|
+
}
|
|
400
|
+
if (!changed) { saved = null; return; }
|
|
248
401
|
if (!view) { bumpRecovery(); return; }
|
|
249
|
-
if (
|
|
250
|
-
render(
|
|
402
|
+
if (local) { saved = null; return; }
|
|
403
|
+
render(own);
|
|
251
404
|
};
|
|
252
405
|
const portMove = (tr, move) => {
|
|
253
|
-
const result = moveBlocks({ pd, view, um, origins, render }, move);
|
|
406
|
+
const result = moveBlocks({ pd, view, um, origins, render, walked: currentWalk() }, move);
|
|
254
407
|
if (result.status !== "moved") {
|
|
255
408
|
if (result.status === "refused") stats.moveRefusals = (stats.moveRefusals ?? 0) + 1;
|
|
256
409
|
return result;
|
|
@@ -273,17 +426,23 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
|
|
|
273
426
|
return result;
|
|
274
427
|
};
|
|
275
428
|
const board = createBoardCommands({ pd, um, origins, render: () => (view ? render() : refresh()) });
|
|
429
|
+
pd.ydoc.on("beforeTransaction", stale);
|
|
276
430
|
pd.ydoc.on("beforeTransaction", onBefore);
|
|
431
|
+
pd.ydoc.on("afterTransaction", onTransaction);
|
|
277
432
|
pd.ydoc.on("afterTransaction", onAfter);
|
|
433
|
+
pd.ydoc.on("afterAllTransactions", onSettled);
|
|
278
434
|
return {
|
|
279
435
|
pd, stats, render, um, board,
|
|
280
436
|
attach(v) {
|
|
281
437
|
view = v;
|
|
282
|
-
|
|
438
|
+
walked(walk(pd, origins));
|
|
283
439
|
},
|
|
284
440
|
dispose() {
|
|
441
|
+
pd.ydoc.off("beforeTransaction", stale);
|
|
285
442
|
pd.ydoc.off("beforeTransaction", onBefore);
|
|
443
|
+
pd.ydoc.off("afterTransaction", onTransaction);
|
|
286
444
|
pd.ydoc.off("afterTransaction", onAfter);
|
|
445
|
+
pd.ydoc.off("afterAllTransactions", onSettled);
|
|
287
446
|
offOrigins();
|
|
288
447
|
origins.dispose();
|
|
289
448
|
um.destroy();
|
|
@@ -320,7 +479,8 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
|
|
|
320
479
|
}
|
|
321
480
|
return;
|
|
322
481
|
}
|
|
323
|
-
|
|
482
|
+
if (walkedAt !== docVersion) walkNow();
|
|
483
|
+
else stats.reusedWalks = (stats.reusedWalks ?? 0) + 1;
|
|
324
484
|
const history = transactions.every((t) => t.getMeta("addToHistory") !== false);
|
|
325
485
|
const register = registerAuthor;
|
|
326
486
|
registerAuthor = null;
|
|
@@ -334,10 +494,17 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
|
|
|
334
494
|
joining = false;
|
|
335
495
|
}
|
|
336
496
|
groupOpen = intent !== null;
|
|
497
|
+
const current = view.state.doc;
|
|
498
|
+
forget(local[0].before, current);
|
|
337
499
|
const derived = refresh();
|
|
338
|
-
if (!sameBareShape(derived,
|
|
500
|
+
if (!sameBareShape(derived, current)) {
|
|
339
501
|
stats.mismatch++;
|
|
340
|
-
stats.first ??= { before: bareShape(local[0].before), pm: bareShape(
|
|
502
|
+
stats.first ??= { before: bareShape(local[0].before), pm: bareShape(current), y: bareShape(derived) };
|
|
503
|
+
} else if (keepsView(current, derived)) {
|
|
504
|
+
stats.renders++;
|
|
505
|
+
saved = null;
|
|
506
|
+
adopt(current);
|
|
507
|
+
return;
|
|
341
508
|
}
|
|
342
509
|
render(false, derived);
|
|
343
510
|
},
|
|
@@ -377,7 +544,7 @@ export function createBridge({ pd, onNotice = () => {}, ownedTokens = [], author
|
|
|
377
544
|
return { status: "restored", id: blockId };
|
|
378
545
|
},
|
|
379
546
|
moveBlocks(args) {
|
|
380
|
-
return moveBlocks({ pd, view, um, origins, render }, args);
|
|
547
|
+
return moveBlocks({ pd, view, um, origins, render, walked: currentWalk() }, args);
|
|
381
548
|
},
|
|
382
549
|
undo() { return um.undo() !== null; },
|
|
383
550
|
redo() { return um.redo() !== null; },
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* containment.js — whether an update adds anything to a state, asked without applying it.
|
|
3
|
+
*
|
|
4
|
+
* Applying an update changes nothing exactly when every item it carries is already there,
|
|
5
|
+
* by id, and every id it deletes is already deleted. So a state is read once into ranges,
|
|
6
|
+
* the clock ranges its items occupy and the ranges it deletes, and each update is then
|
|
7
|
+
* read, never applied: a hundred records cost one reading of the state, not a hundred.
|
|
8
|
+
*
|
|
9
|
+
* Two kinds of state answer. A state integrated into a document answers exactly as
|
|
10
|
+
* applying would, and one that leaves anything pending contains nothing. The union of some
|
|
11
|
+
* entries' own ranges answers for content: an update inside it adds no item and no explicit
|
|
12
|
+
* deletion any of those entries lacks, so retiring it loses nothing they do not hold. It can
|
|
13
|
+
* miss a deletion Yjs only makes while integrating, never claim one. A state vector alone
|
|
14
|
+
* proves nothing about deletions.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Doc, Skip, applyUpdateV2, decodeUpdateV2, createDeleteSetFromStructStore } from "../../vendor/yjs.vendor.js";
|
|
18
|
+
|
|
19
|
+
function merged(map) {
|
|
20
|
+
for (const [client, list] of map) {
|
|
21
|
+
list.sort((a, b) => a[0] - b[0]);
|
|
22
|
+
const out = [];
|
|
23
|
+
for (const range of list) {
|
|
24
|
+
const last = out[out.length - 1];
|
|
25
|
+
if (last && range[0] <= last[1]) { if (range[1] > last[1]) last[1] = range[1]; }
|
|
26
|
+
else out.push([range[0], range[1]]);
|
|
27
|
+
}
|
|
28
|
+
map.set(client, out);
|
|
29
|
+
}
|
|
30
|
+
return map;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const add = (map, client, from, to) => {
|
|
34
|
+
if (to <= from) return;
|
|
35
|
+
const list = map.get(client);
|
|
36
|
+
if (list) list.push([from, to]);
|
|
37
|
+
else map.set(client, [[from, to]]);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The clock ranges one update's items occupy and the ranges it deletes.
|
|
42
|
+
*
|
|
43
|
+
* @param {Uint8Array} update - a V2 update
|
|
44
|
+
* @returns {{items: Map<number, Array<[number, number]>>, deletes: Map<number, Array<[number, number]>>}}
|
|
45
|
+
*/
|
|
46
|
+
export function rangesOf(update) {
|
|
47
|
+
const { structs, ds } = decodeUpdateV2(update);
|
|
48
|
+
const items = new Map();
|
|
49
|
+
for (const struct of structs) if (!(struct instanceof Skip)) add(items, struct.id.client, struct.id.clock, struct.id.clock + struct.length);
|
|
50
|
+
const deletes = new Map();
|
|
51
|
+
for (const [client, list] of ds.clients) for (const { clock, len } of list) add(deletes, client, clock, clock + len);
|
|
52
|
+
return { items: merged(items), deletes: merged(deletes) };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The ranges several entries hold together.
|
|
57
|
+
*
|
|
58
|
+
* @param {Array<Object>} list - from rangesOf() or unite()
|
|
59
|
+
* @returns {Object}
|
|
60
|
+
*/
|
|
61
|
+
export function unite(list) {
|
|
62
|
+
if (list.length === 1) return list[0];
|
|
63
|
+
const items = new Map();
|
|
64
|
+
const deletes = new Map();
|
|
65
|
+
for (const ranges of list) {
|
|
66
|
+
for (const [client, spans] of ranges.items) for (const [from, to] of spans) add(items, client, from, to);
|
|
67
|
+
for (const [client, spans] of ranges.deletes) for (const [from, to] of spans) add(deletes, client, from, to);
|
|
68
|
+
}
|
|
69
|
+
return { items: merged(items), deletes: merged(deletes) };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function inside(spans, from, to) {
|
|
73
|
+
if (!spans) return false;
|
|
74
|
+
let lo = 0, hi = spans.length - 1;
|
|
75
|
+
while (lo <= hi) {
|
|
76
|
+
const mid = (lo + hi) >> 1;
|
|
77
|
+
const span = spans[mid];
|
|
78
|
+
if (from < span[0]) hi = mid - 1;
|
|
79
|
+
else if (from >= span[1]) lo = mid + 1;
|
|
80
|
+
else return to <= span[1];
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Whether every item and deletion of `ranges` is already inside `state`.
|
|
87
|
+
*
|
|
88
|
+
* @param {Object} state - from unite(), rangesOf() or indexOf()
|
|
89
|
+
* @param {Object} ranges - from rangesOf()
|
|
90
|
+
* @returns {boolean}
|
|
91
|
+
*/
|
|
92
|
+
export function within(state, ranges) {
|
|
93
|
+
if (state.complete === false) return false;
|
|
94
|
+
for (const [client, spans] of ranges.items) for (const [from, to] of spans) if (!inside(state.items.get(client), from, to)) return false;
|
|
95
|
+
for (const [client, spans] of ranges.deletes) for (const [from, to] of spans) if (!inside(state.deletes.get(client), from, to)) return false;
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A document's state as ranges: every item from clock 0 to its state, and every deletion
|
|
101
|
+
* it holds, its own and the ones integration made.
|
|
102
|
+
*
|
|
103
|
+
* @param {Doc} doc
|
|
104
|
+
* @returns {Object}
|
|
105
|
+
*/
|
|
106
|
+
export function docRanges(doc) {
|
|
107
|
+
if (doc.store.pendingStructs || doc.store.pendingDs) return { complete: false };
|
|
108
|
+
const items = new Map();
|
|
109
|
+
for (const [client, structs] of doc.store.clients) {
|
|
110
|
+
const last = structs[structs.length - 1];
|
|
111
|
+
add(items, client, 0, last.id.clock + last.length);
|
|
112
|
+
}
|
|
113
|
+
const deletes = new Map();
|
|
114
|
+
for (const [client, list] of createDeleteSetFromStructStore(doc.store).clients) for (const { clock, len } of list) add(deletes, client, clock, clock + len);
|
|
115
|
+
return { complete: true, items, deletes: merged(deletes) };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The state some updates make together, integrated once.
|
|
120
|
+
*
|
|
121
|
+
* @param {Array<Uint8Array>} updates
|
|
122
|
+
* @returns {Object}
|
|
123
|
+
*/
|
|
124
|
+
export function stateIndex(updates) {
|
|
125
|
+
const doc = new Doc({ gc: false });
|
|
126
|
+
try {
|
|
127
|
+
for (const update of updates) applyUpdateV2(doc, update);
|
|
128
|
+
return docRanges(doc);
|
|
129
|
+
} finally {
|
|
130
|
+
doc.destroy();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const indexes = new WeakMap();
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The state one checkpoint's bytes make, read once for as long as those bytes are held.
|
|
138
|
+
* The bytes are never written to after they are indexed.
|
|
139
|
+
*
|
|
140
|
+
* @param {Uint8Array} bytes
|
|
141
|
+
* @param {Doc} [doc] - the document these bytes were just encoded from, when there is one
|
|
142
|
+
* @returns {Object}
|
|
143
|
+
*/
|
|
144
|
+
export function indexOf(bytes, doc = null) {
|
|
145
|
+
let index = indexes.get(bytes);
|
|
146
|
+
if (!index) {
|
|
147
|
+
index = doc ? docRanges(doc) : stateIndex([bytes]);
|
|
148
|
+
indexes.set(bytes, index);
|
|
149
|
+
}
|
|
150
|
+
return index;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Whether applying `update` to the indexed state would add no item and no deletion.
|
|
155
|
+
*
|
|
156
|
+
* @param {Object} index - from indexOf() or stateIndex()
|
|
157
|
+
* @param {Uint8Array} update
|
|
158
|
+
* @returns {boolean}
|
|
159
|
+
*/
|
|
160
|
+
export function indexContains(index, update) {
|
|
161
|
+
return index.complete !== false && within(index, rangesOf(update));
|
|
162
|
+
}
|
package/src/page/ybind/doc.js
CHANGED
|
@@ -26,16 +26,21 @@ const rawOf = (pd, b) => {
|
|
|
26
26
|
return out;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
// `nodes`, when given, maps a walk segment to a node already proven equal to what this
|
|
30
|
+
// function would build for it (the bridge fills it from the view): that block costs
|
|
31
|
+
// nothing to derive again, and the same node object comes back.
|
|
32
|
+
export function docOf(pd, w, nodes = null) {
|
|
33
|
+
const build = (b) => {
|
|
31
34
|
if (b.opaque) {
|
|
32
35
|
const fields = b.recovered ? { ...copy(b.fields ?? {}) } : rawOf(pd, b);
|
|
33
36
|
return { b, type: schema.nodes.opaque_block, fields, opaque: true };
|
|
34
37
|
}
|
|
35
38
|
const type = schema.nodes[b.t] ?? schema.nodes.paragraph;
|
|
36
39
|
return { b, type, fields: { ...(b.fields ?? fieldsOf(pd, b, type)) } };
|
|
37
|
-
}
|
|
38
|
-
const
|
|
40
|
+
};
|
|
41
|
+
const blocks = w.visible.map((b) => { const node = nodes?.get(b); return node ? { b, node } : build(b); });
|
|
42
|
+
const byId = new Map(blocks.map((block, i) => [block.b.id, i]));
|
|
43
|
+
const parentOf = (i) => (blocks[i].node ? blocks[i].node.attrs.parent : blocks[i].fields.parent);
|
|
39
44
|
const rankOf = (block) => {
|
|
40
45
|
const item = block.b.recovered
|
|
41
46
|
? pd.gone._map.get(attrKey(block.b.sourceKey, "parent"))
|
|
@@ -48,17 +53,20 @@ export function docOf(pd, w) {
|
|
|
48
53
|
while (byId.has(id) && !positions.has(id)) {
|
|
49
54
|
positions.set(id, chain.length);
|
|
50
55
|
chain.push(id);
|
|
51
|
-
id = byId.get(id)
|
|
56
|
+
id = parentOf(byId.get(id));
|
|
52
57
|
}
|
|
53
58
|
if (!positions.has(id)) continue;
|
|
54
59
|
const cycle = chain.slice(positions.get(id));
|
|
55
60
|
const lowest = cycle.reduce((left, right) => {
|
|
56
|
-
const a = rankOf(byId.get(left)), b = rankOf(byId.get(right));
|
|
61
|
+
const a = rankOf(blocks[byId.get(left)]), b = rankOf(blocks[byId.get(right)]);
|
|
57
62
|
return a.clock < b.clock || (a.clock === b.clock && a.client < b.client) ? left : right;
|
|
58
63
|
});
|
|
59
|
-
byId.get(lowest)
|
|
64
|
+
const at = byId.get(lowest);
|
|
65
|
+
if (blocks[at].node) blocks[at] = build(blocks[at].b);
|
|
66
|
+
blocks[at].fields.parent = null;
|
|
60
67
|
}
|
|
61
|
-
const
|
|
68
|
+
const built = blocks.map(({ b, node, type, fields, opaque }) => {
|
|
69
|
+
if (node) return node;
|
|
62
70
|
if (opaque) {
|
|
63
71
|
const content = [];
|
|
64
72
|
for (const r of b.runs) {
|
|
@@ -79,5 +87,5 @@ export function docOf(pd, w) {
|
|
|
79
87
|
}
|
|
80
88
|
return type.create(attrs, content);
|
|
81
89
|
});
|
|
82
|
-
return
|
|
90
|
+
return built.length ? schema.nodes.doc.create(null, built) : schema.nodes.doc.createAndFill();
|
|
83
91
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* file-debt.js — what this tab authored that no accepted file save has covered yet.
|
|
3
|
+
*
|
|
4
|
+
* One merged Yjs update per page binding, not a list of per-keystroke records and not an
|
|
5
|
+
* IndexedDB journal: a leave needs to know whether this tab's own input still owes the
|
|
6
|
+
* file, and it needs that answer synchronously. Adopting a peer's update, delivering one
|
|
7
|
+
* and compacting the store are not this tab's input, so none of them records here.
|
|
8
|
+
*
|
|
9
|
+
* Only accepted page bytes clear it, and only by the coverage primitive: a state that does
|
|
10
|
+
* not decode, or does not carry every insertion and deletion this tab owns, leaves the
|
|
11
|
+
* obligation exactly where it was.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { mergeUpdatesV2 } from "../../vendor/yjs.vendor.js";
|
|
15
|
+
import { canonicalPageBytes, containsUpdate } from "./recovery-proof.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The file obligation of one page binding.
|
|
19
|
+
*
|
|
20
|
+
* @param {{changed?: Function}} [options] - notified whenever the answer may have changed
|
|
21
|
+
* @returns {{local: Function, pending: Function, accepted: Function}}
|
|
22
|
+
*/
|
|
23
|
+
export function createPageFileDebt({ changed = () => {} } = {}) {
|
|
24
|
+
// Merged in runs of doubling length, as a binary counter carries: slot k holds 2^k of
|
|
25
|
+
// this tab's updates. A keystroke merges with the runs it completes, not with everything
|
|
26
|
+
// the session typed, so a long session without an accepted save stays cheap to type in.
|
|
27
|
+
let runs = [];
|
|
28
|
+
const owned = () => {
|
|
29
|
+
const parts = runs.filter(Boolean);
|
|
30
|
+
if (!parts.length) return null;
|
|
31
|
+
const merged = parts.length === 1 ? parts[0] : mergeUpdatesV2(parts);
|
|
32
|
+
runs = runs.map(() => null);
|
|
33
|
+
runs[runs.length - 1] = merged;
|
|
34
|
+
return merged;
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
local(update) {
|
|
38
|
+
let carry = update.slice();
|
|
39
|
+
let k = 0;
|
|
40
|
+
for (; runs[k]; k++) {
|
|
41
|
+
carry = mergeUpdatesV2([runs[k], carry]);
|
|
42
|
+
runs[k] = null;
|
|
43
|
+
}
|
|
44
|
+
runs[k] = carry;
|
|
45
|
+
changed();
|
|
46
|
+
},
|
|
47
|
+
pending() {
|
|
48
|
+
return runs.some(Boolean);
|
|
49
|
+
},
|
|
50
|
+
accepted(state) {
|
|
51
|
+
const merged = owned();
|
|
52
|
+
if (!merged) return true;
|
|
53
|
+
let covered = false;
|
|
54
|
+
try {
|
|
55
|
+
covered = containsUpdate(canonicalPageBytes(state), merged);
|
|
56
|
+
} catch {}
|
|
57
|
+
if (!covered) return false;
|
|
58
|
+
runs = [];
|
|
59
|
+
changed();
|
|
60
|
+
return true;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
package/src/page/ybind/layout.js
CHANGED
|
@@ -9,9 +9,14 @@ export const HISTORY_META = Symbol("page-history");
|
|
|
9
9
|
export const PAGE_ROOTS = Object.freeze(["flow", "attrs", "gone", "boardFields", "tableFields", "origins"]);
|
|
10
10
|
|
|
11
11
|
const SKIP = new Set(["id", "pos"]);
|
|
12
|
-
|
|
12
|
+
const KEYS = new WeakMap();
|
|
13
|
+
export const attrKeys = (type) => {
|
|
14
|
+
let keys = KEYS.get(type);
|
|
15
|
+
if (!keys) KEYS.set(type, keys = Object.freeze([...Object.keys(type.spec.attrs || {}).filter((k) => !SKIP.has(k)), ...(type.name === "table" ? ["rows"] : [])]));
|
|
16
|
+
return keys;
|
|
17
|
+
};
|
|
13
18
|
export const defaultOf = (type, k) => type.name === "table" && k === "rows" ? {} : type.spec.attrs[k].default;
|
|
14
|
-
export const sameJson = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
19
|
+
export const sameJson = (a, b) => a === b || JSON.stringify(a) === JSON.stringify(b);
|
|
15
20
|
export const idKey = (id) => `${id.client}.${id.clock}`;
|
|
16
21
|
export const parseKey = (k) => { const i = k.indexOf("."); return { client: +k.slice(0, i), clock: +k.slice(i + 1) }; };
|
|
17
22
|
export const isHigh = (u) => u >= 0xd800 && u <= 0xdbff;
|
package/src/page/ybind/marks.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { schema } from "../schema.js";
|
|
2
2
|
import { entriesOfYjs, isOpaque, opaqueMark, yjsFields } from "../format-codec.js";
|
|
3
|
+
import { NATIVE_NAMES, nativeAttrs } from "../format-native.js";
|
|
3
4
|
|
|
4
5
|
export const markName = (k) => k.split("!")[0].split("~")[0];
|
|
5
6
|
export const isCancel = (k) => k.includes("!");
|
|
@@ -29,7 +30,7 @@ export function marksFromAttributes(a, blockType) {
|
|
|
29
30
|
const marks = [];
|
|
30
31
|
let unknown = null;
|
|
31
32
|
for (const [n, v] of best) {
|
|
32
|
-
if (schema.marks[n]) marks.push(schema.marks[n].create(v === true ? null : v));
|
|
33
|
+
if (schema.marks[n] && (!NATIVE_NAMES.has(n) || nativeAttrs(n, v === true ? {} : v))) marks.push(schema.marks[n].create(v === true ? null : v));
|
|
33
34
|
else (unknown ??= {})[n] = v;
|
|
34
35
|
}
|
|
35
36
|
// The walk asks this of every text run on every keystroke, and almost none carry a
|