@panphora/clayjs 1.23.0 → 1.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/all.js +1 -1
- package/dist/blockeditor/themes/light-1.css +431 -0
- 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 +30285 -26613
- package/dist/m/c-277QP5ET.js +1 -0
- package/dist/m/c-2BLVA6IN.js +5 -0
- package/dist/m/c-2OJMOUPC.js +1 -0
- package/dist/m/c-2PVGJFI5.js +1 -0
- package/dist/m/{c-AYVGD6A6.js → c-3UW5EJ77.js} +1 -1
- package/dist/m/{c-T5NK2TVY.js → c-42ZNV7LI.js} +1 -1
- package/dist/m/{c-3TLG3OLG.js → c-46LJAZZJ.js} +1 -1
- package/dist/m/c-47IBJGPQ.js +1 -0
- package/dist/m/c-5FXEKU4Y.js +1 -0
- package/dist/m/c-5KXDKC5G.js +1 -0
- package/dist/m/{c-EUGJVHDD.js → c-5RZNFTWD.js} +1 -1
- package/dist/m/c-65LJU2H2.js +72 -0
- package/dist/m/{c-NQJRVJ56.js → c-6IJEVE6B.js} +1 -1
- package/dist/m/{c-NW54NHKE.js → c-6LEYVQJK.js} +1 -1
- package/dist/m/{c-GSRLXYWL.js → c-6Q6RGFBF.js} +1 -1
- package/dist/m/c-6RGEQNVV.js +1 -0
- package/dist/m/c-7CM4GMTS.js +6 -0
- package/dist/m/c-7NCWENHV.js +1 -0
- package/dist/m/c-7V6MGMX2.js +1 -0
- package/dist/m/{c-LKSL4AUJ.js → c-7WZSUTYP.js} +1 -1
- package/dist/m/c-A4L7APIU.js +1 -0
- package/dist/m/c-APGHGDZM.js +1 -0
- package/dist/m/c-AT7GLQGV.js +34 -0
- package/dist/m/{c-ZRXJD4G6.js → c-B7GOFGQR.js} +1 -1
- package/dist/m/{c-KDXIJEEF.js → c-BGN7RCTJ.js} +1 -1
- package/dist/m/c-BKM46LUJ.js +1 -0
- package/dist/m/c-BOMQIQ3V.js +1 -0
- package/dist/m/{c-62HZW5DH.js → c-C5AKJVRH.js} +1 -1
- package/dist/m/{c-BXYSDKAN.js → c-CJ7QYEWI.js} +1 -1
- package/dist/m/c-CTGRDT6E.js +1 -0
- package/dist/m/c-CV4T65JE.js +6 -0
- package/dist/m/c-CVSWLX3X.js +1 -0
- package/dist/m/c-CZHTXGJS.js +1 -0
- package/dist/m/c-D7NT5S4F.js +1 -0
- package/dist/m/c-DD26VXAP.js +1 -0
- package/dist/m/{c-ANNFZFLN.js → c-DDS23EY6.js} +1 -1
- package/dist/m/{c-ALDVFPWL.js → c-DFKBGSN2.js} +1 -1
- package/dist/m/c-DIKR7H57.js +147 -0
- package/dist/m/c-DXPOFLEJ.js +1 -0
- package/dist/m/c-EDE45NM7.js +1 -0
- package/dist/m/c-EILLN2H2.js +1 -0
- package/dist/m/c-EODTTNVA.js +1 -0
- package/dist/m/{c-G6COVIZB.js → c-ESK4S67I.js} +1 -1
- package/dist/m/c-FDWR3FL7.js +1 -0
- package/dist/m/{c-WQZTI5P2.js → c-FEC6LGFF.js} +1 -1
- package/dist/m/c-FLEIU6CG.js +1 -0
- package/dist/m/c-FQAQRXGK.js +1 -0
- package/dist/m/{c-W4JCVLAB.js → c-FSQXFG5T.js} +1 -1
- package/dist/m/c-FVPNX2GO.js +135 -0
- package/dist/m/c-FYDKYW6E.js +1 -0
- package/dist/m/c-GNI7WHKF.js +32 -0
- package/dist/m/c-GO635CRU.js +3 -0
- package/dist/m/c-H3FNLBHN.js +2 -0
- package/dist/m/c-HDVH3MVC.js +72 -0
- package/dist/m/c-HIX5H5L7.js +1 -0
- package/dist/m/c-HKSLOVU4.js +1 -0
- package/dist/m/{c-SGRIYOAJ.js → c-HL6BLJNA.js} +1 -1
- package/dist/m/c-HTLYXCUH.js +1 -0
- package/dist/m/{c-QIR5CAKM.js → c-IUL7PU4P.js} +1 -1
- package/dist/m/c-IXO7EI2Q.js +1 -0
- package/dist/m/{c-W5E5P2TZ.js → c-JQF2EUCG.js} +1 -1
- package/dist/m/{c-NEL7HQW3.js → c-JWDCQL6J.js} +1 -1
- package/dist/m/c-KQKKIVLW.js +20 -0
- package/dist/m/c-LATNQ6Q5.js +1 -0
- package/dist/m/c-LD6UVV5R.js +1 -0
- package/dist/m/c-LS7IVQVW.js +1 -0
- package/dist/m/{c-JCOQVZYS.js → c-LSVN3OLG.js} +1 -1
- package/dist/m/{c-JDZDF2QM.js → c-LTSPHVAK.js} +1 -1
- package/dist/m/c-MFOSMAVM.js +3 -0
- package/dist/m/c-MJ7GVE46.js +1 -0
- package/dist/m/c-MP6S4AZU.js +6 -0
- package/dist/m/c-MQKSKQHG.js +2 -0
- package/dist/m/{c-UHUNXWKN.js → c-MQYEDF3Q.js} +1 -1
- package/dist/m/{c-IHUWUKIU.js → c-MR75YPRT.js} +1 -1
- package/dist/m/c-ND3Q2G2I.js +1 -0
- package/dist/m/c-NEQRFC4O.js +1 -0
- package/dist/m/{c-5FKRZNJV.js → c-NFQFV7OP.js} +1 -1
- package/dist/m/c-NHXOBD6A.js +1 -0
- package/dist/m/{c-QIKT6SKM.js → c-NICICT4P.js} +1 -1
- package/dist/m/c-NQAPWM7O.js +1 -0
- package/dist/m/{c-QUGCKMWF.js → c-NVU3DORS.js} +1 -1
- package/dist/m/{c-LWIDDWAR.js → c-OCYG4NBQ.js} +22 -22
- package/dist/m/c-OH2FPYSY.js +19 -0
- package/dist/m/c-OYHI67B6.js +1 -0
- package/dist/m/{c-R2XUODVY.js → c-OYW5MFPZ.js} +1 -1
- package/dist/m/{c-5B4YBZXC.js → c-P2HKMJAW.js} +1 -1
- package/dist/m/{c-TSBVMWAH.js → c-PCNJETUA.js} +1 -1
- package/dist/m/c-PDBNCJCT.js +1 -0
- package/dist/m/c-PGSBXHUE.js +1 -0
- package/dist/m/{c-5TIZ2I64.js → c-PYG2NGWU.js} +1 -1
- package/dist/m/c-Q26XHBLQ.js +1 -0
- package/dist/m/c-Q6BG3ZLO.js +1 -0
- package/dist/m/c-QENKN7QI.js +1 -0
- package/dist/m/c-QQFHEODI.js +1 -0
- package/dist/m/c-QTEUO2K6.js +1 -0
- package/dist/m/c-R23UHMXG.js +1 -0
- package/dist/m/c-RETTCOWO.js +10 -0
- package/dist/m/c-SE3SIY7Y.js +7 -0
- package/dist/m/{c-XX75FFNL.js → c-SGGGCIVD.js} +1 -1
- package/dist/m/c-SGH3UTF3.js +1 -0
- package/dist/m/c-SIM7Z3U7.js +1 -0
- package/dist/m/{c-X5LNHET5.js → c-SKMIR5HQ.js} +1 -1
- package/dist/m/{c-R27DWQKX.js → c-STRI73GQ.js} +1 -1
- package/dist/m/c-SUXHUL7U.js +1 -0
- package/dist/m/c-SXGEWPFC.js +10 -0
- package/dist/m/c-SXNSSHPP.js +1 -0
- package/dist/m/c-T3P3AXEW.js +1 -0
- package/dist/m/{c-PXLTUYZC.js → c-TFLRT5JX.js} +1 -1
- package/dist/m/{c-YCLU4UNT.js → c-TGIMBAWE.js} +1 -1
- package/dist/m/c-TGSHHFO6.js +1 -0
- package/dist/m/c-U4NTUVDZ.js +1 -0
- package/dist/m/c-UOOIF2AL.js +1 -0
- package/dist/m/{c-6TYGD5AB.js → c-UOUUI265.js} +1 -1
- package/dist/m/c-V2U3F4QX.js +1 -0
- package/dist/m/c-V2V5FKXX.js +1 -0
- package/dist/m/c-VBXRXTVS.js +26 -0
- package/dist/m/c-VHSMPITY.js +1 -0
- package/dist/m/c-VPXCXBDT.js +1 -0
- package/dist/m/c-WCG6AKA7.js +1 -0
- package/dist/m/c-WNOLMHI2.js +1 -0
- package/dist/m/{c-CRPIDGXC.js → c-X6MOYATI.js} +1 -1
- package/dist/m/{c-FQGHR32O.js → c-X75G43SZ.js} +1 -1
- package/dist/m/c-XB6EAIAR.js +13 -0
- package/dist/m/c-XMIDHEKF.js +1 -0
- package/dist/m/c-XUZMRQ7O.js +4 -0
- package/dist/m/{c-2Z22FCFK.js → c-XW5TIYYB.js} +1 -1
- package/dist/m/c-YDW4VCHP.js +1 -0
- package/dist/m/{c-QGAPG7OM.js → c-YLHTD3KX.js} +1 -1
- package/dist/m/c-YNIEIPBS.js +1 -0
- package/dist/m/c-Z4G2UK6T.js +4 -0
- package/dist/m/c-Z6MROEQ4.js +1 -0
- package/dist/m/c-ZFO5RW6G.js +2 -0
- package/dist/m/c-ZGCO4J3H.js +8 -0
- package/dist/m/c-ZKKAF4UT.js +3 -0
- package/dist/m/c-ZRGEN6QQ.js +4 -0
- package/dist/m/core/{admin-attrs-U7IJNYBG.js → admin-attrs-ONRWVXPK.js} +1 -1
- package/dist/m/core/{autosave-F5YQCKVL.js → autosave-TBKAAAFB.js} +1 -1
- package/dist/m/core/conflict-notice-FOEE6I7E.js +1 -0
- package/dist/m/core/draft-mode-4MCL3LYI.js +1 -0
- package/dist/m/core/{host-permission-6EVJGNPL.js → host-permission-X4BENZEU.js} +1 -1
- package/dist/m/core/{is-edit-mode-GCX6767E.js → is-edit-mode-FFHCQ5DR.js} +1 -1
- package/dist/m/core/{persist-CJXIL7D4.js → persist-6ILUR34S.js} +1 -1
- package/dist/m/core/{recovery-DRFZRJED.js → recovery-APWADU4K.js} +1 -1
- package/dist/m/core/save-6HT2LABE.js +1 -0
- package/dist/m/core/save-core-EVMNZQ4V.js +1 -0
- package/dist/m/core/site-edits-G4BOZMV4.js +1 -0
- package/dist/m/core/{unsaved-warning-HNQVTA2D.js → unsaved-warning-ITZDIZ2I.js} +1 -1
- package/dist/m/internals/{index-NRPNDTSA.js → index-HS3CQDFW.js} +1 -1
- package/dist/m/loader-GLLVDUZA.js +1 -0
- package/dist/m/manifest-50FF1C45.js +1 -0
- package/dist/m/manifest-edit-BE1A621D.js +1 -0
- package/dist/m/manifest.json +2072 -1811
- package/dist/m/page/editor-KAZDJNND.js +307 -0
- package/dist/m/plugins/ai-edit-LABECCSN.js +6 -0
- package/dist/m/plugins/{cms-H7DTMUQX.js → cms-T2HLI7JG.js} +16 -16
- package/dist/m/plugins/{demo-42BSYQ7T.js → demo-T7B4RIKL.js} +1 -1
- package/dist/m/plugins/{derive-QVI3LI3A.js → derive-6LPJOCAV.js} +1 -1
- package/dist/m/plugins/indicator-J4ABFARX.js +1 -0
- package/dist/m/plugins/{live-DCUBKXY4.js → live-PGBIEMGB.js} +1 -1
- package/dist/m/plugins/menu-YZL3X2AJ.js +1 -0
- package/dist/m/plugins/page-WOCNX2IN.js +2 -0
- package/dist/m/plugins/page-view-SJODOOGE.js +1 -0
- package/dist/m/plugins/{people-5XRNRP5U.js → people-C4TCWFOQ.js} +1 -1
- package/dist/m/plugins/{quickcrop-WWF5I422.js → quickcrop-X5MRIBAP.js} +2 -2
- package/dist/m/plugins/richclay-Y2NV44NA.js +549 -0
- package/dist/m/plugins/sortable-XCHQG65Z.js +1 -0
- package/dist/m/plugins/{source-6BKMK4PO.js → source-YK7MO23W.js} +1 -1
- package/dist/m/plugins/{undo-3JEYK5QY.js → undo-VLRBQNPN.js} +1 -1
- package/dist/m/plugins/upgrade-NNTJBSK2.js +1 -0
- package/dist/m/plugins/{upload-TTCQRIM5.js → upload-ZKKJV76X.js} +1 -1
- package/dist/m/plugins/visitor-drafts-RTZNONHT.js +20 -0
- package/dist/m/plugins/{visitor-drafts-owner-Z5B2OQBR.js → visitor-drafts-owner-LHUHFU3L.js} +1 -1
- package/dist/m/plugins/{visitor-menu-4LQMKLPP.js → visitor-menu-FY4KM2SW.js} +1 -1
- package/dist/m/plugins/wire-M2NR354P.js +1 -0
- package/dist/m/{reader-core-POFQCKRV.js → reader-core-T2S3JC4E.js} +2 -2
- package/dist/m/sync/live-sync-Z64YREWQ.js +1 -0
- package/dist/m/ui/index-MGTWOW2C.js +1 -0
- package/dist/m/vendor/{sap-core.vendor-OA5SKL7Q.js → sap-core.vendor-QAEPMETX.js} +1 -1
- package/dist/offline-manifest.json +1250 -1082
- package/dist/server/core.cjs +132 -109
- package/dist/server/page-compaction-worker.cjs +5 -5
- package/dist/server/rooms.mjs +3 -1
- package/docs/guide.md +89 -44
- package/entries/clay-data.js +1 -1
- package/entries/sap.js +1 -1
- package/package.json +5 -3
- package/src/blockeditor/themes/content.css +70 -0
- package/src/blockeditor/themes/light-1.css +431 -0
- package/src/blockeditor/themes/light-1.source.css +270 -0
- package/src/blockeditor/themes/light-2.css +211 -0
- package/src/blockeditor/themes/light-3.css +130 -0
- package/src/blockeditor/themes/light-4.css +299 -0
- package/src/core/conflict-notice.js +28 -0
- package/src/core/draft-mode.js +6 -0
- package/src/core/host-meta.js +6 -6
- package/src/core/is-edit-mode.js +9 -3
- package/src/core/mode-support.js +3 -0
- package/src/core/mode-switch.js +4 -4
- package/src/core/mode-transition.js +3 -1
- package/src/core/page-format-wire.js +1 -1
- package/src/core/page-format.js +8 -8
- package/src/core/save-accepted.js +6 -7
- package/src/core/save-core.js +52 -23
- package/src/core/save-failure-notice.js +37 -0
- package/src/core/save-feedback.js +6 -0
- package/src/core/save-replace.js +1 -1
- package/src/core/save-size.js +2 -2
- package/src/core/save.js +78 -15
- package/src/core/site-edits.js +166 -0
- package/src/core/unsaved-warning.js +2 -2
- package/src/core/upgrade-run.js +4 -4
- package/src/lib/ai-comment.js +23 -0
- package/src/lib/effect.js +8 -0
- package/src/lib/idb-error-code.js +20 -0
- package/src/live/fast-lane.js +10 -10
- package/src/live/local-lane.js +10 -10
- package/src/live/page-compaction.js +8 -8
- package/src/live/server-lane.js +1 -1
- package/src/loader-logic.js +48 -18
- package/src/loader.js +11 -6
- package/src/page/adopt-boot.js +1 -1
- package/src/page/adopt.js +2 -2
- package/src/page/ai-area.js +182 -0
- package/src/page/ai-preview.js +80 -0
- package/src/page/ai-surface.js +257 -0
- package/src/page/ai-tool.js +308 -145
- package/src/page/announce.js +2 -2
- package/src/page/awareness.js +18 -18
- package/src/page/blocks.js +35 -34
- package/src/page/blocksel.js +2 -2
- package/src/page/board-drag.js +11 -11
- package/src/page/board-face.js +1 -1
- package/src/page/board-panel.js +26 -26
- package/src/page/board-style.js +127 -127
- package/src/page/board-view.js +48 -48
- package/src/page/chart.js +29 -29
- package/src/page/checkpoint-proof.js +19 -19
- package/src/page/chrome.js +74 -65
- package/src/page/clipboard-shape.js +1 -1
- package/src/page/csv-import.js +4 -4
- package/src/page/drag.js +5 -5
- package/src/page/editor.js +301 -296
- package/src/page/export-html.js +8 -8
- package/src/page/features/checklist.js +36 -36
- package/src/page/features/export-html.js +19 -19
- package/src/page/features/export-markdown.js +10 -10
- package/src/page/features/import-markdown.js +13 -13
- package/src/page/features/media.js +50 -50
- package/src/page/features/table.js +17 -17
- package/src/page/features/title-cover.js +17 -17
- package/src/page/features/title-icon.js +7 -7
- package/src/page/features/toc.js +1 -1
- package/src/page/features/tool.js +42 -18
- package/src/page/format-command.js +4 -4
- package/src/page/format-gate.js +6 -6
- package/src/page/format-host.js +6 -6
- package/src/page/format-level.js +3 -3
- package/src/page/format-native-commands.js +2 -2
- package/src/page/highlight-slot.js +1 -1
- package/src/page/highlight.js +4 -4
- package/src/page/history-intent.js +2 -2
- package/src/page/image-actions.js +4 -4
- package/src/page/link.js +10 -10
- package/src/page/media.js +3 -3
- package/src/page/mention-picker.js +7 -7
- package/src/page/merge.js +1 -1
- package/src/page/placeholder.js +3 -3
- package/src/page/projection.js +11 -11
- package/src/page/reader-patch.js +2 -2
- package/src/page/recover.js +26 -26
- package/src/page/recovery-ui.js +28 -28
- package/src/page/rich/chart.js +1 -1
- package/src/page/rich/page-tool.js +21 -6
- package/src/page/rich/tool-runtime.js +3 -2
- package/src/page/save-projection.js +2 -2
- package/src/page/save-states.js +9 -9
- package/src/page/schema.js +9 -9
- package/src/page/table-style.js +21 -21
- package/src/page/toc-link.js +1 -1
- package/src/page/tool-capability.js +8 -3
- package/src/page/tool-transport.js +52 -0
- package/src/page/tool-views.js +4 -0
- package/src/page/tools.js +6 -1
- package/src/page/view.js +6 -6
- package/src/page/ybind/binding.js +1 -1
- package/src/page/ybind/conversion.js +1 -1
- package/src/page/ybind/layout.js +4 -4
- package/src/page/ybind/migrate.js +1 -1
- package/src/page/ybind/origins.js +2 -2
- package/src/page/ybind/provider.js +2 -2
- package/src/page/ybind/recovery-proof.js +3 -3
- package/src/page/ybind/recovery-replay.js +2 -2
- package/src/plugins/ai-edit/picker.js +235 -0
- package/src/plugins/ai-edit.js +220 -34
- package/src/plugins/indicator-chip.js +39 -34
- package/src/plugins/indicator.js +13 -4
- package/src/plugins/live.js +21 -13
- package/src/plugins/menu/actions.js +89 -17
- package/src/plugins/menu/seo.js +32 -6
- package/src/plugins/menu/shell-style.js +26 -7
- package/src/plugins/menu/shell.js +12 -7
- package/src/plugins/menu/variants.js +6 -10
- package/src/plugins/menu/visitor.js +44 -34
- package/src/plugins/menu.js +3 -2
- package/src/plugins/page-view.js +2 -2
- package/src/plugins/page.js +257 -98
- package/src/plugins/sortable.js +7 -0
- package/src/plugins/upgrade.js +2 -1
- package/src/plugins/visitor-drafts/commit.js +303 -0
- package/src/plugins/visitor-drafts/idb.js +221 -0
- package/src/plugins/visitor-drafts/intro-style.js +1 -1
- package/src/plugins/visitor-drafts/intro.js +8 -25
- package/src/plugins/visitor-drafts/prompt.js +30 -4
- package/src/plugins/visitor-drafts/session.js +345 -0
- package/src/plugins/visitor-drafts/store.js +351 -32
- package/src/plugins/visitor-drafts.js +158 -27
- package/src/server/checkpoint/index.js +8 -8
- package/src/server/rooms/index.js +2 -2
- package/src/server/rooms/live-data-rooms.js +35 -35
- package/src/server/rooms/live-page-history.js +2 -2
- package/src/server/rooms/page-admission.js +34 -6
- package/src/server/rooms/page-conversion.js +1 -1
- package/src/server/rooms/page-lineage.js +4 -4
- package/src/server/rooms/page-rebase.js +4 -4
- package/src/server/rooms/page-room-conformance.js +14 -14
- package/src/server/rooms/page-state.js +2 -2
- package/src/server/rooms/page-write-admission.js +7 -7
- package/src/server/rooms/page-write-guard.js +3 -3
- package/src/sync/data-guard-notice.js +265 -0
- package/src/sync/live-sync.js +54 -11
- package/src/sync/page-relay.js +4 -4
- package/src/sync/presence-store.js +1 -1
- package/src/sync/presence-target.js +3 -3
- package/src/sync/presence.js +1 -1
- package/src/sync/stream.js +54 -13
- package/src/sync/wire-profile.js +8 -3
- package/src/ui/bevel.js +1 -1
- package/src/ui/index.js +9 -10
- package/src/ui/skins/cms.js +1 -1
- package/src/ui/skins/quickcrop.js +1 -1
- package/src/ui/skins/richclay.js +2 -2
- package/src/ui/toast-lazy.js +12 -3
- package/src/ui/toast.js +9 -3
- package/src/vendor/hyper-html-api-data.vendor.js +1 -1
- package/src/vendor/hyper-html-api-upgrade.vendor.js +2 -2
- package/src/vendor/hypercms.vendor.js +16 -16
- package/src/vendor/richclay.vendor.js +48 -20
- package/src/vendor/sap-core.vendor.js +1 -1
- package/tool-generation/context.md +44 -0
- package/tool-generation/system.md +23 -0
- package/dist/m/c-24PMUJIV.js +0 -2
- package/dist/m/c-2AYVYEI4.js +0 -1
- package/dist/m/c-2AZDZWCK.js +0 -1
- package/dist/m/c-2L6O5PDS.js +0 -6
- package/dist/m/c-2RYOGUQM.js +0 -2
- package/dist/m/c-2ZVOEU4M.js +0 -1
- package/dist/m/c-37NAONZA.js +0 -1
- package/dist/m/c-3GJ4SDE2.js +0 -1
- package/dist/m/c-3O7OBDCM.js +0 -1
- package/dist/m/c-5OJ7YF2N.js +0 -1
- package/dist/m/c-5WMXEKFH.js +0 -135
- package/dist/m/c-6RDOKLN7.js +0 -1
- package/dist/m/c-A4IP6MBC.js +0 -1
- package/dist/m/c-A6L3FMUS.js +0 -1
- package/dist/m/c-ATKAOH23.js +0 -1
- package/dist/m/c-B6O7AJYG.js +0 -32
- package/dist/m/c-BCGWJYXW.js +0 -1
- package/dist/m/c-BGKAT2FL.js +0 -7
- package/dist/m/c-BYTKL6UX.js +0 -1
- package/dist/m/c-DHBRA6DL.js +0 -1
- package/dist/m/c-DTMZ3ZHI.js +0 -1
- package/dist/m/c-E3ZXARWA.js +0 -1
- package/dist/m/c-EK63G3CG.js +0 -1
- package/dist/m/c-ELYIIYRS.js +0 -1
- package/dist/m/c-EXKHPOVC.js +0 -3
- package/dist/m/c-FCSZ3DC5.js +0 -20
- package/dist/m/c-FTWGYI5E.js +0 -1
- package/dist/m/c-G4PWUDD5.js +0 -1
- package/dist/m/c-GBKB5WDL.js +0 -2
- package/dist/m/c-GBTVCJXC.js +0 -1
- package/dist/m/c-GC2HICFJ.js +0 -1
- package/dist/m/c-GRWMD6O5.js +0 -1
- package/dist/m/c-HCNWJE4A.js +0 -4
- package/dist/m/c-HG2C77BM.js +0 -4
- package/dist/m/c-JGHEACGK.js +0 -1
- package/dist/m/c-JGLX6TBK.js +0 -1
- package/dist/m/c-JNRKHYWO.js +0 -1
- package/dist/m/c-KBS3UV6B.js +0 -1
- package/dist/m/c-KQ7E3PYY.js +0 -1
- package/dist/m/c-L3F5ZLIV.js +0 -1
- package/dist/m/c-L4KMT7FO.js +0 -6
- package/dist/m/c-L5VFATT6.js +0 -1
- package/dist/m/c-LCYPMULG.js +0 -1
- package/dist/m/c-LIGQYZ7M.js +0 -6
- package/dist/m/c-LKIWEPAV.js +0 -1
- package/dist/m/c-MPSKIU2I.js +0 -1
- package/dist/m/c-N7JPUPGC.js +0 -1
- package/dist/m/c-NK33MMYF.js +0 -1
- package/dist/m/c-NQLWOHLH.js +0 -10
- package/dist/m/c-NTHUTABN.js +0 -1
- package/dist/m/c-NWX3MFT4.js +0 -1
- package/dist/m/c-OKXI52N7.js +0 -1
- package/dist/m/c-OPONJ2UK.js +0 -128
- package/dist/m/c-OWQWOGYI.js +0 -1
- package/dist/m/c-P6Q2ZTFQ.js +0 -1
- package/dist/m/c-QEN6HZCA.js +0 -1
- package/dist/m/c-QIXT3Q7C.js +0 -28
- package/dist/m/c-QM5LXRDT.js +0 -9
- package/dist/m/c-QYYUVLVG.js +0 -1
- package/dist/m/c-RQRMPZXD.js +0 -1
- package/dist/m/c-SBCBXKOZ.js +0 -26
- package/dist/m/c-SKF5HL4I.js +0 -4
- package/dist/m/c-SRPC3VBE.js +0 -1
- package/dist/m/c-TANBH7FI.js +0 -1
- package/dist/m/c-THGQQDYY.js +0 -1
- package/dist/m/c-UD5JZTDL.js +0 -1
- package/dist/m/c-UDGEFWNU.js +0 -1
- package/dist/m/c-UGNV5MFB.js +0 -1
- package/dist/m/c-VIRFF5HG.js +0 -8
- package/dist/m/c-VJYEVRYK.js +0 -1
- package/dist/m/c-VQCEOGKH.js +0 -13
- package/dist/m/c-VYJ7O4RI.js +0 -34
- package/dist/m/c-W7IPVCAA.js +0 -1
- package/dist/m/c-WBGWGOM7.js +0 -1
- package/dist/m/c-WJ5DRBHT.js +0 -72
- package/dist/m/c-X5WC4PX5.js +0 -1
- package/dist/m/c-XCFQEWW6.js +0 -3
- package/dist/m/c-XGH4GS2T.js +0 -1
- package/dist/m/c-XLQ3BDMM.js +0 -1
- package/dist/m/c-ZQXNHZZT.js +0 -72
- package/dist/m/core/conflict-notice-42H4ASLA.js +0 -1
- package/dist/m/core/draft-mode-WSQAT67S.js +0 -1
- package/dist/m/core/save-ESPSQPLV.js +0 -1
- package/dist/m/core/save-core-6SQHOR6W.js +0 -1
- package/dist/m/loader-FBDE2UAB.js +0 -1
- package/dist/m/manifest-1D2F5CA5.js +0 -1
- package/dist/m/manifest-edit-F7CC5CFD.js +0 -1
- package/dist/m/page/editor-OSKOW6RS.js +0 -304
- package/dist/m/plugins/ai-edit-VV4QTINH.js +0 -6
- package/dist/m/plugins/indicator-WVPGMOZ7.js +0 -1
- package/dist/m/plugins/menu-Q4MFFQ6E.js +0 -1
- package/dist/m/plugins/page-OAWUJQEL.js +0 -2
- package/dist/m/plugins/page-view-DZ26ZDDQ.js +0 -1
- package/dist/m/plugins/richclay-PGOOJ4OK.js +0 -517
- package/dist/m/plugins/sortable-266HY6TR.js +0 -1
- package/dist/m/plugins/upgrade-BNZVFHMZ.js +0 -1
- package/dist/m/plugins/visitor-drafts-2AUYKAIJ.js +0 -20
- package/dist/m/plugins/wire-W2EV36KZ.js +0 -1
- package/dist/m/sync/live-sync-XGUPHI3I.js +0 -1
- package/dist/m/ui/index-VGYAKAKI.js +0 -1
- package/dist/page/themes/light-1.css +0 -431
- package/src/page/themes/content.css +0 -70
- package/src/page/themes/light-1.css +0 -431
- package/src/page/themes/light-1.source.css +0 -270
- package/src/page/themes/light-2.css +0 -211
- package/src/page/themes/light-3.css +0 -130
- package/src/page/themes/light-4.css +0 -299
- /package/dist/m/{c-FILLTJA7.js → c-QTTJYJMA.js} +0 -0
- /package/dist/m/plugins/{quickcrop-WWF5I422.js.LEGAL.txt → quickcrop-X5MRIBAP.js.LEGAL.txt} +0 -0
- /package/dist/m/plugins/{richclay-PGOOJ4OK.js.LEGAL.txt → richclay-Y2NV44NA.js.LEGAL.txt} +0 -0
|
@@ -1,17 +1,38 @@
|
|
|
1
1
|
import { isDraftMode, isEditMode, isOwner } from "../core/is-edit-mode.js";
|
|
2
2
|
import { hasSaveToken } from "../core/host-attrs.js";
|
|
3
|
-
import { draftSlot, setDraftTransport, setDraftFlush } from "../core/draft-mode.js";
|
|
4
|
-
import {
|
|
3
|
+
import { draftSlot, draftStoreFailed, setDraftTransport, setDraftFlush, setDraftUnconfirmed } from "../core/draft-mode.js";
|
|
4
|
+
import { reloadAfterDiscard } from "../core/unsaved-warning.js";
|
|
5
|
+
import { createStore, adoptLegacy, draftPath, legacyKey, safeStorage, storageFailure } from "./visitor-drafts/store.js";
|
|
5
6
|
import { createUiMemory } from "./visitor-drafts/ui-memory.js";
|
|
6
7
|
import { rememberSource, downloadUrl } from "./visitor-drafts/source.js";
|
|
7
8
|
import { mountPrompt } from "./visitor-drafts/prompt.js";
|
|
8
9
|
import { DOWNLOAD_URL, backToDraft, editOriginal, inertVisitorDrafts } from "./visitor-drafts/inert.js";
|
|
9
10
|
|
|
10
11
|
let store = null;
|
|
12
|
+
// This tab's side of the draft database, once it has opened. Without one, `localStorage`
|
|
13
|
+
// holds the record for this page load, as it did before the database.
|
|
14
|
+
let session = null;
|
|
15
|
+
let direct = true;
|
|
16
|
+
let database = false;
|
|
17
|
+
let opening = Promise.resolve();
|
|
18
|
+
// The database did not open and the full draft is only there: nothing is saved over it
|
|
19
|
+
// for the rest of this page load.
|
|
20
|
+
let refused = false;
|
|
11
21
|
let restoredDetail = null;
|
|
12
22
|
let downloadLink = DOWNLOAD_URL;
|
|
13
23
|
let source = null;
|
|
14
24
|
let ready = Promise.resolve();
|
|
25
|
+
// Settles once the draft is on the page. Already settled for a first visit and for a
|
|
26
|
+
// draft `localStorage` holds in full; the loader awaits it before the next plugin loads.
|
|
27
|
+
let restoring = Promise.resolve();
|
|
28
|
+
// The synchronous half of a save: false while something this tab changed is neither
|
|
29
|
+
// stored nor in its journal slot.
|
|
30
|
+
let keep = () => true;
|
|
31
|
+
// A promise while the page's last changes are not yet where the download page reads them,
|
|
32
|
+
// null once they are.
|
|
33
|
+
let settle = () => null;
|
|
34
|
+
// Past this deadline the download uses this page's current HTML.
|
|
35
|
+
const DOWNLOAD_WAIT_MS = 3000;
|
|
15
36
|
|
|
16
37
|
// The chip is a convenience: a failed import leaves the draft working without it.
|
|
17
38
|
function mountChip() {
|
|
@@ -41,29 +62,121 @@ const rulesTag = document.querySelector('script[data-rules-name~="api"]');
|
|
|
41
62
|
if (isDraftMode && slot && rulesTag) {
|
|
42
63
|
const path = draftPath();
|
|
43
64
|
const moved = adoptLegacy(slot, { path });
|
|
65
|
+
const local = safeStorage();
|
|
44
66
|
store = createStore({ path, fallback: moved === "failed" ? legacyKey(slot) : null });
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
direct = !(typeof globalThis.indexedDB?.open === "function" && globalThis.IDBKeyRange);
|
|
68
|
+
database = !direct;
|
|
69
|
+
// The session loads beside the database open, off the path of a draft `localStorage` holds
|
|
70
|
+
// in full. Until it is there a save still makes its operation and journals it.
|
|
71
|
+
opening = direct ? Promise.resolve() : import("./visitor-drafts/session.js")
|
|
72
|
+
.then((m) => m.openSession({ store, storage: local, reload: () => reloadAfterDiscard({ isCurrent: () => true, reload: () => window.location.reload() }) }))
|
|
73
|
+
.then((opened) => { session = opened; }, () => { direct = true; });
|
|
74
|
+
|
|
75
|
+
setDraftTransport((html) => {
|
|
76
|
+
const recapture = !restoredDetail;
|
|
77
|
+
return restoring.then(() => {
|
|
78
|
+
if (refused) throw storageFailure(document, new Error("the draft database did not open"));
|
|
79
|
+
if (recapture) html = window.clay.getHTML();
|
|
80
|
+
if (direct) return void store.write(html);
|
|
81
|
+
store.capture(html);
|
|
82
|
+
return opening.then(() => {
|
|
83
|
+
if (session) return session.sync().then(() => {});
|
|
84
|
+
store.write(html);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const memory = createUiMemory(store.key, local);
|
|
50
90
|
source = rememberSource(memory);
|
|
51
91
|
downloadLink = downloadUrl(source);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
92
|
+
|
|
93
|
+
function finish(result) {
|
|
94
|
+
restoredDetail = { restored: result.restored, hadDraft: result.hadDraft, parked: Boolean(result.parked), ...(result.partial && { partial: true }), ...(result.blocked && { blocked: true }) };
|
|
95
|
+
document.dispatchEvent(new CustomEvent("clay:drafts-restored", { detail: restoredDetail }));
|
|
96
|
+
const appName = document.querySelector('meta[name="application-name"]')?.content?.trim() || document.title.split(/:|\s\|\s/)[0].trim() || "This app";
|
|
97
|
+
const template = document.querySelector("template[visitor-intro]");
|
|
98
|
+
const start = () => {
|
|
99
|
+
ready = mountPrompt({ memory, appName, link: downloadLink, settle: () => settle(), canDownloadCurrent: () => !refused && !restoredDetail.blocked }).then((prompt) => mountIntroLate({ memory, template, appName, hadDraft: result.hadDraft || store.kept() }).then(() => prompt)).then((prompt) => {
|
|
100
|
+
document.addEventListener("clay:drafts-store-failed", () => prompt.showError());
|
|
101
|
+
document.addEventListener("clay:drafts-saved", () => prompt.clearError());
|
|
102
|
+
if (draftStoreFailed()) prompt.showError();
|
|
103
|
+
return (isOwner ? mountChip() : Promise.resolve(null)).then(() => prompt);
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", start, { once: true }) : start();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
keep = () => {
|
|
110
|
+
if (refused) return false;
|
|
111
|
+
if (!restoredDetail) return true;
|
|
112
|
+
if (direct) return store.flush();
|
|
113
|
+
const safe = store.keep();
|
|
114
|
+
session?.hide();
|
|
115
|
+
return safe;
|
|
116
|
+
};
|
|
117
|
+
setDraftFlush(keep);
|
|
118
|
+
setDraftUnconfirmed(() => !direct && store.unsafe());
|
|
119
|
+
settle = () => {
|
|
120
|
+
if (refused) return Promise.resolve(false);
|
|
121
|
+
if (!restoredDetail) return restoring.then(() => settle());
|
|
122
|
+
if (direct) {
|
|
123
|
+
return store.flush() ? null : Promise.resolve(false);
|
|
124
|
+
}
|
|
125
|
+
const safe = store.keep();
|
|
126
|
+
if (!store.pending()) return safe ? null : Promise.resolve(false);
|
|
127
|
+
const committed = opening.then(() => session ? session.sync().then(() => true) : store.flush()).catch(() => false);
|
|
128
|
+
let timer;
|
|
129
|
+
return Promise.race([committed, new Promise((resolve) => { timer = setTimeout(() => resolve(false), DOWNLOAD_WAIT_MS); })])
|
|
130
|
+
.finally(() => clearTimeout(timer));
|
|
60
131
|
};
|
|
61
|
-
document.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
132
|
+
document.addEventListener("visibilitychange", () => { if (document.visibilityState === "hidden") keep(); });
|
|
133
|
+
window.addEventListener("pagehide", keep);
|
|
134
|
+
|
|
135
|
+
// Another tab's save reaches a page on the database through its session. With
|
|
136
|
+
// `localStorage` as the record it arrives as that key's storage event, once the page
|
|
137
|
+
// editor listens (an event before that is missed), and when the tab comes back from the
|
|
138
|
+
// page cache.
|
|
139
|
+
const takeOtherTab = () => {
|
|
140
|
+
if (!direct || refused || !restoredDetail) return;
|
|
141
|
+
// Read before adopting: adopting reads the record as this tab's own.
|
|
142
|
+
const unit = store.externalUnit();
|
|
143
|
+
// A page another tab wrote goes to the page editor, which merges it into its own.
|
|
144
|
+
if (unit) document.dispatchEvent(new CustomEvent("clay:drafts-blockeditor-external", { detail: { live: unit.live, adopted: () => store.adoptedUnit(unit) } }));
|
|
145
|
+
store.adoptExternal();
|
|
146
|
+
};
|
|
147
|
+
window.addEventListener("storage", (event) => { if (event.key === store.key) takeOtherTab(); });
|
|
148
|
+
document.addEventListener("clay:drafts-blockeditor-listening", takeOtherTab);
|
|
149
|
+
window.addEventListener("pageshow", (event) => { if (event.persisted) takeOtherTab(); });
|
|
150
|
+
|
|
151
|
+
const held = store.held();
|
|
152
|
+
const head = held?.v === 2 && held.idb === true;
|
|
153
|
+
const foreignReset = !direct && held?.v === 1 && held.w === undefined && store.generation() !== "";
|
|
154
|
+
if (head || foreignReset || (!direct && held?.w && held.w.reset !== store.generation())) {
|
|
155
|
+
// The full draft is only in the database, or this copy is from before the last Start
|
|
156
|
+
// over: the page waits for the database's record, and plugins that read the document
|
|
157
|
+
// wait with the page.
|
|
158
|
+
restoring = opening.then(() => session?.first).then((result) => {
|
|
159
|
+
if (result) return finish(store.restore(result.record && { raw: JSON.stringify(result.record), record: result.record }));
|
|
160
|
+
throw new Error("the draft database did not open");
|
|
161
|
+
}).catch((error) => {
|
|
162
|
+
if (!head) {
|
|
163
|
+
if (!foreignReset) { try { local?.removeItem(store.key); } catch {} }
|
|
164
|
+
return finish(store.restore());
|
|
165
|
+
}
|
|
166
|
+
refused = true;
|
|
167
|
+
storageFailure(document, error);
|
|
168
|
+
finish({ restored: false, hadDraft: true, blocked: true });
|
|
169
|
+
});
|
|
170
|
+
} else {
|
|
171
|
+
finish(store.restore());
|
|
172
|
+
// The database did not open: `localStorage` is the record for this page load, and what
|
|
173
|
+
// this tab journaled while it waited is saved there.
|
|
174
|
+
opening.then(() => {
|
|
175
|
+
if (session) return;
|
|
176
|
+
store.release();
|
|
177
|
+
store.flush();
|
|
178
|
+
});
|
|
179
|
+
}
|
|
67
180
|
} else if (!slot || !rulesTag) {
|
|
68
181
|
const missing = [];
|
|
69
182
|
if (!slot) missing.push('a visitor-drafts="<slot>" attribute on <html>');
|
|
@@ -73,18 +186,33 @@ if (isDraftMode && slot && rulesTag) {
|
|
|
73
186
|
ready = mountChip();
|
|
74
187
|
}
|
|
75
188
|
|
|
76
|
-
export { ready };
|
|
189
|
+
export { ready, restoring };
|
|
77
190
|
|
|
78
191
|
export const visitorDrafts = store ? {
|
|
79
192
|
active: true,
|
|
80
193
|
slot: store.slot,
|
|
81
194
|
key: store.key,
|
|
82
|
-
hasDraft: () =>
|
|
195
|
+
hasDraft: () => store.kept(),
|
|
83
196
|
restored: () => restoredDetail,
|
|
84
|
-
saveNow
|
|
197
|
+
saveNow() {
|
|
198
|
+
if (refused || !restoredDetail) return false;
|
|
199
|
+
if (direct) return store.writeLive();
|
|
200
|
+
try { store.capture(window.clay.getHTML()); } catch { return false; }
|
|
201
|
+
session?.sync().catch(() => {});
|
|
202
|
+
return !store.unsafe();
|
|
203
|
+
},
|
|
85
204
|
reset() {
|
|
86
|
-
|
|
87
|
-
|
|
205
|
+
if (!database) {
|
|
206
|
+
store.clear();
|
|
207
|
+
window.location.reload();
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
// The generation ends in the database before the page loads again.
|
|
211
|
+
opening.then(() => {
|
|
212
|
+
if (session) return session.startOver();
|
|
213
|
+
if (direct && !refused) return store.clear();
|
|
214
|
+
throw new Error("the draft database did not open");
|
|
215
|
+
}).then(() => window.location.reload(), (error) => storageFailure(document, error));
|
|
88
216
|
return true;
|
|
89
217
|
},
|
|
90
218
|
onRestore(fn) {
|
|
@@ -98,6 +226,9 @@ export const visitorDrafts = store ? {
|
|
|
98
226
|
editOriginal,
|
|
99
227
|
backToDraft,
|
|
100
228
|
downloadUrl: () => downloadLink,
|
|
101
|
-
download: () => ready.then((prompt) => {
|
|
229
|
+
download: () => ready.then(async (prompt) => {
|
|
230
|
+
const saved = await settle();
|
|
231
|
+
return prompt?.download(saved === false);
|
|
232
|
+
}),
|
|
102
233
|
source,
|
|
103
234
|
} : inertVisitorDrafts();
|
|
@@ -9,7 +9,7 @@ import { merge3, mergeJson } from 'hyper-morph';
|
|
|
9
9
|
import { mergePageModels } from '../../page/merge.js';
|
|
10
10
|
import { documentTitle } from '../../page/document-title.js';
|
|
11
11
|
|
|
12
|
-
const PAGE = /^
|
|
12
|
+
const PAGE = /^blockeditor(\.[0-9]+)?$/;
|
|
13
13
|
const TITLE_MODE = 'clay-seo-title-mode';
|
|
14
14
|
const NAME = /^[A-Za-z0-9_.:-]{1,64}$/;
|
|
15
15
|
const attr = (node, name) => node.attrs.find((item) => item.name === name)?.value;
|
|
@@ -25,7 +25,7 @@ function provenanceOf(room, store) {
|
|
|
25
25
|
export function captureCheckpoint(room, { nodeId, roomKey }) {
|
|
26
26
|
if (!room || room.key !== roomKey || String(room.nodeId) !== String(nodeId)) throw new TypeError('The room identity changed');
|
|
27
27
|
const { epoch, seq } = room;
|
|
28
|
-
const generation = room.
|
|
28
|
+
const generation = room.blockeditorTransition?.id ?? null;
|
|
29
29
|
const stores = [...room.stores].filter(([, store]) => !store.ephemeral).map(([name, store]) => {
|
|
30
30
|
const text = serializeLive(store.value);
|
|
31
31
|
return { name, value: cloneJson(store.value), text, liveAt: stampOf(epoch, seq, text), provenance: provenanceOf(room, store) };
|
|
@@ -51,7 +51,7 @@ function inspect(html) {
|
|
|
51
51
|
if (!blocked && node.parentNode === head && node.tagName === 'meta' && (attr(node, 'name') ?? '').trim().toLowerCase() === TITLE_MODE) {
|
|
52
52
|
titleModes.push(attr(node, 'content'));
|
|
53
53
|
}
|
|
54
|
-
if (!blocked && node.tagName === 'article' && attr(node, 'data-
|
|
54
|
+
if (!blocked && node.tagName === 'article' && attr(node, 'data-blockeditor') !== undefined) articles.push(node);
|
|
55
55
|
if (!blocked && node.tagName === 'script' && attr(node, 'src') === undefined &&
|
|
56
56
|
(attr(node, 'clay') ?? '').split(/\s+/).includes('live')) {
|
|
57
57
|
const type = (attr(node, 'type') ?? '').split(';')[0].trim().toLowerCase();
|
|
@@ -81,8 +81,8 @@ function startTag(html, node, store) {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
function projection(capture) {
|
|
84
|
-
const page = capture.stores.find((store) => store.name === '
|
|
85
|
-
if (!page || capture.stores.some((store) => PAGE.test(store.name) && store.name !== '
|
|
84
|
+
const page = capture.stores.find((store) => store.name === 'blockeditor');
|
|
85
|
+
if (!page || capture.stores.some((store) => PAGE.test(store.name) && store.name !== 'blockeditor')) return { kind: 'refused', reason: 'invalid-document' };
|
|
86
86
|
try {
|
|
87
87
|
const blocks = blocksFromYValue(page.value);
|
|
88
88
|
if (Object.values(blocks).some((block) => block.t === 'tool' || (block.t === 'table' && !Object.keys(block.cols ?? {}).length))) {
|
|
@@ -91,8 +91,8 @@ function projection(capture) {
|
|
|
91
91
|
return { kind: 'ok', blocks };
|
|
92
92
|
} catch (error) {
|
|
93
93
|
if (error instanceof YDecodeError) {
|
|
94
|
-
return { kind: 'refused', reason: error.code === 'incomplete-update' || error.code === 'unresolved-update' ? 'incomplete-
|
|
95
|
-
: error.code === 'unsupported-content' ? 'unknown-
|
|
94
|
+
return { kind: 'refused', reason: error.code === 'incomplete-update' || error.code === 'unresolved-update' ? 'incomplete-blockeditor'
|
|
95
|
+
: error.code === 'unsupported-content' ? 'unknown-blockeditor-content' : 'invalid-document' };
|
|
96
96
|
}
|
|
97
97
|
throw error;
|
|
98
98
|
}
|
|
@@ -234,7 +234,7 @@ export function renderCheckpoint(currentHtml, capture, { createDocument, recover
|
|
|
234
234
|
if (!recovery && found.kind === 'adopt' && capture.adopted !== found.A && !holdsEdit(found, decoded.blocks, doc)) return { kind: 'refused', reason: 'needs-adoption' };
|
|
235
235
|
const current = currentHtml.slice(articleLoc.startTag.endOffset, articleLoc.endTag.startOffset);
|
|
236
236
|
rendered = renderBlocks(decoded.blocks, { clean: fragmentCleaner(doc) });
|
|
237
|
-
if (current !== rendered.html && holdsEnvelope(current)) return { kind: 'refused', reason: 'unknown-
|
|
237
|
+
if (current !== rendered.html && holdsEnvelope(current)) return { kind: 'refused', reason: 'unknown-blockeditor-content' };
|
|
238
238
|
}
|
|
239
239
|
catch (error) {
|
|
240
240
|
if (error instanceof YDecodeError || error instanceof SyntaxError) return { kind: 'refused', reason: 'invalid-document' };
|
|
@@ -40,8 +40,8 @@ export {
|
|
|
40
40
|
postReceipt,
|
|
41
41
|
} from './live-provenance.js';
|
|
42
42
|
|
|
43
|
-
export { isHybridPageValue, isYPageValue, inspectPage, pageBaseline, captureFormat, checkPageFormatAdmission } from './page-admission.js';
|
|
44
|
-
export { PAGE_FORMAT, PAGE_FORMAT_HEADER, PAGE_FORMAT_EXTENSION, PAGE_FORMAT_UPGRADE, formatCapability,
|
|
43
|
+
export { isHybridPageValue, isYPageValue, inspectPage, pageBaseline, captureFormat, checkPageFormatAdmission, oldBlockeditorFormat, oldFormatRefusal } from './page-admission.js';
|
|
44
|
+
export { PAGE_FORMAT, PAGE_FORMAT_HEADER, PAGE_FORMAT_EXTENSION, PAGE_FORMAT_UPGRADE, formatCapability, blockeditorFormatFloor, withPageFormatFloor } from '../../core/page-format.js';
|
|
45
45
|
|
|
46
46
|
export { splitPage, composePage, pageRev } from './page-state.js';
|
|
47
47
|
|
|
@@ -55,7 +55,7 @@ export const LIVE_LIMITS = Object.freeze({
|
|
|
55
55
|
movesBytes: 1024 * 1024,
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
const PAGE = /^
|
|
58
|
+
const PAGE = /^blockeditor(\.[0-9]+)?$/;
|
|
59
59
|
export const COMPACTOR = 'compactor';
|
|
60
60
|
const isContainer = (v) => v !== null && typeof v === 'object';
|
|
61
61
|
|
|
@@ -239,7 +239,7 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
239
239
|
lastSentAt: new Map(),
|
|
240
240
|
seen: new Map(),
|
|
241
241
|
emptySince: null,
|
|
242
|
-
...(inspectPage(html).kind === 'yjs' && {
|
|
242
|
+
...(inspectPage(html).kind === 'yjs' && { blockeditorTransition: makePageTransition() }),
|
|
243
243
|
compaction: hostMode(inspectPage(html).kind === 'yjs'),
|
|
244
244
|
});
|
|
245
245
|
|
|
@@ -249,7 +249,7 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
249
249
|
return total;
|
|
250
250
|
};
|
|
251
251
|
const heldBy = (room) => {
|
|
252
|
-
let total = (room.receiptBytes ?? 0) + (room.
|
|
252
|
+
let total = (room.receiptBytes ?? 0) + (room.blockeditorTransition?.bytes ?? 0) + (room.compaction?.job?.reserved ?? 0);
|
|
253
253
|
for (const s of room.stores.values()) total += s.bytes + s.logBytes;
|
|
254
254
|
return total;
|
|
255
255
|
};
|
|
@@ -265,7 +265,7 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
265
265
|
return {
|
|
266
266
|
limit,
|
|
267
267
|
over() {
|
|
268
|
-
if (answer.failed || (state.lastFull && state.deletesSince === 0)) return { error: '
|
|
268
|
+
if (answer.failed || (state.lastFull && state.deletesSince === 0)) return { error: 'blockeditor-full' };
|
|
269
269
|
due(room, true);
|
|
270
270
|
return { error: 'checkpoint-required', retryAfter: 0 };
|
|
271
271
|
},
|
|
@@ -342,7 +342,7 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
342
342
|
|
|
343
343
|
const pageSource = (key, nodeId) => {
|
|
344
344
|
if (nodeId == null) throw new PageConversionError('invalid-document');
|
|
345
|
-
if (pageReservations.has(key) || persistentReservations.has(key) || reservedNodes.has(String(nodeId))) throw new PageConversionError('
|
|
345
|
+
if (pageReservations.has(key) || persistentReservations.has(key) || reservedNodes.has(String(nodeId))) throw new PageConversionError('blockeditor-converting');
|
|
346
346
|
let room = rooms.get(key) ?? null;
|
|
347
347
|
if (room && String(room.nodeId) !== String(nodeId)) {
|
|
348
348
|
used -= heldBy(room);
|
|
@@ -355,7 +355,7 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
355
355
|
const reservePageTarget = (key, nodeId, room, html, details) => {
|
|
356
356
|
const receipts = new Set([...(room?.lastN.keys() ?? [])].filter((key) => PAGE.test(key.slice(0, key.indexOf('\n')))));
|
|
357
357
|
if (receipts.size >= LIVE_LIMITS.receipts ||
|
|
358
|
-
(room?.
|
|
358
|
+
(room?.blockeditorTransition?.receiptCount ?? 0) + receipts.size > LIVE_LIMITS.receipts) throw new PageConversionError('receipt-capacity');
|
|
359
359
|
const names = details.names ?? [...new Set([
|
|
360
360
|
...[...(room?.stores.keys() ?? [])].filter((name) => PAGE.test(name)),
|
|
361
361
|
...[...receipts].map((key) => key.slice(0, key.indexOf('\n'))),
|
|
@@ -373,7 +373,7 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
373
373
|
heldCount: others.length,
|
|
374
374
|
});
|
|
375
375
|
for (const store of stores.values()) store.from = null;
|
|
376
|
-
budget = transition.bytes - (room.
|
|
376
|
+
budget = transition.bytes - (room.blockeditorTransition?.historyBytes ?? 0) +
|
|
377
377
|
[...stores.values()].reduce((sum, store) => sum + store.bytes, 0);
|
|
378
378
|
const replacedBytes = [...room.stores].filter(([name]) => PAGE.test(name))
|
|
379
379
|
.reduce((sum, [, store]) => sum + store.bytes + store.logBytes, 0);
|
|
@@ -413,11 +413,11 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
413
413
|
}
|
|
414
414
|
if (!isContainer(value) || Array.isArray(value) !== Array.isArray(store.value)) continue;
|
|
415
415
|
if (PAGE.test(tag.name)) {
|
|
416
|
-
if (room.
|
|
417
|
-
if (tag.name !== '
|
|
416
|
+
if (room.blockeditorTransition?.format === 'yjs') {
|
|
417
|
+
if (tag.name !== 'blockeditor' || !isYPageValue(value)) continue;
|
|
418
418
|
} else if (isYPageValue(value, { complete: false })) continue;
|
|
419
419
|
}
|
|
420
|
-
const host = Boolean(room.compaction && tag.name === '
|
|
420
|
+
const host = Boolean(room.compaction && tag.name === 'blockeditor' && room.blockeditorTransition?.format === 'yjs');
|
|
421
421
|
const before = store.landed;
|
|
422
422
|
const base = baseOf(room, store, tag.at);
|
|
423
423
|
// A capture (stamped, and still the value its stamp names) holds no merge
|
|
@@ -548,9 +548,9 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
548
548
|
return reservePageTarget(key, nodeId, room, html, { source, base, epoch, names, format: 'yjs' });
|
|
549
549
|
},
|
|
550
550
|
|
|
551
|
-
reservePageInitial(key, { nodeId, currentHtml, html, ifMatch,
|
|
551
|
+
reservePageInitial(key, { nodeId, currentHtml, html, ifMatch, blockeditorWrite = null, createDocument }) {
|
|
552
552
|
const room = pageSource(key, nodeId);
|
|
553
|
-
checkPageInitial({ currentHtml, html, ifMatch, room,
|
|
553
|
+
checkPageInitial({ currentHtml, html, ifMatch, room, blockeditorWrite, createDocument });
|
|
554
554
|
return reservePageTarget(key, nodeId, room, html, { source: 'initial', names: [], format: 'yjs' });
|
|
555
555
|
},
|
|
556
556
|
|
|
@@ -591,7 +591,7 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
591
591
|
room.stores.set(name, store);
|
|
592
592
|
bytes += store.bytes;
|
|
593
593
|
}
|
|
594
|
-
room.
|
|
594
|
+
room.blockeditorTransition = transition;
|
|
595
595
|
if (hostLimits) room.compaction = hostMode(transition.format === 'yjs');
|
|
596
596
|
reservation.target = null;
|
|
597
597
|
used += heldBy(room) - before - reservation.budget;
|
|
@@ -662,8 +662,8 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
662
662
|
throw new PersistentReservationError('invalid-target');
|
|
663
663
|
}
|
|
664
664
|
if (PAGE.test(name)) {
|
|
665
|
-
if (record.room.
|
|
666
|
-
if (name !== '
|
|
665
|
+
if (record.room.blockeditorTransition?.format === 'yjs') {
|
|
666
|
+
if (name !== 'blockeditor' || !isYPageValue(value) || value.g !== store.value.g) {
|
|
667
667
|
throw new PersistentReservationError('invalid-target');
|
|
668
668
|
}
|
|
669
669
|
} else if (isYPageValue(value, { complete: false })) throw new PersistentReservationError('invalid-target');
|
|
@@ -839,14 +839,14 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
839
839
|
if (current === room && persistentReservations.has(room.key)) return { error: 'checkpointing' };
|
|
840
840
|
if (!isContainer(frame)) return { error: 'bad-frame' };
|
|
841
841
|
if (current !== room) return { stale: true, epoch: current?.epoch ?? null };
|
|
842
|
-
if (pageReservations.has(room.key) && PAGE.test(frame.name)) return { error: '
|
|
842
|
+
if (pageReservations.has(room.key) && PAGE.test(frame.name)) return { error: 'blockeditor-converting' };
|
|
843
843
|
// Made for an earlier room: the tab resends its ops once it has this room's state.
|
|
844
844
|
if ((PAGE.test(frame.name) || frame.epoch !== undefined) && frame.epoch !== room.epoch) return { stale: true, epoch: room.epoch };
|
|
845
|
-
const transition = PAGE.test(frame.name) ? room.
|
|
846
|
-
if (PAGE.test(frame.name) && (transition?.format === 'yjs' || frame.
|
|
847
|
-
(frame.
|
|
845
|
+
const transition = PAGE.test(frame.name) ? room.blockeditorTransition : null;
|
|
846
|
+
if (PAGE.test(frame.name) && (transition?.format === 'yjs' || frame.blockeditorGeneration !== undefined) &&
|
|
847
|
+
(frame.blockeditorGeneration ?? null) !== (transition?.id ?? null)) return { error: 'blockeditor-generation' };
|
|
848
848
|
const converted = transition?.format === 'yjs';
|
|
849
|
-
if (converted && frame.name !== '
|
|
849
|
+
if (converted && frame.name !== 'blockeditor') return { error: 'blockeditor-format' };
|
|
850
850
|
const store = room.stores.get(frame.name);
|
|
851
851
|
if (!store) return { error: 'unknown-store' };
|
|
852
852
|
if (!Number.isInteger(frame.n) || frame.n < 1) return { error: 'bad-frame' };
|
|
@@ -862,11 +862,11 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
862
862
|
return { error: 'bad-op', message: err.message };
|
|
863
863
|
}
|
|
864
864
|
if (ops.some((op) => op.path.length !== 2 || op.path[0] !== 'y' ||
|
|
865
|
-
!(op.op === 'del' || (op.op === 'set' && typeof op.value === 'string')))) return { error: '
|
|
865
|
+
!(op.op === 'del' || (op.op === 'set' && typeof op.value === 'string')))) return { error: 'blockeditor-format' };
|
|
866
866
|
}
|
|
867
867
|
const nKey = frame.name + '\n' + clientId;
|
|
868
868
|
const last = room.lastN.get(nKey);
|
|
869
|
-
const admission = room.compaction && frame.name === '
|
|
869
|
+
const admission = room.compaction && frame.name === 'blockeditor' && !receiptHas(last, frame.n) ? admit(room, store, frame, ops) : null;
|
|
870
870
|
if (admission?.error) return admission;
|
|
871
871
|
// Answered with the seq the sender's last frame landed at, so the tab knows
|
|
872
872
|
// which applied state holds it.
|
|
@@ -914,12 +914,12 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
914
914
|
}
|
|
915
915
|
if (converted && !isYPageValue(value, { complete: false })) {
|
|
916
916
|
undo();
|
|
917
|
-
return { error: '
|
|
917
|
+
return { error: 'blockeditor-format' };
|
|
918
918
|
}
|
|
919
919
|
if (!converted && PAGE.test(frame.name) && (isYPageValue(value, { complete: false }) ||
|
|
920
920
|
(hybridSource && (Object.hasOwn(value, 'y') || Object.hasOwn(value, 'v'))))) {
|
|
921
921
|
undo();
|
|
922
|
-
return { error: '
|
|
922
|
+
return { error: 'blockeditor-conversion-required' };
|
|
923
923
|
}
|
|
924
924
|
if (bytes > LIVE_LIMITS.storeBytes || roomBytes(room) - store.bytes + bytes > LIVE_LIMITS.roomBytes) {
|
|
925
925
|
undo();
|
|
@@ -957,7 +957,7 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
957
957
|
if (admission && done.length) admission.admitted(bytes);
|
|
958
958
|
const writerGone = room.writer && connected && !connected.includes(room.writer);
|
|
959
959
|
if ((!room.writer || writerGone) && (!connected || connected.includes(clientId))) room.writer = clientId;
|
|
960
|
-
return { seq: room.seq, epoch: room.epoch, name: frame.name, ops, writer: room.writer, checkpoint: pressed(store), receiptGeneration: room.receiptGeneration ?? 0, ...(transition && {
|
|
960
|
+
return { seq: room.seq, epoch: room.epoch, name: frame.name, ops, writer: room.writer, checkpoint: pressed(store), receiptGeneration: room.receiptGeneration ?? 0, ...(transition && { blockeditorGeneration: transition.id }) };
|
|
961
961
|
},
|
|
962
962
|
|
|
963
963
|
/** Everything a joining tab needs, with clientId's own receipt per store. Values are the room's own objects: serialize, never mutate. With since in this room's epoch, moves carries each store's log entries after since.seq (null where the log no longer reaches back that far). */
|
|
@@ -970,12 +970,12 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
970
970
|
}
|
|
971
971
|
const hidden = Boolean(legacy && (capable !== undefined ? capable !== hostLimits.version : clientId && legacy.has(clientId)));
|
|
972
972
|
for (const [name, s] of room.stores) {
|
|
973
|
-
if (hidden && name === '
|
|
973
|
+
if (hidden && name === 'blockeditor') continue;
|
|
974
974
|
const seen = {};
|
|
975
975
|
const mine = clientId ? room.lastN.get(name + '\n' + clientId) : null;
|
|
976
976
|
if (mine) seen[clientId] = mine.n;
|
|
977
977
|
stores[name] = { value: s.value, ephemeral: s.ephemeral, from: s.from, landed: s.landed, floor: s.floor, last: s.last, seen, ...(mine?.saved?.length && { saved: mine.saved }),
|
|
978
|
-
...(name === '
|
|
978
|
+
...(name === 'blockeditor' && room.compaction && { compaction: { v: hostLimits.version, loose: hostLimits.loose, activeBytes: hostLimits.activeBytes } }) };
|
|
979
979
|
}
|
|
980
980
|
// A client's own read (since is its { epoch, seq }, or null for a first
|
|
981
981
|
// read) carries moves: those after its seq in its own epoch, else this
|
|
@@ -983,8 +983,8 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
983
983
|
// heals other tabs already sent by their steps. A broadcast passes no since
|
|
984
984
|
// and carries none.
|
|
985
985
|
const provenance = { provenanceVersion: 2, receiptGeneration: room.receiptGeneration ?? 0 };
|
|
986
|
-
if (room.
|
|
987
|
-
provenance.
|
|
986
|
+
if (room.blockeditorTransition) {
|
|
987
|
+
provenance.blockeditorTransition = pageTransitionState(room.blockeditorTransition, clientId);
|
|
988
988
|
}
|
|
989
989
|
if (since === undefined) return { epoch: room.epoch, seq: room.seq, writer: room.writer, stores, ...provenance };
|
|
990
990
|
const from = since && since.epoch === room.epoch && Number.isInteger(since.seq) ? since.seq : 0;
|
|
@@ -1110,7 +1110,7 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
1110
1110
|
if (now() < state.job.deadline) return null;
|
|
1111
1111
|
endJob(state.job);
|
|
1112
1112
|
}
|
|
1113
|
-
const store = room.stores.get('
|
|
1113
|
+
const store = room.stores.get('blockeditor');
|
|
1114
1114
|
if (!store || pageReservations.has(room.key) || persistentReservations.has(room.key)) return null;
|
|
1115
1115
|
const plan = planCompaction(store.value.y, { overflow }, hostLimits);
|
|
1116
1116
|
if (!plan) return null;
|
|
@@ -1118,7 +1118,7 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
1118
1118
|
if (used + reserved > liveBytes) return null;
|
|
1119
1119
|
state.fence += 1;
|
|
1120
1120
|
const token = Object.freeze({ key: room.key, epoch: room.epoch, fence: state.fence });
|
|
1121
|
-
const job = { token, room, store, epoch: room.epoch, generation: room.
|
|
1121
|
+
const job = { token, room, store, epoch: room.epoch, generation: room.blockeditorTransition?.id ?? null, kind: plan.kind,
|
|
1122
1122
|
sources: new Map(plan.sources), physical: Object.keys(store.value.y), reserved, deadline: now() + hostLimits.leaseMs, deletesAtBegin: state.deletesSince };
|
|
1123
1123
|
state.job = job;
|
|
1124
1124
|
used += reserved;
|
|
@@ -1141,8 +1141,8 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
1141
1141
|
jobs.delete(token);
|
|
1142
1142
|
const { room, store } = job;
|
|
1143
1143
|
if (!endJob(job)) return { refused: 'stale-token' };
|
|
1144
|
-
if (rooms.get(room.key) !== room || room.epoch !== job.epoch || room.stores.get('
|
|
1145
|
-
(room.
|
|
1144
|
+
if (rooms.get(room.key) !== room || room.epoch !== job.epoch || room.stores.get('blockeditor') !== store ||
|
|
1145
|
+
(room.blockeditorTransition?.id ?? null) !== job.generation) return { refused: 'stale-token' };
|
|
1146
1146
|
const y = store.value.y;
|
|
1147
1147
|
const retire = job.physical.filter((k) => {
|
|
1148
1148
|
const part = PART_KEY.exec(k);
|
|
@@ -1196,8 +1196,8 @@ export function createRooms({ now = Date.now, newEpoch = randomUUID, liveBytes =
|
|
|
1196
1196
|
state.deletesSince -= job.deletesAtBegin;
|
|
1197
1197
|
}
|
|
1198
1198
|
if (pageShape(store.value.y, hostLimits).loose >= hostLimits.soft || !pageShape(store.value.y, hostLimits).normal) due(room);
|
|
1199
|
-
return { seq: room.seq, epoch: room.epoch, name: '
|
|
1200
|
-
...(room.
|
|
1199
|
+
return { seq: room.seq, epoch: room.epoch, name: 'blockeditor', ops, from: COMPACTOR, kind: job.kind, landed: store.landed,
|
|
1200
|
+
...(room.blockeditorTransition && { blockeditorGeneration: room.blockeditorTransition.id }) };
|
|
1201
1201
|
},
|
|
1202
1202
|
|
|
1203
1203
|
/** Every room's key. */
|
|
@@ -3,10 +3,10 @@ import { cloneJson, serializeLive } from './live-data-ops.js';
|
|
|
3
3
|
|
|
4
4
|
export function makePageTransition({ room = null, format = 'yjs', sources = [], receipts = new Map() } = {}) {
|
|
5
5
|
const archived = new Map([...receipts].map(([key, receipt]) => [key, cloneJson(receipt)]));
|
|
6
|
-
const history = [...(room?.
|
|
6
|
+
const history = [...(room?.blockeditorTransition?.history ?? [])];
|
|
7
7
|
if (archived.size) history.push({
|
|
8
8
|
epoch: room.epoch,
|
|
9
|
-
generation: room.
|
|
9
|
+
generation: room.blockeditorTransition?.id ?? null,
|
|
10
10
|
receiptGeneration: room.receiptGeneration ?? 0,
|
|
11
11
|
sources: [...sources],
|
|
12
12
|
receipts: archived,
|
|
@@ -5,9 +5,9 @@ import { liveTags } from './live-data-tags.js';
|
|
|
5
5
|
import { recent } from './recent.js';
|
|
6
6
|
import { project } from '../../page/projection.js';
|
|
7
7
|
import { blocksFromPageValues } from '../../page/ybind/decode.js';
|
|
8
|
-
import { pageFormatDecision,
|
|
8
|
+
import { pageFormatDecision, blockeditorFormatFloor, pageFormatRefusal, requiredPageFormat, withPageFormatFloor } from '../../page/format-level.js';
|
|
9
9
|
|
|
10
|
-
const PAGE = /^
|
|
10
|
+
const PAGE = /^blockeditor(\.[0-9]+)?$/;
|
|
11
11
|
const PART = /^(.+)\.(\d+)\.(\d+)$/;
|
|
12
12
|
const record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
13
13
|
export const isHybridPageValue = (value) => record(value) && record(value.blocks) && (value.deleted == null || record(value.deleted)) && Object.keys(value).every((key) => key === 'blocks' || key === 'deleted');
|
|
@@ -37,6 +37,34 @@ export function isYPageValue(value, { complete = true } = {}) {
|
|
|
37
37
|
return true;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
// A live tag in the format before 1.24.0 (`merge="page"` or `page.N`) holding a Yjs envelope.
|
|
41
|
+
// Hosts refuse such a write, because 1.24.0 reads none of it. An author's own live store
|
|
42
|
+
// named `page` with ordinary JSON is not the block editor's and is never refused.
|
|
43
|
+
const OLD_TAG = /^page(\.[0-9]+)?$/;
|
|
44
|
+
const OLD_FORMAT_MSG = 'This document now uses the new Block editor format, so this change was not saved. Copy any text you need from this tab, then reload.';
|
|
45
|
+
|
|
46
|
+
// Thrown by a host's save path; hosts answer it like a format refusal: 409, code and message.
|
|
47
|
+
export function oldFormatRefusal() {
|
|
48
|
+
const error = new Error(OLD_FORMAT_MSG);
|
|
49
|
+
error.name = 'BlockeditorOldFormat';
|
|
50
|
+
error.code = 'blockeditor-old-format';
|
|
51
|
+
error.statusCode = 409;
|
|
52
|
+
return error;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function oldBlockeditorFormat(html) {
|
|
56
|
+
if (typeof html !== 'string' || !/merge/i.test(html)) return false;
|
|
57
|
+
return liveTags(html, { includeDuplicates: true }).some((tag) => {
|
|
58
|
+
if (!OLD_TAG.test(tag.name)) return false;
|
|
59
|
+
try {
|
|
60
|
+
const value = parseJsonRelaxed(tag.text);
|
|
61
|
+
return record(value) && (Object.hasOwn(value, 'y') || Object.hasOwn(value, 'v'));
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
40
68
|
// Callers get their own copy of the tags, so none can change another's answer.
|
|
41
69
|
export function inspectPage(html) {
|
|
42
70
|
const answer = inspected(html);
|
|
@@ -60,7 +88,7 @@ const inspected = recent(4, (html) => {
|
|
|
60
88
|
}
|
|
61
89
|
}
|
|
62
90
|
if (tags.some(([, value]) => record(value) && (Object.hasOwn(value, 'y') || Object.hasOwn(value, 'v')))) {
|
|
63
|
-
if (tags.length !== 1 || tags[0][0] !== '
|
|
91
|
+
if (tags.length !== 1 || tags[0][0] !== 'blockeditor' || !isYPageValue(tags[0][1])) return { kind: 'invalid', reason: 'invalid-yjs', tags };
|
|
64
92
|
return { kind: 'yjs', tags };
|
|
65
93
|
}
|
|
66
94
|
return { kind: tags.every(([, value]) => isHybridPageValue(value)) ? 'hybrid' : 'other', tags };
|
|
@@ -94,16 +122,16 @@ export function captureFormat(html) {
|
|
|
94
122
|
// The floor decision for one page write, taken before anything is mutated.
|
|
95
123
|
// `floor` is the document's established floor, `required` what the captured
|
|
96
124
|
// content needs (its own marker included) and `capability` what the writer
|
|
97
|
-
// declares in X-Clay-
|
|
125
|
+
// declares in X-Clay-Blockeditor-Format. `admitted` lists the capabilities of the page
|
|
98
126
|
// editors the room has admitted, so a floor rise is refused while an editor that
|
|
99
127
|
// could not read the result is connected. On success the returned body carries
|
|
100
128
|
// the marker the room must persist, so a stale save that omitted it cannot lower
|
|
101
129
|
// the floor.
|
|
102
130
|
export function checkPageFormatAdmission({ currentHtml, html, capability = 0, admitted = [] }) {
|
|
103
|
-
const established =
|
|
131
|
+
const established = blockeditorFormatFloor(currentHtml ?? '');
|
|
104
132
|
const decision = pageFormatDecision({
|
|
105
133
|
floor: established,
|
|
106
|
-
required: Math.max(captureFormat(html),
|
|
134
|
+
required: Math.max(captureFormat(html), blockeditorFormatFloor(html ?? '')),
|
|
107
135
|
capability,
|
|
108
136
|
admitted,
|
|
109
137
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ifMatchSatisfied } from './spec-wire.js';
|
|
2
2
|
import { inspectPage, pageBaseline } from './page-admission.js';
|
|
3
3
|
|
|
4
|
-
const PAGE = /^
|
|
4
|
+
const PAGE = /^blockeditor(\.[0-9]+)?$/;
|
|
5
5
|
const BASE = /^[0-9a-f]{64}$/;
|
|
6
6
|
const EPOCH = /^[A-Za-z0-9-]{1,64}$/;
|
|
7
7
|
|