From fc6a1a3819cc769ba8e70edbd04ac1c3e96a01f4 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Tue, 9 Sep 2025 09:54:08 -0700 Subject: [PATCH] Move alerts to their own page, add direct message alerts --- package.json | 22 +-- pnpm-lock.yaml | 172 +++++++++--------- src/app/components/AlertAdd.svelte | 97 ++-------- src/app/components/AlertDelete.svelte | 8 +- src/app/components/AlertItem.svelte | 32 +--- src/app/components/AlertStatus.svelte | 42 +++++ src/app/components/Alerts.svelte | 107 +++++++++-- src/app/components/ChatEnable.svelte | 12 +- src/app/components/ChatMenu.svelte | 56 ++++++ src/app/components/MenuSettings.svelte | 30 ++- src/app/components/ProfileDelete.svelte | 13 +- src/app/components/ThunkPending.svelte | 1 + src/app/core/commands.ts | 158 +++++++++++++--- src/app/core/state.ts | 29 +++ src/app/util/push.ts | 31 ++-- src/app/util/routes.ts | 28 +-- src/routes/+layout.svelte | 19 +- src/routes/chat/[chat]/+page.svelte | 10 +- src/routes/settings/+layout.svelte | 26 ++- src/routes/settings/alerts/+page.svelte | 7 + .../settings/{ => content}/+page.svelte | 0 src/routes/settings/profile/+page.svelte | 2 - 22 files changed, 592 insertions(+), 310 deletions(-) create mode 100644 src/app/components/AlertStatus.svelte create mode 100644 src/routes/settings/alerts/+page.svelte rename src/routes/settings/{ => content}/+page.svelte (100%) diff --git a/package.json b/package.json index f35906d9..8f0c4687 100644 --- a/package.json +++ b/package.json @@ -57,17 +57,17 @@ "@types/throttle-debounce": "^5.0.2", "@vite-pwa/assets-generator": "^0.2.6", "@vite-pwa/sveltekit": "^0.6.6", - "@welshman/app": "^0.4.4", - "@welshman/content": "^0.4.4", - "@welshman/editor": "^0.4.4", - "@welshman/feeds": "^0.4.4", - "@welshman/lib": "^0.4.4", - "@welshman/net": "^0.4.4", - "@welshman/relay": "^0.4.4", - "@welshman/router": "^0.4.4", - "@welshman/signer": "^0.4.4", - "@welshman/store": "^0.4.4", - "@welshman/util": "^0.4.4", + "@welshman/app": "^0.4.6", + "@welshman/content": "^0.4.6", + "@welshman/editor": "^0.4.6", + "@welshman/feeds": "^0.4.6", + "@welshman/lib": "^0.4.6", + "@welshman/net": "^0.4.6", + "@welshman/relay": "^0.4.6", + "@welshman/router": "^0.4.6", + "@welshman/signer": "^0.4.6", + "@welshman/store": "^0.4.6", + "@welshman/util": "^0.4.6", "compressorjs": "^1.2.1", "daisyui": "^4.12.10", "date-picker-svelte": "^2.13.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d5237497..27990320 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,38 +66,38 @@ importers: specifier: ^0.6.6 version: 0.6.8(@sveltejs/kit@2.20.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.25.10)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0)))(svelte@5.25.10)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0)))(@vite-pwa/assets-generator@0.2.6)(vite-plugin-pwa@0.21.2(@vite-pwa/assets-generator@0.2.6)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0))(workbox-build@7.3.0)(workbox-window@7.3.0)) '@welshman/app': - specifier: ^0.4.4 - version: 0.4.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) + specifier: ^0.4.6 + version: 0.4.6(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) '@welshman/content': - specifier: ^0.4.4 - version: 0.4.4(typescript@5.8.3) + specifier: ^0.4.6 + version: 0.4.6(typescript@5.8.3) '@welshman/editor': - specifier: ^0.4.4 - version: 0.4.4(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3) + specifier: ^0.4.6 + version: 0.4.6(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3) '@welshman/feeds': - specifier: ^0.4.4 - version: 0.4.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) + specifier: ^0.4.6 + version: 0.4.6(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) '@welshman/lib': - specifier: ^0.4.4 - version: 0.4.4 + specifier: ^0.4.6 + version: 0.4.6 '@welshman/net': - specifier: ^0.4.4 - version: 0.4.4(typescript@5.8.3)(ws@8.18.3) + specifier: ^0.4.6 + version: 0.4.6(typescript@5.8.3)(ws@8.18.3) '@welshman/relay': - specifier: ^0.4.4 - version: 0.4.4(typescript@5.8.3) + specifier: ^0.4.6 + version: 0.4.6(typescript@5.8.3) '@welshman/router': - specifier: ^0.4.4 - version: 0.4.4(typescript@5.8.3) + specifier: ^0.4.6 + version: 0.4.6(typescript@5.8.3) '@welshman/signer': - specifier: ^0.4.4 - version: 0.4.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) + specifier: ^0.4.6 + version: 0.4.6(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) '@welshman/store': - specifier: ^0.4.4 - version: 0.4.4(typescript@5.8.3) + specifier: ^0.4.6 + version: 0.4.6(typescript@5.8.3) '@welshman/util': - specifier: ^0.4.4 - version: 0.4.4(typescript@5.8.3) + specifier: ^0.4.6 + version: 0.4.6(typescript@5.8.3) compressorjs: specifier: ^1.2.1 version: 1.2.1 @@ -1640,41 +1640,41 @@ packages: '@vite-pwa/assets-generator': optional: true - '@welshman/app@0.4.4': - resolution: {integrity: sha512-QFOIiAokbI/sK0q3tmThjc6CaJmTLeOc4RdXL7CzMrMdV6Dkv0WKjLUdP+1nqxCrVlarIcY7gH0Ruw++LHx5cQ==} + '@welshman/app@0.4.6': + resolution: {integrity: sha512-u3BUyOCiBivJ847t7JvicgeedLpYDLg/2bie4d+OGJpdoNsI73/raGIfP5KBUqy2pgnZMIm4kOFztF46oDaSGQ==} - '@welshman/content@0.4.4': - resolution: {integrity: sha512-OUlEIrWpn03440yRkdcJ3tkXLyn5yxAZTDbvX9lbz7SxWZjg1jXdALq1izOVNC+E44e79oKj1HiqrSPFvFclsQ==} + '@welshman/content@0.4.6': + resolution: {integrity: sha512-1eqNroerKXcj7kI/X7oJq8h0F2Gn09RIgyNLWLds9qBXjIyK6JdpS7j8wj667NqXlCVkXbq49X1FNkWohtqdCw==} - '@welshman/editor@0.4.4': - resolution: {integrity: sha512-JmtenW/2Qtmv6J+KiTN0PgzJzuKA8+PVlJqQI7xGQ+C/b2ESbTRd/M6JV5a4OyYYB3pGChwcHnSHGIvHaYWpKA==} + '@welshman/editor@0.4.6': + resolution: {integrity: sha512-oFEIgoA/G7zjdw2QmrghNkaGUKYXI1axOYaW+PlPMOvx5iVFup9KYI8/M5eFtjndBlGC0qg/TR8fZYC/AUQmXg==} - '@welshman/feeds@0.4.4': - resolution: {integrity: sha512-KWon9e7Ad5oUrAXVS5NMa/YMnKhq8EGglwhtc93ad1e3tHpO9ekHfKrFaovYtfeHfgbBUi9SkJq/rJTC16IAxg==} + '@welshman/feeds@0.4.6': + resolution: {integrity: sha512-oVQASdYqwspAJLDYJUBI5ZNRp0Fl5gROqqulC3ftACvj6HNNjzeGfd9obYyJfyU7zNLHs8eEuLuSBBQoeOOoQg==} - '@welshman/lib@0.4.4': - resolution: {integrity: sha512-mHntmhEv9JOHWxD1dmmwoz3h8EjHh29ws0a57rWWAZgLq4u0U4pAwJlSgqm4FZdCn8TC5YHjdkIEGqfE55lPhw==} + '@welshman/lib@0.4.6': + resolution: {integrity: sha512-CyIlIPKxv/xaF+1U0wjA6MMiTOnoInkcECTaSzRXTaiFAwMl91MJpmV9X0zGK+SgUMoq91hM2GCUt301fcCXVg==} engines: {node: '>=12.0.0'} - '@welshman/net@0.4.4': - resolution: {integrity: sha512-d98+QFRsUhkuwRe3Cvp3r60yBxNRye/HGpJW/oxTL1EeSA2EEYwbi5nIu7dAEwTEPA9Z5i+DFnEi04nHsYgihw==} + '@welshman/net@0.4.6': + resolution: {integrity: sha512-sivSyKKTqYaUyU8335SoOwXi5IW/wz3rru97GrrmtLDPDWPbuLN5DBI8WL42YFxjIlIqCKzgagkMWwEWkYZpsA==} - '@welshman/relay@0.4.4': - resolution: {integrity: sha512-NRdFQFIP2/bxUC5fKPk0sLqwdsaNFGcQnxlRC/t0GwPgTnzL5p701XOkaV3yEVLz6N5l/vyttJ++7pNxEvLAWg==} + '@welshman/relay@0.4.6': + resolution: {integrity: sha512-Mg820k7bYR5PLEjtf0BNYXHMuJC4jELwjajqNYZ04gtcifyyY3x7uWnBBSVr6Hfipx97PCsrxWZiuuACI9vfsQ==} - '@welshman/router@0.4.4': - resolution: {integrity: sha512-P/PEhG+vbVjDg+9FRQYuAg/NarXoF02jbMkikEnOtgIHMOh9DKilujzdAu0Ch4lUfb5koDLV3p2GFNG+j4lLPA==} + '@welshman/router@0.4.6': + resolution: {integrity: sha512-5Yxhh+o8OA2MYgOOAKsd3CrOeR2im1Wc9st6bfj6fRNRuZ4AkHi8O1JRCdWEDEwYST/1G0x69mSPuYr3bub0Gw==} - '@welshman/signer@0.4.4': - resolution: {integrity: sha512-Ajv4uuBhS+WzpzkyjjkH5exsw4VSpV1j/0GwCmtNTNaYgDq+MjlH4gpvYj8QVDGXoeXHf1Lbr6WQz7i7mJSK9A==} + '@welshman/signer@0.4.6': + resolution: {integrity: sha512-KkmPPwyWRTDQQxed0cHZE2LxlsdzqnwqOzx9aKoSPh82QWR/3/K+EkZYtU6QMllwc/VUoYJUyHQRPOi0J012WA==} peerDependencies: nostr-signer-capacitor-plugin: ~0.0.4 - '@welshman/store@0.4.4': - resolution: {integrity: sha512-v9L5aw/K9JtdVkvEx6bVSqQ03Ghyy2py2PEEiT9c9zGeklKDjvDce52kDWFomV5Nlo/bgrCmpbdAQwo2Q9hRaQ==} + '@welshman/store@0.4.6': + resolution: {integrity: sha512-NU+5p1jQ6pG21QZmfVE9fXd85C+2aKxv5gCX1YY8uDOU1kqyTP4SmQtRmllx3ABxsOtCErayn3vGP6rekKy5Lw==} - '@welshman/util@0.4.4': - resolution: {integrity: sha512-iOTVqqB4Ef1T36dvQjvQv9EabjgYWgsKq1vFvCAFoKfLarnJJyqtMqRNumiicFWXcs+4xG1fBs1HN3kQXYan9A==} + '@welshman/util@0.4.6': + resolution: {integrity: sha512-Fc4Lz+goLNAKBMdQF19IeT6KJWPpk+dLdlS9H+ctIQNLCYbviz1yyKNCQ12MjkEjEQy0LEJGxo/yIPffA9x36g==} '@xml-tools/parser@1.0.11': resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==} @@ -6512,17 +6512,17 @@ snapshots: optionalDependencies: '@vite-pwa/assets-generator': 0.2.6 - '@welshman/app@0.4.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)': + '@welshman/app@0.4.6(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)': dependencies: '@types/throttle-debounce': 5.0.2 - '@welshman/feeds': 0.4.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) - '@welshman/lib': 0.4.4 - '@welshman/net': 0.4.4(typescript@5.8.3)(ws@8.18.3) - '@welshman/relay': 0.4.4(typescript@5.8.3) - '@welshman/router': 0.4.4(typescript@5.8.3) - '@welshman/signer': 0.4.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) - '@welshman/store': 0.4.4(typescript@5.8.3) - '@welshman/util': 0.4.4(typescript@5.8.3) + '@welshman/feeds': 0.4.6(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) + '@welshman/lib': 0.4.6 + '@welshman/net': 0.4.6(typescript@5.8.3)(ws@8.18.3) + '@welshman/relay': 0.4.6(typescript@5.8.3) + '@welshman/router': 0.4.6(typescript@5.8.3) + '@welshman/signer': 0.4.6(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) + '@welshman/store': 0.4.6(typescript@5.8.3) + '@welshman/util': 0.4.6(typescript@5.8.3) fuse.js: 7.1.0 idb: 8.0.2 svelte: 4.2.20 @@ -6532,14 +6532,14 @@ snapshots: - typescript - ws - '@welshman/content@0.4.4(typescript@5.8.3)': + '@welshman/content@0.4.6(typescript@5.8.3)': dependencies: '@braintree/sanitize-url': 7.1.1 nostr-tools: 2.14.2(typescript@5.8.3) transitivePeerDependencies: - typescript - '@welshman/editor@0.4.4(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)': + '@welshman/editor@0.4.6(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)': dependencies: '@tiptap/core': 2.12.0(@tiptap/pm@2.12.0) '@tiptap/extension-code': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) @@ -6554,8 +6554,8 @@ snapshots: '@tiptap/extension-text': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)) '@tiptap/pm': 2.12.0 '@tiptap/suggestion': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0) - '@welshman/lib': 0.4.4 - '@welshman/util': 0.4.4(typescript@5.8.3) + '@welshman/lib': 0.4.6 + '@welshman/util': 0.4.6(typescript@5.8.3) nostr-editor: 1.0.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(linkifyjs@4.3.1)(nostr-tools@2.14.2(typescript@5.8.3))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))) nostr-tools: 2.14.2(typescript@5.8.3) tippy.js: 6.3.7 @@ -6570,78 +6570,78 @@ snapshots: - tiptap-markdown - typescript - '@welshman/feeds@0.4.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)': + '@welshman/feeds@0.4.6(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)': dependencies: - '@welshman/lib': 0.4.4 - '@welshman/net': 0.4.4(typescript@5.8.3)(ws@8.18.3) - '@welshman/relay': 0.4.4(typescript@5.8.3) - '@welshman/router': 0.4.4(typescript@5.8.3) - '@welshman/signer': 0.4.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) - '@welshman/util': 0.4.4(typescript@5.8.3) + '@welshman/lib': 0.4.6 + '@welshman/net': 0.4.6(typescript@5.8.3)(ws@8.18.3) + '@welshman/relay': 0.4.6(typescript@5.8.3) + '@welshman/router': 0.4.6(typescript@5.8.3) + '@welshman/signer': 0.4.6(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) + '@welshman/util': 0.4.6(typescript@5.8.3) trava: 1.2.1 transitivePeerDependencies: - nostr-signer-capacitor-plugin - typescript - ws - '@welshman/lib@0.4.4': + '@welshman/lib@0.4.6': dependencies: '@scure/base': 1.2.6 '@types/events': 3.0.3 events: 3.3.0 - '@welshman/net@0.4.4(typescript@5.8.3)(ws@8.18.3)': + '@welshman/net@0.4.6(typescript@5.8.3)(ws@8.18.3)': dependencies: - '@welshman/lib': 0.4.4 - '@welshman/relay': 0.4.4(typescript@5.8.3) - '@welshman/util': 0.4.4(typescript@5.8.3) + '@welshman/lib': 0.4.6 + '@welshman/relay': 0.4.6(typescript@5.8.3) + '@welshman/util': 0.4.6(typescript@5.8.3) events: 3.3.0 isomorphic-ws: 5.0.0(ws@8.18.3) transitivePeerDependencies: - typescript - ws - '@welshman/relay@0.4.4(typescript@5.8.3)': + '@welshman/relay@0.4.6(typescript@5.8.3)': dependencies: - '@welshman/lib': 0.4.4 - '@welshman/util': 0.4.4(typescript@5.8.3) + '@welshman/lib': 0.4.6 + '@welshman/util': 0.4.6(typescript@5.8.3) transitivePeerDependencies: - typescript - '@welshman/router@0.4.4(typescript@5.8.3)': + '@welshman/router@0.4.6(typescript@5.8.3)': dependencies: - '@welshman/lib': 0.4.4 - '@welshman/relay': 0.4.4(typescript@5.8.3) - '@welshman/util': 0.4.4(typescript@5.8.3) + '@welshman/lib': 0.4.6 + '@welshman/relay': 0.4.6(typescript@5.8.3) + '@welshman/util': 0.4.6(typescript@5.8.3) transitivePeerDependencies: - typescript - '@welshman/signer@0.4.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)': + '@welshman/signer@0.4.6(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)': dependencies: '@noble/curves': 1.9.2 '@noble/hashes': 1.8.0 - '@welshman/lib': 0.4.4 - '@welshman/net': 0.4.4(typescript@5.8.3)(ws@8.18.3) - '@welshman/util': 0.4.4(typescript@5.8.3) + '@welshman/lib': 0.4.6 + '@welshman/net': 0.4.6(typescript@5.8.3)(ws@8.18.3) + '@welshman/util': 0.4.6(typescript@5.8.3) nostr-signer-capacitor-plugin: 0.0.4(@capacitor/core@7.2.0) nostr-tools: 2.14.2(typescript@5.8.3) transitivePeerDependencies: - typescript - ws - '@welshman/store@0.4.4(typescript@5.8.3)': + '@welshman/store@0.4.6(typescript@5.8.3)': dependencies: - '@welshman/lib': 0.4.4 - '@welshman/relay': 0.4.4(typescript@5.8.3) - '@welshman/util': 0.4.4(typescript@5.8.3) + '@welshman/lib': 0.4.6 + '@welshman/relay': 0.4.6(typescript@5.8.3) + '@welshman/util': 0.4.6(typescript@5.8.3) svelte: 4.2.20 transitivePeerDependencies: - typescript - '@welshman/util@0.4.4(typescript@5.8.3)': + '@welshman/util@0.4.6(typescript@5.8.3)': dependencies: '@types/ws': 8.18.1 - '@welshman/lib': 0.4.4 + '@welshman/lib': 0.4.6 js-base64: 3.7.7 nostr-tools: 2.14.2(typescript@5.8.3) nostr-wasm: 0.1.0 diff --git a/src/app/components/AlertAdd.svelte b/src/app/components/AlertAdd.svelte index 5e52e26f..b6dd6dad 100644 --- a/src/app/components/AlertAdd.svelte +++ b/src/app/components/AlertAdd.svelte @@ -1,17 +1,8 @@ + +{#if $status} + {@const statusText = getTagValue("status", $status.tags) || "error"} + {#if statusText === "ok"} + + Active + + {:else if statusText === "pending"} + + Pending + + {:else} + + {statusText.replace("-", " ").replace(/^(.)/, x => x.toUpperCase())} + + {/if} +{:else} + + Inactive + +{/if} diff --git a/src/app/components/Alerts.svelte b/src/app/components/Alerts.svelte index 6b9891c4..8516f54e 100644 --- a/src/app/components/Alerts.svelte +++ b/src/app/components/Alerts.svelte @@ -1,5 +1,7 @@ -
-
- - - Alerts - - +
+
+
+ + + Alerts + + +
+
+ {#each filteredAlerts as alert (alert.event.id)} + + {:else} +

Nothing here yet!

+ {/each} +
-
- {#each filteredAlerts as alert (alert.event.id)} - - {:else} -

Nothing here yet!

- {/each} +
+
+

Notify me about new direct messages

+ +
+ {#if $dmStatus} + {@const status = getTagValue("status", $dmStatus.tags) || "error"} + {#if status !== "ok"} +
+

+ {getTagValue("message", $dmStatus.tags) || + "The notification server did not respond to your request."} +

+
+ {/if} + {/if}
diff --git a/src/app/components/ChatEnable.svelte b/src/app/components/ChatEnable.svelte index cd9ecde0..7abc8198 100644 --- a/src/app/components/ChatEnable.svelte +++ b/src/app/components/ChatEnable.svelte @@ -1,7 +1,5 @@
@@ -24,4 +65,19 @@ Mark all read + {#if (!enablingAlert && $dmAlert) || disablingAlert} + + {:else} + + {/if}
diff --git a/src/app/components/MenuSettings.svelte b/src/app/components/MenuSettings.svelte index 61722afa..553240d9 100644 --- a/src/app/components/MenuSettings.svelte +++ b/src/app/components/MenuSettings.svelte @@ -4,6 +4,8 @@ import Settings from "@assets/icons/settings-minimalistic.svg?dataurl" import Code2 from "@assets/icons/code-2.svg?dataurl" import Exit from "@assets/icons/logout-3.svg?dataurl" + import Bell from "@assets/icons/bell.svg?dataurl" + import Wallet from "@assets/icons/wallet.svg?dataurl" import Icon from "@lib/components/Icon.svelte" import Link from "@lib/components/Link.svelte" import Button from "@lib/components/Button.svelte" @@ -29,6 +31,32 @@ {/snippet} + + + {#snippet icon()} +
+ {/snippet} + {#snippet title()} +
Alerts
+ {/snippet} + {#snippet info()} +
Set up email digests and push notifications
+ {/snippet} +
+ + + + {#snippet icon()} +
+ {/snippet} + {#snippet title()} +
Wallet
+ {/snippet} + {#snippet info()} +
Connect a bitcoin wallet for sending social tips
+ {/snippet} +
+ {#snippet icon()} @@ -42,7 +70,7 @@ {/snippet} - + {#snippet icon()}
diff --git a/src/app/components/ProfileDelete.svelte b/src/app/components/ProfileDelete.svelte index 079c7b95..29136621 100644 --- a/src/app/components/ProfileDelete.svelte +++ b/src/app/components/ProfileDelete.svelte @@ -7,9 +7,8 @@ DELETE, isReplaceable, getAddress, - getRelaysFromList, } from "@welshman/util" - import {pubkey, userRelaySelections, publishThunk, repository} from "@welshman/app" + import {pubkey, publishThunk, repository} from "@welshman/app" import {preventDefault} from "@lib/html" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl" @@ -20,7 +19,13 @@ import ModalFooter from "@lib/components/ModalFooter.svelte" import {pushToast} from "@app/util/toast" import {logout} from "@app/core/commands" - import {INDEXER_RELAYS, PLATFORM_NAME, userMembership, getMembershipUrls} from "@app/core/state" + import { + INDEXER_RELAYS, + PLATFORM_NAME, + userMembership, + getMembershipUrls, + userWriteRelays, + } from "@app/core/state" let progress: number | undefined = $state(undefined) let confirmText = $state("") @@ -43,7 +48,7 @@ const denominator = chunks.length + 2 const relays = uniq([ ...INDEXER_RELAYS, - ...getRelaysFromList($userRelaySelections), + ...$userWriteRelays, ...getMembershipUrls($userMembership), ]) diff --git a/src/app/components/ThunkPending.svelte b/src/app/components/ThunkPending.svelte index 4223a96c..218fbf48 100644 --- a/src/app/components/ThunkPending.svelte +++ b/src/app/components/ThunkPending.svelte @@ -25,6 +25,7 @@ class="underline transition-all" class:link={isSending} class:opacity-25={!isSending} + class:pointer-events-none={!isSending} onclick={stopPropagation(abort)}> Cancel diff --git a/src/app/core/commands.ts b/src/app/core/commands.ts index f806dae1..6d256bef 100644 --- a/src/app/core/commands.ts +++ b/src/app/core/commands.ts @@ -1,6 +1,7 @@ import {nwc} from "@getalby/sdk" import * as nip19 from "nostr-tools/nip19" import {get} from "svelte/store" +import type {Override, MakeOptional} from "@welshman/lib" import { randomId, append, @@ -11,10 +12,15 @@ import { equals, TIMEZONE, LOCALE, + parseJson, + fromPairs, } from "@welshman/lib" +import {decrypt} from "@welshman/signer" import type {Feed} from "@welshman/feeds" +import {makeIntersectionFeed, feedFromFilters, makeRelayFeed} from "@welshman/feeds" import type {TrustedEvent, EventContent} from "@welshman/util" import { + WRAP, DELETE, REPORT, PROFILE, @@ -44,6 +50,8 @@ import { toNostrURI, getRelaysFromList, RelayMode, + getAddress, + getTagValue, } from "@welshman/util" import {Pool, AuthStatus, SocketStatus} from "@welshman/net" import {Router} from "@welshman/router" @@ -54,7 +62,6 @@ import { repository, publishThunk, profilesByPubkey, - relaySelectionsByPubkey, tagEvent, tagEventForReaction, userRelaySelections, @@ -66,8 +73,9 @@ import { tagEventForComment, tagEventForQuote, waitForThunkError, + getPubkeyRelays, } from "@welshman/app" -import type {SettingsValues} from "@app/core/state" +import type {SettingsValues, Alert} from "@app/core/state" import { SETTINGS, PROTECTED, @@ -77,14 +85,18 @@ import { NOTIFIER_RELAY, userRoomsByUrl, userSettingsValues, + canDecrypt, + ensureUnwrapped, + userInboxRelays, } from "@app/core/state" +import {loadAlertStatuses} from "@app/core/requests" +import {platform, platformName, getPushInfo} from "@app/util/push" import {preferencesStorageProvider} from "@src/lib/storage" // Utils export const getPubkeyHints = (pubkey: string) => { - const selections = relaySelectionsByPubkey.get().get(pubkey) - const relays = selections ? getRelaysFromList(selections, RelayMode.Write) : [] + const relays = getPubkeyRelays(pubkey, RelayMode.Write) const hints = relays.length ? relays : INDEXER_RELAYS return hints @@ -417,27 +429,35 @@ export const publishComment = ({relays, ...params}: CommentParams & {relays: str // Alerts +export type AlertParamsEmail = { + cron: string + email: string + handler: string[] +} + +export type AlertParamsWeb = { + endpoint: string + p256dh: string + auth: string +} + +export type AlertParamsIos = { + device_token: string + bundle_identifier: string +} + +export type AlertParamsAndroid = { + device_token: string +} + export type AlertParams = { feed: Feed description: string - claims: Record - email?: { - cron: string - email: string - handler: string[] - } - web?: { - endpoint: string - p256dh: string - auth: string - } - ios?: { - device_token: string - bundle_identifier: string - } - android?: { - device_token: string - } + claims?: Record + email?: AlertParamsEmail + web?: AlertParamsWeb + ios?: AlertParamsIos + android?: AlertParamsAndroid } export const makeAlert = async (params: AlertParams) => { @@ -448,7 +468,7 @@ export const makeAlert = async (params: AlertParams) => { ["description", params.description], ] - for (const [relay, claim] of Object.entries(params.claims)) { + for (const [relay, claim] of Object.entries(params.claims || [])) { tags.push(["claim", relay, claim]) } @@ -481,6 +501,88 @@ export const makeAlert = async (params: AlertParams) => { export const publishAlert = async (params: AlertParams) => publishThunk({event: await makeAlert(params), relays: [NOTIFIER_RELAY]}) +export const deleteAlert = (alert: Alert) => { + const relays = [NOTIFIER_RELAY] + const tags = [["p", NOTIFIER_PUBKEY]] + + return publishDelete({event: alert.event, relays, tags, protect: false}) +} + +export type CreateAlertParams = Override< + AlertParams, + { + email?: MakeOptional + } +> + +export type CreateAlertResult = { + ok?: true + error?: string +} + +export const createAlert = async (params: CreateAlertParams): Promise => { + if (params.email) { + const cadence = params.email.cron.endsWith("1") ? "Weekly" : "Daily" + const handler = [ + "31990:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:1737058597050", + "wss://relay.nostr.band/", + "web", + ] + + params.email = {handler, ...params.email} + params.description = `${cadence} alert ${params.description}, sent via email.` + } else { + try { + // @ts-ignore + params[platform] = await getPushInfo() + params.description = `${platformName} push notification ${params.description}.` + } catch (e: any) { + return {error: String(e)} + } + } + + // If we don't do this we'll get an event rejection + await attemptAuth(NOTIFIER_RELAY) + + const thunk = await publishAlert(params as AlertParams) + const error = await waitForThunkError(thunk) + + if (error) { + return {error} + } + + // Fetch our new status to make sure it's active + const $pubkey = pubkey.get()! + const address = getAddress(thunk.event) + const statusEvents = await loadAlertStatuses($pubkey!) + const statusEvent = statusEvents.find(event => getTagValue("d", event.tags) === address) + const statusTags = statusEvent + ? parseJson(await decrypt(signer.get(), NOTIFIER_PUBKEY, statusEvent.content)) + : [] + const {status = "error", message = "Your alert was not activated"}: Record = + fromPairs(statusTags) + + if (status === "error") { + return {error: message} + } + + return {ok: true} +} + +export const createDmAlert = async () => { + if (!get(canDecrypt)) { + enableGiftWraps() + } + + return createAlert({ + description: `for direct messages.`, + feed: makeIntersectionFeed( + feedFromFilters([{kinds: [WRAP], "#p": [pubkey.get()!]}]), + makeRelayFeed(...get(userInboxRelays)), + ), + }) +} + // Settings export const makeSettings = async (params: Partial) => { @@ -532,3 +634,13 @@ export const payInvoice = async (invoice: string) => { .then(() => getWebLn().sendPayment(invoice)) } } + +// Gift Wraps + +export const enableGiftWraps = () => { + canDecrypt.set(true) + + for (const event of repository.query([{kinds: [WRAP]}])) { + ensureUnwrapped(event) + } +} diff --git a/src/app/core/state.ts b/src/app/core/state.ts index 636487f8..475968e9 100644 --- a/src/app/core/state.ts +++ b/src/app/core/state.ts @@ -21,6 +21,7 @@ import { identity, groupBy, always, + tryCatch, } from "@welshman/lib" import type {Socket} from "@welshman/net" import {Pool, load, AuthStateEvent, SocketEvent, netContext} from "@welshman/net" @@ -32,6 +33,7 @@ import { withGetter, synced, } from "@welshman/store" +import {isKindFeed, findFeed} from "@welshman/feeds" import { getIdFilters, WRAP, @@ -70,6 +72,8 @@ import { getTagValues, verifyEvent, makeEvent, + RelayMode, + getRelaysFromList, } from "@welshman/util" import type {TrustedEvent, SignedEvent, PublishedList, List, Filter} from "@welshman/util" import {Nip59, decrypt} from "@welshman/signer" @@ -94,6 +98,8 @@ import { appContext, getThunkError, publishThunk, + userRelaySelections, + userInboxRelaySelections, } from "@welshman/app" import type {Thunk, Relay} from "@welshman/app" import {preferencesStorageProvider} from "@src/lib/storage" @@ -375,6 +381,18 @@ export const relaysPendingTrust = writable([]) export const relaysMostlyRestricted = writable>({}) +// Relay selections + +export const userReadRelays = derived(userRelaySelections, $l => + getRelaysFromList($l, RelayMode.Read), +) + +export const userWriteRelays = derived(userRelaySelections, $l => + getRelaysFromList($l, RelayMode.Write), +) + +export const userInboxRelays = derived(userInboxRelaySelections, $l => getRelaysFromList($l)) + // Alerts export type Alert = { @@ -394,6 +412,17 @@ export const alerts = withGetter( }), ) +export const getAlertFeed = (alert: Alert) => + tryCatch(() => JSON.parse(getTagValue("feed", alert.tags)!)) + +export const dmAlert = derived(alerts, $alerts => + $alerts.find(alert => { + const feed = getAlertFeed(alert) + + return findFeed(feed, f => isKindFeed(f) && f.includes(WRAP)) + }), +) + // Alert Statuses export type AlertStatus = { diff --git a/src/app/util/push.ts b/src/app/util/push.ts index 0eae805d..6c069404 100644 --- a/src/app/util/push.ts +++ b/src/app/util/push.ts @@ -2,7 +2,7 @@ import * as nip19 from "nostr-tools/nip19" import {Capacitor} from "@capacitor/core" import type {ActionPerformed, RegistrationError, Token} from "@capacitor/push-notifications" import {PushNotifications} from "@capacitor/push-notifications" -import {parseJson, poll} from "@welshman/lib" +import {parseJson, sleep, poll} from "@welshman/lib" import {isSignedEvent} from "@welshman/util" import {goto} from "$app/navigation" import {ucFirst} from "@lib/util" @@ -50,6 +50,8 @@ export const getWebPushInfo = async () => { } const registration = await navigator.serviceWorker.ready + + // This will hang on firefox in development builds, but works in production let subscription = await registration.pushManager.getSubscription() if (!subscription) { @@ -118,14 +120,19 @@ export const getCapacitorPushInfo = async () => { return info } -export const getPushInfo = (): Promise> => { - switch (platform) { - case "web": - return getWebPushInfo() - case "ios": - case "android": - return getCapacitorPushInfo() - default: - throw new Error(`Invalid push platform: ${platform}`) - } -} +export const getPushInfo = (): Promise> => + new Promise((resolve, reject) => { + sleep(3000).then(() => reject("Failed to request notification permissions")) + + switch (platform) { + case "web": + getWebPushInfo().then(resolve, reject) + break + case "ios": + case "android": + getCapacitorPushInfo().then(resolve, reject) + break + default: + reject(`Invalid push platform: ${platform}`) + } + }) diff --git a/src/app/util/routes.ts b/src/app/util/routes.ts index c06328e8..117af85d 100644 --- a/src/app/util/routes.ts +++ b/src/app/util/routes.ts @@ -14,8 +14,10 @@ import { THREAD, ZAP_GOAL, EVENT_TIME, + getPubkeyTagValues, } from "@welshman/util" import { + ensureUnwrapped, makeChatId, entityLink, decodeRelay, @@ -74,24 +76,28 @@ export const getPrimaryNavItemIndex = ($page: Page) => { } export const goToEvent = async (event: TrustedEvent, options: Record = {}) => { - if (event.kind === DIRECT_MESSAGE || event.kind === DIRECT_MESSAGE_FILE) { - await scrollToEvent(event.id) - } + const unwrapped = await ensureUnwrapped(event) - const urls = Array.from(tracker.getRelays(event.id)) - const path = await getEventPath(event, urls) + if (unwrapped) { + const urls = Array.from(tracker.getRelays(unwrapped.id)) + const path = await getEventPath(unwrapped, urls) - if (path.includes("://")) { - window.open(path) - } else { - goto(path, options) + if (path.includes("://")) { + window.open(path) + } else { + goto(path, options) - await sleep(300) - await scrollToEvent(event.id) + await sleep(300) + await scrollToEvent(unwrapped.id) + } } } export const getEventPath = async (event: TrustedEvent, urls: string[]) => { + if (event.kind === DIRECT_MESSAGE || event.kind === DIRECT_MESSAGE_FILE) { + return makeChatPath([event.pubkey, ...getPubkeyTagValues(event.tags)]) + } + const room = getTagValue(ROOM, event.tags) if (urls.length > 0) { diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index c6ce64a8..a3a9e085 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -26,6 +26,11 @@ import type {TrustedEvent, StampedEvent} from "@welshman/util" import { WRAP, + ALERT_STATUS, + ALERT_EMAIL, + ALERT_WEB, + ALERT_IOS, + ALERT_ANDROID, EVENT_TIME, APP_DATA, THREAD, @@ -39,7 +44,6 @@ RELAYS, BLOSSOM_SERVERS, ROOMS, - getRelaysFromList, } from "@welshman/util" import {Nip46Broker, makeSecret} from "@welshman/signer" import type {Socket, RelayMessage, ClientMessage} from "@welshman/net" @@ -67,7 +71,6 @@ signerLog, dropSession, defaultStorageAdapters, - userInboxRelaySelections, loginWithNip01, loginWithNip46, EventsStorageAdapter, @@ -96,6 +99,7 @@ canDecrypt, getSetting, relaysMostlyRestricted, + userInboxRelays, } from "@app/core/state" import {loadUserData, listenForNotifications} from "@app/core/requests" import {theme} from "@app/util/theme" @@ -290,6 +294,11 @@ INBOX_RELAYS, ROOMS, APP_DATA, + ALERT_STATUS, + ALERT_EMAIL, + ALERT_WEB, + ALERT_IOS, + ALERT_ANDROID, ].includes(e.kind) ) { return 1 @@ -437,19 +446,19 @@ // Listen for chats, populate chat-based notifications let controller: AbortController - derived([pubkey, canDecrypt, userInboxRelaySelections], identity).subscribe( - ([$pubkey, $canDecrypt, $userInboxRelaySelections]) => { + derived([pubkey, canDecrypt, userInboxRelays], identity).subscribe( + ([$pubkey, $canDecrypt, $userInboxRelays]) => { controller?.abort() controller = new AbortController() if ($pubkey && $canDecrypt) { request({ signal: controller.signal, + relays: $userInboxRelays, filters: [ {kinds: [WRAP], "#p": [$pubkey], since: ago(WEEK, 2)}, {kinds: [WRAP], "#p": [$pubkey], limit: 100}, ], - relays: getRelaysFromList($userInboxRelaySelections), }) } }, diff --git a/src/routes/chat/[chat]/+page.svelte b/src/routes/chat/[chat]/+page.svelte index b5e71acb..c44568fa 100644 --- a/src/routes/chat/[chat]/+page.svelte +++ b/src/routes/chat/[chat]/+page.svelte @@ -1,11 +1,13 @@ diff --git a/src/routes/settings/+layout.svelte b/src/routes/settings/+layout.svelte index ee96332f..818ddc3a 100644 --- a/src/routes/settings/+layout.svelte +++ b/src/routes/settings/+layout.svelte @@ -8,6 +8,8 @@ import Moon from "@assets/icons/moon.svg?dataurl" import InfoSquare from "@assets/icons/info-square.svg?dataurl" import Exit from "@assets/icons/logout-3.svg?dataurl" + import GalleryMinimalistic from "@assets/icons/gallery-minimalistic.svg?dataurl" + import Bell from "@assets/icons/bell.svg?dataurl" import Icon from "@lib/components/Icon.svelte" import Page from "@lib/components/Page.svelte" import SecondaryNav from "@lib/components/SecondaryNav.svelte" @@ -30,37 +32,45 @@ + + Your Settings +
Profile
-
+
+ + Alerts + +
+
Wallet
-
+
Relays
-
- - Settings +
+ + Content
-
+
Theme
-
+
About
-
+
Log Out diff --git a/src/routes/settings/alerts/+page.svelte b/src/routes/settings/alerts/+page.svelte new file mode 100644 index 00000000..9e3b7c25 --- /dev/null +++ b/src/routes/settings/alerts/+page.svelte @@ -0,0 +1,7 @@ + + +
+ +
diff --git a/src/routes/settings/+page.svelte b/src/routes/settings/content/+page.svelte similarity index 100% rename from src/routes/settings/+page.svelte rename to src/routes/settings/content/+page.svelte diff --git a/src/routes/settings/profile/+page.svelte b/src/routes/settings/profile/+page.svelte index 79c8d6d4..b1ce4837 100644 --- a/src/routes/settings/profile/+page.svelte +++ b/src/routes/settings/profile/+page.svelte @@ -22,7 +22,6 @@ import ProfileDelete from "@app/components/ProfileDelete.svelte" import SignerStatus from "@app/components/SignerStatus.svelte" import InfoKeys from "@app/components/InfoKeys.svelte" - import Alerts from "@app/components/Alerts.svelte" import {PLATFORM_NAME} from "@app/core/state" import {pushModal} from "@app/util/modal" import {clip} from "@app/util/toast" @@ -141,7 +140,6 @@ {/if}
-