Compare commits

...

16 Commits

Author SHA1 Message Date
Jon Staab 037c8cb41b Disable zaps on ios 2025-07-17 14:39:59 -07:00
Jon Staab 79de2e1176 Bump version 2025-07-17 14:30:18 -07:00
Jon Staab d4b026a3ad Add zaps to threads/events 2025-07-15 15:56:55 -07:00
Jon Staab 00f383ff2e Add qr scanning for wallet connect 2025-07-15 15:49:26 -07:00
Jon Staab 6f6bb508db Handle invalid bunker url, update synced stores 2025-07-15 11:34:29 -07:00
Jon Staab e2a0672ca5 load messages in general on room relay 2025-07-09 14:28:07 -07:00
Jon Staab e2a5fe7a79 Fix sidebar overflow 2025-07-09 14:22:59 -07:00
Jon Staab 5d02ae75dc Bump welshman 2025-07-09 14:00:42 -07:00
Jon Staab 2460bbbc83 Fix balance coming from webln 2025-07-09 13:19:33 -07:00
Jon Staab 084d8d931b Load relay selections whenever we see a new pubkey 2025-07-09 09:17:45 -07:00
Jon Staab 6ee4ac1a89 Add funding goals 2025-07-07 15:28:36 -07:00
Jon Staab 1d07097350 Fix some zap bugs 2025-07-07 13:58:43 -07:00
Jon Staab 63d6b362c7 Remove info missing rooms 2025-07-07 12:46:17 -07:00
Jon Staab bfed277ea9 Add zaps 2025-07-04 06:22:19 -07:00
Jon Staab 9e8aa2ef3a show and copy npub 2025-07-02 16:48:44 -07:00
Jon Staab 4bbc0878f7 Bump apple version, add vapid key 2025-07-01 12:53:09 -07:00
49 changed files with 1544 additions and 255 deletions
+1 -1
View File
@@ -13,6 +13,6 @@ VITE_INDEXER_RELAYS=wss://purplepag.es/,wss://relay.damus.io/,wss://relay.nostr.
VITE_SIGNER_RELAYS=wss://relay.nsec.app/,wss://bucket.coracle.social/
VITE_NOTIFIER_PUBKEY=27b7c2ed89ef78322114225ea3ebf5f72c7767c2528d4d0c1854d039c00085df
VITE_NOTIFIER_RELAY=wss://anchor.coracle.social/
VITE_VAPID_PUBLIC_KEY=
VITE_VAPID_PUBLIC_KEY=BIt2D4BdgdbCowD_0d3Np6GbrIGHxd7aIEUeZNe3hQuRlHz02OhzvDaai0XSFoJYVzSzdMjdyW-QhvW9_yq8j4Y
VITE_GLITCHTIP_API_KEY=
GLITCHTIP_AUTH_TOKEN=
+10
View File
@@ -1,5 +1,15 @@
# Changelog
# 1.2.1
* Add zaps to chat, threads, and events
* Add funding goals
* Add NWC support
* Add wallet settings page
* Handle invalid bunker url
* Fix sidebar overflow
* Fix profile npub display
# 1.2.0
* Fix sort order of thread comments
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "social.flotilla"
minSdk rootProject.ext.minSdkVersion
targetSdk rootProject.ext.targetSdkVersion
versionCode 21
versionName "1.2.0"
versionCode 22
versionName "1.2.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
+4 -2
View File
@@ -1,8 +1,10 @@
#!/usr/bin/env bash
set -e
# Fetch tags and set to env vars
git fetch --prune --unshallow --tags
git describe --tags --abbrev=0
git fetch --prune --unshallow --tags || true
git describe --tags --abbrev=0 || true
export VITE_BUILD_VERSION=$RENDER_GIT_COMMIT
export VITE_BUILD_HASH=$RENDER_GIT_COMMIT
+4 -4
View File
@@ -354,14 +354,14 @@
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 13;
CURRENT_PROJECT_VERSION = 15;
DEVELOPMENT_TEAM = S26U9DYW3A;
INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.2.0;
MARKETING_VERSION = 1.2.1;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -380,14 +380,14 @@
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 13;
CURRENT_PROJECT_VERSION = 15;
DEVELOPMENT_TEAM = S26U9DYW3A;
INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.2.0;
MARKETING_VERSION = 1.2.1;
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
+16 -12
View File
@@ -1,6 +1,6 @@
{
"name": "flotilla",
"version": "1.2.0",
"version": "1.2.1",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -47,24 +47,26 @@
"@capacitor/keyboard": "^7.0.0",
"@capacitor/push-notifications": "^7.0.1",
"@capawesome/capacitor-badge": "^7.0.1",
"@getalby/sdk": "^5.1.0",
"@poppanator/sveltekit-svg": "^4.2.1",
"@sentry/browser": "^8.35.0",
"@sveltejs/adapter-static": "^3.0.4",
"@tiptap/core": "^2.12.0",
"@types/qrcode": "^1.5.5",
"@types/throttle-debounce": "^5.0.2",
"@vite-pwa/assets-generator": "^0.2.6",
"@vite-pwa/sveltekit": "^0.6.6",
"@welshman/app": "^0.3.8",
"@welshman/content": "^0.3.8",
"@welshman/editor": "^0.3.8",
"@welshman/feeds": "^0.3.8",
"@welshman/lib": "^0.3.8",
"@welshman/net": "^0.3.8",
"@welshman/relay": "^0.3.8",
"@welshman/router": "^0.3.8",
"@welshman/signer": "^0.3.8",
"@welshman/store": "^0.3.8",
"@welshman/util": "^0.3.8",
"@welshman/app": "^0.4.0",
"@welshman/content": "^0.4.0",
"@welshman/editor": "^0.4.0",
"@welshman/feeds": "^0.4.0",
"@welshman/lib": "^0.4.0",
"@welshman/net": "^0.4.0",
"@welshman/relay": "^0.4.0",
"@welshman/router": "^0.4.0",
"@welshman/signer": "^0.4.0",
"@welshman/store": "^0.4.0",
"@welshman/util": "^0.4.0",
"compressorjs": "^1.2.1",
"daisyui": "^4.12.10",
"date-picker-svelte": "^2.13.0",
@@ -76,7 +78,9 @@
"nostr-signer-capacitor-plugin": "^0.0.4",
"nostr-tools": "^2.14.2",
"prettier-plugin-tailwindcss": "^0.6.5",
"qr-scanner": "^1.4.2",
"qrcode": "^1.5.4",
"throttle-debounce": "^5.0.2",
"tippy.js": "^6.3.7"
},
"pnpm": {
+201 -140
View File
@@ -35,6 +35,9 @@ importers:
'@capawesome/capacitor-badge':
specifier: ^7.0.1
version: 7.0.1(@capacitor/core@7.2.0)
'@getalby/sdk':
specifier: ^5.1.0
version: 5.1.0(typescript@5.8.3)
'@poppanator/sveltekit-svg':
specifier: ^4.2.1
version: 4.2.1(rollup@2.79.2)(svelte@5.25.10)(svgo@3.3.2)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0))
@@ -50,6 +53,9 @@ importers:
'@types/qrcode':
specifier: ^1.5.5
version: 1.5.5
'@types/throttle-debounce':
specifier: ^5.0.2
version: 5.0.2
'@vite-pwa/assets-generator':
specifier: ^0.2.6
version: 0.2.6
@@ -57,38 +63,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.3.8
version: 0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
specifier: ^0.4.0
version: 0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/content':
specifier: ^0.3.8
version: 0.3.8(typescript@5.8.3)
specifier: ^0.4.0
version: 0.4.0(typescript@5.8.3)
'@welshman/editor':
specifier: ^0.3.8
version: 0.3.8(@tiptap/extension-image@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.23.0(@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.0
version: 0.4.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))(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.3.8
version: 0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
specifier: ^0.4.0
version: 0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/lib':
specifier: ^0.3.8
version: 0.3.8
specifier: ^0.4.0
version: 0.4.0
'@welshman/net':
specifier: ^0.3.8
version: 0.3.8(typescript@5.8.3)(ws@8.18.3)
specifier: ^0.4.0
version: 0.4.0(typescript@5.8.3)(ws@8.18.3)
'@welshman/relay':
specifier: ^0.3.8
version: 0.3.8(typescript@5.8.3)
specifier: ^0.4.0
version: 0.4.0(typescript@5.8.3)
'@welshman/router':
specifier: ^0.3.8
version: 0.3.8(typescript@5.8.3)
specifier: ^0.4.0
version: 0.4.0(typescript@5.8.3)
'@welshman/signer':
specifier: ^0.3.8
version: 0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
specifier: ^0.4.0
version: 0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/store':
specifier: ^0.3.8
version: 0.3.8(typescript@5.8.3)
specifier: ^0.4.0
version: 0.4.0(typescript@5.8.3)
'@welshman/util':
specifier: ^0.3.8
version: 0.3.8(typescript@5.8.3)
specifier: ^0.4.0
version: 0.4.0(typescript@5.8.3)
compressorjs:
specifier: ^1.2.1
version: 1.2.1
@@ -122,9 +128,15 @@ importers:
prettier-plugin-tailwindcss:
specifier: ^0.6.5
version: 0.6.11(prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@5.25.10))(prettier@3.5.3)
qr-scanner:
specifier: ^1.4.2
version: 1.4.2
qrcode:
specifier: ^1.5.4
version: 1.5.4
throttle-debounce:
specifier: ^5.0.2
version: 5.0.2
tippy.js:
specifier: ^6.3.7
version: 6.3.7
@@ -944,6 +956,14 @@ packages:
resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@getalby/lightning-tools@5.2.0':
resolution: {integrity: sha512-8kBvENBTMh541VjGKhw3I29+549/C02gLSh3AQaMfoMNSZaMxfQW+7dcMcc7vbFaCKEcEe18ST5bUveTRBuXCQ==}
engines: {node: '>=14'}
'@getalby/sdk@5.1.0':
resolution: {integrity: sha512-0ijo4enzoxZinyhOMFlR4h3qTQ9I0Se+dBkefk0ja5zOcpi61ZqT86n0T+7u94l8SH6/poysFBObdtN61u+6tQ==}
engines: {node: '>=14'}
'@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'}
@@ -1383,77 +1403,77 @@ packages:
peerDependencies:
'@tiptap/pm': ^2.7.0
'@tiptap/extension-code-block@2.23.0':
resolution: {integrity: sha512-p8iizp5nQBBhYPrIgBVwEqcDnc2fFRAZCXy/xjmAk2kKOhB7NYe3+1yrbFcQKVAdaUFxG+BRj2WxNDeeRt5tJA==}
'@tiptap/extension-code-block@2.26.1':
resolution: {integrity: sha512-/TDDOwONl0qEUc4+B6V9NnWtSjz95eg7/8uCb8Y8iRbGvI9vT4/znRKofFxstvKmW4URu/H74/g0ywV57h0B+A==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
'@tiptap/extension-code@2.23.0':
resolution: {integrity: sha512-Ip/5+kNoqrxYPHLnZMf7i6wfjjRuR5QgfC3IR3Mk1WQM1JGXCLL+uUjTUxKXFUj28hjSJfsmVbTUhoVvgZEWfw==}
'@tiptap/extension-code@2.26.1':
resolution: {integrity: sha512-GU9deB1A/Tr4FMPu71CvlcjGKwRhGYz60wQ8m4aM+ELZcVIcZRa1ebR8bExRIEWnvRztQuyRiCQzw2N0xQJ1QQ==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-document@2.23.0':
resolution: {integrity: sha512-kuRPqH0UdjZ4RcnpPELsu1N8LqeixEin+mv5eaQJI/aZ6rFq+kcY4UZF3C7q56Rat5r9CgHBiZbD0t5l6E3gdA==}
'@tiptap/extension-document@2.26.1':
resolution: {integrity: sha512-2P2IZp1NRAE+21mRuFBiP3X2WKfZ6kUC23NJKpn8bcOamY3obYqCt0ltGPhE4eR8n8QAl2fI/3jIgjR07dC8ow==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-dropcursor@2.23.0':
resolution: {integrity: sha512-m2LzkJpipHLPEllD3MXZQMssu7Xng7YJOJ8ZNDkF0uUkXljwh7G0ROjGNKUlV8/dqoCVmJIZIyF6t9saQwTTbA==}
'@tiptap/extension-dropcursor@2.26.1':
resolution: {integrity: sha512-JkDQU2ZYFOuT5mNYb8OiWGwD1HcjbtmX8tLNugQbToECmz9WvVPqJmn7V/q8VGpP81iEECz/IsyRmuf2kSD4uA==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
'@tiptap/extension-gapcursor@2.23.0':
resolution: {integrity: sha512-SpYsDtMiVwqcSB84g714PrnHo985R5UiIaGngef6iMNy/0xjKcO0tj/feu0WwJDuSj22Opzlnb/Ld/D4Va27Ng==}
'@tiptap/extension-gapcursor@2.26.1':
resolution: {integrity: sha512-KOiMZc3PwJS3hR0nSq5d0TJi2jkNZkLZElcT6pCEnhRHzPH6dRMu9GM5Jj798ZRUy0T9UFcKJalFZaDxnmRnpg==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
'@tiptap/extension-hard-break@2.23.0':
resolution: {integrity: sha512-OpNBEYv9HDUPo8SgvmI5oPd0b+xmdadtFyL7t4lxhYar8n5NDYubaXYgbKcdJfXvUxEeGwdc3ePnTFpsF0mrYw==}
'@tiptap/extension-hard-break@2.26.1':
resolution: {integrity: sha512-d6uStdNKi8kjPlHAyO59M6KGWATNwhLCD7dng0NXfwGndc22fthzIk/6j9F6ltQx30huy5qQram6j3JXwNACoA==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-history@2.23.0':
resolution: {integrity: sha512-W+2bZ/02nm56g/wmEaSx9QcdZ8mHjoFyc8MKf54Mrzi+nIdNjsNreKrn1yCp683CGEPd8DLadDFkz0o13N+rxA==}
'@tiptap/extension-history@2.26.1':
resolution: {integrity: sha512-m6YR1gkkauIDo3PRl0gP+7Oc4n5OqDzcjVh6LvWREmZP8nmi94hfseYbqOXUb6RPHIc0JKF02eiRifT4MSd2nw==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
'@tiptap/extension-image@2.23.0':
resolution: {integrity: sha512-/rW2+a21VBGBv5c/78CVW8XA7bThSqE3FqcBtWyq8IxZoe8Hj9+Jac7FcB2YR3aY0BeHwso474e1RuVr1iYBKQ==}
'@tiptap/extension-image@2.26.1':
resolution: {integrity: sha512-96+MaYBJebQlR/ik5W72GLUfXdEoxFs+6jsoERxbM5qEdhb7TEnodBFtWZOwgDO27kFd6rSNZuW9r5KJNtljEg==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-link@2.23.0':
resolution: {integrity: sha512-D+ethAE8+2f7RH7kqS+//EsC2wNblhmssJYVE0hCXM5BKIBixjs8eCOAvLbJsw0u/5LqFYjsyAimTqa4hD5uvg==}
'@tiptap/extension-link@2.26.1':
resolution: {integrity: sha512-7yfum5Jymkue/uOSTQPt2SmkZIdZx7t3QhZLqBU7R9ettkdSCBgEGok6N+scJM1R1Zes+maSckLm0JZw5BKYNA==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
'@tiptap/extension-paragraph@2.23.0':
resolution: {integrity: sha512-MXhRkb741UOcJp2evG/H0MY3WJQnX7z8PsejmJbJXOHBrS/Esxq0AlrDAjuFhbfAnJwYiWQ1lk6ucvKV6DhFuQ==}
'@tiptap/extension-paragraph@2.26.1':
resolution: {integrity: sha512-UezvM9VDRAVJlX1tykgHWSD1g3MKfVMWWZ+Tg+PE4+kizOwoYkRWznVPgCAxjmyHajxpCKRXgqTZkOxjJ9Kjzg==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-placeholder@2.23.0':
resolution: {integrity: sha512-I5RQk0qn6nj7l7z4mWKIxjO2nluvKsm00W2CbC75b4YcScBfsMInHQdjN2s+W8xuF0zquhwVITxA+Bmn4zynqg==}
'@tiptap/extension-placeholder@2.26.1':
resolution: {integrity: sha512-MBlqbkd+63btY7Qu+SqrXvWjPwooGZDsLTtl7jp52BczBl61cq9yygglt9XpM11TFMBdySgdLHBrLtQ0B7fBlw==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
'@tiptap/extension-text@2.23.0':
resolution: {integrity: sha512-hF+CU1H4B4UgqjBXXPPaACVZdSGuMH0TDYTd7h403qUAIBKkYbjuan7laQpiT0qnF0Dg+sGgvmGcd4H1tTBM8g==}
'@tiptap/extension-text@2.26.1':
resolution: {integrity: sha512-p2n8WVMd/2vckdJlol24acaTDIZAhI7qle5cM75bn01sOEZoFlSw6SwINOULrUCzNJsYb43qrLEibZb4j2LeQw==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm@2.12.0':
resolution: {integrity: sha512-TNzVwpeNzFfHAcYTOKqX9iU4fRxliyoZrCnERR+RRzeg7gWrXrCLubQt1WEx0sojMAfznshSL3M5HGsYjEbYwA==}
'@tiptap/suggestion@2.23.0':
resolution: {integrity: sha512-WUUGADu8ZezXZ4hXZWdfGcfoitB5tiBrc2u1oXqqL8VmJJedhY4MdWUPYqgh3359tAI2yJWmv+gPabX361gBEA==}
'@tiptap/suggestion@2.26.1':
resolution: {integrity: sha512-iNWJdQN7h01keNoVwyCsdI7ZX11YkrexZjCnutWK17Dd72s3NYVTmQXu7saftwddT4nDdlczNxAFosrt0zMhcg==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
@@ -1528,6 +1548,9 @@ packages:
'@types/normalize-package-data@2.4.4':
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
'@types/offscreencanvas@2019.7.3':
resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==}
'@types/qrcode@1.5.5':
resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
@@ -1609,41 +1632,41 @@ packages:
'@vite-pwa/assets-generator':
optional: true
'@welshman/app@0.3.8':
resolution: {integrity: sha512-WmvqB8Z/qPN0dJerf3QTp5MniZQGXToNXJWorEFA3LQwBJWAovPFfdq8xoNGe9tWwYl0m9Rt/ObZf3gFlX28Cw==}
'@welshman/app@0.4.0':
resolution: {integrity: sha512-LTlqbuiRFYAdwXIUYPOxaAusjhlj2ZgZlAuyEpQoBwNTyD7TUaTXj0kA5pbQZLFXWYuqDmrDB14Nl1zzBJBESQ==}
'@welshman/content@0.3.8':
resolution: {integrity: sha512-ic7imQR0cpolUlwnWVfUqiIo9zkOt6DS2M92BD4Y/mCLGrUMzlUw0/NE5TzBJ6dSywVh8/aBBOTWotzpmbttKg==}
'@welshman/content@0.4.0':
resolution: {integrity: sha512-3pWxr0Byc/Asmvlnq5UchkT0yeaGg63xTEk9fVJyzIrphIxn5bboaIixEw7y2w2lggFaqHgx+DFrulmhdJ9dXQ==}
'@welshman/editor@0.3.8':
resolution: {integrity: sha512-XZ/cXEM3MIwhR7CZvboH2askm9dZJ9cH7/CS4Asd3Q/OaaPUrCTCoacEpR1z3raMMOz0TiDn+BgjGqrHYU/58Q==}
'@welshman/editor@0.4.0':
resolution: {integrity: sha512-aIt/t+pMs2XKWZ6wN58jdPWlN9MXVdK1rccKk6Z54ckarCzB4B7usSZvstwMMkmZra/HPLOaWw5KXqhDR1YiUA==}
'@welshman/feeds@0.3.8':
resolution: {integrity: sha512-Rjf36Eng22PMY9p1yepMDfXH2dlCPg5yBVidu8WArFWLgxSv67DCxAsDlHkRd/mJD5DARFDob1WZP9bUsljJBw==}
'@welshman/feeds@0.4.0':
resolution: {integrity: sha512-fwQ4eDzEtcSxFj2LKps6XYFXuZv6lFXKDTq+Nvs5tNYYJUbv/Cz4x3aLQo2ivInz9gAMOLmgpIgNCxkzMqCnoQ==}
'@welshman/lib@0.3.8':
resolution: {integrity: sha512-fbq94UkyoC7kieAlWsDzH6zgZt+GVhkh1RiFvtE4iGug3bIPxh1QmmvO70EHlFkiEDcYAFEEHd9OMm6/JRc97Q==}
'@welshman/lib@0.4.0':
resolution: {integrity: sha512-1GPQ2X1FT2R55KWPKhDs+ZK/EkVpeMkVwWdSXC88w+YCoUop00keFm7P452kQKgA/lixNURJSyeWgfI2tUdpkQ==}
engines: {node: '>=12.0.0'}
'@welshman/net@0.3.8':
resolution: {integrity: sha512-DWNL+BGmOGCfXdYOnpd3IJ7IuCskftUfXnjBo8F0rkHWUWAE6Q1zTJmdJG3kDVDoWl3X4XBfxkl4WGckF9pSkw==}
'@welshman/net@0.4.0':
resolution: {integrity: sha512-QBU5dsALCr9V51lIyNseUDIfvjCJo6VFWe6G1gkJ1PQGh5rgJNZJWCaD430PDpCKsufv2JIkCVYZG5xYZgxzMg==}
'@welshman/relay@0.3.8':
resolution: {integrity: sha512-vWUOxvG4WV0+EsC/BYoCF9L2W/qml4TIAnGHmWpDt9jTS82kIQt3cu2+3uJs9IZli+etRJ3xYJLVvfzfQqBaig==}
'@welshman/relay@0.4.0':
resolution: {integrity: sha512-5zTPSDPhMR2v55hotQf4JO3XgBXEws4k/xChAbYZDfUwxG7HQxmDM2n56aFrKqgI3w+qp8l1lx/1KeksKvBiWw==}
'@welshman/router@0.3.8':
resolution: {integrity: sha512-3Gn3yjMbQ9sQ8qsX5bjUtTSKkwdOREqGVzcQiBq1FRATh42ih06Opu5/t8ujcMRMhV3w/02ckfZJ46DF34ozWQ==}
'@welshman/router@0.4.0':
resolution: {integrity: sha512-ccpx9QrJ7Uq3CI7r/PyBOwO0G/2xknKXN0xLW995hta1Z1bUzYWhz+C9YvoceDPHCUPaZotBCgdmpY9oOiYqHg==}
'@welshman/signer@0.3.8':
resolution: {integrity: sha512-L3hAJlS0smS4uSC5b1xskHDbrkuCykXr5b6Z9aNV9MJjczbb5pCuYjHf9lCvcmqaz4NN4kcksXlLZgxMbE/5Jg==}
'@welshman/signer@0.4.0':
resolution: {integrity: sha512-I+4l1gmSBVQkFtu6Bm5aAxsFXlE5oXeCsUX+GSsTb0Pg1e4FnTMgeaI3xM8tcCLma4EK+3mD7Yi9MaZMSYX8YQ==}
peerDependencies:
nostr-signer-capacitor-plugin: ~0.0.4
'@welshman/store@0.3.8':
resolution: {integrity: sha512-SHKF9RjcvoqcduHDleKSjhubrS10f5XmcU2kBPFy/U9G7hi4M5f4HQa46Kt7Mf4OHuofnjRw+4RLopkqjFL3ow==}
'@welshman/store@0.4.0':
resolution: {integrity: sha512-/gIX1hTTGPkhFlMm91oY7khqriIZwnNIFs3leWIbJGWXLd4pd4fMFM7bKuOTuEsnHkB9thkeXurAdYMquhlHOw==}
'@welshman/util@0.3.8':
resolution: {integrity: sha512-+doowqtIjUChPGdmGNopO6bAvC/0LkF2zKaEKPHnIfDAsw4gAJyuAvIlS/PUv8tY6scXORGeGMnoBC0Htd3Ovg==}
'@welshman/util@0.4.0':
resolution: {integrity: sha512-UiJyqXeWEx0s83M0AD/bN5ylvpCfYUSjLepb0QxxZpBPnZgMj07oO1OS4967QFuePSvmjTQNcxy3ABm8aagxGg==}
'@xml-tools/parser@1.0.11':
resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==}
@@ -3412,6 +3435,14 @@ packages:
peerDependencies:
'@capacitor/core': ^7.0.0
nostr-tools@2.12.0:
resolution: {integrity: sha512-pUWEb020gTvt1XZvTa8AKNIHWFapjsv2NKyk43Ez2nnvz6WSXsrTFE0XtkNLSRBjPn6EpxumKeNiVzLz74jNSA==}
peerDependencies:
typescript: '>=5.0.0'
peerDependenciesMeta:
typescript:
optional: true
nostr-tools@2.14.2:
resolution: {integrity: sha512-YOIOn5EdJ2Kq5sQW5Zh4wOcqzR6kUyrCDHG4+mVD2szzthsyOTpiWX0yrwaRZGlHJG6q83vkhg95qc2W201XTQ==}
peerDependencies:
@@ -3835,6 +3866,9 @@ packages:
(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)
qr-scanner@1.4.2:
resolution: {integrity: sha512-kV1yQUe2FENvn59tMZW6mOVfpq9mGxGf8l6+EGaXUOd4RBOLg7tRC83OrirM5AtDvZRpdjdlXURsHreAOSPOUw==}
qrcode@1.5.4:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
engines: {node: '>=10.13.0'}
@@ -5689,6 +5723,15 @@ snapshots:
'@eslint/core': 0.13.0
levn: 0.4.1
'@getalby/lightning-tools@5.2.0': {}
'@getalby/sdk@5.1.0(typescript@5.8.3)':
dependencies:
'@getalby/lightning-tools': 5.2.0
nostr-tools: 2.12.0(typescript@5.8.3)
transitivePeerDependencies:
- typescript
'@humanfs/core@0.19.1': {}
'@humanfs/node@0.16.6':
@@ -6169,58 +6212,58 @@ snapshots:
dependencies:
'@tiptap/pm': 2.12.0
'@tiptap/extension-code-block@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
'@tiptap/extension-code-block@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
'@tiptap/extension-code@2.23.0(@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))':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/extension-document@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
'@tiptap/extension-document@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/extension-dropcursor@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
'@tiptap/extension-dropcursor@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
'@tiptap/extension-gapcursor@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
'@tiptap/extension-gapcursor@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
'@tiptap/extension-hard-break@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
'@tiptap/extension-hard-break@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/extension-history@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
'@tiptap/extension-history@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
'@tiptap/extension-image@2.23.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))':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/extension-link@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@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)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
linkifyjs: 4.3.1
'@tiptap/extension-paragraph@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
'@tiptap/extension-paragraph@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/extension-placeholder@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
'@tiptap/extension-placeholder@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
'@tiptap/extension-text@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
'@tiptap/extension-text@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
@@ -6245,7 +6288,7 @@ snapshots:
prosemirror-transform: 1.10.4
prosemirror-view: 1.39.3
'@tiptap/suggestion@2.23.0(@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)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
@@ -6341,6 +6384,8 @@ snapshots:
'@types/normalize-package-data@2.4.4': {}
'@types/offscreencanvas@2019.7.3': {}
'@types/qrcode@1.5.5':
dependencies:
'@types/node': 22.14.0
@@ -6454,17 +6499,17 @@ snapshots:
optionalDependencies:
'@vite-pwa/assets-generator': 0.2.6
'@welshman/app@0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
'@welshman/app@0.4.0(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.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/lib': 0.3.8
'@welshman/net': 0.3.8(typescript@5.8.3)(ws@8.18.3)
'@welshman/relay': 0.3.8(typescript@5.8.3)
'@welshman/router': 0.3.8(typescript@5.8.3)
'@welshman/signer': 0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/store': 0.3.8(typescript@5.8.3)
'@welshman/util': 0.3.8(typescript@5.8.3)
'@welshman/feeds': 0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/lib': 0.4.0
'@welshman/net': 0.4.0(typescript@5.8.3)(ws@8.18.3)
'@welshman/relay': 0.4.0(typescript@5.8.3)
'@welshman/router': 0.4.0(typescript@5.8.3)
'@welshman/signer': 0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/store': 0.4.0(typescript@5.8.3)
'@welshman/util': 0.4.0(typescript@5.8.3)
fuse.js: 7.1.0
idb: 8.0.2
svelte: 4.2.20
@@ -6474,31 +6519,31 @@ snapshots:
- typescript
- ws
'@welshman/content@0.3.8(typescript@5.8.3)':
'@welshman/content@0.4.0(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.3.8(@tiptap/extension-image@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.23.0(@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.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))(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.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-code-block': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-document': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-dropcursor': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-gapcursor': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-hard-break': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-history': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-paragraph': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-placeholder': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-text': 2.23.0(@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))
'@tiptap/extension-code-block': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-document': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-dropcursor': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-gapcursor': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-hard-break': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-history': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-paragraph': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-placeholder': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-text': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/pm': 2.12.0
'@tiptap/suggestion': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@welshman/lib': 0.3.8
'@welshman/util': 0.3.8(typescript@5.8.3)
nostr-editor: 1.0.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-image@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.23.0(@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)))
'@tiptap/suggestion': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@welshman/lib': 0.4.0
'@welshman/util': 0.4.0(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
transitivePeerDependencies:
@@ -6512,78 +6557,78 @@ snapshots:
- tiptap-markdown
- typescript
'@welshman/feeds@0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
'@welshman/feeds@0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
dependencies:
'@welshman/lib': 0.3.8
'@welshman/net': 0.3.8(typescript@5.8.3)(ws@8.18.3)
'@welshman/relay': 0.3.8(typescript@5.8.3)
'@welshman/router': 0.3.8(typescript@5.8.3)
'@welshman/signer': 0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/util': 0.3.8(typescript@5.8.3)
'@welshman/lib': 0.4.0
'@welshman/net': 0.4.0(typescript@5.8.3)(ws@8.18.3)
'@welshman/relay': 0.4.0(typescript@5.8.3)
'@welshman/router': 0.4.0(typescript@5.8.3)
'@welshman/signer': 0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/util': 0.4.0(typescript@5.8.3)
trava: 1.2.1
transitivePeerDependencies:
- nostr-signer-capacitor-plugin
- typescript
- ws
'@welshman/lib@0.3.8':
'@welshman/lib@0.4.0':
dependencies:
'@scure/base': 1.2.6
'@types/events': 3.0.3
events: 3.3.0
'@welshman/net@0.3.8(typescript@5.8.3)(ws@8.18.3)':
'@welshman/net@0.4.0(typescript@5.8.3)(ws@8.18.3)':
dependencies:
'@welshman/lib': 0.3.8
'@welshman/relay': 0.3.8(typescript@5.8.3)
'@welshman/util': 0.3.8(typescript@5.8.3)
'@welshman/lib': 0.4.0
'@welshman/relay': 0.4.0(typescript@5.8.3)
'@welshman/util': 0.4.0(typescript@5.8.3)
events: 3.3.0
isomorphic-ws: 5.0.0(ws@8.18.3)
transitivePeerDependencies:
- typescript
- ws
'@welshman/relay@0.3.8(typescript@5.8.3)':
'@welshman/relay@0.4.0(typescript@5.8.3)':
dependencies:
'@welshman/lib': 0.3.8
'@welshman/util': 0.3.8(typescript@5.8.3)
'@welshman/lib': 0.4.0
'@welshman/util': 0.4.0(typescript@5.8.3)
transitivePeerDependencies:
- typescript
'@welshman/router@0.3.8(typescript@5.8.3)':
'@welshman/router@0.4.0(typescript@5.8.3)':
dependencies:
'@welshman/lib': 0.3.8
'@welshman/relay': 0.3.8(typescript@5.8.3)
'@welshman/util': 0.3.8(typescript@5.8.3)
'@welshman/lib': 0.4.0
'@welshman/relay': 0.4.0(typescript@5.8.3)
'@welshman/util': 0.4.0(typescript@5.8.3)
transitivePeerDependencies:
- typescript
'@welshman/signer@0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
'@welshman/signer@0.4.0(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.3.8
'@welshman/net': 0.3.8(typescript@5.8.3)(ws@8.18.3)
'@welshman/util': 0.3.8(typescript@5.8.3)
'@welshman/lib': 0.4.0
'@welshman/net': 0.4.0(typescript@5.8.3)(ws@8.18.3)
'@welshman/util': 0.4.0(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.3.8(typescript@5.8.3)':
'@welshman/store@0.4.0(typescript@5.8.3)':
dependencies:
'@welshman/lib': 0.3.8
'@welshman/relay': 0.3.8(typescript@5.8.3)
'@welshman/util': 0.3.8(typescript@5.8.3)
'@welshman/lib': 0.4.0
'@welshman/relay': 0.4.0(typescript@5.8.3)
'@welshman/util': 0.4.0(typescript@5.8.3)
svelte: 4.2.20
transitivePeerDependencies:
- typescript
'@welshman/util@0.3.8(typescript@5.8.3)':
'@welshman/util@0.4.0(typescript@5.8.3)':
dependencies:
'@types/ws': 8.18.1
'@welshman/lib': 0.3.8
'@welshman/lib': 0.4.0
js-base64: 3.7.7
nostr-tools: 2.14.2(typescript@5.8.3)
nostr-wasm: 0.1.0
@@ -8415,11 +8460,11 @@ snapshots:
normalize-range@0.1.2: {}
nostr-editor@1.0.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-image@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.23.0(@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-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))):
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/extension-image': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-link': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@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
js-base64: 3.7.7
light-bolt11-decoder: 3.2.0
@@ -8435,6 +8480,18 @@ snapshots:
dependencies:
'@capacitor/core': 7.2.0
nostr-tools@2.12.0(typescript@5.8.3):
dependencies:
'@noble/ciphers': 0.5.3
'@noble/curves': 1.2.0
'@noble/hashes': 1.3.1
'@scure/base': 1.1.1
'@scure/bip32': 1.3.1
'@scure/bip39': 1.2.1
optionalDependencies:
nostr-wasm: 0.1.0
typescript: 5.8.3
nostr-tools@2.14.2(typescript@5.8.3):
dependencies:
'@noble/ciphers': 0.5.3
@@ -8818,6 +8875,10 @@ snapshots:
q@1.5.1: {}
qr-scanner@1.4.2:
dependencies:
'@types/offscreencanvas': 2019.7.3
qrcode@1.5.4:
dependencies:
dijkstrajs: 1.0.3
+20 -3
View File
@@ -1,3 +1,4 @@
import {nwc} from "@getalby/sdk"
import * as nip19 from "nostr-tools/nip19"
import {get} from "svelte/store"
import {randomId, flatten, poll, uniq, equals, TIMEZONE, LOCALE} from "@welshman/lib"
@@ -55,6 +56,8 @@ import {
getThunkError,
} from "@welshman/app"
import {
wallet,
getWebLn,
PROTECTED,
userMembership,
INDEXER_RELAYS,
@@ -242,9 +245,7 @@ export const checkRelayAccess = async (url: string, claim = "") => {
if (error?.startsWith("mute: ")) return
// Ignore rejected empty claims
if (!claim && error?.includes("invite code")) {
return `failed to request access to relay`
}
if (!claim && error?.includes("invite code")) return
return message
}
@@ -445,3 +446,19 @@ export const makeAlert = async (params: AlertParams) => {
export const publishAlert = async (params: AlertParams) =>
publishThunk({event: await makeAlert(params), relays: [NOTIFIER_RELAY]})
export const payInvoice = async (invoice: string) => {
const $wallet = get(wallet)
if (!$wallet) {
throw new Error("No wallet is connected")
}
if ($wallet.type === "nwc") {
return new nwc.NWCClient($wallet.info).payInvoice({invoice})
} else if ($wallet.type === "webln") {
return getWebLn()
.enable()
.then(() => getWebLn().sendPayment(invoice))
}
}
+5 -1
View File
@@ -11,10 +11,11 @@
import ThunkStatus from "@app/components/ThunkStatus.svelte"
import ProfileDetail from "@app/components/ProfileDetail.svelte"
import ReactionSummary from "@app/components/ReactionSummary.svelte"
import ChannelMessageZapButton from "@app/components/ChannelMessageZapButton.svelte"
import ChannelMessageEmojiButton from "@app/components/ChannelMessageEmojiButton.svelte"
import ChannelMessageMenuButton from "@app/components/ChannelMessageMenuButton.svelte"
import ChannelMessageMenuMobile from "@app/components/ChannelMessageMenuMobile.svelte"
import {colors} from "@app/state"
import {colors, ENABLE_ZAPS} from "@app/state"
import {publishDelete, publishReaction} from "@app/commands"
import {pushModal} from "@app/modal"
@@ -94,6 +95,9 @@
<button
class="join absolute right-1 top-1 border border-solid border-neutral text-xs opacity-0 transition-all"
class:group-hover:opacity-100={!isMobile}>
{#if ENABLE_ZAPS}
<ChannelMessageZapButton {url} {event} />
{/if}
<ChannelMessageEmojiButton {url} {event} />
{#if replyTo}
<Button class="btn join-item btn-xs" onclick={reply}>
@@ -0,0 +1,10 @@
<script lang="ts">
import Icon from "@lib/components/Icon.svelte"
import ZapButton from "@app/components/ZapButton.svelte"
const {url, event} = $props()
</script>
<ZapButton {url} {event} class="btn join-item btn-xs">
<Icon icon="bolt" size={4} />
</ZapButton>
+1 -1
View File
@@ -52,7 +52,7 @@
alt="Link preview"
onerror={onError}
src={imgproxy(preview.image)}
class="bg-alt max-h-72 object-contain object-center" />
class="bg-alt max-h-72 rounded-t-box object-contain object-center" />
{/if}
<div class="flex flex-col gap-2 p-4">
<strong class="overflow-hidden text-ellipsis whitespace-nowrap"
+9 -1
View File
@@ -6,18 +6,21 @@
import Icon from "@lib/components/Icon.svelte"
import Tippy from "@lib/components/Tippy.svelte"
import Button from "@lib/components/Button.svelte"
import ZapButton from "@app/components/ZapButton.svelte"
import EmojiButton from "@lib/components/EmojiButton.svelte"
import EventMenu from "@app/components/EventMenu.svelte"
import {ENABLE_ZAPS} from "@app/state"
import {publishReaction} from "@app/commands"
type Props = {
url: string
noun: string
event: TrustedEvent
hideZap?: boolean
customActions?: Snippet
}
const {url, noun, event, customActions}: Props = $props()
const {url, noun, event, hideZap, customActions}: Props = $props()
const showPopover = () => popover?.show()
@@ -30,6 +33,11 @@
</script>
<Button class="join rounded-full">
{#if ENABLE_ZAPS && !hideZap}
<ZapButton {url} {event} class="btn join-item btn-neutral btn-xs">
<Icon icon="bolt" size={4} />
</ZapButton>
{/if}
<EmojiButton {onEmoji} class="btn join-item btn-neutral btn-xs">
<Icon icon="smile-circle" size={4} />
</EmojiButton>
+35
View File
@@ -0,0 +1,35 @@
<script lang="ts">
import type {TrustedEvent, EventContent} from "@welshman/util"
import ReactionSummary from "@app/components/ReactionSummary.svelte"
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
import EventActivity from "@app/components/EventActivity.svelte"
import EventActions from "@app/components/EventActions.svelte"
import {publishDelete, publishReaction} from "@app/commands"
import {makeGoalPath} from "@app/routes"
interface Props {
url: any
event: any
showActivity?: boolean
}
const {url, event, showActivity = false}: Props = $props()
const path = makeGoalPath(url, event.id)
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
const createReaction = (template: EventContent) =>
publishReaction({...template, event, relays: [url]})
</script>
<div class="flex flex-wrap items-center justify-between gap-2">
<div class="flex flex-grow flex-wrap justify-end gap-2">
<ReactionSummary {url} {event} {deleteReaction} {createReaction} reactionClass="tooltip-left" />
<ThunkStatusOrDeleted {event} />
{#if showActivity}
<EventActivity {url} {path} {event} />
{/if}
<EventActions {url} {event} hideZap noun="Goal" />
</div>
</div>
+146
View File
@@ -0,0 +1,146 @@
<script lang="ts">
import {writable} from "svelte/store"
import {makeEvent, ZAP_GOAL} from "@welshman/util"
import {publishThunk} from "@welshman/app"
import {isMobile, preventDefault} from "@lib/html"
import Icon from "@lib/components/Icon.svelte"
import Field from "@lib/components/Field.svelte"
import FieldInline from "@lib/components/FieldInline.svelte"
import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import EditorContent from "@app/editor/EditorContent.svelte"
import {pushToast} from "@app/toast"
import {PROTECTED} from "@app/state"
import {makeEditor} from "@app/editor"
const {url} = $props()
const uploading = writable(false)
const back = () => history.back()
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
const submit = async () => {
if ($uploading) return
if (!content) {
return pushToast({
theme: "error",
message: "Please provide a title for your funding goal.",
})
}
const ed = await editor
const summary = ed.getText({blockSeparator: "\n"}).trim()
if (!summary.trim()) {
return pushToast({
theme: "error",
message: "Please provide details about your funding goal.",
})
}
const tags = [
...ed.storage.nostr.getEditorTags(),
["summary", summary],
["amount", String(amount)],
["relays", url],
PROTECTED,
]
publishThunk({
relays: [url],
event: makeEvent(ZAP_GOAL, {content, tags}),
})
history.back()
}
const editor = makeEditor({url, submit, uploading, placeholder: "What's on your mind?"})
let content = $state("")
let amount = $state(1000)
</script>
<form class="column gap-4" onsubmit={preventDefault(submit)}>
<ModalHeader>
{#snippet title()}
<div>Create a Funding Goal</div>
{/snippet}
{#snippet info()}
<div>Request contributions for your fundraiser.</div>
{/snippet}
</ModalHeader>
<div class="col-8 relative">
<Field>
{#snippet label()}
<p>Title*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<!-- svelte-ignore a11y_autofocus -->
<input
autofocus={!isMobile}
bind:value={content}
class="grow"
type="text"
placeholder="What do funds go towards?" />
</label>
{/snippet}
</Field>
<div class="relative">
<Field>
{#snippet label()}
<p>Details*</p>
{/snippet}
{#snippet input()}
<div class="note-editor flex-grow overflow-hidden">
<EditorContent {editor} />
</div>
{/snippet}
</Field>
<Button
data-tip="Add an image"
class="tooltip tooltip-left absolute bottom-1 right-2"
onclick={selectFiles}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon="paperclip" size={3} />
{/if}
</Button>
</div>
<div class="flex flex-col gap-1">
<FieldInline>
{#snippet label()}
Goal Amount (sats)*
{/snippet}
{#snippet input()}
<div class="flex flex-grow justify-end">
<label class="input input-bordered flex items-center gap-2">
<Icon icon="bolt" />
<input bind:value={amount} type="number" class="w-28" />
<p class="opacity-50">sats</p>
</label>
</div>
{/snippet}
</FieldInline>
<input
class="range range-primary -mt-2"
type="range"
min="1000"
max="100000"
step="1000"
bind:value={amount} />
</div>
</div>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" />
Go back
</Button>
<Button type="submit" class="btn btn-primary">Create Goal</Button>
</ModalFooter>
</form>
+36
View File
@@ -0,0 +1,36 @@
<script lang="ts">
import type {TrustedEvent} from "@welshman/util"
import {getTagValue} from "@welshman/util"
import Link from "@lib/components/Link.svelte"
import Content from "@app/components/Content.svelte"
import ProfileLink from "@app/components/ProfileLink.svelte"
import GoalActions from "@app/components/GoalActions.svelte"
import GoalSummary from "@app/components/GoalSummary.svelte"
import {makeGoalPath} from "@app/routes"
type Props = {
url: string
event: TrustedEvent
}
const {url, event}: Props = $props()
const summary = getTagValue("summary", event.tags)
</script>
<Link class="col-2 card2 bg-alt w-full cursor-pointer" href={makeGoalPath(url, event.id)}>
<p class="text-2xl">{event.content}</p>
<Content
event={{content: summary, tags: event.tags}}
{url}
expandMode="inline"
minLength={50}
maxLength={300} />
<GoalSummary {url} {event} />
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
<span class="whitespace-nowrap py-1 text-sm opacity-75">
Posted by <ProfileLink pubkey={event.pubkey} {url} />
</span>
<GoalActions showActivity {url} {event} />
</div>
</Link>
+49
View File
@@ -0,0 +1,49 @@
<script lang="ts">
import {now, DAY, uniq, sum} from "@welshman/lib"
import type {Zap, TrustedEvent} from "@welshman/util"
import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util"
import {deriveEventsMapped} from "@welshman/store"
import {repository, getValidZap} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
import ZapButton from "@app/components/ZapButton.svelte"
type Props = {
url: string
event: TrustedEvent
}
const {url, event}: Props = $props()
const zaps = deriveEventsMapped<Zap>(repository, {
filters: [{kinds: [ZAP_RESPONSE], "#e": [event.id]}],
itemToEvent: item => item.response,
eventToItem: (response: TrustedEvent) => getValidZap(response, event),
})
const goalAmount = parseInt(getTagValue("amount", event.tags) || "0")
const zapAmount = $derived(fromMsats(sum($zaps.map(zap => zap.invoiceAmount))))
const contributorsCount = $derived(uniq($zaps.map(zap => zap.request.pubkey)).length)
const daysOld = Math.ceil((now() - event.created_at) / DAY)
</script>
<div class="card2 bg-alt flex flex-col gap-8">
<div class="flex gap-8">
<div>
<p class="text-xl text-primary">{zapAmount} sats</p>
<p class="text-sm opacity-75">funded of {goalAmount} sats</p>
</div>
<div>
<p class="text-xl">{contributorsCount}</p>
<p class="text-sm opacity-75">{contributorsCount === 1 ? "contributor" : "contributors"}</p>
</div>
<div>
<p class="text-xl">{daysOld}</p>
<p class="text-sm opacity-75">{daysOld === 1 ? "day" : "days"} old</p>
</div>
</div>
<progress class="progress progress-primary" value={zapAmount} max={goalAmount}></progress>
<ZapButton {url} {event} class="btn btn-primary lg:m-auto lg:px-20">
<Icon icon="bolt" />
Contribute to this goal
</ZapButton>
</div>
@@ -1,31 +0,0 @@
<script lang="ts">
import Button from "@lib/components/Button.svelte"
import Link from "@lib/components/Link.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import {PLATFORM_NAME} from "@app/state"
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>Where did my rooms go?</div>
{/snippet}
</ModalHeader>
<p>
You might have noticed that old rooms have disappeared from navigation. {PLATFORM_NAME} is still
under heavy development, which means that we occasionally have to make breaking changes. In this
case, we've changed how rooms work in {PLATFORM_NAME} to be more fully compatible with other NIP
29 clients, like <Link external class="link" href="https://chachi.chat">Chachi</Link> and
<Link external class="link" href="https://0xchat.com">0xChat</Link>.
</p>
<p>
If you run a relay, please upgrade to a version that supports NIP 29. {PLATFORM_NAME} works best
with the latest version of <Link
external
class="link"
href="https://github.com/coracle-social/frith">Frith</Link
>, which will automatically migrate your rooms. In the meantime, your messages are all still
available under the "Chat" tab (all conversations have been temporarily merged together).
</p>
<Button class="btn btn-primary" onclick={() => history.back()}>Got it</Button>
</div>
+36
View File
@@ -0,0 +1,36 @@
<script lang="ts">
import {deriveZapperForPubkey} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import ProfileLink from "@app/components/ProfileLink.svelte"
const {pubkey} = $props()
const zapper = deriveZapperForPubkey(pubkey)
const back = () => history.back()
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>Unable to Zap</div>
{/snippet}
</ModalHeader>
<p>
Zapping <ProfileLink {pubkey} class="!text-primary" /> isn't possible right now because
{#if $zapper}
their zap receiver isn't correctly set up.
{:else}
they don't currently have a zap receiver set up.
{/if}
</p>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" />
Go back
</Button>
</ModalFooter>
</div>
+20 -11
View File
@@ -33,18 +33,20 @@
const onSubmit = async () => {
if (controller.loading) return
const {signerPubkey, connectSecret, relays} = Nip46Broker.parseBunkerUrl(controller.bunker)
if (!signerPubkey || relays.length === 0) {
return pushToast({
theme: "error",
message: "Sorry, it looks like that's an invalid bunker link.",
})
}
controller.loading = true
try {
const {signerPubkey, connectSecret, relays} = Nip46Broker.parseBunkerUrl(controller.bunker)
console.log({signerPubkey, connectSecret, relays})
if (!signerPubkey || relays.length === 0) {
return pushToast({
theme: "error",
message: "Sorry, it looks like that's an invalid bunker link.",
})
}
controller.loading = true
const {clientSecret} = controller
const broker = new Nip46Broker({relays, clientSecret, signerPubkey})
const result = await broker.connect(connectSecret, NIP46_PERMS)
@@ -64,6 +66,13 @@
message: "Something went wrong, please try again!",
})
}
} catch (e) {
console.error(e)
return pushToast({
theme: "error",
message: "Something went wrong, please try again!",
})
} finally {
controller.loading = false
}
+11 -8
View File
@@ -17,8 +17,8 @@
import Alerts from "@app/components/Alerts.svelte"
import RoomCreate from "@app/components/RoomCreate.svelte"
import MenuSpaceRoomItem from "@app/components/MenuSpaceRoomItem.svelte"
import InfoMissingRooms from "@app/components/InfoMissingRooms.svelte"
import {
ENABLE_ZAPS,
userRoomsByUrl,
hasMembershipUrl,
memberships,
@@ -35,6 +35,7 @@
const relay = deriveRelay(url)
const chatPath = makeSpacePath(url, "chat")
const goalsPath = makeSpacePath(url, "goals")
const threadsPath = makeSpacePath(url, "threads")
const calendarPath = makeSpacePath(url, "calendar")
const userRooms = deriveUserRooms(url)
@@ -49,8 +50,6 @@
showMenu = !showMenu
}
const showMissingRooms = () => pushModal(InfoMissingRooms)
const showMembers = () =>
pushModal(
ProfileList,
@@ -129,10 +128,18 @@
</Popover>
{/if}
</div>
<div class="flex min-h-0 flex-col gap-1 overflow-auto">
<div class="flex max-h-[calc(100vh-150px)] min-h-0 flex-col gap-1 overflow-auto">
<SecondaryNavItem {replaceState} href={makeSpacePath(url)}>
<Icon icon="home-smile" /> Home
</SecondaryNavItem>
{#if ENABLE_ZAPS}
<SecondaryNavItem
{replaceState}
href={goalsPath}
notification={$notifications.has(goalsPath)}>
<Icon icon="star-fall-minimalistic-2" /> Goals
</SecondaryNavItem>
{/if}
<SecondaryNavItem
{replaceState}
href={threadsPath}
@@ -177,10 +184,6 @@
notification={$notifications.has(chatPath)}>
<Icon icon="chat-round" /> Chat
</SecondaryNavItem>
<Button class="link flex items-center gap-2 py-2 pl-4 text-sm" onclick={showMissingRooms}>
<Icon icon="info-circle" size={4} />
Where did my rooms go?
</Button>
{/if}
</div>
</SecondaryNavSection>
+3 -2
View File
@@ -9,14 +9,15 @@
type Props = {
pubkey: string
url?: string
class?: string
unstyled?: boolean
}
const {pubkey, url, unstyled}: Props = $props()
const {pubkey, url, unstyled, ...props}: Props = $props()
const openProfile = () => pushModal(ProfileDetail, {pubkey, url})
</script>
<Button onclick={preventDefault(openProfile)} class={cx({"link-content": !unstyled})}>
<Button onclick={preventDefault(openProfile)} class={cx(props.class, {"link-content": !unstyled})}>
@<ProfileName {pubkey} {url} />
</Button>
+35 -6
View File
@@ -1,24 +1,27 @@
<script lang="ts">
import {onMount} from "svelte"
import type {Snippet} from "svelte"
import {groupBy, uniq, uniqBy, batch, displayList} from "@welshman/lib"
import {groupBy, sum, uniq, uniqBy, batch, displayList} from "@welshman/lib"
import {
REACTION,
ZAP_RESPONSE,
getReplyFilters,
getEmojiTags,
getEmojiTag,
fromMsats,
getTag,
REPORT,
DELETE,
} from "@welshman/util"
import type {TrustedEvent, EventContent} from "@welshman/util"
import {deriveEvents} from "@welshman/store"
import type {TrustedEvent, EventContent, Zap} from "@welshman/util"
import {deriveEvents, deriveEventsMapped} from "@welshman/store"
import {load} from "@welshman/net"
import {pubkey, repository, displayProfileByPubkey} from "@welshman/app"
import {pubkey, repository, getValidZap, displayProfileByPubkey} from "@welshman/app"
import {isMobile, preventDefault, stopPropagation} from "@lib/html"
import Icon from "@lib/components/Icon.svelte"
import Reaction from "@app/components/Reaction.svelte"
import EventReportDetails from "@app/components/EventReportDetails.svelte"
import {REACTION_KINDS} from "@app/state"
import {pushModal} from "@app/modal"
interface Props {
@@ -49,6 +52,12 @@
filters: [{kinds: [REACTION], "#e": [event.id]}],
})
const zaps = deriveEventsMapped<Zap>(repository, {
filters: [{kinds: [ZAP_RESPONSE], "#e": [event.id]}],
itemToEvent: item => item.response,
eventToItem: (response: TrustedEvent) => getValidZap(response, event),
})
const onReactionClick = (events: TrustedEvent[]) => {
const reaction = events.find(e => e.pubkey === $pubkey)
@@ -77,6 +86,8 @@
),
)
const groupedZaps = $derived(groupBy(e => getReactionKey(e.request), $zaps))
onMount(() => {
const controller = new AbortController()
@@ -84,7 +95,7 @@
load({
relays: [url],
signal: controller.signal,
filters: getReplyFilters([event], {kinds: [REACTION, REPORT, DELETE]}),
filters: getReplyFilters([event], {kinds: [REPORT, DELETE, ...REACTION_KINDS]}),
onEvent: batch(300, (events: TrustedEvent[]) => {
load({
relays: [url],
@@ -100,7 +111,7 @@
})
</script>
{#if $reactions.length > 0 || $reports.length > 0}
{#if $reactions.length > 0 || $zaps.length || $reports.length > 0}
<div class="flex min-w-0 flex-wrap gap-2">
{#if url && $reports.length > 0}
<button
@@ -113,6 +124,24 @@
<span>{$reports.length}</span>
</button>
{/if}
{#each groupedZaps.entries() as [key, zaps]}
{@const amount = fromMsats(sum(zaps.map(zap => zap.invoiceAmount)))}
{@const pubkeys = uniq(zaps.map(zap => zap.request.pubkey))}
{@const isOwn = $pubkey && pubkeys.includes($pubkey)}
{@const info = displayList(pubkeys.map(pubkey => displayProfileByPubkey(pubkey)))}
{@const tooltip = `${info} zapped`}
<button
type="button"
data-tip={tooltip}
class="flex-inline btn btn-neutral btn-xs gap-1 rounded-full {reactionClass}"
class:tooltip={!noTooltip && !isMobile}
class:border={isOwn}
class:border-solid={isOwn}
class:border-primary={isOwn}>
<Reaction event={zaps[0].request} />
<span>{amount}</span>
</button>
{/each}
{#each groupedReactions.entries() as [key, events]}
{@const pubkeys = events.map(e => e.pubkey)}
{@const isOwn = $pubkey && pubkeys.includes($pubkey)}
+173
View File
@@ -0,0 +1,173 @@
<script lang="ts">
import {debounce} from "throttle-debounce"
import {nwc} from "@getalby/sdk"
import {sleep} from "@welshman/lib"
import Link from "@lib/components/Link.svelte"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Scanner from "@lib/components/Scanner.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Field from "@lib/components/Field.svelte"
import Divider from "@lib/components/Divider.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import type {NWCInfo} from "@app/state"
import {wallet, getWebLn} from "@app/state"
import {pushToast} from "@app/toast"
const back = () => history.back()
const connectWithWebLn = async () => {
loading = true
try {
await Promise.all([sleep(800), getWebLn().enable()])
const info = await getWebLn().getInfo()
if (!info?.supports?.includes("lightning")) {
pushToast({
theme: "error",
message: "Your extension does not support lightning payments",
})
} else {
wallet.set({type: "webln", info})
pushToast({message: "Wallet successfully connected!"})
await sleep(400)
back()
}
} catch (e) {
console.error(e)
pushToast({
theme: "error",
message: "Wallet failed to connect",
})
} finally {
loading = false
}
}
const connectWithNWC = async () => {
loading = true
try {
const client = new nwc.NWCClient({nostrWalletConnectUrl})
const [_, info] = await Promise.all([sleep(800), client.getInfo()])
if (!info) {
pushToast({
theme: "error",
message: "Wallet failed to connect",
})
} else {
wallet.set({type: "nwc", info: client.options as unknown as NWCInfo})
pushToast({message: "Wallet successfully connected!"})
await sleep(400)
back()
}
} catch (e) {
console.error(e)
pushToast({
theme: "error",
message: "Wallet failed to connect",
})
} finally {
loading = false
}
}
const toggleScanner = () => {
showScanner = !showScanner
}
const onScan = debounce(1000, async (data: string) => {
showScanner = false
nostrWalletConnectUrl = data
await connectWithNWC()
})
let nostrWalletConnectUrl = $state("")
let showScanner = $state(false)
let loading = $state(false)
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>Connect a Wallet</div>
{/snippet}
{#snippet info()}
Use Nostr Wallet Connect to send Bitcoin payments over Lightning.
{/snippet}
</ModalHeader>
{#if getWebLn()}
<Button
class="btn btn-primary"
disabled={Boolean(nostrWalletConnectUrl || loading)}
onclick={connectWithWebLn}>
<Spinner loading={!nostrWalletConnectUrl && loading}>
{#if !nostrWalletConnectUrl && loading}
Connecting...
{:else}
<div class="flex items-center gap-2">
<Icon icon="cpu" />
Connect with WebLN
</div>
{/if}
</Spinner>
</Button>
<Divider>Or</Divider>
{/if}
<Field>
{#snippet label()}
Connection Secret*
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="lock" />
<input
bind:value={nostrWalletConnectUrl}
autocomplete="off"
name="flotilla-nwc"
class="grow"
type="password" />
<Button onclick={toggleScanner}>
<Icon icon="qr-code" />
</Button>
</label>
{/snippet}
{#snippet info()}
You can find this in any wallet that supports
<Link external href="https://nwc.getalby.com/about" class="text-primary"
>Nostr Wallet Connect</Link
>.
{/snippet}
</Field>
{#if showScanner}
<Scanner onscan={onScan} />
{/if}
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" />
Go back
</Button>
<Button
class="btn btn-primary"
disabled={!nostrWalletConnectUrl || loading}
onclick={connectWithNWC}>
<Spinner loading={Boolean(nostrWalletConnectUrl && loading)}>
{#if nostrWalletConnectUrl && loading}
Connecting...
{:else}
<div class="flex items-center gap-2">
Connect Wallet
<Icon icon="alt-arrow-right" />
</div>
{/if}
</Spinner>
</Button>
</ModalFooter>
</div>
@@ -0,0 +1,16 @@
<script lang="ts">
import Confirm from "@lib/components/Confirm.svelte"
import {wallet} from "@app/state"
import {clearModals} from "@app/modal"
const confirm = async () => {
wallet.set(undefined)
clearModals()
}
</script>
<Confirm
{confirm}
title="Disconnect Wallet"
message="Are you sure you want to disconnect your bitcoin wallet?" />
+166
View File
@@ -0,0 +1,166 @@
<script lang="ts">
import type {NativeEmoji} from "emoji-picker-element/shared"
import {signer, deriveZapperForPubkey} from "@welshman/app"
import {load} from "@welshman/net"
import {Router} from "@welshman/router"
import {requestZap, makeZapRequest, getZapResponseFilter} from "@welshman/util"
import Icon from "@lib/components/Icon.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import FieldInline from "@lib/components/FieldInline.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import EmojiButton from "@lib/components/EmojiButton.svelte"
import ProfileLink from "@app/components/ProfileLink.svelte"
import {payInvoice} from "@app/commands"
import {pushToast} from "@app/toast"
type Props = {
url: string
pubkey: string
eventId?: string
}
const {url, pubkey, eventId}: Props = $props()
const minPos = 1
const maxPos = 1000
const minVal = 21
const maxVal = 1000000
const zapperStore = deriveZapperForPubkey(pubkey)
const posToAmount = (pos: number) => {
const normalizedPos = (pos - minPos) / (maxPos - minPos)
const logMin = Math.log(minVal)
const logMax = Math.log(maxVal)
const logValue = logMin + normalizedPos * (logMax - logMin)
return Math.round(Math.exp(logValue))
}
const amountToPos = (amount: number) => {
const clampedAmount = Math.max(minVal, Math.min(maxVal, amount))
const logMin = Math.log(minVal)
const logMax = Math.log(maxVal)
const logValue = Math.log(clampedAmount)
const normalizedPos = (logValue - logMin) / (logMax - logMin)
return Math.round(minPos + normalizedPos * (maxPos - minPos))
}
const back = () => history.back()
const onEmoji = (emoji: NativeEmoji) => {
content = emoji.unicode
}
const sendZap = async () => {
loading = true
try {
const zapper = $zapperStore!
const msats = amount * 1000
const relays = url ? [url] : Router.get().ForPubkey(pubkey).getUrls()
const filters = [getZapResponseFilter({zapper, pubkey, eventId})]
const params = {pubkey, content, eventId, msats, relays, zapper}
const event = await $signer!.sign(makeZapRequest(params))
const res = await requestZap({zapper, event})
if (!res.invoice) {
return pushToast({
theme: "error",
message: `Failed to zap: ${res.error || "no error given"}`,
})
}
await payInvoice(res.invoice)
await load({relays, filters})
pushToast({message: "Zap successfully sent!"})
back()
} catch (e) {
console.error(e)
const message = String(e).replace(/^.*Error: /, "")
pushToast({
theme: "error",
message: `Failed to zap: ${message}`,
})
} finally {
loading = false
}
}
let pos = $state(minPos)
let amount = $state(minVal)
let content = $state("⚡️")
let loading = $state(false)
$effect(() => {
amount = posToAmount(pos)
})
$effect(() => {
const newPos = amountToPos(amount)
if (newPos !== pos) {
pos = newPos
}
})
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>Send a Zap</div>
{/snippet}
{#snippet info()}
<div>To <ProfileLink {pubkey} class="!text-primary" /></div>
{/snippet}
</ModalHeader>
<FieldInline>
{#snippet label()}
Emoji Reaction
{/snippet}
{#snippet input()}
<div class="flex flex-grow items-center justify-end gap-4">
<EmojiButton {onEmoji} class="btn btn-neutral">
{content}
</EmojiButton>
</div>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
Amount
{/snippet}
{#snippet input()}
<div class="flex flex-grow justify-end">
<label class="input input-bordered flex items-center gap-2">
<Icon icon="bolt" />
<input bind:value={amount} type="number" class="w-24" />
</label>
</div>
{/snippet}
</FieldInline>
<input
class="range range-primary -mt-2"
type="range"
min={minPos}
max={maxPos}
bind:value={pos} />
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" />
Go back
</Button>
<Button class="btn btn-primary" onclick={sendZap} disabled={loading}>
<Spinner {loading}>
<div class="flex items-center gap-2">
{#if !loading}
<Icon icon="bolt" />
{/if}
Send Zap
</div>
</Spinner>
</Button>
</ModalFooter>
</div>
+27
View File
@@ -0,0 +1,27 @@
<script lang="ts">
import {deriveZapperForPubkey} from "@welshman/app"
import Button from "@lib/components/Button.svelte"
import Zap from "@app/components/Zap.svelte"
import InfoZapperError from "@app/components/InfoZapperError.svelte"
import WalletConnect from "@app/components/WalletConnect.svelte"
import {pushModal} from "@app/modal"
import {wallet} from "@app/state"
const {url, event, children, ...props} = $props()
const zapper = deriveZapperForPubkey(event.pubkey)
const onClick = () => {
if (!$zapper?.allowsNostr) {
pushModal(InfoZapperError, {url, pubkey: event.pubkey, eventId: event.id})
} else if ($wallet) {
pushModal(Zap, {url, pubkey: event.pubkey, eventId: event.id})
} else {
pushModal(WalletConnect)
}
}
</script>
<Button onclick={onClick} {...props}>
{@render children?.()}
</Button>
+6 -2
View File
@@ -1,5 +1,5 @@
import {derived} from "svelte/store"
import {synced, throttled} from "@welshman/store"
import {synced, localStorageProvider, throttled} from "@welshman/store"
import {pubkey} from "@welshman/app"
import {prop, spec, identity, now, groupBy} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
@@ -16,7 +16,11 @@ import {chats, getUrlsForEvent, userRoomsByUrl, repositoryStore} from "@app/stat
// Checked state
export const checked = synced<Record<string, number>>("checked", {})
export const checked = synced<Record<string, number>>({
key: "checked",
defaultValue: {},
storage: localStorageProvider,
})
export const deriveChecked = (key: string) => derived(checked, prop(key))
+1 -1
View File
@@ -34,7 +34,7 @@ export const getWebPushInfo = async () => {
}
if (!("PushManager" in window)) {
throw new Error("Push messaging not supported")
throw new Error("Push notifications are not supported")
}
if (Notification.permission === "denied") {
+11
View File
@@ -12,6 +12,7 @@ import {
DIRECT_MESSAGE_FILE,
MESSAGE,
THREAD,
ZAP_GOAL,
EVENT_TIME,
} from "@welshman/util"
import {makeChatId, entityLink, decodeRelay, encodeRelay, userRoomsByUrl, ROOM} from "@app/state"
@@ -37,6 +38,8 @@ export const makeRoomPath = (url: string, room: string) => `/spaces/${encodeRela
export const makeSpaceChatPath = (url: string) => makeRoomPath(url, "chat")
export const makeGoalPath = (url: string, eventId?: string) => makeSpacePath(url, "goals", eventId)
export const makeThreadPath = (url: string, eventId?: string) =>
makeSpacePath(url, "threads", eventId)
@@ -87,6 +90,10 @@ export const getEventPath = async (event: TrustedEvent, urls: string[]) => {
if (urls.length > 0) {
const url = urls[0]
if (event.kind === ZAP_GOAL) {
return makeGoalPath(url, event.id)
}
if (event.kind === THREAD) {
return makeThreadPath(url, event.id)
}
@@ -103,6 +110,10 @@ export const getEventPath = async (event: TrustedEvent, urls: string[]) => {
const id = event.tags.find(nthEq(0, "E"))?.[1]
if (id && kind) {
if (parseInt(kind) === ZAP_GOAL) {
return makeGoalPath(url, id)
}
if (parseInt(kind) === THREAD) {
return makeThreadPath(url, id)
}
+50 -4
View File
@@ -1,4 +1,5 @@
import twColors from "tailwindcss/colors"
import {Capacitor} from "@capacitor/core"
import {get, derived} from "svelte/store"
import * as nip19 from "nostr-tools/nip19"
import {
@@ -30,6 +31,7 @@ import {
deriveEventsMapped,
withGetter,
synced,
localStorageProvider,
} from "@welshman/store"
import {
getIdFilters,
@@ -37,6 +39,7 @@ import {
CLIENT_AUTH,
AUTH_JOIN,
REACTION,
ZAP_REQUEST,
ZAP_RESPONSE,
DIRECT_MESSAGE,
DIRECT_MESSAGE_FILE,
@@ -96,6 +99,10 @@ export const ROOM = "h"
export const PROTECTED = ["-"]
export const ENABLE_ZAPS = Capacitor.getPlatform() != "ios"
export const REACTION_KINDS = ENABLE_ZAPS ? [REACTION, ZAP_RESPONSE] : [REACTION]
export const NOTIFIER_PUBKEY = import.meta.env.VITE_NOTIFIER_PUBKEY
export const NOTIFIER_RELAY = import.meta.env.VITE_NOTIFIER_RELAY
@@ -130,11 +137,9 @@ export const DUFFLEPUD_URL = "https://dufflepud.onrender.com"
export const IMGPROXY_URL = "https://imgproxy.coracle.social"
export const REACTION_KINDS = [REACTION, ZAP_RESPONSE]
export const NIP46_PERMS =
"nip44_encrypt,nip44_decrypt," +
[CLIENT_AUTH, AUTH_JOIN, MESSAGE, THREAD, COMMENT, ROOMS, WRAP, REACTION]
[CLIENT_AUTH, AUTH_JOIN, MESSAGE, THREAD, COMMENT, ROOMS, WRAP, REACTION, ZAP_REQUEST]
.map(k => `sign_event:${k}`)
.join(",")
@@ -301,7 +306,11 @@ routerContext.getIndexerRelays = always(INDEXER_RELAYS)
// Settings
export const canDecrypt = synced("canDecrypt", false)
export const canDecrypt = synced({
key: "canDecrypt",
defaultValue: false,
storage: localStorageProvider,
})
export const SETTINGS = 38489
@@ -346,6 +355,43 @@ export const {
load: makeOutboxLoader(SETTINGS),
})
// Wallets
export type WebLNInfo = {
methods?: string[]
supports?: string[]
version?: string
node?: {
alias: string
}
}
export type NWCInfo = {
lud16: string
secret: string
relayUrl: string
walletPubkey: string
nostrWalletConnectUrl: string
}
export type Wallet =
| {
type: "webln"
info: WebLNInfo
}
| {
type: "nwc"
info: NWCInfo
}
export const wallet = synced<Wallet | undefined>({
key: "wallet",
defaultValue: undefined,
storage: localStorageProvider,
})
export const getWebLn = () => (window as any).webln
// Alerts
export type Alert = {
+6 -2
View File
@@ -1,3 +1,7 @@
import {synced} from "@welshman/store"
import {synced, localStorageProvider} from "@welshman/store"
export const theme = synced<string>("theme", "dark")
export const theme = synced({
key: "theme",
defaultValue: "dark",
storage: localStorageProvider,
})
+11
View File
@@ -0,0 +1,11 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 16.9C2 15.5906 2 14.9359 2.29472 14.455C2.45963 14.1859 2.68589 13.9596 2.955 13.7947C3.43594 13.5 4.09063 13.5 5.4 13.5H6.5C8.38562 13.5 9.32843 13.5 9.91421 14.0858C10.5 14.6716 10.5 15.6144 10.5 17.5V18.6C10.5 19.9094 10.5 20.5641 10.2053 21.045C10.0404 21.3141 9.81411 21.5404 9.545 21.7053C9.06406 22 8.40937 22 7.1 22C5.13594 22 4.15391 22 3.4325 21.5579C3.02884 21.3106 2.68945 20.9712 2.44208 20.5675C2 19.8461 2 18.8641 2 16.9Z" stroke="#1C274C" stroke-width="1.5"/>
<path d="M13.5 5.4C13.5 4.09063 13.5 3.43594 13.7947 2.955C13.9596 2.68589 14.1859 2.45963 14.455 2.29472C14.9359 2 15.5906 2 16.9 2C18.8641 2 19.8461 2 20.5675 2.44208C20.9712 2.68945 21.3106 3.02884 21.5579 3.4325C22 4.15391 22 5.13594 22 7.1C22 8.40937 22 9.06406 21.7053 9.545C21.5404 9.81411 21.3141 10.0404 21.045 10.2053C20.5641 10.5 19.9094 10.5 18.6 10.5H17.5C15.6144 10.5 14.6716 10.5 14.0858 9.91421C13.5 9.32843 13.5 8.38562 13.5 6.5V5.4Z" stroke="#1C274C" stroke-width="1.5"/>
<path d="M16.5 6.25C16.5 5.73459 16.5 5.47689 16.6291 5.29493C16.6747 5.23072 16.7307 5.17466 16.7949 5.12911C16.9769 5 17.2346 5 17.75 5C18.2654 5 18.5231 5 18.7051 5.12911C18.7693 5.17466 18.8253 5.23072 18.8709 5.29493C19 5.47689 19 5.73459 19 6.25C19 6.76541 19 7.02311 18.8709 7.20507C18.8253 7.26928 18.7693 7.32534 18.7051 7.37089C18.5231 7.5 18.2654 7.5 17.75 7.5C17.2346 7.5 16.9769 7.5 16.7949 7.37089C16.7307 7.32534 16.6747 7.26928 16.6291 7.20507C16.5 7.02311 16.5 6.76541 16.5 6.25Z" fill="#1C274C"/>
<path d="M12.75 22C12.75 22.4142 13.0858 22.75 13.5 22.75C13.9142 22.75 14.25 22.4142 14.25 22H12.75ZM14.3889 13.8371L14.8055 14.4607L14.8055 14.4607L14.3889 13.8371ZM13.8371 14.3889L13.2135 13.9722L13.2135 13.9722L13.8371 14.3889ZM19 12.75H17V14.25H19V12.75ZM12.75 19V22H14.25V19H12.75ZM17 12.75C16.3134 12.75 15.742 12.7491 15.281 12.796C14.8075 12.8441 14.3682 12.9489 13.9722 13.2135L14.8055 14.4607C14.914 14.3882 15.078 14.3244 15.4328 14.2883C15.8002 14.2509 16.2822 14.25 17 14.25V12.75ZM14.25 17C14.25 16.2822 14.2509 15.8002 14.2883 15.4328C14.3244 15.078 14.3882 14.914 14.4607 14.8055L13.2135 13.9722C12.9489 14.3682 12.8441 14.8075 12.796 15.281C12.7491 15.742 12.75 16.3134 12.75 17H14.25ZM13.9722 13.2135C13.6719 13.4141 13.4141 13.6719 13.2135 13.9722L14.4607 14.8055C14.5519 14.669 14.669 14.5519 14.8055 14.4607L13.9722 13.2135Z" fill="#1C274C"/>
<path d="M22.75 13.5C22.75 13.0858 22.4142 12.75 22 12.75C21.5858 12.75 21.25 13.0858 21.25 13.5H22.75ZM20.7654 21.8478L21.0524 22.5407L21.0524 22.5407L20.7654 21.8478ZM21.8478 20.7654L21.1548 20.4784V20.4784L21.8478 20.7654ZM17 22.75H19V21.25H17V22.75ZM22.75 17V13.5H21.25V17H22.75ZM19 22.75C19.4557 22.75 19.835 22.7504 20.1454 22.7292C20.4625 22.7076 20.762 22.661 21.0524 22.5407L20.4784 21.1548C20.4012 21.1868 20.284 21.2163 20.0433 21.2327C19.7958 21.2496 19.4762 21.25 19 21.25V22.75ZM21.25 19C21.25 19.4762 21.2496 19.7958 21.2327 20.0433C21.2163 20.284 21.1868 20.4012 21.1548 20.4784L22.5407 21.0524C22.661 20.762 22.7076 20.4625 22.7292 20.1454C22.7504 19.835 22.75 19.4557 22.75 19H21.25ZM21.0524 22.5407C21.7262 22.2616 22.2616 21.7262 22.5407 21.0524L21.1548 20.4784C21.028 20.7846 20.7846 21.028 20.4784 21.1549L21.0524 22.5407Z" fill="#1C274C"/>
<path d="M2 7.1C2 5.13594 2 4.15391 2.44208 3.4325C2.68945 3.02884 3.02884 2.68945 3.4325 2.44208C4.15391 2 5.13594 2 7.1 2C8.40937 2 9.06406 2 9.545 2.29472C9.81411 2.45963 10.0404 2.68589 10.2053 2.955C10.5 3.43594 10.5 4.09063 10.5 5.4V6.5C10.5 8.38562 10.5 9.32843 9.91421 9.91421C9.32843 10.5 8.38562 10.5 6.5 10.5H5.4C4.09063 10.5 3.43594 10.5 2.955 10.2053C2.68589 10.0404 2.45963 9.81411 2.29472 9.545C2 9.06406 2 8.40937 2 7.1Z" stroke="#1C274C" stroke-width="1.5"/>
<path d="M5 6.25C5 5.73459 5 5.47689 5.12911 5.29493C5.17466 5.23072 5.23072 5.17466 5.29493 5.12911C5.47689 5 5.73459 5 6.25 5C6.76541 5 7.02311 5 7.20507 5.12911C7.26928 5.17466 7.32534 5.23072 7.37089 5.29493C7.5 5.47689 7.5 5.73459 7.5 6.25C7.5 6.76541 7.5 7.02311 7.37089 7.20507C7.32534 7.26928 7.26928 7.32534 7.20507 7.37089C7.02311 7.5 6.76541 7.5 6.25 7.5C5.73459 7.5 5.47689 7.5 5.29493 7.37089C5.23072 7.32534 5.17466 7.26928 5.12911 7.20507C5 7.02311 5 6.76541 5 6.25Z" fill="#1C274C"/>
<path d="M5 17.75C5 17.2346 5 16.9769 5.12911 16.7949C5.17466 16.7307 5.23072 16.6747 5.29493 16.6291C5.47689 16.5 5.73459 16.5 6.25 16.5C6.76541 16.5 7.02311 16.5 7.20507 16.6291C7.26928 16.6747 7.32534 16.7307 7.37089 16.7949C7.5 16.9769 7.5 17.2346 7.5 17.75C7.5 18.2654 7.5 18.5231 7.37089 18.7051C7.32534 18.7693 7.26928 18.8253 7.20507 18.8709C7.02311 19 6.76541 19 6.25 19C5.73459 19 5.47689 19 5.29493 18.8709C5.23072 18.8253 5.17466 18.7693 5.12911 18.7051C5 18.5231 5 18.2654 5 17.75Z" fill="#1C274C"/>
<path d="M16 17.75C16 17.0478 16 16.6967 16.1685 16.4444C16.2415 16.3352 16.3352 16.2415 16.4444 16.1685C16.6967 16 17.0478 16 17.75 16C18.4522 16 18.8033 16 19.0556 16.1685C19.1648 16.2415 19.2585 16.3352 19.3315 16.4444C19.5 16.6967 19.5 17.0478 19.5 17.75C19.5 18.4522 19.5 18.8033 19.3315 19.0556C19.2585 19.1648 19.1648 19.2585 19.0556 19.3315C18.8033 19.5 18.4522 19.5 17.75 19.5C17.0478 19.5 16.6967 19.5 16.4444 19.3315C16.3352 19.2585 16.2415 19.1648 16.1685 19.0556C16 18.8033 16 18.4522 16 17.75Z" fill="#1C274C"/>
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.8114 6.7267C12.8247 4.9089 13.3314 4 14.0889 4C14.8464 4 15.353 4.9089 16.3663 6.7267L16.6285 7.19699C16.9164 7.71355 17.0604 7.97183 17.2849 8.14225C17.5094 8.31266 17.789 8.37592 18.3482 8.50244L18.8572 8.61762C20.825 9.06284 21.8089 9.28545 22.0429 10.0382C22.277 10.7909 21.6063 11.5753 20.2648 13.1439L19.9177 13.5498C19.5365 13.9955 19.3459 14.2184 19.2602 14.4942C19.1744 14.7699 19.2032 15.0673 19.2609 15.662L19.3134 16.2035C19.5162 18.2965 19.6176 19.343 19.0047 19.8082C18.3919 20.2734 17.4707 19.8492 15.6283 19.0009L15.1517 18.7815C14.6281 18.5404 14.3664 18.4199 14.0889 18.4199C13.8114 18.4199 13.5496 18.5404 13.0261 18.7815L12.5494 19.0009C10.707 19.8492 9.78581 20.2734 9.17299 19.8082C8.56016 19.343 8.66157 18.2965 8.86438 16.2035L8.91685 15.662C8.97449 15.0673 9.0033 14.7699 8.91756 14.4942C8.83181 14.2184 8.64121 13.9955 8.26 13.5498L7.91295 13.1439C6.57147 11.5753 5.90073 10.7909 6.1348 10.0382C6.36888 9.28545 7.35275 9.06284 9.3205 8.61762L9.82958 8.50244C10.3887 8.37592 10.6683 8.31266 10.8928 8.14225C11.1173 7.97183 11.2613 7.71355 11.5492 7.19699L11.8114 6.7267Z" stroke="#1C274C" stroke-width="1.5"/>
<path d="M2.08887 16C3.20445 15.121 4.68639 14.7971 6.08887 15.1257" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
<path d="M2.08887 10.5C3.08887 10 3.37862 10.0605 4.08887 10" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
<path d="M2 5.60867L2.20816 5.48676C4.41383 4.19506 6.75032 3.84687 8.95304 4.48161L9.16092 4.54152" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+6
View File
@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 8H10" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M20.8333 9H18.2308C16.4465 9 15 10.3431 15 12C15 13.6569 16.4465 15 18.2308 15H20.8333C20.9167 15 20.9583 15 20.9935 14.9979C21.5328 14.965 21.9623 14.5662 21.9977 14.0654C22 14.0327 22 13.994 22 13.9167V10.0833C22 10.006 22 9.96726 21.9977 9.9346C21.9623 9.43384 21.5328 9.03496 20.9935 9.00214C20.9583 9 20.9167 9 20.8333 9Z" stroke="#1C274C" stroke-width="1.5"/>
<path d="M20.965 9C20.8873 7.1277 20.6366 5.97975 19.8284 5.17157C18.6569 4 16.7712 4 13 4L10 4C6.22876 4 4.34315 4 3.17157 5.17157C2 6.34315 2 8.22876 2 12C2 15.7712 2 17.6569 3.17157 18.8284C4.34315 20 6.22876 20 10 20H13C16.7712 20 18.6569 20 19.8284 18.8284C20.6366 18.0203 20.8873 16.8723 20.965 15" stroke="#1C274C" stroke-width="1.5"/>
<path d="M17.9912 12H18.0002" stroke="#1C274C" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+1 -1
View File
@@ -20,7 +20,7 @@
if (popover) {
const {x, y, width, height} = popover.popper.getBoundingClientRect()
if (!between([x, x + width], clientX) || !between([y - 30, y + height + 30], clientY)) {
if (!between([x, x + width], clientX) || !between([y - 100, y + height + 100], clientY)) {
popover.hide()
}
}
+6
View File
@@ -68,6 +68,7 @@
import Pallete2 from "@assets/icons/Pallete 2.svg?dataurl"
import Paperclip from "@assets/icons/Paperclip.svg?dataurl"
import Plain from "@assets/icons/Plain.svg?dataurl"
import QRCode from "@assets/icons/QR Code.svg?dataurl"
import QuestionSquare from "@assets/icons/Question Square.svg?dataurl"
import RemoteControllerMinimalistic from "@assets/icons/Remote Controller Minimalistic.svg?dataurl"
import Rocket2 from "@assets/icons/Rocket 2.svg?dataurl"
@@ -85,11 +86,13 @@
import SquareShareLine from "@assets/icons/Square Share Line.svg?dataurl"
import SortVertical from "@assets/icons/Sort Vertical.svg?dataurl"
import Star from "@assets/icons/Star.svg?dataurl"
import StarFallMinimalistic2 from "@assets/icons/Star Fall Minimalistic 2.svg?dataurl"
import TrashBin2 from "@assets/icons/Trash Bin 2.svg?dataurl"
import UFO3 from "@assets/icons/UFO 3.svg?dataurl"
import UserHeart from "@assets/icons/User Heart.svg?dataurl"
import UserCircle from "@assets/icons/User Circle.svg?dataurl"
import UserRounded from "@assets/icons/User Rounded.svg?dataurl"
import Wallet from "@assets/icons/Wallet.svg?dataurl"
import Widget from "@assets/icons/Widget.svg?dataurl"
import WidgetAdd from "@assets/icons/Widget Add.svg?dataurl"
import WiFiRouterRound from "@assets/icons/Wi-Fi Router Round.svg?dataurl"
@@ -168,6 +171,7 @@
"pallete-2": Pallete2,
paperclip: Paperclip,
plain: Plain,
"qr-code": QRCode,
"question-square": QuestionSquare,
reply: Reply,
"remote-controller-minimalistic": RemoteControllerMinimalistic,
@@ -187,9 +191,11 @@
"square-share-line": SquareShareLine,
"sort-vertical": SortVertical,
star: Star,
"star-fall-minimalistic-2": StarFallMinimalistic2,
"user-heart": UserHeart,
"user-circle": UserCircle,
"user-rounded": UserRounded,
wallet: Wallet,
widget: Widget,
"widget-add": WidgetAdd,
"wifi-router-round": WiFiRouterRound,
+32
View File
@@ -0,0 +1,32 @@
<script lang="ts">
import {onMount} from "svelte"
import QrScanner from "qr-scanner"
import Spinner from "@lib/components/Spinner.svelte"
const {onscan} = $props()
let video: HTMLVideoElement
let scanner: QrScanner
let loading = $state(true)
onMount(() => {
scanner = new QrScanner(video, r => onscan(r.data), {
returnDetailedScanResult: true,
})
scanner.start().then(() => {
loading = false
})
return () => scanner.destroy()
})
</script>
<div class="bg-alt flex min-h-48 w-full flex-col items-center justify-center rounded p-px">
{#if loading}
<p class="py-20">
<Spinner loading>Loading your camera...</Spinner>
</p>
{/if}
<video class="m-auto rounded" class:h-0={loading} bind:this={video}></video>
</div>
+21 -6
View File
@@ -7,6 +7,7 @@
import {App} from "@capacitor/app"
import {dev} from "$app/environment"
import {goto} from "$app/navigation"
import {sync, localStorageProvider} from "@welshman/store"
import {identity, memoize, sleep, defer, ago, WEEK, TaskQueue} from "@welshman/lib"
import type {TrustedEvent, StampedEvent} from "@welshman/util"
import {
@@ -33,14 +34,16 @@
initStorage,
repository,
pubkey,
defaultStorageAdapters,
session,
sessions,
signer,
dropSession,
defaultStorageAdapters,
userInboxRelaySelections,
loginWithNip01,
loginWithNip46,
EventsStorageAdapter,
loadRelaySelections,
} from "@welshman/app"
import * as lib from "@welshman/lib"
import * as util from "@welshman/util"
@@ -168,17 +171,29 @@
const unwrapper = new TaskQueue<TrustedEvent>({batchSize: 10, processItem: ensureUnwrapped})
repository.on("update", ({added}) => {
if (!$canDecrypt) {
return
}
for (const event of added) {
if (event.kind === WRAP) {
loadRelaySelections(event.pubkey)
if ($canDecrypt && event.kind === WRAP) {
unwrapper.push(event)
}
}
})
// Sync current pubkey
sync({
key: "pubkey",
store: pubkey,
storage: localStorageProvider,
})
// Sync user sessions
sync({
key: "sessions",
store: sessions,
storage: localStorageProvider,
})
await initStorage("flotilla", 8, {
...defaultStorageAdapters,
events: new EventsStorageAdapter({
+5
View File
@@ -28,6 +28,11 @@
<Icon icon="user-circle" /> Profile
</SecondaryNavItem>
</div>
<div in:fly|local>
<SecondaryNavItem href="/settings/wallet">
<Icon icon="wallet" /> Wallet
</SecondaryNavItem>
</div>
<div in:fly|local={{delay: 50}}>
<SecondaryNavItem href="/settings/relays">
<Icon icon="server" /> Relays
+3 -3
View File
@@ -17,11 +17,11 @@
import {pushModal} from "@app/modal"
import {clip} from "@app/toast"
const npub = nip19.npubEncode($pubkey!)
const profile = deriveProfile($pubkey!)
const pubkeyDisplay = displayPubkey($pubkey!)
const copyNpub = () => clip(nip19.npubEncode($session!.pubkey))
const copyNpub = () => clip(npub)
const copyNsec = () => clip(nip19.nsecEncode(hexToBytes($session!.secret!)))
@@ -93,7 +93,7 @@
<label class="input input-bordered flex w-full items-center justify-between gap-2">
<div class="row-2 flex-grow items-center">
<Icon icon="link-round" />
<input readonly class="ellipsize flex-grow" value={$session?.pubkey} />
<input readonly class="ellipsize flex-grow" value={npub} />
</div>
<Button class="flex items-center" onclick={copyNpub}>
<Icon icon="copy" />
+87
View File
@@ -0,0 +1,87 @@
<script lang="ts">
import {nwc} from "@getalby/sdk"
import {LOCALE} from "@welshman/lib"
import {displayRelayUrl, fromMsats} from "@welshman/util"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import WalletConnect from "@app/components/WalletConnect.svelte"
import WalletDisconnect from "@app/components/WalletDisconnect.svelte"
import {pushModal} from "@app/modal"
import {wallet, getWebLn} from "@app/state"
const connect = () => pushModal(WalletConnect)
const disconnect = () => pushModal(WalletDisconnect)
</script>
<div class="content column gap-4">
<div class="card2 bg-alt flex flex-col gap-6 shadow-xl">
<div class="flex items-center justify-between">
<strong class="flex items-center gap-3">
<Icon icon="wallet" />
Wallet
</strong>
{#if $wallet}
<div class="flex items-center gap-2 text-sm text-success">
<Icon icon="check-circle" size={4} />
Connected
</div>
{:else}
<Button class="btn btn-primary btn-sm" onclick={connect}>
<Icon icon="add-circle" />
Connect Wallet
</Button>
{/if}
</div>
<div class="col-4">
{#if $wallet}
{#if $wallet?.type === "webln"}
{@const {node, version} = $wallet.info}
<div class="flex flex-col justify-between gap-2 lg:flex-row">
<p>
Connected to <strong>{node?.alias || version || "unknown wallet"}</strong>
via <strong>{$wallet.type}</strong>
</p>
<p class="flex gap-2 whitespace-nowrap">
Balance:
{#await getWebLn()
?.enable()
.then(() => getWebLn().getBalance())}
<span class="loading loading-spinner loading-sm"></span>
{:then res}
{new Intl.NumberFormat(LOCALE).format(res?.balance || 0)}
{:catch}
[unknown]
{/await}
sats
</p>
</div>
{:else if $wallet.type === "nwc"}
{@const {lud16, relayUrl, nostrWalletConnectUrl} = $wallet.info}
<div class="flex flex-col justify-between gap-2 lg:flex-row">
<p>
Connected to <strong>{lud16}</strong> via <strong>{displayRelayUrl(relayUrl)}</strong>
</p>
<p class="flex gap-2 whitespace-nowrap">
Balance:
{#await new nwc.NWCClient({nostrWalletConnectUrl}).getBalance()}
<span class="loading loading-spinner loading-sm"></span>
{:then res}
{new Intl.NumberFormat(LOCALE).format(fromMsats(res?.balance || 0))}
{:catch}
[unknown]
{/await}
sats
</p>
</div>
{/if}
<Button class="btn btn-neutral btn-sm" onclick={disconnect}>
<Icon icon="close-circle" />
Disconnect Wallet
</Button>
{:else}
<p class="py-12 text-center opacity-75">No wallet connected</p>
{/if}
</div>
</div>
</div>
+1 -1
View File
@@ -62,7 +62,7 @@
relays,
filters: [
{kinds: [ROOM_META]},
{kinds: [THREAD, EVENT_TIME], since},
{kinds: [THREAD, EVENT_TIME, MESSAGE], since},
{kinds: [COMMENT], "#K": [String(THREAD), String(EVENT_TIME)], since},
...rooms.map(room => ({kinds: [MESSAGE], "#h": [room], since})),
],
@@ -12,7 +12,6 @@
makeRoomMeta,
MESSAGE,
DELETE,
REACTION,
ROOM_ADD_USER,
ROOM_REMOVE_USER,
} from "@welshman/util"
@@ -37,6 +36,7 @@
deriveUserMembershipStatus,
deriveChannel,
MembershipStatus,
REACTION_KINDS,
} from "@app/state"
import {setChecked, checked} from "@app/notifications"
import {addRoomMembership, removeRoomMembership, prependParent} from "@app/commands"
@@ -226,7 +226,9 @@
element: element!,
relays: [url],
feedFilters: [filter],
subscriptionFilters: [{kinds: [DELETE, REACTION, MESSAGE], "#h": [room], since: now()}],
subscriptionFilters: [
{kinds: [DELETE, MESSAGE, ...REACTION_KINDS], "#h": [room], since: now()},
],
initialEvents: getEventsForUrl(url, [{...filter, limit: 20}]),
onExhausted: () => {
loadingEvents = false
@@ -5,7 +5,7 @@
import {page} from "$app/stores"
import {now, last, formatTimestampAsDate} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {REACTION, DELETE, EVENT_TIME, getTagValue} from "@welshman/util"
import {DELETE, EVENT_TIME, getTagValue} from "@welshman/util"
import {fly} from "@lib/transition"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
@@ -17,7 +17,7 @@
import CalendarEventItem from "@app/components/CalendarEventItem.svelte"
import CalendarEventCreate from "@app/components/CalendarEventCreate.svelte"
import {pushModal} from "@app/modal"
import {getEventsForUrl, decodeRelay} from "@app/state"
import {getEventsForUrl, decodeRelay, REACTION_KINDS} from "@app/state"
import {makeCalendarFeed} from "@app/requests"
import {setChecked} from "@app/notifications"
@@ -93,7 +93,7 @@
onMount(() => {
const feedFilters = [{kinds: [EVENT_TIME]}]
const subscriptionFilters = [{kinds: [DELETE, REACTION, EVENT_TIME], since: now()}]
const subscriptionFilters = [{kinds: [DELETE, EVENT_TIME, ...REACTION_KINDS], since: now()}]
;({events, cleanup} = makeCalendarFeed({
element: element!,
+3 -3
View File
@@ -5,7 +5,7 @@
import type {Readable} from "svelte/store"
import {now, formatTimestampAsDate} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util"
import {makeEvent, MESSAGE, DELETE, REACTION} from "@welshman/util"
import {makeEvent, MESSAGE, DELETE} from "@welshman/util"
import {pubkey, publishThunk} from "@welshman/app"
import {slide, fade, fly} from "@lib/transition"
import Icon from "@lib/components/Icon.svelte"
@@ -21,7 +21,7 @@
import {userSettingValues, decodeRelay, getEventsForUrl} from "@app/state"
import {setChecked, checked} from "@app/notifications"
import {prependParent} from "@app/commands"
import {PROTECTED} from "@app/state"
import {PROTECTED, REACTION_KINDS} from "@app/state"
import {makeFeed} from "@app/requests"
import {popKey} from "@app/implicit"
@@ -174,7 +174,7 @@
element: element!,
relays: [url],
feedFilters: [filter],
subscriptionFilters: [{kinds: [DELETE, REACTION, MESSAGE], since: now()}],
subscriptionFilters: [{kinds: [DELETE, MESSAGE, ...REACTION_KINDS], since: now()}],
initialEvents: getEventsForUrl(url, [{...filter, limit: 20}]),
onExhausted: () => {
loadingEvents = false
@@ -0,0 +1,114 @@
<script lang="ts">
import {onMount} from "svelte"
import {page} from "$app/stores"
import {sortBy, max, nthEq} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {ZAP_GOAL, DELETE, COMMENT, getListTags, getPubkeyTagValues} from "@welshman/util"
import {userMutes} from "@welshman/app"
import {fly} from "@lib/transition"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import PageBar from "@lib/components/PageBar.svelte"
import PageContent from "@lib/components/PageContent.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
import GoalItem from "@app/components/GoalItem.svelte"
import GoalCreate from "@app/components/GoalCreate.svelte"
import {decodeRelay, getEventsForUrl, REACTION_KINDS} from "@app/state"
import {setChecked} from "@app/notifications"
import {makeFeed} from "@app/requests"
import {pushModal} from "@app/modal"
const url = decodeRelay($page.params.relay)
const mutedPubkeys = getPubkeyTagValues(getListTags($userMutes))
const goals: TrustedEvent[] = $state([])
const comments: TrustedEvent[] = $state([])
let loading = $state(true)
let element: HTMLElement | undefined = $state()
const createGoal = () => pushModal(GoalCreate, {url})
const events = $derived.by(() => {
const scores = new Map<string, number>()
for (const comment of comments) {
const id = comment.tags.find(nthEq(0, "E"))?.[1]
if (id) {
scores.set(id, max([scores.get(id), comment.created_at]))
}
}
return sortBy(e => -max([scores.get(e.id), e.created_at]), goals)
})
onMount(() => {
const {cleanup} = makeFeed({
element: element!,
relays: [url],
feedFilters: [{kinds: [ZAP_GOAL, COMMENT]}],
subscriptionFilters: [
{kinds: [ZAP_GOAL, DELETE, ...REACTION_KINDS]},
{kinds: [COMMENT], "#K": [String(ZAP_GOAL)]},
],
initialEvents: getEventsForUrl(url, [{kinds: [ZAP_GOAL, COMMENT], limit: 10}]),
onEvent: event => {
if (event.kind === ZAP_GOAL && !mutedPubkeys.includes(event.pubkey)) {
goals.push(event)
}
if (event.kind === COMMENT) {
comments.push(event)
}
},
onExhausted: () => {
loading = false
},
})
return () => {
cleanup()
setChecked($page.url.pathname)
}
})
</script>
<PageBar>
{#snippet icon()}
<div class="center">
<Icon icon="notes-minimalistic" />
</div>
{/snippet}
{#snippet title()}
<strong>Fundraising Goals</strong>
{/snippet}
{#snippet action()}
<div class="row-2">
<Button class="btn btn-primary btn-sm" onclick={createGoal}>
<Icon icon="notes-minimalistic" />
Create a Goal
</Button>
<MenuSpaceButton {url} />
</div>
{/snippet}
</PageBar>
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
{#each events as event (event.id)}
<div in:fly>
<GoalItem {url} event={$state.snapshot(event)} />
</div>
{/each}
<p class="flex h-10 items-center justify-center py-20">
<Spinner {loading}>
{#if loading}
Looking for goals...
{:else if events.length === 0}
No goals found.
{:else}
That's all!
{/if}
</Spinner>
</p>
</PageContent>
@@ -0,0 +1,123 @@
<script lang="ts">
import {onMount} from "svelte"
import {page} from "$app/stores"
import {sortBy, sleep} from "@welshman/lib"
import {COMMENT, getTagValue} from "@welshman/util"
import {repository} from "@welshman/app"
import {request} from "@welshman/net"
import {deriveEvents} from "@welshman/store"
import Icon from "@lib/components/Icon.svelte"
import PageBar from "@lib/components/PageBar.svelte"
import PageContent from "@lib/components/PageContent.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Content from "@app/components/Content.svelte"
import NoteCard from "@app/components/NoteCard.svelte"
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
import GoalSummary from "@app/components/GoalSummary.svelte"
import GoalActions from "@app/components/GoalActions.svelte"
import CommentActions from "@app/components/CommentActions.svelte"
import EventReply from "@app/components/EventReply.svelte"
import {deriveEvent, decodeRelay} from "@app/state"
import {setChecked} from "@app/notifications"
const {relay, id} = $page.params
const url = decodeRelay(relay)
const event = deriveEvent(id)
const filters = [{kinds: [COMMENT], "#E": [id]}]
const replies = deriveEvents(repository, {filters})
const summary = getTagValue("summary", $event.tags)
const back = () => history.back()
const openReply = () => {
showReply = true
}
const closeReply = () => {
showReply = false
}
const expand = () => {
showAll = true
}
let showAll = $state(false)
let showReply = $state(false)
onMount(() => {
const controller = new AbortController()
request({relays: [url], filters, signal: controller.signal})
return () => {
controller.abort()
setChecked($page.url.pathname)
}
})
</script>
<PageBar>
{#snippet icon()}
<div>
<Button class="btn btn-neutral btn-sm flex-nowrap whitespace-nowrap" onclick={back}>
<Icon icon="alt-arrow-left" />
<span class="hidden sm:inline">Go back</span>
</Button>
</div>
{/snippet}
{#snippet title()}
<h1 class="text-xl">{$event.content}</h1>
{/snippet}
{#snippet action()}
<div>
<MenuSpaceButton {url} />
</div>
{/snippet}
</PageBar>
<PageContent class="flex flex-col p-2 pt-4">
{#if $event}
<div class="flex flex-col gap-3">
<NoteCard event={$event} {url} class="card2 bg-alt z-feature w-full">
<div class="col-3 ml-12">
<Content showEntire event={{...$event, content: summary}} {url} />
<GoalSummary event={$event} {url} />
<GoalActions event={$event} {url} />
</div>
</NoteCard>
{#if !showAll && $replies.length > 4}
<div class="flex justify-center">
<Button class="btn btn-link" onclick={expand}>
<Icon icon="sort-vertical" />
Show all {$replies.length} replies
</Button>
</div>
{/if}
{#each sortBy(e => e.created_at, $replies).slice(0, showAll ? undefined : 4) as reply (reply.id)}
<NoteCard event={reply} {url} class="card2 bg-alt z-feature w-full">
<div class="col-3 ml-12">
<Content showEntire event={reply} {url} />
<CommentActions event={reply} {url} />
</div>
</NoteCard>
{/each}
</div>
{#if showReply}
<EventReply {url} event={$event} onClose={closeReply} onSubmit={closeReply} />
{:else}
<div class="flex justify-end p-2">
<Button class="btn btn-primary" onclick={openReply}>
<Icon icon="reply" />
Comment on this goal
</Button>
</div>
{/if}
{:else}
{#await sleep(5000)}
<Spinner loading>Loading funding goal...</Spinner>
{:then}
<p>Failed to load funding goal.</p>
{/await}
{/if}
</PageContent>
@@ -3,7 +3,7 @@
import {page} from "$app/stores"
import {sortBy, max, nthEq} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {THREAD, REACTION, DELETE, COMMENT, getListTags, getPubkeyTagValues} from "@welshman/util"
import {THREAD, DELETE, COMMENT, getListTags, getPubkeyTagValues} from "@welshman/util"
import {userMutes} from "@welshman/app"
import {fly} from "@lib/transition"
import Icon from "@lib/components/Icon.svelte"
@@ -16,6 +16,7 @@
import ThreadCreate from "@app/components/ThreadCreate.svelte"
import {decodeRelay, getEventsForUrl} from "@app/state"
import {setChecked} from "@app/notifications"
import {REACTION_KINDS} from "@app/state"
import {makeFeed} from "@app/requests"
import {pushModal} from "@app/modal"
@@ -49,7 +50,7 @@
relays: [url],
feedFilters: [{kinds: [THREAD, COMMENT]}],
subscriptionFilters: [
{kinds: [THREAD, REACTION, DELETE]},
{kinds: [THREAD, DELETE, ...REACTION_KINDS]},
{kinds: [COMMENT], "#K": [String(THREAD)]},
],
initialEvents: getEventsForUrl(url, [{kinds: [THREAD, COMMENT], limit: 10}]),
+5
View File
@@ -25,4 +25,9 @@ export default {
},
},
},
compilerOptions: {
warningFilter: (warning) => {
return !['a11y_media_has_caption'].includes(warning.code)
},
}
}