@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/plugins/page.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { FEATURES, featureForNode, featureForType } from "../page/features/registry.js";
|
|
2
2
|
import { mergeEngine } from "../sync/merge-engine.js";
|
|
3
3
|
import { registerUnsavedState } from "../lib/unsaved-state.js";
|
|
4
|
+
import Mutation from "../lib/mutation.js";
|
|
4
5
|
import { hostMeta } from "../core/host-meta.js";
|
|
5
|
-
import { savePageForce, holdAllSaves, releaseAllSaves, suspendAutosave, resumeAutosave, getLastSavedBytes, savesAdmitted } from "../core/save.js";
|
|
6
|
+
import { savePageForce, holdAllSaves, releaseAllSaves, suspendAutosave, resumeAutosave, getLastSavedBytes, savesAdmitted, claimFailure } from "../core/save.js";
|
|
6
7
|
import { setSaveHeader, onSaveAccepted } from "../core/save-core.js";
|
|
7
8
|
import { lastSeenEtag, representedEtag, recordEtag } from "../core/etag.js";
|
|
8
9
|
import { captureForSaveAndComparison } from "../core/snapshot.js";
|
|
@@ -15,19 +16,23 @@ import { claim } from "../page/runtime-owner.js";
|
|
|
15
16
|
import { authorIdentity } from "../page/ybind/author.js";
|
|
16
17
|
import { createInstall, disposeInstall, addDisposer, finishError, isCurrent, ownsView, runBoot, activateInput } from "../page/boot.js";
|
|
17
18
|
import { capabilityAdvertised } from "../page/tool-capability.js";
|
|
18
|
-
import { startsAtImport } from "../core/is-edit-mode.js";
|
|
19
|
+
import { startsAtImport, isDraftMode } from "../core/is-edit-mode.js";
|
|
19
20
|
import { toolViews } from "../page/tool-views.js";
|
|
20
21
|
import { style } from "../lib/hostile-css.js";
|
|
22
|
+
import { addViewHook } from "../page/view-hooks.js";
|
|
23
|
+
import { previewShown } from "../page/ai-preview.js";
|
|
21
24
|
|
|
22
|
-
const ARTICLE_SEL = "article[data-
|
|
23
|
-
const TAG_SEL = 'script[type="application/json"][merge="
|
|
25
|
+
const ARTICLE_SEL = "article[data-blockeditor]";
|
|
26
|
+
const TAG_SEL = 'script[type="application/json"][merge="blockeditor"][clay~="live"]';
|
|
24
27
|
const BLOCKED = { state: "blocked", code: "unsupported-host", message: "This host does not support page editing" };
|
|
25
28
|
const STORE_TEXT = "Changes cannot be stored on this device. Download a copy to keep them.";
|
|
26
29
|
const RETRY_BOUND_MS = 45000;
|
|
27
30
|
const GENERIC_ERROR = "Couldn't save";
|
|
28
|
-
const GENERIC_OFFLINE = "Offline,
|
|
31
|
+
const GENERIC_OFFLINE = "Offline, will retry";
|
|
29
32
|
const REPLACED_TEXT = "This page was changed outside the editor. Reload to see it.";
|
|
30
33
|
const TOO_LARGE_TEXT = "That change is too large to share, so it was not saved. Reload the page to keep editing.";
|
|
34
|
+
const VISITOR_MEDIA = "Download your copy to add pictures and files.";
|
|
35
|
+
const draftUpload = (file, opts) => Promise.resolve({ ok: false, code: "draft", msg: VISITOR_MEDIA });
|
|
31
36
|
const CLIENT_KEY = /^[ur][0-9a-z]+(-[0-9a-z]+){2}$/;
|
|
32
37
|
export const PAGE_FULL_MESSAGE = "This document is full, though your changes are still here to download. To edit again, reload this tab after another editor, or the owner in another browser, deletes content.";
|
|
33
38
|
const OVERWRITTEN_TEXT = "Some changes you made were replaced by a change made outside the editor.";
|
|
@@ -46,11 +51,11 @@ const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArr
|
|
|
46
51
|
function unreadablePage(value) {
|
|
47
52
|
if (!isPlainObject(value)) {
|
|
48
53
|
console.warn("clayjs: page plugin: the page tag is not JSON, leaving the page as saved HTML");
|
|
49
|
-
return "invalid-
|
|
54
|
+
return "invalid-blockeditor-json";
|
|
50
55
|
}
|
|
51
56
|
if (value.v !== 1 || !isPlainObject(value.y) || Object.hasOwn(value, "blocks") || Object.hasOwn(value, "deleted")) {
|
|
52
57
|
console.warn("clayjs: page plugin: the page tag is not a strict Yjs page, leaving the page as saved HTML");
|
|
53
|
-
return "unsupported-
|
|
58
|
+
return "unsupported-blockeditor-format";
|
|
54
59
|
}
|
|
55
60
|
return null;
|
|
56
61
|
}
|
|
@@ -98,7 +103,7 @@ function armLoadingStatus(inst, article) {
|
|
|
98
103
|
inst.statusTimer = null;
|
|
99
104
|
if (!isCurrent(inst)) return;
|
|
100
105
|
const note = article.ownerDocument.createElement("div");
|
|
101
|
-
note.className = "
|
|
106
|
+
note.className = "blockeditor-boot-status";
|
|
102
107
|
note.setAttribute("clay", "editor-ui");
|
|
103
108
|
note.setAttribute("sap-ignore", "");
|
|
104
109
|
note.setAttribute("role", "status");
|
|
@@ -120,6 +125,22 @@ let ended = null;
|
|
|
120
125
|
|
|
121
126
|
// Shorter than installAdoption's 10 s hold, so an unelected room still adopts.
|
|
122
127
|
export const WRITER_WAIT_MS = 8_000;
|
|
128
|
+
// ai-edit loads late, after the editor takes input, so the first /ai or Revise can
|
|
129
|
+
// arrive before `clay.aiEdit` exists. The wait is bounded like the menu's own.
|
|
130
|
+
const AI_EDIT_WAIT_MS = 8_000;
|
|
131
|
+
const AI_UNAVAILABLE = "AI tool making isn't available on this page right now.";
|
|
132
|
+
|
|
133
|
+
function whenPresent(get, ms) {
|
|
134
|
+
return new Promise((resolve) => {
|
|
135
|
+
const start = Date.now();
|
|
136
|
+
const tick = () => {
|
|
137
|
+
const value = get();
|
|
138
|
+
if (value || Date.now() - start >= ms) resolve(value || null);
|
|
139
|
+
else setTimeout(tick, 50);
|
|
140
|
+
};
|
|
141
|
+
tick();
|
|
142
|
+
});
|
|
143
|
+
}
|
|
123
144
|
|
|
124
145
|
function scrollToHashBlock(view, loc = globalThis.location) {
|
|
125
146
|
const raw = loc?.hash?.slice(1);
|
|
@@ -133,7 +154,7 @@ function scrollToHashBlock(view, loc = globalThis.location) {
|
|
|
133
154
|
}
|
|
134
155
|
|
|
135
156
|
function dispatchState(state, code = null, message = "") {
|
|
136
|
-
document.dispatchEvent(new CustomEvent("clay:
|
|
157
|
+
document.dispatchEvent(new CustomEvent("clay:blockeditor-save-state", { detail: { state, code, message } }));
|
|
137
158
|
}
|
|
138
159
|
|
|
139
160
|
function dismissGeneric() {
|
|
@@ -183,7 +204,7 @@ function showBlocked(inst, code, message, state = "blocked") {
|
|
|
183
204
|
toastPersistent(state === "unavailable" ? STORE_TEXT : BLOCKED_TEXT, "error", {
|
|
184
205
|
detail: message,
|
|
185
206
|
actions: [
|
|
186
|
-
{ label: "Download
|
|
207
|
+
{ label: "Download copy", onClick: downloadPageCopy },
|
|
187
208
|
{ label: "Copy text", onClick: copyPageText },
|
|
188
209
|
],
|
|
189
210
|
});
|
|
@@ -275,16 +296,58 @@ function listen(inst, type, handler) {
|
|
|
275
296
|
inst.listeners.push([type, handler]);
|
|
276
297
|
}
|
|
277
298
|
|
|
299
|
+
// The page's own history, the one the editor's Cmd+Z already uses: nothing is undoable
|
|
300
|
+
// while a proposal is only a preview, because Discard is the whole of that change and
|
|
301
|
+
// Keep is the one step worth undoing.
|
|
302
|
+
function pageHistory(inst) {
|
|
303
|
+
const binding = inst?.binding;
|
|
304
|
+
const um = binding?.bridge?.um;
|
|
305
|
+
if (!binding || !um) return null;
|
|
306
|
+
const showing = () => {
|
|
307
|
+
const view = inst.view;
|
|
308
|
+
return Boolean(view && !view.isDestroyed && previewShown(view.state));
|
|
309
|
+
};
|
|
310
|
+
const focus = () => inst.view?.focus?.();
|
|
311
|
+
return {
|
|
312
|
+
undo: () => {
|
|
313
|
+
if (binding.undo()) focus();
|
|
314
|
+
},
|
|
315
|
+
redo: () => {
|
|
316
|
+
if (binding.redo()) focus();
|
|
317
|
+
},
|
|
318
|
+
get canUndo() {
|
|
319
|
+
return um.undoStack.length > 0 && !showing();
|
|
320
|
+
},
|
|
321
|
+
get canRedo() {
|
|
322
|
+
return um.redoStack.length > 0 && !showing();
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// One event for whatever presents the history, the same one plugins/undo.js emits: after
|
|
328
|
+
// every entry the page's stack takes or replays, and whenever a preview comes up or goes.
|
|
329
|
+
function notifyHistory(inst) {
|
|
330
|
+
const history = pageHistory(inst);
|
|
331
|
+
document.dispatchEvent(new CustomEvent("clay:undo-change", {
|
|
332
|
+
detail: { canUndo: !!history?.canUndo, canRedo: !!history?.canRedo },
|
|
333
|
+
}));
|
|
334
|
+
}
|
|
335
|
+
|
|
278
336
|
function listenForSaves(inst) {
|
|
337
|
+
// Retrying and blocked carry the editor's own notices, so core adds no generic one on
|
|
338
|
+
// top; a plain error is left to the generic "Couldn't save".
|
|
339
|
+
const reportFailure = (event, state) => {
|
|
340
|
+
if (state !== "error") claimFailure(event);
|
|
341
|
+
applySaveState(inst, state, event.detail?.code ?? null, event.detail?.msg ?? "");
|
|
342
|
+
};
|
|
279
343
|
listen(inst, "clay:save-error", (event) => {
|
|
280
344
|
const code = event.detail?.code ?? null;
|
|
281
345
|
// The host holds exactly what this tab would have written: nothing to report.
|
|
282
|
-
if (code === "
|
|
283
|
-
|
|
346
|
+
if (code === "blockeditor-nothing-to-write") return claimFailure(event);
|
|
347
|
+
reportFailure(event, saveStateFor({ code }));
|
|
284
348
|
});
|
|
285
349
|
listen(inst, "clay:save-offline", (event) => {
|
|
286
|
-
|
|
287
|
-
applySaveState(inst, saveStateFor({ code, offline: true }), code, event.detail?.msg ?? "");
|
|
350
|
+
reportFailure(event, saveStateFor({ code: event.detail?.code ?? null, offline: true }));
|
|
288
351
|
});
|
|
289
352
|
listen(inst, "clay:save-conflict", (event) => {
|
|
290
353
|
const code = event.detail?.code ?? null;
|
|
@@ -299,7 +362,7 @@ function listenForSaves(inst) {
|
|
|
299
362
|
inst.adoption?.saved(getLastSavedBytes());
|
|
300
363
|
});
|
|
301
364
|
listen(inst, "clay:live-notice", (event) => {
|
|
302
|
-
if (event.detail?.name !== "
|
|
365
|
+
if (event.detail?.name !== "blockeditor") return;
|
|
303
366
|
if (event.detail?.code === "op-too-large") return;
|
|
304
367
|
if (event.detail?.code === "unknown-store") {
|
|
305
368
|
showReplaced(inst);
|
|
@@ -312,6 +375,7 @@ function listenForSaves(inst) {
|
|
|
312
375
|
|
|
313
376
|
// The page the host stores now, including any merge state it keeps beside the file.
|
|
314
377
|
async function readStoredArticle() {
|
|
378
|
+
if (isDraftMode) return null;
|
|
315
379
|
const response = await fetch(new URL(globalThis.location.href).href, { cache: "no-store" });
|
|
316
380
|
if (!response?.ok) return null;
|
|
317
381
|
const html = await response.text();
|
|
@@ -335,7 +399,7 @@ async function retryFirstWrite(inst, event) {
|
|
|
335
399
|
const staged = inst.staged;
|
|
336
400
|
if (!staged || inst.staleRetry || inst.replaced) return;
|
|
337
401
|
const refusal = event?.detail?.code ?? null;
|
|
338
|
-
if (refusal !== "
|
|
402
|
+
if (refusal !== "blockeditor-lineage-stale" && refusal !== "conflict") return;
|
|
339
403
|
if (!inst.store || !staged.isPending()) return;
|
|
340
404
|
inst.staleRetry = true;
|
|
341
405
|
try {
|
|
@@ -387,15 +451,13 @@ function releaseMounted(inst) {
|
|
|
387
451
|
inst.adoption = null;
|
|
388
452
|
try { inst.recover?.destroy(); } catch (error) { console.error("clayjs: page plugin recover list failed to dispose", error); }
|
|
389
453
|
inst.recover = null;
|
|
390
|
-
inst.aiTool?.dispose();
|
|
391
|
-
inst.aiTool = null;
|
|
392
454
|
inst.awareness?.dispose();
|
|
393
455
|
inst.awareness = null;
|
|
394
456
|
inst.toolHost?.dispose();
|
|
395
457
|
inst.toolHost = null;
|
|
396
458
|
if (inst.view) toolViews.delete(inst.view);
|
|
397
459
|
if (inst.article) {
|
|
398
|
-
const shell = inst.article.querySelector(".
|
|
460
|
+
const shell = inst.article.querySelector(".blockeditor-shell");
|
|
399
461
|
if (shell) shell.remove();
|
|
400
462
|
}
|
|
401
463
|
try { inst.binding?.dispose(); } catch (error) { console.error("clayjs: page plugin binding failed to dispose", error); }
|
|
@@ -403,7 +465,7 @@ function releaseMounted(inst) {
|
|
|
403
465
|
try { inst.staged?.close(); } catch (error) { console.error("clayjs: page plugin staged store failed to close", error); }
|
|
404
466
|
inst.staged = null;
|
|
405
467
|
if (inst.pageStore) {
|
|
406
|
-
try { closeStore("
|
|
468
|
+
try { closeStore("blockeditor", inst.pageStore); } catch (error) { console.error("clayjs: page plugin store failed to close", error); }
|
|
407
469
|
inst.pageStore = null;
|
|
408
470
|
}
|
|
409
471
|
inst.binding = null;
|
|
@@ -433,7 +495,7 @@ function dispose() {
|
|
|
433
495
|
generation += 1;
|
|
434
496
|
const inst = active;
|
|
435
497
|
active = null;
|
|
436
|
-
setSaveHeader("
|
|
498
|
+
setSaveHeader("Blockeditor-Write", null);
|
|
437
499
|
registerPageCheckpoints(null);
|
|
438
500
|
if (!inst) return;
|
|
439
501
|
disposeInstall(inst);
|
|
@@ -479,11 +541,12 @@ function pageRecovery() {
|
|
|
479
541
|
return recoveryModules;
|
|
480
542
|
}
|
|
481
543
|
|
|
544
|
+
// A visitor's draft has no recovery identity on this host: its record in this browser is the copy.
|
|
482
545
|
// What this session journals under, or null when this page cannot pin an identity or this
|
|
483
546
|
// host has no recovery service: the document is then edited with no local copy, as before.
|
|
484
547
|
async function openJournal(meta, store) {
|
|
485
548
|
const service = globalThis.window?.clay?.recovery ?? null;
|
|
486
|
-
if (!service || !store) return null;
|
|
549
|
+
if (isDraftMode || !service || !store) return null;
|
|
487
550
|
const modules = await pageRecovery();
|
|
488
551
|
if (!modules) return null;
|
|
489
552
|
try {
|
|
@@ -521,7 +584,7 @@ async function replayJournal(inst) {
|
|
|
521
584
|
function installRecoveryReview(inst, ui) {
|
|
522
585
|
if (inst.recoveryPanel) return inst.recoveryPanel;
|
|
523
586
|
const view = inst.view;
|
|
524
|
-
const mount = view?.dom.closest(".
|
|
587
|
+
const mount = view?.dom.closest(".blockeditor-shell") ?? inst.article;
|
|
525
588
|
if (!mount) return null;
|
|
526
589
|
const review = ui.createRecoveryReview({
|
|
527
590
|
mount,
|
|
@@ -581,6 +644,23 @@ function installJournalStatus(inst, modules) {
|
|
|
581
644
|
return inst.pageStatus;
|
|
582
645
|
}
|
|
583
646
|
|
|
647
|
+
// The page store the session mounts, brought to the file the reader holds. A page store's
|
|
648
|
+
// value is a set of Yjs updates and this tab's own unsaved ops are updates of their own, so
|
|
649
|
+
// installing the file's value as the store's own leaves the unconfirmed work replayed over
|
|
650
|
+
// the page the file states; the binding that mounts next applies both sets in one Y
|
|
651
|
+
// document, which is the merge. False when the store cannot take the value, and the entry
|
|
652
|
+
// refuses rather than mounting a page the file does not state.
|
|
653
|
+
function mergeRepresentedPage(store, value) {
|
|
654
|
+
if (unreadablePage(value)) return false;
|
|
655
|
+
if (serializeLive(store.authoritative) === serializeLive(value)) return true;
|
|
656
|
+
try {
|
|
657
|
+
return store.rebase(value, { source: "state" });
|
|
658
|
+
} catch (error) {
|
|
659
|
+
console.warn("clayjs: page plugin could not reconcile the open page store with the saved file", error);
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
584
664
|
export function createSteps(inst, options) {
|
|
585
665
|
let meta = null;
|
|
586
666
|
let binding = null;
|
|
@@ -591,7 +671,7 @@ export function createSteps(inst, options) {
|
|
|
591
671
|
let featuresReady = null;
|
|
592
672
|
let savedContent = [];
|
|
593
673
|
let toolsStore = null;
|
|
594
|
-
let
|
|
674
|
+
let pageAiReady = null;
|
|
595
675
|
let toolsOpening = null;
|
|
596
676
|
let chrome = null;
|
|
597
677
|
let setEditorActive = null;
|
|
@@ -642,7 +722,7 @@ export function createSteps(inst, options) {
|
|
|
642
722
|
// A document with a tool block, at boot or when one arrives, opens the store its
|
|
643
723
|
// tools keep state in and the host that mounts them. A document with no tool
|
|
644
724
|
// never waits for either.
|
|
645
|
-
const TOOLS_SEL = 'script[type="application/json"][merge="
|
|
725
|
+
const TOOLS_SEL = 'script[type="application/json"][merge="blockeditor.tools"][clay~="live"]';
|
|
646
726
|
const openTools = () => {
|
|
647
727
|
if (toolsOpening) return toolsOpening;
|
|
648
728
|
toolsOpening = (async () => {
|
|
@@ -652,14 +732,14 @@ export function createSteps(inst, options) {
|
|
|
652
732
|
if (!document.querySelector(TOOLS_SEL)) {
|
|
653
733
|
const tools = document.createElement("script");
|
|
654
734
|
tools.setAttribute("type", "application/json");
|
|
655
|
-
tools.setAttribute("merge", "
|
|
735
|
+
tools.setAttribute("merge", "blockeditor.tools");
|
|
656
736
|
tools.setAttribute("clay", "live");
|
|
657
737
|
tools.textContent = "{}";
|
|
658
738
|
document.head.appendChild(tools);
|
|
659
739
|
}
|
|
660
740
|
if (document.querySelector(TOOLS_SEL)) {
|
|
661
741
|
try {
|
|
662
|
-
toolsStore = window.clay.live("
|
|
742
|
+
toolsStore = window.clay.live("blockeditor.tools");
|
|
663
743
|
await toolsStore.ready;
|
|
664
744
|
} catch (error) {
|
|
665
745
|
console.warn("clayjs: page plugin could not open the tool store", error);
|
|
@@ -676,13 +756,6 @@ export function createSteps(inst, options) {
|
|
|
676
756
|
tables: () => tablesFromDoc(editorView?.state.doc ?? binding.doc()).map(({ id, cols, rows }) => ({ id, cols, rows })),
|
|
677
757
|
});
|
|
678
758
|
inst.toolHost = toolHost;
|
|
679
|
-
if (capabilityAdvertised(meta)) {
|
|
680
|
-
const { createAiTool } = await import("../page/ai-tool.js");
|
|
681
|
-
if (!isCurrent(inst)) return null;
|
|
682
|
-
const transport = window.clay?.wire?.pageTool ?? options.transport ?? null;
|
|
683
|
-
aiTool = createAiTool({ getView: () => inst.view, store: toolsStore, transport, acceptState: options.acceptState });
|
|
684
|
-
inst.aiTool = aiTool;
|
|
685
|
-
}
|
|
686
759
|
return toolHost;
|
|
687
760
|
})().catch((error) => {
|
|
688
761
|
toolsOpening = null;
|
|
@@ -694,7 +767,7 @@ export function createSteps(inst, options) {
|
|
|
694
767
|
// The document's own switches travel with it: the gear panel writes them into the
|
|
695
768
|
// page's live store, and a document that never touched one opens no store at all.
|
|
696
769
|
// Turning a switch off hides a tag; the owner and due values stay in the file.
|
|
697
|
-
const SETTINGS_SEL = 'script[type="application/json"][merge="
|
|
770
|
+
const SETTINGS_SEL = 'script[type="application/json"][merge="blockeditor.settings"][clay~="live"]';
|
|
698
771
|
const SETTING_KEYS = ["assignees", "dates"];
|
|
699
772
|
let settingsOpening = null;
|
|
700
773
|
let settingsValue = { assignees: true, dates: true };
|
|
@@ -729,14 +802,14 @@ export function createSteps(inst, options) {
|
|
|
729
802
|
if (!document.querySelector(SETTINGS_SEL)) {
|
|
730
803
|
const tag = document.createElement("script");
|
|
731
804
|
tag.setAttribute("type", "application/json");
|
|
732
|
-
tag.setAttribute("merge", "
|
|
805
|
+
tag.setAttribute("merge", "blockeditor.settings");
|
|
733
806
|
tag.setAttribute("clay", "live");
|
|
734
807
|
tag.textContent = "{}";
|
|
735
808
|
document.head.appendChild(tag);
|
|
736
809
|
}
|
|
737
810
|
let handle = null;
|
|
738
811
|
try {
|
|
739
|
-
handle = window.clay.live("
|
|
812
|
+
handle = window.clay.live("blockeditor.settings");
|
|
740
813
|
await handle.ready;
|
|
741
814
|
} catch (error) {
|
|
742
815
|
console.warn("clayjs: page plugin could not open the settings store", error);
|
|
@@ -786,13 +859,23 @@ export function createSteps(inst, options) {
|
|
|
786
859
|
});
|
|
787
860
|
};
|
|
788
861
|
inst.notices = notices;
|
|
789
|
-
// Making or revising a tool opens the store and the host first, so the panel
|
|
790
|
-
//
|
|
862
|
+
// Making or revising a tool opens the store and the host first, so the panel keeps
|
|
863
|
+
// what it makes; the panel itself is ai-edit's, on a target this page builds.
|
|
791
864
|
const openAi = async ({ target = null, remove = null } = {}) => {
|
|
792
865
|
if (chrome && editorView && remove) chrome.holdSlashRange(editorView, remove);
|
|
793
866
|
await features.ensure("tool").catch(() => {});
|
|
794
|
-
if (!isCurrent(inst) || !
|
|
795
|
-
|
|
867
|
+
if (!isCurrent(inst) || !editorView) return;
|
|
868
|
+
const held = remove ? chrome.heldSlashRange(editorView.state) : null;
|
|
869
|
+
const surface = await (pageAiReady ?? Promise.resolve(null));
|
|
870
|
+
if (!surface || !isCurrent(inst)) return;
|
|
871
|
+
const aiTarget = target ? surface.reviseTool(target.toolId) : surface.newTool(held);
|
|
872
|
+
const ai = window.clay?.aiEdit || await whenPresent(() => window.clay?.aiEdit, AI_EDIT_WAIT_MS);
|
|
873
|
+
if (!isCurrent(inst)) return;
|
|
874
|
+
if (!ai?.openTarget) {
|
|
875
|
+
toast(AI_UNAVAILABLE, "warning");
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
ai.openTarget(aiTarget);
|
|
796
879
|
};
|
|
797
880
|
const aiRequested = () => capabilityAdvertised(meta);
|
|
798
881
|
|
|
@@ -809,7 +892,7 @@ export function createSteps(inst, options) {
|
|
|
809
892
|
: null;
|
|
810
893
|
const articles = represented ? represented.querySelectorAll(ARTICLE_SEL) : liveArticles;
|
|
811
894
|
const tags = represented ? represented.querySelectorAll(TAG_SEL) : document.querySelectorAll(TAG_SEL);
|
|
812
|
-
if (!articles.length && !tags.length) return { ok: false, reason: "no-
|
|
895
|
+
if (!articles.length && !tags.length) return { ok: false, reason: "no-blockeditor" };
|
|
813
896
|
// A page saved without its merge state (HTML Clay, an outside edit, a download)
|
|
814
897
|
// is booted from its article alone: the seed is deterministic, so every tab
|
|
815
898
|
// rebuilding the same article holds the same state and their edits merge. The tag
|
|
@@ -818,11 +901,11 @@ export function createSteps(inst, options) {
|
|
|
818
901
|
// the next install to read as a page the file already has.
|
|
819
902
|
const seeded = articles.length === 1 && tags.length === 0;
|
|
820
903
|
if (articles.length !== 1 || (!seeded && tags.length !== 1)) {
|
|
821
|
-
console.warn(`clayjs: page plugin needs exactly one article[data-
|
|
822
|
-
return { ok: false, reason: "ambiguous-
|
|
904
|
+
console.warn(`clayjs: page plugin needs exactly one article[data-blockeditor] and one merge="blockeditor" live tag; found ${articles.length} article(s) and ${tags.length} tag(s), leaving the page as saved HTML`);
|
|
905
|
+
return { ok: false, reason: "ambiguous-blockeditor" };
|
|
823
906
|
}
|
|
824
907
|
const element = liveArticles[0];
|
|
825
|
-
if (!element) return { ok: false, reason: "no-
|
|
908
|
+
if (!element) return { ok: false, reason: "no-blockeditor" };
|
|
826
909
|
// The file the reader represented is the page this session edits: its tag and the
|
|
827
910
|
// article the seed is built from come off those bytes once, so a tag another tab
|
|
828
911
|
// changed and a reader's own local state cannot reach what is staged. The live
|
|
@@ -844,7 +927,7 @@ export function createSteps(inst, options) {
|
|
|
844
927
|
if (seeded) {
|
|
845
928
|
try {
|
|
846
929
|
const meta = await hostMeta();
|
|
847
|
-
const href = meta?.extensions?.includes("
|
|
930
|
+
const href = meta?.extensions?.includes("blockeditor-recovery-v1") && meta.document?.blockeditorState?.href;
|
|
848
931
|
const etag = inst.context?.represented ? inst.context.represented.etag : representedEtag() ?? lastSeenEtag();
|
|
849
932
|
if (href && etag) {
|
|
850
933
|
const { readPageState } = await import("../page/checkpoint-proof.js");
|
|
@@ -854,7 +937,7 @@ export function createSteps(inst, options) {
|
|
|
854
937
|
if (stored.etag !== etag) return { ok: false, reason: "etag-mismatch" };
|
|
855
938
|
value = parseJsonRelaxed(stored.tag);
|
|
856
939
|
source.persisted = true;
|
|
857
|
-
} else if (stored.code !== "no-
|
|
940
|
+
} else if (stored.code !== "no-blockeditor-state") {
|
|
858
941
|
return { ok: false, reason: stored.code };
|
|
859
942
|
}
|
|
860
943
|
} else if (href) {
|
|
@@ -867,7 +950,7 @@ export function createSteps(inst, options) {
|
|
|
867
950
|
value = source.seedValue = value ?? code.seedPageFromArticle(source.article.cloneNode(true));
|
|
868
951
|
} catch (error) {
|
|
869
952
|
console.warn("clayjs: page plugin could not rebuild the page it found, leaving the page as saved HTML", error);
|
|
870
|
-
return { ok: false, reason: "invalid-
|
|
953
|
+
return { ok: false, reason: "invalid-blockeditor-source" };
|
|
871
954
|
}
|
|
872
955
|
const unreadable = unreadablePage(value);
|
|
873
956
|
if (unreadable) return { ok: false, reason: unreadable };
|
|
@@ -877,7 +960,7 @@ export function createSteps(inst, options) {
|
|
|
877
960
|
source.blocks = code.blocksFromYValue(value);
|
|
878
961
|
} catch (error) {
|
|
879
962
|
console.warn("clayjs: page plugin: the page tag is not a valid Yjs page, leaving the page as saved HTML", error);
|
|
880
|
-
return { ok: false, reason: "invalid-
|
|
963
|
+
return { ok: false, reason: "invalid-blockeditor-json" };
|
|
881
964
|
}
|
|
882
965
|
return { ok: true };
|
|
883
966
|
})();
|
|
@@ -885,23 +968,6 @@ export function createSteps(inst, options) {
|
|
|
885
968
|
return { ok: true, source };
|
|
886
969
|
}
|
|
887
970
|
|
|
888
|
-
// The page store the session mounts, brought to the file the reader holds. A page store's
|
|
889
|
-
// value is a set of Yjs updates and this tab's own unsaved ops are updates of their own, so
|
|
890
|
-
// installing the file's value as the store's own leaves the unconfirmed work replayed over
|
|
891
|
-
// the page the file states; the binding that mounts next applies both sets in one Y
|
|
892
|
-
// document, which is the merge. False when the store cannot take the value, and the entry
|
|
893
|
-
// refuses rather than mounting a page the file does not state.
|
|
894
|
-
function mergeRepresentedPage(store, value) {
|
|
895
|
-
if (unreadablePage(value)) return false;
|
|
896
|
-
if (serializeLive(store.authoritative) === serializeLive(value)) return true;
|
|
897
|
-
try {
|
|
898
|
-
return store.rebase(value, { source: "state" });
|
|
899
|
-
} catch (error) {
|
|
900
|
-
console.warn("clayjs: page plugin could not reconcile the open page store with the saved file", error);
|
|
901
|
-
return false;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
971
|
// The page's own code, the editor it starts and the feature views the saved article
|
|
906
972
|
// demands are separate downloads, and the page's own starts here beside the room join.
|
|
907
973
|
// Nothing evaluates the editor until the coordinator calls it; the parts are
|
|
@@ -919,7 +985,7 @@ function mergeRepresentedPage(store, value) {
|
|
|
919
985
|
install: async (id, mod) => {
|
|
920
986
|
if (!editorView) await editorReady;
|
|
921
987
|
if (!isCurrent(inst) || !editorView) return null;
|
|
922
|
-
const context = { view: editorView, upload: options.upload, board: binding.board, undo: binding.undo, redo: binding.redo, revise: aiRequested() ? (toolId
|
|
988
|
+
const context = { view: editorView, upload: isDraftMode ? draftUpload : options.upload, board: binding.board, undo: binding.undo, redo: binding.redo, revise: aiRequested() ? (toolId) => { openAi({ target: { toolId } }); } : null };
|
|
923
989
|
if (id === "tool") {
|
|
924
990
|
context.toolHost = await openTools();
|
|
925
991
|
if (!isCurrent(inst)) return null;
|
|
@@ -954,9 +1020,10 @@ function mergeRepresentedPage(store, value) {
|
|
|
954
1020
|
// download: it starts here, beside authority, and only a page that must rebuild
|
|
955
1021
|
// its tag waits for it before the join. Nothing else on this path touches a vendor.
|
|
956
1022
|
const codePromise = pageCodeReady();
|
|
957
|
-
|
|
1023
|
+
// A visitor's draft never asks the host: the editor's only store is this browser's.
|
|
1024
|
+
try { meta = isDraftMode ? { spec: null, extensions: ["blockeditor"], document: null } : await hostMeta(); } catch (error) { meta = null; }
|
|
958
1025
|
if (!isCurrent(inst)) return { ok: false, cancelled: true };
|
|
959
|
-
if (!meta || !Array.isArray(meta.extensions) || !meta.extensions.includes("
|
|
1026
|
+
if (!meta || !Array.isArray(meta.extensions) || !meta.extensions.includes("blockeditor")) {
|
|
960
1027
|
showBlocked(inst, BLOCKED.code, BLOCKED.message);
|
|
961
1028
|
return { ok: false, reason: "missing-capability" };
|
|
962
1029
|
}
|
|
@@ -965,7 +1032,7 @@ function mergeRepresentedPage(store, value) {
|
|
|
965
1032
|
// is merged by the boot instead of being read as a replacement.
|
|
966
1033
|
inst.mergeTagless = !meta.extensions.includes("live-data");
|
|
967
1034
|
// The one declaration that lets a host answer a seed-only body with nothing-to-write.
|
|
968
|
-
setSaveHeader("
|
|
1035
|
+
setSaveHeader("Blockeditor-Write", "v1");
|
|
969
1036
|
// A seeded page is the one boot whose join needs the page code, which rebuilds
|
|
970
1037
|
// the seed the article states.
|
|
971
1038
|
let read = null;
|
|
@@ -985,7 +1052,7 @@ function mergeRepresentedPage(store, value) {
|
|
|
985
1052
|
let ownsStore = true;
|
|
986
1053
|
try {
|
|
987
1054
|
if (source.seeded) {
|
|
988
|
-
const staged = stageStore("
|
|
1055
|
+
const staged = stageStore("blockeditor", {
|
|
989
1056
|
value: source.seedValue,
|
|
990
1057
|
represented: options.represented ?? { bytes: source.article.outerHTML, etag: representedEtag() ?? lastSeenEtag() },
|
|
991
1058
|
persisted: source.persisted,
|
|
@@ -993,14 +1060,14 @@ function mergeRepresentedPage(store, value) {
|
|
|
993
1060
|
inst.staged = staged;
|
|
994
1061
|
handle = staged.handle;
|
|
995
1062
|
} else {
|
|
996
|
-
const opened = storeOf("
|
|
1063
|
+
const opened = storeOf("blockeditor") === null;
|
|
997
1064
|
ownsStore = opened;
|
|
998
|
-
handle = window.clay.live("
|
|
999
|
-
if (opened) inst.pageStore = storeOf("
|
|
1065
|
+
handle = window.clay.live("blockeditor");
|
|
1066
|
+
if (opened) inst.pageStore = storeOf("blockeditor");
|
|
1000
1067
|
}
|
|
1001
1068
|
} catch (error) {
|
|
1002
1069
|
console.warn("clayjs: page plugin could not open the page store", error);
|
|
1003
|
-
return { ok: false, reason: "missing-
|
|
1070
|
+
return { ok: false, reason: "missing-blockeditor-store" };
|
|
1004
1071
|
}
|
|
1005
1072
|
try {
|
|
1006
1073
|
await handle.ready;
|
|
@@ -1018,14 +1085,14 @@ function mergeRepresentedPage(store, value) {
|
|
|
1018
1085
|
// file before anything mounts on it, and a store that cannot take it refuses the
|
|
1019
1086
|
// entry rather than mounting a page the file does not state. A store that has joined
|
|
1020
1087
|
// a room is the exception: the room is newer than any file, and its value stands.
|
|
1021
|
-
const inRoom = storeOf("
|
|
1022
|
-
if (!ownsStore && !inRoom && !mergeRepresentedPage(storeOf("
|
|
1088
|
+
const inRoom = storeOf("blockeditor")?.handle?.status?.mode === "fast";
|
|
1089
|
+
if (!ownsStore && !inRoom && !mergeRepresentedPage(storeOf("blockeditor"), source.tags?.[0])) {
|
|
1023
1090
|
return { ok: false, reason: "stale-store" };
|
|
1024
1091
|
}
|
|
1025
1092
|
code ??= await codePromise;
|
|
1026
1093
|
if (!isCurrent(inst)) return { ok: false, cancelled: true };
|
|
1027
|
-
const store = storeOf("
|
|
1028
|
-
if (!store) return { ok: false, reason: "missing-
|
|
1094
|
+
const store = storeOf("blockeditor");
|
|
1095
|
+
if (!store) return { ok: false, reason: "missing-blockeditor-store" };
|
|
1029
1096
|
inst.store = store;
|
|
1030
1097
|
|
|
1031
1098
|
let flushFailed = false;
|
|
@@ -1047,9 +1114,9 @@ function mergeRepresentedPage(store, value) {
|
|
|
1047
1114
|
onStopped: (reason) => {
|
|
1048
1115
|
inst.tabValue = code.yValueOf(binding.ydoc);
|
|
1049
1116
|
if (reason === "lineage") {
|
|
1050
|
-
fastLane()?.abandon?.("
|
|
1117
|
+
fastLane()?.abandon?.("blockeditor");
|
|
1051
1118
|
showReplaced(inst);
|
|
1052
|
-
} else if (reason === "
|
|
1119
|
+
} else if (reason === "blockeditor-full" || reason === "compaction-failed") {
|
|
1053
1120
|
showFull(inst, reason);
|
|
1054
1121
|
} else {
|
|
1055
1122
|
showTooLarge(inst);
|
|
@@ -1138,8 +1205,10 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
1138
1205
|
board: binding.board,
|
|
1139
1206
|
editable: false,
|
|
1140
1207
|
features,
|
|
1208
|
+
upload: isDraftMode ? draftUpload : options.upload,
|
|
1141
1209
|
onAiTool: aiRequested() ? (_view, remove) => { openAi({ remove }); } : null,
|
|
1142
|
-
|
|
1210
|
+
mediaNote: isDraftMode ? VISITOR_MEDIA : null,
|
|
1211
|
+
reviseTool: aiRequested() ? (toolId) => { openAi({ target: { toolId } }); } : null,
|
|
1143
1212
|
formatGate: inst.loadFormatGate,
|
|
1144
1213
|
});
|
|
1145
1214
|
} catch (error) {
|
|
@@ -1152,6 +1221,22 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
1152
1221
|
}
|
|
1153
1222
|
editorView = view;
|
|
1154
1223
|
inst.view = view;
|
|
1224
|
+
// The AI surface is the page's own, and it arrives beside the editor rather than in
|
|
1225
|
+
// the plugin chunk: nothing rewrites a page until a person asks it to.
|
|
1226
|
+
pageAiReady = import("../page/ai-surface.js")
|
|
1227
|
+
.then(({ createPageAi }) => {
|
|
1228
|
+
if (!isCurrent(inst) || inst.view !== view) return null;
|
|
1229
|
+
inst.pageAi = createPageAi({
|
|
1230
|
+
getView: () => inst.view,
|
|
1231
|
+
store: () => toolsStore,
|
|
1232
|
+
acceptState: options.acceptState,
|
|
1233
|
+
});
|
|
1234
|
+
return inst.pageAi;
|
|
1235
|
+
})
|
|
1236
|
+
.catch((error) => {
|
|
1237
|
+
console.warn("clayjs: page plugin could not load the AI surface", error);
|
|
1238
|
+
return null;
|
|
1239
|
+
});
|
|
1155
1240
|
applySettings(readSettings());
|
|
1156
1241
|
// A document that states settings opens their store so a peer's change reaches this
|
|
1157
1242
|
// tab; one that states none opens it when a switch is first written.
|
|
@@ -1159,18 +1244,52 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
1159
1244
|
else listen(inst, "clay:sync-applied", () => {
|
|
1160
1245
|
if (!settingsOpening && document.querySelector(SETTINGS_SEL)) openSettings().catch(() => null);
|
|
1161
1246
|
});
|
|
1162
|
-
const shell = view.dom.closest(".
|
|
1247
|
+
const shell = view.dom.closest(".blockeditor-shell");
|
|
1163
1248
|
// A shell still waiting for its boot takes no input of any kind: a control inside
|
|
1164
1249
|
// a non-editable editor would otherwise write before the boot says ready. It is
|
|
1165
1250
|
// cleared at the one place input is enabled.
|
|
1166
1251
|
if (shell) shell.inert = true;
|
|
1167
1252
|
if (loaded.featureIds.length && shell) shell.style.visibility = "hidden";
|
|
1168
1253
|
// One synchronous move: the finished shell replaces the saved surface as a
|
|
1169
|
-
// single change, and the article element itself stays where it is.
|
|
1170
|
-
|
|
1171
|
-
|
|
1254
|
+
// single change, and the article element itself stays where it is. The editor
|
|
1255
|
+
// chunk can land after the boot settle, so the move is paused: it is the editor
|
|
1256
|
+
// presenting the saved surface, never an edit to save.
|
|
1257
|
+
Mutation.pause();
|
|
1258
|
+
try {
|
|
1259
|
+
if (!loaded.featureIds.length) for (const node of savedContent) node.remove();
|
|
1260
|
+
if (shell) article.appendChild(shell);
|
|
1261
|
+
} finally {
|
|
1262
|
+
Mutation.resume();
|
|
1263
|
+
}
|
|
1172
1264
|
loaded.refreshView?.(view);
|
|
1173
1265
|
binding.attach(view);
|
|
1266
|
+
// The page's history tells whoever presents it once it exists (a menu opened before the
|
|
1267
|
+
// editor came up gets its Undo row then), and once per entry it takes or replays. A
|
|
1268
|
+
// peer's typing takes none of them: their work is not this tab's to undo.
|
|
1269
|
+
const um = binding.bridge?.um;
|
|
1270
|
+
if (um) {
|
|
1271
|
+
const notify = () => notifyHistory(inst);
|
|
1272
|
+
um.on("stack-item-added", notify);
|
|
1273
|
+
um.on("stack-item-popped", notify);
|
|
1274
|
+
um.on("stack-cleared", notify);
|
|
1275
|
+
addDisposer(inst, () => {
|
|
1276
|
+
um.off("stack-item-added", notify);
|
|
1277
|
+
um.off("stack-item-popped", notify);
|
|
1278
|
+
um.off("stack-cleared", notify);
|
|
1279
|
+
});
|
|
1280
|
+
notify();
|
|
1281
|
+
}
|
|
1282
|
+
// A proposal shown or cleared changes what the pair may do without touching the stack,
|
|
1283
|
+
// so the change of that flag is what is told, not every transaction.
|
|
1284
|
+
let previewWas = previewShown(view.state);
|
|
1285
|
+
addDisposer(inst, addViewHook(view, {
|
|
1286
|
+
update: (next) => {
|
|
1287
|
+
const previewIs = previewShown(next.state);
|
|
1288
|
+
if (previewIs === previewWas) return;
|
|
1289
|
+
previewWas = previewIs;
|
|
1290
|
+
notifyHistory(inst);
|
|
1291
|
+
},
|
|
1292
|
+
}));
|
|
1174
1293
|
installAwareness(inst, view);
|
|
1175
1294
|
editorReadyResolve();
|
|
1176
1295
|
// The parts the saved article demands install onto the mounted view now, whether
|
|
@@ -1180,7 +1299,7 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
1180
1299
|
scrollToHashBlock(view);
|
|
1181
1300
|
inst.recover = loaded.code.createRecoverList({
|
|
1182
1301
|
binding,
|
|
1183
|
-
mount: view.dom.closest(".
|
|
1302
|
+
mount: view.dom.closest(".blockeditor-shell") ?? article,
|
|
1184
1303
|
ownTokens: [identity.token, ...identity.owned],
|
|
1185
1304
|
onRestored: (id) => loaded.code.focusBlock(view, id),
|
|
1186
1305
|
onStale: () => toast("That change was updated. The list now shows the latest version.", "warning"),
|
|
@@ -1269,7 +1388,7 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
1269
1388
|
inst.store.handle.set(["y", key], value);
|
|
1270
1389
|
// The session is not admitted until the commit, so its ops stay in the tab: the
|
|
1271
1390
|
// claim alone is framed now, before adoption writes anything, or no election runs.
|
|
1272
|
-
lane.release("
|
|
1391
|
+
lane.release("blockeditor");
|
|
1273
1392
|
}
|
|
1274
1393
|
// A lane that never reports a writer must not cost the hand edit: give up
|
|
1275
1394
|
// before adoption's own bound, and adopt as the file-mode tab would.
|
|
@@ -1292,7 +1411,7 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
1292
1411
|
mergeTagless: inst.mergeTagless,
|
|
1293
1412
|
seeded: source.seeded,
|
|
1294
1413
|
writerReady: awaitWriter,
|
|
1295
|
-
adopted: (rev) => fastLane()?.adopted?.("
|
|
1414
|
+
adopted: (rev) => fastLane()?.adopted?.("blockeditor", rev, inst.store.mark),
|
|
1296
1415
|
notice: (counts) => {
|
|
1297
1416
|
if (!counts.copy) return;
|
|
1298
1417
|
toastPersistent(OVERWRITTEN_TEXT, "warning", {
|
|
@@ -1325,12 +1444,12 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
1325
1444
|
async function prepareInput(inst, mounted) {
|
|
1326
1445
|
// A replacement that landed while the page was still booting keeps the editor
|
|
1327
1446
|
// read-only: the notice is already up, and this tab must not edit the old article.
|
|
1328
|
-
if (inst.replaced) return { ok: false, reason: "
|
|
1447
|
+
if (inst.replaced) return { ok: false, reason: "blockeditor-replaced" };
|
|
1329
1448
|
await featuresReady;
|
|
1330
1449
|
if (!isCurrent(inst)) return { ok: false, cancelled: true };
|
|
1331
1450
|
// The gate: this installation's own view, still in the article it claimed, over
|
|
1332
1451
|
// the binding the room accepted. Anything else is not something to type into.
|
|
1333
|
-
const shell = mounted.view.dom.closest(".
|
|
1452
|
+
const shell = mounted.view.dom.closest(".blockeditor-shell");
|
|
1334
1453
|
if (!ownsView(inst, mounted) || !shell || !shell.isConnected || mounted.view.isDestroyed) {
|
|
1335
1454
|
return { ok: false, reason: "view-detached" };
|
|
1336
1455
|
}
|
|
@@ -1350,17 +1469,23 @@ function mountAndAttach(inst, source, loaded) {
|
|
|
1350
1469
|
// opens. It changes activation only, and only after preparation already passed, so
|
|
1351
1470
|
// the commit that enters edit cannot fail here.
|
|
1352
1471
|
function openInput(inst, mounted) {
|
|
1353
|
-
const shell = mounted.view.dom.closest(".
|
|
1472
|
+
const shell = mounted.view.dom.closest(".blockeditor-shell");
|
|
1473
|
+
// Like the boot move, revealing the editor over the saved surface is presentation, not an edit.
|
|
1354
1474
|
const showEditor = () => {
|
|
1355
|
-
|
|
1356
|
-
|
|
1475
|
+
Mutation.pause();
|
|
1476
|
+
try {
|
|
1477
|
+
for (const node of savedContent) node.remove();
|
|
1478
|
+
if (shell) shell.style.visibility = "";
|
|
1479
|
+
} finally {
|
|
1480
|
+
Mutation.resume();
|
|
1481
|
+
}
|
|
1357
1482
|
};
|
|
1358
1483
|
// A replacement that landed during the feature wait keeps the editor read-only,
|
|
1359
1484
|
// exactly as one that landed before it does.
|
|
1360
1485
|
if (inst.replaced) {
|
|
1361
1486
|
showEditor();
|
|
1362
1487
|
stopEditing(inst);
|
|
1363
|
-
return { ok: false, reason: "
|
|
1488
|
+
return { ok: false, reason: "blockeditor-replaced" };
|
|
1364
1489
|
}
|
|
1365
1490
|
if (inst.inputOpen) return { ok: true };
|
|
1366
1491
|
if (!ownsView(inst, mounted) || !shell || !shell.isConnected || mounted.view.isDestroyed) {
|
|
@@ -1431,7 +1556,7 @@ function begin(options = {}, { activate = true, context = null } = {}) {
|
|
|
1431
1556
|
options.onPhase?.(result);
|
|
1432
1557
|
},
|
|
1433
1558
|
}),
|
|
1434
|
-
article: null, view: null, binding: null, adoption: null, source: null, store: null, toolHost: null,
|
|
1559
|
+
article: null, view: null, binding: null, adoption: null, source: null, store: null, toolHost: null, pageAi: null, bootHtml: "", bootRev: null, savedRev: null, staged: null, pageStore: null, releaseArticle: null, replaced: false, savesHeld: false, flush: null, loadFormatGate: null, listeners: [], retryTimer: null, retryShown: false, statusTimer: null, statusNote: null, notices: null, editorReadyResolve: null, context, inputOpen: false, sessionHeld: false, autosaveSuspended: false, staleRetry: false,
|
|
1435
1560
|
};
|
|
1436
1561
|
active = inst;
|
|
1437
1562
|
const boot = run(inst, options, { activate }).catch((error) => {
|
|
@@ -1455,7 +1580,7 @@ function endSession(inst) {
|
|
|
1455
1580
|
active = null;
|
|
1456
1581
|
generation += 1;
|
|
1457
1582
|
}
|
|
1458
|
-
setSaveHeader("
|
|
1583
|
+
setSaveHeader("Blockeditor-Write", null);
|
|
1459
1584
|
disposeInstall(inst);
|
|
1460
1585
|
dismissSaveStateToasts(inst, true);
|
|
1461
1586
|
inst.titleObserver?.dispose();
|
|
@@ -1557,7 +1682,7 @@ export function preparePageSession(context = null, options = {}) {
|
|
|
1557
1682
|
*/
|
|
1558
1683
|
export function pageParticipant(options = {}) {
|
|
1559
1684
|
return {
|
|
1560
|
-
id: "clay:
|
|
1685
|
+
id: "clay:blockeditor",
|
|
1561
1686
|
// After live (200) and upload (340): the page builds its stores over the admission
|
|
1562
1687
|
// those two already opened.
|
|
1563
1688
|
order: 400,
|
|
@@ -1590,6 +1715,14 @@ export const page = {
|
|
|
1590
1715
|
get view() {
|
|
1591
1716
|
return active?.view ?? null;
|
|
1592
1717
|
},
|
|
1718
|
+
// The page's chosen-area AI, read by ai-edit at use time: null until the editor is up.
|
|
1719
|
+
get ai() {
|
|
1720
|
+
return active?.pageAi ?? null;
|
|
1721
|
+
},
|
|
1722
|
+
// The document's history, for the Claygoo menu: null until its editor is up.
|
|
1723
|
+
get history() {
|
|
1724
|
+
return active ? pageHistory(active) : null;
|
|
1725
|
+
},
|
|
1593
1726
|
// The document's settings, for the panel that switches them.
|
|
1594
1727
|
get settings() {
|
|
1595
1728
|
return active?.settings ?? null;
|
|
@@ -1606,6 +1739,32 @@ onSaveAccepted((html) => {
|
|
|
1606
1739
|
if (state) binding.acceptFile(state);
|
|
1607
1740
|
});
|
|
1608
1741
|
|
|
1742
|
+
if (isDraftMode) {
|
|
1743
|
+
// Another tab of this visitor's draft recorded its page: it joins this tab's in one Y
|
|
1744
|
+
// document, the same merge a represented page takes, and the next save records both.
|
|
1745
|
+
document.addEventListener("clay:drafts-blockeditor-external", (event) => {
|
|
1746
|
+
const { live, adopted } = event.detail || {};
|
|
1747
|
+
if (!live || typeof live !== "object") return;
|
|
1748
|
+
let merged = true;
|
|
1749
|
+
for (const [name, value] of Object.entries(live)) {
|
|
1750
|
+
const store = storeOf(name);
|
|
1751
|
+
if (!store) continue;
|
|
1752
|
+
try {
|
|
1753
|
+
const ok = name === "blockeditor" ? mergeRepresentedPage(store, value) : store.rebase(value, { source: "state" });
|
|
1754
|
+
if (ok === false) merged = false;
|
|
1755
|
+
} catch (error) {
|
|
1756
|
+
console.warn("clayjs: another tab's page could not join this one", error);
|
|
1757
|
+
merged = false;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
// The provider re-offers this tab's own edits on a timer; a hide before it fires would
|
|
1761
|
+
// capture the other tab's page alone. Reconcile now, so a capture holds both.
|
|
1762
|
+
if (merged) active?.binding?.provider?.settle?.();
|
|
1763
|
+
if (merged) adopted?.();
|
|
1764
|
+
});
|
|
1765
|
+
document.dispatchEvent(new CustomEvent("clay:drafts-blockeditor-listening"));
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1609
1768
|
// A live entry imports this module while the committed mode is still view: the install
|
|
1610
1769
|
// belongs to the explicit edit boot alone, and a managed session starts its own.
|
|
1611
1770
|
page.ready = startsAtImport() ? install() : null;
|