Compare commits

..

1 Commits

Author SHA1 Message Date
hodlbod 9df8cee501 Migrate to new @welshman/domain + instance-based @welshman/app API
Adopts the rewritten welshman API: the removed @welshman/util helpers
(Profile/List/Room/Handler/Encryptable) are now Reader/Builder classes in
@welshman/domain, and @welshman/app dropped its global singletons for an App
instance + app.use(Plugin) registry.

- src/app/welshman.ts is now the app bootstrap + session-state module (one shared
  App instance, multi-account sessions/login, app-wide reactive views) rather than
  a compat shim re-exporting the old globals.
- Rewrote ~100 callers to use app.use(Plugin) directly (thunks, profiles, relays,
  rooms, zaps, tags, wot, feeds, sync); thunk helpers are now thunk methods.
- Added @welshman/domain dependency.
- Resolved residual gaps (storage hydration via plugin.onItem/wrapManager/Plaintext,
  relay-list mutators, search-relay list, outbox #d filter).

Best-effort: no toolchain/linking available, so this is not build- or
type-checked. Remaining judgment calls are flagged with TODO(welshman-migration).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsMjvv7krpZeHK1Njeneru
2026-06-20 14:55:06 +00:00
208 changed files with 2131 additions and 2474 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
VITE_DEFAULT_PUBKEYS=06639a386c9c1014217622ccbcf40908c4f1a0c33e23f8d6d68f4abf655f8f71,266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5,391819e2f2f13b90cac7209419eb574ef7c0d1f4e81867fc24c47a3ce5e8a248,3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d,3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24,55f04590674f3648f4cdc9dc8ce32da2a282074cd0b020596ee033d12d385185,58c741aa630c2da35a56a77c1d05381908bd10504fdd2d8b43f725efa6d23196,61066504617ee79387021e18c89fb79d1ddbc3e7bff19cf2298f40466f8715e9,6389be6491e7b693e9f368ece88fcd145f07c068d2c1bbae4247b9b5ef439d32,63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed,6e75f7972397ca3295e0f4ca0fbc6eb9cc79be85bafdd56bd378220ca8eee74e,76c71aae3a491f1d9eec47cba17e229cda4113a0bbb6e6ae1776d7643e29cafa,7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194,82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2,84dee6e676e5bb67b4ad4e042cf70cbd8681155db535942fcc6a0533858a7240,97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322,b676ded7c768d66a757aa3967b1243d90bf57afb09d1044d3219d8d424e4aea0,dace63b00c42e6e017d00dd190a9328386002ff597b841eb5ef91de4f1ce8491,eeb11961b25442b16389fe6c7ebea9adf0ac36dd596816ea7119e521b8821b9e,fe7f6bc6f7338b76bbf80db402ade65953e20b2f23e66e898204b63cc42539a3 VITE_DEFAULT_PUBKEYS=06639a386c9c1014217622ccbcf40908c4f1a0c33e23f8d6d68f4abf655f8f71,266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5,391819e2f2f13b90cac7209419eb574ef7c0d1f4e81867fc24c47a3ce5e8a248,3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d,3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24,55f04590674f3648f4cdc9dc8ce32da2a282074cd0b020596ee033d12d385185,58c741aa630c2da35a56a77c1d05381908bd10504fdd2d8b43f725efa6d23196,61066504617ee79387021e18c89fb79d1ddbc3e7bff19cf2298f40466f8715e9,6389be6491e7b693e9f368ece88fcd145f07c068d2c1bbae4247b9b5ef439d32,63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed,6e75f7972397ca3295e0f4ca0fbc6eb9cc79be85bafdd56bd378220ca8eee74e,76c71aae3a491f1d9eec47cba17e229cda4113a0bbb6e6ae1776d7643e29cafa,7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194,82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2,84dee6e676e5bb67b4ad4e042cf70cbd8681155db535942fcc6a0533858a7240,97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322,b676ded7c768d66a757aa3967b1243d90bf57afb09d1044d3219d8d424e4aea0,dace63b00c42e6e017d00dd190a9328386002ff597b841eb5ef91de4f1ce8491,eeb11961b25442b16389fe6c7ebea9adf0ac36dd596816ea7119e521b8821b9e,fe7f6bc6f7338b76bbf80db402ade65953e20b2f23e66e898204b63cc42539a3
VITE_DEFAULT_BLOSSOM_SERVERS=https://blossom.primal.net/ VITE_DEFAULT_BLOSSOM_SERVERS=https://blossom.primal.net/
VITE_DEFAULT_SPACES=https://support.flotilla.social/ VITE_DEFAULT_SPACES=https://chat.flotilla.social/
VITE_POMADE_SIGNERS=https://pomade.coracle.social,https://pomade.fiatjaf.com,https://pomade.nostrver.se,https://pomade.scuttle.works VITE_POMADE_SIGNERS=https://pomade.coracle.social,https://pomade.fiatjaf.com,https://pomade.nostrver.se,https://pomade.scuttle.works
VITE_PLATFORM_URL=https://app.flotilla.social VITE_PLATFORM_URL=https://app.flotilla.social
VITE_PLATFORM_TERMS=https://flotilla.social/terms VITE_PLATFORM_TERMS=https://flotilla.social/terms
-3
View File
@@ -170,9 +170,6 @@ src/
- Do not define svelte event handlers inline, instead name them and put them in the script section of templates - Do not define svelte event handlers inline, instead name them and put them in the script section of templates
- Avoid using `as`, except where necessary. Instead, annotate function parameters, and ensure upstream values are typed correctly. - Avoid using `as`, except where necessary. Instead, annotate function parameters, and ensure upstream values are typed correctly.
- Instead of `getTag(tagName, event.tags)?.[1] || ""`, use `getTagValue(tagName, event.tags)` - Instead of `getTag(tagName, event.tags)?.[1] || ""`, use `getTagValue(tagName, event.tags)`
- Do not render a profile's `about` directly (e.g. `profile.about`); use the `ProfileInfo` component instead.
- Use `type Props` instead of interface when defining props for svelte components.
- When a component's value/prop shape mirrors a subset of an existing type, derive it with `Pick`/`Partial` and `export` that type from the component's `<script module>` (e.g. a `Values` type) for callers to import, instead of re-enumerating its sub-properties.
**Human-First Simplicity (Jon Staab Style):** **Human-First Simplicity (Jon Staab Style):**
-26
View File
@@ -1,31 +1,5 @@
# Changelog # Changelog
# 1.8.2
* Fix thread board styling
* Fix space joining errors
# 1.8.1
* Add space dashboard and directory
* Add space roles
* Fix UI bugs
* Redesign threads as a linear phpBB-style forum view
* Unwrap messages that are only quotes
* Use direct zapping for the donate page, link to flotilla space for support
* Speed up feed loading
* Fix bunker login
* Add welshman skill
* Replace zap slider with common amount pills
* Make join rejections due to an empty claim more forgiving
* Show voice room participants before joining
* Fix direct links to spaces
* Show per-relay publish status on outgoing messages
* Fix A/V call bugs
* Sync checked read state for cross-device badges
* Fix deleted rooms persisting in navigation
* Turn on notification defaults and prompt on first DM visit
# 1.8.0 # 1.8.0
* Fix relay badge overflow * Fix relay badge overflow
+1 -1
View File
@@ -14,7 +14,7 @@ RUN corepack enable
WORKDIR /app WORKDIR /app
ENV NODE_OPTIONS=--max_old_space_size=16384 ENV NODE_OPTIONS=--max_old_space_size=16384
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ COPY package.json pnpm-lock.yaml ./
RUN pnpm i --frozen-lockfile RUN pnpm i --frozen-lockfile
COPY . . COPY . .
ARG VITE_BUILD_HASH ARG VITE_BUILD_HASH
+2 -2
View File
@@ -8,8 +8,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 49 versionCode 47
versionName "1.8.2" versionName "1.8.0"
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.
+1
View File
@@ -10,6 +10,7 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies { dependencies {
implementation project(':aparajita-capacitor-secure-storage') implementation project(':aparajita-capacitor-secure-storage')
implementation project(':capacitor-community-safe-area')
implementation project(':capacitor-app') implementation project(':capacitor-app')
implementation project(':capacitor-clipboard') implementation project(':capacitor-clipboard')
implementation project(':capacitor-filesystem') implementation project(':capacitor-filesystem')
@@ -11,6 +11,7 @@
<item name="windowActionBar">false</item> <item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>
<item name="android:background">@null</item> <item name="android:background">@null</item>
<item name="android:windowOptOutEdgeToEdgeEnforcement" tools:ignore="NewApi">true</item>
</style> </style>
+14 -11
View File
@@ -1,36 +1,39 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android' include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/.pnpm/@capacitor+android@8.3.4_@capacitor+core@8.3.4/node_modules/@capacitor/android/capacitor') project(':capacitor-android').projectDir = new File('../node_modules/.pnpm/@capacitor+android@8.0.1_@capacitor+core@8.0.1/node_modules/@capacitor/android/capacitor')
include ':aparajita-capacitor-secure-storage' include ':aparajita-capacitor-secure-storage'
project(':aparajita-capacitor-secure-storage').projectDir = new File('../node_modules/.pnpm/@aparajita+capacitor-secure-storage@8.0.0/node_modules/@aparajita/capacitor-secure-storage/android') project(':aparajita-capacitor-secure-storage').projectDir = new File('../node_modules/.pnpm/@aparajita+capacitor-secure-storage@8.0.0/node_modules/@aparajita/capacitor-secure-storage/android')
include ':capacitor-community-safe-area'
project(':capacitor-community-safe-area').projectDir = new File('../node_modules/.pnpm/@capacitor-community+safe-area@8.0.1_@capacitor+core@8.0.1/node_modules/@capacitor-community/safe-area/android')
include ':capacitor-app' include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacitor+app@8.1.0_@capacitor+core@8.3.4/node_modules/@capacitor/app/android') project(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacitor+app@8.0.0_@capacitor+core@8.0.1/node_modules/@capacitor/app/android')
include ':capacitor-clipboard' include ':capacitor-clipboard'
project(':capacitor-clipboard').projectDir = new File('../node_modules/.pnpm/@capacitor+clipboard@8.0.1_@capacitor+core@8.3.4/node_modules/@capacitor/clipboard/android') project(':capacitor-clipboard').projectDir = new File('../node_modules/.pnpm/@capacitor+clipboard@8.0.1_@capacitor+core@8.0.1/node_modules/@capacitor/clipboard/android')
include ':capacitor-filesystem' include ':capacitor-filesystem'
project(':capacitor-filesystem').projectDir = new File('../node_modules/.pnpm/@capacitor+filesystem@8.1.2_@capacitor+core@8.3.4/node_modules/@capacitor/filesystem/android') project(':capacitor-filesystem').projectDir = new File('../node_modules/.pnpm/@capacitor+filesystem@8.1.0_@capacitor+core@8.0.1/node_modules/@capacitor/filesystem/android')
include ':capacitor-keyboard' include ':capacitor-keyboard'
project(':capacitor-keyboard').projectDir = new File('../node_modules/.pnpm/@capacitor+keyboard@8.0.3_@capacitor+core@8.3.4/node_modules/@capacitor/keyboard/android') project(':capacitor-keyboard').projectDir = new File('../node_modules/.pnpm/@capacitor+keyboard@8.0.0_@capacitor+core@8.0.1/node_modules/@capacitor/keyboard/android')
include ':capacitor-preferences' include ':capacitor-preferences'
project(':capacitor-preferences').projectDir = new File('../node_modules/.pnpm/@capacitor+preferences@8.0.1_@capacitor+core@8.3.4/node_modules/@capacitor/preferences/android') project(':capacitor-preferences').projectDir = new File('../node_modules/.pnpm/@capacitor+preferences@8.0.0_@capacitor+core@8.0.1/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@8.1.1_@capacitor+core@8.3.4/node_modules/@capacitor/push-notifications/android') project(':capacitor-push-notifications').projectDir = new File('../node_modules/.pnpm/@capacitor+push-notifications@8.0.0_@capacitor+core@8.0.1/node_modules/@capacitor/push-notifications/android')
include ':capacitor-share' include ':capacitor-share'
project(':capacitor-share').projectDir = new File('../node_modules/.pnpm/@capacitor+share@8.0.1_@capacitor+core@8.3.4/node_modules/@capacitor/share/android') project(':capacitor-share').projectDir = new File('../node_modules/.pnpm/@capacitor+share@8.0.1_@capacitor+core@8.0.1/node_modules/@capacitor/share/android')
include ':capawesome-capacitor-android-dark-mode-support' 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@8.0.1_@capacitor+core@8.3.4/node_modules/@capawesome/capacitor-android-dark-mode-support/android') project(':capawesome-capacitor-android-dark-mode-support').projectDir = new File('../node_modules/.pnpm/@capawesome+capacitor-android-dark-mode-support@8.0.0_@capacitor+core@8.0.1/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@8.0.2_@capacitor+core@8.3.4/node_modules/@capawesome/capacitor-badge/android') project(':capawesome-capacitor-badge').projectDir = new File('../node_modules/.pnpm/@capawesome+capacitor-badge@8.0.0_@capacitor+core@8.0.1/node_modules/@capawesome/capacitor-badge/android')
include ':nostr-signer-capacitor-plugin' include ':nostr-signer-capacitor-plugin'
project(':nostr-signer-capacitor-plugin').projectDir = new File('../node_modules/.pnpm/nostr-signer-capacitor-plugin@https+++codeload.github.com+coracle-social+nostr-signer-c_fd4b5c957724da9f45e5678eca5c7fd4/node_modules/nostr-signer-capacitor-plugin/android') project(':nostr-signer-capacitor-plugin').projectDir = new File('../node_modules/.pnpm/nostr-signer-capacitor-plugin@https+++codeload.github.com+coracle-social+nostr-signer-c_a3c0fb15d5bfa83f24d0070ca2583fc9/node_modules/nostr-signer-capacitor-plugin/android')
+5 -1
View File
@@ -7,18 +7,22 @@ const config: CapacitorConfig = {
ios: { ios: {
scheme: "Flotilla Chat", scheme: "Flotilla Chat",
}, },
android: {
adjustMarginsForEdgeToEdge: true,
},
plugins: { plugins: {
CapacitorHttp: { CapacitorHttp: {
enabled: true, enabled: true,
}, },
SystemBars: { SystemBars: {
insetsHandling: "css", insetsHandling: "enable",
}, },
SplashScreen: { SplashScreen: {
androidSplashResourceName: "splash", androidSplashResourceName: "splash",
}, },
Keyboard: { Keyboard: {
style: "DARK", style: "DARK",
resizeOnFullScreen: true,
}, },
Badge: { Badge: {
persist: true, persist: true,
+4 -4
View File
@@ -372,7 +372,7 @@
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements"; CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 40; CURRENT_PROJECT_VERSION = 38;
INFOPLIST_FILE = App/Info.plist; INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat"; INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
@@ -381,7 +381,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.8.2; MARKETING_VERSION = 1.8.0;
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)";
@@ -401,7 +401,7 @@
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements"; CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 40; CURRENT_PROJECT_VERSION = 38;
INFOPLIST_FILE = App/Info.plist; INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat"; INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
@@ -410,7 +410,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.8.2; MARKETING_VERSION = 1.8.0;
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla; PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
+13 -12
View File
@@ -1,4 +1,4 @@
require_relative '../../node_modules/.pnpm/@capacitor+ios@8.3.4_@capacitor+core@8.3.4/node_modules/@capacitor/ios/scripts/pods_helpers' require_relative '../../node_modules/.pnpm/@capacitor+ios@8.0.1_@capacitor+core@8.0.1/node_modules/@capacitor/ios/scripts/pods_helpers'
platform :ios, '15.0' platform :ios, '15.0'
use_frameworks! use_frameworks!
@@ -9,18 +9,19 @@ use_frameworks!
install! 'cocoapods', :disable_input_output_paths => true install! 'cocoapods', :disable_input_output_paths => true
def capacitor_pods def capacitor_pods
pod 'Capacitor', :path => '../../node_modules/.pnpm/@capacitor+ios@8.3.4_@capacitor+core@8.3.4/node_modules/@capacitor/ios' pod 'Capacitor', :path => '../../node_modules/.pnpm/@capacitor+ios@8.0.1_@capacitor+core@8.0.1/node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../node_modules/.pnpm/@capacitor+ios@8.3.4_@capacitor+core@8.3.4/node_modules/@capacitor/ios' pod 'CapacitorCordova', :path => '../../node_modules/.pnpm/@capacitor+ios@8.0.1_@capacitor+core@8.0.1/node_modules/@capacitor/ios'
pod 'AparajitaCapacitorSecureStorage', :path => '../../node_modules/.pnpm/@aparajita+capacitor-secure-storage@8.0.0/node_modules/@aparajita/capacitor-secure-storage' pod 'AparajitaCapacitorSecureStorage', :path => '../../node_modules/.pnpm/@aparajita+capacitor-secure-storage@8.0.0/node_modules/@aparajita/capacitor-secure-storage'
pod 'CapacitorApp', :path => '../../node_modules/.pnpm/@capacitor+app@8.1.0_@capacitor+core@8.3.4/node_modules/@capacitor/app' pod 'CapacitorCommunitySafeArea', :path => '../../node_modules/.pnpm/@capacitor-community+safe-area@8.0.1_@capacitor+core@8.0.1/node_modules/@capacitor-community/safe-area'
pod 'CapacitorClipboard', :path => '../../node_modules/.pnpm/@capacitor+clipboard@8.0.1_@capacitor+core@8.3.4/node_modules/@capacitor/clipboard' pod 'CapacitorApp', :path => '../../node_modules/.pnpm/@capacitor+app@8.0.0_@capacitor+core@8.0.1/node_modules/@capacitor/app'
pod 'CapacitorFilesystem', :path => '../../node_modules/.pnpm/@capacitor+filesystem@8.1.2_@capacitor+core@8.3.4/node_modules/@capacitor/filesystem' pod 'CapacitorClipboard', :path => '../../node_modules/.pnpm/@capacitor+clipboard@8.0.1_@capacitor+core@8.0.1/node_modules/@capacitor/clipboard'
pod 'CapacitorKeyboard', :path => '../../node_modules/.pnpm/@capacitor+keyboard@8.0.3_@capacitor+core@8.3.4/node_modules/@capacitor/keyboard' pod 'CapacitorFilesystem', :path => '../../node_modules/.pnpm/@capacitor+filesystem@8.1.0_@capacitor+core@8.0.1/node_modules/@capacitor/filesystem'
pod 'CapacitorPreferences', :path => '../../node_modules/.pnpm/@capacitor+preferences@8.0.1_@capacitor+core@8.3.4/node_modules/@capacitor/preferences' pod 'CapacitorKeyboard', :path => '../../node_modules/.pnpm/@capacitor+keyboard@8.0.0_@capacitor+core@8.0.1/node_modules/@capacitor/keyboard'
pod 'CapacitorPushNotifications', :path => '../../node_modules/.pnpm/@capacitor+push-notifications@8.1.1_@capacitor+core@8.3.4/node_modules/@capacitor/push-notifications' pod 'CapacitorPreferences', :path => '../../node_modules/.pnpm/@capacitor+preferences@8.0.0_@capacitor+core@8.0.1/node_modules/@capacitor/preferences'
pod 'CapacitorShare', :path => '../../node_modules/.pnpm/@capacitor+share@8.0.1_@capacitor+core@8.3.4/node_modules/@capacitor/share' pod 'CapacitorPushNotifications', :path => '../../node_modules/.pnpm/@capacitor+push-notifications@8.0.0_@capacitor+core@8.0.1/node_modules/@capacitor/push-notifications'
pod 'CapawesomeCapacitorBadge', :path => '../../node_modules/.pnpm/@capawesome+capacitor-badge@8.0.2_@capacitor+core@8.3.4/node_modules/@capawesome/capacitor-badge' pod 'CapacitorShare', :path => '../../node_modules/.pnpm/@capacitor+share@8.0.1_@capacitor+core@8.0.1/node_modules/@capacitor/share'
pod 'NostrSignerCapacitorPlugin', :path => '../../node_modules/.pnpm/nostr-signer-capacitor-plugin@https+++codeload.github.com+coracle-social+nostr-signer-c_fd4b5c957724da9f45e5678eca5c7fd4/node_modules/nostr-signer-capacitor-plugin' pod 'CapawesomeCapacitorBadge', :path => '../../node_modules/.pnpm/@capawesome+capacitor-badge@8.0.0_@capacitor+core@8.0.1/node_modules/@capawesome/capacitor-badge'
pod 'NostrSignerCapacitorPlugin', :path => '../../node_modules/.pnpm/nostr-signer-capacitor-plugin@https+++codeload.github.com+coracle-social+nostr-signer-c_a3c0fb15d5bfa83f24d0070ca2583fc9/node_modules/nostr-signer-capacitor-plugin'
end end
target 'Flotilla Chat' do target 'Flotilla Chat' do
+4 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "flotilla", "name": "flotilla",
"version": "1.8.2", "version": "1.8.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
@@ -45,6 +45,7 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@aparajita/capacitor-secure-storage": "^8.0.0", "@aparajita/capacitor-secure-storage": "^8.0.0",
"@capacitor-community/safe-area": "^8.0.1",
"@capacitor/android": "^8.0.1", "@capacitor/android": "^8.0.1",
"@capacitor/app": "^8.0.0", "@capacitor/app": "^8.0.0",
"@capacitor/cli": "^8.0.1", "@capacitor/cli": "^8.0.1",
@@ -62,7 +63,7 @@
"@getalby/sdk": "^5.1.2", "@getalby/sdk": "^5.1.2",
"@hono/node-server": "^2.0.0", "@hono/node-server": "^2.0.0",
"@noble/curves": "^1.9.7", "@noble/curves": "^1.9.7",
"@pomade/core": "^0.3.1", "@pomade/core": "^0.3.0",
"@poppanator/sveltekit-svg": "^7.0.0", "@poppanator/sveltekit-svg": "^7.0.0",
"@sveltejs/adapter-static": "^3.0.10", "@sveltejs/adapter-static": "^3.0.10",
"@tiptap/core": "^2.27.2", "@tiptap/core": "^2.27.2",
@@ -73,6 +74,7 @@
"@vite-pwa/sveltekit": "^1.1.0", "@vite-pwa/sveltekit": "^1.1.0",
"@welshman/app": "^0.8.16", "@welshman/app": "^0.8.16",
"@welshman/content": "^0.8.16", "@welshman/content": "^0.8.16",
"@welshman/domain": "^0.8.16",
"@welshman/editor": "^0.8.16", "@welshman/editor": "^0.8.16",
"@welshman/feeds": "^0.8.16", "@welshman/feeds": "^0.8.16",
"@welshman/lib": "^0.8.16", "@welshman/lib": "^0.8.16",
+26 -14
View File
@@ -14,6 +14,9 @@ importers:
'@aparajita/capacitor-secure-storage': '@aparajita/capacitor-secure-storage':
specifier: ^8.0.0 specifier: ^8.0.0
version: 8.0.0 version: 8.0.0
'@capacitor-community/safe-area':
specifier: ^8.0.1
version: 8.0.1(@capacitor/core@8.3.4)
'@capacitor/android': '@capacitor/android':
specifier: ^8.0.1 specifier: ^8.0.1
version: 8.3.4(@capacitor/core@8.3.4) version: 8.3.4(@capacitor/core@8.3.4)
@@ -66,8 +69,8 @@ importers:
specifier: ^1.9.7 specifier: ^1.9.7
version: 1.9.7 version: 1.9.7
'@pomade/core': '@pomade/core':
specifier: ^0.3.1 specifier: ^0.3.0
version: 0.3.1(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.2.0)(@welshman/lib@0.8.16)(@welshman/net@0.8.16(@welshman/lib@0.8.16)(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(ws@8.21.0))(@welshman/signer@0.8.16(@noble/curves@1.9.7)(@noble/hashes@2.2.0)(@welshman/lib@0.8.16)(@welshman/net@0.8.16(@welshman/lib@0.8.16)(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(ws@8.21.0))(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/436adec9ed1e71569748cd56aa697f361e7a8d47(@capacitor/core@8.3.4))(nostr-tools@2.23.5(typescript@5.9.3)))(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(nostr-tools@2.23.5(typescript@5.9.3)) version: 0.3.0(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.2.0)(@welshman/lib@0.8.16)(@welshman/net@0.8.16(@welshman/lib@0.8.16)(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(ws@8.21.0))(@welshman/signer@0.8.16(@noble/curves@1.9.7)(@noble/hashes@2.2.0)(@welshman/lib@0.8.16)(@welshman/net@0.8.16(@welshman/lib@0.8.16)(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(ws@8.21.0))(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/436adec9ed1e71569748cd56aa697f361e7a8d47(@capacitor/core@8.3.4))(nostr-tools@2.23.5(typescript@5.9.3)))(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(nostr-tools@2.23.5(typescript@5.9.3))
'@poppanator/sveltekit-svg': '@poppanator/sveltekit-svg':
specifier: ^7.0.0 specifier: ^7.0.0
version: 7.0.0(rollup@4.60.4)(svelte@5.55.9(@typescript-eslint/types@8.60.0))(vite@6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)) version: 7.0.0(rollup@4.60.4)(svelte@5.55.9(@typescript-eslint/types@8.60.0))(vite@6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0))
@@ -94,7 +97,7 @@ importers:
version: 1.1.0(@sveltejs/kit@2.61.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.9(@typescript-eslint/types@8.60.0))(vite@6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)))(svelte@5.55.9(@typescript-eslint/types@8.60.0))(typescript@5.9.3)(vite@6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)))(@vite-pwa/assets-generator@1.0.2)(vite@6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0))(workbox-build@7.4.1)(workbox-window@7.4.1) version: 1.1.0(@sveltejs/kit@2.61.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.9(@typescript-eslint/types@8.60.0))(vite@6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)))(svelte@5.55.9(@typescript-eslint/types@8.60.0))(typescript@5.9.3)(vite@6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0)))(@vite-pwa/assets-generator@1.0.2)(vite@6.4.2(@types/node@25.9.1)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.48.0))(workbox-build@7.4.1)(workbox-window@7.4.1)
'@welshman/app': '@welshman/app':
specifier: ^0.8.16 specifier: ^0.8.16
version: 0.8.16(9e2dd3230191940679c41b23e5e365c3) version: 0.8.16(7683b6be0f65191b839378ceee4e4014)
'@welshman/content': '@welshman/content':
specifier: ^0.8.16 specifier: ^0.8.16
version: 0.8.16(nostr-tools@2.23.5(typescript@5.9.3)) version: 0.8.16(nostr-tools@2.23.5(typescript@5.9.3))
@@ -776,6 +779,11 @@ packages:
'@canvas/image-data@1.1.0': '@canvas/image-data@1.1.0':
resolution: {integrity: sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA==} resolution: {integrity: sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA==}
'@capacitor-community/safe-area@8.0.1':
resolution: {integrity: sha512-zVVQ7k94DbOff1mHP8qrZGTJZWyhGZnKHD2eXdcobIoOBFW9CeVphLNZYDCop/DBsHXfv6r8k0/Ac8xMIlwT+A==}
peerDependencies:
'@capacitor/core': '>=8.0.0'
'@capacitor/android@8.3.4': '@capacitor/android@8.3.4':
resolution: {integrity: sha512-7gJjrG3X32Am1QMLqgMztWTYMLMVNE+VZwhekNxhvYizH4mOV05vH+rC9B+f17bCkYZfyu/qXQX6hoY7kLeVZw==} resolution: {integrity: sha512-7gJjrG3X32Am1QMLqgMztWTYMLMVNE+VZwhekNxhvYizH4mOV05vH+rC9B+f17bCkYZfyu/qXQX6hoY7kLeVZw==}
peerDependencies: peerDependencies:
@@ -1468,9 +1476,9 @@ packages:
'@polka/url@1.0.0-next.29': '@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
'@pomade/core@0.3.1': '@pomade/core@0.3.0':
resolution: {integrity: sha512-lNsM60bu2o9JlqPc47JoAz19QACXS5dNYgvoeApLW8LpxuWy7RcMyHDZ3llklVPYd99PXslFtBdVRpbP26oKyQ==} resolution: {integrity: sha512-zWx0wJftbW92GSIEdLnOk8oUzaGAd0DbOqCBOoNeyCtgn9i4aNy0QsVmYDwxI8xKtVujsxppqk+fMJvinIiEqA==}
version: 0.3.1 version: 0.3.0
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
peerDependencies: peerDependencies:
'@frostr/bifrost': ^1.0.7 '@frostr/bifrost': ^1.0.7
@@ -5839,6 +5847,10 @@ snapshots:
'@canvas/image-data@1.1.0': {} '@canvas/image-data@1.1.0': {}
'@capacitor-community/safe-area@8.0.1(@capacitor/core@8.3.4)':
dependencies:
'@capacitor/core': 8.3.4
'@capacitor/android@8.3.4(@capacitor/core@8.3.4)': '@capacitor/android@8.3.4(@capacitor/core@8.3.4)':
dependencies: dependencies:
'@capacitor/core': 8.3.4 '@capacitor/core': 8.3.4
@@ -6001,7 +6013,7 @@ snapshots:
'@emnapi/runtime@1.10.0': '@emnapi/runtime@1.10.0':
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.6.2
optional: true optional: true
'@esbuild/aix-ppc64@0.25.12': '@esbuild/aix-ppc64@0.25.12':
@@ -6306,7 +6318,7 @@ snapshots:
debug: 4.3.4 debug: 4.3.4
signal-exit: 3.0.7 signal-exit: 3.0.7
tree-kill: 1.2.2 tree-kill: 1.2.2
tslib: 2.8.1 tslib: 2.6.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -6324,7 +6336,7 @@ snapshots:
'@ionic/utils-stream@3.1.6': '@ionic/utils-stream@3.1.6':
dependencies: dependencies:
debug: 4.3.4 debug: 4.3.4
tslib: 2.8.1 tslib: 2.6.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -6344,7 +6356,7 @@ snapshots:
'@ionic/utils-terminal': 2.3.4 '@ionic/utils-terminal': 2.3.4
cross-spawn: 7.0.6 cross-spawn: 7.0.6
debug: 4.3.4 debug: 4.3.4
tslib: 2.8.1 tslib: 2.6.2
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -6369,7 +6381,7 @@ snapshots:
slice-ansi: 4.0.0 slice-ansi: 4.0.0
string-width: 4.2.3 string-width: 4.2.3
strip-ansi: 6.0.1 strip-ansi: 6.0.1
tslib: 2.8.1 tslib: 2.6.2
untildify: 4.0.0 untildify: 4.0.0
wrap-ansi: 7.0.0 wrap-ansi: 7.0.0
transitivePeerDependencies: transitivePeerDependencies:
@@ -6566,7 +6578,7 @@ snapshots:
'@polka/url@1.0.0-next.29': {} '@polka/url@1.0.0-next.29': {}
'@pomade/core@0.3.1(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.2.0)(@welshman/lib@0.8.16)(@welshman/net@0.8.16(@welshman/lib@0.8.16)(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(ws@8.21.0))(@welshman/signer@0.8.16(@noble/curves@1.9.7)(@noble/hashes@2.2.0)(@welshman/lib@0.8.16)(@welshman/net@0.8.16(@welshman/lib@0.8.16)(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(ws@8.21.0))(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/436adec9ed1e71569748cd56aa697f361e7a8d47(@capacitor/core@8.3.4))(nostr-tools@2.23.5(typescript@5.9.3)))(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(nostr-tools@2.23.5(typescript@5.9.3))': '@pomade/core@0.3.0(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.2.0)(@welshman/lib@0.8.16)(@welshman/net@0.8.16(@welshman/lib@0.8.16)(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(ws@8.21.0))(@welshman/signer@0.8.16(@noble/curves@1.9.7)(@noble/hashes@2.2.0)(@welshman/lib@0.8.16)(@welshman/net@0.8.16(@welshman/lib@0.8.16)(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(ws@8.21.0))(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/436adec9ed1e71569748cd56aa697f361e7a8d47(@capacitor/core@8.3.4))(nostr-tools@2.23.5(typescript@5.9.3)))(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(nostr-tools@2.23.5(typescript@5.9.3))':
dependencies: dependencies:
'@frostr/bifrost': 1.0.7(typescript@5.9.3) '@frostr/bifrost': 1.0.7(typescript@5.9.3)
'@noble/hashes': 2.2.0 '@noble/hashes': 2.2.0
@@ -7185,9 +7197,9 @@ snapshots:
- workbox-build - workbox-build
- workbox-window - workbox-window
'@welshman/app@0.8.16(9e2dd3230191940679c41b23e5e365c3)': '@welshman/app@0.8.16(7683b6be0f65191b839378ceee4e4014)':
dependencies: dependencies:
'@pomade/core': 0.3.1(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.2.0)(@welshman/lib@0.8.16)(@welshman/net@0.8.16(@welshman/lib@0.8.16)(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(ws@8.21.0))(@welshman/signer@0.8.16(@noble/curves@1.9.7)(@noble/hashes@2.2.0)(@welshman/lib@0.8.16)(@welshman/net@0.8.16(@welshman/lib@0.8.16)(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(ws@8.21.0))(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/436adec9ed1e71569748cd56aa697f361e7a8d47(@capacitor/core@8.3.4))(nostr-tools@2.23.5(typescript@5.9.3)))(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(nostr-tools@2.23.5(typescript@5.9.3)) '@pomade/core': 0.3.0(@frostr/bifrost@1.0.7(typescript@5.9.3))(@noble/hashes@2.2.0)(@welshman/lib@0.8.16)(@welshman/net@0.8.16(@welshman/lib@0.8.16)(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(ws@8.21.0))(@welshman/signer@0.8.16(@noble/curves@1.9.7)(@noble/hashes@2.2.0)(@welshman/lib@0.8.16)(@welshman/net@0.8.16(@welshman/lib@0.8.16)(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(ws@8.21.0))(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(nostr-signer-capacitor-plugin@https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/436adec9ed1e71569748cd56aa697f361e7a8d47(@capacitor/core@8.3.4))(nostr-tools@2.23.5(typescript@5.9.3)))(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(nostr-tools@2.23.5(typescript@5.9.3))
'@welshman/feeds': 0.8.16(942b3be06d36b211cee078a14ee828c5) '@welshman/feeds': 0.8.16(942b3be06d36b211cee078a14ee828c5)
'@welshman/lib': 0.8.16 '@welshman/lib': 0.8.16
'@welshman/net': 0.8.16(@welshman/lib@0.8.16)(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(ws@8.21.0) '@welshman/net': 0.8.16(@welshman/lib@0.8.16)(@welshman/util@0.8.16(@noble/curves@1.9.7)(@welshman/lib@0.8.16)(nostr-tools@2.23.5(typescript@5.9.3)))(ws@8.21.0)
+3 -8
View File
@@ -85,7 +85,7 @@
} }
@utility card2 { @utility card2 {
@apply rounded-box text-base-content border-base-content/20 bg-base-100 border border-solid p-4 shadow-xl/5 sm:p-6; @apply rounded-box text-base-content p-4 sm:p-6;
} }
@utility column { @utility column {
@@ -276,11 +276,6 @@
@apply text-base-content p-2 sm:p-4; @apply text-base-content p-2 sm:p-4;
} }
.card2 .card2,
.dialog .card2 {
@apply shadow-none;
}
[data-tip]::before { [data-tip]::before {
@apply overflow-hidden text-ellipsis; @apply overflow-hidden text-ellipsis;
} }
@@ -419,11 +414,11 @@ progress[value]::-webkit-progress-value {
/* content width for fixed elements */ /* content width for fixed elements */
.left-content { .left-content {
@apply left-sai md:left-[calc(18.5rem+var(--sail))]; @apply md:left-[calc(18.5rem+var(--sail))];
} }
.left-content-full { .left-content-full {
@apply left-sai md:left-[calc(3.5rem+var(--sail))]; @apply md:left-[calc(3.5rem+var(--sail))];
} }
/* Keyboard open state adjustments */ /* Keyboard open state adjustments */
+3 -1
View File
@@ -4,7 +4,9 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>{NAME}</title> <title>{NAME}</title>
<link rel="canonical" href="{URL}" /> <link rel="canonical" href="{URL}" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> <meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content" />
<meta name="theme-color" content="{ACCENT}" /> <meta name="theme-color" content="{ACCENT}" />
<meta name="description" content="{DESCRIPTION}" /> <meta name="description" content="{DESCRIPTION}" />
<meta property="og:url" content="{URL}" /> <meta property="og:url" content="{URL}" />
+1 -1
View File
@@ -18,7 +18,7 @@ import {derived, get} from "svelte/store"
import {map, not, nthEq, reject, removeUndefined, uniqBy} from "@welshman/lib" import {map, not, nthEq, reject, removeUndefined, uniqBy} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {makeHttpAuth, makeHttpAuthHeader, getTags} from "@welshman/util" import {makeHttpAuth, makeHttpAuthHeader, getTags} from "@welshman/util"
import {signer} from "@welshman/app" import {signer} from "@app/welshman"
import {load} from "@welshman/net" import {load} from "@welshman/net"
import {getLivekitEndpoint} from "$lib/livekit" import {getLivekitEndpoint} from "$lib/livekit"
import {AbortError, TimeoutError, whenAborted, whenTimeout} from "$lib/util" import {AbortError, TimeoutError, whenAborted, whenTimeout} from "$lib/util"
+4 -1
View File
@@ -2,7 +2,8 @@ import {DELETE, PROFILE, getPubkeyTagValues} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {append, call, on, reject, remove, sort, sortBy, spec, uniq, uniqBy} from "@welshman/lib" import {append, call, on, reject, remove, sort, sortBy, spec, uniq, uniqBy} from "@welshman/lib"
import type {Override} from "@welshman/lib" import type {Override} from "@welshman/lib"
import {createSearch, displayProfileByPubkey, pubkey, repository} from "@welshman/app" import {Profiles} from "@welshman/app"
import {app, createSearch, pubkey, repository} from "@app/welshman"
import {derived, readable} from "svelte/store" import {derived, readable} from "svelte/store"
import {DM_KINDS} from "@app/content" import {DM_KINDS} from "@app/content"
import type {RepositoryUpdate} from "@welshman/net" import type {RepositoryUpdate} from "@welshman/net"
@@ -35,6 +36,8 @@ export const chatsById = call(() => {
const chatsByPubkey = new Map<string, string[]>() const chatsByPubkey = new Map<string, string[]>()
const addSearchText = (chat: Override<Chat, {search_text?: string}>) => { const addSearchText = (chat: Override<Chat, {search_text?: string}>) => {
const displayProfileByPubkey = (pk: string) => app.use(Profiles).display(pk).get()
chat.search_text = chat.search_text =
chat.pubkeys.length === 1 chat.pubkeys.length === 1
? displayProfileByPubkey(chat.pubkeys[0]) + " note to self" ? displayProfileByPubkey(chat.pubkeys[0]) + " note to self"
+4 -3
View File
@@ -1,6 +1,7 @@
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {COMMENT, makeEvent} from "@welshman/util" import {COMMENT, makeEvent} from "@welshman/util"
import {publishThunk, tagEventForComment} from "@welshman/app" import {Thunks, Tags} from "@welshman/app"
import {app} from "@app/welshman"
export type CommentParams = { export type CommentParams = {
event: TrustedEvent event: TrustedEvent
@@ -10,7 +11,7 @@ export type CommentParams = {
} }
export const makeComment = ({url, event, content, tags = []}: CommentParams) => export const makeComment = ({url, event, content, tags = []}: CommentParams) =>
makeEvent(COMMENT, {content, tags: [...tags, ...tagEventForComment(event, url)]}) makeEvent(COMMENT, {content, tags: [...tags, ...app.use(Tags).tagEventForComment(event, url)]})
export const publishComment = ({relays, ...params}: CommentParams & {relays: string[]}) => export const publishComment = ({relays, ...params}: CommentParams & {relays: string[]}) =>
publishThunk({event: makeComment({url: relays[0], ...params}), relays}) app.use(Thunks).publish({event: makeComment({url: relays[0], ...params}), relays})
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type {Snippet} from "svelte" import type {Snippet} from "svelte"
import {pubkey} from "@welshman/app" import {pubkey} from "@app/welshman"
import Dialog from "@lib/components/Dialog.svelte" import Dialog from "@lib/components/Dialog.svelte"
import Landing from "@app/components/Landing.svelte" import Landing from "@app/components/Landing.svelte"
import Toast from "@app/components/Toast.svelte" import Toast from "@app/components/Toast.svelte"
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import {getTagValue, getAddress} from "@welshman/util" import {getTagValue, getAddress} from "@welshman/util"
import {pubkey} from "@welshman/app" import {pubkey} from "@app/welshman"
import Pen2 from "@assets/icons/pen-2.svg?dataurl" import Pen2 from "@assets/icons/pen-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"
+4 -3
View File
@@ -3,7 +3,8 @@
import {writable} from "svelte/store" import {writable} from "svelte/store"
import {randomId, HOUR} from "@welshman/lib" import {randomId, HOUR} from "@welshman/lib"
import {makeEvent, EVENT_TIME} from "@welshman/util" import {makeEvent, EVENT_TIME} from "@welshman/util"
import {publishThunk, waitForThunkError} from "@welshman/app" import {app} from "@app/welshman"
import {Thunks} 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 GallerySend from "@assets/icons/gallery-send.svg?dataurl"
@@ -107,8 +108,8 @@
} }
const event = makeEvent(EVENT_TIME, {content, tags}) const event = makeEvent(EVENT_TIME, {content, tags})
const calendarThunk = publishThunk({event, relays: [url]}) const calendarThunk = app.use(Thunks).publish({event, relays: [url]})
const error = await waitForThunkError(calendarThunk) const error = await calendarThunk.waitForError()
if (error) { if (error) {
return pushToast({theme: "error", message: error}) return pushToast({theme: "error", message: error})
+3 -1
View File
@@ -18,7 +18,9 @@
const h = getTagValue("h", event.tags) const h = getTagValue("h", event.tags)
</script> </script>
<Link class="cv col-3 card2 w-full cursor-pointer" href={makeCalendarPath(url, getAddress(event))}> <Link
class="cv col-3 card2 bg-alt w-full cursor-pointer shadow-md"
href={makeCalendarPath(url, getAddress(event))}>
<CalendarEventHeader {event} /> <CalendarEventHeader {event} />
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row"> <div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
<span class="whitespace-nowrap py-1 text-sm opacity-75"> <span class="whitespace-nowrap py-1 text-sm opacity-75">
+9 -13
View File
@@ -26,14 +26,10 @@
DIRECT_MESSAGE, DIRECT_MESSAGE,
DIRECT_MESSAGE_FILE, DIRECT_MESSAGE_FILE,
} from "@welshman/util" } from "@welshman/util"
import { import {app, pubkey} from "@app/welshman"
pubkey, import {Tags, Wraps, Thunks, MessagingRelayLists} from "@welshman/app"
tagPubkey,
sendWrapped, const messagingRelayListsByPubkey = app.use(MessagingRelayLists).index.$
mergeThunks,
loadMessagingRelayList,
messagingRelayListsByPubkey,
} from "@welshman/app"
import Danger from "@assets/icons/danger-triangle.svg?dataurl" import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import ArrowLeft from "@assets/icons/arrow-left.svg?dataurl" import ArrowLeft from "@assets/icons/arrow-left.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
@@ -96,7 +92,7 @@
const onSubmit = async (params: EventContent) => { const onSubmit = async (params: EventContent) => {
try { try {
const ptags = remove($pubkey!, pubkeys).map(tagPubkey) const ptags = remove($pubkey!, pubkeys).map(pk => app.use(Tags).tagPubkey(pk))
// Remove p tags since they result in forking the conversation // Remove p tags since they result in forking the conversation
params.tags = params.tags.filter(nthNe(0, "p")) params.tags = params.tags.filter(nthNe(0, "p"))
@@ -109,7 +105,7 @@
return return
} }
await sendWrapped({ await app.use(Wraps).publish({
event: makeDelete({event: eventToEdit, protect: false}), event: makeDelete({event: eventToEdit, protect: false}),
recipients: pubkeys, recipients: pubkeys,
pow: 16, pow: 16,
@@ -158,7 +154,7 @@
// Sleep 1 second between each one to make sure timestamps are distinct // Sleep 1 second between each one to make sure timestamps are distinct
const thunks = await Promise.all( const thunks = await Promise.all(
Array.from(enumerate(templates)).map(([i, event]) => Array.from(enumerate(templates)).map(([i, event]) =>
sendWrapped({ app.use(Wraps).publish({
event, event,
recipients: pubkeys, recipients: pubkeys,
delay: $userSettingsValues.send_delay + ms(i), delay: $userSettingsValues.send_delay + ms(i),
@@ -171,7 +167,7 @@
timeout: 30_000, timeout: 30_000,
children: { children: {
component: ThunkToast, component: ThunkToast,
props: {thunk: mergeThunks(thunks)}, props: {thunk: app.use(Thunks).merge(thunks)},
}, },
}) })
} finally { } finally {
@@ -234,7 +230,7 @@
onMount(() => { onMount(() => {
for (const pubkey of others) { for (const pubkey of others) {
loadMessagingRelayList(pubkey) app.use(MessagingRelayLists).load(pubkey)
} }
}) })
+3 -2
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {displayProfileByPubkey} from "@welshman/app" import {app} from "@app/welshman"
import {Profiles} from "@welshman/app"
import {slide} from "@lib/transition" import {slide} from "@lib/transition"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl" import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
@@ -21,7 +22,7 @@
<div <div
class="relative border-l-2 border-solid border-primary bg-base-300 px-2 py-1 pr-8" class="relative border-l-2 border-solid border-primary bg-base-300 px-2 py-1 pr-8"
transition:slide> transition:slide>
<p class="text-xs text-primary">{verb} @{displayProfileByPubkey(event.pubkey)}</p> <p class="text-xs text-primary">{verb} @{app.use(Profiles).display(event.pubkey).get()}</p>
{#key event.id} {#key event.id}
<NoteContentMinimal trimParent {event} /> <NoteContentMinimal trimParent {event} />
{/key} {/key}
+9 -5
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import {getRelaysFromList} from "@welshman/util" import {app, userRelayList} from "@app/welshman"
import {waitForThunkError, setMessagingRelays, userRelayList, setRelays} from "@welshman/app" import {RelayLists, MessagingRelayLists} from "@welshman/app"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl" import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
@@ -29,8 +29,10 @@
loading = true loading = true
try { try {
if (getRelaysFromList($userRelayList).length === 0) { if (($userRelayList?.urls() ?? []).length === 0) {
const error = await waitForThunkError(await setRelays(DEFAULT_RELAYS.map(r => ["r", r]))) const error = await (
await app.use(RelayLists).setRelays(DEFAULT_RELAYS.map(r => ["r", r]))
).waitForError()
if (error) { if (error) {
pushToast({theme: "error", message: error}) pushToast({theme: "error", message: error})
@@ -38,7 +40,9 @@
} }
} }
const error = await waitForThunkError(await setMessagingRelays(DEFAULT_MESSAGING_RELAYS)) const error = await (
await app.use(MessagingRelayLists).setUrls(DEFAULT_MESSAGING_RELAYS)
).waitForError()
if (error) { if (error) {
pushToast({theme: "error", message: error}) pushToast({theme: "error", message: error})
+3 -2
View File
@@ -3,7 +3,8 @@
import {page} from "$app/stores" import {page} from "$app/stores"
import {remove, uniq, formatTimestamp} from "@welshman/lib" import {remove, uniq, formatTimestamp} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {pubkey, loadMessagingRelayList} from "@welshman/app" import {app, pubkey} from "@app/welshman"
import {MessagingRelayLists} from "@welshman/app"
import {fade} from "@lib/transition" import {fade} from "@lib/transition"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ProfileName from "@app/components/ProfileName.svelte" import ProfileName from "@app/components/ProfileName.svelte"
@@ -28,7 +29,7 @@
onMount(() => { onMount(() => {
for (const pk of others) { for (const pk of others) {
loadMessagingRelayList(pk) app.use(MessagingRelayLists).load(pk)
} }
}) })
</script> </script>
+6 -5
View File
@@ -2,7 +2,8 @@
import {type Instance} from "tippy.js" import {type Instance} from "tippy.js"
import {hash, formatTimestampAsTime} from "@welshman/lib" import {hash, formatTimestampAsTime} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import {thunks, mergeThunks, pubkey, deriveProfileDisplay, sendWrapped} from "@welshman/app" import {app, thunks, pubkey} from "@app/welshman"
import {Thunks, Profiles, Wraps} from "@welshman/app"
import {isMobile} from "@lib/html" import {isMobile} from "@lib/html"
import MenuDots from "@assets/icons/menu-dots.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"
@@ -33,18 +34,18 @@
const {event, replyTo, canEdit, onEdit, pubkeys, showPubkey = false}: Props = $props() const {event, replyTo, canEdit, onEdit, pubkeys, showPubkey = false}: Props = $props()
const isOwn = event.pubkey === $pubkey const isOwn = event.pubkey === $pubkey
const profileDisplay = deriveProfileDisplay(event.pubkey) const profileDisplay = app.use(Profiles).display(event.pubkey).$
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id)) const thunk = app.use(Thunks).merge($thunks.filter(t => t.event.id === event.id))
const [_, colorValue] = colors[hash(event.pubkey) % colors.length] const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
const reply = () => replyTo(event) const reply = () => replyTo(event)
const edit = canEdit?.(event) ? () => onEdit?.(event) : undefined const edit = canEdit?.(event) ? () => onEdit?.(event) : undefined
const deleteReaction = (event: TrustedEvent) => const deleteReaction = (event: TrustedEvent) =>
sendWrapped({event: makeDelete({event, protect: false}), recipients: pubkeys, pow: 16}) app.use(Wraps).publish({event: makeDelete({event, protect: false}), recipients: pubkeys, pow: 16})
const createReaction = (template: EventContent) => const createReaction = (template: EventContent) =>
sendWrapped({ app.use(Wraps).publish({
event: makeReaction({event, protect: false, ...template}), event: makeReaction({event, protect: false, ...template}),
recipients: pubkeys, recipients: pubkeys,
pow: 16, pow: 16,
@@ -1,7 +1,8 @@
<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} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {sendWrapped} from "@welshman/app" import {app} from "@app/welshman"
import {Wraps} from "@welshman/app"
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl" 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"
@@ -15,7 +16,7 @@
const {event, pubkeys}: Props = $props() const {event, pubkeys}: Props = $props()
const onEmoji = (emoji: NativeEmoji) => const onEmoji = (emoji: NativeEmoji) =>
sendWrapped({ app.use(Wraps).publish({
event: makeReaction({event, content: emoji.unicode, protect: false}), event: makeReaction({event, content: emoji.unicode, protect: false}),
recipients: pubkeys, recipients: pubkeys,
pow: 16, pow: 16,
@@ -1,7 +1,8 @@
<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} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {sendWrapped} from "@welshman/app" import {app} from "@app/welshman"
import {Wraps} from "@welshman/app"
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl" import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
import Pen from "@assets/icons/pen.svg?dataurl" import Pen from "@assets/icons/pen.svg?dataurl"
import Reply from "@assets/icons/reply-2.svg?dataurl" import Reply from "@assets/icons/reply-2.svg?dataurl"
@@ -28,7 +29,7 @@
const onEmoji = ((event: TrustedEvent, pubkeys: string[], emoji: NativeEmoji) => { const onEmoji = ((event: TrustedEvent, pubkeys: string[], emoji: NativeEmoji) => {
history.back() history.back()
sendWrapped({ app.use(Wraps).publish({
event: makeReaction({event, content: emoji.unicode, protect: false}), event: makeReaction({event, content: emoji.unicode, protect: false}),
recipients: pubkeys, recipients: pubkeys,
pow: 16, pow: 16,
+3 -2
View File
@@ -4,7 +4,8 @@
import {writable} from "svelte/store" import {writable} from "svelte/store"
import {tryCatch, uniq} from "@welshman/lib" import {tryCatch, uniq} from "@welshman/lib"
import {fromNostrURI} from "@welshman/util" import {fromNostrURI} from "@welshman/util"
import {loadMessagingRelayList} from "@welshman/app" import {app} from "@app/welshman"
import {MessagingRelayLists} 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 Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -34,7 +35,7 @@
let pubkeys: string[] = $state([]) let pubkeys: string[] = $state([])
$effect(() => { $effect(() => {
pubkeys.forEach(pubkey => loadMessagingRelayList(pubkey)) pubkeys.forEach(pubkey => app.use(MessagingRelayLists).load(pubkey))
}) })
onMount(() => { onMount(() => {
+1 -1
View File
@@ -2,7 +2,7 @@
import {uniq} from "@welshman/lib" import {uniq} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import {getTagValue, getTagValues, getAddress} from "@welshman/util" import {getTagValue, getTagValues, getAddress} from "@welshman/util"
import {pubkey} from "@welshman/app" import {pubkey} from "@app/welshman"
import Pen2 from "@assets/icons/pen-2.svg?dataurl" import Pen2 from "@assets/icons/pen-2.svg?dataurl"
import {normalizeTopic} from "@lib/util" import {normalizeTopic} from "@lib/util"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
+4 -3
View File
@@ -2,7 +2,8 @@
import type {Snippet} from "svelte" import type {Snippet} from "svelte"
import {removeUndefined, randomId, uniq} from "@welshman/lib" import {removeUndefined, randomId, uniq} from "@welshman/lib"
import {makeEvent, CLASSIFIED} from "@welshman/util" import {makeEvent, CLASSIFIED} from "@welshman/util"
import {publishThunk, waitForThunkError} from "@welshman/app" import {app} from "@app/welshman"
import {Thunks} from "@welshman/app"
import {isMobile, preventDefault} from "@lib/html" import {isMobile, preventDefault} from "@lib/html"
import {normalizeTopic} from "@lib/util" import {normalizeTopic} from "@lib/util"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
@@ -118,12 +119,12 @@
} }
} }
const classifiedThunk = publishThunk({ const classifiedThunk = app.use(Thunks).publish({
relays: [url], relays: [url],
event: makeEvent(CLASSIFIED, {content, tags}), event: makeEvent(CLASSIFIED, {content, tags}),
}) })
const error = await waitForThunkError(classifiedThunk) const error = await classifiedThunk.waitForError()
if (error) { if (error) {
return pushToast({theme: "error", message: error}) return pushToast({theme: "error", message: error})
+1 -1
View File
@@ -25,7 +25,7 @@
</script> </script>
<Link <Link
class="cv col-2 card2 w-full cursor-pointer" class="cv col-2 card2 bg-alt w-full cursor-pointer shadow-xl"
href={makeClassifiedPath(url, getAddress(event))}> href={makeClassifiedPath(url, getAddress(event))}>
{#if title} {#if title}
<div class="flex w-full items-center justify-between gap-2"> <div class="flex w-full items-center justify-between gap-2">
+1 -1
View File
@@ -3,7 +3,7 @@
</script> </script>
<code <code
class="w-full overflow-auto whitespace-pre rounded bg-neutral px-1 text-neutral-content text-sm" class="w-full overflow-auto whitespace-pre rounded bg-neutral px-1 text-neutral-content"
class:block={isBlock}> class:block={isBlock}>
{value.trim()} {value.trim()}
</code> </code>
+2 -3
View File
@@ -77,8 +77,7 @@
<span class="loading loading-spinner"></span> <span class="loading loading-spinner"></span>
</div> </div>
{:then preview} {:then preview}
<div <div class="bg-alt flex max-w-xl flex-col leading-normal">
class="border border-solid border-base-content/20 flex max-w-xl flex-col leading-normal rounded-box">
{#if preview.image && !hideImage} {#if preview.image && !hideImage}
<img <img
alt="" alt=""
@@ -93,7 +92,7 @@
</div> </div>
</div> </div>
{:catch} {:catch}
<p class="border border-solid border-base-content/20 p-12 text-center leading-normal"> <p class="bg-alt p-12 text-center leading-normal">
Unable to load a preview for {url} Unable to load a preview for {url}
</p> </p>
{/await} {/await}
@@ -9,7 +9,7 @@
tagsFromIMeta, tagsFromIMeta,
makeBlossomAuthEvent, makeBlossomAuthEvent,
} from "@welshman/util" } from "@welshman/util"
import {signer} from "@welshman/app" import {signer} from "@app/welshman"
import LinkRound from "@assets/icons/link-round.svg?dataurl" import LinkRound from "@assets/icons/link-round.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
+3 -2
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import {removeUndefined} from "@welshman/lib" import {removeUndefined} from "@welshman/lib"
import type {ProfilePointer} from "@welshman/content" import type {ProfilePointer} from "@welshman/content"
import {deriveProfileDisplay} from "@welshman/app" import {app} from "@app/welshman"
import {Profiles} from "@welshman/app"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ProfileDetail from "@app/components/ProfileDetail.svelte" import ProfileDetail from "@app/components/ProfileDetail.svelte"
import {pushModal} from "@app/modal" import {pushModal} from "@app/modal"
@@ -13,7 +14,7 @@
const {value, url}: Props = $props() const {value, url}: Props = $props()
const display = deriveProfileDisplay(value.pubkey, removeUndefined([url])) const display = app.use(Profiles).display(value.pubkey, removeUndefined([url])).$
const openProfile = () => pushModal(ProfileDetail, {pubkey: value.pubkey, url}) const openProfile = () => pushModal(ProfileDetail, {pubkey: value.pubkey, url})
</script> </script>
+2 -12
View File
@@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import cx from "classnames"
import {fromNostrURI} from "@welshman/util" import {fromNostrURI} from "@welshman/util"
import {nthEq} from "@welshman/lib" import {nthEq} from "@welshman/lib"
import { import {
@@ -38,11 +37,10 @@
interface Props { interface Props {
event: any event: any
trimParent?: boolean trimParent?: boolean
singleLine?: boolean
url?: string url?: string
} }
const {event, trimParent = false, singleLine = false, url}: Props = $props() const {event, trimParent = false, url}: Props = $props()
const fullContent = parse(event) const fullContent = parse(event)
@@ -106,18 +104,10 @@
</p> </p>
</div> </div>
{:else} {:else}
<div <div class="overflow-hidden text-ellipsis wrap-break-word">
class={cx(
"overflow-hidden text-ellipsis",
singleLine ? "whitespace-nowrap" : "wrap-break-word",
)}>
{#each shortContent as parsed, i} {#each shortContent as parsed, i}
{#if isNewline(parsed)} {#if isNewline(parsed)}
{#if singleLine}
{" "}
{:else}
<ContentNewline value={parsed.value} /> <ContentNewline value={parsed.value} />
{/if}
{:else if isTopic(parsed)} {:else if isTopic(parsed)}
<ContentTopic value={parsed.value} /> <ContentTopic value={parsed.value} />
{:else if isEmoji(parsed)} {:else if isEmoji(parsed)}
+1 -5
View File
@@ -53,11 +53,7 @@
<NoteContentMinimal trimParent {url} event={$quote} /> <NoteContentMinimal trimParent {url} event={$quote} />
</div> </div>
{:else} {:else}
<NoteCard <NoteCard noShadow event={$quote} {url} class="bg-alt rounded-box p-4">
noShadow
event={$quote}
{url}
class="border border-solid border-base-content/20 rounded-box p-4">
<NoteContentMinimal {url} event={$quote} /> <NoteContentMinimal {url} event={$quote} />
</NoteCard> </NoteCard>
{/if} {/if}
+1 -1
View File
@@ -4,7 +4,7 @@
import type {Writable} from "svelte/store" import type {Writable} from "svelte/store"
import type {Instance} from "tippy.js" import type {Instance} from "tippy.js"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import {createSearch} from "@welshman/app" import {createSearch} from "@app/welshman"
import {currencyOptions, displayCurrency} from "@lib/currency" import {currencyOptions, displayCurrency} from "@lib/currency"
import Suggestions from "@lib/components/Suggestions.svelte" import Suggestions from "@lib/components/Suggestions.svelte"
import CurrencySuggestion from "@app/components/CurrencySuggestion.svelte" import CurrencySuggestion from "@app/components/CurrencySuggestion.svelte"
@@ -1,79 +0,0 @@
<script lang="ts">
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Field from "@lib/components/Field.svelte"
import Icon from "@lib/components/Icon.svelte"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalTitle from "@lib/components/ModalTitle.svelte"
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import InputList from "@lib/components/InputList.svelte"
import RelayName from "@app/components/RelayName.svelte"
import {setFeaturedContent} from "@app/featured"
import {pushToast} from "@app/toast"
type Props = {
url: string
initial: string[]
}
const {url, initial}: Props = $props()
let content = $state([...initial])
let loading = $state(false)
const back = () => history.back()
const submit = async () => {
loading = true
try {
const error = await setFeaturedContent(url, content)
if (error) {
pushToast({theme: "error", message: error})
} else {
pushToast({message: "Featured content updated!"})
back()
}
} finally {
loading = false
}
}
</script>
<Modal>
<ModalBody>
<ModalHeader>
<ModalTitle>Featured Content</ModalTitle>
<ModalSubtitle>on <RelayName {url} class="text-primary" /></ModalSubtitle>
</ModalHeader>
<Field>
{#snippet info()}
<p>
Each entry is shown on the space's About page. Links will be fetched and displayed
automatically.
</p>
{/snippet}
{#snippet input()}
<InputList bind:value={content} placeholder="URL or nevent...">
{#snippet addLabel()}
Add content
{/snippet}
</InputList>
{/snippet}
</Field>
</ModalBody>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
Go back
</Button>
<Button class="btn btn-primary" onclick={submit} disabled={loading}>
<Spinner {loading}>Save changes</Spinner>
</Button>
</ModalFooter>
</Modal>
+1 -1
View File
@@ -5,7 +5,7 @@
import {load} from "@welshman/net" import {load} from "@welshman/net"
import {deriveArray, deriveEventsById} from "@welshman/store" import {deriveArray, deriveEventsById} from "@welshman/store"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {repository} from "@welshman/app" import {repository} from "@app/welshman"
import {deriveChecked} from "@app/notifications" import {deriveChecked} from "@app/notifications"
import Reply from "@assets/icons/reply-2.svg?dataurl" import Reply from "@assets/icons/reply-2.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
+1 -1
View File
@@ -4,7 +4,7 @@
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 {tracker} from "@welshman/app" import {tracker} from "@app/welshman"
import FileText from "@assets/icons/file-text.svg?dataurl" import FileText from "@assets/icons/file-text.svg?dataurl"
import Copy from "@assets/icons/copy.svg?dataurl" import Copy from "@assets/icons/copy.svg?dataurl"
import UserCircle from "@assets/icons/user-circle.svg?dataurl" import UserCircle from "@assets/icons/user-circle.svg?dataurl"
+3 -2
View File
@@ -4,7 +4,8 @@
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {COMMENT, ManagementMethod} from "@welshman/util" import {COMMENT, ManagementMethod} from "@welshman/util"
import {pubkey, repository, relaysByUrl, manageRelay} from "@welshman/app" import {app, pubkey, repository, relaysByUrl} from "@app/welshman"
import {RelayManagement} from "@welshman/app"
import ShareCircle from "@assets/icons/share-circle.svg?dataurl" import ShareCircle from "@assets/icons/share-circle.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl" import Code2 from "@assets/icons/code-2.svg?dataurl"
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl" import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
@@ -56,7 +57,7 @@
title: `Delete ${noun}`, title: `Delete ${noun}`,
message: `Are you sure you want to delete this ${noun.toLowerCase()} from the space?`, message: `Are you sure you want to delete this ${noun.toLowerCase()} from the space?`,
confirm: async () => { confirm: async () => {
const {error} = await manageRelay(url, { const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.BanEvent, method: ManagementMethod.BanEvent,
params: [event.id], params: [event.id],
}) })
+4 -3
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import {writable} from "svelte/store" import {writable} from "svelte/store"
import {makeEvent, ZAP_GOAL} from "@welshman/util" import {makeEvent, ZAP_GOAL} from "@welshman/util"
import {publishThunk, waitForThunkError} from "@welshman/app" import {app} from "@app/welshman"
import {Thunks} 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 Paperclip from "@assets/icons/paperclip-2.svg?dataurl"
import Bolt from "@assets/icons/bolt.svg?dataurl" import Bolt from "@assets/icons/bolt.svg?dataurl"
@@ -93,12 +94,12 @@
tags.push(["h", h]) tags.push(["h", h])
} }
const goalThunk = publishThunk({ const goalThunk = app.use(Thunks).publish({
relays: [url], relays: [url],
event: makeEvent(ZAP_GOAL, {content: title, tags}), event: makeEvent(ZAP_GOAL, {content: title, tags}),
}) })
const error = await waitForThunkError(goalThunk) const error = await goalThunk.waitForError()
if (error) { if (error) {
return pushToast({theme: "error", message: error}) return pushToast({theme: "error", message: error})
+3 -1
View File
@@ -20,7 +20,9 @@
const h = getTagValue("h", event.tags) const h = getTagValue("h", event.tags)
</script> </script>
<Link class="cv col-2 card2 w-full cursor-pointer" href={makeGoalPath(url, event.id)}> <Link
class="cv col-2 card2 bg-alt w-full cursor-pointer shadow-md"
href={makeGoalPath(url, event.id)}>
<p class="text-2xl">{event.content}</p> <p class="text-2xl">{event.content}</p>
<Content <Content
event={{content: summary, tags: event.tags}} event={{content: summary, tags: event.tags}}
+13 -9
View File
@@ -2,8 +2,10 @@
import {now, DAY, uniq, sum} from "@welshman/lib" import {now, DAY, uniq, sum} from "@welshman/lib"
import type {Zap, TrustedEvent} from "@welshman/util" import type {Zap, TrustedEvent} from "@welshman/util"
import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util" import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util"
import {deriveItemsByKey, deriveArray} from "@welshman/store" import {derived} from "svelte/store"
import {repository, getValidZap} from "@welshman/app" import {deriveEvents} from "@welshman/store"
import {app, repository} from "@app/welshman"
import {Zappers} from "@welshman/app"
import Bolt from "@assets/icons/bolt.svg?dataurl" 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"
@@ -16,13 +18,15 @@
const {url, event, ...props}: Props = $props() const {url, event, ...props}: Props = $props()
const zaps = deriveArray( // Validated zaps for this goal. `validZapReceipts` is a reactive Projection
deriveItemsByKey<Zap>({ // (resolves recipient zappers from loaded profiles); we re-derive it whenever
repository, // the set of ZAP_RESPONSE events in the repository changes.
getKey: zap => zap.response.id, const zapReceipts = deriveEvents({repository, filters: [{kinds: [ZAP_RESPONSE], "#e": [event.id]}]})
filters: [{kinds: [ZAP_RESPONSE], "#e": [event.id]}], const zaps = derived(
eventToItem: (response: TrustedEvent) => getValidZap(response, event), zapReceipts,
}), ($receipts: TrustedEvent[], set) =>
app.use(Zappers).validZapReceipts($receipts, event).$.subscribe(set),
[] as Zap[],
) )
const goalAmount = parseInt(getTagValue("amount", event.tags) || "0") const goalAmount = parseInt(getTagValue("amount", event.tags) || "0")
+1 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {createSearch} from "@welshman/app" import {createSearch} from "@app/welshman"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Magnifier from "@assets/icons/magnifier.svg?dataurl" import Magnifier from "@assets/icons/magnifier.svg?dataurl"
+1 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {session} from "@welshman/app" import {session} from "@app/welshman"
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 AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import CheckCircle from "@assets/icons/check-circle.svg?dataurl" import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
+3 -2
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import {deriveZapperForPubkey} from "@welshman/app" import {app} from "@app/welshman"
import {Zappers} from "@welshman/app"
import AltArrowLeft from "@assets/icons/alt-arrow-left.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"
@@ -12,7 +13,7 @@
const {pubkey} = $props() const {pubkey} = $props()
const zapper = deriveZapperForPubkey(pubkey) const zapper = app.use(Zappers).forPubkey(pubkey).$
const back = () => history.back() const back = () => history.back()
</script> </script>
+8 -2
View File
@@ -1,8 +1,14 @@
<script lang="ts"> <script lang="ts">
import {Client} from "@pomade/core" import {Client} from "@pomade/core"
import {getPubkey} from "@welshman/util" import {getPubkey} from "@welshman/util"
import type {SessionPomade} from "@welshman/app" import {session} from "@app/welshman"
import {session} from "@welshman/app" // TODO(welshman-migration): `SessionPomade` was removed from @welshman/app; the
// new generic Session nests its descriptor under `.data` ({clientOptions, email}),
// whereas this code destructures `email`/`clientOptions` from the top level.
// Confirm whether flotilla's FlotillaSession still surfaces these at the top
// level (loginWithPomade stores them under `.data` via toSession) and adjust the
// destructuring accordingly. Local type kept loose to avoid a broken import.
type SessionPomade = {email: string; clientOptions: {secret: string; peers: any}}
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl" import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
+7 -2
View File
@@ -1,7 +1,12 @@
<script lang="ts"> <script lang="ts">
import {Client} from "@pomade/core" import {Client} from "@pomade/core"
import type {SessionPomade} from "@welshman/app" import {session} from "@app/welshman"
import {session} from "@welshman/app" // TODO(welshman-migration): `SessionPomade` was removed from @welshman/app; the
// new generic Session nests its descriptor under `.data` ({clientOptions, email}),
// whereas this code destructures from the top level. Confirm whether flotilla's
// FlotillaSession still surfaces these at the top level and adjust accordingly.
// Local type kept loose to avoid a broken import.
type SessionPomade = {email: string; clientOptions: {peers: any}}
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl" import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
+5 -5
View File
@@ -2,7 +2,7 @@
import {onMount} from "svelte" import {onMount} from "svelte"
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 FlotillaSession, makeNip07Session, makeNip55Session} from "@app/welshman"
import Widget from "@assets/icons/widget-4.svg?dataurl" import Widget from "@assets/icons/widget-4.svg?dataurl"
import Letter from "@assets/icons/letter.svg?dataurl" import Letter from "@assets/icons/letter.svg?dataurl"
import Cpu from "@assets/icons/cpu-bolt.svg?dataurl" import Cpu from "@assets/icons/cpu-bolt.svg?dataurl"
@@ -32,8 +32,8 @@
const signUp = () => pushModal(SignUp) const signUp = () => pushModal(SignUp)
const onSuccess = async (session: Session) => { const onSuccess = async (session: FlotillaSession, pubkey: string) => {
addSession(session) addSession({...session, pubkey})
setChecked("*") setChecked("*")
clearModals() clearModals()
} }
@@ -45,7 +45,7 @@
const pubkey = await getNip07()?.getPublicKey() const pubkey = await getNip07()?.getPublicKey()
if (pubkey) { if (pubkey) {
await onSuccess(makeNip07Session(pubkey)) await onSuccess(makeNip07Session(pubkey), pubkey)
} else { } else {
pushToast({ pushToast({
theme: "error", theme: "error",
@@ -65,7 +65,7 @@
const pubkey = await signer.getPubkey() const pubkey = await signer.getPubkey()
if (pubkey) { if (pubkey) {
await onSuccess(makeNip55Session(pubkey, app.packageName)) await onSuccess(makeNip55Session(pubkey, app.packageName), pubkey)
} else { } else {
pushToast({ pushToast({
theme: "error", theme: "error",
+1 -1
View File
@@ -4,7 +4,7 @@
import type {Nip46ResponseWithResult} from "@welshman/signer" import type {Nip46ResponseWithResult} from "@welshman/signer"
import {Nip46Broker} from "@welshman/signer" import {Nip46Broker} from "@welshman/signer"
import {makeSecret} from "@welshman/util" import {makeSecret} from "@welshman/util"
import {loginWithNip01, loginWithNip46} from "@welshman/app" import {loginWithNip01, loginWithNip46} from "@app/welshman"
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"
+2 -2
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import {bytesToHex} from "@welshman/lib" import {bytesToHex} from "@welshman/lib"
import {loginWithNip01} from "@welshman/app" import {loginWithNip01} from "@app/welshman"
import {decrypt} from "nostr-tools/nip49" import {decrypt} from "nostr-tools/nip49"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import {nsecDecode} from "@lib/util" import {nsecDecode} from "@lib/util"
@@ -56,7 +56,7 @@
}) })
} }
loginWithNip01(secret) await loginWithNip01(secret)
setChecked("*") setChecked("*")
clearModals() clearModals()
} catch (e) { } catch (e) {
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import {Capacitor} from "@capacitor/core" import {Capacitor} from "@capacitor/core"
import {pubkey} from "@welshman/app" import {pubkey} from "@app/welshman"
import Server from "@assets/icons/server.svg?dataurl" import Server from "@assets/icons/server.svg?dataurl"
import GalleryMinimalistic from "@assets/icons/gallery-minimalistic.svg?dataurl" import GalleryMinimalistic from "@assets/icons/gallery-minimalistic.svg?dataurl"
import Shield from "@assets/icons/shield-minimalistic.svg?dataurl" import Shield from "@assets/icons/shield-minimalistic.svg?dataurl"
@@ -3,8 +3,10 @@
import {sum} from "@welshman/lib" import {sum} from "@welshman/lib"
import type {Zap, TrustedEvent} from "@welshman/util" import type {Zap, TrustedEvent} from "@welshman/util"
import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util" import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util"
import {deriveItemsByKey, deriveArray} from "@welshman/store" import {derived} from "svelte/store"
import {repository, getValidZap} from "@welshman/app" import {deriveEvents} from "@welshman/store"
import {app, repository} from "@app/welshman"
import {Zappers} from "@welshman/app"
import Bolt from "@assets/icons/bolt.svg?dataurl" import Bolt from "@assets/icons/bolt.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ContentMinimal from "@app/components/ContentMinimal.svelte" import ContentMinimal from "@app/components/ContentMinimal.svelte"
@@ -14,13 +16,14 @@
const content = getTagValue("summary", props.event.tags) const content = getTagValue("summary", props.event.tags)
const fakeEvent = {content, tags: props.event.tags} const fakeEvent = {content, tags: props.event.tags}
const zaps = deriveArray( // Validated zaps for this goal (reactive Projection, re-derived as the set of
deriveItemsByKey<Zap>({ // ZAP_RESPONSE events in the repository changes).
repository, const zapReceipts = deriveEvents({repository, filters: [{kinds: [ZAP_RESPONSE], "#e": [props.event.id]}]})
getKey: zap => zap.response.id, const zaps = derived(
filters: [{kinds: [ZAP_RESPONSE], "#e": [props.event.id]}], zapReceipts,
eventToItem: (response: TrustedEvent) => getValidZap(response, props.event), ($receipts: TrustedEvent[], set) =>
}), app.use(Zappers).validZapReceipts($receipts, props.event).$.subscribe(set),
[] as Zap[],
) )
const goalAmount = parseInt(getTagValue("amount", props.event.tags) || "0") const goalAmount = parseInt(getTagValue("amount", props.event.tags) || "0")
+1 -1
View File
@@ -40,7 +40,7 @@
}) })
</script> </script>
<NoteCard {event} {url} class="cv card2"> <NoteCard {event} {url} class="cv card2 bg-alt">
<NoteContent {event} expandMode="inline" /> <NoteContent {event} expandMode="inline" />
<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">
+1 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import {Client} from "@pomade/core" import {Client} from "@pomade/core"
import type {SessionPomade} from "@welshman/app" import type {SessionPomade} from "@welshman/app"
import {session} from "@welshman/app" import {session} from "@app/welshman"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl" import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
@@ -1,7 +1,12 @@
<script lang="ts"> <script lang="ts">
import {Client} from "@pomade/core" import {Client} from "@pomade/core"
import {session} from "@welshman/app" import {session} from "@app/welshman"
import type {SessionPomade} from "@welshman/app" // TODO(welshman-migration): `SessionPomade` was removed from @welshman/app; the
// new generic Session nests its descriptor under `.data` ({clientOptions, email}),
// whereas this code destructures `email` from the top level. Confirm whether
// flotilla's FlotillaSession still surfaces `email` at the top level and adjust.
// Local type kept loose to avoid a broken import.
type SessionPomade = {email: string}
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Key from "@assets/icons/key.svg?dataurl" import Key from "@assets/icons/key.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
+4 -3
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import {insertAt, now, randomId, removeAt, removeUndefined} from "@welshman/lib" import {insertAt, now, randomId, removeAt, removeUndefined} from "@welshman/lib"
import {makeEvent, POLL} from "@welshman/util" import {makeEvent, POLL} from "@welshman/util"
import {publishThunk, waitForThunkError} from "@welshman/app" import {Thunks} from "@welshman/app"
import {app} from "@app/welshman"
import {isMobile, preventDefault} from "@lib/html" import {isMobile, preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import HamburgerMenu from "@assets/icons/hamburger-menu.svg?dataurl" import HamburgerMenu from "@assets/icons/hamburger-menu.svg?dataurl"
@@ -142,12 +143,12 @@
tags.push(PROTECTED) tags.push(PROTECTED)
} }
const pollThunk = publishThunk({ const pollThunk = app.use(Thunks).publish({
relays: [url], relays: [url],
event: makeEvent(POLL, {content: title.trim(), tags}), event: makeEvent(POLL, {content: title.trim(), tags}),
}) })
const error = await waitForThunkError(pollThunk) const error = await pollThunk.waitForError()
if (error) { if (error) {
return pushToast({theme: "error", message: error}) return pushToast({theme: "error", message: error})
+7 -5
View File
@@ -2,7 +2,9 @@
import {onDestroy} from "svelte" import {onDestroy} from "svelte"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {POLL_RESPONSE} from "@welshman/util" import {POLL_RESPONSE} from "@welshman/util"
import {pubkey, publishThunk, abortThunk} from "@welshman/app" import type {Thunk} from "@welshman/app"
import {Thunks} from "@welshman/app"
import {pubkey, app} from "@app/welshman"
import {formatTimestampRelative} from "@welshman/lib" import {formatTimestampRelative} from "@welshman/lib"
import {deriveEvents} from "@app/repository" import {deriveEvents} from "@app/repository"
import {pushToast} from "@app/toast" import {pushToast} from "@app/toast"
@@ -50,7 +52,7 @@
const publishSelection = (selection: string[]) => { const publishSelection = (selection: string[]) => {
if (activeThunk) { if (activeThunk) {
abortThunk(activeThunk) activeThunk.abort()
} }
if (selection.length === 0) { if (selection.length === 0) {
@@ -58,7 +60,7 @@
return return
} }
activeThunk = publishThunk({ activeThunk = app.use(Thunks).publish({
relays: [url], relays: [url],
event: makePollResponse({event, selectedIds: selection}), event: makePollResponse({event, selectedIds: selection}),
delay: publishDelay, delay: publishDelay,
@@ -92,7 +94,7 @@
} }
let selectedIds = $state<string[]>([]) let selectedIds = $state<string[]>([])
let activeThunk: ReturnType<typeof publishThunk> | undefined let activeThunk: Thunk | undefined
$effect(() => { $effect(() => {
if (ownResponse) { if (ownResponse) {
@@ -102,7 +104,7 @@
onDestroy(() => { onDestroy(() => {
if (activeThunk) { if (activeThunk) {
abortThunk(activeThunk) activeThunk.abort()
} }
}) })
</script> </script>
+1 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {Client} from "@pomade/core" import {Client} from "@pomade/core"
import {session, isPomadeSession} from "@welshman/app" import {session, isPomadeSession} from "@app/welshman"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl" import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import {fly} from "@lib/transition" import {fly} from "@lib/transition"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
+6 -6
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type {Snippet} from "svelte" import type {Snippet} from "svelte"
import {userProfile} from "@welshman/app" import {userProfile} from "@app/welshman"
import Letter from "@assets/icons/letter.svg?dataurl" import Letter from "@assets/icons/letter.svg?dataurl"
import Magnifier from "@assets/icons/magnifier.svg?dataurl" import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import Widget from "@assets/icons/widget-4.svg?dataurl" import Widget from "@assets/icons/widget-4.svg?dataurl"
@@ -33,7 +33,7 @@
</script> </script>
<div <div
class="ml-sai mt-sai mb-sai relative z-popover isolate hidden w-14 shrink-0 bg-base-200 pt-2 md:block border-r border-solid border-base-content/15 dark:border-base-content/10"> class="ml-sai mt-sai mb-sai relative z-popover isolate hidden w-14 shrink-0 bg-base-200 pt-2 md:block">
<div class="flex h-full flex-col" class:justify-between={PLATFORM_RELAYS.length === 0}> <div class="flex h-full flex-col" class:justify-between={PLATFORM_RELAYS.length === 0}>
<PrimaryNavSpaces /> <PrimaryNavSpaces />
{#if PLATFORM_RELAYS.length > 0} {#if PLATFORM_RELAYS.length > 0}
@@ -41,8 +41,8 @@
{/if} {/if}
<div class="flex flex-col"> <div class="flex flex-col">
<PrimaryNavItem title="Settings" href="/settings/profile" prefix="/settings"> <PrimaryNavItem title="Settings" href="/settings/profile" prefix="/settings">
{#if $userProfile?.picture} {#if $userProfile?.picture()}
<ImageIcon alt="Settings" src={$userProfile?.picture} class="rounded-full" size={10} /> <ImageIcon alt="Settings" src={$userProfile?.picture()} class="rounded-full" size={10} />
{:else} {:else}
<ImageIcon alt="Settings" src={UserRounded} class="rounded-full" size={8} /> <ImageIcon alt="Settings" src={UserRounded} class="rounded-full" size={8} />
{/if} {/if}
@@ -86,8 +86,8 @@
{/if} {/if}
</div> </div>
<PrimaryNavItem title="Settings" onclick={showSettingsMenu}> <PrimaryNavItem title="Settings" onclick={showSettingsMenu}>
{#if $userProfile?.picture} {#if $userProfile?.picture()}
<ImageIcon alt="Settings" src={$userProfile?.picture} size={10} class="rounded-full" /> <ImageIcon alt="Settings" src={$userProfile?.picture()} size={10} class="rounded-full" />
{:else} {:else}
<ImageIcon alt="Settings" src={Settings} size={8} class="rounded-full" /> <ImageIcon alt="Settings" src={Settings} size={8} class="rounded-full" />
{/if} {/if}
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import {deriveRelayDisplay} from "@welshman/app" import {Relays} from "@welshman/app"
import {app} from "@app/welshman"
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte" import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
import RelayIcon from "@app/components/RelayIcon.svelte" import RelayIcon from "@app/components/RelayIcon.svelte"
import {makeSpacePath, goToSpace} from "@app/routes" import {makeSpacePath, goToSpace} from "@app/routes"
@@ -15,7 +16,7 @@
const path = makeSpacePath(url) const path = makeSpacePath(url)
const display = $derived(deriveRelayDisplay(url)) const display = $derived(app.use(Relays).display(url).$)
</script> </script>
<PrimaryNavItem <PrimaryNavItem
+8 -7
View File
@@ -1,8 +1,9 @@
<script lang="ts"> <script lang="ts">
import * as nip19 from "nostr-tools/nip19" import * as nip19 from "nostr-tools/nip19"
import {removeUndefined} from "@welshman/lib" import {removeUndefined} from "@welshman/lib"
import {displayPubkey} from "@welshman/util" import {displayPubkey, displayNip05} from "@welshman/util"
import {deriveHandleForPubkey, displayHandle, deriveProfileDisplay} from "@welshman/app" import {Handles, Profiles} from "@welshman/app"
import {app} from "@app/welshman"
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 ProfileCircle from "@app/components/ProfileCircle.svelte" import ProfileCircle from "@app/components/ProfileCircle.svelte"
@@ -23,8 +24,8 @@
const {pubkey, url, showPubkey, inert, avatarSize = 10}: Props = $props() const {pubkey, url, showPubkey, inert, avatarSize = 10}: Props = $props()
const relays = removeUndefined([url]) const relays = removeUndefined([url])
const profileDisplay = deriveProfileDisplay(pubkey, relays) const profileDisplay = app.use(Profiles).display(pubkey, relays).$
const handle = deriveHandleForPubkey(pubkey) const handle = app.use(Handles).forPubkey(pubkey).$
const openProfile = () => { const openProfile = () => {
pushModal(ProfileDetail, {pubkey, url}) pushModal(ProfileDetail, {pubkey, url})
@@ -33,7 +34,7 @@
const copyPubkey = () => clip(nip19.npubEncode(pubkey)) const copyPubkey = () => clip(nip19.npubEncode(pubkey))
</script> </script>
<div class="flex max-w-full items-start gap-2"> <div class="flex max-w-full items-start gap-3">
{#if inert} {#if inert}
<span class="py-1"> <span class="py-1">
<ProfileCircle {pubkey} size={avatarSize} /> <ProfileCircle {pubkey} size={avatarSize} />
@@ -46,7 +47,7 @@
<div class="flex min-w-0 flex-col"> <div class="flex min-w-0 flex-col">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
{#if inert} {#if inert}
<span class="text-bold overflow-hidden text-ellipsis whitespace-nowrap"> <span class="text-bold overflow-hidden text-ellipsis">
{$profileDisplay} {$profileDisplay}
</span> </span>
{:else} {:else}
@@ -58,7 +59,7 @@
</div> </div>
{#if $handle} {#if $handle}
<div class="overflow-hidden text-ellipsis text-sm opacity-75"> <div class="overflow-hidden text-ellipsis text-sm opacity-75">
{displayHandle($handle)} {displayNip05($handle?.nip05)}
</div> </div>
{/if} {/if}
{#if showPubkey} {#if showPubkey}
+3 -2
View File
@@ -6,7 +6,8 @@
import {deriveEventsDesc, deriveEventsById} from "@welshman/store" import {deriveEventsDesc, deriveEventsById} from "@welshman/store"
import {formatTimestampRelative} from "@welshman/lib" import {formatTimestampRelative} from "@welshman/lib"
import {NOTE, ROOMS, COMMENT, MESSAGE} from "@welshman/util" import {NOTE, ROOMS, COMMENT, MESSAGE} from "@welshman/util"
import {repository, loadRelayList} from "@welshman/app" import {RelayLists} from "@welshman/app"
import {repository, app} from "@app/welshman"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ProfileSpaces from "@app/components/ProfileSpaces.svelte" import ProfileSpaces from "@app/components/ProfileSpaces.svelte"
import {deriveGroupList, getSpaceUrlsFromGroupList} from "@app/groups" import {deriveGroupList, getSpaceUrlsFromGroupList} from "@app/groups"
@@ -30,7 +31,7 @@
onMount(async () => { onMount(async () => {
// Make sure we have their relay selections before we load their posts // Make sure we have their relay selections before we load their posts
await loadRelayList(pubkey) await app.use(RelayLists).load(pubkey)
// Load groups and at least one note, regardless of time frame // Load groups and at least one note, regardless of time frame
load({ load({
+4 -3
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import cx from "classnames" import cx from "classnames"
import {removeUndefined} from "@welshman/lib" import {removeUndefined} from "@welshman/lib"
import {deriveProfile} from "@welshman/app" import {Profiles} from "@welshman/app"
import {app} from "@app/welshman"
import UserRounded from "@assets/icons/user-rounded.svg?dataurl" import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
import ImageIcon from "@lib/components/ImageIcon.svelte" import ImageIcon from "@lib/components/ImageIcon.svelte"
@@ -14,11 +15,11 @@
const {pubkey, url, size = 7, ...props}: Props = $props() const {pubkey, url, size = 7, ...props}: Props = $props()
const profile = deriveProfile(pubkey, removeUndefined([url])) const profile = app.use(Profiles).one(pubkey, removeUndefined([url]))
</script> </script>
<ImageIcon <ImageIcon
{size} {size}
alt="" alt=""
class={cx(props.class, "rounded-full")} class={cx(props.class, "rounded-full")}
src={$profile?.picture || UserRounded} /> src={$profile?.picture() || UserRounded} />
+4 -3
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import cx from "classnames" import cx from "classnames"
import {getProfile, loadProfile} from "@welshman/app" import {Profiles} from "@welshman/app"
import {app} from "@app/welshman"
import {isMobile} from "@lib/html" import {isMobile} from "@lib/html"
import ProfileCircle from "@app/components/ProfileCircle.svelte" import ProfileCircle from "@app/components/ProfileCircle.svelte"
@@ -23,11 +24,11 @@
) )
for (const pubkey of pubkeys) { for (const pubkey of pubkeys) {
loadProfile(pubkey) app.use(Profiles).load(pubkey)
} }
const visiblePubkeys = $derived.by(() => { const visiblePubkeys = $derived.by(() => {
const filtered = pubkeys.filter(pubkey => getProfile(pubkey)?.picture) const filtered = pubkeys.filter(pubkey => app.use(Profiles).get(pubkey)?.picture())
return filtered.length > 0 ? filtered : pubkeys.slice(0, 1) return filtered.length > 0 ? filtered : pubkeys.slice(0, 1)
}) })
+9 -15
View File
@@ -1,15 +1,9 @@
<script lang="ts"> <script lang="ts">
import {chunk, sleep, uniq} from "@welshman/lib" import {chunk, sleep, uniq} from "@welshman/lib"
import { import {makeEvent, DELETE, isReplaceable, getAddress} from "@welshman/util"
makeEvent, import {ProfileBuilder} from "@welshman/domain"
createProfile, import {Thunks, RelayLists} from "@welshman/app"
PROFILE, import {pubkey, repository, app} from "@app/welshman"
DELETE,
isReplaceable,
getAddress,
RelayMode,
} from "@welshman/util"
import {pubkey, publishThunk, repository, derivePubkeyRelays} from "@welshman/app"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl" import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
@@ -31,7 +25,7 @@
let confirmText = $state("") let confirmText = $state("")
const CONFIRM_TEXT = "permanently delete my nostr account" const CONFIRM_TEXT = "permanently delete my nostr account"
const userWriteRelays = derivePubkeyRelays($pubkey!, RelayMode.Write) const userWriteRelays = app.use(RelayLists).writeUrls($pubkey!).$
const confirmOk = $derived(confirmText.toLowerCase().trim() === CONFIRM_TEXT) const confirmOk = $derived(confirmText.toLowerCase().trim() === CONFIRM_TEXT)
const showProgress = $derived(progress !== undefined) const showProgress = $derived(progress !== undefined)
@@ -44,7 +38,7 @@
} }
const chunks = chunk(500, repository.query([{authors: [$pubkey!]}])) const chunks = chunk(500, repository.query([{authors: [$pubkey!]}]))
const profileEvent = makeEvent(PROFILE, createProfile({name: "[deleted]"})) const profileEvent = await new ProfileBuilder().update({name: "[deleted]"}).toTemplate()
const vanishEvent = makeEvent(62, {tags: [["relay", "ALL_RELAYS"]]}) const vanishEvent = makeEvent(62, {tags: [["relay", "ALL_RELAYS"]]})
const denominator = chunks.length + 2 const denominator = chunks.length + 2
const relays = uniq([...INDEXER_RELAYS, ...$userWriteRelays, ...$userSpaceUrls]) const relays = uniq([...INDEXER_RELAYS, ...$userWriteRelays, ...$userSpaceUrls])
@@ -58,12 +52,12 @@
} }
// First, blank out their profile in case relays don't support deletion by address // First, blank out their profile in case relays don't support deletion by address
await publishThunk({relays, event: profileEvent}) await app.use(Thunks).publish({relays, event: profileEvent})
await incrementProgress() await incrementProgress()
// Next, send a "right to vanish" event to all relays // Next, send a "right to vanish" event to all relays
await publishThunk({relays, event: vanishEvent}) await app.use(Thunks).publish({relays, event: vanishEvent})
await incrementProgress() await incrementProgress()
@@ -79,7 +73,7 @@
} }
} }
await publishThunk({relays, event: makeEvent(DELETE, {tags})}) await app.use(Thunks).publish({relays, event: makeEvent(DELETE, {tags})})
await incrementProgress() await incrementProgress()
} }
+11 -11
View File
@@ -1,7 +1,9 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {removeUndefined} from "@welshman/lib" import {removeUndefined} from "@welshman/lib"
import {deriveProfile, displayProfileByPubkey, loadMessagingRelayList} from "@welshman/app" import {ManagementMethod} from "@welshman/util"
import {RelayManagement, Profiles, MessagingRelayLists} from "@welshman/app"
import {app} from "@app/welshman"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl" import Code2 from "@assets/icons/code-2.svg?dataurl"
import Letter from "@assets/icons/letter-opened.svg?dataurl" import Letter from "@assets/icons/letter-opened.svg?dataurl"
@@ -23,12 +25,7 @@
import EventInfo from "@app/components/EventInfo.svelte" import EventInfo from "@app/components/EventInfo.svelte"
import ProfileBadges from "@app/components/ProfileBadges.svelte" import ProfileBadges from "@app/components/ProfileBadges.svelte"
import {pubkeyLink} from "@app/env" import {pubkeyLink} from "@app/env"
import { import {deriveUserIsSpaceAdmin, deriveSpaceBannedPubkeyItems, addSpaceMembers} from "@app/members"
deriveUserIsSpaceAdmin,
deriveSpaceBannedPubkeyItems,
addSpaceMembers,
banSpaceMembers,
} from "@app/members"
import {pushModal} from "@app/modal" import {pushModal} from "@app/modal"
import {pushToast} from "@app/toast" import {pushToast} from "@app/toast"
import {goToChat} from "@app/routes" import {goToChat} from "@app/routes"
@@ -40,7 +37,7 @@
const {pubkey, url}: Props = $props() const {pubkey, url}: Props = $props()
const profile = deriveProfile(pubkey, removeUndefined([url])) const profile = app.use(Profiles).one(pubkey, removeUndefined([url]))
const userIsAdmin = deriveUserIsSpaceAdmin(url) const userIsAdmin = deriveUserIsSpaceAdmin(url)
@@ -65,9 +62,12 @@
const banMember = () => const banMember = () =>
pushModal(Confirm, { pushModal(Confirm, {
title: "Ban User", title: "Ban User",
message: `Are you sure you want to ban @${displayProfileByPubkey(pubkey)} from the space?`, message: `Are you sure you want to ban @${app.use(Profiles).display(pubkey).get()} from the space?`,
confirm: async () => { confirm: async () => {
const error = await banSpaceMembers(url!, [pubkey]) const {error} = await app.use(RelayManagement).post(url!, {
method: ManagementMethod.BanPubkey,
params: [pubkey],
})
if (error) { if (error) {
pushToast({theme: "error", message: error}) pushToast({theme: "error", message: error})
@@ -92,7 +92,7 @@
let showMenu = $state(false) let showMenu = $state(false)
onMount(() => { onMount(() => {
loadMessagingRelayList(pubkey) app.use(MessagingRelayLists).load(pubkey)
}) })
</script> </script>
+16 -5
View File
@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import type {Profile} from "@welshman/util" import {Profile, ProfileBuilder} from "@welshman/domain"
import {makeProfile} from "@welshman/util" import {pubkey, profilesByPubkey} from "@app/welshman"
import {pubkey, profilesByPubkey, waitForThunkError} from "@welshman/app"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import {errorMessage} from "@lib/util" import {errorMessage} from "@lib/util"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
@@ -12,16 +11,28 @@
import {pushToast} from "@app/toast" import {pushToast} from "@app/toast"
import {updateProfile} from "@app/profiles" import {updateProfile} from "@app/profiles"
const profile = $profilesByPubkey.get($pubkey!) || makeProfile() // The edit form binds to plain mutable fields (name/about/nip05/picture), so we
// hand it a plain values object rather than a Profile Reader. A Reader exposes its
// raw parsed content as `.values`; a fresh profile starts from an empty builder.
const reader = $profilesByPubkey.get($pubkey!)
const profile = reader instanceof Profile ? {...reader.values} : new ProfileBuilder().update({}).values
const initialValues = {profile} const initialValues = {profile}
const back = () => history.back() const back = () => history.back()
// TODO(welshman-migration): `profile` here is a plain values object (the form binds
// mutable fields), not a Profile Reader. It is typed Profile to match ProfileEditForm's
// Values type and updateProfile's signature, both of which still say Profile; updateProfile
// routes a non-Reader through `new ProfileBuilder().update(profile)`. Confirm the intended
// values vs Reader contract once the shared types settle.
const onsubmit = async ({profile}: {profile: Profile}) => { const onsubmit = async ({profile}: {profile: Profile}) => {
loading = true loading = true
try { try {
const error = await waitForThunkError(updateProfile({profile})) // TODO(welshman-migration): updateProfile is async (returns Promise<Thunk>); the old
// waitForThunkError shim did not await its arg. Awaiting the thunk first before
// .waitForError() — confirm this matches intended behavior.
const error = await (await updateProfile({profile})).waitForError()
if (error) { if (error) {
pushToast({ pushToast({
+3 -2
View File
@@ -4,7 +4,8 @@
import {feedFromFilter, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds" import {feedFromFilter, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
import {NOTE, getReplyTags} from "@welshman/util" import {NOTE, getReplyTags} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {makeFeedController} from "@welshman/app" import {Feeds} from "@welshman/app"
import {app} from "@app/welshman"
import {createScroller} from "@lib/html" import {createScroller} from "@lib/html"
import {fly} from "@lib/transition" import {fly} from "@lib/transition"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
@@ -19,7 +20,7 @@
let {url, pubkey, events = $bindable([]), hideLoading = false}: Props = $props() let {url, pubkey, events = $bindable([]), hideLoading = false}: Props = $props()
const ctrl = makeFeedController({ const ctrl = app.use(Feeds).makeFeedController({
useWindowing: true, useWindowing: true,
feed: makeIntersectionFeed( feed: makeIntersectionFeed(
makeRelayFeed(url), makeRelayFeed(url),
+5 -5
View File
@@ -1,19 +1,19 @@
<script lang="ts"> <script lang="ts">
import {removeUndefined} from "@welshman/lib" import {removeUndefined} from "@welshman/lib"
import {deriveProfile} from "@welshman/app" import {Profiles} from "@welshman/app"
import {app} from "@app/welshman"
import ContentMinimal from "@app/components/ContentMinimal.svelte" import ContentMinimal from "@app/components/ContentMinimal.svelte"
export type Props = { export type Props = {
pubkey: string pubkey: string
singleLine?: boolean
url?: string url?: string
} }
const {pubkey, url, singleLine}: Props = $props() const {pubkey, url}: Props = $props()
const profile = deriveProfile(pubkey, removeUndefined([url])) const profile = app.use(Profiles).one(pubkey, removeUndefined([url]))
</script> </script>
{#if $profile} {#if $profile}
<ContentMinimal event={{content: $profile.about || "", tags: []}} {singleLine} /> <ContentMinimal event={{content: $profile.about() || "", tags: []}} />
{/if} {/if}
+1 -1
View File
@@ -4,7 +4,7 @@
import type {Writable} from "svelte/store" import type {Writable} from "svelte/store"
import {type Instance} from "tippy.js" import {type Instance} from "tippy.js"
import {append, remove, uniq} from "@welshman/lib" import {append, remove, uniq} from "@welshman/lib"
import {profileSearch} from "@welshman/app" import {profileSearch} from "@app/welshman"
import Suggestions from "@lib/components/Suggestions.svelte" import Suggestions from "@lib/components/Suggestions.svelte"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl" import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import Magnifier from "@assets/icons/magnifier.svg?dataurl" import Magnifier from "@assets/icons/magnifier.svg?dataurl"
+3 -2
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import {removeUndefined} from "@welshman/lib" import {removeUndefined} from "@welshman/lib"
import {deriveProfileDisplay} from "@welshman/app" import {Profiles} from "@welshman/app"
import {app} from "@app/welshman"
type Props = { type Props = {
pubkey: string pubkey: string
@@ -9,7 +10,7 @@
const {pubkey, url}: Props = $props() const {pubkey, url}: Props = $props()
const profileDisplay = deriveProfileDisplay(pubkey, removeUndefined([url])) const profileDisplay = app.use(Profiles).display(pubkey, removeUndefined([url])).$
</script> </script>
{$profileDisplay} {$profileDisplay}
+7 -6
View File
@@ -17,7 +17,8 @@
import type {TrustedEvent, EventContent, Zap} from "@welshman/util" import type {TrustedEvent, EventContent, Zap} from "@welshman/util"
import {deriveArray, deriveEventsById, deriveItemsByKey} from "@welshman/store" import {deriveArray, deriveEventsById, deriveItemsByKey} from "@welshman/store"
import {load} from "@welshman/net" import {load} from "@welshman/net"
import {pubkey, repository, getValidZap, displayProfileByPubkey} from "@welshman/app" import {Zappers, Profiles} from "@welshman/app"
import {pubkey, repository, app} from "@app/welshman"
import {isMobile, preventDefault, stopPropagation} from "@lib/html" import {isMobile, preventDefault, stopPropagation} from "@lib/html"
import Danger from "@assets/icons/danger-triangle.svg?dataurl" import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
@@ -64,15 +65,15 @@
repository, repository,
getKey: zap => zap.response.id, getKey: zap => zap.response.id,
filters: [{kinds: [ZAP_RESPONSE], "#e": eventIds}], filters: [{kinds: [ZAP_RESPONSE], "#e": eventIds}],
eventToItem: (response: TrustedEvent) => { eventToItem: async (response: TrustedEvent) => {
const zap = getValidZap(response, event) const zap = await app.use(Zappers).validateZapReceipt(response, event)
if (zap) { if (zap) {
return zap return zap
} }
if (innerEvent) { if (innerEvent) {
return getValidZap(response, innerEvent) return await app.use(Zappers).validateZapReceipt(response, innerEvent)
} }
}, },
}), }),
@@ -150,7 +151,7 @@
{@const amount = fromMsats(sum(zaps.map(zap => zap.invoiceAmount)))} {@const amount = fromMsats(sum(zaps.map(zap => zap.invoiceAmount)))}
{@const pubkeys = uniq(zaps.map(zap => zap.request.pubkey))} {@const pubkeys = uniq(zaps.map(zap => zap.request.pubkey))}
{@const isOwn = $pubkey && pubkeys.includes($pubkey)} {@const isOwn = $pubkey && pubkeys.includes($pubkey)}
{@const info = displayList(pubkeys.map(pubkey => displayProfileByPubkey(pubkey)))} {@const info = displayList(pubkeys.map(pubkey => app.use(Profiles).display(pubkey).get()))}
{@const tooltip = `${info} zapped`} {@const tooltip = `${info} zapped`}
<button <button
type="button" type="button"
@@ -171,7 +172,7 @@
{#each groupedReactions.entries() as [key, events]} {#each groupedReactions.entries() as [key, events]}
{@const pubkeys = events.map(e => e.pubkey)} {@const pubkeys = events.map(e => e.pubkey)}
{@const isOwn = $pubkey && pubkeys.includes($pubkey)} {@const isOwn = $pubkey && pubkeys.includes($pubkey)}
{@const info = displayList(pubkeys.map(pubkey => displayProfileByPubkey(pubkey)))} {@const info = displayList(pubkeys.map(pubkey => app.use(Profiles).display(pubkey).get()))}
{@const tooltip = `${info} reacted`} {@const tooltip = `${info} reacted`}
{@const onClick = () => onReactionClick(events)} {@const onClick = () => onReactionClick(events)}
<button <button
+1 -1
View File
@@ -25,7 +25,7 @@
const onClick = () => goto(h ? makeRoomPath(url, h) : makeSpaceChatPath(url)) const onClick = () => goto(h ? makeRoomPath(url, h) : makeSpaceChatPath(url))
</script> </script>
<Button class="cv card2" onclick={onClick}> <Button class="cv card2 bg-alt shadow-md" onclick={onClick}>
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<div class="flex items-center gap-2 text-sm"> <div class="flex items-center gap-2 text-sm">
{#if h} {#if h}
-34
View File
@@ -1,34 +0,0 @@
<script lang="ts">
import {THREAD, CLASSIFIED, ZAP_GOAL, EVENT_TIME, POLL} from "@welshman/util"
import NoteItem from "@app/components/NoteItem.svelte"
import ThreadItem from "@app/components/ThreadItem.svelte"
import ClassifiedItem from "@app/components/ClassifiedItem.svelte"
import GoalItem from "@app/components/GoalItem.svelte"
import CalendarEventItem from "@app/components/CalendarEventItem.svelte"
import PollItem from "@app/components/PollItem.svelte"
import RecentConversation from "@app/components/RecentConversation.svelte"
import type {RecentActivityItem} from "@app/recent"
type Props = {
url: string
item: RecentActivityItem
}
const {url, item}: Props = $props()
</script>
{#if item.type === "message"}
<RecentConversation {url} event={item.event} count={item.count} />
{:else if item.event.kind === THREAD}
<ThreadItem {url} event={item.event} />
{:else if item.event.kind === CLASSIFIED}
<ClassifiedItem {url} event={item.event} />
{:else if item.event.kind === ZAP_GOAL}
<GoalItem {url} event={item.event} />
{:else if item.event.kind === EVENT_TIME}
<CalendarEventItem {url} event={item.event} />
{:else if item.event.kind === POLL}
<PollItem {url} event={item.event} />
{:else}
<NoteItem {url} event={item.event} />
{/if}
+2 -2
View File
@@ -5,7 +5,7 @@
import {tryCatch} from "@welshman/lib" import {tryCatch} from "@welshman/lib"
import {isShareableRelayUrl, isIPAddress, normalizeRelayUrl} from "@welshman/util" import {isShareableRelayUrl, isIPAddress, normalizeRelayUrl} from "@welshman/util"
import type {Thunk} from "@welshman/app" import type {Thunk} from "@welshman/app"
import {waitForThunkError, relaySearch} from "@welshman/app" import {relaySearch} from "@app/welshman"
import {createScroller} from "@lib/html" import {createScroller} from "@lib/html"
import {errorMessage} from "@lib/util" import {errorMessage} from "@lib/util"
import Magnifier from "@assets/icons/magnifier.svg?dataurl" import Magnifier from "@assets/icons/magnifier.svg?dataurl"
@@ -34,7 +34,7 @@
loading.add(url) loading.add(url)
try { try {
const error = await waitForThunkError(await addRelay(url)) const error = await (await addRelay(url)).waitForError()
if (error) { if (error) {
pushToast({ pushToast({
+3 -2
View File
@@ -1,9 +1,10 @@
<script lang="ts"> <script lang="ts">
import {deriveRelay} from "@welshman/app" import {Relays} from "@welshman/app"
import {app} from "@app/welshman"
const {...props} = $props() const {...props} = $props()
const relay = deriveRelay(props.url) const relay = app.use(Relays).one(props.url)
</script> </script>
{#if $relay?.description} {#if $relay?.description}
+3 -2
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import {deriveRelay} from "@welshman/app" import {Relays} from "@welshman/app"
import {app} from "@app/welshman"
import RemoteControllerMinimalistic from "@assets/icons/remote-controller-minimalistic.svg?dataurl" import RemoteControllerMinimalistic from "@assets/icons/remote-controller-minimalistic.svg?dataurl"
import ImageIcon from "@lib/components/ImageIcon.svelte" import ImageIcon from "@lib/components/ImageIcon.svelte"
@@ -11,7 +12,7 @@
const {url, size = 7, ...props}: Props = $props() const {url, size = 7, ...props}: Props = $props()
const relay = deriveRelay(url) const relay = app.use(Relays).one(url)
</script> </script>
{#if $relay?.icon} {#if $relay?.icon}
+4 -3
View File
@@ -4,12 +4,13 @@
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import {displayUrl} from "@welshman/lib" import {displayUrl} from "@welshman/lib"
import {displayRelayUrl} from "@welshman/util" import {displayRelayUrl} from "@welshman/util"
import {deriveRelay, deriveRelayStats} from "@welshman/app" import {Relays, RelayStats} from "@welshman/app"
import {app} from "@app/welshman"
const {url, children} = $props() const {url, children} = $props()
const relay = deriveRelay(url) const relay = app.use(Relays).one(url)
const relayStats = deriveRelayStats(url) const relayStats = app.use(RelayStats).one(url)
const connections = $derived($relayStats?.open_count || 0) const connections = $derived($relayStats?.open_count || 0)
</script> </script>
+1 -2
View File
@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import type {Readable} from "svelte/store" import type {Readable} from "svelte/store"
import {SvelteSet} from "svelte/reactivity" import {SvelteSet} from "svelte/reactivity"
import {waitForThunkError} from "@welshman/app"
import type {Thunk} from "@welshman/app" import type {Thunk} from "@welshman/app"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl" import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
@@ -37,7 +36,7 @@
loading.add(url) loading.add(url)
try { try {
const error = await waitForThunkError(await removeRelay(url)) const error = await (await removeRelay(url)).waitForError()
if (error) { if (error) {
pushToast({ pushToast({
+3 -2
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import {deriveRelayDisplay} from "@welshman/app" import {Relays} from "@welshman/app"
import {app} from "@app/welshman"
type Props = { type Props = {
url: string url: string
@@ -8,7 +9,7 @@
const {url, ...props}: Props = $props() const {url, ...props}: Props = $props()
const display = $derived(deriveRelayDisplay(url)) const display = $derived(app.use(Relays).display(url).$)
</script> </script>
<span class={props.class}> <span class={props.class}>
+1 -1
View File
@@ -2,7 +2,7 @@
import {REPORT} from "@welshman/util" import {REPORT} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {deriveEventsById} from "@welshman/store" import {deriveEventsById} from "@welshman/store"
import {repository} from "@welshman/app" import {repository} from "@app/welshman"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalTitle from "@lib/components/ModalTitle.svelte" import ModalTitle from "@lib/components/ModalTitle.svelte"
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte" import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
+10 -6
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import {getTag, ManagementMethod} from "@welshman/util" import {getTag, ManagementMethod} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {pubkey, manageRelay, repository, displayProfileByPubkey} from "@welshman/app" import {RelayManagement, Profiles} from "@welshman/app"
import {pubkey, repository, app} from "@app/welshman"
import InboxOut from "@assets/icons/inbox-out.svg?dataurl" import InboxOut from "@assets/icons/inbox-out.svg?dataurl"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl" import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import MinusCircle from "@assets/icons/minus-circle.svg?dataurl" import MinusCircle from "@assets/icons/minus-circle.svg?dataurl"
@@ -12,7 +13,7 @@
import Popover from "@lib/components/Popover.svelte" import Popover from "@lib/components/Popover.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Confirm from "@lib/components/Confirm.svelte" import Confirm from "@lib/components/Confirm.svelte"
import {deriveUserIsSpaceAdmin, banSpaceMembers} from "@app/members" import {deriveUserIsSpaceAdmin} from "@app/members"
import {publishDelete} from "@app/deletes" import {publishDelete} from "@app/deletes"
import {canEnforceNip70} from "@app/relays" import {canEnforceNip70} from "@app/relays"
import {pushToast} from "@app/toast" import {pushToast} from "@app/toast"
@@ -45,7 +46,7 @@
} }
const dismissReport = async () => { const dismissReport = async () => {
const {error} = await manageRelay(url, { const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.BanEvent, method: ManagementMethod.BanEvent,
params: [event.id, "Dismissed by admin"], params: [event.id, "Dismissed by admin"],
}) })
@@ -66,7 +67,7 @@
title: `Remove Content`, title: `Remove Content`,
message: `Are you sure you want to delete this content from the space?`, message: `Are you sure you want to delete this content from the space?`,
confirm: async () => { confirm: async () => {
const {error} = await manageRelay(url, { const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.BanEvent, method: ManagementMethod.BanEvent,
params: [id, reason], params: [id, reason],
}) })
@@ -89,9 +90,12 @@
pushModal(Confirm, { pushModal(Confirm, {
title: "Ban User", title: "Ban User",
message: `Are you sure you want to ban @${displayProfileByPubkey(pubkey)} from the space?`, message: `Are you sure you want to ban @${app.use(Profiles).display(pubkey).get()} from the space?`,
confirm: async () => { confirm: async () => {
const error = await banSpaceMembers(url, [pubkey], reason) const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.BanPubkey,
params: [pubkey, reason],
})
if (error) { if (error) {
pushToast({theme: "error", message: error}) pushToast({theme: "error", message: error})
-17
View File
@@ -1,17 +0,0 @@
<script lang="ts">
import {roleColor, roleColorSoft, type SpaceRole} from "@app/members"
interface Props {
role: SpaceRole
}
const {role}: Props = $props()
</script>
<div
class="badge min-w-0"
style="background-color: {roleColorSoft(role.color)}; border-color: {roleColor(
role.color,
)}; color: {roleColor(role.color)};">
<strong>{role.label || "Untitled Role"}</strong>
</div>
-49
View File
@@ -1,49 +0,0 @@
<script lang="ts">
import {randomId} from "@welshman/lib"
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalTitle from "@lib/components/ModalTitle.svelte"
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
import RelayName from "@app/components/RelayName.svelte"
import RoleForm, {type Values} from "@app/components/RoleForm.svelte"
import {createRole} from "@app/members"
import {pushToast} from "@app/toast"
type Props = {
url: string
}
const {url}: Props = $props()
const back = () => history.back()
let loading = $state(false)
const onSubmit = async ({label, description, color}: Values) => {
loading = true
try {
const error = await createRole(url, randomId(), label, description, color, 0)
if (error) {
pushToast({theme: "error", message: error})
} else {
pushToast({message: "Role created!"})
back()
}
} finally {
loading = false
}
}
</script>
<Modal>
<ModalBody>
<ModalHeader>
<ModalTitle>Create Role</ModalTitle>
<ModalSubtitle>in <RelayName {url} class="text-primary" /></ModalSubtitle>
</ModalHeader>
<RoleForm {loading} {onSubmit} />
</ModalBody>
</Modal>
-49
View File
@@ -1,49 +0,0 @@
<script lang="ts">
import Modal from "@lib/components/Modal.svelte"
import ModalBody from "@lib/components/ModalBody.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalTitle from "@lib/components/ModalTitle.svelte"
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
import RelayName from "@app/components/RelayName.svelte"
import RoleForm, {type Values} from "@app/components/RoleForm.svelte"
import {editRole, type SpaceRole} from "@app/members"
import {pushToast} from "@app/toast"
type Props = {
url: string
role: SpaceRole
}
const {url, role}: Props = $props()
const back = () => history.back()
let loading = $state(false)
const onSubmit = async ({label, description, color}: Values) => {
loading = true
try {
const error = await editRole(url, role.id, label, description, color, role.order)
if (error) {
pushToast({theme: "error", message: error})
} else {
pushToast({message: "Role updated!"})
back()
}
} finally {
loading = false
}
}
</script>
<Modal>
<ModalBody>
<ModalHeader>
<ModalTitle>Edit Role</ModalTitle>
<ModalSubtitle>in <RelayName {url} class="text-primary" /></ModalSubtitle>
</ModalHeader>
<RoleForm {loading} {onSubmit} initialValues={role} />
</ModalBody>
</Modal>
-86
View File
@@ -1,86 +0,0 @@
<script module lang="ts">
import type {SpaceRole} from "@app/members"
export type Values = Pick<SpaceRole, "label" | "description" | "color">
</script>
<script lang="ts">
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Field from "@lib/components/Field.svelte"
import Icon from "@lib/components/Icon.svelte"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {roleColor} from "@app/members"
type Props = {
initialValues?: Partial<Values>
loading?: boolean
onSubmit: (values: Values) => void
}
const {initialValues = {}, loading = false, onSubmit}: Props = $props()
const values: Values = $state({
label: "",
description: "",
color: Math.floor(Math.random() * 256),
...initialValues,
})
const back = () => history.back()
const submit = () => onSubmit(values)
</script>
<div class="flex flex-col gap-4">
<Field>
{#snippet label()}
<p>Name</p>
{/snippet}
{#snippet input()}
<input
bind:value={values.label}
class="input input-bordered w-full"
placeholder="Moderator" />
{/snippet}
</Field>
<Field>
{#snippet label()}
<p>Description</p>
{/snippet}
{#snippet input()}
<textarea bind:value={values.description} class="textarea textarea-bordered w-full" rows="2"
></textarea>
{/snippet}
</Field>
<Field>
{#snippet label()}
<p>Color</p>
{/snippet}
{#snippet input()}
<div class="flex items-center gap-3">
<div
class="h-8 w-8 shrink-0 rounded-full border-2 border-base-300"
style="background-color: {roleColor(values.color)}">
</div>
<input
type="range"
min="0"
max="255"
bind:value={values.color}
class="range range-sm grow"
style="color: {roleColor(values.color)}; --range-shdw: {roleColor(values.color)}" />
</div>
{/snippet}
</Field>
</div>
<ModalFooter>
<Button class="btn btn-link" onclick={back}>
<Icon icon={AltArrowLeft} />
Go back
</Button>
<Button class="btn btn-primary" onclick={submit} disabled={loading || !values.label}>
<Spinner {loading}>Save changes</Spinner>
</Button>
</ModalFooter>
-17
View File
@@ -1,17 +0,0 @@
<script lang="ts">
import RoleBadge from "@app/components/RoleBadge.svelte"
import type {SpaceRole} from "@app/members"
interface Props {
role: SpaceRole
}
const {role}: Props = $props()
</script>
<div class="flex min-w-0 flex-col gap-2">
<RoleBadge {role} />
{#if role.description}
<p class="text-sm opacity-70">{role.description}</p>
{/if}
</div>
+3 -2
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {displayProfileByPubkey} from "@welshman/app" import {Profiles} from "@welshman/app"
import {app} from "@app/welshman"
import {slide} from "@lib/transition" import {slide} from "@lib/transition"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl" import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
@@ -21,7 +22,7 @@
<div <div
class="relative border-l-2 border-solid border-primary bg-base-300 px-2 py-1 pr-8" class="relative border-l-2 border-solid border-primary bg-base-300 px-2 py-1 pr-8"
transition:slide> transition:slide>
<p class="text-xs text-primary">{verb} @{displayProfileByPubkey(event.pubkey)}</p> <p class="text-xs text-primary">{verb} @{app.use(Profiles).display(event.pubkey).get()}</p>
{#key event.id} {#key event.id}
<NoteContentMinimal trimParent {event} /> <NoteContentMinimal trimParent {event} />
{/key} {/key}
+11 -9
View File
@@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import type {RoomMeta} from "@welshman/util" import {displayRelayUrl} from "@welshman/util"
import {displayRelayUrl, makeRoomMeta} from "@welshman/util"
import type {Thunk} from "@welshman/app" import type {Thunk} from "@welshman/app"
import {deleteRoom, waitForThunkError, repository, joinRoom, leaveRoom} from "@welshman/app" import {Rooms} from "@welshman/app"
import {repository, app} from "@app/welshman"
import Pen from "@assets/icons/pen.svg?dataurl" import Pen from "@assets/icons/pen.svg?dataurl"
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl" import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
import Login3 from "@assets/icons/login-3.svg?dataurl" import Login3 from "@assets/icons/login-3.svg?dataurl"
@@ -71,11 +71,13 @@
const startEdit = () => pushModal(RoomEdit, {url, h}) const startEdit = () => pushModal(RoomEdit, {url, h})
const handleLoading = async (f: (url: string, room: RoomMeta) => Thunk) => { const handleLoading = async (
f: (url: string, room: {h: string}) => Promise<Thunk>,
) => {
loading = true loading = true
try { try {
const message = await waitForThunkError(f(url, makeRoomMeta({h}))) const message = await (await f(url, {h})).waitForError()
if (message && !message.startsWith("duplicate:")) { if (message && !message.startsWith("duplicate:")) {
pushToast({theme: "error", message}) pushToast({theme: "error", message})
@@ -85,9 +87,9 @@
} }
} }
const join = () => handleLoading(joinRoom) const join = () => handleLoading((url, room) => app.use(Rooms).join(url, room))
const leave = () => handleLoading(leaveRoom) const leave = () => handleLoading((url, room) => app.use(Rooms).leave(url, room))
const showMembers = () => pushModal(RoomMembers, {url, h}) const showMembers = () => pushModal(RoomMembers, {url, h})
@@ -109,8 +111,8 @@
message: message:
"This room will no longer be accessible to space members, and all messages posted to it will be deleted.", "This room will no longer be accessible to space members, and all messages posted to it will be deleted.",
confirm: async () => { confirm: async () => {
const thunk = deleteRoom(url, $room) const thunk = await app.use(Rooms).delete(url, $room)
const message = await waitForThunkError(thunk) const message = await thunk.waitForError()
if (message) { if (message) {
repository.removeEvent(thunk.event.id) repository.removeEvent(thunk.event.id)
+58 -6
View File
@@ -1,8 +1,7 @@
<script lang="ts"> <script lang="ts">
import type {Snippet} from "svelte" import type {Snippet} from "svelte"
import type {RoomMeta} from "@welshman/util" import {Rooms} from "@welshman/app"
import {makeRoomMeta} from "@welshman/util" import {app} from "@app/welshman"
import {waitForThunkError, createRoom, editRoom, joinRoom} from "@welshman/app"
import StickerSmileSquare from "@assets/icons/sticker-smile-square.svg?dataurl" import StickerSmileSquare from "@assets/icons/sticker-smile-square.svg?dataurl"
import Hashtag from "@assets/icons/hashtag.svg?dataurl" import Hashtag from "@assets/icons/hashtag.svg?dataurl"
import Volume from "@assets/icons/volume.svg?dataurl" import Volume from "@assets/icons/volume.svg?dataurl"
@@ -19,6 +18,56 @@
import {deriveHasLivekit} from "@app/relays" import {deriveHasLivekit} from "@app/relays"
import {getRoomType, RoomType} from "@app/groups" import {getRoomType, RoomType} from "@app/groups"
// Plain mutable form object (the old @welshman/util `RoomMeta` plain-object
// type, removed in the migration). The new domain `RoomMeta` is an async
// method-accessor Reader, which doesn't fit a `$state` object bound to inputs,
// so we keep a plain object here and let `app.use(Rooms).create/edit/join`
// (which accept a plain object) build the events at submit time.
type RoomMeta = {
h: string
name?: string
about?: string
picture?: string
pictureMeta?: string[]
isClosed?: boolean
isHidden?: boolean
isPrivate?: boolean
isRestricted?: boolean
livekit?: boolean
}
// TODO(welshman-migration): reimplemented inline from the removed
// @welshman/util `generateH`/`makeRoomMeta` (room-id generator). Verify the
// generated id still matches the expected `^[a-z]+[1-9]$` shape and that no
// shared generator should be used instead.
const vowels = "a,e,i,o,u,ay,ey,oy,ou,ia,ea,ough,oo,ee,argh".split(",")
const consonants =
"p,b,t,d,k,g,ch,sh,th,f,v,s,z,l,r,m,n,pl,bl,cl,gl,pr,br,tr,dr,kr,gr,fl,sl,fr,thr,str,sk,sp,st".split(
",",
)
const generateH = () => {
const n = (6 + Math.random() * 2) | 0
const s = [consonants.slice(), vowels.slice()]
if (Math.random() < 0.5) {
s.reverse()
}
return (
Array.from({length: n}, (_, i) =>
s[i % 2].splice((Math.random() * s[i % 2].length) | 0, 1),
).join("") +
(1 + Math.floor(Math.random() * 9))
)
}
const makeRoomMeta = (room: Partial<RoomMeta> = {}): RoomMeta => ({
h: room.h ?? generateH(),
...room,
})
type Props = { type Props = {
url: string url: string
header: Snippet header: Snippet
@@ -58,19 +107,22 @@
room.pictureMeta = result.tags room.pictureMeta = result.tags
} }
const createMessage = await waitForThunkError(createRoom(url, room)) // TODO(welshman-migration): app.use(Rooms).create/edit/join are async
// (return Promise<Thunk>); the old code passed the un-awaited result to
// waitForThunkError. Awaiting the thunk first before .waitForError().
const createMessage = await (await app.use(Rooms).create(url, room)).waitForError()
if (createMessage && !createMessage.includes("already")) { if (createMessage && !createMessage.includes("already")) {
return pushToast({theme: "error", message: createMessage}) return pushToast({theme: "error", message: createMessage})
} }
const editMessage = await waitForThunkError(editRoom(url, room)) const editMessage = await (await app.use(Rooms).edit(url, room)).waitForError()
if (editMessage) { if (editMessage) {
return pushToast({theme: "error", message: editMessage}) return pushToast({theme: "error", message: editMessage})
} }
const joinMessage = await waitForThunkError(joinRoom(url, room)) const joinMessage = await (await app.use(Rooms).join(url, room)).waitForError()
if (joinMessage && !joinMessage.includes("already")) { if (joinMessage && !joinMessage.includes("already")) {
return pushToast({theme: "error", message: joinMessage}) return pushToast({theme: "error", message: joinMessage})
+5 -10
View File
@@ -11,13 +11,8 @@
} from "@welshman/lib" } from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import {MESSAGE, COMMENT, getTag} from "@welshman/util" import {MESSAGE, COMMENT, getTag} from "@welshman/util"
import { import {Thunks, Profiles} from "@welshman/app"
thunks, import {thunks, pubkey, app} from "@app/welshman"
pubkey,
mergeThunks,
deriveProfileDisplay,
displayProfileByPubkey,
} from "@welshman/app"
import {isMobile} from "@lib/html" import {isMobile} from "@lib/html"
import Pen from "@assets/icons/pen.svg?dataurl" import Pen from "@assets/icons/pen.svg?dataurl"
import Reply from "@assets/icons/reply-2.svg?dataurl" import Reply from "@assets/icons/reply-2.svg?dataurl"
@@ -58,8 +53,8 @@
const path = getRoomItemPath(url, event) const path = getRoomItemPath(url, event)
const shouldProtect = canEnforceNip70(url) const shouldProtect = canEnforceNip70(url)
const today = formatTimestampAsDate(now()) const today = formatTimestampAsDate(now())
const profileDisplay = deriveProfileDisplay(event.pubkey, [url]) const profileDisplay = app.use(Profiles).display(event.pubkey, [url]).$
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id)) const thunk = app.use(Thunks).merge($thunks.filter(t => t.event.id === event.id))
const [_, colorValue] = colors[hash(event.pubkey) % colors.length] const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
const qTag = getTag("q", event.tags) const qTag = getTag("q", event.tags)
@@ -138,7 +133,7 @@
{#if path && $innerComments.length > 0} {#if path && $innerComments.length > 0}
{@const pubkeys = $innerComments.map(e => e.pubkey)} {@const pubkeys = $innerComments.map(e => e.pubkey)}
{@const isOwn = $pubkey && pubkeys.includes($pubkey)} {@const isOwn = $pubkey && pubkeys.includes($pubkey)}
{@const info = displayList(pubkeys.map(pubkey => displayProfileByPubkey(pubkey)))} {@const info = displayList(pubkeys.map(pubkey => app.use(Profiles).display(pubkey).get()))}
{@const tooltip = `${info} commented`} {@const tooltip = `${info} commented`}
<div data-tip={tooltip} class="tooltip tooltip-right flex"> <div data-tip={tooltip} class="tooltip tooltip-right flex">
<Link <Link
+3 -2
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {ManagementMethod} from "@welshman/util" import {ManagementMethod} from "@welshman/util"
import {pubkey, manageRelay, repository} from "@welshman/app" import {RelayManagement} from "@welshman/app"
import {pubkey, repository, app} from "@app/welshman"
import Code2 from "@assets/icons/code-2.svg?dataurl" import Code2 from "@assets/icons/code-2.svg?dataurl"
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl" import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
import Danger from "@assets/icons/danger.svg?dataurl" import Danger from "@assets/icons/danger.svg?dataurl"
@@ -45,7 +46,7 @@
title: `Delete Message`, title: `Delete Message`,
message: `Are you sure you want to delete this message from the space?`, message: `Are you sure you want to delete this message from the space?`,
confirm: async () => { confirm: async () => {
const {error} = await manageRelay(url, { const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.BanEvent, method: ManagementMethod.BanEvent,
params: [event.id], params: [event.id],
}) })
+1 -1
View File
@@ -1,7 +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} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {pubkey} from "@welshman/app" import {pubkey} from "@app/welshman"
import Bolt from "@assets/icons/bolt.svg?dataurl" import Bolt from "@assets/icons/bolt.svg?dataurl"
import Reply from "@assets/icons/reply-2.svg?dataurl" import Reply from "@assets/icons/reply-2.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl" import Code2 from "@assets/icons/code-2.svg?dataurl"
+9 -4
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import {getTagValue, ManagementMethod} from "@welshman/util" import {getTagValue, ManagementMethod} from "@welshman/util"
import type {TrustedEvent, PublishedRoomMeta} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {repository, manageRelay} from "@welshman/app" import {RelayManagement} from "@welshman/app"
import {app, repository} from "@app/welshman"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ProfileName from "@app/components/ProfileName.svelte" import ProfileName from "@app/components/ProfileName.svelte"
import ProfileDetail from "@app/components/ProfileDetail.svelte" import ProfileDetail from "@app/components/ProfileDetail.svelte"
@@ -28,7 +29,7 @@
loading = true loading = true
try { try {
const {error} = await manageRelay(url, { const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.BanEvent, method: ManagementMethod.BanEvent,
params: [event.id, "Join request dismissed"], params: [event.id, "Join request dismissed"],
}) })
@@ -49,7 +50,11 @@
loading = true loading = true
try { try {
const error = await addRoomMembers(url, $room as PublishedRoomMeta, [event.pubkey]) // TODO(welshman-migration): addRoomMembers now expects a RoomMeta domain
// Reader, but deriveRoom yields the plain `Room` object. Passing it through
// as `any` preserves prior runtime behavior; verify Rooms.addMember accepts
// the plain Room shape (url/h/...) or resolve a real RoomMeta reader here.
const error = await addRoomMembers(url, $room as any, [event.pubkey])
if (error) { if (error) {
pushToast({theme: "error", message: error}) pushToast({theme: "error", message: error})
+4 -2
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import {waitForThunkError, removeRoomMember} from "@welshman/app" import {Rooms} from "@welshman/app"
import {app} from "@app/welshman"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl" import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import MinusCircle from "@assets/icons/minus-circle.svg?dataurl" import MinusCircle from "@assets/icons/minus-circle.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl" import AddCircle from "@assets/icons/add-circle.svg?dataurl"
@@ -51,7 +52,8 @@
title: "Remove Member", title: "Remove Member",
message: "Are you sure you want to remove this user from the room?", message: "Are you sure you want to remove this user from the room?",
confirm: async () => { confirm: async () => {
const error = await waitForThunkError(removeRoomMember(url, $room, pubkey)) const thunk = await app.use(Rooms).removeMember(url, $room, pubkey)
const error = await thunk.waitForError()
if (error) { if (error) {
pushToast({theme: "error", message: error}) pushToast({theme: "error", message: error})

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