Compare commits

...

22 Commits

Author SHA1 Message Date
Jon Staab 3c9b3f23df Upload profile pictures instead of doing base64 2025-09-18 13:43:43 -07:00
Jon Staab e0d83608be Bump version, changelog 2025-09-18 13:09:03 -07:00
Jon Staab a0301d599b Bump welshman, rename stripExifData 2025-09-18 12:17:31 -07:00
Jon Staab 7dcaa0e8d7 Change login icon 2025-09-16 11:06:36 -07:00
Matthew Remmel 129f49bcc7 Compress profile pictures on upload 2025-09-15 10:55:01 -07:00
Jon Staab fc3b68c390 Fix default avatar icon 2025-09-11 16:44:02 -07:00
Jon Staab 52c7df8a15 Default to light mode 2025-09-11 14:47:06 -07:00
Jon Staab ce1c4dd488 Fix some icons 2025-09-11 12:43:18 -07:00
Jon Staab fc6a1a3819 Move alerts to their own page, add direct message alerts 2025-09-11 12:28:54 -07:00
Jon Staab 69bd6d0e70 Use new icons 2025-09-11 08:59:47 -07:00
Jon Staab 6d383d54e8 Fix app image clipping 2025-09-09 10:53:16 -07:00
Jon Staab 998c48b1d3 Wait for thunk errors 2025-09-08 08:34:52 -07:00
Jon Staab 7217d122b5 Re-work invite links 2025-09-08 08:34:52 -07:00
Jon Staab 1c37c5bb3d Make white labeled nav look less bad 2025-09-05 16:21:17 -07:00
Jon Staab e8f785b558 Bump welshman 2025-09-05 11:35:34 -07:00
Matthew Remmel c94d314f6d Use capacitor preferences package instead of localStorage 2025-09-05 11:34:52 -07:00
Jon Staab 2672a8f922 Include instructions in key file 2025-09-05 10:49:20 -07:00
hodlbod 8a8d80d692 Merge pull request #197 from coracle-social/auth-errors
Auth errors
2025-09-04 11:25:30 -07:00
Jon Staab 95698813c6 Monitor relay connections for restricted responses and show error to user 2025-09-04 11:25:12 -07:00
hodlbod 4001e877b4 Merge pull request #193 from coracle-social/trusted-relays
Buffer unsigned events until approved
2025-09-04 11:21:54 -07:00
Jon Staab 99defc6d79 Allow users to opt-in to spaces that strip signatures 2025-09-03 16:36:30 -07:00
Jon Staab a94883089e Rename username to nickname 2025-09-03 16:34:00 -07:00
1457 changed files with 9189 additions and 1574 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ VITE_PLATFORM_URL=https://flotilla.social
VITE_PLATFORM_TERMS=https://flotilla.social/terms VITE_PLATFORM_TERMS=https://flotilla.social/terms
VITE_PLATFORM_PRIVACY=https://flotilla.social/privacy VITE_PLATFORM_PRIVACY=https://flotilla.social/privacy
VITE_PLATFORM_NAME=Flotilla VITE_PLATFORM_NAME=Flotilla
VITE_PLATFORM_LOGO=static/flotilla.png VITE_PLATFORM_LOGO=static/logo.png
VITE_PLATFORM_RELAYS= VITE_PLATFORM_RELAYS=
VITE_PLATFORM_ACCENT="#7161FF" VITE_PLATFORM_ACCENT="#7161FF"
VITE_PLATFORM_SECONDARY="#EB5E28" VITE_PLATFORM_SECONDARY="#EB5E28"
+12
View File
@@ -1,5 +1,17 @@
# Changelog # Changelog
# 1.2.4
* Add direct message alerts
* Add alert settings page
* Add instructions to key download
* Add option that allows relays to strip signatures
* Detect relays that mostly refuse to serve requests
* Compress and upload profile images
* Use system theme by default
* Switch icon set, refactor how they're included
* Use capacitor's preferences for storage instead of localStorage
# 1.2.3 # 1.2.3
* Add `created_at` to event info dialog * Add `created_at` to event info dialog
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "social.flotilla" applicationId "social.flotilla"
minSdk rootProject.ext.minSdkVersion minSdk rootProject.ext.minSdkVersion
targetSdk rootProject.ext.targetSdkVersion targetSdk rootProject.ext.targetSdkVersion
versionCode 24 versionCode 25
versionName "1.2.3" versionName "1.2.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions { aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
+2
View File
@@ -12,7 +12,9 @@ dependencies {
implementation project(':capacitor-community-safe-area') implementation project(':capacitor-community-safe-area')
implementation project(':capacitor-app') implementation project(':capacitor-app')
implementation project(':capacitor-keyboard') implementation project(':capacitor-keyboard')
implementation project(':capacitor-preferences')
implementation project(':capacitor-push-notifications') implementation project(':capacitor-push-notifications')
implementation project(':capawesome-capacitor-android-dark-mode-support')
implementation project(':capawesome-capacitor-badge') implementation project(':capawesome-capacitor-badge')
implementation project(':nostr-signer-capacitor-plugin') implementation project(':nostr-signer-capacitor-plugin')
+6
View File
@@ -11,9 +11,15 @@ project(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacito
include ':capacitor-keyboard' include ':capacitor-keyboard'
project(':capacitor-keyboard').projectDir = new File('../node_modules/.pnpm/@capacitor+keyboard@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/keyboard/android') project(':capacitor-keyboard').projectDir = new File('../node_modules/.pnpm/@capacitor+keyboard@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/keyboard/android')
include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../node_modules/.pnpm/@capacitor+preferences@7.0.2_@capacitor+core@7.2.0/node_modules/@capacitor/preferences/android')
include ':capacitor-push-notifications' include ':capacitor-push-notifications'
project(':capacitor-push-notifications').projectDir = new File('../node_modules/.pnpm/@capacitor+push-notifications@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/push-notifications/android') project(':capacitor-push-notifications').projectDir = new File('../node_modules/.pnpm/@capacitor+push-notifications@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/push-notifications/android')
include ':capawesome-capacitor-android-dark-mode-support'
project(':capawesome-capacitor-android-dark-mode-support').projectDir = new File('../node_modules/.pnpm/@capawesome+capacitor-android-dark-mode-support@7.0.0_@capacitor+core@7.2.0/node_modules/@capawesome/capacitor-android-dark-mode-support/android')
include ':capawesome-capacitor-badge' include ':capawesome-capacitor-badge'
project(':capawesome-capacitor-badge').projectDir = new File('../node_modules/.pnpm/@capawesome+capacitor-badge@7.0.1_@capacitor+core@7.2.0/node_modules/@capawesome/capacitor-badge/android') project(':capawesome-capacitor-badge').projectDir = new File('../node_modules/.pnpm/@capawesome+capacitor-badge@7.0.1_@capacitor+core@7.2.0/node_modules/@capawesome/capacitor-badge/android')
+2 -2
View File
@@ -361,7 +361,7 @@
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.2.3; MARKETING_VERSION = 1.2.4;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla; PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -387,7 +387,7 @@
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.2.3; MARKETING_VERSION = 1.2.4;
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla; PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
+1
View File
@@ -14,6 +14,7 @@ def capacitor_pods
pod 'CapacitorCommunitySafeArea', :path => '../../node_modules/.pnpm/@capacitor-community+safe-area@7.0.0-alpha.1_@capacitor+core@7.2.0/node_modules/@capacitor-community/safe-area' pod 'CapacitorCommunitySafeArea', :path => '../../node_modules/.pnpm/@capacitor-community+safe-area@7.0.0-alpha.1_@capacitor+core@7.2.0/node_modules/@capacitor-community/safe-area'
pod 'CapacitorApp', :path => '../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/app' pod 'CapacitorApp', :path => '../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/app'
pod 'CapacitorKeyboard', :path => '../../node_modules/.pnpm/@capacitor+keyboard@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/keyboard' pod 'CapacitorKeyboard', :path => '../../node_modules/.pnpm/@capacitor+keyboard@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/keyboard'
pod 'CapacitorPreferences', :path => '../../node_modules/.pnpm/@capacitor+preferences@7.0.2_@capacitor+core@7.2.0/node_modules/@capacitor/preferences'
pod 'CapacitorPushNotifications', :path => '../../node_modules/.pnpm/@capacitor+push-notifications@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/push-notifications' pod 'CapacitorPushNotifications', :path => '../../node_modules/.pnpm/@capacitor+push-notifications@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/push-notifications'
pod 'CapawesomeCapacitorBadge', :path => '../../node_modules/.pnpm/@capawesome+capacitor-badge@7.0.1_@capacitor+core@7.2.0/node_modules/@capawesome/capacitor-badge' pod 'CapawesomeCapacitorBadge', :path => '../../node_modules/.pnpm/@capawesome+capacitor-badge@7.0.1_@capacitor+core@7.2.0/node_modules/@capawesome/capacitor-badge'
pod 'NostrSignerCapacitorPlugin', :path => '../../node_modules/.pnpm/nostr-signer-capacitor-plugin@0.0.4_@capacitor+core@7.2.0/node_modules/nostr-signer-capacitor-plugin' pod 'NostrSignerCapacitorPlugin', :path => '../../node_modules/.pnpm/nostr-signer-capacitor-plugin@0.0.4_@capacitor+core@7.2.0/node_modules/nostr-signer-capacitor-plugin'
+14 -12
View File
@@ -1,6 +1,6 @@
{ {
"name": "flotilla", "name": "flotilla",
"version": "1.2.3", "version": "1.2.4",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
@@ -45,7 +45,9 @@
"@capacitor/core": "^7.0.1", "@capacitor/core": "^7.0.1",
"@capacitor/ios": "^7.0.0", "@capacitor/ios": "^7.0.0",
"@capacitor/keyboard": "^7.0.0", "@capacitor/keyboard": "^7.0.0",
"@capacitor/preferences": "^7.0.2",
"@capacitor/push-notifications": "^7.0.1", "@capacitor/push-notifications": "^7.0.1",
"@capawesome/capacitor-android-dark-mode-support": "^7.0.0",
"@capawesome/capacitor-badge": "^7.0.1", "@capawesome/capacitor-badge": "^7.0.1",
"@getalby/sdk": "^5.1.0", "@getalby/sdk": "^5.1.0",
"@poppanator/sveltekit-svg": "^4.2.1", "@poppanator/sveltekit-svg": "^4.2.1",
@@ -56,17 +58,17 @@
"@types/throttle-debounce": "^5.0.2", "@types/throttle-debounce": "^5.0.2",
"@vite-pwa/assets-generator": "^0.2.6", "@vite-pwa/assets-generator": "^0.2.6",
"@vite-pwa/sveltekit": "^0.6.6", "@vite-pwa/sveltekit": "^0.6.6",
"@welshman/app": "^0.4.3", "@welshman/app": "^0.4.7",
"@welshman/content": "^0.4.3", "@welshman/content": "^0.4.7",
"@welshman/editor": "^0.4.3", "@welshman/editor": "^0.4.7",
"@welshman/feeds": "^0.4.3", "@welshman/feeds": "^0.4.7",
"@welshman/lib": "^0.4.3", "@welshman/lib": "^0.4.7",
"@welshman/net": "^0.4.3", "@welshman/net": "^0.4.7",
"@welshman/relay": "^0.4.3", "@welshman/relay": "^0.4.7",
"@welshman/router": "^0.4.3", "@welshman/router": "^0.4.7",
"@welshman/signer": "^0.4.3", "@welshman/signer": "^0.4.7",
"@welshman/store": "^0.4.3", "@welshman/store": "^0.4.7",
"@welshman/util": "^0.4.3", "@welshman/util": "^0.4.7",
"compressorjs": "^1.2.1", "compressorjs": "^1.2.1",
"daisyui": "^4.12.10", "daisyui": "^4.12.10",
"date-picker-svelte": "^2.13.0", "date-picker-svelte": "^2.13.0",
+110 -86
View File
@@ -29,9 +29,15 @@ importers:
'@capacitor/keyboard': '@capacitor/keyboard':
specifier: ^7.0.0 specifier: ^7.0.0
version: 7.0.1(@capacitor/core@7.2.0) version: 7.0.1(@capacitor/core@7.2.0)
'@capacitor/preferences':
specifier: ^7.0.2
version: 7.0.2(@capacitor/core@7.2.0)
'@capacitor/push-notifications': '@capacitor/push-notifications':
specifier: ^7.0.1 specifier: ^7.0.1
version: 7.0.1(@capacitor/core@7.2.0) version: 7.0.1(@capacitor/core@7.2.0)
'@capawesome/capacitor-android-dark-mode-support':
specifier: ^7.0.0
version: 7.0.0(@capacitor/core@7.2.0)
'@capawesome/capacitor-badge': '@capawesome/capacitor-badge':
specifier: ^7.0.1 specifier: ^7.0.1
version: 7.0.1(@capacitor/core@7.2.0) version: 7.0.1(@capacitor/core@7.2.0)
@@ -63,38 +69,38 @@ importers:
specifier: ^0.6.6 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)) 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': '@welshman/app':
specifier: ^0.4.3 specifier: ^0.4.7
version: 0.4.3(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) version: 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/content': '@welshman/content':
specifier: ^0.4.3 specifier: ^0.4.7
version: 0.4.3(typescript@5.8.3) version: 0.4.7(typescript@5.8.3)
'@welshman/editor': '@welshman/editor':
specifier: ^0.4.3 specifier: ^0.4.7
version: 0.4.3(@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) version: 0.4.7(@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': '@welshman/feeds':
specifier: ^0.4.3 specifier: ^0.4.7
version: 0.4.3(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) version: 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/lib': '@welshman/lib':
specifier: ^0.4.3 specifier: ^0.4.7
version: 0.4.3 version: 0.4.7
'@welshman/net': '@welshman/net':
specifier: ^0.4.3 specifier: ^0.4.7
version: 0.4.3(typescript@5.8.3)(ws@8.18.3) version: 0.4.7(typescript@5.8.3)(ws@8.18.3)
'@welshman/relay': '@welshman/relay':
specifier: ^0.4.3 specifier: ^0.4.7
version: 0.4.3(typescript@5.8.3) version: 0.4.7(typescript@5.8.3)
'@welshman/router': '@welshman/router':
specifier: ^0.4.3 specifier: ^0.4.7
version: 0.4.3(typescript@5.8.3) version: 0.4.7(typescript@5.8.3)
'@welshman/signer': '@welshman/signer':
specifier: ^0.4.3 specifier: ^0.4.7
version: 0.4.3(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) version: 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/store': '@welshman/store':
specifier: ^0.4.3 specifier: ^0.4.7
version: 0.4.3(typescript@5.8.3) version: 0.4.7(typescript@5.8.3)
'@welshman/util': '@welshman/util':
specifier: ^0.4.3 specifier: ^0.4.7
version: 0.4.3(typescript@5.8.3) version: 0.4.7(typescript@5.8.3)
compressorjs: compressorjs:
specifier: ^1.2.1 specifier: ^1.2.1
version: 1.2.1 version: 1.2.1
@@ -758,11 +764,21 @@ packages:
peerDependencies: peerDependencies:
'@capacitor/core': '>=7.0.0' '@capacitor/core': '>=7.0.0'
'@capacitor/preferences@7.0.2':
resolution: {integrity: sha512-JVCy0/oc6RsRencLOZ8rMqjNxAlHs7awPJU/MXqangsJ48oO2PnYGHfCvci6WgIJlqyC0QhvWZaO1BR1lVkHWQ==}
peerDependencies:
'@capacitor/core': '>=7.0.0'
'@capacitor/push-notifications@7.0.1': '@capacitor/push-notifications@7.0.1':
resolution: {integrity: sha512-nSHsMSrTHX5pOkX1Khse75/uvSx/JTcXG+9aT6a66CvzalH6MCs0ha8Jv+xu0k9xW8caO+qSUMjfj5Oy82Uxmw==} resolution: {integrity: sha512-nSHsMSrTHX5pOkX1Khse75/uvSx/JTcXG+9aT6a66CvzalH6MCs0ha8Jv+xu0k9xW8caO+qSUMjfj5Oy82Uxmw==}
peerDependencies: peerDependencies:
'@capacitor/core': '>=7.0.0' '@capacitor/core': '>=7.0.0'
'@capawesome/capacitor-android-dark-mode-support@7.0.0':
resolution: {integrity: sha512-xLa988v9MEnFZj7Aje3v2vvYbs5U3zk8vaO24OioXUV1ZPjGy4R2VkdoKG8fTsSYRdF6s4/8DpCuos8JLJqpjw==}
peerDependencies:
'@capacitor/core': '>=7.0.0'
'@capawesome/capacitor-badge@7.0.1': '@capawesome/capacitor-badge@7.0.1':
resolution: {integrity: sha512-jhVieRRVLgGO1NU7PW8uWZmf3WD4IsYUlkrJ82KuoRgLFx1tbJGwHU1ro0sUJmEwfLO9vldhBnJJ/J5nHrjbQQ==} resolution: {integrity: sha512-jhVieRRVLgGO1NU7PW8uWZmf3WD4IsYUlkrJ82KuoRgLFx1tbJGwHU1ro0sUJmEwfLO9vldhBnJJ/J5nHrjbQQ==}
peerDependencies: peerDependencies:
@@ -1632,41 +1648,41 @@ packages:
'@vite-pwa/assets-generator': '@vite-pwa/assets-generator':
optional: true optional: true
'@welshman/app@0.4.3': '@welshman/app@0.4.7':
resolution: {integrity: sha512-bnanLtSsX45gqdFKlCZr4IRBMQo+7TXycduI9ffgj/9rEu+94rKIEjUVOnbZF8+hhtaqL/Eypvjz2+N33O1mUQ==} resolution: {integrity: sha512-JEgr3NhzDLeOoTSZ4+AESKhW+kwqYClLbLd6ccHV+Wa7hjoPJy2FlY3rDuppw9QSeTNCDEOvqCsjeWYz83lUBA==}
'@welshman/content@0.4.3': '@welshman/content@0.4.7':
resolution: {integrity: sha512-5QxfY+QOBfrEHF76/feJ1Oxvp/SSkvp091b3AhxbcuvLnPOfaaUqAHmfktaM3XhAZF2T2bGDICV/vO7eUghz+Q==} resolution: {integrity: sha512-PF2FqiE3QUybl0CwwaEI2aGCZIis2rbdvBgeafgPiHW7fRYROxUZdtcMy+MyiRfiV40uoBcYwu/N6hHCRU7Pag==}
'@welshman/editor@0.4.3': '@welshman/editor@0.4.7':
resolution: {integrity: sha512-R03W3XfOFqougx2qJrfHchpVA445Rt7zYQ0hzNo8oFLjWCMG9flvNXY5n7imPGTGthNaUm+s87dDLbkxpL1m/A==} resolution: {integrity: sha512-K6XCLG+vVvVeEYPx+m/+Lfx/JO+NtvvIemCds6gFjY7ac+WaQuEQDP3kFGYQu69XiFXp/UZrIa+t7SezEpnU8A==}
'@welshman/feeds@0.4.3': '@welshman/feeds@0.4.7':
resolution: {integrity: sha512-176IpUPpvYl0pLXNnHRL2pWAx0C1XJ2b7BSxAXw/CC77Dyn857AFL5tO6YnZSQg5/RtH3WlldMtY2UsJF0fybg==} resolution: {integrity: sha512-aZQuTUD4aSkL0s2BkjwEpo5KTd9BKf/XiOssQrltLdc8NIsz8RIO0XLgCpFb0/dmqHRoJEqU/plIBy7AlleRCQ==}
'@welshman/lib@0.4.3': '@welshman/lib@0.4.7':
resolution: {integrity: sha512-wOfrdHfoA+WQwFI54lvVUoRrnZJNTeYHPYlKA+g+wKN1iS4rvpsKi+echWNkzRN8WcHT53qymVhgHEoqqDfOXA==} resolution: {integrity: sha512-VP3WO2ROo5pf2vHwnrdt6lQVTc8Eo52Ie+1/9ZzfTrSxtLrreSSxW3H+1oPDbHl3FXbDnQWdFWbxys6OxzKZWw==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
'@welshman/net@0.4.3': '@welshman/net@0.4.7':
resolution: {integrity: sha512-JBnOAYCP1LgEXuPrM1G/QrKwUClXxTmyj+Ksdf41Scr8XLxPPpAh1HQ2RirCekydGdw6Ax6SxzUzN69Hru3vew==} resolution: {integrity: sha512-S0dGVqNAfo5cvBxIuaI2oEX0JUs2FuzkOcYXkMNB1plWzi1mBcskNCBWfFf/zHJYaqUoYjZ/tDARy64lge9m/w==}
'@welshman/relay@0.4.3': '@welshman/relay@0.4.7':
resolution: {integrity: sha512-UI+IAwEkicaiu8DhBmy81NVPFkHSUVjymbXZsxaCZuT86tJQvtr5cpf0/f/blh9Ncl8Gf1qmjj3A0Q4WnM6Z2A==} resolution: {integrity: sha512-FkqYswNA3uT1NeJVHdEZ7p9jEPGCFMx4ci2y+h9o25rCDdxg3WUhqDSdc5d85sGTO0qG2pNnvNMfS/Du/nFlOA==}
'@welshman/router@0.4.3': '@welshman/router@0.4.7':
resolution: {integrity: sha512-ptWsxTkxIstELFMxkgOHrK65JvRUIN6q3HC/nu9Xy590tohZ3NeQUCfA7ujFo8LfymRavx/gdMwqOfePvgwHUQ==} resolution: {integrity: sha512-HnB1qrKGNxL8HtC6p47yHUnaDHevi+IKtqWEVCIFMRf17GwINBc3wp3+d3pu9KBBXItFZz1awABvZK+pNKQcgg==}
'@welshman/signer@0.4.3': '@welshman/signer@0.4.7':
resolution: {integrity: sha512-WQduEU5arxaHDza6NfbRMBBh9PqCsmD/a32fsnY/+eaID9eUtABu9s/NM2ZWu2rQWi1VeQ95RSxAZWZOzgUmqw==} resolution: {integrity: sha512-V5Jdmblb2kPO5bAv1CzVrodZiwKpYYotmS6MFXfWmyrKKp+9B5KoMWzXt6yfd4HWYbEKEjLhbm1gzbckupW8Nw==}
peerDependencies: peerDependencies:
nostr-signer-capacitor-plugin: ~0.0.4 nostr-signer-capacitor-plugin: ~0.0.4
'@welshman/store@0.4.3': '@welshman/store@0.4.7':
resolution: {integrity: sha512-D1MgedZROr+7X+O+2xH+ZV/5W1CmkmEpceiDQfxLTmgrihNGsnBf7Ub+b6DwdzCrVZNgh3wVByASZe4ppjV9LA==} resolution: {integrity: sha512-8PniW1AOOYFtLRYMuay62taumW7zgwtBmouwoMh08fBQjLb+c90V4g2cEGVWoyvKXSLzQkQppPlaqYzdSDqgwg==}
'@welshman/util@0.4.3': '@welshman/util@0.4.7':
resolution: {integrity: sha512-DN/Au5e17cNBGvuuiJAM0cVe1XSvFbiZvZ+WZK9K2ACNVK2WccIywAGYTAP3buZZIvnpt7Af+60qmhMQSBD+/g==} resolution: {integrity: sha512-FlmBiZeKlAEAAwyhu7cWtlfAxU3CWX7WQGn0NkCZaAjgGV3n8LIDjT1u9m1PmXirBT0+qFNGLWas3p72IQMLgg==}
'@xml-tools/parser@1.0.11': '@xml-tools/parser@1.0.11':
resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==} resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==}
@@ -5593,10 +5609,18 @@ snapshots:
dependencies: dependencies:
'@capacitor/core': 7.2.0 '@capacitor/core': 7.2.0
'@capacitor/preferences@7.0.2(@capacitor/core@7.2.0)':
dependencies:
'@capacitor/core': 7.2.0
'@capacitor/push-notifications@7.0.1(@capacitor/core@7.2.0)': '@capacitor/push-notifications@7.0.1(@capacitor/core@7.2.0)':
dependencies: dependencies:
'@capacitor/core': 7.2.0 '@capacitor/core': 7.2.0
'@capawesome/capacitor-android-dark-mode-support@7.0.0(@capacitor/core@7.2.0)':
dependencies:
'@capacitor/core': 7.2.0
'@capawesome/capacitor-badge@7.0.1(@capacitor/core@7.2.0)': '@capawesome/capacitor-badge@7.0.1(@capacitor/core@7.2.0)':
dependencies: dependencies:
'@capacitor/core': 7.2.0 '@capacitor/core': 7.2.0
@@ -6500,17 +6524,17 @@ snapshots:
optionalDependencies: optionalDependencies:
'@vite-pwa/assets-generator': 0.2.6 '@vite-pwa/assets-generator': 0.2.6
'@welshman/app@0.4.3(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)': '@welshman/app@0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
dependencies: dependencies:
'@types/throttle-debounce': 5.0.2 '@types/throttle-debounce': 5.0.2
'@welshman/feeds': 0.4.3(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) '@welshman/feeds': 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/lib': 0.4.3 '@welshman/lib': 0.4.7
'@welshman/net': 0.4.3(typescript@5.8.3)(ws@8.18.3) '@welshman/net': 0.4.7(typescript@5.8.3)(ws@8.18.3)
'@welshman/relay': 0.4.3(typescript@5.8.3) '@welshman/relay': 0.4.7(typescript@5.8.3)
'@welshman/router': 0.4.3(typescript@5.8.3) '@welshman/router': 0.4.7(typescript@5.8.3)
'@welshman/signer': 0.4.3(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) '@welshman/signer': 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/store': 0.4.3(typescript@5.8.3) '@welshman/store': 0.4.7(typescript@5.8.3)
'@welshman/util': 0.4.3(typescript@5.8.3) '@welshman/util': 0.4.7(typescript@5.8.3)
fuse.js: 7.1.0 fuse.js: 7.1.0
idb: 8.0.2 idb: 8.0.2
svelte: 4.2.20 svelte: 4.2.20
@@ -6520,14 +6544,14 @@ snapshots:
- typescript - typescript
- ws - ws
'@welshman/content@0.4.3(typescript@5.8.3)': '@welshman/content@0.4.7(typescript@5.8.3)':
dependencies: dependencies:
'@braintree/sanitize-url': 7.1.1 '@braintree/sanitize-url': 7.1.1
nostr-tools: 2.14.2(typescript@5.8.3) nostr-tools: 2.14.2(typescript@5.8.3)
transitivePeerDependencies: transitivePeerDependencies:
- typescript - typescript
'@welshman/editor@0.4.3(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)': '@welshman/editor@0.4.7(@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: dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.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': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
@@ -6542,8 +6566,8 @@ snapshots:
'@tiptap/extension-text': 2.26.1(@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))
'@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) '@tiptap/suggestion': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@welshman/lib': 0.4.3 '@welshman/lib': 0.4.7
'@welshman/util': 0.4.3(typescript@5.8.3) '@welshman/util': 0.4.7(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-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) nostr-tools: 2.14.2(typescript@5.8.3)
tippy.js: 6.3.7 tippy.js: 6.3.7
@@ -6558,78 +6582,78 @@ snapshots:
- tiptap-markdown - tiptap-markdown
- typescript - typescript
'@welshman/feeds@0.4.3(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)': '@welshman/feeds@0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
dependencies: dependencies:
'@welshman/lib': 0.4.3 '@welshman/lib': 0.4.7
'@welshman/net': 0.4.3(typescript@5.8.3)(ws@8.18.3) '@welshman/net': 0.4.7(typescript@5.8.3)(ws@8.18.3)
'@welshman/relay': 0.4.3(typescript@5.8.3) '@welshman/relay': 0.4.7(typescript@5.8.3)
'@welshman/router': 0.4.3(typescript@5.8.3) '@welshman/router': 0.4.7(typescript@5.8.3)
'@welshman/signer': 0.4.3(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3) '@welshman/signer': 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/util': 0.4.3(typescript@5.8.3) '@welshman/util': 0.4.7(typescript@5.8.3)
trava: 1.2.1 trava: 1.2.1
transitivePeerDependencies: transitivePeerDependencies:
- nostr-signer-capacitor-plugin - nostr-signer-capacitor-plugin
- typescript - typescript
- ws - ws
'@welshman/lib@0.4.3': '@welshman/lib@0.4.7':
dependencies: dependencies:
'@scure/base': 1.2.6 '@scure/base': 1.2.6
'@types/events': 3.0.3 '@types/events': 3.0.3
events: 3.3.0 events: 3.3.0
'@welshman/net@0.4.3(typescript@5.8.3)(ws@8.18.3)': '@welshman/net@0.4.7(typescript@5.8.3)(ws@8.18.3)':
dependencies: dependencies:
'@welshman/lib': 0.4.3 '@welshman/lib': 0.4.7
'@welshman/relay': 0.4.3(typescript@5.8.3) '@welshman/relay': 0.4.7(typescript@5.8.3)
'@welshman/util': 0.4.3(typescript@5.8.3) '@welshman/util': 0.4.7(typescript@5.8.3)
events: 3.3.0 events: 3.3.0
isomorphic-ws: 5.0.0(ws@8.18.3) isomorphic-ws: 5.0.0(ws@8.18.3)
transitivePeerDependencies: transitivePeerDependencies:
- typescript - typescript
- ws - ws
'@welshman/relay@0.4.3(typescript@5.8.3)': '@welshman/relay@0.4.7(typescript@5.8.3)':
dependencies: dependencies:
'@welshman/lib': 0.4.3 '@welshman/lib': 0.4.7
'@welshman/util': 0.4.3(typescript@5.8.3) '@welshman/util': 0.4.7(typescript@5.8.3)
transitivePeerDependencies: transitivePeerDependencies:
- typescript - typescript
'@welshman/router@0.4.3(typescript@5.8.3)': '@welshman/router@0.4.7(typescript@5.8.3)':
dependencies: dependencies:
'@welshman/lib': 0.4.3 '@welshman/lib': 0.4.7
'@welshman/relay': 0.4.3(typescript@5.8.3) '@welshman/relay': 0.4.7(typescript@5.8.3)
'@welshman/util': 0.4.3(typescript@5.8.3) '@welshman/util': 0.4.7(typescript@5.8.3)
transitivePeerDependencies: transitivePeerDependencies:
- typescript - typescript
'@welshman/signer@0.4.3(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)': '@welshman/signer@0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
dependencies: dependencies:
'@noble/curves': 1.9.2 '@noble/curves': 1.9.2
'@noble/hashes': 1.8.0 '@noble/hashes': 1.8.0
'@welshman/lib': 0.4.3 '@welshman/lib': 0.4.7
'@welshman/net': 0.4.3(typescript@5.8.3)(ws@8.18.3) '@welshman/net': 0.4.7(typescript@5.8.3)(ws@8.18.3)
'@welshman/util': 0.4.3(typescript@5.8.3) '@welshman/util': 0.4.7(typescript@5.8.3)
nostr-signer-capacitor-plugin: 0.0.4(@capacitor/core@7.2.0) nostr-signer-capacitor-plugin: 0.0.4(@capacitor/core@7.2.0)
nostr-tools: 2.14.2(typescript@5.8.3) nostr-tools: 2.14.2(typescript@5.8.3)
transitivePeerDependencies: transitivePeerDependencies:
- typescript - typescript
- ws - ws
'@welshman/store@0.4.3(typescript@5.8.3)': '@welshman/store@0.4.7(typescript@5.8.3)':
dependencies: dependencies:
'@welshman/lib': 0.4.3 '@welshman/lib': 0.4.7
'@welshman/relay': 0.4.3(typescript@5.8.3) '@welshman/relay': 0.4.7(typescript@5.8.3)
'@welshman/util': 0.4.3(typescript@5.8.3) '@welshman/util': 0.4.7(typescript@5.8.3)
svelte: 4.2.20 svelte: 4.2.20
transitivePeerDependencies: transitivePeerDependencies:
- typescript - typescript
'@welshman/util@0.4.3(typescript@5.8.3)': '@welshman/util@0.4.7(typescript@5.8.3)':
dependencies: dependencies:
'@types/ws': 8.18.1 '@types/ws': 8.18.1
'@welshman/lib': 0.4.3 '@welshman/lib': 0.4.7
js-base64: 3.7.7 js-base64: 3.7.7
nostr-tools: 2.14.2(typescript@5.8.3) nostr-tools: 2.14.2(typescript@5.8.3)
nostr-wasm: 0.1.0 nostr-wasm: 0.1.0
+23 -81
View File
@@ -1,37 +1,22 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import {decrypt} from "@welshman/signer" import {randomInt, displayList, TIMEZONE, identity} from "@welshman/lib"
import {randomInt, parseJson, fromPairs, displayList, TIMEZONE, identity} from "@welshman/lib" import {displayRelayUrl, getTagValue, THREAD, MESSAGE, EVENT_TIME, COMMENT} from "@welshman/util"
import {
displayRelayUrl,
getTagValue,
getAddress,
THREAD,
MESSAGE,
EVENT_TIME,
COMMENT,
} from "@welshman/util"
import type {Filter} from "@welshman/util" import type {Filter} from "@welshman/util"
import {makeIntersectionFeed, makeRelayFeed, feedFromFilters} from "@welshman/feeds" import {makeIntersectionFeed, makeRelayFeed, feedFromFilters} from "@welshman/feeds"
import {pubkey, signer, getThunkError} from "@welshman/app" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import FieldInline from "@lib/components/FieldInline.svelte" import FieldInline from "@lib/components/FieldInline.svelte"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
import { import {alerts, getMembershipUrls, userMembership} from "@app/core/state"
alerts, import {requestRelayClaim} from "@app/core/requests"
getMembershipUrls, import {createAlert} from "@app/core/commands"
userMembership, import {canSendPushNotifications} from "@app/util/push"
NOTIFIER_PUBKEY,
NOTIFIER_RELAY,
} from "@app/core/state"
import {loadAlertStatuses, requestRelayClaim} from "@app/core/requests"
import {publishAlert, attemptAuth} from "@app/core/commands"
import type {AlertParams} from "@app/core/commands"
import {platform, platformName, canSendPushNotifications, getPushInfo} from "@app/util/push"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
type Props = { type Props = {
@@ -110,66 +95,20 @@
try { try {
const claim = url ? await requestRelayClaim(url) : undefined const claim = url ? await requestRelayClaim(url) : undefined
const claims = claim ? {[url]: claim} : {}
const feed = makeIntersectionFeed(feedFromFilters(filters), makeRelayFeed(url))
const description = `for ${displayList(display)} on ${displayRelayUrl(url)}`
const params: AlertParams = {feed, claims, description}
if (channel === "email") { const {error} = await createAlert({
const cadence = cron?.endsWith("1") ? "Weekly" : "Daily" feed: makeIntersectionFeed(feedFromFilters(filters), makeRelayFeed(url)),
claims: claim ? {[url]: claim} : {},
params.description = `${cadence} alert ${description}, sent via email.` description: `for ${displayList(display)} on ${displayRelayUrl(url)}`,
params.email = { email: channel === "email" ? {cron, email} : undefined,
cron, })
email,
handler: [
"31990:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:1737058597050",
"wss://relay.nostr.band/",
"web",
],
}
} else {
try {
// @ts-ignore
params[platform] = await getPushInfo()
params.description = `${platformName} push notification ${description}.`
} catch (e: any) {
return pushToast({
theme: "error",
message: String(e),
})
}
}
// If we don't do this we'll get an event rejection
await attemptAuth(NOTIFIER_RELAY)
const thunk = await publishAlert(params)
const error = await getThunkError(thunk)
if (error) { if (error) {
return pushToast({ pushToast({theme: "error", message: error})
theme: "error", } else {
message: `Failed to send your alert to the notification server (${error}).`, pushToast({message: "Your alert has been successfully created!"})
}) back()
} }
// Fetch our new status to make sure it's active
const address = getAddress(thunk.event)
const statusEvents = await loadAlertStatuses($pubkey!)
const statusEvent = statusEvents.find(event => getTagValue("d", event.tags) === address)
const statusTags = statusEvent
? parseJson(await decrypt(signer.get(), NOTIFIER_PUBKEY, statusEvent.content))
: []
const {status = "error", message = "Your alert was not activated"}: Record<string, string> =
fromPairs(statusTags)
if (status === "error") {
return pushToast({theme: "error", message})
}
pushToast({message: "Your alert has been successfully created!"})
back()
} finally { } finally {
loading = false loading = false
} }
@@ -187,6 +126,9 @@
{#snippet title()} {#snippet title()}
Add an Alert Add an Alert
{/snippet} {/snippet}
{#snippet info()}
Enable notifications to keep up to date on activity you care about.
{/snippet}
</ModalHeader> </ModalHeader>
{#if canSendPushNotifications()} {#if canSendPushNotifications()}
<FieldInline> <FieldInline>
@@ -262,12 +204,12 @@
</FieldInline> </FieldInline>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary" disabled={loading}> <Button type="submit" class="btn btn-primary" disabled={loading}>
<Spinner {loading}>Confirm</Spinner> <Spinner {loading}>Confirm</Spinner>
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
</form> </form>
+2 -6
View File
@@ -1,8 +1,7 @@
<script lang="ts"> <script lang="ts">
import Confirm from "@lib/components/Confirm.svelte" import Confirm from "@lib/components/Confirm.svelte"
import type {Alert} from "@app/core/state" import type {Alert} from "@app/core/state"
import {NOTIFIER_RELAY, NOTIFIER_PUBKEY} from "@app/core/state" import {deleteAlert} from "@app/core/commands"
import {publishDelete} from "@app/core/commands"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
type Props = { type Props = {
@@ -12,10 +11,7 @@
const {alert}: Props = $props() const {alert}: Props = $props()
const confirm = () => { const confirm = () => {
const relays = [NOTIFIER_RELAY] deleteAlert(alert)
const tags = [["p", NOTIFIER_PUBKEY]]
publishDelete({event: alert.event, relays, tags, protect: false})
pushToast({message: "Your alert has been deleted!"}) pushToast({message: "Your alert has been deleted!"})
history.back() history.back()
} }
+5 -32
View File
@@ -1,12 +1,13 @@
<script lang="ts"> <script lang="ts">
import {parseJson} from "@welshman/lib" import {parseJson} from "@welshman/lib"
import {displayFeeds} from "@welshman/feeds" import {displayFeeds} from "@welshman/feeds"
import {getAddress, getTagValue, getTagValues} from "@welshman/util" import {getTagValue, getTagValues} from "@welshman/util"
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import AlertDelete from "@app/components/AlertDelete.svelte" import AlertDelete from "@app/components/AlertDelete.svelte"
import AlertStatus from "@app/components/AlertStatus.svelte"
import type {Alert} from "@app/core/state" import type {Alert} from "@app/core/state"
import {deriveAlertStatus} from "@app/core/state"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
type Props = { type Props = {
@@ -15,7 +16,6 @@
const {alert}: Props = $props() const {alert}: Props = $props()
const status = deriveAlertStatus(getAddress(alert.event))
const cron = $derived(getTagValue("cron", alert.tags)) const cron = $derived(getTagValue("cron", alert.tags))
const channel = $derived(getTagValue("channel", alert.tags)) const channel = $derived(getTagValue("channel", alert.tags))
const feeds = $derived(getTagValues("feed", alert.tags)) const feeds = $derived(getTagValues("feed", alert.tags))
@@ -34,36 +34,9 @@
<div class="flex items-start justify-between gap-4"> <div class="flex items-start justify-between gap-4">
<div class="flex items-start gap-4"> <div class="flex items-start gap-4">
<Button class="py-1" onclick={startDelete}> <Button class="py-1" onclick={startDelete}>
<Icon icon="trash-bin-2" /> <Icon icon={TrashBin2} />
</Button> </Button>
<div class="flex-inline gap-1">{description}</div> <div class="flex-inline gap-1">{description}</div>
</div> </div>
{#if $status} <AlertStatus {alert} />
{@const statusText = getTagValue("status", $status.tags) || "error"}
{#if statusText === "ok"}
<span
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-base-content px-3 py-1 text-sm"
data-tip={getTagValue("message", $status.tags)}>
Active
</span>
{:else if statusText === "pending"}
<span
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-base-content border-yellow-500 px-3 py-1 text-sm text-yellow-500"
data-tip={getTagValue("message", $status.tags)}>
Pending
</span>
{:else}
<span
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-error px-3 py-1 text-sm text-error"
data-tip={getTagValue("message", $status.tags)}>
{statusText.replace("-", " ").replace(/^(.)/, x => x.toUpperCase())}
</span>
{/if}
{:else}
<span
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-error px-3 py-1 text-sm text-error"
data-tip="The notification server did not respond to your request.">
Inactive
</span>
{/if}
</div> </div>
+42
View File
@@ -0,0 +1,42 @@
<script lang="ts">
import {getAddress, getTagValue} from "@welshman/util"
import type {Alert} from "@app/core/state"
import {deriveAlertStatus} from "@app/core/state"
type Props = {
alert: Alert
}
const {alert}: Props = $props()
const status = deriveAlertStatus(getAddress(alert.event))
</script>
{#if $status}
{@const statusText = getTagValue("status", $status.tags) || "error"}
{#if statusText === "ok"}
<span
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-base-content px-3 py-1 text-sm"
data-tip={getTagValue("message", $status.tags)}>
Active
</span>
{:else if statusText === "pending"}
<span
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-base-content border-yellow-500 px-3 py-1 text-sm text-yellow-500"
data-tip={getTagValue("message", $status.tags)}>
Pending
</span>
{:else}
<span
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-error px-3 py-1 text-sm text-error"
data-tip={getTagValue("message", $status.tags)}>
{statusText.replace("-", " ").replace(/^(.)/, x => x.toUpperCase())}
</span>
{/if}
{:else}
<span
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-error px-3 py-1 text-sm text-error"
data-tip="The notification server did not respond to your request.">
Inactive
</span>
{/if}
+89 -20
View File
@@ -1,11 +1,17 @@
<script lang="ts"> <script lang="ts">
import {getTagValue} from "@welshman/util" import {sleep} from "@welshman/lib"
import {getTagValue, getAddress} from "@welshman/util"
import {isRelayFeed, findFeed} from "@welshman/feeds"
import Inbox from "@assets/icons/inbox.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import AlertAdd from "@app/components/AlertAdd.svelte" import AlertAdd from "@app/components/AlertAdd.svelte"
import AlertItem from "@app/components/AlertItem.svelte" import AlertItem from "@app/components/AlertItem.svelte"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import {alerts} from "@app/core/state" import {pushToast} from "@app/util/toast"
import {alerts, dmAlert, deriveAlertStatus, userInboxRelays, getAlertFeed} from "@app/core/state"
import {deleteAlert, createDmAlert} from "@app/core/commands"
type Props = { type Props = {
url?: string url?: string
@@ -15,29 +21,92 @@
const {url = "", channel = "push", hideSpaceField = false}: Props = $props() const {url = "", channel = "push", hideSpaceField = false}: Props = $props()
const startAlert = () => pushModal(AlertAdd, {url, channel, hideSpaceField}) const dmStatus = $derived($dmAlert ? deriveAlertStatus(getAddress($dmAlert.event)) : undefined)
const filteredAlerts = $derived( const filteredAlerts = $derived(
url ? $alerts.filter(a => getTagValue("feed", a.tags)?.includes(url)) : $alerts, $alerts.filter(alert => {
const feed = getAlertFeed(alert)
// Skip non-feeds and DM alerts
if (!feed || alert === $dmAlert) return false
// If we have a space url, only match feeds for this space
if (url) return findFeed(feed, f => isRelayFeed(f) && f.includes(url))
return true
}),
) )
const startAlert = () => pushModal(AlertAdd, {url, channel, hideSpaceField})
const uncheckDmAlert = async (message: string) => {
await sleep(100)
toggle.checked = false
pushToast({theme: "error", message})
}
const onToggle = async () => {
if ($dmAlert) {
deleteAlert($dmAlert)
} else {
if ($userInboxRelays.length === 0) {
return uncheckDmAlert("Please set up your messaging relays before enabling alerts.")
}
const {error} = await createDmAlert()
if (error) {
return uncheckDmAlert(error)
}
pushToast({message: "Your alert has been successfully created!"})
}
}
let toggle: HTMLInputElement
</script> </script>
<div class="card2 bg-alt flex flex-col gap-6 shadow-xl"> <div class="col-4">
<div class="flex items-center justify-between"> <div class="card2 bg-alt flex flex-col gap-6 shadow-xl">
<strong class="flex items-center gap-3"> <div class="flex items-center justify-between">
<Icon icon="inbox" /> <strong class="flex items-center gap-3">
Alerts <Icon icon={Inbox} />
</strong> Alerts
<Button class="btn btn-primary btn-sm" onclick={startAlert}> </strong>
<Icon icon="add-circle" /> <Button class="btn btn-primary btn-sm" onclick={startAlert}>
Add Alert <Icon icon={AddCircle} />
</Button> Add Alert
</Button>
</div>
<div class="col-4">
{#each filteredAlerts as alert (alert.event.id)}
<AlertItem {alert} />
{:else}
<p class="text-center opacity-75 py-12">Nothing here yet!</p>
{/each}
</div>
</div> </div>
<div class="col-4"> <div class="card2 bg-alt flex flex-col gap-4 shadow-xl">
{#each filteredAlerts as alert (alert.event.id)} <div class="flex justify-between">
<AlertItem {alert} /> <p>Notify me about new direct messages</p>
{:else} <input
<p class="text-center opacity-75 py-12">Nothing here yet!</p> type="checkbox"
{/each} class="toggle toggle-primary"
bind:this={toggle}
checked={Boolean($dmAlert)}
oninput={onToggle} />
</div>
{#if $dmStatus}
{@const status = getTagValue("status", $dmStatus.tags) || "error"}
{#if status !== "ok"}
<div class="alert alert-error border border-solid border-error bg-transparent text-error">
<p>
{getTagValue("message", $dmStatus.tags) ||
"The notification server did not respond to your request."}
</p>
</div>
{/if}
{/if}
</div> </div>
</div> </div>
+4 -2
View File
@@ -3,6 +3,8 @@
import Scanner from "@lib/components/Scanner.svelte" import Scanner from "@lib/components/Scanner.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Field from "@lib/components/Field.svelte" import Field from "@lib/components/Field.svelte"
import CpuBolt from "@assets/icons/cpu-bolt.svg?dataurl"
import QrCode from "@assets/icons/qr-code.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import InfoBunker from "@app/components/InfoBunker.svelte" import InfoBunker from "@app/components/InfoBunker.svelte"
import type {Nip46Controller} from "@app/util/nip46" import type {Nip46Controller} from "@app/util/nip46"
@@ -33,10 +35,10 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="cpu" /> <Icon icon={CpuBolt} />
<input disabled={$loading} bind:value={$bunker} class="grow" placeholder="bunker://" /> <input disabled={$loading} bind:value={$bunker} class="grow" placeholder="bunker://" />
<Button onclick={toggleScanner}> <Button onclick={toggleScanner}>
<Icon icon="qr-code" /> <Icon icon={QrCode} />
</Button> </Button>
</label> </label>
{/snippet} {/snippet}
@@ -11,6 +11,7 @@
import {publishDelete, publishReaction, canEnforceNip70} from "@app/core/commands" import {publishDelete, publishReaction, canEnforceNip70} from "@app/core/commands"
import {makeCalendarPath} from "@app/util/routes" import {makeCalendarPath} from "@app/util/routes"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import Pen2 from "@assets/icons/pen-2.svg?dataurl"
const { const {
url, url,
@@ -47,7 +48,7 @@
{#if event.pubkey === $pubkey} {#if event.pubkey === $pubkey}
<li> <li>
<Button onclick={editEvent}> <Button onclick={editEvent}>
<Icon size={4} icon="pen" /> <Icon size={4} icon={Pen2} />
Edit Event Edit Event
</Button> </Button>
</li> </li>
+6 -3
View File
@@ -6,6 +6,9 @@
import {publishThunk} from "@welshman/app" import {publishThunk} from "@welshman/app"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import {daysBetween} from "@lib/util" import {daysBetween} from "@lib/util"
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
import MapPoint from "@assets/icons/map-point.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Field from "@lib/components/Field.svelte" import Field from "@lib/components/Field.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -131,7 +134,7 @@
{#if $uploading} {#if $uploading}
<span class="loading loading-spinner loading-xs"></span> <span class="loading loading-spinner loading-xs"></span>
{:else} {:else}
<Icon icon="gallery-send" /> <Icon icon={GallerySend} />
{/if} {/if}
</Button> </Button>
</div> </div>
@@ -159,14 +162,14 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="map-point" /> <Icon icon={MapPoint} />
<input bind:value={location} class="grow" type="text" /> <input bind:value={location} class="grow" type="text" />
</label> </label>
{/snippet} {/snippet}
</Field> </Field>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary" disabled={$uploading}> <Button type="submit" class="btn btn-primary" disabled={$uploading}>
@@ -6,6 +6,7 @@
formatTimestampAsTime, formatTimestampAsTime,
} from "@welshman/lib" } from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import ClockCircle from "@assets/icons/clock-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
type Props = { type Props = {
@@ -24,7 +25,7 @@
<div class="flex flex-grow flex-wrap justify-between gap-2"> <div class="flex flex-grow flex-wrap justify-between gap-2">
<p class="text-xl">{meta.title || meta.name}</p> <p class="text-xl">{meta.title || meta.name}</p>
<div class="flex items-center gap-2 text-sm"> <div class="flex items-center gap-2 text-sm">
<Icon icon="clock-circle" size={4} /> <Icon icon={ClockCircle} size={4} />
<span class="sm:hidden">{formatTimestampAsDate(start)}</span> <span class="sm:hidden">{formatTimestampAsDate(start)}</span>
{formatTimestampAsTime(start)}{isSingleDay {formatTimestampAsTime(start)}{isSingleDay
? formatTimestampAsTime(end) ? formatTimestampAsTime(end)
+4 -2
View File
@@ -1,6 +1,8 @@
<script lang="ts"> <script lang="ts">
import {fromPairs} from "@welshman/lib" import {fromPairs} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
import MapPoint from "@assets/icons/map-point.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ProfileLink from "@app/components/ProfileLink.svelte" import ProfileLink from "@app/components/ProfileLink.svelte"
@@ -15,12 +17,12 @@
<div class="flex min-w-0 flex-col gap-1 text-sm opacity-75"> <div class="flex min-w-0 flex-col gap-1 text-sm opacity-75">
<span class="flex items-center gap-1"> <span class="flex items-center gap-1">
<Icon icon="user-circle" size={4} /> <Icon icon={UserCircle} size={4} />
Posted by <ProfileLink pubkey={event.pubkey} {url} /> Posted by <ProfileLink pubkey={event.pubkey} {url} />
</span> </span>
{#if meta.location} {#if meta.location}
<span class="flex items-start gap-1"> <span class="flex items-start gap-1">
<Icon icon="map-point" class="mt-[2px]" size={4} /> <Icon icon={MapPoint} class="mt-[2px]" size={4} />
<span class="break-words">{meta.location}</span> <span class="break-words">{meta.location}</span>
</span> </span>
{/if} {/if}
+4 -2
View File
@@ -2,6 +2,8 @@
import {writable} from "svelte/store" import {writable} from "svelte/store"
import type {EventContent} from "@welshman/util" import type {EventContent} from "@welshman/util"
import {isMobile, preventDefault} from "@lib/html" import {isMobile, preventDefault} from "@lib/html"
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
import Plane from "@assets/icons/plane-2.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import EditorContent from "@app/editor/EditorContent.svelte" import EditorContent from "@app/editor/EditorContent.svelte"
@@ -48,7 +50,7 @@
{#if $uploading} {#if $uploading}
<span class="loading loading-spinner loading-xs"></span> <span class="loading loading-spinner loading-xs"></span>
{:else} {:else}
<Icon icon="gallery-send" /> <Icon icon={GallerySend} />
{/if} {/if}
</Button> </Button>
<div class="chat-editor flex-grow overflow-hidden"> <div class="chat-editor flex-grow overflow-hidden">
@@ -59,6 +61,6 @@
class="center tooltip tooltip-left absolute right-4 h-10 w-10 min-w-10 rounded-full" class="center tooltip tooltip-left absolute right-4 h-10 w-10 min-w-10 rounded-full"
disabled={$uploading} disabled={$uploading}
onclick={submit}> onclick={submit}>
<Icon icon="plain" /> <Icon icon={Plane} />
</Button> </Button>
</form> </form>
@@ -2,6 +2,7 @@
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {displayProfileByPubkey} from "@welshman/app" import {displayProfileByPubkey} from "@welshman/app"
import {slide} from "@lib/transition" import {slide} from "@lib/transition"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import NoteContent from "@app/components/NoteContent.svelte" import NoteContent from "@app/components/NoteContent.svelte"
@@ -30,6 +31,6 @@
expandMode="disabled" /> expandMode="disabled" />
{/key} {/key}
<Button class="absolute right-2 top-2 cursor-pointer" onclick={clear}> <Button class="absolute right-2 top-2 cursor-pointer" onclick={clear}>
<Icon icon="close-circle" /> <Icon icon={CloseCircle} />
</Button> </Button>
</div> </div>
+2 -1
View File
@@ -5,6 +5,7 @@
import {isMobile} from "@lib/html" import {isMobile} from "@lib/html"
import TapTarget from "@lib/components/TapTarget.svelte" import TapTarget from "@lib/components/TapTarget.svelte"
import Avatar from "@lib/components/Avatar.svelte" import Avatar from "@lib/components/Avatar.svelte"
import Reply from "@assets/icons/reply-2.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Content from "@app/components/Content.svelte" import Content from "@app/components/Content.svelte"
@@ -103,7 +104,7 @@
<ChannelMessageEmojiButton {url} {event} /> <ChannelMessageEmojiButton {url} {event} />
{#if replyTo} {#if replyTo}
<Button class="btn join-item btn-xs" onclick={reply}> <Button class="btn join-item btn-xs" onclick={reply}>
<Icon icon="reply" size={4} /> <Icon icon={Reply} size={4} />
</Button> </Button>
{/if} {/if}
<ChannelMessageMenuButton {url} {event} /> <ChannelMessageMenuButton {url} {event} />
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import type {NativeEmoji} from "emoji-picker-element/shared" import type {NativeEmoji} from "emoji-picker-element/shared"
import EmojiButton from "@lib/components/EmojiButton.svelte" import EmojiButton from "@lib/components/EmojiButton.svelte"
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import {publishReaction, canEnforceNip70} from "@app/core/commands" import {publishReaction, canEnforceNip70} from "@app/core/commands"
@@ -18,5 +19,5 @@
</script> </script>
<EmojiButton {onEmoji} class="btn join-item btn-xs"> <EmojiButton {onEmoji} class="btn join-item btn-xs">
<Icon icon="smile-circle" size={4} /> <Icon icon={SmileCircle} size={4} />
</EmojiButton> </EmojiButton>
+6 -3
View File
@@ -6,6 +6,9 @@
import EventReport from "@app/components/EventReport.svelte" import EventReport from "@app/components/EventReport.svelte"
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte" import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import Code2 from "@assets/icons/code-2.svg?dataurl"
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
import Danger from "@assets/icons/danger.svg?dataurl"
const {url, event, onClick} = $props() const {url, event, onClick} = $props()
@@ -28,21 +31,21 @@
<ul class="menu whitespace-nowrap rounded-box bg-base-100 p-2 shadow-xl"> <ul class="menu whitespace-nowrap rounded-box bg-base-100 p-2 shadow-xl">
<li> <li>
<Button onclick={showInfo}> <Button onclick={showInfo}>
<Icon size={4} icon="code-2" /> <Icon size={4} icon={Code2} />
Message Details Message Details
</Button> </Button>
</li> </li>
{#if event.pubkey === $pubkey} {#if event.pubkey === $pubkey}
<li> <li>
<Button onclick={showDelete} class="text-error"> <Button onclick={showDelete} class="text-error">
<Icon size={4} icon="trash-bin-2" /> <Icon size={4} icon={TrashBin2} />
Delete Message Delete Message
</Button> </Button>
</li> </li>
{:else} {:else}
<li> <li>
<Button class="text-error" onclick={report}> <Button class="text-error" onclick={report}>
<Icon size={4} icon="danger" /> <Icon size={4} icon={Danger} />
Report Content Report Content
</Button> </Button>
</li> </li>
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import {type Instance} from "tippy.js" import {type Instance} from "tippy.js"
import {between} from "@welshman/lib" import {between} from "@welshman/lib"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Tippy from "@lib/components/Tippy.svelte" import Tippy from "@lib/components/Tippy.svelte"
@@ -29,7 +30,7 @@
<div class="flex"> <div class="flex">
<Button class="btn join-item btn-xs" onclick={open}> <Button class="btn join-item btn-xs" onclick={open}>
<Icon icon="menu-dots" size={4} /> <Icon icon={MenuDots} size={4} />
</Button> </Button>
<Tippy <Tippy
bind:popover bind:popover
@@ -11,6 +11,11 @@
import {ENABLE_ZAPS} from "@app/core/state" import {ENABLE_ZAPS} from "@app/core/state"
import {publishReaction, canEnforceNip70} from "@app/core/commands" import {publishReaction, canEnforceNip70} from "@app/core/commands"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
import Bolt from "@assets/icons/bolt.svg?dataurl"
import Reply from "@assets/icons/reply-2.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl"
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
type Props = { type Props = {
url: string url: string
@@ -46,26 +51,26 @@
<div class="col-2"> <div class="col-2">
<Button class="btn btn-primary w-full" onclick={showEmojiPicker}> <Button class="btn btn-primary w-full" onclick={showEmojiPicker}>
<Icon size={4} icon="smile-circle" /> <Icon size={4} icon={SmileCircle} />
Send Reaction Send Reaction
</Button> </Button>
{#if ENABLE_ZAPS} {#if ENABLE_ZAPS}
<ZapButton replaceState {url} {event} class="btn btn-secondary w-full"> <ZapButton replaceState {url} {event} class="btn btn-secondary w-full">
<Icon size={4} icon="bolt" /> <Icon size={4} icon={Bolt} />
Send Zap Send Zap
</ZapButton> </ZapButton>
{/if} {/if}
<Button class="btn btn-neutral w-full" onclick={sendReply}> <Button class="btn btn-neutral w-full" onclick={sendReply}>
<Icon size={4} icon="reply" /> <Icon size={4} icon={Reply} />
Send Reply Send Reply
</Button> </Button>
<Button class="btn btn-neutral" onclick={showInfo}> <Button class="btn btn-neutral" onclick={showInfo}>
<Icon size={4} icon="code-2" /> <Icon size={4} icon={Code2} />
Message Details Message Details
</Button> </Button>
{#if event.pubkey === $pubkey} {#if event.pubkey === $pubkey}
<Button class="btn btn-neutral text-error" onclick={showDelete}> <Button class="btn btn-neutral text-error" onclick={showDelete}>
<Icon size={4} icon="trash-bin-2" /> <Icon size={4} icon={TrashBin2} />
Delete Message Delete Message
</Button> </Button>
{/if} {/if}
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import Bolt from "@assets/icons/bolt.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ZapButton from "@app/components/ZapButton.svelte" import ZapButton from "@app/components/ZapButton.svelte"
@@ -6,5 +7,5 @@
</script> </script>
<ZapButton {url} {event} class="btn join-item btn-xs"> <ZapButton {url} {event} class="btn join-item btn-xs">
<Icon icon="bolt" size={4} /> <Icon icon={Bolt} size={4} />
</ZapButton> </ZapButton>
+6 -5
View File
@@ -31,6 +31,7 @@
inboxRelaySelectionsByPubkey, inboxRelaySelectionsByPubkey,
} from "@welshman/app" } from "@welshman/app"
import type {AbstractThunk} from "@welshman/app" import type {AbstractThunk} from "@welshman/app"
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
@@ -49,7 +50,7 @@
import ThunkToast from "@app/components/ThunkToast.svelte" import ThunkToast from "@app/components/ThunkToast.svelte"
import { import {
INDEXER_RELAYS, INDEXER_RELAYS,
userSettingValues, userSettingsValues,
deriveChat, deriveChat,
splitChatId, splitChatId,
PLATFORM_NAME, PLATFORM_NAME,
@@ -130,7 +131,7 @@
const template = templates[i] const template = templates[i]
thunks.push( thunks.push(
await sendWrapped({pubkeys, template, delay: $userSettingValues.send_delay + ms(i)}), await sendWrapped({pubkeys, template, delay: $userSettingsValues.send_delay + ms(i)}),
) )
} }
@@ -250,7 +251,7 @@
<div <div
class="row-2 badge badge-error badge-lg tooltip tooltip-left cursor-pointer" class="row-2 badge badge-error badge-lg tooltip tooltip-left cursor-pointer"
data-tip="{count} {label} not configured."> data-tip="{count} {label} not configured.">
<Icon icon="danger" /> <Icon icon={Danger} />
{count} {count}
</div> </div>
{/if} {/if}
@@ -264,7 +265,7 @@
<div class="py-12"> <div class="py-12">
<div class="card2 col-2 m-auto max-w-md items-center text-center"> <div class="card2 col-2 m-auto max-w-md items-center text-center">
<p class="row-2 text-lg text-error"> <p class="row-2 text-lg text-error">
<Icon icon="danger" /> <Icon icon={Danger} />
Your inbox is not configured. Your inbox is not configured.
</p> </p>
<p> <p>
@@ -277,7 +278,7 @@
<div class="py-12"> <div class="py-12">
<div class="card2 col-2 m-auto max-w-md items-center text-center"> <div class="card2 col-2 m-auto max-w-md items-center text-center">
<p class="row-2 text-lg text-error"> <p class="row-2 text-lg text-error">
<Icon icon="danger" /> <Icon icon={Danger} />
{missingInboxes.length} {missingInboxes.length}
{missingInboxes.length > 1 ? "inboxes are" : "inbox is"} not configured. {missingInboxes.length > 1 ? "inboxes are" : "inbox is"} not configured.
</p> </p>
+4 -2
View File
@@ -2,6 +2,8 @@
import {writable} from "svelte/store" import {writable} from "svelte/store"
import type {EventContent} from "@welshman/util" import type {EventContent} from "@welshman/util"
import {isMobile, preventDefault} from "@lib/html" import {isMobile, preventDefault} from "@lib/html"
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
import Plane from "@assets/icons/plane-2.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import EditorContent from "@app/editor/EditorContent.svelte" import EditorContent from "@app/editor/EditorContent.svelte"
@@ -54,7 +56,7 @@
{#if $uploading} {#if $uploading}
<span class="loading loading-spinner loading-xs"></span> <span class="loading loading-spinner loading-xs"></span>
{:else} {:else}
<Icon icon="gallery-send" /> <Icon icon={GallerySend} />
{/if} {/if}
</Button> </Button>
<div class="chat-editor flex-grow overflow-hidden"> <div class="chat-editor flex-grow overflow-hidden">
@@ -65,6 +67,6 @@
class="center tooltip tooltip-left absolute right-4 h-10 w-10 min-w-10 rounded-full" class="center tooltip tooltip-left absolute right-4 h-10 w-10 min-w-10 rounded-full"
disabled={$uploading} disabled={$uploading}
onclick={submit}> onclick={submit}>
<Icon icon="plain" /> <Icon icon={Plane} />
</Button> </Button>
</form> </form>
+2 -1
View File
@@ -2,6 +2,7 @@
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {displayProfileByPubkey} from "@welshman/app" import {displayProfileByPubkey} from "@welshman/app"
import {slide} from "@lib/transition" import {slide} from "@lib/transition"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import NoteContent from "@app/components/NoteContent.svelte" import NoteContent from "@app/components/NoteContent.svelte"
@@ -30,6 +31,6 @@
expandMode="disabled" /> expandMode="disabled" />
{/key} {/key}
<Button class="absolute right-2 top-2 cursor-pointer" onclick={clear}> <Button class="absolute right-2 top-2 cursor-pointer" onclick={clear}>
<Icon icon="close-circle" /> <Icon icon={CloseCircle} />
</Button> </Button>
</div> </div>
+7 -11
View File
@@ -1,14 +1,15 @@
<script lang="ts"> <script lang="ts">
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import {WRAP} from "@welshman/util"
import {repository} from "@welshman/app"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
import {canDecrypt, PLATFORM_NAME, ensureUnwrapped} from "@app/core/state" import {PLATFORM_NAME} from "@app/core/state"
import {enableGiftWraps} from "@app/core/commands"
import {clearModals} from "@app/util/modal" import {clearModals} from "@app/util/modal"
const {next} = $props() const {next} = $props()
@@ -18,12 +19,7 @@
let loading = $state(false) let loading = $state(false)
const enableChat = async () => { const enableChat = async () => {
canDecrypt.set(true) enableGiftWraps()
for (const event of repository.query([{kinds: [WRAP]}])) {
ensureUnwrapped(event)
}
clearModals() clearModals()
goto(nextUrl) goto(nextUrl)
} }
@@ -60,12 +56,12 @@
</p> </p>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary" disabled={loading}> <Button type="submit" class="btn btn-primary" disabled={loading}>
<Spinner {loading}>Enable Messages</Spinner> <Spinner {loading}>Enable Messages</Spinner>
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
</form> </form>
+60 -2
View File
@@ -1,9 +1,18 @@
<script lang="ts"> <script lang="ts">
import {waitForThunkCompletion} from "@welshman/app"
import ChatSquare from "@assets/icons/chat-square.svg?dataurl"
import Check from "@assets/icons/check.svg?dataurl"
import Bell from "@assets/icons/bell.svg?dataurl"
import BellOff from "@assets/icons/bell-off.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import ChatStart from "@app/components/ChatStart.svelte" import ChatStart from "@app/components/ChatStart.svelte"
import {setChecked} from "@app/util/notifications" import {setChecked} from "@app/util/notifications"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import {pushToast} from "@app/util/toast"
import {dmAlert, userInboxRelays} from "@app/core/state"
import {deleteAlert, createDmAlert} from "@app/core/commands"
const startChat = () => pushModal(ChatStart, {}, {replaceState: true}) const startChat = () => pushModal(ChatStart, {}, {replaceState: true})
@@ -11,15 +20,64 @@
setChecked("/chat/*") setChecked("/chat/*")
history.back() history.back()
} }
const enableAlerts = async () => {
if ($userInboxRelays.length === 0) {
return pushToast({
theme: "error",
message: "Please set up your messaging relays before enabling alerts.",
})
}
enablingAlert = true
try {
const {error} = await createDmAlert()
if (error) {
return pushToast({theme: "error", message: error})
}
} finally {
enablingAlert = false
}
}
const disableAlerts = async () => {
disablingAlert = true
try {
await waitForThunkCompletion(deleteAlert($dmAlert!))
} finally {
disablingAlert = false
}
}
let enablingAlert = $state(false)
let disablingAlert = $state(false)
</script> </script>
<div class="col-2"> <div class="col-2">
<Button class="btn btn-primary" onclick={startChat}> <Button class="btn btn-primary" onclick={startChat}>
<Icon size={4} icon="add-circle" /> <Icon size={5} icon={ChatSquare} />
Start chat Start chat
</Button> </Button>
<Button class="btn btn-neutral" onclick={markAsRead}> <Button class="btn btn-neutral" onclick={markAsRead}>
<Icon size={4} icon="check-circle" /> <Icon size={5} icon={Check} />
Mark all read Mark all read
</Button> </Button>
{#if (!enablingAlert && $dmAlert) || disablingAlert}
<Button class="btn btn-neutral" onclick={disableAlerts} disabled={disablingAlert}>
{#if !disablingAlert}
<Icon size={4} icon={BellOff} />
{/if}
<Spinner loading={disablingAlert}>Disable alerts</Spinner>
</Button>
{:else}
<Button class="btn btn-neutral" onclick={enableAlerts} disabled={enablingAlert}>
{#if !enablingAlert}
<Icon size={4} icon={Bell} />
{/if}
<Spinner loading={enablingAlert}>Enable alerts</Spinner>
</Button>
{/if}
</div> </div>
+2 -1
View File
@@ -4,6 +4,7 @@
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import {thunks, pubkey, deriveProfile, deriveProfileDisplay, sendWrapped} from "@welshman/app" import {thunks, pubkey, deriveProfile, deriveProfileDisplay, sendWrapped} from "@welshman/app"
import {isMobile} from "@lib/html" import {isMobile} from "@lib/html"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Tippy from "@lib/components/Tippy.svelte" import Tippy from "@lib/components/Tippy.svelte"
@@ -87,7 +88,7 @@
class="opacity-0 transition-all" class="opacity-0 transition-all"
class:group-hover:opacity-100={!isMobile} class:group-hover:opacity-100={!isMobile}
onclick={togglePopover}> onclick={togglePopover}>
<Icon icon="menu-dots" size={4} /> <Icon icon={MenuDots} size={4} />
</button> </button>
</Tippy> </Tippy>
{/if} {/if}
@@ -2,6 +2,7 @@
import type {NativeEmoji} from "emoji-picker-element/shared" import type {NativeEmoji} from "emoji-picker-element/shared"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {sendWrapped} from "@welshman/app" import {sendWrapped} from "@welshman/app"
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import EmojiButton from "@lib/components/EmojiButton.svelte" import EmojiButton from "@lib/components/EmojiButton.svelte"
import {makeReaction} from "@app/core/commands" import {makeReaction} from "@app/core/commands"
@@ -18,5 +19,5 @@
</script> </script>
<EmojiButton {onEmoji} class="btn join-item btn-xs"> <EmojiButton {onEmoji} class="btn join-item btn-xs">
<Icon icon="smile-circle" size={4} /> <Icon icon={SmileCircle} size={4} />
</EmojiButton> </EmojiButton>
+4 -2
View File
@@ -4,6 +4,8 @@
import ChatMessageEmojiButton from "@app/components/ChatMessageEmojiButton.svelte" import ChatMessageEmojiButton from "@app/components/ChatMessageEmojiButton.svelte"
import EventInfo from "@app/components/EventInfo.svelte" import EventInfo from "@app/components/EventInfo.svelte"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import Reply from "@assets/icons/reply-2.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl"
const {event, pubkeys, popover, replyTo} = $props() const {event, pubkeys, popover, replyTo} = $props()
@@ -19,10 +21,10 @@
<ChatMessageEmojiButton {event} {pubkeys} /> <ChatMessageEmojiButton {event} {pubkeys} />
{#if replyTo} {#if replyTo}
<Button class="btn join-item btn-xs" onclick={reply}> <Button class="btn join-item btn-xs" onclick={reply}>
<Icon size={4} icon="reply" /> <Icon size={4} icon={Reply} />
</Button> </Button>
{/if} {/if}
<Button class="btn join-item btn-xs" onclick={showInfo}> <Button class="btn join-item btn-xs" onclick={showInfo}>
<Icon size={4} icon="code-2" /> <Icon size={4} icon={Code2} />
</Button> </Button>
</div> </div>
@@ -9,6 +9,10 @@
import {makeReaction} from "@app/core/commands" import {makeReaction} from "@app/core/commands"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import {clip} from "@app/util/toast" import {clip} from "@app/util/toast"
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
import Reply from "@assets/icons/reply-2.svg?dataurl"
import Copy from "@assets/icons/copy.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl"
type Props = { type Props = {
pubkeys: string[] pubkeys: string[]
@@ -40,19 +44,19 @@
<div class="col-2"> <div class="col-2">
<Button class="btn btn-primary w-full" onclick={showEmojiPicker}> <Button class="btn btn-primary w-full" onclick={showEmojiPicker}>
<Icon size={4} icon="smile-circle" /> <Icon size={4} icon={SmileCircle} />
Send Reaction Send Reaction
</Button> </Button>
<Button class="btn btn-neutral w-full" onclick={sendReply}> <Button class="btn btn-neutral w-full" onclick={sendReply}>
<Icon size={4} icon="reply" /> <Icon size={4} icon={Reply} />
Send Reply Send Reply
</Button> </Button>
<Button class="btn btn-neutral w-full" onclick={copyText}> <Button class="btn btn-neutral w-full" onclick={copyText}>
<Icon size={4} icon="copy" /> <Icon size={4} icon={Copy} />
Copy Text Copy Text
</Button> </Button>
<Button class="btn btn-neutral" onclick={showInfo}> <Button class="btn btn-neutral" onclick={showInfo}>
<Icon size={4} icon="code-2" /> <Icon size={4} icon={Code2} />
Message Details Message Details
</Button> </Button>
</div> </div>
+4 -2
View File
@@ -9,6 +9,8 @@
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Field from "@lib/components/Field.svelte" import Field from "@lib/components/Field.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
@@ -67,12 +69,12 @@
</Field> </Field>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary" disabled={pubkeys.length === 0}> <Button type="submit" class="btn btn-primary" disabled={pubkeys.length === 0}>
Create Chat Create Chat
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
</form> </form>
+5 -4
View File
@@ -20,6 +20,7 @@
} from "@welshman/content" } from "@welshman/content"
import {preventDefault, stopPropagation} from "@lib/html" import {preventDefault, stopPropagation} from "@lib/html"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ContentToken from "@app/components/ContentToken.svelte" import ContentToken from "@app/components/ContentToken.svelte"
@@ -31,7 +32,7 @@
import ContentQuote from "@app/components/ContentQuote.svelte" import ContentQuote from "@app/components/ContentQuote.svelte"
import ContentTopic from "@app/components/ContentTopic.svelte" import ContentTopic from "@app/components/ContentTopic.svelte"
import ContentMention from "@app/components/ContentMention.svelte" import ContentMention from "@app/components/ContentMention.svelte"
import {entityLink, userSettingValues} from "@app/core/state" import {entityLink, userSettingsValues} from "@app/core/state"
interface Props { interface Props {
event: any event: any
@@ -68,7 +69,7 @@
if (!parsed || hideMediaAtDepth <= depth) return false if (!parsed || hideMediaAtDepth <= depth) return false
if (isLink(parsed) && $userSettingValues.show_media && isStartOrEnd(i)) { if (isLink(parsed) && $userSettingsValues.show_media && isStartOrEnd(i)) {
return true return true
} }
@@ -101,7 +102,7 @@
} }
let warning = $state( let warning = $state(
$userSettingValues.hide_sensitive && event.tags.find(nthEq(0, "content-warning"))?.[1], $userSettingsValues.hide_sensitive && event.tags.find(nthEq(0, "content-warning"))?.[1],
) )
const shortContent = $derived( const shortContent = $derived(
@@ -122,7 +123,7 @@
<div class="relative"> <div class="relative">
{#if warning} {#if warning}
<div class="card2 card2-sm bg-alt row-2"> <div class="card2 card2-sm bg-alt row-2">
<Icon icon="danger" /> <Icon icon={Danger} />
<p> <p>
This note has been flagged by the author as "{warning}".<br /> This note has been flagged by the author as "{warning}".<br />
<Button class="link" onclick={ignoreWarning}>Show anyway</Button> <Button class="link" onclick={ignoreWarning}>Show anyway</Button>
@@ -2,6 +2,7 @@
import {onMount, onDestroy} from "svelte" import {onMount, onDestroy} from "svelte"
import {displayUrl} from "@welshman/lib" import {displayUrl} from "@welshman/lib"
import {getTags, decryptFile, getTagValue, tagsFromIMeta} from "@welshman/util" import {getTags, decryptFile, getTagValue, tagsFromIMeta} from "@welshman/util"
import LinkRound from "@assets/icons/link-round.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import {imgproxy} from "@app/core/state" import {imgproxy} from "@app/core/state"
@@ -44,7 +45,7 @@
{#if hasError} {#if hasError}
<a href={url} class="link-content whitespace-nowrap"> <a href={url} class="link-content whitespace-nowrap">
<Icon icon="link-round" size={3} class="inline-block" /> <Icon icon={LinkRound} size={3} class="inline-block" />
{displayUrl(url)} {displayUrl(url)}
</a> </a>
{:else} {:else}
+3 -2
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import {displayUrl} from "@welshman/lib" import {displayUrl} from "@welshman/lib"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import LinkRound from "@assets/icons/link-round.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import ContentLinkDetail from "@app/components/ContentLinkDetail.svelte" import ContentLinkDetail from "@app/components/ContentLinkDetail.svelte"
@@ -16,12 +17,12 @@
{#if url.match(/\.(jpe?g|png|gif|webp)$/)} {#if url.match(/\.(jpe?g|png|gif|webp)$/)}
<!-- Use a real link so people can copy the href --> <!-- Use a real link so people can copy the href -->
<a href={url} class="link-content whitespace-nowrap" onclick={preventDefault(expand)}> <a href={url} class="link-content whitespace-nowrap" onclick={preventDefault(expand)}>
<Icon icon="link-round" size={3} class="inline-block" /> <Icon icon={LinkRound} size={3} class="inline-block" />
{displayUrl(url)} {displayUrl(url)}
</a> </a>
{:else} {:else}
<Link external href={url} class="link-content whitespace-nowrap"> <Link external href={url} class="link-content whitespace-nowrap">
<Icon icon="link-round" size={3} class="inline-block" /> <Icon icon={LinkRound} size={3} class="inline-block" />
{displayUrl(url)} {displayUrl(url)}
</Link> </Link>
{/if} {/if}
+2 -1
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import Bolt from "@assets/icons/bolt.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import {clip} from "@app/util/toast" import {clip} from "@app/util/toast"
@@ -9,6 +10,6 @@
</script> </script>
<Button onclick={copy} class="link-content"> <Button onclick={copy} class="link-content">
<Icon icon="bolt" size={3} class="inline-block translate-y-px" /> <Icon icon={Bolt} size={3} class="inline-block translate-y-px" />
{value.slice(0, 16)}... {value.slice(0, 16)}...
</Button> </Button>
+2 -1
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import {formatTimestamp} from "@welshman/lib" import {formatTimestamp} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Content from "@app/components/Content.svelte" import Content from "@app/components/Content.svelte"
@@ -40,7 +41,7 @@
</div> </div>
<div class="ml-13 flex items-center justify-between"> <div class="ml-13 flex items-center justify-between">
<div class="flex gap-1"> <div class="flex gap-1">
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
<span class="text-sm opacity-70"> <span class="text-sm opacity-70">
{events.length} {events.length}
{events.length === 1 ? "message" : "messages"} {events.length === 1 ? "message" : "messages"}
+6 -3
View File
@@ -3,6 +3,9 @@
import type {Instance} from "tippy.js" import type {Instance} from "tippy.js"
import type {NativeEmoji} from "emoji-picker-element/shared" import type {NativeEmoji} from "emoji-picker-element/shared"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import Bolt from "@assets/icons/bolt.svg?dataurl"
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Tippy from "@lib/components/Tippy.svelte" import Tippy from "@lib/components/Tippy.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -42,11 +45,11 @@
<Button class="join rounded-full"> <Button class="join rounded-full">
{#if ENABLE_ZAPS && !hideZap} {#if ENABLE_ZAPS && !hideZap}
<ZapButton {url} {event} class="btn join-item btn-neutral btn-xs"> <ZapButton {url} {event} class="btn join-item btn-neutral btn-xs">
<Icon icon="bolt" size={4} /> <Icon icon={Bolt} size={4} />
</ZapButton> </ZapButton>
{/if} {/if}
<EmojiButton {onEmoji} class="btn join-item btn-neutral btn-xs"> <EmojiButton {onEmoji} class="btn join-item btn-neutral btn-xs">
<Icon icon="smile-circle" size={4} /> <Icon icon={SmileCircle} size={4} />
</EmojiButton> </EmojiButton>
<Tippy <Tippy
bind:popover bind:popover
@@ -54,7 +57,7 @@
props={{url, noun, event, customActions, onClick: hidePopover}} props={{url, noun, event, customActions, onClick: hidePopover}}
params={{trigger: "manual", interactive: true}}> params={{trigger: "manual", interactive: true}}>
<Button class="btn join-item btn-neutral btn-xs" onclick={showPopover}> <Button class="btn join-item btn-neutral btn-xs" onclick={showPopover}>
<Icon icon="menu-dots" size={4} /> <Icon icon={MenuDots} size={4} />
</Button> </Button>
</Tippy> </Tippy>
</Button> </Button>
+2 -1
View File
@@ -7,6 +7,7 @@
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {repository} from "@welshman/app" import {repository} from "@welshman/app"
import {notifications} from "@app/util/notifications" import {notifications} from "@app/util/notifications"
import Reply from "@assets/icons/reply-2.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
const {url, path, event}: {url: string; path: string; event: TrustedEvent} = $props() const {url, path, event}: {url: string; path: string; event: TrustedEvent} = $props()
@@ -21,7 +22,7 @@
</script> </script>
<div class="flex-inline btn btn-neutral btn-xs gap-1 rounded-full"> <div class="flex-inline btn btn-neutral btn-xs gap-1 rounded-full">
<Icon icon="reply" /> <Icon icon={Reply} />
<span>{$replies.length} {$replies.length === 1 ? "reply" : "replies"}</span> <span>{$replies.length} {$replies.length === 1 ? "reply" : "replies"}</span>
</div> </div>
<div class="btn btn-neutral btn-xs relative hidden rounded-full sm:flex"> <div class="btn btn-neutral btn-xs relative hidden rounded-full sm:flex">
+8 -5
View File
@@ -4,6 +4,9 @@
import {LOCALE, secondsToDate} from "@welshman/lib" import {LOCALE, secondsToDate} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {displayRelayUrl} from "@welshman/util" import {displayRelayUrl} from "@welshman/util"
import FileText from "@assets/icons/file-text.svg?dataurl"
import Copy from "@assets/icons/copy.svg?dataurl"
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import FieldInline from "@lib/components/FieldInline.svelte" import FieldInline from "@lib/components/FieldInline.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -56,10 +59,10 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="file" /> <Icon icon={FileText} />
<input type="text" class="ellipsize min-w-0 grow" value={nevent1} /> <input type="text" class="ellipsize min-w-0 grow" value={nevent1} />
<Button onclick={copyLink} class="flex items-center"> <Button onclick={copyLink} class="flex items-center">
<Icon icon="copy" /> <Icon icon={Copy} />
</Button> </Button>
</label> </label>
{/snippet} {/snippet}
@@ -70,10 +73,10 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="user-circle" /> <Icon icon={UserCircle} />
<input type="text" class="ellipsize min-w-0 grow" value={npub1} /> <input type="text" class="ellipsize min-w-0 grow" value={npub1} />
<Button onclick={copyPubkey} class="flex items-center"> <Button onclick={copyPubkey} class="flex items-center">
<Icon icon="copy" /> <Icon icon={Copy} />
</Button> </Button>
</label> </label>
{/snippet} {/snippet}
@@ -98,7 +101,7 @@
<pre class="card2 card2-sm bg-alt overflow-auto text-xs"><code>{json}</code></pre> <pre class="card2 card2-sm bg-alt overflow-auto text-xs"><code>{json}</code></pre>
<p class="absolute right-2 top-2 flex flex-grow items-center justify-between"> <p class="absolute right-2 top-2 flex flex-grow items-center justify-between">
<Button onclick={copyJson} class="btn btn-neutral btn-sm flex items-center"> <Button onclick={copyJson} class="btn btn-neutral btn-sm flex items-center">
<Icon icon="copy" /> Copy <Icon icon={Copy} /> Copy
</Button> </Button>
</p> </p>
</div> </div>
+8 -4
View File
@@ -11,6 +11,10 @@
import EventShare from "@app/components/EventShare.svelte" import EventShare from "@app/components/EventShare.svelte"
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte" import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import ShareCircle from "@assets/icons/share-circle.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl"
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
import Danger from "@assets/icons/danger.svg?dataurl"
type Props = { type Props = {
url: string url: string
@@ -43,14 +47,14 @@
{#if isRoot} {#if isRoot}
<li> <li>
<Button onclick={share}> <Button onclick={share}>
<Icon size={4} icon="share-circle" /> <Icon size={4} icon={ShareCircle} />
Share to Chat Share to Chat
</Button> </Button>
</li> </li>
{/if} {/if}
<li> <li>
<Button onclick={showInfo}> <Button onclick={showInfo}>
<Icon size={4} icon="code-2" /> <Icon size={4} icon={Code2} />
{noun} Details {noun} Details
</Button> </Button>
</li> </li>
@@ -58,14 +62,14 @@
{#if event.pubkey === $pubkey} {#if event.pubkey === $pubkey}
<li> <li>
<Button onclick={showDelete} class="text-error"> <Button onclick={showDelete} class="text-error">
<Icon size={4} icon="trash-bin-2" /> <Icon size={4} icon={TrashBin2} />
Delete {noun} Delete {noun}
</Button> </Button>
</li> </li>
{:else} {:else}
<li> <li>
<Button class="text-error" onclick={report}> <Button class="text-error" onclick={report}>
<Icon size={4} icon="danger" /> <Icon size={4} icon={Danger} />
Report Content Report Content
</Button> </Button>
</li> </li>
+2 -1
View File
@@ -3,6 +3,7 @@
import {writable} from "svelte/store" import {writable} from "svelte/store"
import {isMobile, preventDefault} from "@lib/html" import {isMobile, preventDefault} from "@lib/html"
import {fly} from "@lib/transition" import {fly} from "@lib/transition"
import Paperclip from "@assets/icons/paperclip-2.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
@@ -81,7 +82,7 @@
{#if $uploading} {#if $uploading}
<span class="loading loading-spinner loading-xs"></span> <span class="loading loading-spinner loading-xs"></span>
{:else} {:else}
<Icon icon="paperclip" size={3} /> <Icon icon={Paperclip} size={3} />
{/if} {/if}
</Button> </Button>
</div> </div>
+4 -2
View File
@@ -3,6 +3,8 @@
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Field from "@lib/components/Field.svelte" import Field from "@lib/components/Field.svelte"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
@@ -78,12 +80,12 @@
</Field> </Field>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary" disabled={loading}> <Button type="submit" class="btn btn-primary" disabled={loading}>
<Spinner {loading}>Send Report</Spinner> <Spinner {loading}>Send Report</Spinner>
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
</form> </form>
+4 -2
View File
@@ -2,6 +2,8 @@
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
@@ -50,12 +52,12 @@
</div> </div>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary" disabled={!selection}> <Button type="submit" class="btn btn-primary" disabled={!selection}>
Share {noun} Share {noun}
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
</form> </form>
+6 -3
View File
@@ -3,6 +3,9 @@
import {makeEvent, ZAP_GOAL} from "@welshman/util" import {makeEvent, ZAP_GOAL} from "@welshman/util"
import {publishThunk} from "@welshman/app" import {publishThunk} from "@welshman/app"
import {isMobile, preventDefault} from "@lib/html" import {isMobile, preventDefault} from "@lib/html"
import Paperclip from "@assets/icons/paperclip-2.svg?dataurl"
import Bolt from "@assets/icons/bolt.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Field from "@lib/components/Field.svelte" import Field from "@lib/components/Field.svelte"
import FieldInline from "@lib/components/FieldInline.svelte" import FieldInline from "@lib/components/FieldInline.svelte"
@@ -114,7 +117,7 @@
{#if $uploading} {#if $uploading}
<span class="loading loading-spinner loading-xs"></span> <span class="loading loading-spinner loading-xs"></span>
{:else} {:else}
<Icon icon="paperclip" size={3} /> <Icon icon={Paperclip} size={3} />
{/if} {/if}
</Button> </Button>
</div> </div>
@@ -126,7 +129,7 @@
{#snippet input()} {#snippet input()}
<div class="flex flex-grow justify-end"> <div class="flex flex-grow justify-end">
<label class="input input-bordered flex items-center gap-2"> <label class="input input-bordered flex items-center gap-2">
<Icon icon="bolt" /> <Icon icon={Bolt} />
<input bind:value={amount} type="number" class="w-28" /> <input bind:value={amount} type="number" class="w-28" />
<p class="opacity-50">sats</p> <p class="opacity-50">sats</p>
</label> </label>
@@ -144,7 +147,7 @@
</div> </div>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary">Create Goal</Button> <Button type="submit" class="btn btn-primary">Create Goal</Button>
+2 -1
View File
@@ -4,6 +4,7 @@
import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util" import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util"
import {deriveEventsMapped} from "@welshman/store" import {deriveEventsMapped} from "@welshman/store"
import {repository, getValidZap} from "@welshman/app" import {repository, getValidZap} from "@welshman/app"
import Bolt from "@assets/icons/bolt.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ZapButton from "@app/components/ZapButton.svelte" import ZapButton from "@app/components/ZapButton.svelte"
@@ -43,7 +44,7 @@
</div> </div>
<progress class="progress progress-primary" value={zapAmount} max={goalAmount}></progress> <progress class="progress progress-primary" value={zapAmount} max={goalAmount}></progress>
<ZapButton {url} {event} class="btn btn-primary lg:m-auto lg:px-20"> <ZapButton {url} {event} class="btn btn-primary lg:m-auto lg:px-20">
<Icon icon="bolt" /> <Icon icon={Bolt} />
Contribute to this goal Contribute to this goal
</ZapButton> </ZapButton>
</div> </div>
+6 -4
View File
@@ -1,6 +1,8 @@
<script lang="ts"> <script lang="ts">
import {session} from "@welshman/app" import {session} from "@welshman/app"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
@@ -27,8 +29,8 @@
</p> </p>
<p> <p>
On <Link external href="https://nostr.com/">Nostr</Link>, <strong>you</strong> control your own On <Link external href="https://nostr.com/">Nostr</Link>, <strong>you</strong> control your own
identity and social data, through the magic of crytography. The basic idea is that you have a identity and social data, through the magic of cryptography. The basic idea is that you have a
<strong>public key</strong>, which acts as your user id, and a <strong>public key</strong>, which acts as your user ID, and a
<strong>private key</strong> which allows you to prove your identity. <strong>private key</strong> which allows you to prove your identity.
</p> </p>
{#if $session?.email} {#if $session?.email}
@@ -39,11 +41,11 @@
<p>If you'd like to switch to self-custody, please click below to get started.</p> <p>If you'd like to switch to self-custody, please click below to get started.</p>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button class="btn btn-primary" onclick={startEject}> <Button class="btn btn-primary" onclick={startEject}>
<Icon icon="check-circle" /> <Icon icon={CheckCircle} />
I want to hold my own keys I want to hold my own keys
</Button> </Button>
</ModalFooter> </ModalFooter>
+32
View File
@@ -0,0 +1,32 @@
<script lang="ts">
import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
const back = () => history.back()
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
<div>What are digital signatures?</div>
{/snippet}
</ModalHeader>
<p>
Most online services ask their users to trust them that they're being honest, and they usually
are. However, traditional social media platforms have the ability to <strong
>create forged content</strong> that can appear to be genuinely authored, but which are actually
counterfeit.
</p>
<p>
On <Link external href="https://nostr.com/">Nostr</Link>, all your content is authenticated
using <strong>digital signatures</strong>, which cryptographically tie a particular person to a
given post or message.
</p>
<p>
The result is that you don't normally have to trust service providers not to tamper with the
information flowing through the network — instead, your client software can prove that a given
piece of data is authentic.
</p>
<Button class="btn btn-primary" onclick={back}>Got it</Button>
</div>
+2 -1
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import {deriveZapperForPubkey} from "@welshman/app" import {deriveZapperForPubkey} from "@welshman/app"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
@@ -29,7 +30,7 @@
</p> </p>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
</ModalFooter> </ModalFooter>
@@ -1,7 +1,11 @@
<script lang="ts"> <script lang="ts">
import {randomId} from "@welshman/lib" import {randomId, call} from "@welshman/lib"
import {preventDefault, stopPropagation} from "@lib/html" import {preventDefault, stopPropagation, compressFile} from "@lib/html"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import {uploadFile} from "@app/core/commands"
interface Props { interface Props {
file?: File | undefined file?: File | undefined
@@ -24,14 +28,14 @@
active = false active = false
} }
const onDrop = (e: any) => { const onDrop = async (e: any) => {
active = false active = false
file = e.dataTransfer.files[0] file = await compressFile(e.dataTransfer.files[0])
} }
const onChange = (e: any) => { const onChange = async (e: any) => {
file = e.target.files[0] file = await compressFile(e.target.files[0])
} }
const onClear = () => { const onClear = () => {
@@ -44,20 +48,29 @@
let initialUrl = $state(url) let initialUrl = $state(url)
$effect(() => { $effect(() => {
if (file) { call(async () => {
const reader = new FileReader() if (file) {
const {result} = await uploadFile(file)
reader.addEventListener( if (result?.url) {
"load", url = result.url
() => { } else {
url = reader.result as string const reader = new FileReader()
},
false, reader.addEventListener(
) "load",
reader.readAsDataURL(file) () => {
} else { url = reader.result as string
url = initialUrl },
} false,
)
reader.readAsDataURL(file)
}
} else {
url = initialUrl
}
})
}) })
</script> </script>
@@ -84,14 +97,14 @@
tabindex="-1" tabindex="-1"
onmousedown={stopPropagation(onClear)} onmousedown={stopPropagation(onClear)}
ontouchstart={stopPropagation(onClear)}> ontouchstart={stopPropagation(onClear)}>
<Icon icon="close-circle" class="scale-150 !bg-base-300" /> <Icon icon={CloseCircle} class="scale-150 !bg-base-300" />
</span> </span>
{:else} {:else}
<Icon icon="add-circle" class="scale-150 !bg-base-300" /> <Icon icon={AddCircle} class="scale-150 !bg-base-300" />
{/if} {/if}
</div> </div>
{#if !url} {#if !url}
<Icon icon="gallery-send" size={7} /> <Icon icon={GallerySend} size={7} />
{/if} {/if}
</label> </label>
</form> </form>
+6 -4
View File
@@ -1,4 +1,6 @@
<script lang="ts"> <script lang="ts">
import Login from "@assets/icons/login-3.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
@@ -21,9 +23,9 @@
<p class="text-center">The chat app built for self-hosted communities.</p> <p class="text-center">The chat app built for self-hosted communities.</p>
</div> </div>
<Button onclick={logIn}> <Button onclick={logIn}>
<CardButton class="!btn-primary"> <CardButton class="btn-primary">
{#snippet icon()} {#snippet icon()}
<div><Icon icon="login-2" size={7} /></div> <div><Icon icon={Login} size={7} /></div>
{/snippet} {/snippet}
{#snippet title()} {#snippet title()}
<div>Log in</div> <div>Log in</div>
@@ -33,10 +35,10 @@
{/snippet} {/snippet}
</CardButton> </CardButton>
</Button> </Button>
<Button onclick={signUp}> <Button onclick={signUp} class="dark:btn-neutral">
<CardButton> <CardButton>
{#snippet icon()} {#snippet icon()}
<div><Icon icon="add-circle" size={7} /></div> <div><Icon icon={AddCircle} size={7} /></div>
{/snippet} {/snippet}
{#snippet title()} {#snippet title()}
<div>Create an account</div> <div>Create an account</div>
+9 -5
View File
@@ -3,6 +3,10 @@
import {Capacitor} from "@capacitor/core" import {Capacitor} from "@capacitor/core"
import {getNip07, getNip55, Nip55Signer} from "@welshman/signer" import {getNip07, getNip55, Nip55Signer} from "@welshman/signer"
import {addSession, type Session, makeNip07Session, makeNip55Session} from "@welshman/app" import {addSession, type Session, makeNip07Session, makeNip55Session} from "@welshman/app"
import Widget from "@assets/icons/widget-2.svg?dataurl"
import Key from "@assets/icons/key-minimalistic.svg?dataurl"
import Cpu from "@assets/icons/cpu-bolt.svg?dataurl"
import Compass from "@assets/icons/compass-big.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -96,7 +100,7 @@
{#if loading === "nip07"} {#if loading === "nip07"}
<span class="loading loading-spinner mr-3"></span> <span class="loading loading-spinner mr-3"></span>
{:else} {:else}
<Icon icon="widget" /> <Icon icon={Widget} />
{/if} {/if}
Log in with Extension Log in with Extension
</Button> </Button>
@@ -116,7 +120,7 @@
{#if loading === "password"} {#if loading === "password"}
<span class="loading loading-spinner mr-3"></span> <span class="loading loading-spinner mr-3"></span>
{:else} {:else}
<Icon icon="key" /> <Icon icon={Key} />
{/if} {/if}
Log in with Password Log in with Password
</Button> </Button>
@@ -125,7 +129,7 @@
onclick={loginWithBunker} onclick={loginWithBunker}
{disabled} {disabled}
class="btn {hasSigner || BURROW_URL ? 'btn-neutral' : 'btn-primary'}"> class="btn {hasSigner || BURROW_URL ? 'btn-neutral' : 'btn-primary'}">
<Icon icon="cpu" /> <Icon icon={Cpu} />
Log in with Remote Signer Log in with Remote Signer
</Button> </Button>
{#if BURROW_URL && hasSigner} {#if BURROW_URL && hasSigner}
@@ -133,7 +137,7 @@
{#if loading === "password"} {#if loading === "password"}
<span class="loading loading-spinner mr-3"></span> <span class="loading loading-spinner mr-3"></span>
{:else} {:else}
<Icon icon="key" /> <Icon icon={Key} />
{/if} {/if}
Log in with Password Log in with Password
</Button> </Button>
@@ -144,7 +148,7 @@
{disabled} {disabled}
href="https://nostrapps.com#signers" href="https://nostrapps.com#signers"
class="btn {hasSigner || BURROW_URL ? '' : 'btn-neutral'}"> class="btn {hasSigner || BURROW_URL ? '' : 'btn-neutral'}">
<Icon icon="compass" /> <Icon icon={Compass} />
Browse Signer Apps Browse Signer Apps
</Link> </Link>
{/if} {/if}
+4 -2
View File
@@ -6,6 +6,8 @@
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
@@ -133,13 +135,13 @@
{/if} {/if}
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back} disabled={$loading}> <Button class="btn btn-link" onclick={back} disabled={$loading}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
{#if mode === "bunker"} {#if mode === "bunker"}
<Button type="submit" class="btn btn-primary" disabled={$loading || !$bunker}> <Button type="submit" class="btn btn-primary" disabled={$loading || !$bunker}>
<Spinner loading={$loading}>Next</Spinner> <Spinner loading={$loading}>Next</Spinner>
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
{/if} {/if}
</ModalFooter> </ModalFooter>
+8 -4
View File
@@ -8,6 +8,10 @@
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import FieldInline from "@lib/components/FieldInline.svelte" import FieldInline from "@lib/components/FieldInline.svelte"
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
import Key from "@assets/icons/key-minimalistic.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
@@ -121,7 +125,7 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="user-rounded" /> <Icon icon={UserRounded} />
<input bind:value={email} /> <input bind:value={email} />
</label> </label>
{/snippet} {/snippet}
@@ -132,7 +136,7 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="key" /> <Icon icon={Key} />
<input bind:value={password} type="password" /> <input bind:value={password} type="password" />
</label> </label>
{/snippet} {/snippet}
@@ -144,12 +148,12 @@
</p> </p>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back} disabled={loading}> <Button class="btn btn-link" onclick={back} disabled={loading}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary" disabled={loading || !email || !password}> <Button type="submit" class="btn btn-primary" disabled={loading || !email || !password}>
<Spinner {loading}>Next</Spinner> <Spinner {loading}>Next</Spinner>
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
</form> </form>
+2 -1
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
@@ -32,7 +33,7 @@
<p class="text-center">Your local database will be cleared.</p> <p class="text-center">Your local database will be cleared.</p>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary" disabled={loading}> <Button type="submit" class="btn btn-primary" disabled={loading}>
+44 -11
View File
@@ -1,4 +1,11 @@
<script lang="ts"> <script lang="ts">
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
import Server from "@assets/icons/server.svg?dataurl"
import Settings from "@assets/icons/settings-minimalistic.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl"
import Exit from "@assets/icons/logout-3.svg?dataurl"
import Bell from "@assets/icons/bell.svg?dataurl"
import Wallet from "@assets/icons/wallet.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -12,9 +19,9 @@
<div class="column menu gap-2"> <div class="column menu gap-2">
<Link replaceState href="/settings/profile"> <Link replaceState href="/settings/profile">
<CardButton> <CardButton class="dark:btn-neutral">
{#snippet icon()} {#snippet icon()}
<div><Icon icon="user-rounded" size={7} /></div> <div><Icon icon={UserRounded} size={7} /></div>
{/snippet} {/snippet}
{#snippet title()} {#snippet title()}
<div>Profile</div> <div>Profile</div>
@@ -24,10 +31,36 @@
{/snippet} {/snippet}
</CardButton> </CardButton>
</Link> </Link>
<Link replaceState href="/settings/relays"> <Link replaceState href="/settings/alerts">
<CardButton> <CardButton class="dark:btn-neutral">
{#snippet icon()} {#snippet icon()}
<div><Icon icon="server" size={7} /></div> <div><Icon icon={Bell} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Alerts</div>
{/snippet}
{#snippet info()}
<div>Set up email digests and push notifications</div>
{/snippet}
</CardButton>
</Link>
<Link replaceState href="/settings/wallet">
<CardButton class="dark:btn-neutral">
{#snippet icon()}
<div><Icon icon={Wallet} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Wallet</div>
{/snippet}
{#snippet info()}
<div>Connect a bitcoin wallet for sending social tips</div>
{/snippet}
</CardButton>
</Link>
<Link replaceState href="/settings/relays">
<CardButton class="dark:btn-neutral">
{#snippet icon()}
<div><Icon icon={Server} size={7} /></div>
{/snippet} {/snippet}
{#snippet title()} {#snippet title()}
<div>Relays</div> <div>Relays</div>
@@ -37,10 +70,10 @@
{/snippet} {/snippet}
</CardButton> </CardButton>
</Link> </Link>
<Link replaceState href="/settings"> <Link replaceState href="/settings/content">
<CardButton> <CardButton class="dark:btn-neutral">
{#snippet icon()} {#snippet icon()}
<div><Icon icon="settings" size={7} /></div> <div><Icon icon={Settings} size={7} /></div>
{/snippet} {/snippet}
{#snippet title()} {#snippet title()}
<div>Settings</div> <div>Settings</div>
@@ -51,9 +84,9 @@
</CardButton> </CardButton>
</Link> </Link>
<Link replaceState href="/settings/about"> <Link replaceState href="/settings/about">
<CardButton> <CardButton class="dark:btn-neutral">
{#snippet icon()} {#snippet icon()}
<div><Icon icon="code-2" size={7} /></div> <div><Icon icon={Code2} size={7} /></div>
{/snippet} {/snippet}
{#snippet title()} {#snippet title()}
<div>About</div> <div>About</div>
@@ -64,6 +97,6 @@
</CardButton> </CardButton>
</Link> </Link>
<Button onclick={logout} class="btn btn-neutral"> <Button onclick={logout} class="btn btn-neutral">
<Icon icon="exit" /> Log Out <Icon icon={Exit} /> Log Out
</Button> </Button>
</div> </div>
+24 -12
View File
@@ -3,6 +3,18 @@
import {displayRelayUrl, getTagValue} from "@welshman/util" import {displayRelayUrl, getTagValue} from "@welshman/util"
import {deriveRelay} from "@welshman/app" import {deriveRelay} from "@welshman/app"
import {fly} from "@lib/transition" import {fly} from "@lib/transition"
import AltArrowDown from "@assets/icons/alt-arrow-down.svg?dataurl"
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
import LinkRound from "@assets/icons/link-round.svg?dataurl"
import Exit from "@assets/icons/logout-3.svg?dataurl"
import Login from "@assets/icons/login-3.svg?dataurl"
import HomeSmile from "@assets/icons/home-smile.svg?dataurl"
import StarFallMinimalistic from "@assets/icons/star-fall-minimalistic-2.svg?dataurl"
import NotesMinimalistic from "@assets/icons/notes-minimalistic.svg?dataurl"
import CalendarMinimalistic from "@assets/icons/calendar-minimalistic.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import ChatRound from "@assets/icons/chat-round.svg?dataurl"
import Bell from "@assets/icons/bell.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Popover from "@lib/components/Popover.svelte" import Popover from "@lib/components/Popover.svelte"
@@ -92,7 +104,7 @@
<strong class="ellipsize flex items-center gap-3"> <strong class="ellipsize flex items-center gap-3">
{displayRelayUrl(url)} {displayRelayUrl(url)}
</strong> </strong>
<Icon icon="alt-arrow-down" /> <Icon icon={AltArrowDown} />
</SecondaryNavItem> </SecondaryNavItem>
{#if showMenu} {#if showMenu}
<Popover hideOnClick onClose={toggleMenu}> <Popover hideOnClick onClose={toggleMenu}>
@@ -101,25 +113,25 @@
class="menu absolute z-popover mt-2 w-full gap-1 rounded-box bg-base-100 p-2 shadow-xl"> class="menu absolute z-popover mt-2 w-full gap-1 rounded-box bg-base-100 p-2 shadow-xl">
<li> <li>
<Button onclick={showMembers}> <Button onclick={showMembers}>
<Icon icon="user-rounded" /> <Icon icon={UserRounded} />
View Members ({members.length}) View Members ({members.length})
</Button> </Button>
</li> </li>
<li> <li>
<Button onclick={createInvite}> <Button onclick={createInvite}>
<Icon icon="link-round" /> <Icon icon={LinkRound} />
Create Invite Create Invite
</Button> </Button>
</li> </li>
<li> <li>
{#if $userRoomsByUrl.has(url)} {#if $userRoomsByUrl.has(url)}
<Button onclick={leaveSpace} class="text-error"> <Button onclick={leaveSpace} class="text-error">
<Icon icon="exit" /> <Icon icon={Exit} />
Leave Space Leave Space
</Button> </Button>
{:else} {:else}
<Button onclick={joinSpace} class="bg-primary text-primary-content"> <Button onclick={joinSpace} class="bg-primary text-primary-content">
<Icon icon="login-2" /> <Icon icon={Login} />
Join Space Join Space
</Button> </Button>
{/if} {/if}
@@ -130,27 +142,27 @@
</div> </div>
<div class="flex max-h-[calc(100vh-150px)] 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)}> <SecondaryNavItem {replaceState} href={makeSpacePath(url)}>
<Icon icon="home-smile" /> Home <Icon icon={HomeSmile} /> Home
</SecondaryNavItem> </SecondaryNavItem>
{#if ENABLE_ZAPS} {#if ENABLE_ZAPS}
<SecondaryNavItem <SecondaryNavItem
{replaceState} {replaceState}
href={goalsPath} href={goalsPath}
notification={$notifications.has(goalsPath)}> notification={$notifications.has(goalsPath)}>
<Icon icon="star-fall-minimalistic-2" /> Goals <Icon icon={StarFallMinimalistic} /> Goals
</SecondaryNavItem> </SecondaryNavItem>
{/if} {/if}
<SecondaryNavItem <SecondaryNavItem
{replaceState} {replaceState}
href={threadsPath} href={threadsPath}
notification={$notifications.has(threadsPath)}> notification={$notifications.has(threadsPath)}>
<Icon icon="notes-minimalistic" /> Threads <Icon icon={NotesMinimalistic} /> Threads
</SecondaryNavItem> </SecondaryNavItem>
<SecondaryNavItem <SecondaryNavItem
{replaceState} {replaceState}
href={calendarPath} href={calendarPath}
notification={$notifications.has(calendarPath)}> notification={$notifications.has(calendarPath)}>
<Icon icon="calendar-minimalistic" /> Calendar <Icon icon={CalendarMinimalistic} /> Calendar
</SecondaryNavItem> </SecondaryNavItem>
{#if hasNip29($relay)} {#if hasNip29($relay)}
{#if $userRooms.length > 0} {#if $userRooms.length > 0}
@@ -174,7 +186,7 @@
<MenuSpaceRoomItem {replaceState} {url} {room} /> <MenuSpaceRoomItem {replaceState} {url} {room} />
{/each} {/each}
<SecondaryNavItem {replaceState} onclick={addRoom}> <SecondaryNavItem {replaceState} onclick={addRoom}>
<Icon icon="add-circle" /> <Icon icon={AddCircle} />
Create room Create room
</SecondaryNavItem> </SecondaryNavItem>
{:else} {:else}
@@ -182,14 +194,14 @@
{replaceState} {replaceState}
href={chatPath} href={chatPath}
notification={$notifications.has(chatPath)}> notification={$notifications.has(chatPath)}>
<Icon icon="chat-round" /> Chat <Icon icon={ChatRound} /> Chat
</SecondaryNavItem> </SecondaryNavItem>
{/if} {/if}
</div> </div>
</SecondaryNavSection> </SecondaryNavSection>
<div class="p-4"> <div class="p-4">
<button class="btn btn-neutral btn-sm w-full" onclick={manageAlerts}> <button class="btn btn-neutral btn-sm w-full" onclick={manageAlerts}>
<Icon icon="bell" /> <Icon icon={Bell} />
Manage Alerts Manage Alerts
</button> </button>
</div> </div>
+2 -1
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import MenuSpace from "@app/components/MenuSpace.svelte" import MenuSpace from "@app/components/MenuSpace.svelte"
@@ -14,7 +15,7 @@
</script> </script>
<Button onclick={openMenu} class="btn btn-neutral btn-sm relative md:hidden"> <Button onclick={openMenu} class="btn btn-neutral btn-sm relative md:hidden">
<Icon icon="menu-dots" /> <Icon icon={MenuDots} />
{#if $notifications.has(path)} {#if $notifications.has(path)}
<div class="absolute right-0 top-0 -mr-1 -mt-1 h-2 w-2 rounded-full bg-primary"></div> <div class="absolute right-0 top-0 -mr-1 -mt-1 h-2 w-2 rounded-full bg-primary"></div>
{/if} {/if}
+4 -2
View File
@@ -1,4 +1,6 @@
<script lang="ts"> <script lang="ts">
import Lock from "@assets/icons/lock-keyhole.svg?dataurl"
import Hashtag from "@assets/icons/hashtag.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte" import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
import ChannelName from "@app/components/ChannelName.svelte" import ChannelName from "@app/components/ChannelName.svelte"
@@ -24,9 +26,9 @@
{replaceState} {replaceState}
notification={notify ? $notifications.has(path) : false}> notification={notify ? $notifications.has(path) : false}>
{#if $channel?.closed || $channel?.private} {#if $channel?.closed || $channel?.private}
<Icon icon="lock" size={4} /> <Icon icon={Lock} size={4} />
{:else} {:else}
<Icon icon="hashtag" /> <Icon icon={Hashtag} />
{/if} {/if}
<div class="min-w-0 overflow-hidden text-ellipsis"> <div class="min-w-0 overflow-hidden text-ellipsis">
<ChannelName {url} {room} /> <ChannelName {url} {room} />
+3 -2
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import Login from "@assets/icons/login-3.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Divider from "@lib/components/Divider.svelte" import Divider from "@lib/components/Divider.svelte"
@@ -22,9 +23,9 @@
<Divider /> <Divider />
{/if} {/if}
<Button onclick={addSpace}> <Button onclick={addSpace}>
<CardButton> <CardButton class="dark:btn-neutral">
{#snippet icon()} {#snippet icon()}
<div><Icon icon="login-2" size={7} /></div> <div><Icon icon={Login} size={7} /></div>
{/snippet} {/snippet}
{#snippet title()} {#snippet title()}
<div>Add a space</div> <div>Add a space</div>
+1 -1
View File
@@ -13,7 +13,7 @@
</script> </script>
<Link replaceState href={path}> <Link replaceState href={path}>
<CardButton> <CardButton class="dark:btn-neutral">
{#snippet icon()} {#snippet icon()}
<div><SpaceAvatar {url} /></div> <div><SpaceAvatar {url} /></div>
{/snippet} {/snippet}
+10 -12
View File
@@ -1,8 +1,7 @@
<script lang="ts"> <script lang="ts">
import {page} from "$app/stores"
import Drawer from "@lib/components/Drawer.svelte" import Drawer from "@lib/components/Drawer.svelte"
import Dialog from "@lib/components/Dialog.svelte" import Dialog from "@lib/components/Dialog.svelte"
import {modals, clearModals} from "@app/util/modal" import {modal, clearModals} from "@app/util/modal"
const onKeyDown = (e: any) => { const onKeyDown = (e: any) => {
if (e.code === "Escape" && e.target === document.body) { if (e.code === "Escape" && e.target === document.body) {
@@ -10,22 +9,21 @@
} }
} }
const hash = $derived($page.url.hash.slice(1)) const m = $derived($modal)
const modal = $derived($modals[hash])
</script> </script>
<svelte:window onkeydown={onKeyDown} /> <svelte:window onkeydown={onKeyDown} />
{#if modal?.options?.drawer} {#if m?.options?.drawer}
<Drawer onClose={clearModals} {...modal.options}> <Drawer onClose={clearModals} {...m.options}>
{#key modal.id} {#key m.id}
<modal.component {...modal.props} /> <m.component {...m.props} />
{/key} {/key}
</Drawer> </Drawer>
{:else if modal} {:else if m}
<Dialog onClose={clearModals} {...modal.options}> <Dialog onClose={clearModals} {...m.options}>
{#key modal.id} {#key m.id}
<modal.component {...modal.props} /> <m.component {...m.props} />
{/key} {/key}
</Dialog> </Dialog>
{/if} {/if}
+2 -1
View File
@@ -8,6 +8,7 @@
import {Router} from "@welshman/router" import {Router} from "@welshman/router"
import {userMutes} from "@welshman/app" import {userMutes} from "@welshman/app"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Profile from "@app/components/Profile.svelte" import Profile from "@app/components/Profile.svelte"
@@ -44,7 +45,7 @@
{#if muted} {#if muted}
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="row-2 relative"> <div class="row-2 relative">
<Icon icon="danger" class="mt-1" /> <Icon icon={Danger} class="mt-1" />
<p>You have muted this person.</p> <p>You have muted this person.</p>
</div> </div>
<Button class="link ml-8" onclick={ignoreMute}>Show anyway</Button> <Button class="link ml-8" onclick={ignoreMute}>Show anyway</Button>
+2 -1
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import type {NativeEmoji} from "emoji-picker-element/shared" import type {NativeEmoji} from "emoji-picker-element/shared"
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import EmojiButton from "@lib/components/EmojiButton.svelte" import EmojiButton from "@lib/components/EmojiButton.svelte"
import NoteContent from "@app/components/NoteContent.svelte" import NoteContent from "@app/components/NoteContent.svelte"
@@ -32,7 +33,7 @@
<div class="flex w-full justify-between gap-2"> <div class="flex w-full justify-between gap-2">
<ReactionSummary {url} {event} {deleteReaction} {createReaction} reactionClass="tooltip-right"> <ReactionSummary {url} {event} {deleteReaction} {createReaction} reactionClass="tooltip-right">
<EmojiButton {onEmoji} class="btn btn-neutral btn-xs h-[26px] rounded-box"> <EmojiButton {onEmoji} class="btn btn-neutral btn-xs h-[26px] rounded-box">
<Icon icon="smile-circle" size={4} /> <Icon icon={SmileCircle} size={4} />
</EmojiButton> </EmojiButton>
</ReactionSummary> </ReactionSummary>
</div> </div>
+4 -2
View File
@@ -3,6 +3,8 @@
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
import Key from "@assets/icons/key-minimalistic.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import FieldInline from "@lib/components/FieldInline.svelte" import FieldInline from "@lib/components/FieldInline.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
@@ -49,7 +51,7 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="user-rounded" /> <Icon icon={UserRounded} />
<input readonly value={email} class="grow" /> <input readonly value={email} class="grow" />
</label> </label>
{/snippet} {/snippet}
@@ -60,7 +62,7 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="key" /> <Icon icon={Key} />
<input bind:value={password} class="grow" type="password" /> <input bind:value={password} class="grow" type="password" />
</label> </label>
{/snippet} {/snippet}
@@ -1,6 +1,8 @@
<script lang="ts"> <script lang="ts">
import {postJson, sleep} from "@welshman/lib" import {postJson, sleep} from "@welshman/lib"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import FieldInline from "@lib/components/FieldInline.svelte" import FieldInline from "@lib/components/FieldInline.svelte"
@@ -55,7 +57,7 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="user-rounded" /> <Icon icon={UserRounded} />
<input bind:value={email} class="grow" /> <input bind:value={email} class="grow" />
</label> </label>
{/snippet} {/snippet}
@@ -65,7 +67,7 @@
</FieldInline> </FieldInline>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary" disabled={loading}> <Button type="submit" class="btn btn-primary" disabled={loading}>
+3 -2
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Profile from "@app/components/Profile.svelte" import Profile from "@app/components/Profile.svelte"
@@ -21,14 +22,14 @@
<div class="flex justify-between"> <div class="flex justify-between">
<Profile {pubkey} {url} /> <Profile {pubkey} {url} />
<Button onclick={openProfile} class="btn btn-primary hidden sm:flex"> <Button onclick={openProfile} class="btn btn-primary hidden sm:flex">
<Icon icon="user-circle" /> <Icon icon={UserCircle} />
View Profile View Profile
</Button> </Button>
</div> </div>
<ProfileInfo {pubkey} {url} /> <ProfileInfo {pubkey} {url} />
<ProfileBadges {pubkey} {url} /> <ProfileBadges {pubkey} {url} />
<Button onclick={openProfile} class="btn btn-primary sm:hidden"> <Button onclick={openProfile} class="btn btn-primary sm:hidden">
<Icon icon="user-circle" /> <Icon icon={UserCircle} />
View Profile View Profile
</Button> </Button>
</div> </div>
+19 -9
View File
@@ -17,6 +17,13 @@
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import {makeSpacePath} from "@app/util/routes" import {makeSpacePath} from "@app/util/routes"
import {notifications} from "@app/util/notifications" import {notifications} from "@app/util/notifications"
import Widget from "@assets/icons/widget.svg?dataurl"
import AddSquare from "@assets/icons/add-square.svg?dataurl"
import Letter from "@assets/icons/letter.svg?dataurl"
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import HomeSmile from "@assets/icons/home-smile.svg?dataurl"
import SettingsMinimalistic from "@assets/icons/settings-minimalistic.svg?dataurl"
import Settings from "@assets/icons/settings.svg?dataurl"
type Props = { type Props = {
children?: Snippet children?: Snippet
@@ -55,7 +62,7 @@
<div <div
class="ml-sai mt-sai mb-sai relative z-nav hidden w-14 flex-shrink-0 bg-base-200 pt-4 md:block"> class="ml-sai mt-sai mb-sai relative z-nav hidden w-14 flex-shrink-0 bg-base-200 pt-4 md:block">
<div class="flex h-full flex-col justify-between"> <div class="flex h-full flex-col" class:justify-between={PLATFORM_RELAYS.length === 0}>
<div> <div>
{#each PLATFORM_RELAYS as url (url)} {#each PLATFORM_RELAYS as url (url)}
<PrimaryNavItemSpace {url} /> <PrimaryNavItemSpace {url} />
@@ -73,14 +80,17 @@
class="tooltip-right" class="tooltip-right"
onclick={showOtherSpacesMenu} onclick={showOtherSpacesMenu}
notification={otherSpaceNotifications}> notification={otherSpaceNotifications}>
<Avatar icon="widget" class="!h-10 !w-10" /> <Avatar icon={Widget} class="!h-10 !w-10" />
</PrimaryNavItem> </PrimaryNavItem>
{/if} {/if}
<PrimaryNavItem title="Add Space" onclick={addSpace} class="tooltip-right"> <PrimaryNavItem title="Add Space" onclick={addSpace} class="tooltip-right">
<Avatar icon="add-square" class="!h-10 !w-10" /> <Avatar icon={AddSquare} class="!h-10 !w-10" />
</PrimaryNavItem> </PrimaryNavItem>
{/each} {/each}
</div> </div>
{#if PLATFORM_RELAYS.length > 0}
<Divider />
{/if}
<div> <div>
<PrimaryNavItem <PrimaryNavItem
title="Settings" title="Settings"
@@ -94,10 +104,10 @@
onclick={openChat} onclick={openChat}
class="tooltip-right" class="tooltip-right"
notification={$notifications.has("/chat")}> notification={$notifications.has("/chat")}>
<Avatar icon="letter" class="!h-10 !w-10" /> <Avatar icon={Letter} class="!h-10 !w-10" />
</PrimaryNavItem> </PrimaryNavItem>
<PrimaryNavItem title="Search" href="/people" class="tooltip-right"> <PrimaryNavItem title="Search" href="/people" class="tooltip-right">
<Avatar icon="magnifer" class="!h-10 !w-10" /> <Avatar icon={Magnifier} class="!h-10 !w-10" />
</PrimaryNavItem> </PrimaryNavItem>
</div> </div>
</div> </div>
@@ -112,25 +122,25 @@
<div class="content-padding-x content-sizing flex justify-between px-2"> <div class="content-padding-x content-sizing flex justify-between px-2">
<div class="flex gap-2 sm:gap-8"> <div class="flex gap-2 sm:gap-8">
<PrimaryNavItem title="Home" href="/home"> <PrimaryNavItem title="Home" href="/home">
<Avatar icon="home-smile" class="!h-10 !w-10" /> <Avatar icon={HomeSmile} class="!h-10 !w-10" />
</PrimaryNavItem> </PrimaryNavItem>
<PrimaryNavItem <PrimaryNavItem
title="Messages" title="Messages"
onclick={openChat} onclick={openChat}
notification={$notifications.has("/chat")}> notification={$notifications.has("/chat")}>
<Avatar icon="letter" class="!h-10 !w-10" /> <Avatar icon={Letter} class="!h-10 !w-10" />
</PrimaryNavItem> </PrimaryNavItem>
{#if PLATFORM_RELAYS.length !== 1} {#if PLATFORM_RELAYS.length !== 1}
<PrimaryNavItem <PrimaryNavItem
title="Spaces" title="Spaces"
onclick={showSpacesMenu} onclick={showSpacesMenu}
notification={anySpaceNotifications}> notification={anySpaceNotifications}>
<Avatar icon="settings-minimalistic" class="!h-10 !w-10" /> <Avatar icon={SettingsMinimalistic} class="!h-10 !w-10" />
</PrimaryNavItem> </PrimaryNavItem>
{/if} {/if}
</div> </div>
<PrimaryNavItem title="Settings" onclick={showSettingsMenu}> <PrimaryNavItem title="Settings" onclick={showSettingsMenu}>
<Avatar icon="settings" src={$userProfile?.picture} class="!h-10 !w-10" /> <Avatar icon={Settings} src={$userProfile?.picture} class="!h-10 !w-10" />
</PrimaryNavItem> </PrimaryNavItem>
</div> </div>
</div> </div>
+2 -1
View File
@@ -15,6 +15,7 @@
import ProfileDetail from "@app/components/ProfileDetail.svelte" import ProfileDetail from "@app/components/ProfileDetail.svelte"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import {clip} from "@app/util/toast" import {clip} from "@app/util/toast"
import Copy from "@assets/icons/copy.svg?dataurl"
type Props = { type Props = {
pubkey: string pubkey: string
@@ -55,7 +56,7 @@
<div class="flex items-center gap-1 overflow-hidden text-ellipsis text-xs opacity-60"> <div class="flex items-center gap-1 overflow-hidden text-ellipsis text-xs opacity-60">
{displayPubkey(pubkey)} {displayPubkey(pubkey)}
<Button onclick={copyPubkey} class="pt-1"> <Button onclick={copyPubkey} class="pt-1">
<Icon size={3} icon="copy" /> <Icon size={3} icon={Copy} />
</Button> </Button>
</div> </div>
{/if} {/if}
+2 -1
View File
@@ -2,6 +2,7 @@
import Avatar from "@lib/components/Avatar.svelte" import Avatar from "@lib/components/Avatar.svelte"
import {removeNil} from "@welshman/lib" import {removeNil} from "@welshman/lib"
import {deriveProfile} from "@welshman/app" import {deriveProfile} from "@welshman/app"
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
type Props = { type Props = {
pubkey: string pubkey: string
@@ -13,4 +14,4 @@
const profile = deriveProfile(pubkey, removeNil([url])) const profile = deriveProfile(pubkey, removeNil([url]))
</script> </script>
<Avatar src={$profile?.picture} icon="user-circle" {...props} /> <Avatar src={$profile?.picture} icon={UserCircle} {...props} />
+13 -6
View File
@@ -7,10 +7,11 @@
DELETE, DELETE,
isReplaceable, isReplaceable,
getAddress, getAddress,
getRelaysFromList,
} from "@welshman/util" } from "@welshman/util"
import {pubkey, userRelaySelections, publishThunk, repository} from "@welshman/app" import {pubkey, publishThunk, repository} from "@welshman/app"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
@@ -18,7 +19,13 @@
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
import {logout} from "@app/core/commands" import {logout} from "@app/core/commands"
import {INDEXER_RELAYS, PLATFORM_NAME, userMembership, getMembershipUrls} from "@app/core/state" import {
INDEXER_RELAYS,
PLATFORM_NAME,
userMembership,
getMembershipUrls,
userWriteRelays,
} from "@app/core/state"
let progress: number | undefined = $state(undefined) let progress: number | undefined = $state(undefined)
let confirmText = $state("") let confirmText = $state("")
@@ -41,7 +48,7 @@
const denominator = chunks.length + 2 const denominator = chunks.length + 2
const relays = uniq([ const relays = uniq([
...INDEXER_RELAYS, ...INDEXER_RELAYS,
...getRelaysFromList($userRelaySelections), ...$userWriteRelays,
...getMembershipUrls($userMembership), ...getMembershipUrls($userMembership),
]) ])
@@ -136,12 +143,12 @@
{/if} {/if}
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-error" disabled={showProgress || !confirmOk}> <Button type="submit" class="btn btn-error" disabled={showProgress || !confirmOk}>
<Spinner loading={progress !== undefined}>Confirm</Spinner> <Spinner loading={progress !== undefined}>Confirm</Spinner>
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
</form> </form>
+4 -2
View File
@@ -1,5 +1,7 @@
<script lang="ts"> <script lang="ts">
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Letter from "@assets/icons/letter-opened.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Avatar from "@lib/components/Avatar.svelte" import Avatar from "@lib/components/Avatar.svelte"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
@@ -33,7 +35,7 @@
<ProfileBadges {pubkey} {url} /> <ProfileBadges {pubkey} {url} />
<ModalFooter> <ModalFooter>
<Button onclick={back} class="hidden md:btn md:btn-link"> <Button onclick={back} class="hidden md:btn md:btn-link">
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<div class="flex gap-2"> <div class="flex gap-2">
@@ -42,7 +44,7 @@
Open in Coracle Open in Coracle
</Link> </Link>
<Button onclick={openChat} class="btn btn-primary"> <Button onclick={openChat} class="btn btn-primary">
<Icon icon="letter" /> <Icon icon={Letter} />
Open Chat Open Chat
</Button> </Button>
</div> </div>
+6 -4
View File
@@ -2,11 +2,13 @@
import type {Snippet} from "svelte" import type {Snippet} from "svelte"
import type {Profile} from "@welshman/util" import type {Profile} from "@welshman/util"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
import MapPoint from "@assets/icons/map-point.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Field from "@lib/components/Field.svelte" import Field from "@lib/components/Field.svelte"
import FieldInline from "@lib/components/FieldInline.svelte" import FieldInline from "@lib/components/FieldInline.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import InputProfilePicture from "@lib/components/InputProfilePicture.svelte" import InputProfilePicture from "@app/components/InputProfilePicture.svelte"
import InfoHandle from "@app/components/InfoHandle.svelte" import InfoHandle from "@app/components/InfoHandle.svelte"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
@@ -53,11 +55,11 @@
{/if} {/if}
<Field> <Field>
{#snippet label()} {#snippet label()}
<p>Username</p> <p>Nickname</p>
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="user-circle" /> <Icon icon={UserCircle} />
<input bind:value={values.profile.name} class="grow" type="text" /> <input bind:value={values.profile.name} class="grow" type="text" />
</label> </label>
{/snippet} {/snippet}
@@ -86,7 +88,7 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="map-point" /> <Icon icon={MapPoint} />
<input bind:value={values.profile.nip05} class="grow" type="text" /> <input bind:value={values.profile.nip05} class="grow" type="text" />
</label> </label>
{/snippet} {/snippet}
+7 -4
View File
@@ -3,6 +3,9 @@
import {session} from "@welshman/app" import {session} from "@welshman/app"
import {slideAndFade} from "@lib/transition" import {slideAndFade} from "@lib/transition"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import Key from "@assets/icons/key-minimalistic.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
@@ -81,7 +84,7 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="key" /> <Icon icon={Key} />
<input type="password" disabled={loading} bind:value={password} class="grow" /> <input type="password" disabled={loading} bind:value={password} class="grow" />
</label> </label>
{/snippet} {/snippet}
@@ -90,17 +93,17 @@
{/if} {/if}
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" disabled={loading || success} onclick={back}> <Button class="btn btn-link" disabled={loading || success} onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
{#if success} {#if success}
<Button class="btn btn-primary" disabled={loading} onclick={reload}> <Button class="btn btn-primary" disabled={loading} onclick={reload}>
<Icon icon="check-circle" /> <Icon icon={CheckCircle} />
<Spinner {loading}>Refresh the page</Spinner> <Spinner {loading}>Refresh the page</Spinner>
</Button> </Button>
{:else} {:else}
<Button class="btn btn-error" disabled={loading} onclick={confirm}> <Button class="btn btn-error" disabled={loading} onclick={confirm}>
<Icon icon="check-circle" /> <Icon icon={CheckCircle} />
<Spinner {loading}>I understand, send me my private key</Spinner> <Spinner {loading}>I understand, send me my private key</Spinner>
</Button> </Button>
{/if} {/if}
+4 -2
View File
@@ -5,6 +5,8 @@
import {append, remove, uniq} from "@welshman/lib" import {append, remove, uniq} from "@welshman/lib"
import {profileSearch} from "@welshman/app" import {profileSearch} from "@welshman/app"
import Suggestions from "@lib/components/Suggestions.svelte" import Suggestions from "@lib/components/Suggestions.svelte"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Tippy from "@lib/components/Tippy.svelte" import Tippy from "@lib/components/Tippy.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -61,7 +63,7 @@
{@const onClick = () => pushModal(ProfileDetail, {pubkey})} {@const onClick = () => pushModal(ProfileDetail, {pubkey})}
<div class="flex-inline badge badge-neutral mr-1 gap-1"> <div class="flex-inline badge badge-neutral mr-1 gap-1">
<Button class="flex items-center" onclick={() => removePubkey(pubkey)}> <Button class="flex items-center" onclick={() => removePubkey(pubkey)}>
<Icon icon="close-circle" size={4} class="-ml-1 mt-px" /> <Icon icon={CloseCircle} size={4} class="-ml-1 mt-px" />
</Button> </Button>
<Button onclick={onClick}> <Button onclick={onClick}>
<ProfileName {pubkey} /> <ProfileName {pubkey} />
@@ -70,7 +72,7 @@
{/each} {/each}
</div> </div>
<label class="input input-bordered flex w-full items-center gap-2" bind:this={input}> <label class="input input-bordered flex w-full items-center gap-2" bind:this={input}>
<Icon icon="magnifer" /> <Icon icon={Magnifier} />
<!-- svelte-ignore a11y_autofocus --> <!-- svelte-ignore a11y_autofocus -->
<input <input
{autofocus} {autofocus}
+4 -2
View File
@@ -1,4 +1,6 @@
<script lang="ts"> <script lang="ts">
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -33,7 +35,7 @@
</div> </div>
<Link class="btn btn-primary" href={makeSpacePath(url)}> <Link class="btn btn-primary" href={makeSpacePath(url)}>
Go to space Go to space
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Link> </Link>
</div> </div>
{:else} {:else}
@@ -43,7 +45,7 @@
{/each} {/each}
<ModalFooter> <ModalFooter>
<Button onclick={back} class="hidden md:btn md:btn-link"> <Button onclick={back} class="hidden md:btn md:btn-link">
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
</ModalFooter> </ModalFooter>
+4 -2
View File
@@ -1,5 +1,7 @@
<script lang="ts"> <script lang="ts">
import {parse, isEmoji, renderAsHtml} from "@welshman/content" import {parse, isEmoji, renderAsHtml} from "@welshman/content"
import Heart from "@assets/icons/heart-angle.svg?dataurl"
import ThumbsDown from "@assets/icons/dislike.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ContentEmoji from "@app/components/ContentEmoji.svelte" import ContentEmoji from "@app/components/ContentEmoji.svelte"
@@ -7,9 +9,9 @@
</script> </script>
{#if event.content === "+" || event.content === ""} {#if event.content === "+" || event.content === ""}
<Icon icon="heart" /> <Icon icon={Heart} />
{:else if event.content === "-"} {:else if event.content === "-"}
<Icon icon="thumbs-down" /> <Icon icon={ThumbsDown} />
{:else} {:else}
{#each parse(event) as parsed} {#each parse(event) as parsed}
{#if isEmoji(parsed)} {#if isEmoji(parsed)}
+2 -1
View File
@@ -18,6 +18,7 @@
import {load} from "@welshman/net" import {load} from "@welshman/net"
import {pubkey, repository, getValidZap, displayProfileByPubkey} from "@welshman/app" import {pubkey, repository, getValidZap, displayProfileByPubkey} from "@welshman/app"
import {isMobile, preventDefault, stopPropagation} from "@lib/html" import {isMobile, preventDefault, stopPropagation} from "@lib/html"
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Reaction from "@app/components/Reaction.svelte" import Reaction from "@app/components/Reaction.svelte"
import EventReportDetails from "@app/components/EventReportDetails.svelte" import EventReportDetails from "@app/components/EventReportDetails.svelte"
@@ -120,7 +121,7 @@
class="btn btn-error btn-xs tooltip-right flex items-center gap-1 rounded-full" class="btn btn-error btn-xs tooltip-right flex items-center gap-1 rounded-full"
class:tooltip={!noTooltip && !isMobile} class:tooltip={!noTooltip && !isMobile}
onclick={stopPropagation(preventDefault(onReportClick))}> onclick={stopPropagation(preventDefault(onReportClick))}>
<Icon icon="danger" /> <Icon icon={Danger} />
<span>{$reports.length}</span> <span>{$reports.length}</span>
</button> </button>
{/if} {/if}
+5 -3
View File
@@ -5,6 +5,8 @@
import {isShareableRelayUrl, normalizeRelayUrl} from "@welshman/util" import {isShareableRelayUrl, normalizeRelayUrl} from "@welshman/util"
import {relaySearch} from "@welshman/app" import {relaySearch} from "@welshman/app"
import {createScroller} from "@lib/html" import {createScroller} from "@lib/html"
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import RelayItem from "@app/components/RelayItem.svelte" import RelayItem from "@app/components/RelayItem.svelte"
@@ -38,14 +40,14 @@
</script> </script>
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="magnifer" /> <Icon icon={Magnifier} />
<input bind:value={term} class="grow" type="text" placeholder="Search for relays..." /> <input bind:value={term} class="grow" type="text" placeholder="Search for relays..." />
</label> </label>
<div class="column -m-6 mt-0 h-[50vh] gap-2 overflow-auto p-6 pt-2" bind:this={element}> <div class="column -m-6 mt-0 h-[50vh] gap-2 overflow-auto p-6 pt-2" bind:this={element}>
{#if customUrl && isShareableRelayUrl(customUrl) && !$relays.includes(normalizeRelayUrl(customUrl))} {#if customUrl && isShareableRelayUrl(customUrl) && !$relays.includes(normalizeRelayUrl(customUrl))}
<RelayItem url={term}> <RelayItem url={term}>
<Button class="btn btn-outline btn-sm flex items-center" onclick={() => addRelay(customUrl)}> <Button class="btn btn-outline btn-sm flex items-center" onclick={() => addRelay(customUrl)}>
<Icon icon="add-circle" /> <Icon icon={AddCircle} />
Add Relay Add Relay
</Button> </Button>
</RelayItem> </RelayItem>
@@ -56,7 +58,7 @@
.slice(0, limit) as url (url)} .slice(0, limit) as url (url)}
<RelayItem {url}> <RelayItem {url}>
<Button class="btn btn-outline btn-sm flex items-center" onclick={() => addRelay(url)}> <Button class="btn btn-outline btn-sm flex items-center" onclick={() => addRelay(url)}>
<Icon icon="add-circle" /> <Icon icon={AddCircle} />
Add Relay Add Relay
</Button> </Button>
</RelayItem> </RelayItem>
+2 -1
View File
@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import Server from "@assets/icons/server.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import {displayUrl} from "@welshman/lib" import {displayUrl} from "@welshman/lib"
@@ -15,7 +16,7 @@
<div class="card2 card2-sm bg-alt column gap-2"> <div class="card2 card2-sm bg-alt column gap-2">
<div class="flex items-center justify-between gap-4"> <div class="flex items-center justify-between gap-4">
<div class="ellipsize flex items-center gap-2"> <div class="ellipsize flex items-center gap-2">
<Icon icon="server" /> <Icon icon={Server} />
<p class="ellipsize">{displayRelayUrl(url)}</p> <p class="ellipsize">{displayRelayUrl(url)}</p>
</div> </div>
{@render children?.()} {@render children?.()}
+1 -1
View File
@@ -3,7 +3,7 @@
const {url} = $props() const {url} = $props()
const display = deriveRelayDisplay(url) const display = $derived(deriveRelayDisplay(url))
</script> </script>
{$display} {$display}
+57
View File
@@ -0,0 +1,57 @@
<script lang="ts">
import {gt} from "@welshman/lib"
import {deriveRelay} from "@welshman/app"
import Ghost from "@assets/icons/ghost-smile.svg?dataurl"
import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import RelayName from "@app/components/RelayName.svelte"
import RelayDescription from "@app/components/RelayDescription.svelte"
import ProfileCircles from "@app/components/ProfileCircles.svelte"
import {membersByUrl, userRoomsByUrl} from "@app/core/state"
type Props = {
url: string
}
const {url}: Props = $props()
const relay = deriveRelay(url)
</script>
<div class="col-4 text-left">
<div class="col-2">
<div class="relative flex gap-4">
<div class="relative">
<div class="avatar relative">
<div
class="center !flex h-12 w-12 min-w-12 rounded-full border-2 border-solid border-base-300 bg-base-300">
{#if $relay?.profile?.icon}
<img alt="" src={$relay.profile.icon} />
{:else}
<Icon icon={Ghost} size={5} />
{/if}
</div>
</div>
{#if $userRoomsByUrl.has(url)}
<div
class="tooltip absolute -right-1 -top-1 h-5 w-5 rounded-full bg-primary"
data-tip="You are already a member of this space.">
<Icon icon={CheckCircle} class="scale-110" />
</div>
{/if}
</div>
<div>
<h2 class="ellipsize whitespace-nowrap text-xl">
<RelayName {url} />
</h2>
<p class="text-sm opacity-75">{url}</p>
</div>
</div>
<RelayDescription {url} />
</div>
{#if gt($membersByUrl.get(url)?.size, 0)}
<div class="row-2 card2 card2-sm bg-alt">
Members:
<ProfileCircles pubkeys={Array.from($membersByUrl.get(url) || [])} />
</div>
{/if}
</div>
+12 -8
View File
@@ -2,11 +2,15 @@
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import {uniqBy, nth} from "@welshman/lib" import {uniqBy, nth} from "@welshman/lib"
import {displayRelayUrl, makeRoomMeta} from "@welshman/util" import {displayRelayUrl, makeRoomMeta} from "@welshman/util"
import {deriveRelay, getThunkError, createRoom, editRoom, joinRoom} from "@welshman/app" import {deriveRelay, waitForThunkError, createRoom, editRoom, joinRoom} from "@welshman/app"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Field from "@lib/components/Field.svelte" import Field from "@lib/components/Field.svelte"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Hashtag from "@assets/icons/hashtag.svg?dataurl"
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
@@ -24,19 +28,19 @@
const tryCreate = async () => { const tryCreate = async () => {
room.tags = uniqBy(nth(0), [...room.tags, ["name", name]]) room.tags = uniqBy(nth(0), [...room.tags, ["name", name]])
const createMessage = await getThunkError(createRoom(url, room)) const createMessage = await waitForThunkError(createRoom(url, room))
if (createMessage && !createMessage.match(/^duplicate:|already a member/)) { if (createMessage && !createMessage.match(/^duplicate:|already a member/)) {
return pushToast({theme: "error", message: createMessage}) return pushToast({theme: "error", message: createMessage})
} }
const editMessage = await getThunkError(editRoom(url, room)) const editMessage = await waitForThunkError(editRoom(url, room))
if (editMessage) { if (editMessage) {
return pushToast({theme: "error", message: editMessage}) return pushToast({theme: "error", message: editMessage})
} }
const joinMessage = await getThunkError(joinRoom(url, room)) const joinMessage = await waitForThunkError(joinRoom(url, room))
if (joinMessage && !joinMessage.includes("already")) { if (joinMessage && !joinMessage.includes("already")) {
return pushToast({theme: "error", message: joinMessage}) return pushToast({theme: "error", message: joinMessage})
@@ -79,25 +83,25 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="hashtag" /> <Icon icon={Hashtag} />
<input bind:value={name} class="grow" type="text" /> <input bind:value={name} class="grow" type="text" />
</label> </label>
{/snippet} {/snippet}
</Field> </Field>
{:else} {:else}
<p class="bg-alt card2 row-2"> <p class="bg-alt card2 row-2">
<Icon icon="danger" /> <Icon icon={Danger} />
This relay does not support creating rooms. This relay does not support creating rooms.
</p> </p>
{/if} {/if}
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary" disabled={!name || loading || !hasNip29($relay)}> <Button type="submit" class="btn btn-primary" disabled={!name || loading || !hasNip29($relay)}>
<Spinner {loading}>Create Room</Spinner> <Spinner {loading}>Create Room</Spinner>
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
</form> </form>
+7 -4
View File
@@ -1,6 +1,9 @@
<script lang="ts"> <script lang="ts">
import {postJson} from "@welshman/lib" import {postJson} from "@welshman/lib"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
import Key from "@assets/icons/key-minimalistic.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import FieldInline from "@lib/components/FieldInline.svelte" import FieldInline from "@lib/components/FieldInline.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -59,7 +62,7 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="user-rounded" /> <Icon icon={UserRounded} />
<input bind:value={email} /> <input bind:value={email} />
</label> </label>
{/snippet} {/snippet}
@@ -70,14 +73,14 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="key" /> <Icon icon={Key} />
<input bind:value={password} type="password" /> <input bind:value={password} type="password" />
</label> </label>
{/snippet} {/snippet}
</FieldInline> </FieldInline>
<Button type="submit" class="btn btn-primary" disabled={loading || !email || !password}> <Button type="submit" class="btn btn-primary" disabled={loading || !email || !password}>
<Spinner {loading}>Sign Up</Spinner> <Spinner {loading}>Sign Up</Spinner>
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
<p class="text-sm opacity-75"> <p class="text-sm opacity-75">
Note that your email and password will only work to log in to {PLATFORM_NAME}. To use your key Note that your email and password will only work to log in to {PLATFORM_NAME}. To use your key
@@ -87,7 +90,7 @@
<Divider>Or</Divider> <Divider>Or</Divider>
{/if} {/if}
<Button onclick={next} class="btn {email || password ? 'btn-neutral' : 'btn-primary'}"> <Button onclick={next} class="btn {email || password ? 'btn-neutral' : 'btn-primary'}">
<Icon icon="key" /> <Icon icon={Key} />
Generate a key Generate a key
</Button> </Button>
<div class="text-sm"> <div class="text-sm">
+7 -2
View File
@@ -3,10 +3,13 @@
import {createProfile, PROFILE, makeEvent} from "@welshman/util" import {createProfile, PROFILE, makeEvent} from "@welshman/util"
import {publishThunk, loginWithNip01} from "@welshman/app" import {publishThunk, loginWithNip01} from "@welshman/app"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import HomeSmile from "@assets/icons/home-smile.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
import {clearModals} from "@app/util/modal"
import {PROTECTED} from "@app/core/state" import {PROTECTED} from "@app/core/state"
type Props = { type Props = {
@@ -31,6 +34,8 @@
// Don't publish anywhere yet, wait until they join a space // Don't publish anywhere yet, wait until they join a space
publishThunk({event, relays: []}) publishThunk({event, relays: []})
clearModals()
} }
</script> </script>
@@ -50,11 +55,11 @@
</p> </p>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button class="btn btn-primary" type="submit"> <Button class="btn btn-primary" type="submit">
<Icon icon="home-smile" /> <Icon icon={HomeSmile} />
Go to Dashboard Go to Dashboard
</Button> </Button>
</ModalFooter> </ModalFooter>
+55 -6
View File
@@ -5,6 +5,10 @@
import {makeSecret} from "@welshman/signer" import {makeSecret} from "@welshman/signer"
import type {Profile} from "@welshman/util" import type {Profile} from "@welshman/util"
import {preventDefault, downloadText} from "@lib/html" import {preventDefault, downloadText} from "@lib/html"
import Key from "@assets/icons/key-minimalistic.svg?dataurl"
import ArrowDown from "@assets/icons/arrow-down.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Field from "@lib/components/Field.svelte" import Field from "@lib/components/Field.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -13,6 +17,7 @@
import SignUpComplete from "@app/components/SignUpComplete.svelte" import SignUpComplete from "@app/components/SignUpComplete.svelte"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import {PLATFORM_NAME} from "@app/core/state"
type Props = { type Props = {
profile: Profile profile: Profile
@@ -24,7 +29,26 @@
const back = () => history.back() const back = () => history.back()
const cleanupCopy = (copy: string) =>
copy
.replace(/\n\s*\n\s*/g, "NEWLINE")
.replace(/\s+/g, " ")
.replace(/NEWLINE/g, "\n\n")
.trim()
const downloadKey = () => { const downloadKey = () => {
const sharedCopy = `
Most online services keep track of users by giving them a username and password. This gives the
service total control over their users, allowing them to ban them at any time, or sell their activity.
On Nostr, you control your own identity and social data, through the magic of cryptography. The basic
idea is that you have a public key, which acts as your user ID, and a private key which allows you to
prove your identity.
It's very important to keep your private key secret because it grants permanent and complete access to your
account.
`
if (usePassword) { if (usePassword) {
if (password.length < 12) { if (password.length < 12) {
return pushToast({ return pushToast({
@@ -34,12 +58,37 @@
} }
const ncryptsec = encrypt(hexToBytes(secret), password) const ncryptsec = encrypt(hexToBytes(secret), password)
const instructions = `
This file contains a backup of your Nostr secret key, downloaded from ${PLATFORM_NAME} and encrypted using
a password you chose when you signed up.
downloadText("Nostr Secret Key.txt", ncryptsec) ${sharedCopy}
Your encrypted private key is:
${ncryptsec}
To use it to log in to other Nostr apps, find a Nostr Signer app (https://nostrapps.com/#signers is a good
place to look), and import your key.
`
downloadText("Nostr Secret Key.txt", cleanupCopy(instructions))
} else { } else {
const nsec = nsecEncode(hexToBytes(secret)) const nsec = nsecEncode(hexToBytes(secret))
const instructions = `
This file contains a backup of your Nostr secret key, downloaded from ${PLATFORM_NAME}.
downloadText("Nostr Secret Key.txt", nsec) ${sharedCopy}
Your private key is:
${nsec}
To use it to log in to other Nostr apps, find a Nostr Signer app (https://nostrapps.com/#signers is a good
place to look), and import your key.
`
downloadText("Nostr Secret Key.txt", cleanupCopy(instructions))
} }
didDownload = true didDownload = true
@@ -84,7 +133,7 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="key" /> <Icon icon={Key} />
<input bind:value={password} onchange={onPasswordChange} class="grow" type="password" /> <input bind:value={password} onchange={onPasswordChange} class="grow" type="password" />
</label> </label>
{/snippet} {/snippet}
@@ -96,7 +145,7 @@
<div class="flex flex-col"> <div class="flex flex-col">
<Button class="btn {didDownload ? 'btn-neutral' : 'btn-primary'}" onclick={downloadKey}> <Button class="btn {didDownload ? 'btn-neutral' : 'btn-primary'}" onclick={downloadKey}>
Download my key Download my key
<Icon icon="arrow-down" /> <Icon icon={ArrowDown} />
</Button> </Button>
<Button class="btn btn-link no-underline" onclick={toggleUsePassword}> <Button class="btn btn-link no-underline" onclick={toggleUsePassword}>
{#if usePassword} {#if usePassword}
@@ -108,12 +157,12 @@
</div> </div>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button disabled={!didDownload} class="btn btn-primary" type="submit"> <Button disabled={!didDownload} class="btn btn-primary" type="submit">
Continue Continue
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
</form> </form>
+4 -2
View File
@@ -1,6 +1,8 @@
<script lang="ts"> <script lang="ts">
import type {Profile} from "@welshman/util" import type {Profile} from "@welshman/util"
import {makeProfile} from "@welshman/util" import {makeProfile} from "@welshman/util"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
@@ -23,12 +25,12 @@
{#snippet footer()} {#snippet footer()}
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button class="btn btn-primary" type="submit"> <Button class="btn btn-primary" type="submit">
Create Account Create Account
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
{/snippet} {/snippet}
+8 -4
View File
@@ -1,6 +1,10 @@
<script lang="ts"> <script lang="ts">
import {spec, prop, avg} from "@welshman/lib" import {spec, prop, avg} from "@welshman/lib"
import {signerLog, SignerLogEntryStatus} from "@welshman/app" import {signerLog, SignerLogEntryStatus} from "@welshman/app"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import ClockCircle from "@assets/icons/clock-circle.svg?dataurl"
import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import LogOut from "@app/components/LogOut.svelte" import LogOut from "@app/components/LogOut.svelte"
@@ -28,13 +32,13 @@
<span class="text-xl font-bold">Signer Status</span> <span class="text-xl font-bold">Signer Status</span>
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
{#if isDisconnected} {#if isDisconnected}
<Icon icon="close-circle" class="text-error" size={4} /> Disconnected <Icon icon={CloseCircle} class="text-error" size={4} /> Disconnected
{:else if recentFailure > 3} {:else if recentFailure > 3}
<Icon icon="danger" class="text-warning" size={4} /> Partial Failure <Icon icon={Danger} class="text-warning" size={4} /> Partial Failure
{:else if recentAvg > 1000 || recentPending > 3} {:else if recentAvg > 1000 || recentPending > 3}
<Icon icon="clock-circle" class="text-warning" size={4} /> Slow connection <Icon icon={ClockCircle} class="text-warning" size={4} /> Slow connection
{:else if recentSuccess === 0 && recentFailure > 0}{:else} {:else if recentSuccess === 0 && recentFailure > 0}{:else}
<Icon icon="check-circle" class="text-success" size={4} /> Ok <Icon icon={CheckCircle} class="text-success" size={4} /> Ok
{/if} {/if}
</span> </span>
</div> </div>
+23 -21
View File
@@ -1,17 +1,22 @@
<script lang="ts"> <script lang="ts">
import {displayUrl} from "@welshman/lib" import {displayUrl} from "@welshman/lib"
import {Pool, AuthStatus} from "@welshman/net" import {AuthStatus} from "@welshman/net"
import {waitForThunkError} from "@welshman/app"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Field from "@lib/components/Field.svelte" import Field from "@lib/components/Field.svelte"
import LinkRound from "@assets/icons/link-round.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
import SpaceJoinConfirm, {confirmSpaceJoin} from "@app/components/SpaceJoinConfirm.svelte" import SpaceJoinConfirm, {confirmSpaceJoin} from "@app/components/SpaceJoinConfirm.svelte"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import {attemptRelayAccess} from "@app/core/commands" import {publishJoinRequest} from "@app/core/commands"
import {deriveSocket} from "@app/core/state"
type Props = { type Props = {
url: string url: string
@@ -21,27 +26,24 @@
const back = () => history.back() const back = () => history.back()
const joinRelay = async () => { const socket = deriveSocket(url)
const error = await attemptRelayAccess(url, claim)
if (error) {
return pushToast({theme: "error", message: error, timeout: 30_000})
}
const socket = Pool.get().get(url)
if (socket.auth.status === AuthStatus.None) {
pushModal(SpaceJoinConfirm, {url}, {replaceState: true})
} else {
await confirmSpaceJoin(url)
}
}
const join = async () => { const join = async () => {
loading = true loading = true
try { try {
await joinRelay() const thunk = publishJoinRequest({url, claim})
const error = await waitForThunkError(thunk)
if (error) {
return pushToast({theme: "error", message: error, timeout: 30_000})
}
if ($socket.auth.status === AuthStatus.None) {
pushModal(SpaceJoinConfirm, {url}, {replaceState: true})
} else {
await confirmSpaceJoin(url)
}
} finally { } finally {
loading = false loading = false
} }
@@ -66,19 +68,19 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="link-round" /> <Icon icon={LinkRound} />
<input bind:value={claim} class="grow" type="text" /> <input bind:value={claim} class="grow" type="text" />
</label> </label>
{/snippet} {/snippet}
</Field> </Field>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary" disabled={loading}> <Button type="submit" class="btn btn-primary" disabled={loading}>
<Spinner {loading}>Join Space</Spinner> <Spinner {loading}>Join Space</Spinner>
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
</form> </form>
+9 -6
View File
@@ -1,4 +1,7 @@
<script lang="ts"> <script lang="ts">
import Compass from "@assets/icons/compass-big.svg?dataurl"
import Login from "@assets/icons/login-3.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -23,9 +26,9 @@
{/snippet} {/snippet}
</ModalHeader> </ModalHeader>
<Link href="/discover"> <Link href="/discover">
<CardButton class="!btn-primary"> <CardButton class="btn-primary">
{#snippet icon()} {#snippet icon()}
<div><Icon icon="compass" size={7} /></div> <div><Icon icon={Compass} size={7} /></div>
{/snippet} {/snippet}
{#snippet title()} {#snippet title()}
<div>Discover spaces</div> <div>Discover spaces</div>
@@ -36,9 +39,9 @@
</CardButton> </CardButton>
</Link> </Link>
<Button onclick={startJoin}> <Button onclick={startJoin}>
<CardButton> <CardButton class="dark:btn-neutral">
{#snippet icon()} {#snippet icon()}
<div><Icon icon="login-2" size={7} /></div> <div><Icon icon={Login} size={7} /></div>
{/snippet} {/snippet}
{#snippet title()} {#snippet title()}
<div>Join a space</div> <div>Join a space</div>
@@ -49,9 +52,9 @@
</CardButton> </CardButton>
</Button> </Button>
<Button onclick={startCreate}> <Button onclick={startCreate}>
<CardButton> <CardButton class="dark:btn-neutral">
{#snippet icon()} {#snippet icon()}
<div><Icon icon="add-circle" size={7} /></div> <div><Icon icon={AddCircle} size={7} /></div>
{/snippet} {/snippet}
{#snippet title()} {#snippet title()}
<div>Create a space</div> <div>Create a space</div>
+4 -2
View File
@@ -2,6 +2,8 @@
import {displayRelayUrl} from "@welshman/util" import {displayRelayUrl} from "@welshman/util"
import {parse, renderAsHtml} from "@welshman/content" import {parse, renderAsHtml} from "@welshman/content"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import {ucFirst} from "@lib/util" import {ucFirst} from "@lib/util"
@@ -34,12 +36,12 @@
</p> </p>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary"> <Button type="submit" class="btn btn-primary">
Request Access Request Access
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
</form> </form>
+2 -1
View File
@@ -2,6 +2,7 @@
import {displayRelayUrl} from "@welshman/util" import {displayRelayUrl} from "@welshman/util"
import Avatar from "@lib/components/Avatar.svelte" import Avatar from "@lib/components/Avatar.svelte"
import {deriveRelay} from "@welshman/app" import {deriveRelay} from "@welshman/app"
import RemoteControllerMinimalistic from "@assets/icons/remote-controller-minimalistic.svg?dataurl"
interface Props { interface Props {
url?: string url?: string
@@ -13,7 +14,7 @@
</script> </script>
<Avatar <Avatar
icon="remote-controller-minimalistic" icon={RemoteControllerMinimalistic}
class="!h-10 !w-10" class="!h-10 !w-10"
alt={displayRelayUrl(url)} alt={displayRelayUrl(url)}
src={$relay?.profile?.icon} /> src={$relay?.profile?.icon} />
+4 -2
View File
@@ -4,6 +4,8 @@
import {Pool, AuthStatus} from "@welshman/net" import {Pool, AuthStatus} from "@welshman/net"
import {displayRelayUrl} from "@welshman/util" import {displayRelayUrl} from "@welshman/util"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
@@ -64,12 +66,12 @@
</div> </div>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary" disabled={loading}> <Button type="submit" class="btn btn-primary" disabled={loading}>
Go to Space Go to Space
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
</form> </form>
+9 -5
View File
@@ -1,12 +1,16 @@
<script lang="ts"> <script lang="ts">
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import InputProfilePicture from "@lib/components/InputProfilePicture.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Field from "@lib/components/Field.svelte" import Field from "@lib/components/Field.svelte"
import FireMinimalistic from "@assets/icons/fire-minimalistic.svg?dataurl"
import Server from "@assets/icons/server.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
import InfoRelay from "@app/components/InfoRelay.svelte" import InfoRelay from "@app/components/InfoRelay.svelte"
import InputProfilePicture from "@app/components/InputProfilePicture.svelte"
import SpaceCreateFinish from "@app/components/SpaceCreateFinish.svelte" import SpaceCreateFinish from "@app/components/SpaceCreateFinish.svelte"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
@@ -37,7 +41,7 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="fire-minimalistic" /> <Icon icon={FireMinimalistic} />
<input bind:value={name} class="grow" type="text" /> <input bind:value={name} class="grow" type="text" />
</label> </label>
{/snippet} {/snippet}
@@ -48,7 +52,7 @@
{/snippet} {/snippet}
{#snippet input()} {#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2"> <label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="server" /> <Icon icon={Server} />
<input bind:value={relay} class="grow" type="text" /> <input bind:value={relay} class="grow" type="text" />
</label> </label>
{/snippet} {/snippet}
@@ -61,12 +65,12 @@
</Field> </Field>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary"> <Button type="submit" class="btn btn-primary">
Next Next
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
</form> </form>
@@ -2,6 +2,8 @@
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
@@ -43,12 +45,12 @@
</p> </p>
<ModalFooter> <ModalFooter>
<Button class="btn btn-link" onclick={back}> <Button class="btn btn-link" onclick={back}>
<Icon icon="alt-arrow-left" /> <Icon icon={AltArrowLeft} />
Go back Go back
</Button> </Button>
<Button type="submit" class="btn btn-primary"> <Button type="submit" class="btn btn-primary">
Let's go Let's go
<Icon icon="alt-arrow-right" /> <Icon icon={AltArrowRight} />
</Button> </Button>
</ModalFooter> </ModalFooter>
</form> </form>

Some files were not shown because too many files have changed in this diff Show More