@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
|
@@ -4,6 +4,18 @@ import { draftSlot, setDraftStoreFailed } from "../../core/draft-mode.js";
|
|
|
4
4
|
export const KEY_PREFIX = "clay:visitor-drafts:";
|
|
5
5
|
export const RECORD_VERSION = 1;
|
|
6
6
|
const LINEAGE = 32;
|
|
7
|
+
// What one tab's journal slot in `localStorage` may hold, and what its unconfirmed
|
|
8
|
+
// operations may hold in memory, in characters of JSON.
|
|
9
|
+
export const SLOT_LIMIT = 512 * 1024;
|
|
10
|
+
export const PENDING_LIMIT = 16 * 1024 * 1024;
|
|
11
|
+
|
|
12
|
+
export const newRecordId = () => Math.random().toString(36).slice(2, 10) + Math.random().toString(36).slice(2, 6);
|
|
13
|
+
|
|
14
|
+
function textMark(text) {
|
|
15
|
+
let hash = 0x811c9dc5;
|
|
16
|
+
for (let i = 0; i < text.length; i++) hash = Math.imul(hash ^ text.charCodeAt(i), 0x01000193);
|
|
17
|
+
return (hash >>> 0).toString(36) + "." + text.length.toString(36);
|
|
18
|
+
}
|
|
7
19
|
|
|
8
20
|
// The document a pathname names, spelled the way the hosts resolve it: the path up to
|
|
9
21
|
// and including the first segment that ends in .html or .htmlclay (anything after it is
|
|
@@ -55,10 +67,39 @@ export function fitsInto(wanted, got) {
|
|
|
55
67
|
return wanted === got;
|
|
56
68
|
}
|
|
57
69
|
|
|
58
|
-
function same(a, b) {
|
|
70
|
+
export function same(a, b) {
|
|
59
71
|
return fitsInto(a, b) && fitsInto(b, a);
|
|
60
72
|
}
|
|
61
73
|
|
|
74
|
+
const STORE_SEL = 'script[type="application/json"][merge][clay~="live"]';
|
|
75
|
+
const ARTICLE_SEL = "article[data-blockeditor]";
|
|
76
|
+
const isPagePart = (name) => name === "blockeditor" || /^blockeditor\.\d+$/.test(name);
|
|
77
|
+
const canonical = (live) => JSON.stringify(Object.keys(live).sort().map((name) => [name, live[name]]));
|
|
78
|
+
|
|
79
|
+
// A page document's page: every live store's value and the article the save projected
|
|
80
|
+
// from them. Null for a document that is not one page, or whose store does not parse.
|
|
81
|
+
function readUnit(source) {
|
|
82
|
+
const articles = source.querySelectorAll(ARTICLE_SEL);
|
|
83
|
+
if (articles.length !== 1) return null;
|
|
84
|
+
const live = {};
|
|
85
|
+
for (const tag of source.querySelectorAll(STORE_SEL)) {
|
|
86
|
+
try { live[tag.getAttribute("merge")] = JSON.parse(tag.textContent); } catch { return null; }
|
|
87
|
+
}
|
|
88
|
+
return { live, blockeditor: { rev: articles[0].getAttribute("data-rev") || "", html: articles[0].innerHTML } };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const sameUnit = (a, b) => (!a || !b ? a === b : a.blockeditor.rev === b.blockeditor.rev && canonical(a.live) === canonical(b.live));
|
|
92
|
+
|
|
93
|
+
// One page content, one mark: two pages `sameUnit` calls the same carry the same one.
|
|
94
|
+
export const unitMark = (unit) => (isPlain(unit?.live) && isPlain(unit?.blockeditor) ? `${unit.blockeditor.rev}:${textMark(canonical(unit.live))}` : null);
|
|
95
|
+
|
|
96
|
+
function unitOf(record) {
|
|
97
|
+
if (!isPlain(record?.live) || !isPlain(record?.blockeditor)) return null;
|
|
98
|
+
const { live, blockeditor } = record;
|
|
99
|
+
if (!Object.keys(live).length || typeof blockeditor.rev !== "string" || typeof blockeditor.html !== "string") return null;
|
|
100
|
+
return { live, blockeditor };
|
|
101
|
+
}
|
|
102
|
+
|
|
62
103
|
const hasIds = (rows) => Array.isArray(rows)
|
|
63
104
|
&& rows.every((row) => isPlain(row) && (typeof row.id === "string" || typeof row.id === "number"))
|
|
64
105
|
&& new Set(rows.map((row) => row.id)).size === rows.length;
|
|
@@ -163,7 +204,7 @@ export function merge3(base, mine, theirs) {
|
|
|
163
204
|
return merged;
|
|
164
205
|
}
|
|
165
206
|
|
|
166
|
-
function storageFailure(doc, error) {
|
|
207
|
+
export function storageFailure(doc, error) {
|
|
167
208
|
setDraftStoreFailed(true);
|
|
168
209
|
doc.dispatchEvent(new CustomEvent("clay:drafts-store-failed", { detail: { error } }));
|
|
169
210
|
const failure = new Error("Couldn't save in this browser. Storage is full or turned off.");
|
|
@@ -220,21 +261,85 @@ export function adoptLegacy(slot, { path = draftPath(), storage = safeStorage()
|
|
|
220
261
|
}
|
|
221
262
|
}
|
|
222
263
|
|
|
223
|
-
|
|
264
|
+
// The `site` key holds values the server writes into every page of a multi-page site from its
|
|
265
|
+
// site.json. A draft must never capture or replay them, or a returning visitor would see stale ones.
|
|
266
|
+
function withoutSiteKey(value) {
|
|
267
|
+
if (!isPlain(value) || !("site" in value)) return value;
|
|
268
|
+
const { site, ...rest } = value;
|
|
269
|
+
return rest;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function createStore({ doc = document, storage = safeStorage(), now = () => new Date(), path = draftPath(), fallback = null, slotLimit = SLOT_LIMIT, pendingLimit = PENDING_LIMIT } = {}) {
|
|
224
273
|
const slot = draftSlot(doc);
|
|
225
274
|
const key = draftKey(slot, path);
|
|
226
|
-
const rules = engine.findRulesIn(doc, "api").rules;
|
|
275
|
+
const rules = withoutSiteKey(engine.findRulesIn(doc, "api").rules);
|
|
227
276
|
|
|
228
277
|
let lastRaw = null;
|
|
229
278
|
let base = null;
|
|
279
|
+
// The served page's data, read before any restore, and the stored data this tab's next
|
|
280
|
+
// operation is built on: the record it restored or last took in with its own operations
|
|
281
|
+
// laid over, or the served page's while nothing is stored.
|
|
282
|
+
let seed = null;
|
|
283
|
+
let tip = null;
|
|
284
|
+
// Nothing of a draft is on this page yet: no restore, no operation, nothing taken in.
|
|
285
|
+
let fresh = true;
|
|
230
286
|
let unparked = null;
|
|
231
287
|
let stopped = false;
|
|
232
288
|
// This tab's own last write: its record id, its ancestry, and the data of the record it
|
|
233
289
|
// was built on. A read in one tab can miss a write another tab made a few milliseconds
|
|
234
290
|
// earlier, so a record whose ancestry lacks this write never saw it.
|
|
235
291
|
let own = null;
|
|
292
|
+
// The page as the served file states it, read once before any restore: an untouched
|
|
293
|
+
// page is never recorded, so the owner's later page reaches a visitor who never wrote in it.
|
|
294
|
+
let served;
|
|
295
|
+
// The page this tab's editor holds as its starting point, and the page in the record
|
|
296
|
+
// this tab last read or wrote. Null when no page is recorded.
|
|
297
|
+
let editorUnit = null;
|
|
298
|
+
let seenUnit = null;
|
|
299
|
+
|
|
300
|
+
const readServed = () => { if (served === undefined) served = readUnit(doc); };
|
|
301
|
+
function recordable(unit) {
|
|
302
|
+
if (!unit) return null;
|
|
303
|
+
readServed();
|
|
304
|
+
if (!served) return unit;
|
|
305
|
+
const differs = unit.blockeditor.rev !== served.blockeditor.rev ||
|
|
306
|
+
Object.keys(unit.live).some((name) => !isPagePart(name) && JSON.stringify(unit.live[name]) !== JSON.stringify(served.live[name]));
|
|
307
|
+
// An untouched page is never recorded, so the owner's later page reaches a visitor who
|
|
308
|
+
// never wrote in it. Once this tab has held a recorded page, every page is recorded,
|
|
309
|
+
// even one returned to the served text, so a deletion reaches the other tabs too.
|
|
310
|
+
return differs || editorUnit || seenUnit ? unit : null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Runs once the article exists. The visitor-drafts module runs before the page and live
|
|
314
|
+
// plugins and registers first, so on a page still parsing this listener runs before theirs.
|
|
315
|
+
function whenArticle(fn) {
|
|
316
|
+
if (doc.querySelector(ARTICLE_SEL) || doc.readyState !== "loading") return fn();
|
|
317
|
+
doc.addEventListener("DOMContentLoaded", fn, { once: true });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// The stored values go into the store tags as JSON text with `<` escaped, so a value
|
|
321
|
+
// cannot close its tag. The article is left alone: the editor renders from the store.
|
|
322
|
+
function placeUnit(unit) {
|
|
323
|
+
readServed();
|
|
324
|
+
const article = doc.querySelector(ARTICLE_SEL);
|
|
325
|
+
if (!article) return false;
|
|
326
|
+
const tags = new Map([...doc.querySelectorAll(STORE_SEL)].map((tag) => [tag.getAttribute("merge"), tag]));
|
|
327
|
+
for (const [name, tag] of tags) if (isPagePart(name) && !(name in unit.live)) tag.remove();
|
|
328
|
+
for (const [name, value] of Object.entries(unit.live)) {
|
|
329
|
+
let tag = tags.get(name);
|
|
330
|
+
if (!tag) {
|
|
331
|
+
tag = doc.createElement("script");
|
|
332
|
+
tag.setAttribute("type", "application/json");
|
|
333
|
+
tag.setAttribute("merge", name);
|
|
334
|
+
tag.setAttribute("clay", "live");
|
|
335
|
+
article.parentNode.insertBefore(tag, article);
|
|
336
|
+
}
|
|
337
|
+
tag.textContent = JSON.stringify(value).replace(/</g, "\\u003c");
|
|
338
|
+
}
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
236
341
|
|
|
237
|
-
const newId =
|
|
342
|
+
const newId = newRecordId;
|
|
238
343
|
|
|
239
344
|
// A record's ancestry is the ids it descends from, nearest first, ending at the empty
|
|
240
345
|
// slot it grew from, or at a record from a runtime that kept no ids, named by its bytes.
|
|
@@ -245,11 +350,7 @@ export function createStore({ doc = document, storage = safeStorage(), now = ()
|
|
|
245
350
|
try { return "root:" + (storage?.getItem(resetKey) ?? ""); } catch { return "root:"; }
|
|
246
351
|
}
|
|
247
352
|
|
|
248
|
-
|
|
249
|
-
let hash = 0x811c9dc5;
|
|
250
|
-
for (let i = 0; i < raw.length; i++) hash = Math.imul(hash ^ raw.charCodeAt(i), 0x01000193);
|
|
251
|
-
return "legacy:" + (hash >>> 0).toString(36) + "." + raw.length.toString(36);
|
|
252
|
-
}
|
|
353
|
+
const legacyId = (raw) => "legacy:" + textMark(raw);
|
|
253
354
|
|
|
254
355
|
function lineageOf(record, raw) {
|
|
255
356
|
if (!record) return null;
|
|
@@ -373,30 +474,53 @@ export function createStore({ doc = document, storage = safeStorage(), now = ()
|
|
|
373
474
|
return null;
|
|
374
475
|
}
|
|
375
476
|
|
|
376
|
-
function parkedResult(raw) {
|
|
477
|
+
function parkedResult({ raw, record }) {
|
|
377
478
|
const parkedKey = park(raw);
|
|
378
479
|
if (!parkedKey) lastRaw = raw;
|
|
379
480
|
base = live();
|
|
481
|
+
tip = record?.data ?? base;
|
|
380
482
|
return parkedKey
|
|
381
483
|
? { restored: false, hadDraft: true, parked: true }
|
|
382
484
|
: { restored: false, hadDraft: true, parked: false, blocked: true };
|
|
383
485
|
}
|
|
384
486
|
|
|
385
|
-
|
|
386
|
-
|
|
487
|
+
const seedData = () => (seed ??= live());
|
|
488
|
+
|
|
489
|
+
// `found` is the record to put on the page: the one `localStorage` holds, or the
|
|
490
|
+
// database's when the page waited for it (null: it holds none).
|
|
491
|
+
function restore(found = read()) {
|
|
492
|
+
seedData();
|
|
387
493
|
if (!found) {
|
|
388
|
-
base =
|
|
494
|
+
base = tip = seed;
|
|
495
|
+
whenArticle(readServed);
|
|
389
496
|
return { restored: false, hadDraft: false };
|
|
390
497
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
498
|
+
fresh = false;
|
|
499
|
+
if (!found.record) return parkedResult(found);
|
|
500
|
+
const unit = unitOf(found.record);
|
|
501
|
+
const hasData = Object.keys(found.record.data).length > 0;
|
|
502
|
+
const fitted = hasData ? fit(found.record.data) : { data: {}, dropped: [] };
|
|
503
|
+
const dataFits = Boolean(fitted) && (!hasData || Object.keys(fitted.data).length > 0);
|
|
504
|
+
// The page does not depend on the rules: a page is placed even when no saved field fits,
|
|
505
|
+
// and the whole record is kept aside as well.
|
|
506
|
+
if (!unit && !dataFits) return parkedResult(found);
|
|
507
|
+
if (!unit && !hasData) return parkedResult(found);
|
|
508
|
+
if (hasData && dataFits) applyData(doc, fitted.data, rules);
|
|
509
|
+
if (unit) {
|
|
510
|
+
whenArticle(() => {
|
|
511
|
+
if (placeUnit(unit)) return;
|
|
512
|
+
console.warn("visitor-drafts: this browser's saved page has no page to go into; it was kept aside");
|
|
513
|
+
park(found.raw, { keepSlot: true });
|
|
514
|
+
});
|
|
515
|
+
editorUnit = unit;
|
|
516
|
+
seenUnit = unit;
|
|
517
|
+
}
|
|
395
518
|
lastRaw = found.raw;
|
|
396
519
|
base = live();
|
|
397
|
-
|
|
520
|
+
tip = found.record.data;
|
|
521
|
+
if (!dataFits || fitted.dropped.length) {
|
|
398
522
|
const parkedKey = park(found.raw, { keepSlot: true });
|
|
399
|
-
return { restored: true, hadDraft: true, data: fitted.data, partial: true, parked: Boolean(parkedKey), blocked: !parkedKey };
|
|
523
|
+
return { restored: true, hadDraft: true, data: dataFits ? fitted.data : {}, partial: true, parked: Boolean(parkedKey), blocked: !parkedKey };
|
|
400
524
|
}
|
|
401
525
|
return { restored: true, hadDraft: true, data: fitted.data };
|
|
402
526
|
}
|
|
@@ -405,7 +529,14 @@ export function createStore({ doc = document, storage = safeStorage(), now = ()
|
|
|
405
529
|
// rules), so only what the page captures is compared.
|
|
406
530
|
function changed() {
|
|
407
531
|
if (stopped) return false;
|
|
408
|
-
try {
|
|
532
|
+
try {
|
|
533
|
+
const getHTML = window.clay?.getHTML;
|
|
534
|
+
if (typeof getHTML !== "function") return !fitsInto(live(), base);
|
|
535
|
+
const parsed = new DOMParser().parseFromString(getHTML.call(window.clay), "text/html");
|
|
536
|
+
const unit = recordable(readUnit(parsed));
|
|
537
|
+
// A page equal to the one another tab recorded and this tab took in is not this tab's change.
|
|
538
|
+
return !fitsInto(extractData(parsed, rules), base) || (!sameUnit(unit, editorUnit) && !sameUnit(unit, seenUnit));
|
|
539
|
+
} catch { return true; }
|
|
409
540
|
}
|
|
410
541
|
|
|
411
542
|
// One copy, the latest: a save that would replace another tab's change keeps it
|
|
@@ -446,22 +577,161 @@ export function createStore({ doc = document, storage = safeStorage(), now = ()
|
|
|
446
577
|
return fitted.data;
|
|
447
578
|
}
|
|
448
579
|
|
|
580
|
+
// With the database as the record, a save is an operation: for each key the page changed,
|
|
581
|
+
// the stored value it was built on (`b`, absent when nothing was) and the value now (`m`).
|
|
582
|
+
// `tab.ops` are the ones the database has not confirmed, oldest first, under the Start
|
|
583
|
+
// over marker this page booted on; `sizes` runs beside them. A copy that fits goes into
|
|
584
|
+
// this tab's journal slot at once, so closing the tab loses nothing: `journaled` is the
|
|
585
|
+
// last sequence number the slot holds.
|
|
586
|
+
const tab = { id: newId(), reset: "", acked: 0, seq: 0, journaled: 0, ops: [], sizes: [] };
|
|
587
|
+
try { tab.reset = storage?.getItem(resetKey) ?? ""; } catch {}
|
|
588
|
+
try {
|
|
589
|
+
const known = JSON.parse(window.sessionStorage.getItem(key + ":generation"));
|
|
590
|
+
if (known?.marker === tab.reset && typeof known.reset === "string") tab.reset = known.reset;
|
|
591
|
+
} catch {}
|
|
592
|
+
const slotKey = `${key}:op:${tab.id}`;
|
|
593
|
+
const chain = () => ({ tab: tab.id, reset: tab.reset, acked: tab.acked, at: now().getTime(), ops: tab.ops });
|
|
594
|
+
|
|
595
|
+
function journal() {
|
|
596
|
+
try {
|
|
597
|
+
if (!tab.ops.length) {
|
|
598
|
+
tab.journaled = tab.seq;
|
|
599
|
+
storage.removeItem(slotKey);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const text = JSON.stringify(chain());
|
|
603
|
+
if (text.length > slotLimit) return;
|
|
604
|
+
storage.setItem(slotKey, text);
|
|
605
|
+
tab.journaled = tab.seq;
|
|
606
|
+
} catch {}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// What the page held before its unconfirmed operations, for a page load that falls back
|
|
610
|
+
// to `localStorage` after it made some.
|
|
611
|
+
let before = null;
|
|
612
|
+
|
|
613
|
+
function capture(html) {
|
|
614
|
+
if (stopped) return null;
|
|
615
|
+
if (unparked != null && park(unparked, { keepSlot: true }) === null) {
|
|
616
|
+
throw storageFailure(doc, new Error("an earlier saved copy could not be kept aside"));
|
|
617
|
+
}
|
|
618
|
+
// Too much is waiting: nothing more is taken until it drains, and the page keeps it.
|
|
619
|
+
if (tab.sizes.reduce((sum, size) => sum + size, 0) >= pendingLimit) throw storageFailure(doc, new Error("too many changes are waiting to be saved"));
|
|
620
|
+
const parsed = new DOMParser().parseFromString(html, "text/html");
|
|
621
|
+
const mine = extractData(parsed, rules);
|
|
622
|
+
const mineUnit = recordable(readUnit(parsed));
|
|
623
|
+
const unitChanged = !sameUnit(mineUnit, editorUnit);
|
|
624
|
+
const keys = {};
|
|
625
|
+
for (const name of Object.keys(mine)) {
|
|
626
|
+
if (name in base && fitsInto(mine[name], base[name])) continue;
|
|
627
|
+
// Fields the stored value has and this page's rules lack are carried through.
|
|
628
|
+
keys[name] = name in tip ? { b: tip[name], m: complete(mine[name], tip[name]) } : { m: mine[name] };
|
|
629
|
+
}
|
|
630
|
+
if (!Object.keys(keys).length && !unitChanged) {
|
|
631
|
+
if (!tab.ops.length) {
|
|
632
|
+
setDraftStoreFailed(false);
|
|
633
|
+
doc.dispatchEvent(new CustomEvent("clay:drafts-saved", { detail: { data: mine, unchanged: true } }));
|
|
634
|
+
}
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
if (!tab.ops.length) before = { base, editorUnit, seenUnit };
|
|
638
|
+
const op = { seq: ++tab.seq, savedAt: now().toISOString(), keys, ...(unitChanged && { unit: { seen: unitMark(seenUnit), to: mineUnit } }) };
|
|
639
|
+
base = { ...base };
|
|
640
|
+
tip = { ...tip };
|
|
641
|
+
for (const name of Object.keys(keys)) {
|
|
642
|
+
base[name] = mine[name];
|
|
643
|
+
tip[name] = keys[name].m;
|
|
644
|
+
}
|
|
645
|
+
if (unitChanged) editorUnit = seenUnit = mineUnit;
|
|
646
|
+
fresh = false;
|
|
647
|
+
tab.ops.push(op);
|
|
648
|
+
tab.sizes.push(JSON.stringify(op).length);
|
|
649
|
+
journal();
|
|
650
|
+
return op;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// A hide's synchronous half of a save. False while an operation is neither in the
|
|
654
|
+
// journal slot nor confirmed: closing the tab now could lose it.
|
|
655
|
+
function keep() {
|
|
656
|
+
if (stopped) return true;
|
|
657
|
+
try {
|
|
658
|
+
if (changed()) capture(window.clay.getHTML());
|
|
659
|
+
} catch { return false; }
|
|
660
|
+
return tab.journaled === tab.seq;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function acknowledge(seq) {
|
|
664
|
+
if (seq <= tab.acked) return false;
|
|
665
|
+
tab.acked = seq;
|
|
666
|
+
const done = tab.ops.filter((op) => op.seq <= seq).length;
|
|
667
|
+
tab.ops.splice(0, done);
|
|
668
|
+
tab.sizes.splice(0, done);
|
|
669
|
+
journal();
|
|
670
|
+
return done > 0;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function drop() {
|
|
674
|
+
tab.ops.length = 0;
|
|
675
|
+
tab.sizes.length = 0;
|
|
676
|
+
journal();
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// The database did not open: what the journal holds goes back to being this page's
|
|
680
|
+
// unsaved changes, for `write` to save.
|
|
681
|
+
function release() {
|
|
682
|
+
if (tab.ops.length) ({ base, editorUnit, seenUnit } = before);
|
|
683
|
+
drop();
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// A page with nothing unsaved takes in what the database holds: the keys whose stored
|
|
687
|
+
// value is no longer the one this tab was built on.
|
|
688
|
+
function take(data) {
|
|
689
|
+
const moved = Object.fromEntries(Object.entries(data).filter(([name, value]) => !same(value, tip[name])));
|
|
690
|
+
if (!Object.keys(moved).length) return false;
|
|
691
|
+
const shown = adopt(moved);
|
|
692
|
+
tip = { ...tip, ...moved };
|
|
693
|
+
if (shown) base = { ...base, ...shown };
|
|
694
|
+
fresh = false;
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// The generation this page shows has ended, by this tab's Start over or another tab's:
|
|
699
|
+
// nothing more is saved from it.
|
|
700
|
+
function stop({ keepPending = false } = {}) {
|
|
701
|
+
stopped = true;
|
|
702
|
+
if (!keepPending) drop();
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const journalSlots = () => storedKeys().filter((name) => name.startsWith(key + ":op:"));
|
|
706
|
+
|
|
449
707
|
function write(html) {
|
|
450
708
|
if (stopped) return null;
|
|
451
709
|
if (unparked != null && park(unparked, { keepSlot: true }) === null) {
|
|
452
710
|
throw storageFailure(doc, new Error("an earlier saved copy could not be kept aside"));
|
|
453
711
|
}
|
|
454
|
-
const
|
|
712
|
+
const parsed = new DOMParser().parseFromString(html, "text/html");
|
|
713
|
+
const mine = extractData(parsed, rules);
|
|
714
|
+
const mineUnit = recordable(readUnit(parsed));
|
|
715
|
+
const mineChanged = !sameUnit(mineUnit, editorUnit);
|
|
455
716
|
const current = getRaw();
|
|
456
|
-
if (fitsInto(mine, base) && (current === lastRaw || current == null)) {
|
|
717
|
+
if (fitsInto(mine, base) && !mineChanged && (current === lastRaw || current == null)) {
|
|
457
718
|
setDraftStoreFailed(false);
|
|
458
719
|
doc.dispatchEvent(new CustomEvent("clay:drafts-saved", { detail: { data: mine, unchanged: true } }));
|
|
459
720
|
return mine;
|
|
460
721
|
}
|
|
461
722
|
const { data, stored, ancestry, parentData } = merge(mine);
|
|
723
|
+
const record = current == null ? null : parse(current);
|
|
724
|
+
const theirs = unitOf(record);
|
|
725
|
+
// A tab whose page did not change keeps the recorded page: its editor may hold an older one.
|
|
726
|
+
// Both tabs changed it: this tab's page is written and the other is kept aside, as
|
|
727
|
+
// for any field both tabs changed.
|
|
728
|
+
const unit = mineChanged ? mineUnit : (record ? theirs : mineUnit);
|
|
729
|
+
if (mineChanged && record && !sameUnit(theirs, seenUnit)) keepConflict(current);
|
|
462
730
|
const id = newId();
|
|
463
|
-
const raw = stored
|
|
464
|
-
|
|
731
|
+
const raw = stored && sameUnit(unit, theirs)
|
|
732
|
+
? stored
|
|
733
|
+
: JSON.stringify({ v: RECORD_VERSION, savedAt: now().toISOString(), id, ancestry, reset: tab.reset, data, ...(unit && { live: unit.live, blockeditor: unit.blockeditor }) });
|
|
734
|
+
if (raw !== stored) {
|
|
465
735
|
try {
|
|
466
736
|
if (!storage) throw new Error("no storage");
|
|
467
737
|
storage.setItem(key, raw);
|
|
@@ -474,6 +744,10 @@ export function createStore({ doc = document, storage = safeStorage(), now = ()
|
|
|
474
744
|
if (taken.length) adopt(Object.fromEntries(taken.map((name) => [name, data[name]])));
|
|
475
745
|
lastRaw = raw;
|
|
476
746
|
base = data;
|
|
747
|
+
if (mineChanged) editorUnit = mineUnit;
|
|
748
|
+
// Only a page this tab wrote, or the first record, is one this tab has seen: a page kept
|
|
749
|
+
// from another tab is not in this tab's editor yet.
|
|
750
|
+
if (mineChanged || !record) seenUnit = unit;
|
|
477
751
|
setDraftStoreFailed(false);
|
|
478
752
|
doc.dispatchEvent(new CustomEvent("clay:drafts-saved", { detail: { data } }));
|
|
479
753
|
return data;
|
|
@@ -506,19 +780,64 @@ export function createStore({ doc = document, storage = safeStorage(), now = ()
|
|
|
506
780
|
// adopting a record that dropped its own work.
|
|
507
781
|
if (forked(record) && typeof window.clay?.getHTML === "function") return writeLive();
|
|
508
782
|
const adopted = record && adopt(record.data);
|
|
509
|
-
if (!adopted) return false;
|
|
783
|
+
if (!adopted && !unitOf(record)) return false;
|
|
510
784
|
lastRaw = raw;
|
|
511
|
-
base = { ...base, ...adopted };
|
|
785
|
+
base = { ...base, ...(adopted || {}) };
|
|
512
786
|
return true;
|
|
513
787
|
}
|
|
514
788
|
|
|
789
|
+
// The page another tab recorded since this tab last read or wrote one, or null. `record`
|
|
790
|
+
// is the database's; without one the copy in `localStorage` is read.
|
|
791
|
+
function externalUnit(record) {
|
|
792
|
+
if (stopped) return null;
|
|
793
|
+
if (record === undefined) {
|
|
794
|
+
const raw = getRaw();
|
|
795
|
+
record = raw == null ? null : parse(raw);
|
|
796
|
+
}
|
|
797
|
+
const theirs = unitOf(record);
|
|
798
|
+
return theirs && !sameUnit(theirs, seenUnit) ? theirs : null;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// This tab's editor took that page in beside its own: it is no longer another tab's.
|
|
802
|
+
function adoptedUnit(unit) {
|
|
803
|
+
seenUnit = unit;
|
|
804
|
+
}
|
|
805
|
+
|
|
515
806
|
function clear() {
|
|
516
|
-
|
|
807
|
+
stop();
|
|
517
808
|
unparked = null;
|
|
518
809
|
setDraftStoreFailed(false);
|
|
519
810
|
try { storage.removeItem(key); } catch {}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
811
|
+
const match = /^(\d+)~/.exec(tab.reset);
|
|
812
|
+
const gen = match ? Number(match[1]) : tab.reset === "" ? 1 : null;
|
|
813
|
+
try { storage.setItem(resetKey, gen === null ? newId() : `${gen + 1}~${newId()}`); } catch {}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return {
|
|
817
|
+
slot, key, rules, read, restore, write, writeLive, flush, changed, adoptExternal, externalUnit, adoptedUnit, park, clear, snapshot,
|
|
818
|
+
tab: tab.id, slotKey, chain, capture, keep, acknowledge, release, take, stop, journalSlots,
|
|
819
|
+
seed: seedData,
|
|
820
|
+
// For the session, which is fetched after the boot and imports nothing from here.
|
|
821
|
+
merge3,
|
|
822
|
+
unitMark,
|
|
823
|
+
newId,
|
|
824
|
+
failed: (error) => storageFailure(doc, error),
|
|
825
|
+
generation: () => tab.reset,
|
|
826
|
+
enter: (reset) => { tab.reset = reset; },
|
|
827
|
+
fresh: () => fresh,
|
|
828
|
+
shown: () => base !== null,
|
|
829
|
+
pending: () => tab.ops.length,
|
|
830
|
+
unsafe: () => tab.journaled < tab.seq,
|
|
831
|
+
stopped: () => stopped,
|
|
832
|
+
// What `localStorage` holds at the draft's key, whatever wrote it: a record, the head of
|
|
833
|
+
// a draft whose full record is only in the database (`idb`), or nothing readable.
|
|
834
|
+
held() {
|
|
835
|
+
try {
|
|
836
|
+
const held = JSON.parse(getRaw());
|
|
837
|
+
return isPlain(held) ? held : null;
|
|
838
|
+
} catch { return null; }
|
|
839
|
+
},
|
|
840
|
+
// A draft is kept when `localStorage` holds its copy or head, or any tab's journal.
|
|
841
|
+
kept: () => Boolean(read()) || tab.ops.length > 0 || journalSlots().length > 0,
|
|
842
|
+
};
|
|
524
843
|
}
|