@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
|
@@ -139,15 +139,118 @@ const recordOf = (value, key, out) => {
|
|
|
139
139
|
out.removals.push({ key, sourceKey: key, source, operationId: null, blockId: value.b, end, sv: [], legacy: true });
|
|
140
140
|
};
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
142
|
+
const emptyIntents = () => ({ removals: [], restores: [], copyActive: new Set(), attrs: new Map(), textDeletes: [] });
|
|
143
|
+
|
|
144
|
+
const frozen = (value) => {
|
|
145
|
+
if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
|
|
146
|
+
for (const key of Object.keys(value)) frozen(value[key]);
|
|
147
|
+
Object.freeze(value);
|
|
148
|
+
}
|
|
149
|
+
return value;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const parsedRecord = (value, key) => {
|
|
153
|
+
const out = emptyIntents();
|
|
154
|
+
try {
|
|
155
|
+
recordOf(value, key, out);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
if (!(error instanceof RecoveryError)) throw error;
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
for (const list of [out.removals, out.restores, out.textDeletes]) for (const record of list) frozen(record);
|
|
161
|
+
for (const fields of out.attrs.values()) for (const fieldValue of fields.values()) frozen(fieldValue);
|
|
162
|
+
return out;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const parsedGone = new WeakMap();
|
|
166
|
+
|
|
167
|
+
const foldRecord = (out, record) => {
|
|
168
|
+
for (const removal of record.removals) out.removals.push(removal);
|
|
169
|
+
for (const restore of record.restores) out.restores.push(restore);
|
|
170
|
+
for (const textDelete of record.textDeletes) out.textDeletes.push(textDelete);
|
|
171
|
+
for (const sourceKey of record.copyActive) out.copyActive.add(sourceKey);
|
|
172
|
+
for (const [sourceKey, fields] of record.attrs) {
|
|
173
|
+
const into = out.attrs.get(sourceKey) ?? new Map();
|
|
174
|
+
for (const [field, fieldValue] of fields) into.set(field, fieldValue);
|
|
175
|
+
out.attrs.set(sourceKey, into);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// A document's gone map parsed once per write, in the map's own key order (Yjs keeps a key
|
|
180
|
+
// where it was first written, through deletes and rewrites). The observer keeps it current
|
|
181
|
+
// as transactions settle; keys a transaction that has not settled yet wrote are read again
|
|
182
|
+
// from the map. A new key adds its record to the totals; any other change rebuilds them
|
|
183
|
+
// from the records kept. Every call gets its own lists, as a fresh parse would.
|
|
184
|
+
const goneIndexes = new WeakMap();
|
|
185
|
+
const goneIndexOf = (gone) => {
|
|
186
|
+
const known = goneIndexes.get(gone);
|
|
187
|
+
if (known) return known;
|
|
188
|
+
const entries = new Map();
|
|
189
|
+
let totals = null;
|
|
190
|
+
let added = [];
|
|
191
|
+
const update = (key) => {
|
|
192
|
+
const live = gone.has(key);
|
|
193
|
+
const value = live ? gone.get(key) : undefined;
|
|
194
|
+
const entry = entries.get(key);
|
|
195
|
+
if (entry && entry.live === live && entry.value === value) return;
|
|
196
|
+
const record = live ? parsedRecord(value, key) : null;
|
|
197
|
+
if (!entry) {
|
|
198
|
+
const next = { live, value, record };
|
|
199
|
+
entries.set(key, next);
|
|
200
|
+
if (live && record && totals) added.push(record);
|
|
201
|
+
return;
|
|
149
202
|
}
|
|
203
|
+
entry.live = live;
|
|
204
|
+
entry.value = value;
|
|
205
|
+
entry.record = record;
|
|
206
|
+
totals = null;
|
|
207
|
+
};
|
|
208
|
+
for (const key of gone._map.keys()) update(key);
|
|
209
|
+
gone.observe((event) => { for (const key of event.keysChanged) update(key); });
|
|
210
|
+
const index = {
|
|
211
|
+
intents() {
|
|
212
|
+
for (const t of gone.doc._transactionCleanups) for (const key of t.changed.get(gone) ?? []) update(key);
|
|
213
|
+
if (!totals) {
|
|
214
|
+
totals = emptyIntents();
|
|
215
|
+
for (const entry of entries.values()) if (entry.record) foldRecord(totals, entry.record);
|
|
216
|
+
added = [];
|
|
217
|
+
} else if (added.length) {
|
|
218
|
+
for (const record of added) foldRecord(totals, record);
|
|
219
|
+
added = [];
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
removals: totals.removals.slice(),
|
|
223
|
+
restores: totals.restores.slice(),
|
|
224
|
+
copyActive: new Set(totals.copyActive),
|
|
225
|
+
attrs: new Map([...totals.attrs].map(([sourceKey, fields]) => [sourceKey, new Map(fields)])),
|
|
226
|
+
textDeletes: totals.textDeletes.slice(),
|
|
227
|
+
};
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
goneIndexes.set(gone, index);
|
|
231
|
+
return index;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export function parseGoneIntents(gone, { strict = false, kept = true } = {}) {
|
|
235
|
+
const out = emptyIntents();
|
|
236
|
+
if (strict) {
|
|
237
|
+
gone.forEach((value, key) => recordOf(value, key, out));
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
240
|
+
if (kept && gone instanceof Y.Map && gone.doc) return goneIndexOf(gone).intents();
|
|
241
|
+
if (!kept) {
|
|
242
|
+
gone.forEach((value, key) => { const record = parsedRecord(value, key); if (record) foldRecord(out, record); });
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
const previous = parsedGone.get(gone) ?? new Map();
|
|
246
|
+
const parsed = new Map();
|
|
247
|
+
gone.forEach((value, key) => {
|
|
248
|
+
const hit = previous.get(key);
|
|
249
|
+
const entry = hit && hit.value === value ? hit : { value, record: parsedRecord(value, key) };
|
|
250
|
+
parsed.set(key, entry);
|
|
251
|
+
if (entry.record) foldRecord(out, entry.record);
|
|
150
252
|
});
|
|
253
|
+
parsedGone.set(gone, parsed);
|
|
151
254
|
return out;
|
|
152
255
|
}
|
|
153
256
|
|
|
@@ -220,18 +323,13 @@ const rawOf = (attrs) => {
|
|
|
220
323
|
|
|
221
324
|
const displaced = (item, seen) => seen.some((vector) => item.clock >= (vector.get(item.client) ?? 0));
|
|
222
325
|
|
|
223
|
-
const
|
|
224
|
-
const last = spans[spans.length - 1];
|
|
225
|
-
if (last && clock <= last.end) {
|
|
226
|
-
if (clock === last.end) last.end = clock + 1;
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
spans.push({ start: clock, end: clock + 1 });
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const mergeClocks = (clocks) => {
|
|
326
|
+
const mergeRanges = (list) => {
|
|
233
327
|
const spans = [];
|
|
234
|
-
for (const
|
|
328
|
+
for (const [start, end] of list.sort((a, b) => a[0] - b[0])) {
|
|
329
|
+
const last = spans[spans.length - 1];
|
|
330
|
+
if (last && start <= last.end) last.end = Math.max(last.end, end);
|
|
331
|
+
else spans.push({ start, end });
|
|
332
|
+
}
|
|
235
333
|
return spans;
|
|
236
334
|
};
|
|
237
335
|
|
|
@@ -251,11 +349,10 @@ const visibleOriginIndex = (flow, walked, index) => {
|
|
|
251
349
|
const ranges = createProjectionMap(walked, flow.length).physicalRanges(0, walked.tokens);
|
|
252
350
|
const clocks = new Map();
|
|
253
351
|
const collect = (item, from, to) => {
|
|
254
|
-
for (
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
else clocks.set(source.client, [source.clock]);
|
|
352
|
+
for (const piece of index.resolveRange(item.id.client, item.id.clock + from, to - from)) {
|
|
353
|
+
const list = clocks.get(piece.client);
|
|
354
|
+
if (list) list.push([piece.clock, piece.clock + piece.length]);
|
|
355
|
+
else clocks.set(piece.client, [[piece.clock, piece.clock + piece.length]]);
|
|
259
356
|
}
|
|
260
357
|
};
|
|
261
358
|
let at = 0, cursor = 0;
|
|
@@ -277,7 +374,7 @@ const visibleOriginIndex = (flow, walked, index) => {
|
|
|
277
374
|
cursor = range;
|
|
278
375
|
}
|
|
279
376
|
const spans = new Map();
|
|
280
|
-
for (const [client, list] of clocks) spans.set(client,
|
|
377
|
+
for (const [client, list] of clocks) spans.set(client, mergeRanges(list));
|
|
281
378
|
return (client, clock) => {
|
|
282
379
|
const list = spans.get(client);
|
|
283
380
|
return list ? holdsClock(list, clock) : false;
|
|
@@ -371,7 +468,7 @@ function sourceRecord(pd, index, sourceKey, removals, hidden, intents, visible,
|
|
|
371
468
|
attrs: root.attrs,
|
|
372
469
|
author: blocks[0].author,
|
|
373
470
|
blocks: blocks.map((block) => ({ id: block.id, type: block.type, attrs: block.attrs, author: block.author, runs: block.runs.map((run) => runOut(run, authorOf)) })),
|
|
374
|
-
removals: ordered.map((removal) => ({ key: removal.key, operationId: removal.operationId, end: removal.end, sv: removal.sv })),
|
|
471
|
+
removals: ordered.map((removal) => ({ key: removal.key, operationId: removal.operationId, end: removal.end, sv: removal.sv.map(([client, clock]) => [client, clock]) })),
|
|
375
472
|
restores: intents.restores.filter((restore) => restore.covers.some((cover) => ordered.some((removal) => removal.key === cover))).sort((a, b) => compare(a.key, b.key)).map((restore) => ({ key: restore.key, covers: sortedList(restore.covers) })),
|
|
376
473
|
copyActive: intents.copyActive.has(sourceKey),
|
|
377
474
|
overrides,
|
|
@@ -380,31 +477,63 @@ function sourceRecord(pd, index, sourceKey, removals, hidden, intents, visible,
|
|
|
380
477
|
return { ...out, revision: canonicalRevision(out) };
|
|
381
478
|
}
|
|
382
479
|
|
|
480
|
+
const seenClock = (sv, client) => {
|
|
481
|
+
for (const [c, clock] of sv) if (c === client) return clock;
|
|
482
|
+
return 0;
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const mayDisplace = (removal, entry) => {
|
|
486
|
+
for (const item of entry.items) if (item.kind !== "marker" && item.clock + item.length - 1 >= seenClock(removal.sv, item.client)) return true;
|
|
487
|
+
return false;
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const bySource = (a, b) => {
|
|
491
|
+
const left = parsePhysicalKey(a.source), right = parsePhysicalKey(b.source);
|
|
492
|
+
return left.client - right.client || left.clock - right.clock;
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
function recoveryInputs(pd, index, options) {
|
|
496
|
+
const walked = options.walked ?? walk(pd, index);
|
|
497
|
+
const intents = options.intents ?? parseGoneIntents(pd.gone, { strict: options.strict === true });
|
|
498
|
+
const hidden = new Map();
|
|
499
|
+
for (const entry of walked.hidden ?? []) hidden.set(entry.key, entry);
|
|
500
|
+
const groups = new Map();
|
|
501
|
+
for (const removal of walked.removals ?? intents.removals) {
|
|
502
|
+
const list = groups.get(removal.sourceKey);
|
|
503
|
+
if (list) list.push(removal);
|
|
504
|
+
else groups.set(removal.sourceKey, [removal]);
|
|
505
|
+
}
|
|
506
|
+
let visibleIndex = null;
|
|
507
|
+
const visible = (client, clock) => {
|
|
508
|
+
if (!visibleIndex) visibleIndex = visibleOriginIndex(pd.flow, walked, index);
|
|
509
|
+
return visibleIndex(client, clock);
|
|
510
|
+
};
|
|
511
|
+
return { intents, hidden, groups, visible };
|
|
512
|
+
}
|
|
513
|
+
|
|
383
514
|
export function reduceRecovery(pd, options = {}) {
|
|
384
515
|
const index = options.index ?? createOriginIndex(pd.origins);
|
|
385
516
|
try {
|
|
386
|
-
const
|
|
387
|
-
const intents = options.intents ?? parseGoneIntents(pd.gone, { strict: options.strict === true });
|
|
388
|
-
const hidden = new Map();
|
|
389
|
-
for (const entry of walked.hidden ?? []) hidden.set(entry.key, entry);
|
|
390
|
-
const groups = new Map();
|
|
391
|
-
for (const removal of walked.removals ?? intents.removals) {
|
|
392
|
-
const list = groups.get(removal.sourceKey);
|
|
393
|
-
if (list) list.push(removal);
|
|
394
|
-
else groups.set(removal.sourceKey, [removal]);
|
|
395
|
-
}
|
|
396
|
-
let visibleIndex = null;
|
|
397
|
-
const visible = (client, clock) => {
|
|
398
|
-
if (!visibleIndex) visibleIndex = visibleOriginIndex(pd.flow, walked, index);
|
|
399
|
-
return visibleIndex(client, clock);
|
|
400
|
-
};
|
|
517
|
+
const { intents, hidden, groups, visible } = recoveryInputs(pd, index, options);
|
|
401
518
|
const sources = new Map();
|
|
402
519
|
for (const [sourceKey, removals] of groups) sources.set(sourceKey, sourceRecord(pd, index, sourceKey, removals, hidden, intents, visible, groups));
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
520
|
+
return { sources, list: [...sources.values()].sort(bySource), intents };
|
|
521
|
+
} finally {
|
|
522
|
+
if (!options.index) index.dispose();
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export function reduceRecoverable(pd, options = {}) {
|
|
527
|
+
const index = options.index ?? createOriginIndex(pd.origins);
|
|
528
|
+
try {
|
|
529
|
+
const { intents, hidden, groups, visible } = recoveryInputs(pd, index, options);
|
|
530
|
+
const list = [];
|
|
531
|
+
for (const [sourceKey, removals] of groups) {
|
|
532
|
+
if (!removals.some((removal) => hidden.has(removal.key) && mayDisplace(removal, hidden.get(removal.key)))) continue;
|
|
533
|
+
const record = sourceRecord(pd, index, sourceKey, removals, hidden, intents, visible, groups);
|
|
534
|
+
if (record.recoverable) list.push(record);
|
|
535
|
+
}
|
|
536
|
+
return { list: list.sort(bySource), intents };
|
|
408
537
|
} finally {
|
|
409
538
|
if (!options.index) index.dispose();
|
|
410
539
|
}
|
package/src/page/ybind/table.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as Y from "../../vendor/yjs.vendor.js";
|
|
1
2
|
import { schema } from "../schema.js";
|
|
2
3
|
import { marksOf } from "../inline.js";
|
|
3
4
|
import { ATOM, BREAK, canon, unitsOf } from "../projection.js";
|
|
@@ -45,28 +46,83 @@ function keepOne(live, dead) {
|
|
|
45
46
|
if (first) live[first] = dead[first];
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
// The table a field belongs to, or null when the field is one resolveTableFields refuses.
|
|
50
|
+
export function tableFieldOwner(key, value) {
|
|
51
|
+
let tuple;
|
|
52
|
+
try { tuple = JSON.parse(key); } catch { return null; }
|
|
53
|
+
if (!Array.isArray(tuple) || tuple.length !== 4) return null;
|
|
54
|
+
const [owner, kind, entityId, field] = tuple;
|
|
55
|
+
if (!validId(owner) || !validId(entityId)) return null;
|
|
56
|
+
if (kind === "cell") return validId(field) && validCell(value) ? owner : null;
|
|
57
|
+
return Object.hasOwn(TABLE_FIELDS, kind) && TABLE_FIELDS[kind].includes(field) && validValue(field, value) ? owner : null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Each table's own fields of a document's tableFields map, in the map's own key order
|
|
61
|
+
// (Yjs keeps a key where it was first written, through deletes and rewrites), checked once
|
|
62
|
+
// per write. The observer keeps it current as transactions settle; keys a transaction that
|
|
63
|
+
// has not settled yet wrote are read again from the map.
|
|
64
|
+
const fieldIndexes = new WeakMap();
|
|
65
|
+
const fieldIndexOf = (fields) => {
|
|
66
|
+
const known = fieldIndexes.get(fields);
|
|
67
|
+
if (known) return known;
|
|
68
|
+
const entries = new Map(), byOwner = new Map(), invalid = new Set();
|
|
69
|
+
const apply = (key) => {
|
|
70
|
+
let entry = entries.get(key);
|
|
71
|
+
if (!entry) {
|
|
72
|
+
let tuple = null;
|
|
73
|
+
try { tuple = JSON.parse(key); } catch { tuple = null; }
|
|
74
|
+
const owner = Array.isArray(tuple) && tuple.length === 4 && validId(tuple[0]) ? tuple[0] : null;
|
|
75
|
+
entry = { key, tuple, live: false, value: undefined };
|
|
76
|
+
entries.set(key, entry);
|
|
77
|
+
if (owner !== null) (byOwner.get(owner) ?? byOwner.set(owner, []).get(owner)).push(entry);
|
|
78
|
+
}
|
|
79
|
+
entry.live = fields.has(key);
|
|
80
|
+
entry.value = entry.live ? fields.get(key) : undefined;
|
|
81
|
+
if (entry.live && tableFieldOwner(key, entry.value) === null) invalid.add(key);
|
|
82
|
+
else invalid.delete(key);
|
|
83
|
+
};
|
|
84
|
+
for (const key of fields._map.keys()) apply(key);
|
|
85
|
+
fields.observe((event) => { for (const key of event.keysChanged) apply(key); });
|
|
86
|
+
const index = {
|
|
87
|
+
owned(blockId) {
|
|
88
|
+
for (const t of fields.doc._transactionCleanups) for (const key of t.changed.get(fields) ?? []) apply(key);
|
|
89
|
+
return invalid.size ? null : byOwner.get(blockId) ?? [];
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
fieldIndexes.set(fields, index);
|
|
93
|
+
return index;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const ownedFields = (fields, blockId) => (blockId !== null && fields instanceof Y.Map && fields.doc ? fieldIndexOf(fields).owned(blockId) : null);
|
|
97
|
+
|
|
48
98
|
export function resolveTableFields(fields, blockId = null, seed = null) {
|
|
49
99
|
const rows = new Map(), cols = new Map(), cells = new Map();
|
|
50
|
-
|
|
51
|
-
let tuple;
|
|
52
|
-
try { tuple = JSON.parse(key); } catch { fail(key); }
|
|
53
|
-
if (!Array.isArray(tuple) || tuple.length !== 4) fail(key);
|
|
54
|
-
const [owner, kind, entityId, field] = tuple;
|
|
55
|
-
if (!validId(owner) || !validId(entityId)) fail(key);
|
|
56
|
-
if (kind === "cell") {
|
|
57
|
-
if (!validId(field) || !validCell(value)) fail(key);
|
|
58
|
-
} else if (!Object.hasOwn(TABLE_FIELDS, kind) || !TABLE_FIELDS[kind].includes(field) || !validValue(field, value)) fail(key);
|
|
59
|
-
if (owner !== blockId) continue;
|
|
100
|
+
const take = (kind, entityId, field, value) => {
|
|
60
101
|
if (kind === "cell") {
|
|
61
102
|
const row = cells.get(entityId) ?? new Map();
|
|
62
103
|
row.set(field, value);
|
|
63
104
|
cells.set(entityId, row);
|
|
64
|
-
|
|
105
|
+
return;
|
|
65
106
|
}
|
|
66
107
|
const bucket = kind === "row" ? rows : cols;
|
|
67
108
|
const entity = bucket.get(entityId) ?? {};
|
|
68
109
|
entity[field] = value;
|
|
69
110
|
bucket.set(entityId, entity);
|
|
111
|
+
};
|
|
112
|
+
const owned = ownedFields(fields, blockId);
|
|
113
|
+
if (owned) {
|
|
114
|
+
for (const entry of owned) if (entry.live) take(entry.tuple[1], entry.tuple[2], entry.tuple[3], entry.value);
|
|
115
|
+
} else for (const [key, value] of fields) {
|
|
116
|
+
let tuple;
|
|
117
|
+
try { tuple = JSON.parse(key); } catch { fail(key); }
|
|
118
|
+
if (!Array.isArray(tuple) || tuple.length !== 4) fail(key);
|
|
119
|
+
const [owner, kind, entityId, field] = tuple;
|
|
120
|
+
if (!validId(owner) || !validId(entityId)) fail(key);
|
|
121
|
+
if (kind === "cell") {
|
|
122
|
+
if (!validId(field) || !validCell(value)) fail(key);
|
|
123
|
+
} else if (!Object.hasOwn(TABLE_FIELDS, kind) || !TABLE_FIELDS[kind].includes(field) || !validValue(field, value)) fail(key);
|
|
124
|
+
if (owner !== blockId) continue;
|
|
125
|
+
take(kind, entityId, field, value);
|
|
70
126
|
}
|
|
71
127
|
const outCols = { ...(record(seed?.cols) ? seed.cols : {}) };
|
|
72
128
|
const outRows = { ...(record(seed?.rows) ? seed.rows : {}) };
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// One keystroke changes one block. The walk over the whole flow (walk.js) costs a
|
|
2
|
+
// few milliseconds at 2,000 blocks whatever the keystroke did, so a transaction that
|
|
3
|
+
// only changed text inside one live block is read from the transaction itself, that
|
|
4
|
+
// block is walked again from its own marker, and the result is spliced into the
|
|
5
|
+
// previous walk. Anything else (a marker, an attribute, a recovery record, a move,
|
|
6
|
+
// text inside a removed block's region) returns null and the caller walks from the
|
|
7
|
+
// start. The caller proves the splice against a full walk when asked (walkSignature).
|
|
8
|
+
import * as Y from "../../vendor/yjs.vendor.js";
|
|
9
|
+
import { idKey, sameJson } from "./layout.js";
|
|
10
|
+
import { marksFromAttributes, marksKey } from "./marks.js";
|
|
11
|
+
import { yAfter } from "./tokens.js";
|
|
12
|
+
import { canonicalRevision } from "./recovery.js";
|
|
13
|
+
import { tableFieldOwner } from "./table.js";
|
|
14
|
+
import { schema } from "../schema.js";
|
|
15
|
+
|
|
16
|
+
const TEXT_DELETE_PREFIX = '["text-delete",';
|
|
17
|
+
|
|
18
|
+
// The block whose attribute map a type is, or null.
|
|
19
|
+
const blockOfAttrs = (pd, type) => (type instanceof Y.AbstractType && type._item && type._item.parent === pd.attrs && typeof type._item.parentSub === "string" ? type._item.parentSub : null);
|
|
20
|
+
// The walk reads a block's type, and docOf reads every block's parent to break cycles.
|
|
21
|
+
const crossBlock = (key) => key === "t" || key === "parent";
|
|
22
|
+
|
|
23
|
+
const isMarker = (it) => it.content instanceof Y.ContentEmbed && it.content.embed.b !== undefined;
|
|
24
|
+
const textual = (it) => it.content instanceof Y.ContentString || it.content instanceof Y.ContentFormat || (it.content instanceof Y.ContentEmbed && it.content.embed.b === undefined);
|
|
25
|
+
|
|
26
|
+
// The deleted items the walk acts on (removal and legacy starts), by client. Yjs merges
|
|
27
|
+
// neighbouring deleted items and may have replaced a marker's content, so a deleted item
|
|
28
|
+
// is matched by id range, never by content. Any other deleted item is skipped, as the
|
|
29
|
+
// walk skips it.
|
|
30
|
+
const stopIndex = (w) => {
|
|
31
|
+
const by = new Map();
|
|
32
|
+
for (const stop of w.stops) (by.get(stop.client) ?? by.set(stop.client, []).get(stop.client)).push(stop);
|
|
33
|
+
return by;
|
|
34
|
+
};
|
|
35
|
+
const hitsStop = (it, stops) => {
|
|
36
|
+
const list = stops.get(it.id.client);
|
|
37
|
+
if (!list) return false;
|
|
38
|
+
const lo = it.id.clock, hi = lo + it.length;
|
|
39
|
+
return list.some((s) => s.clock < hi && lo < s.clock + s.len);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// The live marker whose block holds an item, or null when a stop lies between them.
|
|
43
|
+
const markerOf = (it, stops) => {
|
|
44
|
+
for (let left = it.left; left; left = left.left) {
|
|
45
|
+
if (left.deleted) { if (hitsStop(left, stops)) return null; continue; }
|
|
46
|
+
if (isMarker(left)) return left;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Reads a finished Yjs transaction and returns the one live marker whose block alone it
|
|
52
|
+
// changed (or none), the marker of a block it split off that one (`split`, a new marker
|
|
53
|
+
// with a new attribute map), the blocks whose own fields it changed (table fields,
|
|
54
|
+
// attributes other than the type and the parent), whether it deleted anything and
|
|
55
|
+
// whether it wrote a format item; or null when the walk must start over. The walk never
|
|
56
|
+
// reads those fields: only those blocks' nodes are derived again.
|
|
57
|
+
export function captureDelta(pd, transaction, previous) {
|
|
58
|
+
const stops = stopIndex(previous);
|
|
59
|
+
const fields = new Set();
|
|
60
|
+
const born = new Set();
|
|
61
|
+
const tableKeys = transaction.changed.get(pd.tableFields) ?? new Set();
|
|
62
|
+
for (const [type, keys] of transaction.changed) {
|
|
63
|
+
if (type === pd.flow) continue;
|
|
64
|
+
if (type === pd.attrs) {
|
|
65
|
+
// A split writes the new block's first attribute map beside its marker. Only this
|
|
66
|
+
// tab's own: an id it just minted, so no removal, restore or copy record can name it.
|
|
67
|
+
if (!transaction.local) return null;
|
|
68
|
+
for (const key of keys) {
|
|
69
|
+
const map = pd.attrs.get(key);
|
|
70
|
+
if (!(map instanceof Y.Map) || map._item.left || map._item.id.clock < (transaction.beforeState.get(map._item.id.client) ?? 0)) return null;
|
|
71
|
+
born.add(key);
|
|
72
|
+
}
|
|
73
|
+
if (born.size > 1) return null;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (type === pd.tableFields) {
|
|
77
|
+
for (const key of keys) {
|
|
78
|
+
const owner = pd.tableFields.has(key) ? tableFieldOwner(key, pd.tableFields.get(key)) : null;
|
|
79
|
+
if (owner === null) return null;
|
|
80
|
+
fields.add(owner);
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const block = blockOfAttrs(pd, type);
|
|
85
|
+
if (block !== null) {
|
|
86
|
+
for (const key of keys) if (crossBlock(key)) return null;
|
|
87
|
+
fields.add(block);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (type !== pd.gone) return null;
|
|
91
|
+
for (const key of keys) if (typeof key !== "string" || !key.startsWith(TEXT_DELETE_PREFIX)) return null;
|
|
92
|
+
}
|
|
93
|
+
const store = pd.ydoc.store;
|
|
94
|
+
let marker = null, split = null, formats = false;
|
|
95
|
+
const claim = (it) => {
|
|
96
|
+
if (!(it instanceof Y.Item)) return false;
|
|
97
|
+
// A text-delete record is written beside the deletion it describes; the walk never reads it.
|
|
98
|
+
if (it.parent === pd.gone) return typeof it.parentSub === "string" && it.parentSub.startsWith(TEXT_DELETE_PREFIX);
|
|
99
|
+
if (it.parent === pd.tableFields) return tableKeys.has(it.parentSub);
|
|
100
|
+
if (it.parent === pd.attrs) return born.has(it.parentSub);
|
|
101
|
+
if (it.parent !== pd.flow) {
|
|
102
|
+
const block = blockOfAttrs(pd, it.parent);
|
|
103
|
+
return block !== null && (born.has(block) || (fields.has(block) && !crossBlock(it.parentSub)));
|
|
104
|
+
}
|
|
105
|
+
if (isMarker(it)) {
|
|
106
|
+
if (it.deleted || !born.has(it.content.embed.b)) return false;
|
|
107
|
+
split = it;
|
|
108
|
+
} else if (!textual(it)) return false;
|
|
109
|
+
const owner = markerOf(it, stops);
|
|
110
|
+
if (!owner || (marker && owner !== marker)) return false;
|
|
111
|
+
marker = owner;
|
|
112
|
+
if (it.content instanceof Y.ContentFormat) formats = true;
|
|
113
|
+
return true;
|
|
114
|
+
};
|
|
115
|
+
for (const [client, clock] of transaction.afterState) {
|
|
116
|
+
for (let at = transaction.beforeState.get(client) ?? 0; at < clock;) {
|
|
117
|
+
const it = Y.getItem(store, { client, clock: at });
|
|
118
|
+
if (!claim(it)) return null;
|
|
119
|
+
at = it.id.clock + it.length;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
let deleted = false;
|
|
123
|
+
for (const [client, ranges] of transaction.deleteSet.clients) {
|
|
124
|
+
for (const { clock, len } of ranges) {
|
|
125
|
+
for (let at = clock; at < clock + len;) {
|
|
126
|
+
const it = Y.getItem(store, { client, clock: at });
|
|
127
|
+
if (!claim(it)) return null;
|
|
128
|
+
deleted = true;
|
|
129
|
+
at = it.id.clock + it.length;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (born.size && !split) return null;
|
|
134
|
+
return marker || fields.size ? { marker, split, deleted, formats, fields, remote: !transaction.local } : null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const fmtOf = (cattrs) => { for (const key in cattrs) return { ...cattrs }; return null; };
|
|
138
|
+
|
|
139
|
+
// Walks one live block again from its marker and splices it into the previous walk,
|
|
140
|
+
// or returns null when that block is not a plain visible one. A block whose runs hold a
|
|
141
|
+
// hidden run (a removed or moved-away block's marker inside its extent) is walked again
|
|
142
|
+
// up to the first live marker, where its first hidden run starts; what follows is the
|
|
143
|
+
// previous walk's, moved by the change in length. That needs the format state there
|
|
144
|
+
// unchanged, so the transaction must not have written a format item. A split's new
|
|
145
|
+
// marker (`change.split`) ends the block there and opens the new block, which takes the
|
|
146
|
+
// rest of the extent.
|
|
147
|
+
export function rewalk(pd, previous, marker, change = null) {
|
|
148
|
+
const formats = change?.formats ?? true, split = change?.split ?? null;
|
|
149
|
+
const visible = previous.visible;
|
|
150
|
+
let index = -1, start = 0;
|
|
151
|
+
for (let i = 0; i < visible.length; i++) {
|
|
152
|
+
const seg = visible[i];
|
|
153
|
+
if (seg.brk && seg.brk.client === marker.id.client && seg.brk.clock === marker.id.clock) { index = i; break; }
|
|
154
|
+
start += seg.len;
|
|
155
|
+
}
|
|
156
|
+
if (index < 0) return null;
|
|
157
|
+
const old = visible[index];
|
|
158
|
+
if (old.implicit || old.atom || old.opaque || old.recovered || old.hide) return null;
|
|
159
|
+
const hole = old.runs.findIndex((r) => r.hole !== undefined);
|
|
160
|
+
if (hole >= 0 && formats) return null;
|
|
161
|
+
// A moved block keeps its old marker live under the removal that hides it, so its copy
|
|
162
|
+
// records its items. They only decide something when another copy of the block is
|
|
163
|
+
// visible too (walk.js, the post pass): that is the full walk's case.
|
|
164
|
+
if (old.items && previous.copies.has(old.baseId)) return null;
|
|
165
|
+
const stops = stopIndex(previous);
|
|
166
|
+
const cattrs = { ...(old.fmtIn ?? {}) };
|
|
167
|
+
let styled = null;
|
|
168
|
+
const first = { ...old, runs: [], len: 1 };
|
|
169
|
+
if (old.items) first.items = [];
|
|
170
|
+
let cur = first, bounded = false;
|
|
171
|
+
const origin = yAfter(previous.holes, start);
|
|
172
|
+
let at = origin + 1;
|
|
173
|
+
for (let it = marker.right; it; it = it.right) {
|
|
174
|
+
// A stop opens a hidden region inside this block's extent: that is the full walk's case.
|
|
175
|
+
if (it.deleted) { if (hitsStop(it, stops)) return null; continue; }
|
|
176
|
+
if (isMarker(it) && (it !== split || cur !== first)) { bounded = true; break; }
|
|
177
|
+
const here = at;
|
|
178
|
+
if (it.countable) at += it.length;
|
|
179
|
+
const c = it.content;
|
|
180
|
+
if (it === split) {
|
|
181
|
+
const id = c.embed.b;
|
|
182
|
+
const t = pd.attrs.get(id)?.get("t") || "paragraph";
|
|
183
|
+
if (!Object.hasOwn(schema.nodes, t) || !schema.nodes[t].isTextblock) return null;
|
|
184
|
+
const f = fmtOf(cattrs);
|
|
185
|
+
first.fmtOut = f;
|
|
186
|
+
cur = { kind: "b", id, baseId: id, brk: { client: it.id.client, clock: it.id.clock }, implicit: false, runs: [], len: 1, atom: false, items: null, fmtIn: f, fmtOut: null, t, opaque: false };
|
|
187
|
+
styled = null;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (c instanceof Y.ContentFormat) { if (c.value === null) delete cattrs[c.key]; else cattrs[c.key] = c.value; styled = null; continue; }
|
|
191
|
+
if (c instanceof Y.ContentEmbed) {
|
|
192
|
+
cur.runs.push({ br: true });
|
|
193
|
+
if (cur.items) cur.items.push({ at: here, id: idKey(it.id), client: it.id.client, clock: it.id.clock, length: it.length, kind: "br", text: null, blockId: null, raw: { ...cattrs } });
|
|
194
|
+
cur.len += 1;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (c instanceof Y.ContentString) {
|
|
198
|
+
if (!styled) { const marks = marksFromAttributes(cattrs, cur.t); styled = { marks, mk: marksKey(marks) }; }
|
|
199
|
+
cur.runs.push({ text: c.str, marks: styled.marks, mk: styled.mk });
|
|
200
|
+
if (cur.items) cur.items.push({ at: here, id: idKey(it.id), client: it.id.client, clock: it.id.clock, length: it.length, kind: "text", text: c.str, blockId: null, raw: { ...cattrs } });
|
|
201
|
+
cur.len += c.str.length;
|
|
202
|
+
} else if (it.countable) return null;
|
|
203
|
+
}
|
|
204
|
+
let prefix = old.len;
|
|
205
|
+
if (hole < 0) {
|
|
206
|
+
if (!sameJson(cattrs, old.fmtOut ?? {})) return null;
|
|
207
|
+
} else {
|
|
208
|
+
if (!bounded) return null;
|
|
209
|
+
// The tail after the hole keeps the marks it was read with, which follow the old
|
|
210
|
+
// block's type: a split that changes the type is the full walk's case.
|
|
211
|
+
if (cur.t !== old.t) return null;
|
|
212
|
+
prefix = 1;
|
|
213
|
+
for (let i = 0; i < hole; i++) prefix += old.runs[i].br ? 1 : old.runs[i].text.length;
|
|
214
|
+
}
|
|
215
|
+
const parts = cur === first ? [first] : [first, cur];
|
|
216
|
+
const delta = parts.reduce((n, part) => n + part.len, 0) - prefix;
|
|
217
|
+
const cut = origin + prefix;
|
|
218
|
+
const moved = (entry) => (entry.at >= cut ? { ...entry, at: entry.at + delta } : entry);
|
|
219
|
+
cur.fmtOut = old.fmtOut;
|
|
220
|
+
if (hole >= 0) {
|
|
221
|
+
cur.runs = cur.runs.concat(old.runs.slice(hole));
|
|
222
|
+
cur.len += old.len - prefix;
|
|
223
|
+
if (cur.items) cur.items = cur.items.concat(old.items.filter((item) => item.at >= cut).map(moved));
|
|
224
|
+
}
|
|
225
|
+
const next = { ...previous, visible: visible.slice(), tokens: previous.tokens + delta };
|
|
226
|
+
next.visible.splice(index, 1, ...parts);
|
|
227
|
+
if (delta) {
|
|
228
|
+
const end = start + prefix;
|
|
229
|
+
next.holes = previous.holes.map((h) => (h.at >= end ? { at: h.at + delta, len: h.len } : h));
|
|
230
|
+
next.hidden = previous.hidden.map((entry) => (entry.at >= cut ? { ...entry, at: entry.at + delta, items: entry.items.map(moved) } : entry));
|
|
231
|
+
for (let j = index + parts.length; j < next.visible.length; j += 1) {
|
|
232
|
+
const later = next.visible[j];
|
|
233
|
+
if (later.items?.length) next.visible[j] = { ...later, items: later.items.map(moved) };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return next;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const run = (r) => (r.br ? "\n" : r.hole !== undefined ? { hole: r.hole } : { text: r.text, mk: r.mk });
|
|
240
|
+
// Run boundaries follow Yjs item boundaries, and Yjs merges clock-adjacent live items
|
|
241
|
+
// after any transaction; only the text and its marks reach the document.
|
|
242
|
+
const joined = (runs) => {
|
|
243
|
+
const out = [];
|
|
244
|
+
for (const r of runs.map(run)) {
|
|
245
|
+
const last = out[out.length - 1];
|
|
246
|
+
if (last && typeof last === "object" && typeof r === "object" && last.text !== undefined && r.text !== undefined && last.mk === r.mk) out[out.length - 1] = { text: last.text + r.text, mk: r.mk };
|
|
247
|
+
else out.push(r);
|
|
248
|
+
}
|
|
249
|
+
return out;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// Everything a walk result holds, as one string, independent of key order and of the
|
|
253
|
+
// mark objects a run carries (their key stands for them).
|
|
254
|
+
// A fence or stop names a deleted item as it was when the walk ran; Yjs may since have
|
|
255
|
+
// merged it into a neighbour. The writer matches fences by containment (fenceAfter), so
|
|
256
|
+
// the item that holds the id now is what both walks mean.
|
|
257
|
+
const holder = (pd, id) => { const it = Y.getItem(pd.ydoc.store, { client: id.client, clock: id.clock }); return { client: it.id.client, clock: it.id.clock, len: it.length }; };
|
|
258
|
+
export const walkSignature = (pd, w) => canonicalRevision({ ...w, copies: [...w.copies], fences: w.fences.map((f) => holder(pd, f)), stops: w.stops.map((f) => holder(pd, f)), visible: w.visible.map((s) => ({ ...s, runs: joined(s.runs) })) });
|