@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
package/src/core/upgrade-run.js
CHANGED
|
@@ -123,16 +123,16 @@ async function declaredRenames(htmlText) {
|
|
|
123
123
|
const PAGE_STORE = 'script[clay~="live"][merge]';
|
|
124
124
|
|
|
125
125
|
function carryPage(doc, afterDoc, article) {
|
|
126
|
-
const target = afterDoc.querySelector("article[data-
|
|
126
|
+
const target = afterDoc.querySelector("article[data-blockeditor]");
|
|
127
127
|
if (!target) return null;
|
|
128
128
|
target.replaceWith(afterDoc.importNode(article, true));
|
|
129
129
|
for (const store of doc.querySelectorAll(PAGE_STORE)) {
|
|
130
130
|
const name = store.getAttribute("merge");
|
|
131
|
-
if (name !== "
|
|
131
|
+
if (name !== "blockeditor" && !name.startsWith("blockeditor.")) continue;
|
|
132
132
|
const copy = afterDoc.importNode(store, true);
|
|
133
133
|
const existing = [...afterDoc.querySelectorAll(PAGE_STORE)].find((el) => el.getAttribute("merge") === name);
|
|
134
134
|
if (existing) existing.replaceWith(copy);
|
|
135
|
-
else afterDoc.querySelector("article[data-
|
|
135
|
+
else afterDoc.querySelector("article[data-blockeditor]").after(copy);
|
|
136
136
|
}
|
|
137
137
|
const doctype = afterDoc.doctype ? `<!DOCTYPE ${afterDoc.doctype.name}>\n` : "";
|
|
138
138
|
return doctype + afterDoc.documentElement.outerHTML;
|
|
@@ -209,7 +209,7 @@ export async function prepare(update, {
|
|
|
209
209
|
}
|
|
210
210
|
const afterDoc = new DOMParser().parseFromString(result.html, "text/html");
|
|
211
211
|
const before = upgrade.extractAll(doc).dataByName;
|
|
212
|
-
const article = doc.querySelector("article[data-
|
|
212
|
+
const article = doc.querySelector("article[data-blockeditor]");
|
|
213
213
|
if (!article && !Object.keys(before || {}).length) {
|
|
214
214
|
return { ok: false, code: "undeclared", message: UNDECLARED };
|
|
215
215
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// The comment conventions an AI request shares: the payload ceiling, the @tokens that
|
|
2
|
+
// name a context file, and whether a comment asks for the saved page.
|
|
3
|
+
|
|
4
|
+
// The request ceiling, kept below the host's 1 MiB envelope so the refusal happens
|
|
5
|
+
// here, where the message can say what to do about it.
|
|
6
|
+
export const MAX_PAYLOAD_BYTES = 900 * 1024;
|
|
7
|
+
|
|
8
|
+
// Context refs are @tokens containing a dot or slash (@notes.md, @src/x.js). Bare
|
|
9
|
+
// @words are not refs: a leading one is an engine token (@fable, @codex — routed
|
|
10
|
+
// helper-side), and mid-text ones are prose.
|
|
11
|
+
// The @ has to start a word, so an email address or a URL is prose too.
|
|
12
|
+
export function contextRefsOf(comment) {
|
|
13
|
+
return [...String(comment ?? '').matchAll(/(?<![\w\u00C0-\u024F.@\/:-])@([\w.-]*[/.][\w./-]*)/g)]
|
|
14
|
+
.map(m => m[1].replace(/\.+$/, '')) // a sentence's full stop is not part of the name
|
|
15
|
+
.filter(ref => ref !== 'page')
|
|
16
|
+
.filter(ref => /[/.]/.test(ref));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Every request that names @page reads the file from disk rather than a copy in the
|
|
20
|
+
// payload, so the caller has to save first and then ask for it by flag.
|
|
21
|
+
export function wantsPage(comment) {
|
|
22
|
+
return /(?<![\w\u00C0-\u024F.@\/:-])@page(?![\w\/-]|\.\w)/.test(String(comment ?? ''));
|
|
23
|
+
}
|
package/src/lib/effect.js
CHANGED
|
@@ -115,8 +115,15 @@ export function effect(fn) {
|
|
|
115
115
|
for (const type of undoEvents) clay.undo?.on?.(type, schedule);
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
+
// Shared values the host sent back are applied with observers paused, to keep them out of
|
|
119
|
+
// undo, so this event is the only sign they changed the page.
|
|
120
|
+
const onSiteData = (event) => {
|
|
121
|
+
if (event.detail?.applied?.length) schedule();
|
|
122
|
+
};
|
|
123
|
+
|
|
118
124
|
const offMutation = Mutation.onAnyChange({ debounce: 0, require: "observed" }, schedule);
|
|
119
125
|
document.addEventListener("clay:sync-applied", schedule);
|
|
126
|
+
document.addEventListener("clay:site-data", onSiteData);
|
|
120
127
|
document.addEventListener("clay:modechange", bindUndo);
|
|
121
128
|
bindUndo();
|
|
122
129
|
|
|
@@ -124,6 +131,7 @@ export function effect(fn) {
|
|
|
124
131
|
stopped = true;
|
|
125
132
|
offMutation();
|
|
126
133
|
document.removeEventListener("clay:sync-applied", schedule);
|
|
134
|
+
document.removeEventListener("clay:site-data", onSiteData);
|
|
127
135
|
document.removeEventListener("clay:modechange", bindUndo);
|
|
128
136
|
for (const type of undoEvents) clay.undo?.off?.(type, schedule);
|
|
129
137
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The failure code a thrown IndexedDB error means to a caller.
|
|
3
|
+
*
|
|
4
|
+
* Only one of these is about this device being full (`quota`); the rest are about
|
|
5
|
+
* the database being unreachable, refused, or mid-upgrade somewhere else. None of
|
|
6
|
+
* them is a save result, and none of them is durable.
|
|
7
|
+
*
|
|
8
|
+
* @param {*} error
|
|
9
|
+
* @returns {string}
|
|
10
|
+
*/
|
|
11
|
+
export function codeFor(error) {
|
|
12
|
+
const name = error?.name || "";
|
|
13
|
+
if (name === "QuotaExceededError" || name === "NS_ERROR_DOM_QUOTA_REACHED") return "quota";
|
|
14
|
+
if (name === "VersionError") return "unknown-schema";
|
|
15
|
+
if (name === "DataCloneError") return "uncloneable";
|
|
16
|
+
if (name === "AbortError") return "aborted";
|
|
17
|
+
if (name === "TransactionInactiveError" || name === "InvalidStateError") return "aborted";
|
|
18
|
+
if (name === "SecurityError" || name === "UnknownError" || name === "NotSupportedError") return "unavailable";
|
|
19
|
+
return "error";
|
|
20
|
+
}
|
package/src/live/fast-lane.js
CHANGED
|
@@ -24,7 +24,7 @@ export const FRAME_BYTES = 63 * 1024;
|
|
|
24
24
|
// What a host still on the old 16 KB body cap takes: a multi-op frame it
|
|
25
25
|
// answers 413 is packed again at this size for the rest of the session.
|
|
26
26
|
export const OLD_FRAME_BYTES = 15 * 1024;
|
|
27
|
-
const PAGE_STORE = /^
|
|
27
|
+
const PAGE_STORE = /^blockeditor(\.[0-9]+)?$/;
|
|
28
28
|
const BUFFER_MAX = 512;
|
|
29
29
|
const RETRY_MAX_MS = 8000;
|
|
30
30
|
const REVOKED = new Set([401, 403]);
|
|
@@ -79,7 +79,7 @@ export function createFastLane({
|
|
|
79
79
|
let synced = false;
|
|
80
80
|
let epoch = null;
|
|
81
81
|
let receiptGeneration = null;
|
|
82
|
-
let
|
|
82
|
+
let blockeditorGeneration = null;
|
|
83
83
|
let provenanceVersion = 0;
|
|
84
84
|
let seq = 0;
|
|
85
85
|
let writer = null;
|
|
@@ -202,7 +202,7 @@ export function createFastLane({
|
|
|
202
202
|
count++;
|
|
203
203
|
}
|
|
204
204
|
const take = lane.unsent.splice(0, count);
|
|
205
|
-
const frame = { n: ++lane.n, ops: take.map((entry) => entry.op), entries: take, first: take[0].id, through: take[take.length - 1].id, seq: null, sent: false, receiptGeneration,
|
|
205
|
+
const frame = { n: ++lane.n, ops: take.map((entry) => entry.op), entries: take, first: take[0].id, through: take[take.length - 1].id, seq: null, sent: false, receiptGeneration, blockeditorGeneration };
|
|
206
206
|
markAdopted(lane, frame);
|
|
207
207
|
lane.framed = Math.max(lane.framed, frame.through);
|
|
208
208
|
lane.awaiting.push(frame);
|
|
@@ -231,7 +231,7 @@ export function createFastLane({
|
|
|
231
231
|
lastSentAt = now();
|
|
232
232
|
let result;
|
|
233
233
|
try {
|
|
234
|
-
result = await transport.send(clientId, { name, n: frame.n, ops: frame.ops, epoch: sentEpoch, ...(conn !== undefined && { conn }), ...(frame.receiptGeneration !== null && { receiptGeneration: frame.receiptGeneration }), ...(frame.
|
|
234
|
+
result = await transport.send(clientId, { name, n: frame.n, ops: frame.ops, epoch: sentEpoch, ...(conn !== undefined && { conn }), ...(frame.receiptGeneration !== null && { receiptGeneration: frame.receiptGeneration }), ...(frame.blockeditorGeneration !== null && PAGE_STORE.test(name) && { blockeditorGeneration: frame.blockeditorGeneration }), ...(frame.adopted && { adopted: frame.adopted }), ...(PAGE_STORE.test(name) && lane.compaction?.v === 1 && { compaction: 1 }) });
|
|
235
235
|
} catch {
|
|
236
236
|
result = null;
|
|
237
237
|
}
|
|
@@ -255,7 +255,7 @@ export function createFastLane({
|
|
|
255
255
|
frame.sent = false;
|
|
256
256
|
frame.seq = null;
|
|
257
257
|
if (frame.epoch !== epoch) frame.receiptGeneration = receiptGeneration;
|
|
258
|
-
if (frame.epoch !== epoch) frame.
|
|
258
|
+
if (frame.epoch !== epoch) frame.blockeditorGeneration = blockeditorGeneration;
|
|
259
259
|
return;
|
|
260
260
|
}
|
|
261
261
|
// Its store left the room meanwhile: its ops stay pending for the save,
|
|
@@ -494,9 +494,9 @@ export function createFastLane({
|
|
|
494
494
|
const fresh = state.epoch !== epoch;
|
|
495
495
|
provenanceVersion = Number.isInteger(state.provenanceVersion) ? state.provenanceVersion : 0;
|
|
496
496
|
receiptGeneration = Number.isSafeInteger(state.receiptGeneration) ? state.receiptGeneration : null;
|
|
497
|
-
const priorGeneration =
|
|
498
|
-
|
|
499
|
-
const replaced =
|
|
497
|
+
const priorGeneration = blockeditorGeneration;
|
|
498
|
+
blockeditorGeneration = typeof state.blockeditorTransition?.id === "string" ? state.blockeditorTransition.id : null;
|
|
499
|
+
const replaced = blockeditorGeneration !== priorGeneration;
|
|
500
500
|
let joined = false;
|
|
501
501
|
// Every store is read whole here.
|
|
502
502
|
missed = false;
|
|
@@ -573,7 +573,7 @@ export function createFastLane({
|
|
|
573
573
|
frame.sent = false;
|
|
574
574
|
frame.seq = null;
|
|
575
575
|
frame.receiptGeneration = receiptGeneration;
|
|
576
|
-
frame.
|
|
576
|
+
frame.blockeditorGeneration = blockeditorGeneration;
|
|
577
577
|
}
|
|
578
578
|
lane.awaiting = resend;
|
|
579
579
|
entry.store.rebase(room.value, { source: "state", dropIds, room: { seq, fresh: true, moves: state.moves?.[name] ?? null, floor: Number.isInteger(room.floor) ? room.floor : null, compaction: lane.compaction } });
|
|
@@ -842,7 +842,7 @@ export function createFastLane({
|
|
|
842
842
|
const take = pending.splice(0, count);
|
|
843
843
|
const frame = { n: ++lane.n, ops: take.map((e) => e.op), entries: take,
|
|
844
844
|
first: take[0].id, through: take[take.length - 1].id, seq: null,
|
|
845
|
-
sent: false, refused: true, epoch, client: clientId, receiptGeneration,
|
|
845
|
+
sent: false, refused: true, epoch, client: clientId, receiptGeneration, blockeditorGeneration };
|
|
846
846
|
markAdopted(lane, frame);
|
|
847
847
|
lane.awaiting.push(frame);
|
|
848
848
|
lane.framed = frame.through;
|
package/src/live/local-lane.js
CHANGED
|
@@ -68,11 +68,11 @@ const encoder = new TextEncoder();
|
|
|
68
68
|
// Floor 1 moves the durable lane to its own channel scope: a tab that cannot read
|
|
69
69
|
// floor 1 never receives future-format updates, and its writes are refused by the
|
|
70
70
|
// host floor. The message protocol inside the scope is unchanged.
|
|
71
|
-
export const FORMAT_SCOPE = "
|
|
71
|
+
export const FORMAT_SCOPE = "blockeditor-format:1";
|
|
72
72
|
|
|
73
73
|
// The room scope: this lane's sequencer is a host-mode room, so a tab of the lane
|
|
74
74
|
// before it, which cannot read that room, is never on the same channel or lock.
|
|
75
|
-
export const ROOM_SCOPE = "
|
|
75
|
+
export const ROOM_SCOPE = "blockeditor-rooms:2";
|
|
76
76
|
|
|
77
77
|
export function localLaneKey(floor = 0, path = window.location.pathname) {
|
|
78
78
|
const base = "clay-live:" + path;
|
|
@@ -193,7 +193,7 @@ export function localTransport({ seed, key = "clay-live:" + window.location.path
|
|
|
193
193
|
const meta = {};
|
|
194
194
|
for (const [name, m] of mirror.stores) {
|
|
195
195
|
stores[name] = { value: cloneJson(m.value), ephemeral: m.ephemeral, seen: receiptsFor(name), last: m.last, landed: landedIn(m) };
|
|
196
|
-
if (name === "
|
|
196
|
+
if (name === "blockeditor" && isYValue(m.value)) stores[name].compaction = { v: COMPACTION.version, loose: COMPACTION.loose, activeBytes: COMPACTION.activeBytes };
|
|
197
197
|
meta[name] = { log: m.log, last: m.last, landed: m.landed, floor: m.floor, behind: m.behind, file: m.file, inFile: m.inFile };
|
|
198
198
|
}
|
|
199
199
|
const state = { epoch: room.epoch, seq: room.seq, writer: clientId, stores, mirror: { seen: [...mirror.seen], stores: meta } };
|
|
@@ -386,7 +386,7 @@ export function localTransport({ seed, key = "clay-live:" + window.location.path
|
|
|
386
386
|
const pageHost = () => ({ retired: new Set(), lastFull: null, deletesSince: 0, failed: null, ranges: createRangeCache(), task: null, overflow: false });
|
|
387
387
|
|
|
388
388
|
const pageStore = () => {
|
|
389
|
-
const m = mirror.stores.get("
|
|
389
|
+
const m = mirror.stores.get("blockeditor");
|
|
390
390
|
return m && !m.ephemeral && isYValue(m.value) ? m : null;
|
|
391
391
|
};
|
|
392
392
|
|
|
@@ -471,14 +471,14 @@ export function localTransport({ seed, key = "clay-live:" + window.location.path
|
|
|
471
471
|
host.lastFull = { bytes, seq: room.seq };
|
|
472
472
|
host.deletesSince = 0;
|
|
473
473
|
}
|
|
474
|
-
const data = { epoch: room.epoch, seq: room.seq, name: "
|
|
474
|
+
const data = { epoch: room.epoch, seq: room.seq, name: "blockeditor", ops, from: "compactor", n: 0, kind: plan.kind };
|
|
475
475
|
advance(data);
|
|
476
476
|
if (clean) m.landed = room.seq;
|
|
477
477
|
post({ type: "data", data });
|
|
478
478
|
handlers.frame(cloneJson(data));
|
|
479
479
|
if (clean) {
|
|
480
|
-
tellLanded(["
|
|
481
|
-
post({ type: "landed", name: "
|
|
480
|
+
tellLanded(["blockeditor"]);
|
|
481
|
+
post({ type: "landed", name: "blockeditor", at: { epoch: room.epoch, seq: room.seq } });
|
|
482
482
|
}
|
|
483
483
|
const shape = pageShape(m.value.y, COMPACTION);
|
|
484
484
|
if (shape.loose >= COMPACTION.soft || !shape.normal) due();
|
|
@@ -502,7 +502,7 @@ export function localTransport({ seed, key = "clay-live:" + window.location.path
|
|
|
502
502
|
return { status: 422, body: { error: err.message }, retryAfter: 0 };
|
|
503
503
|
}
|
|
504
504
|
const host = room.host;
|
|
505
|
-
const admission = frame.name === "
|
|
505
|
+
const admission = frame.name === "blockeditor" && isYValue(m.value) ? admitPageOps({ y: m.value.y, ops, state: { ...host, compaction: frame.compaction }, limits: COMPACTION }) : null;
|
|
506
506
|
if (admission?.error) {
|
|
507
507
|
if (admission.error === "checkpoint-required") due();
|
|
508
508
|
return refused(admission.error, admission.retryAfter);
|
|
@@ -516,7 +516,7 @@ export function localTransport({ seed, key = "clay-live:" + window.location.path
|
|
|
516
516
|
const bytes = admission ? pageBytes(trial.value) : 0;
|
|
517
517
|
trial.undo();
|
|
518
518
|
if (admission && bytes > admission.limit) {
|
|
519
|
-
if (admission.failed || (host.lastFull && host.deletesSince === 0)) return refused("
|
|
519
|
+
if (admission.failed || (host.lastFull && host.deletesSince === 0)) return refused("blockeditor-full");
|
|
520
520
|
due(true);
|
|
521
521
|
return refused("checkpoint-required");
|
|
522
522
|
}
|
|
@@ -572,7 +572,7 @@ export function localTransport({ seed, key = "clay-live:" + window.location.path
|
|
|
572
572
|
}
|
|
573
573
|
if (value === null || typeof value !== "object" || Array.isArray(value) !== Array.isArray(m.value)) return null;
|
|
574
574
|
const file = { seq: seq ?? null, hash: textHash(serializeLive(value)) };
|
|
575
|
-
if (name === "
|
|
575
|
+
if (name === "blockeditor" && isYValue(m.value) && isYValue(value)) return mergePageFile(m, value, file);
|
|
576
576
|
// A stamp of this room places the file. Still the value its stamp names, it
|
|
577
577
|
// is a capture and holds no merge past the stamp; edited, or unstamped, it
|
|
578
578
|
// descends from the file before it, which holds the merges in inFile.
|
|
@@ -16,8 +16,8 @@ export const COMPACTION = Object.freeze({
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
export const ROOM_ERROR_STATUS = Object.freeze({
|
|
19
|
-
'checkpoint-required': 503, '
|
|
20
|
-
'
|
|
19
|
+
'checkpoint-required': 503, 'blockeditor-full': 409, 'compaction-failed': 409,
|
|
20
|
+
'blockeditor-immutable': 409, 'blockeditor-batch': 409, 'blockeditor-update-invalid': 422, 'blockeditor-upgrade': 404,
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
export const CLIENT_KEY = /^[ur][0-9a-z]+(-[0-9a-z]+){2}$/;
|
|
@@ -98,25 +98,25 @@ export function checkUpdate(text) {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
export function admitPageOps({ y, ops, state, limits }) {
|
|
101
|
-
if (state.compaction !== limits.version) return { error: '
|
|
101
|
+
if (state.compaction !== limits.version) return { error: 'blockeditor-upgrade' };
|
|
102
102
|
let added = 0;
|
|
103
103
|
let deleted = 0;
|
|
104
104
|
let inserts = false;
|
|
105
105
|
for (const op of ops) {
|
|
106
106
|
const key = op.path?.[1];
|
|
107
|
-
if (op.op !== 'set' || typeof key !== 'string' || !CLIENT_KEY.test(key)) return { error: '
|
|
107
|
+
if (op.op !== 'set' || typeof key !== 'string' || !CLIENT_KEY.test(key)) return { error: 'blockeditor-immutable' };
|
|
108
108
|
if (Object.hasOwn(y, key)) {
|
|
109
|
-
if (y[key] !== op.value) return { error: '
|
|
109
|
+
if (y[key] !== op.value) return { error: 'blockeditor-immutable' };
|
|
110
110
|
continue;
|
|
111
111
|
}
|
|
112
|
-
if (state.retired.has(key)) return { error: '
|
|
112
|
+
if (state.retired.has(key)) return { error: 'blockeditor-immutable' };
|
|
113
113
|
const checked = checkUpdate(op.value);
|
|
114
|
-
if (!checked) return { error: '
|
|
114
|
+
if (!checked) return { error: 'blockeditor-update-invalid' };
|
|
115
115
|
added += 1;
|
|
116
116
|
deleted += checked.deleted;
|
|
117
117
|
inserts ||= checked.inserts;
|
|
118
118
|
}
|
|
119
|
-
if (added > limits.newPerFrame) return { error: '
|
|
119
|
+
if (added > limits.newPerFrame) return { error: 'blockeditor-batch' };
|
|
120
120
|
// A page whose compaction failed keeps taking edits up to its byte limit, as a room
|
|
121
121
|
// without compaction does, and refuses past it as full instead of waiting for a fold.
|
|
122
122
|
if (state.failed) return { added, deleted, inserts, limit: limits.activeBytes + (inserts ? 0 : limits.deleteReserve), shape: null, failed: true };
|
package/src/live/server-lane.js
CHANGED
|
@@ -5,7 +5,7 @@ import { PAGE_FORMAT, PAGE_FORMAT_HEADER } from "../core/page-format.js";
|
|
|
5
5
|
// one POST per frame of ops, one GET for the room's whole state. Both name the
|
|
6
6
|
// document with Document-URL, as the sync relay and presence do, and both declare
|
|
7
7
|
// the page format this client writes: registration, operation and checkpoint are
|
|
8
|
-
//
|
|
8
|
+
// blockeditor-writing paths, so each one carries the capability the floor decides on.
|
|
9
9
|
|
|
10
10
|
const DATA_PATH = "/_/sync/data";
|
|
11
11
|
|
package/src/loader-logic.js
CHANGED
|
@@ -39,10 +39,10 @@ export const PLUGIN_PATHS = {
|
|
|
39
39
|
// A live JSON tag as an in-memory object (clay.live). Not editOnly: a reader's
|
|
40
40
|
// tab still shows the game, and its writes stay in that tab.
|
|
41
41
|
live: { path: "plugins/live.js", editOnly: false, default: false },
|
|
42
|
-
|
|
43
|
-
// The reader half of `
|
|
42
|
+
blockeditor: { path: "plugins/page.js", editOnly: true, default: false },
|
|
43
|
+
// The reader half of `blockeditor`, added by resolveModules for a Document in view mode
|
|
44
44
|
// and never named in a URL: the reader's article follows each saved file.
|
|
45
|
-
"
|
|
45
|
+
"blockeditor-view": { path: "plugins/page-view.js", editOnly: false, default: false, internal: true },
|
|
46
46
|
sortable: { path: "plugins/sortable.js", editOnly: true, default: false },
|
|
47
47
|
undo: { path: "plugins/undo.js", editOnly: true, default: false },
|
|
48
48
|
unsent: { path: "plugins/unsent.js", editOnly: true, default: false },
|
|
@@ -118,6 +118,7 @@ export const MODULES = {
|
|
|
118
118
|
"core/snapshot.js": () => import("./core/snapshot.js"),
|
|
119
119
|
"core/save-core.js": () => import("./core/save-core.js"),
|
|
120
120
|
"core/save.js": () => import("./core/save.js"),
|
|
121
|
+
"core/site-edits.js": () => import("./core/site-edits.js"),
|
|
121
122
|
"core/conflict-notice.js": () => import("./core/conflict-notice.js"),
|
|
122
123
|
"core/recovery.js": () => import("./core/recovery.js"),
|
|
123
124
|
"core/unsaved-warning.js": () => import("./core/unsaved-warning.js"),
|
|
@@ -159,7 +160,7 @@ export const MODULES = {
|
|
|
159
160
|
|
|
160
161
|
// `source` is last on purpose: its install captures a save clone, so it wants every
|
|
161
162
|
// plugin that registers a document transform to have registered it first.
|
|
162
|
-
const PLUGIN_ORDER = ["visitor-drafts", "visitor-menu", "richclay", "indicator", "sortable", "undo", "unsent", "quickcrop", "upload", "people", "menu", "upgrade", "cms", "
|
|
163
|
+
const PLUGIN_ORDER = ["visitor-drafts", "visitor-menu", "richclay", "indicator", "sortable", "undo", "unsent", "quickcrop", "upload", "people", "menu", "upgrade", "cms", "blockeditor-view", "sync", "live", "blockeditor", "message", "like", "wire", "ai-edit", "demo", "derive", "bind-text", "source"];
|
|
163
164
|
|
|
164
165
|
// A plugin that cannot do its whole job alone. hypercms reads the cropper through
|
|
165
166
|
// a capability lookup (`clay.quickcrop`) and silently uploads the raw file when it
|
|
@@ -189,15 +190,22 @@ const PLUGIN_ORDER = ["visitor-drafts", "visitor-menu", "richclay", "indicator",
|
|
|
189
190
|
// The menu is how a person opens the CMS: hypercms adds no button of its own. It loads
|
|
190
191
|
// in view too, because that is where a person starts, and it shows itself only to
|
|
191
192
|
// someone who can edit. A page with its own control uses exclude=menu.
|
|
192
|
-
const IMPLIES = { cms: ["quickcrop", "upload", "menu"], "ai-edit": ["wire"],
|
|
193
|
+
const IMPLIES = { cms: ["quickcrop", "upload", "menu"], "ai-edit": ["wire"], blockeditor: ["live", "sync", "upload", "wire", "ai-edit"] };
|
|
193
194
|
|
|
194
195
|
// A Document's editor owns its article's rich text, so richclay (for [editable]) has
|
|
195
|
-
// nothing to do there; plugins=
|
|
196
|
-
const DISPLACES = {
|
|
196
|
+
// nothing to do there; plugins=blockeditor,richclay still loads it.
|
|
197
|
+
const DISPLACES = { blockeditor: ["richclay"] };
|
|
198
|
+
// A Document's editor keeps its own history, the same one Cmd+Z in the editor uses, and
|
|
199
|
+
// hyper-undo takes Cmd+Z for the whole window: loaded together the two would fight over
|
|
200
|
+
// every press. This is the one pair where a name the page asked for by hand still loses.
|
|
201
|
+
const CANNOT_RUN_WITH = { blockeditor: ["undo"] };
|
|
197
202
|
// No first paint or keystroke waits for these. source keeps the file's bytes outside
|
|
198
203
|
// the edited parts stable; a save before it installs is the ordinary full serialization.
|
|
199
204
|
// upgrade only asks the host for a newer version once `clay.ready` resolves.
|
|
205
|
+
// The editor half of `blockeditor` owns AI editing, so only a page that asked for it can wait
|
|
206
|
+
// for the wire and the composer; everywhere else they are ordinary plugins.
|
|
200
207
|
const LATE = new Set(["source", "sortable", "menu", "upgrade"]);
|
|
208
|
+
const LATE_WITH_PAGE = new Set(["wire", "ai-edit"]);
|
|
201
209
|
|
|
202
210
|
function parseCsv(params, key, enabled, apply) {
|
|
203
211
|
const raw = params.get(key);
|
|
@@ -214,8 +222,15 @@ function parseCsv(params, key, enabled, apply) {
|
|
|
214
222
|
}
|
|
215
223
|
|
|
216
224
|
// Each of these talks to the host or writes outside the visitor's own record, so a
|
|
217
|
-
// page that keeps its edits in this browser cannot carry them.
|
|
218
|
-
|
|
225
|
+
// page that keeps its edits in this browser cannot carry them. A page document keeps
|
|
226
|
+
// page and live: the editor runs on a store this browser keeps.
|
|
227
|
+
const DRAFT_DROPS = ["sync", "live", "blockeditor", "upload", "quickcrop", "cms", "people", "menu", "upgrade", "wire", "ai-edit", "demo", "source"];
|
|
228
|
+
|
|
229
|
+
// The one core module a document asks for by what it declares: shared site values are
|
|
230
|
+
// tracked only on a page whose rules tag names them, and only where a save reaches the
|
|
231
|
+
// host (a visitor's draft is saved into this browser). It goes after the save modules,
|
|
232
|
+
// which it registers with. No other document ever fetches it.
|
|
233
|
+
export const SITE_EDITS = "core/site-edits.js";
|
|
219
234
|
|
|
220
235
|
// [refetch-on-save] swaps its element for a fresh GET of the same URL after every
|
|
221
236
|
// save, which on a visitor's own draft is a request to the host. The conflict notice
|
|
@@ -225,9 +240,10 @@ const DRAFT_DROPS_CORE = ["attrs/refetch-on-save.js", "core/conflict-notice.js"]
|
|
|
225
240
|
// Only that host's copies auto-add the update check; another source names it.
|
|
226
241
|
const TEMPLATE_SOURCE_PREFIX = "https://hyperclay.com/templates/";
|
|
227
242
|
|
|
228
|
-
export function resolveModules(params, isEditMode, { isDraftMode = false, hasSap = false, hasVariants = false, canEdit = false, templateSource = null } = {}) {
|
|
243
|
+
export function resolveModules(params, isEditMode, { isDraftMode = false, hasSap = false, hasVariants = false, hasSite = false, canEdit = false, templateSource = null } = {}) {
|
|
229
244
|
const core = [...CORE_WAVES.always];
|
|
230
245
|
if (isEditMode) core.push(...CORE_WAVES.editOnly);
|
|
246
|
+
if (isEditMode && !isDraftMode && hasSite) core.splice(core.indexOf("core/save.js") + 1, 0, SITE_EDITS);
|
|
231
247
|
|
|
232
248
|
const explicit = new Set();
|
|
233
249
|
parseCsv(params, "plugins", explicit, (set, name) => set.add(name));
|
|
@@ -241,23 +257,27 @@ export function resolveModules(params, isEditMode, { isDraftMode = false, hasSap
|
|
|
241
257
|
if (hasSap) enabled.add("derive");
|
|
242
258
|
// Two or more looks need the dock's variant pill, and only someone who can edit sees it.
|
|
243
259
|
if (hasVariants && (isEditMode || canEdit)) enabled.add("menu");
|
|
244
|
-
|
|
245
|
-
if (excluded.has("wire")) enabled.delete("ai-edit");
|
|
246
|
-
if (!isEditMode && !isDraftMode && enabled.has("page")) enabled.add("page-view");
|
|
260
|
+
if (!isEditMode && !isDraftMode && enabled.has("blockeditor")) enabled.add("blockeditor-view");
|
|
247
261
|
// A plugin this mode does not load brings nothing with it, whether a default put
|
|
248
262
|
// it in the set or the tag named it. A page that wants what an edit-only plugin
|
|
249
|
-
// implies in view mode names that plugin too. `
|
|
263
|
+
// implies in view mode names that plugin too. `blockeditor` is the case that matters
|
|
250
264
|
// most: a reader loads its reader half and no generic sync, live data service or
|
|
251
|
-
// upload, so `plugins=
|
|
252
|
-
// saved transport
|
|
265
|
+
// upload, so `plugins=blockeditor` in view mode resolves to blockeditor-view alone and the
|
|
266
|
+
// saved transport blockeditor-view owns is the only stream.
|
|
253
267
|
for (const name of [...enabled]) {
|
|
254
268
|
if (excluded.has(name)) continue;
|
|
255
269
|
if (PLUGIN_PATHS[name].editOnly && !isEditMode) continue;
|
|
256
270
|
for (const implied of IMPLIES[name] || []) enabled.add(implied);
|
|
257
271
|
}
|
|
272
|
+
// ai-edit cannot run without the wire, so excluding the wire excludes it too. After
|
|
273
|
+
// the implications, or a page that implies ai-edit would re-add it without a wire.
|
|
274
|
+
if (excluded.has("wire")) enabled.delete("ai-edit");
|
|
258
275
|
for (const name of excluded) enabled.delete(name);
|
|
259
276
|
if (isDraftMode) {
|
|
260
|
-
|
|
277
|
+
// A page document keeps its editor and the store under it: a visitor edits the real
|
|
278
|
+
// page, and the save pipeline's draft transport is its only host.
|
|
279
|
+
const keepsPage = enabled.has("blockeditor");
|
|
280
|
+
for (const name of DRAFT_DROPS) if (!(keepsPage && (name === "blockeditor" || name === "live"))) enabled.delete(name);
|
|
261
281
|
for (const path of DRAFT_DROPS_CORE) {
|
|
262
282
|
const at = core.indexOf(path);
|
|
263
283
|
if (at > -1) core.splice(at, 1);
|
|
@@ -276,13 +296,23 @@ export function resolveModules(params, isEditMode, { isDraftMode = false, hasSap
|
|
|
276
296
|
if (enabled.has(owner)) for (const name of names) if (!explicit.has(name)) enabled.delete(name);
|
|
277
297
|
}
|
|
278
298
|
|
|
299
|
+
for (const [owner, names] of Object.entries(CANNOT_RUN_WITH)) {
|
|
300
|
+
if (!enabled.has(owner)) continue;
|
|
301
|
+
for (const name of names) {
|
|
302
|
+
if (!enabled.has(name)) continue;
|
|
303
|
+
enabled.delete(name);
|
|
304
|
+
console.warn(`clayjs: plugin "${name}" cannot run with "${owner}" and was left out`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
279
308
|
const plugins = [];
|
|
280
309
|
const late = [];
|
|
281
310
|
for (const name of PLUGIN_ORDER) {
|
|
282
311
|
if (!enabled.has(name)) continue;
|
|
283
312
|
const spec = PLUGIN_PATHS[name];
|
|
284
313
|
if ((spec.editOnly && !isEditMode) || (spec.draft && !isDraftMode)) continue;
|
|
285
|
-
|
|
314
|
+
const lateHere = LATE.has(name) || (LATE_WITH_PAGE.has(name) && enabled.has("blockeditor"));
|
|
315
|
+
(lateHere ? late : plugins).push(spec.ownerPath && !isDraftMode ? spec.ownerPath : spec.path);
|
|
286
316
|
}
|
|
287
317
|
return { core, plugins, late };
|
|
288
318
|
}
|
package/src/loader.js
CHANGED
|
@@ -87,10 +87,13 @@ export async function boot(base, params, readyResolve, manifest = null, suppleme
|
|
|
87
87
|
|
|
88
88
|
const hasSap = Boolean(document.querySelector("[sap]"));
|
|
89
89
|
const hasVariants = document.querySelectorAll("head style[clay-variant]").length >= 2;
|
|
90
|
+
// A page of a multi-page site names the values every page shares under `site` in its
|
|
91
|
+
// rules tag. core/site-edits.js makes the same test before it does anything.
|
|
92
|
+
const hasSite = /(?:^|[{,])\s*["']?site["']?\s*:/.test(document.querySelector('script[data-rules-name~="api"]')?.textContent ?? "");
|
|
90
93
|
// derive.js imports the sapjs core only when no sap.js script took the page over.
|
|
91
94
|
const hasSapJs = Boolean(document.querySelector('script[src*="sap.js"]'));
|
|
92
95
|
bootPhase.holdSettle = hasSap && isEditMode;
|
|
93
|
-
const plan = resolveModules(params, isEditMode, { isDraftMode, hasSap, hasVariants, canEdit, templateSource: templateSource() });
|
|
96
|
+
const plan = resolveModules(params, isEditMode, { isDraftMode, hasSap, hasVariants, hasSite, canEdit, templateSource: templateSource() });
|
|
94
97
|
// Where edits land, resolved once: a visitor draft saves into its draft slot, the
|
|
95
98
|
// demo plugin into localStorage, and everything else to the host. setMode takes no
|
|
96
99
|
// destination, and V4 has no draft-to-host transition.
|
|
@@ -127,13 +130,13 @@ export async function boot(base, params, readyResolve, manifest = null, suppleme
|
|
|
127
130
|
// Nothing is fetched for it here: the decision and the controller behind it wait for the
|
|
128
131
|
// first switch, so this costs a reader with a declaration no more than one attribute.
|
|
129
132
|
if (document.documentElement.getAttribute("clay-mode-switch") === "live") {
|
|
130
|
-
const editing = resolveModules(params, true, { isDraftMode, hasSap, hasVariants, canEdit, templateSource: templateSource() });
|
|
133
|
+
const editing = resolveModules(params, true, { isDraftMode, hasSap, hasVariants, hasSite, canEdit, templateSource: templateSource() });
|
|
131
134
|
loaded["core/edit-mode.js"].noteLiveSwitch({
|
|
132
135
|
edit: editing,
|
|
133
136
|
// The plan a view boot would have resolved, not the edit plan this boot resolved: an
|
|
134
137
|
// exit imports it and starts its reader, and an edit-only module belongs to the
|
|
135
138
|
// session being left.
|
|
136
|
-
view: resolveModules(params, false, { isDraftMode, hasSap, hasVariants, canEdit, templateSource: templateSource() }),
|
|
139
|
+
view: resolveModules(params, false, { isDraftMode, hasSap, hasVariants, hasSite, canEdit, templateSource: templateSource() }),
|
|
137
140
|
loaded,
|
|
138
141
|
modules: MODULES,
|
|
139
142
|
// A live entry attaches the edit plugins after boot: clay.plugins names them from then on.
|
|
@@ -154,7 +157,7 @@ export async function boot(base, params, readyResolve, manifest = null, suppleme
|
|
|
154
157
|
await loaded["core/edit-mode.js"].activateInitialMode();
|
|
155
158
|
window.clay.plugins = Object.freeze([...plan.plugins, ...plan.late].map((path) => PLUGIN_NAMES[path]));
|
|
156
159
|
// What an edit session here would load, so view-mode chrome can offer an edit-only feature.
|
|
157
|
-
const editPlan = isEditMode ? plan : resolveModules(params, true, { isDraftMode, hasSap, hasVariants, canEdit, templateSource: templateSource() });
|
|
160
|
+
const editPlan = isEditMode ? plan : resolveModules(params, true, { isDraftMode, hasSap, hasVariants, hasSite, canEdit, templateSource: templateSource() });
|
|
158
161
|
window.clay.editPlugins = Object.freeze([...editPlan.plugins, ...editPlan.late].map((path) => PLUGIN_NAMES[path]));
|
|
159
162
|
|
|
160
163
|
for (const path of plan.plugins) {
|
|
@@ -171,6 +174,8 @@ export async function boot(base, params, readyResolve, manifest = null, suppleme
|
|
|
171
174
|
continue;
|
|
172
175
|
}
|
|
173
176
|
loaded[path] = mod;
|
|
177
|
+
// A draft held only in the database goes onto the page before a plugin that reads the page loads.
|
|
178
|
+
if (path === "plugins/visitor-drafts.js") await mod.restoring;
|
|
174
179
|
if (path === "plugins/page-view.js") mod.start(readerOptions);
|
|
175
180
|
attachPluginMember(path, mod, loaded); // immediately, not after the loop: hypercms's ?cms=true
|
|
176
181
|
// auto-open runs as a microtask queued during ITS evaluation
|
|
@@ -209,7 +214,7 @@ const stale = (path, editMode) =>
|
|
|
209
214
|
// degrades to absent as at boot.
|
|
210
215
|
async function loadLate(paths, editMode) {
|
|
211
216
|
try {
|
|
212
|
-
await window.clay.
|
|
217
|
+
await window.clay.blockeditor?.ready;
|
|
213
218
|
} catch {}
|
|
214
219
|
for (const path of paths) {
|
|
215
220
|
if (stale(path, editMode)) continue;
|
|
@@ -357,7 +362,7 @@ function attachPluginMember(path, mod, namespaces = loaded) {
|
|
|
357
362
|
} else if (path === "plugins/live.js") {
|
|
358
363
|
clay.live = mod.live;
|
|
359
364
|
} else if (path === "plugins/page.js") {
|
|
360
|
-
clay.
|
|
365
|
+
clay.blockeditor = mod.page;
|
|
361
366
|
} else if (path === "plugins/upgrade.js") {
|
|
362
367
|
clay.save = clay.save || {};
|
|
363
368
|
clay.save.replace = mod.replace;
|
package/src/page/adopt-boot.js
CHANGED
|
@@ -201,7 +201,7 @@ export function installAdoption({
|
|
|
201
201
|
if (!outcome.ok || outcome.outcome !== "adopted") return outcome;
|
|
202
202
|
requestCheckpoint();
|
|
203
203
|
notice(outcome.counts);
|
|
204
|
-
doc.dispatchEvent(new CustomEvent("clay:
|
|
204
|
+
doc.dispatchEvent(new CustomEvent("clay:blockeditor-adopted", { detail: outcome.counts }));
|
|
205
205
|
return outcome;
|
|
206
206
|
});
|
|
207
207
|
return queue;
|
package/src/page/adopt.js
CHANGED
|
@@ -13,7 +13,7 @@ import { DEFAULTS, project, canon, revOf, parseModel, renderModel, fragmentClean
|
|
|
13
13
|
import { unflatten } from "./inline.js";
|
|
14
14
|
import { blocksFromPageValues } from "./ybind/decode.js";
|
|
15
15
|
|
|
16
|
-
export const ADOPT = "
|
|
16
|
+
export const ADOPT = "blockeditor-adopt";
|
|
17
17
|
const same = (a, b) => canon(a) === canon(b);
|
|
18
18
|
const body = ({ id, parent, ...rest }) => ({ ...rest, parent: parent ?? null });
|
|
19
19
|
|
|
@@ -85,7 +85,7 @@ const opaqueNode = (schema, id, block) => {
|
|
|
85
85
|
|
|
86
86
|
// The editor’s blocks as the article and the record state them: an opaque node
|
|
87
87
|
// stands for the record it carries, never for a block type of its own.
|
|
88
|
-
const recordsOf = (blocks) => Object.fromEntries(Object.entries(blocks).map(([id, b]) => {
|
|
88
|
+
export const recordsOf = (blocks) => Object.fromEntries(Object.entries(blocks).map(([id, b]) => {
|
|
89
89
|
if (b.t !== "opaque_block") return [id, b];
|
|
90
90
|
const { t: _t, raw, ...rest } = b;
|
|
91
91
|
const { t, ...fields } = raw ?? {};
|