@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/sortable.js
CHANGED
|
@@ -188,6 +188,13 @@ function makeSortable(sortableElem, Sortable, record = loose) {
|
|
|
188
188
|
let options = {};
|
|
189
189
|
options.draggable = draggableSelector(sortableElem);
|
|
190
190
|
|
|
191
|
+
// A reorder is not a text drag. SortableJS writes the item's text onto the drag by
|
|
192
|
+
// default, and a rich text field under the drop pastes it in, duplicating the text.
|
|
193
|
+
options.setData = (dataTransfer) => {
|
|
194
|
+
dataTransfer.clearData();
|
|
195
|
+
dataTransfer.setData('application/x-clay-sortable', '');
|
|
196
|
+
};
|
|
197
|
+
|
|
191
198
|
// Check if Sortable instance already exists
|
|
192
199
|
if (Sortable.get(sortableElem)) return;
|
|
193
200
|
|
package/src/plugins/upgrade.js
CHANGED
|
@@ -154,7 +154,8 @@ async function showVersion(status, version) {
|
|
|
154
154
|
const clay = window.clay;
|
|
155
155
|
await whenMenuReady(clay);
|
|
156
156
|
if (!menuReady(clay) || !version) return;
|
|
157
|
-
|
|
157
|
+
// Short enough for one line in a phone menu (David, Nov 11); the number stays in the plain line.
|
|
158
|
+
const label = status === "current" ? "File at latest version" : `Version ${version}`;
|
|
158
159
|
clay.menu.addItem({ id: "upgrade", label, text: true });
|
|
159
160
|
}
|
|
160
161
|
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one change a visitor draft's stored state ever takes.
|
|
3
|
+
*
|
|
4
|
+
* IndexedDB holds one record per draft key K, and only `transact()` changes it: one
|
|
5
|
+
* readwrite transaction reads what is stored, runs `commit()` over it, and writes the
|
|
6
|
+
* result. IndexedDB runs readwrite transactions on a store one after another across every
|
|
7
|
+
* tab of the origin, so this is the single writer, with no lock and no leader tab.
|
|
8
|
+
*
|
|
9
|
+
* What it applies:
|
|
10
|
+
*
|
|
11
|
+
* - operations: for each top-level key a tab changed, the value before (`b`) and after
|
|
12
|
+
* (`m`), a missing one meaning the key was absent. Each carries its own merge base, so
|
|
13
|
+
* the store's `merge3` lays it over whatever is stored. A tab's operations are a
|
|
14
|
+
* chain `{tab, reset, acked, ops: [{seq, savedAt, keys, unit?}]}`, where `reset` is
|
|
15
|
+
* the Start over marker of the generation the tab shows (a tab knows that before its
|
|
16
|
+
* first transaction; it does not know the number). An operation applies only when
|
|
17
|
+
* its chain's `reset` is the stored one and its `seq` is the next for its tab, so
|
|
18
|
+
* each applies exactly once and in order, whichever tab delivers it;
|
|
19
|
+
* - Start over: "end generation g", given explicitly or as a marker `<g+1>~<random>`
|
|
20
|
+
* in `localStorage`; an unverified marker starts empty and keeps recovery copies;
|
|
21
|
+
* - the move: the full version 1 record in `localStorage`, taken in as the first record
|
|
22
|
+
* when nothing is stored yet;
|
|
23
|
+
* - another writer's record: a version 1 record in `localStorage` without the cache's
|
|
24
|
+
* `w` field, written by a tab on a released ClayJS or by a page load of this one that
|
|
25
|
+
* had no database. It is taken in once: merged when the copy it was built on is held
|
|
26
|
+
* here, kept whole as a recovery copy when it is not. Nothing is merged on a guess.
|
|
27
|
+
*
|
|
28
|
+
* Stored rows (JSON text): K (the record, today's format), `K:meta`
|
|
29
|
+
* `{gen, rev, reset, ended?, tabs: {tab: {seq, at}}, foreign, unit, recovery?}`, `K:v1base` (the
|
|
30
|
+
* last record of another writer that was merged), `K:op:<tab>` (a chain a tab left at a
|
|
31
|
+
* hide), `K:conflict:<id>` with `K:conflict` naming the latest, and `K:recovery:<mark>`.
|
|
32
|
+
* `meta.unit` is the mark of the Block editor page the record holds (`unitMark`). A record
|
|
33
|
+
* id or ancestry identifies another writer's held base or the reset its copy follows.
|
|
34
|
+
* Operations of this ClayJS never read one. Recovery rows can also hold exact ended chains.
|
|
35
|
+
*
|
|
36
|
+
* This module imports nothing: the page fetches it with the database, after the boot, and
|
|
37
|
+
* the store that is already on the page hands it `merge3` and `unitMark`.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
export const LINEAGE = 32;
|
|
41
|
+
// Recovery copies kept per draft, newest first.
|
|
42
|
+
export const RECOVERY_KEEP = 8;
|
|
43
|
+
// A tab not heard from in this long is forgotten; a chain it still sends carries `acked`.
|
|
44
|
+
export const TAB_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
45
|
+
|
|
46
|
+
const clone = (value) => (value === undefined ? undefined : JSON.parse(JSON.stringify(value)));
|
|
47
|
+
const isPlain = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
48
|
+
|
|
49
|
+
export const metaKey = (key) => key + ":meta";
|
|
50
|
+
export const rowKey = (key, tab) => `${key}:op:${tab}`;
|
|
51
|
+
export const conflictKey = (key, id) => (id === undefined ? key + ":conflict" : `${key}:conflict:${id}`);
|
|
52
|
+
export const recoveryKey = (key, mark) => `${key}:recovery:${mark}`;
|
|
53
|
+
|
|
54
|
+
// A version 1 record without the cache's `w` field is another writer's.
|
|
55
|
+
export const isForeign = (value) => isPlain(value) && value.v === 1 && value.w === undefined && isPlain(value.data);
|
|
56
|
+
// What names one: its id, or its time for a record from before ids (ClayJS 1.22.0 and earlier).
|
|
57
|
+
export const foreignMark = (record) => (typeof record.id === "string" ? record.id : "at:" + record.savedAt);
|
|
58
|
+
|
|
59
|
+
export const markerFor = (gen, nonce) => `${gen}~${nonce}`;
|
|
60
|
+
export function markerGen(marker) {
|
|
61
|
+
const match = /^(\d+)~/.exec(marker ?? "");
|
|
62
|
+
return match ? Number(match[1]) : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// One operation's keys laid over `data` with the store's three-way merge.
|
|
66
|
+
export function applyKeys(data, keys, merge3) {
|
|
67
|
+
const base = { ...data };
|
|
68
|
+
const mine = { ...data };
|
|
69
|
+
for (const [name, pair] of Object.entries(keys)) {
|
|
70
|
+
if ("b" in pair) base[name] = pair.b; else delete base[name];
|
|
71
|
+
if ("m" in pair) mine[name] = pair.m; else delete mine[name];
|
|
72
|
+
}
|
|
73
|
+
return merge3(base, mine, data);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const isChain = (value) => isPlain(value) && typeof value.tab === "string" && typeof value.reset === "string" && Array.isArray(value.ops);
|
|
77
|
+
export const recoveryMark = (copy) => isChain(copy) ? `op:${copy.tab}:${copy.ops.at(-1)?.seq ?? copy.acked ?? 0}` : foreignMark(copy);
|
|
78
|
+
|
|
79
|
+
// Cleanup needs proof for this exact chain, even when its generation has ended.
|
|
80
|
+
export function settled(chain, meta) {
|
|
81
|
+
if (isPlain(chain) && meta.recovery?.includes(recoveryMark(chain))) return true;
|
|
82
|
+
if (!isChain(chain)) return false;
|
|
83
|
+
const at = Math.max(chain.acked ?? 0, chain.reset === meta.reset ? meta.tabs[chain.tab]?.seq ?? 0 : 0);
|
|
84
|
+
return chain.ops.every((op) => op.seq <= at);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function emptyMeta(marker = null) {
|
|
88
|
+
return { gen: markerGen(marker) ?? 1, rev: 0, reset: marker ?? "", tabs: {}, foreign: null, unit: null };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const unitOf = (record) => (record && record.blockeditor ? { live: record.live, blockeditor: record.blockeditor } : null);
|
|
92
|
+
|
|
93
|
+
// The ids a record built on `record` descends from, nearest first.
|
|
94
|
+
function lineage(first, record, meta) {
|
|
95
|
+
const line = [...first, record?.id, ...(record?.ancestry ?? [])].filter((id) => typeof id === "string");
|
|
96
|
+
return line.length ? line.slice(0, LINEAGE) : ["root:" + meta.reset];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// The copy another writer's record was built on, when this side holds it and what it held
|
|
100
|
+
// is in the stored record: the stored record itself, a cache copy of this generation, or
|
|
101
|
+
// the last record of another writer that was merged. A record that descends from one of
|
|
102
|
+
// them (its ancestry names it, or a storage event says it replaced that cache copy)
|
|
103
|
+
// carries every change made since as its difference; the nearest one is the base.
|
|
104
|
+
function foreignBase(line, { record, seen, direct, v1base, meta }) {
|
|
105
|
+
const cache = isPlain(seen) && seen.v === 1 && isPlain(seen.data) && seen.w?.reset === meta.reset ? seen : null;
|
|
106
|
+
const at = (held) => (held && typeof held.id === "string" ? line.indexOf(held.id) : -1);
|
|
107
|
+
const held = [[record, at(record)], [cache, cache && direct ? 0 : at(cache)], [v1base, at(v1base)]].filter(([, index]) => index >= 0);
|
|
108
|
+
return held.length ? held.reduce((nearest, next) => (next[1] < nearest[1] ? next : nearest))[0] : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {{meta: ?Object, record: ?Object, v1base: ?Object}} stored
|
|
113
|
+
* @param {Object} input
|
|
114
|
+
* @param {Object} input.seed - the data a generation starts from (the served page's)
|
|
115
|
+
* @param {() => string} input.newId
|
|
116
|
+
* @param {number} input.now - milliseconds
|
|
117
|
+
* @param {Function} input.merge3 - the store's three-way merge
|
|
118
|
+
* @param {Function} input.unitMark - the store's mark of a Block editor page
|
|
119
|
+
* @param {?string} [input.marker] - the Start over marker `localStorage` holds now
|
|
120
|
+
* @param {Array<{ends: number, marker: string}>} [input.resets]
|
|
121
|
+
* @param {Array<Object>} [input.chains] - oldest carriers first, the caller's own last
|
|
122
|
+
* @param {?Object} [input.local] - what `localStorage` holds at the draft's key now, parsed
|
|
123
|
+
* @param {?Object} [input.seen] - the latest cache copy the caller saw at the draft's key
|
|
124
|
+
* @param {boolean} [input.direct] - a storage event says `local` replaced exactly that copy
|
|
125
|
+
*/
|
|
126
|
+
export function commit(stored, { seed, newId, now, merge3, unitMark, marker = null, resets = [], chains = [], local = null, seen = null, direct = false }) {
|
|
127
|
+
const meta = stored.meta ? clone(stored.meta) : emptyMeta(marker);
|
|
128
|
+
// A database the browser cleared counts on from this ClayJS's cache, so the cache never
|
|
129
|
+
// reads as newer than the record.
|
|
130
|
+
if (!stored.meta && Number.isInteger(local?.w?.rev)) meta.rev = local.w.rev;
|
|
131
|
+
let record = stored.record ? clone(stored.record) : null;
|
|
132
|
+
let v1base = stored.v1base ? clone(stored.v1base) : null;
|
|
133
|
+
const out = { conflicts: [], recovered: [], released: [], dropped: [], stale: [], applied: {}, changed: !stored.meta };
|
|
134
|
+
|
|
135
|
+
function recover(copy, supersedes = []) {
|
|
136
|
+
const mark = recoveryMark(copy);
|
|
137
|
+
if (meta.recovery?.includes(mark)) return;
|
|
138
|
+
out.recovered.push(clone(copy));
|
|
139
|
+
const kept = [mark, ...(meta.recovery ?? []).filter((held) => held !== mark && !supersedes.includes(held))].slice(0, RECOVERY_KEEP);
|
|
140
|
+
out.dropped = [...new Set([...out.dropped, ...(meta.recovery ?? [])])].filter((held) => !kept.includes(held));
|
|
141
|
+
meta.recovery = kept;
|
|
142
|
+
out.changed = true;
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const ends = [...resets];
|
|
147
|
+
if (marker && marker !== meta.reset) ends.push({ ends: markerGen(marker) === null ? null : markerGen(marker) - 1, marker });
|
|
148
|
+
for (const reset of ends) {
|
|
149
|
+
if (reset.marker === meta.reset || meta.ended?.includes(reset.marker) || (reset.ends !== null && reset.ends < meta.gen)) continue;
|
|
150
|
+
if (reset.ends !== meta.gen) {
|
|
151
|
+
if (record) recover(record);
|
|
152
|
+
}
|
|
153
|
+
const follows = typeof local?.reset === "string" ? local.reset === reset.marker
|
|
154
|
+
: reset.ends === null || (Array.isArray(local?.ancestry) && local.ancestry.includes("root:" + reset.marker));
|
|
155
|
+
if (isForeign(local) && foreignMark(local) !== meta.foreign && !follows) {
|
|
156
|
+
recover(local);
|
|
157
|
+
meta.foreign = foreignMark(local);
|
|
158
|
+
}
|
|
159
|
+
meta.ended = [...(meta.ended ?? []), meta.reset].slice(-LINEAGE);
|
|
160
|
+
Object.assign(meta, { gen: meta.gen + 1, reset: reset.marker, tabs: {}, unit: null });
|
|
161
|
+
meta.rev++;
|
|
162
|
+
record = null;
|
|
163
|
+
v1base = null;
|
|
164
|
+
out.changed = true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Nothing stored yet: the full copy in `localStorage` is the first record, whether a
|
|
168
|
+
// 1.23.0 tab wrote it (the move) or it is this ClayJS's cache, of this generation, of a
|
|
169
|
+
// database the browser has since cleared.
|
|
170
|
+
if (!stored.meta && !record && isPlain(local) && local.v === 1 && isPlain(local.data) && (local.w === undefined || local.w.reset === meta.reset)) {
|
|
171
|
+
const { w, ...copy } = local;
|
|
172
|
+
record = clone(copy);
|
|
173
|
+
if (w === undefined) {
|
|
174
|
+
v1base = clone(copy);
|
|
175
|
+
meta.foreign = foreignMark(local);
|
|
176
|
+
meta.rev++;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (!stored.meta) meta.unit = unitMark(unitOf(record));
|
|
180
|
+
|
|
181
|
+
// Another writer's record met after the move, taken in once and before any chain.
|
|
182
|
+
if (stored.meta && isForeign(local) && foreignMark(local) !== meta.foreign) {
|
|
183
|
+
const mark = foreignMark(local);
|
|
184
|
+
const line = Array.isArray(local.ancestry) ? local.ancestry : [];
|
|
185
|
+
const base = record ? foreignBase(line, { record, seen, direct, v1base, meta }) : null;
|
|
186
|
+
if (typeof local.reset === "string" && meta.ended?.includes(local.reset)) {
|
|
187
|
+
if (recover(local, line)) out.released.push(clone(local));
|
|
188
|
+
} else if (!record) {
|
|
189
|
+
// Nothing is stored in this generation: its page is the draft, as it is between two
|
|
190
|
+
// tabs of that ClayJS.
|
|
191
|
+
record = clone(local);
|
|
192
|
+
v1base = clone(local);
|
|
193
|
+
meta.unit = unitMark(unitOf(record));
|
|
194
|
+
meta.rev++;
|
|
195
|
+
} else if (base) {
|
|
196
|
+
const merged = merge3(base.data, local.data, record.data);
|
|
197
|
+
// A page it carries counts as changed only when it differs from its base's.
|
|
198
|
+
const page = unitOf(local);
|
|
199
|
+
const paged = Boolean(page) && unitMark(page) !== unitMark(unitOf(base));
|
|
200
|
+
const pageMoved = paged && unitOf(record) && unitMark(unitOf(base)) !== meta.unit;
|
|
201
|
+
if (merged.conflict || pageMoved) out.conflicts.push(clone(record));
|
|
202
|
+
const unit = paged ? page : unitOf(record);
|
|
203
|
+
record = {
|
|
204
|
+
v: 1,
|
|
205
|
+
savedAt: local.savedAt ?? new Date(now).toISOString(),
|
|
206
|
+
id: newId(),
|
|
207
|
+
ancestry: lineage(typeof local.id === "string" ? [local.id] : [], record, meta),
|
|
208
|
+
data: merged.value,
|
|
209
|
+
...(unit && { live: unit.live, blockeditor: unit.blockeditor }),
|
|
210
|
+
};
|
|
211
|
+
if (paged) meta.unit = unitMark(unit);
|
|
212
|
+
v1base = clone(local);
|
|
213
|
+
meta.rev++;
|
|
214
|
+
} else {
|
|
215
|
+
// The copy it was built on is not held here: it is kept whole, beside the draft. A
|
|
216
|
+
// later copy of the same line replaces the one it descends from.
|
|
217
|
+
if (recover(local, line)) out.released.push(clone(local));
|
|
218
|
+
}
|
|
219
|
+
meta.foreign = mark;
|
|
220
|
+
out.changed = true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (const chain of chains) {
|
|
224
|
+
if (!isChain(chain)) continue;
|
|
225
|
+
if (chain.reset !== meta.reset) {
|
|
226
|
+
const acked = chain.reset === stored.meta?.reset ? stored.meta.tabs[chain.tab]?.seq ?? 0 : 0;
|
|
227
|
+
if (chain.ops.some((op) => op.seq > Math.max(acked, chain.acked ?? 0))) {
|
|
228
|
+
recover(chain);
|
|
229
|
+
}
|
|
230
|
+
if (!out.stale.includes(chain.tab)) out.stale.push(chain.tab);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const before = meta.tabs[chain.tab]?.seq ?? 0;
|
|
234
|
+
let at = Math.max(before, Number.isInteger(chain.acked) ? chain.acked : 0);
|
|
235
|
+
for (const op of chain.ops) {
|
|
236
|
+
if (op.seq <= at) continue;
|
|
237
|
+
if (op.seq !== at + 1) break;
|
|
238
|
+
const merged = applyKeys(record ? record.data : seed, op.keys ?? {}, merge3);
|
|
239
|
+
const unitMoved = op.unit !== undefined && record && unitOf(record) && op.unit.seen !== meta.unit;
|
|
240
|
+
if (record && (merged.conflict || unitMoved)) out.conflicts.push(clone(record));
|
|
241
|
+
const unit = op.unit !== undefined ? op.unit.to : unitOf(record);
|
|
242
|
+
const id = newId();
|
|
243
|
+
record = {
|
|
244
|
+
v: 1,
|
|
245
|
+
savedAt: op.savedAt ?? new Date(now).toISOString(),
|
|
246
|
+
id,
|
|
247
|
+
ancestry: lineage([], record, meta),
|
|
248
|
+
data: merged.value,
|
|
249
|
+
...(unit && { live: unit.live, blockeditor: unit.blockeditor }),
|
|
250
|
+
};
|
|
251
|
+
if (op.unit !== undefined) meta.unit = unitMark(unit);
|
|
252
|
+
meta.rev++;
|
|
253
|
+
at = op.seq;
|
|
254
|
+
out.changed = true;
|
|
255
|
+
}
|
|
256
|
+
if (at !== before || meta.tabs[chain.tab]) {
|
|
257
|
+
meta.tabs[chain.tab] = { seq: at, at: now };
|
|
258
|
+
out.changed = true;
|
|
259
|
+
}
|
|
260
|
+
out.applied[chain.tab] = at;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (const [tab, entry] of Object.entries(meta.tabs)) {
|
|
264
|
+
if (now - (entry.at ?? 0) > TAB_TTL_MS) {
|
|
265
|
+
delete meta.tabs[tab];
|
|
266
|
+
out.changed = true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { meta, record, v1base, ...out };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const parse = (text) => {
|
|
273
|
+
if (text == null) return null;
|
|
274
|
+
try { return JSON.parse(text); } catch { return null; }
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Run `commit()` in one readwrite transaction and write what it returns. `input` is a
|
|
279
|
+
* function, called inside the transaction once its reads are in, so what it reads from
|
|
280
|
+
* `localStorage` (journal slots, the marker, the cache) is read at that moment.
|
|
281
|
+
* Resolves on the transaction's `complete` event with `commit()`'s result.
|
|
282
|
+
*/
|
|
283
|
+
export function transact(drafts, key, input, options) {
|
|
284
|
+
return drafts.change(key, (raw) => {
|
|
285
|
+
const stored = { meta: parse(raw.meta), record: parse(raw.record), v1base: parse(raw.v1base) };
|
|
286
|
+
const rows = raw.rows.map(([name, text]) => [name, parse(text)]);
|
|
287
|
+
const given = input(stored);
|
|
288
|
+
const result = commit(stored, { ...given, chains: [...rows.map(([, chain]) => chain), ...(given.chains ?? [])] });
|
|
289
|
+
const puts = [];
|
|
290
|
+
const deletes = [];
|
|
291
|
+
if (result.changed) {
|
|
292
|
+
puts.push([metaKey(key), JSON.stringify(result.meta)]);
|
|
293
|
+
if (result.record) puts.push([key, JSON.stringify(result.record)]); else if (stored.record) deletes.push(key);
|
|
294
|
+
if (result.v1base) puts.push([key + ":v1base", JSON.stringify(result.v1base)]); else if (stored.v1base) deletes.push(key + ":v1base");
|
|
295
|
+
}
|
|
296
|
+
for (const copy of result.conflicts) puts.push([conflictKey(key, foreignMark(copy)), JSON.stringify(copy)]);
|
|
297
|
+
if (result.conflicts.length) puts.push([conflictKey(key), JSON.stringify(result.conflicts[result.conflicts.length - 1])]);
|
|
298
|
+
for (const copy of result.recovered) puts.push([recoveryKey(key, recoveryMark(copy)), JSON.stringify(copy)]);
|
|
299
|
+
for (const mark of result.dropped) deletes.push(recoveryKey(key, mark));
|
|
300
|
+
for (const [name, chain] of rows) if (settled(chain, result.meta)) deletes.push(name);
|
|
301
|
+
return { puts, deletes, result };
|
|
302
|
+
}, options);
|
|
303
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The IndexedDB half of the visitor draft store.
|
|
3
|
+
*
|
|
4
|
+
* One database per origin (`clay-visitor-drafts`), two stores:
|
|
5
|
+
*
|
|
6
|
+
* records key: the draft's text key (`clay:visitor-drafts:<slot>@<path>`) and its
|
|
7
|
+
* siblings `<key>:<name>`; value: JSON text
|
|
8
|
+
* parts key: `[recordKey, regionKey, partId]`; value: `{ rev, text }`
|
|
9
|
+
*
|
|
10
|
+
* Every change to a draft goes through `change()`, one readwrite transaction that reads,
|
|
11
|
+
* merges (the caller's `build`) and writes; see `commit.js`. The only other write is
|
|
12
|
+
* `putRow()`, a tab's own chain left blind at a hide. A transaction resolves only once it
|
|
13
|
+
* completes. Every failure rejects with an Error whose `code` says what happened
|
|
14
|
+
* (`unavailable`, `blocked`, `quota`, `aborted`, `unknown-schema`, `closed`, `bad-op`,
|
|
15
|
+
* `error`).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { codeFor } from "../../lib/idb-error-code.js";
|
|
19
|
+
|
|
20
|
+
export const DB_NAME = "clay-visitor-drafts";
|
|
21
|
+
export const DB_VERSION = 1;
|
|
22
|
+
export const RECORDS = "records";
|
|
23
|
+
export const PARTS = "parts";
|
|
24
|
+
const OPEN_TIMEOUT_MS = 2000;
|
|
25
|
+
|
|
26
|
+
function failure(code, cause) {
|
|
27
|
+
const error = new Error(`visitor-drafts: the draft database failed (${code})`);
|
|
28
|
+
error.code = code;
|
|
29
|
+
if (cause !== undefined) error.cause = cause;
|
|
30
|
+
return error;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function cursorEntries(source, range) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const out = [];
|
|
36
|
+
const req = source.openCursor(range);
|
|
37
|
+
req.onerror = () => reject(req.error);
|
|
38
|
+
req.onsuccess = () => {
|
|
39
|
+
const cursor = req.result;
|
|
40
|
+
if (!cursor) return resolve(out);
|
|
41
|
+
out.push([cursor.key, cursor.value]);
|
|
42
|
+
cursor.continue();
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Open (and, the first time, create) the draft database.
|
|
49
|
+
*
|
|
50
|
+
* A database that does not open within `timeout` milliseconds (another tab holds an
|
|
51
|
+
* upgrade, or the browser never answers) rejects with `blocked`, so the caller can
|
|
52
|
+
* refuse to write over a draft it could not read rather than wait forever.
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} [options]
|
|
55
|
+
* @param {?IDBFactory} [options.factory]
|
|
56
|
+
* @param {?typeof IDBKeyRange} [options.keyRange]
|
|
57
|
+
* @returns {Promise<{readAll: Function, readParts: Function, change: Function, putRow: Function, close: Function}>}
|
|
58
|
+
*/
|
|
59
|
+
export function openDrafts({ factory = globalThis.indexedDB, keyRange = globalThis.IDBKeyRange, name = DB_NAME, timeout = OPEN_TIMEOUT_MS } = {}) {
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
if (!factory || typeof factory.open !== "function" || !keyRange) return reject(failure("unavailable"));
|
|
62
|
+
let req;
|
|
63
|
+
try {
|
|
64
|
+
req = factory.open(name, DB_VERSION);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return reject(failure(codeFor(error), error));
|
|
67
|
+
}
|
|
68
|
+
let settled = false;
|
|
69
|
+
const finish = (fn, value) => {
|
|
70
|
+
if (settled) return false;
|
|
71
|
+
settled = true;
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
fn(value);
|
|
74
|
+
return true;
|
|
75
|
+
};
|
|
76
|
+
const timer = setTimeout(() => finish(reject, failure("blocked")), timeout);
|
|
77
|
+
req.onupgradeneeded = () => {
|
|
78
|
+
const db = req.result;
|
|
79
|
+
if (!db.objectStoreNames.contains(RECORDS)) db.createObjectStore(RECORDS);
|
|
80
|
+
if (!db.objectStoreNames.contains(PARTS)) db.createObjectStore(PARTS);
|
|
81
|
+
};
|
|
82
|
+
req.onsuccess = () => {
|
|
83
|
+
const db = req.result;
|
|
84
|
+
// An open that answered after the timeout is closed again, or it would hold the
|
|
85
|
+
// database against another tab's upgrade for as long as this page stays open.
|
|
86
|
+
if (!finish(resolve, wrap(db, keyRange))) db.close();
|
|
87
|
+
};
|
|
88
|
+
req.onerror = () => finish(reject, failure(codeFor(req.error), req.error));
|
|
89
|
+
req.onblocked = () => finish(reject, failure("blocked"));
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function wrap(db, keyRange) {
|
|
94
|
+
let closed = false;
|
|
95
|
+
// Another tab opening a newer version needs this connection to stand aside.
|
|
96
|
+
db.onversionchange = () => {
|
|
97
|
+
closed = true;
|
|
98
|
+
try { db.close(); } catch {}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
function transaction(stores, mode) {
|
|
102
|
+
if (closed) throw failure("closed");
|
|
103
|
+
try {
|
|
104
|
+
return db.transaction(stores, mode);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
throw failure(codeFor(error), error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// The record named `recordKey` and its siblings (`<recordKey>:<anything>`), as a Map
|
|
111
|
+
// from key to stored text. A key that only starts with `recordKey`, such as another
|
|
112
|
+
// document whose path extends this one's, is not a sibling.
|
|
113
|
+
async function readAll(recordKey) {
|
|
114
|
+
const tx = transaction(RECORDS, "readonly");
|
|
115
|
+
const range = keyRange.bound(recordKey, recordKey + ":");
|
|
116
|
+
try {
|
|
117
|
+
const entries = await cursorEntries(tx.objectStore(RECORDS), range);
|
|
118
|
+
return new Map(entries.filter(([key]) => key === recordKey || key.startsWith(recordKey + ":")));
|
|
119
|
+
} catch (error) {
|
|
120
|
+
throw failure(codeFor(error), error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// The parts of one region of one record, as a Map from part id to `{ rev, text }`.
|
|
125
|
+
// An array sorts after every string, so `[recordKey, regionKey, []]` bounds them all.
|
|
126
|
+
async function readParts(recordKey, regionKey) {
|
|
127
|
+
const tx = transaction(PARTS, "readonly");
|
|
128
|
+
const range = keyRange.bound([recordKey, regionKey], [recordKey, regionKey, []]);
|
|
129
|
+
try {
|
|
130
|
+
const entries = await cursorEntries(tx.objectStore(PARTS), range);
|
|
131
|
+
return new Map(entries.map(([key, value]) => [key[2], value]));
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw failure(codeFor(error), error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// The one read-merge-write. Reads the record, `:meta`, `:v1base` and every `:op:` row of
|
|
138
|
+
// `key`, hands them to `build` inside the transaction, and writes the `puts` and
|
|
139
|
+
// `deletes` it returns before the transaction can complete. A `build` or a write that
|
|
140
|
+
// throws aborts it, so nothing lands. Resolves with `build`'s `result` on `complete`.
|
|
141
|
+
function change(key, build, { timeout = 0 } = {}) {
|
|
142
|
+
return new Promise((resolve, reject) => {
|
|
143
|
+
let tx;
|
|
144
|
+
try {
|
|
145
|
+
tx = transaction([RECORDS, PARTS], "readwrite");
|
|
146
|
+
} catch (error) {
|
|
147
|
+
return reject(error);
|
|
148
|
+
}
|
|
149
|
+
let thrown = null;
|
|
150
|
+
let result;
|
|
151
|
+
let settled = false;
|
|
152
|
+
let timer;
|
|
153
|
+
const finish = (fn, value) => {
|
|
154
|
+
if (settled) return;
|
|
155
|
+
settled = true;
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
fn(value);
|
|
158
|
+
};
|
|
159
|
+
if (timeout > 0) timer = setTimeout(() => {
|
|
160
|
+
thrown = failure("blocked");
|
|
161
|
+
try { tx.abort(); } catch {}
|
|
162
|
+
finish(reject, thrown);
|
|
163
|
+
}, timeout);
|
|
164
|
+
tx.oncomplete = () => finish(resolve, result);
|
|
165
|
+
tx.onabort = () => finish(reject, thrown || failure(tx.error ? codeFor(tx.error) : "aborted", tx.error));
|
|
166
|
+
try {
|
|
167
|
+
const store = tx.objectStore(RECORDS);
|
|
168
|
+
const rows = keyRange.bound(key + ":op:", key + ":op:\uffff");
|
|
169
|
+
const reads = { record: store.get(key), meta: store.get(key + ":meta"), v1base: store.get(key + ":v1base"), names: store.getAllKeys(rows), values: store.getAll(rows) };
|
|
170
|
+
// Requests in one transaction answer in the order they were made: the last one in
|
|
171
|
+
// means every read is.
|
|
172
|
+
reads.values.onsuccess = () => {
|
|
173
|
+
if (settled) return;
|
|
174
|
+
try {
|
|
175
|
+
const raw = {
|
|
176
|
+
record: reads.record.result ?? null,
|
|
177
|
+
meta: reads.meta.result ?? null,
|
|
178
|
+
v1base: reads.v1base.result ?? null,
|
|
179
|
+
rows: reads.names.result.map((name, i) => [name, reads.values.result[i]]),
|
|
180
|
+
};
|
|
181
|
+
const planned = build(raw);
|
|
182
|
+
for (const [name, value] of planned.puts) store.put(value, name);
|
|
183
|
+
for (const name of planned.deletes) store.delete(name);
|
|
184
|
+
result = planned.result;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
thrown = failure(codeFor(error), error);
|
|
187
|
+
try { tx.abort(); } catch {}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
} catch (error) {
|
|
191
|
+
thrown = failure(codeFor(error), error);
|
|
192
|
+
try { tx.abort(); } catch {}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// A tab's own unconfirmed chain, written blind (no read) at a hide, with an explicit
|
|
198
|
+
// commit so it needs no callback in a page about to go away. `change()` applies it later.
|
|
199
|
+
function putRow(name, value) {
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
if (!/:op:[^:]+$/.test(name)) return reject(failure("bad-op"));
|
|
202
|
+
let tx;
|
|
203
|
+
try {
|
|
204
|
+
tx = transaction(RECORDS, "readwrite");
|
|
205
|
+
tx.objectStore(RECORDS).put(value, name);
|
|
206
|
+
tx.commit?.();
|
|
207
|
+
} catch (error) {
|
|
208
|
+
return reject(error instanceof Error && error.code ? error : failure(codeFor(error), error));
|
|
209
|
+
}
|
|
210
|
+
tx.oncomplete = () => resolve();
|
|
211
|
+
tx.onabort = () => reject(failure(tx.error ? codeFor(tx.error) : "aborted", tx.error));
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function close() {
|
|
216
|
+
closed = true;
|
|
217
|
+
try { db.close(); } catch {}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return { readAll, readParts, change, putRow, close };
|
|
221
|
+
}
|
|
@@ -22,7 +22,7 @@ export const CLOSE_CSS = `:host { color: inherit !important; }
|
|
|
22
22
|
.clay-intro__close svg { display: block; width: 12px; height: 12px; fill: currentColor; stroke: none; }
|
|
23
23
|
@media (pointer: coarse) { .clay-intro__close { width: 44px; height: 44px; top: 4px; right: 4px; } }
|
|
24
24
|
.clay-intro__close[data-manual] {
|
|
25
|
-
width: auto; height: 36px; padding: 0 14px;
|
|
25
|
+
width: auto; height: 36px; padding: 0 14px; opacity: 1;
|
|
26
26
|
border-radius: 999px; font-size: 12px; font-weight: 500;
|
|
27
27
|
text-transform: uppercase; letter-spacing: .04em; white-space: nowrap;
|
|
28
28
|
}
|
|
@@ -6,7 +6,6 @@ import { bootPhase } from "../../core/boot-phase.js";
|
|
|
6
6
|
export const COPY = {
|
|
7
7
|
close: "Hide this introduction",
|
|
8
8
|
hide: "Hide intro",
|
|
9
|
-
show: "Show intro",
|
|
10
9
|
label: (appName) => `About ${appName}`,
|
|
11
10
|
};
|
|
12
11
|
|
|
@@ -86,7 +85,8 @@ export function mountIntro({ memory, template, appName, hadDraft }) {
|
|
|
86
85
|
if (!template || !template.content.textContent.trim() && !template.content.querySelector("img, svg, video, canvas")) return null;
|
|
87
86
|
if (template.getAttribute("visitor-intro") === "manual") {
|
|
88
87
|
const printing = window.matchMedia && window.matchMedia("print").matches;
|
|
89
|
-
|
|
88
|
+
// A returning visitor keeps the intro until they hide it themselves; hiding it is remembered (David, Nov 11).
|
|
89
|
+
const open = forced() || !(memory.get().intro.done || bootPhase.servedHash || printing);
|
|
90
90
|
return mountManual({ memory, template, appName, open });
|
|
91
91
|
}
|
|
92
92
|
if (!forced()) {
|
|
@@ -138,36 +138,19 @@ function mount({ memory, template, appName }) {
|
|
|
138
138
|
return section;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
// Hidden only by its own button, never by scrolling, and once hidden it stays hidden for this
|
|
142
|
+
// visitor: there is no way back from a menu (David, Nov 11).
|
|
141
143
|
function mountManual({ memory, template, appName, open }) {
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
const menu = window.clay?.menu;
|
|
145
|
-
if (!menu) return;
|
|
146
|
-
if (wanted) menu.addItem({ id: "clay.intro", label: COPY.show, run: show });
|
|
147
|
-
else menu.removeItem("clay.intro");
|
|
148
|
-
};
|
|
149
|
-
function attach() {
|
|
150
|
-
section ??= buildSection({ template, appName, labelText: COPY.hide, onClose: hide });
|
|
151
|
-
document.body.prepend(section);
|
|
152
|
-
announceBottomUi();
|
|
153
|
-
showRow(false);
|
|
154
|
-
}
|
|
144
|
+
if (!open) return null;
|
|
145
|
+
const section = buildSection({ template, appName, labelText: COPY.hide, onClose: hide });
|
|
155
146
|
function hide() {
|
|
156
147
|
memory.get().intro.done = "closed";
|
|
157
148
|
memory.save();
|
|
158
149
|
section.remove();
|
|
159
150
|
announceBottomUi();
|
|
160
|
-
showRow(true);
|
|
161
151
|
focusApp();
|
|
162
152
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
memory.save();
|
|
166
|
-
attach();
|
|
167
|
-
window.scrollTo(0, 0);
|
|
168
|
-
}
|
|
169
|
-
document.addEventListener("clay:menu-ready", () => showRow(!section?.isConnected));
|
|
170
|
-
if (open) attach();
|
|
171
|
-
else showRow(true);
|
|
153
|
+
document.body.prepend(section);
|
|
154
|
+
announceBottomUi();
|
|
172
155
|
return section;
|
|
173
156
|
}
|