Compare commits

...

34 Commits

Author SHA1 Message Date
Jon Staab 71dcfae5ff Add heading, update changelog, bump version 2025-10-02 12:43:19 -07:00
Jon Staab 04155f5b23 Bump welshman 2025-10-01 17:03:25 -07:00
Jon Staab b4058389ec Avoid decrypt errors 2025-10-01 10:06:21 -07:00
Jon Staab 483fa81b74 Fix some storage bugs 2025-09-30 17:04:52 -07:00
Jon Staab a8d1c4bbbc Refactor storage 2025-09-30 16:28:12 -07:00
Matthew Remmel 0a8c2faa74 Add filestorage adapters 2025-09-30 10:52:24 -07:00
Jon Staab dd3231e70f Bring back blossom server auth, fix duplicate direct messages 2025-09-29 14:25:07 -07:00
Jon Staab 7ff9c00032 Force url extension for encrypted uploads 2025-09-29 14:25:07 -07:00
Matthew Remmel 9ed483abf7 Ignore exception from if failing to set badge 2025-09-29 10:32:13 -07:00
Matthew Remmel b9aeaf29a4 Disable notification sound when tab is focused 2025-09-29 10:32:13 -07:00
Matthew Remmel 65e3f81f36 Remove comments and test lines 2025-09-29 10:32:13 -07:00
Matthew Remmel c6641dba31 Move notification sound and badge settings to settings store 2025-09-29 10:32:13 -07:00
Matthew Remmel e48d1e0e59 Fix async bug and add sound component for notification sound 2025-09-29 10:32:13 -07:00
Matthew Remmel d1e5aee84e Add naive badge count implementation 2025-09-29 10:32:13 -07:00
Matthew Remmel 5cb22d0bed Add checkboxes for badge/sound settings 2025-09-29 10:32:13 -07:00
Matthew Remmel d1c6f53d7c Add royalty-free sound effect for new notification 2025-09-29 10:32:13 -07:00
Matthew Remmel 6e238f98c0 Auto-add receiving address on wallet setup 2025-09-29 10:25:02 -07:00
Jon Staab 290274d6c8 Tweak light theme, remove conditional button classes 2025-09-25 10:52:32 -07:00
Jon Staab e1de0239c9 Remove css that was breaking tooltips 2025-09-25 10:43:55 -07:00
Jon Staab bec77d59e8 Add theme toggle on mobile, change button color for quick links 2025-09-25 10:37:53 -07:00
Jon Staab 84f8794d7c Make link previews less aggressive 2025-09-25 10:12:58 -07:00
Jon Staab 4cddf41bf3 Set initial delay to 0 2025-09-24 09:50:07 -07:00
Jon Staab 125a7e238e Add qr scanner to discover page 2025-09-22 15:56:48 -07:00
Jon Staab 468200b717 Link directly to discover page 2025-09-22 15:48:13 -07:00
Jon Staab bdfcb99781 Show more information about signer type 2025-09-22 15:09:41 -07:00
Jon Staab 38da650861 Add qr code to invite screen 2025-09-22 14:57:43 -07:00
Jon Staab dd006badfc Bring back blossom feature detection 2025-09-22 14:05:57 -07:00
Jon Staab 87e4e3fe5b Catch all upload errors 2025-09-22 11:43:44 -07:00
Jon Staab af3e38254f Fix focus on input list 2025-09-22 11:06:16 -07:00
Jon Staab 70843f54d3 Increase contrast on mention badges in editor 2025-09-22 10:40:54 -07:00
Jon Staab bda75b29b4 Handle bunker login errors better 2025-09-22 10:35:31 -07:00
Jon Staab 750830d593 Bump version again 2025-09-18 14:39:15 -07:00
Jon Staab 3c0f1a1d2f Restore icons 2025-09-18 14:13:08 -07:00
Jon Staab 4253b0ed29 Remove all icons 2025-09-18 14:12:08 -07:00
85 changed files with 1284 additions and 509 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
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_BURROW_URL=
VITE_PLATFORM_URL=https://flotilla.social
VITE_PLATFORM_TERMS=https://flotilla.social/terms
@@ -10,7 +11,7 @@ VITE_PLATFORM_ACCENT="#7161FF"
VITE_PLATFORM_SECONDARY="#EB5E28"
VITE_PLATFORM_DESCRIPTION="Flotilla is nostr — for communities."
VITE_INDEXER_RELAYS=wss://purplepag.es/,wss://relay.damus.io/,wss://relay.nostr.band/,wss://indexer.coracle.social/
VITE_SIGNER_RELAYS=wss://relay.nsec.app/,wss://bucket.coracle.social/
VITE_SIGNER_RELAYS=wss://relay.nsec.app/,wss://offchain.pub/
VITE_NOTIFIER_PUBKEY=27b7c2ed89ef78322114225ea3ebf5f72c7767c2528d4d0c1854d039c00085df
VITE_NOTIFIER_RELAY=wss://anchor.coracle.social/
VITE_VAPID_PUBLIC_KEY=BIt2D4BdgdbCowD_0d3Np6GbrIGHxd7aIEUeZNe3hQuRlHz02OhzvDaai0XSFoJYVzSzdMjdyW-QhvW9_yq8j4Y
+18
View File
@@ -1,5 +1,23 @@
# Changelog
# 1.3.0
* Add optional badge and sound for notifications
* Improve link rendering
* Remove imgproxy
* Bring back blossom feature detection for spaces
* Improve light theme
* Add more info to signer status
* Simplify navigation for adding a space
* Add ability to scan QR code for invite links
* Streamline wallet setup and move receive address setting
* Remove indexeddb on mobile, use capacitor file storage API
* Fix duplicate DMs showing up
# 1.2.5
* Fix icons in build
# 1.2.4
* Add direct message alerts
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "social.flotilla"
minSdk rootProject.ext.minSdkVersion
targetSdk rootProject.ext.targetSdkVersion
versionCode 25
versionName "1.2.4"
versionCode 27
versionName "1.3.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
+1
View File
@@ -11,6 +11,7 @@ apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-community-safe-area')
implementation project(':capacitor-app')
implementation project(':capacitor-filesystem')
implementation project(':capacitor-keyboard')
implementation project(':capacitor-preferences')
implementation project(':capacitor-push-notifications')
+3
View File
@@ -8,6 +8,9 @@ project(':capacitor-community-safe-area').projectDir = new File('../node_modules
include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/app/android')
include ':capacitor-filesystem'
project(':capacitor-filesystem').projectDir = new File('../node_modules/.pnpm/@capacitor+filesystem@7.1.4_@capacitor+core@7.2.0/node_modules/@capacitor/filesystem/android')
include ':capacitor-keyboard'
project(':capacitor-keyboard').projectDir = new File('../node_modules/.pnpm/@capacitor+keyboard@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/keyboard/android')
+8 -4
View File
@@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
2FAD9763203C412B000D30F8 /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 2FAD9762203C412B000D30F8 /* config.xml */; };
3478F0332E846FEB002431E0 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3478F0322E846FEB002431E0 /* PrivacyInfo.xcprivacy */; };
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */ = {isa = PBXBuildFile; fileRef = 50379B222058CBB4000EE86E /* capacitor.config.json */; };
504EC3081FED79650016851F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504EC3071FED79650016851F /* AppDelegate.swift */; };
504EC30D1FED79650016851F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30B1FED79650016851F /* Main.storyboard */; };
@@ -21,6 +22,7 @@
051414282E0CC28400BE0BC8 /* Flotilla Chat.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Flotilla Chat.entitlements"; sourceTree = "<group>"; };
1F53EE54954731A2328CBC4B /* Pods-Flotilla Chat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Flotilla Chat.release.xcconfig"; path = "Pods/Target Support Files/Pods-Flotilla Chat/Pods-Flotilla Chat.release.xcconfig"; sourceTree = "<group>"; };
2FAD9762203C412B000D30F8 /* config.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = config.xml; sourceTree = "<group>"; };
3478F0322E846FEB002431E0 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
504EC3041FED79650016851F /* Flotilla Chat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Flotilla Chat.app"; sourceTree = BUILT_PRODUCTS_DIR; };
504EC3071FED79650016851F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@@ -58,6 +60,7 @@
504EC2FB1FED79650016851F = {
isa = PBXGroup;
children = (
3478F0322E846FEB002431E0 /* PrivacyInfo.xcprivacy */,
051414282E0CC28400BE0BC8 /* Flotilla Chat.entitlements */,
504EC3061FED79650016851F /* App */,
504EC3051FED79650016851F /* Products */,
@@ -162,6 +165,7 @@
buildActionMask = 2147483647;
files = (
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */,
3478F0332E846FEB002431E0 /* PrivacyInfo.xcprivacy in Resources */,
50B271D11FEDC1A000F3C39B /* public in Resources */,
504EC30F1FED79650016851F /* Assets.xcassets in Resources */,
50379B232058CBB4000EE86E /* capacitor.config.json in Resources */,
@@ -354,14 +358,14 @@
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 17;
CURRENT_PROJECT_VERSION = 19;
DEVELOPMENT_TEAM = S26U9DYW3A;
INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.2.4;
MARKETING_VERSION = 1.3.0;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -380,14 +384,14 @@
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 17;
CURRENT_PROJECT_VERSION = 19;
DEVELOPMENT_TEAM = S26U9DYW3A;
INFOPLIST_FILE = App/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 1.2.4;
MARKETING_VERSION = 1.3.0;
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
+1
View File
@@ -13,6 +13,7 @@ def capacitor_pods
pod 'CapacitorCordova', :path => '../../node_modules/.pnpm/@capacitor+ios@7.2.0_@capacitor+core@7.2.0/node_modules/@capacitor/ios'
pod 'CapacitorCommunitySafeArea', :path => '../../node_modules/.pnpm/@capacitor-community+safe-area@7.0.0-alpha.1_@capacitor+core@7.2.0/node_modules/@capacitor-community/safe-area'
pod 'CapacitorApp', :path => '../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/app'
pod 'CapacitorFilesystem', :path => '../../node_modules/.pnpm/@capacitor+filesystem@7.1.4_@capacitor+core@7.2.0/node_modules/@capacitor/filesystem'
pod 'CapacitorKeyboard', :path => '../../node_modules/.pnpm/@capacitor+keyboard@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/keyboard'
pod 'CapacitorPreferences', :path => '../../node_modules/.pnpm/@capacitor+preferences@7.0.2_@capacitor+core@7.2.0/node_modules/@capacitor/preferences'
pod 'CapacitorPushNotifications', :path => '../../node_modules/.pnpm/@capacitor+push-notifications@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/push-notifications'
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>
+13 -12
View File
@@ -1,6 +1,6 @@
{
"name": "flotilla",
"version": "1.2.4",
"version": "1.3.0",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -43,6 +43,7 @@
"@capacitor/app": "^7.0.0",
"@capacitor/cli": "^7.0.0",
"@capacitor/core": "^7.0.1",
"@capacitor/filesystem": "^7.0.0",
"@capacitor/ios": "^7.0.0",
"@capacitor/keyboard": "^7.0.0",
"@capacitor/preferences": "^7.0.2",
@@ -58,17 +59,17 @@
"@types/throttle-debounce": "^5.0.2",
"@vite-pwa/assets-generator": "^0.2.6",
"@vite-pwa/sveltekit": "^0.6.6",
"@welshman/app": "^0.4.7",
"@welshman/content": "^0.4.7",
"@welshman/editor": "^0.4.7",
"@welshman/feeds": "^0.4.7",
"@welshman/lib": "^0.4.7",
"@welshman/net": "^0.4.7",
"@welshman/relay": "^0.4.7",
"@welshman/router": "^0.4.7",
"@welshman/signer": "^0.4.7",
"@welshman/store": "^0.4.7",
"@welshman/util": "^0.4.7",
"@welshman/app": "^0.5.1",
"@welshman/content": "^0.5.1",
"@welshman/editor": "^0.5.1",
"@welshman/feeds": "^0.5.1",
"@welshman/lib": "^0.5.1",
"@welshman/net": "^0.5.1",
"@welshman/relay": "^0.5.1",
"@welshman/router": "^0.5.1",
"@welshman/signer": "^0.5.1",
"@welshman/store": "^0.5.1",
"@welshman/util": "^0.5.1",
"compressorjs": "^1.2.1",
"daisyui": "^4.12.10",
"date-picker-svelte": "^2.13.0",
+174 -157
View File
@@ -23,6 +23,9 @@ importers:
'@capacitor/core':
specifier: ^7.0.1
version: 7.2.0
'@capacitor/filesystem':
specifier: ^7.0.0
version: 7.1.4(@capacitor/core@7.2.0)
'@capacitor/ios':
specifier: ^7.0.0
version: 7.2.0(@capacitor/core@7.2.0)
@@ -69,38 +72,38 @@ importers:
specifier: ^0.6.6
version: 0.6.8(@sveltejs/kit@2.20.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.25.10)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0)))(svelte@5.25.10)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0)))(@vite-pwa/assets-generator@0.2.6)(vite-plugin-pwa@0.21.2(@vite-pwa/assets-generator@0.2.6)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0))(workbox-build@7.3.0)(workbox-window@7.3.0))
'@welshman/app':
specifier: ^0.4.7
version: 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
specifier: ^0.5.1
version: 0.5.1(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/content':
specifier: ^0.4.7
version: 0.4.7(typescript@5.8.3)
specifier: ^0.5.1
version: 0.5.1(typescript@5.8.3)
'@welshman/editor':
specifier: ^0.4.7
version: 0.4.7(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)
specifier: ^0.5.1
version: 0.5.1(@tiptap/extension-image@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.2)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)
'@welshman/feeds':
specifier: ^0.4.7
version: 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
specifier: ^0.5.1
version: 0.5.1(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/lib':
specifier: ^0.4.7
version: 0.4.7
specifier: ^0.5.1
version: 0.5.1
'@welshman/net':
specifier: ^0.4.7
version: 0.4.7(typescript@5.8.3)(ws@8.18.3)
specifier: ^0.5.1
version: 0.5.1(typescript@5.8.3)(ws@8.18.3)
'@welshman/relay':
specifier: ^0.4.7
version: 0.4.7(typescript@5.8.3)
specifier: ^0.5.1
version: 0.5.1(typescript@5.8.3)
'@welshman/router':
specifier: ^0.4.7
version: 0.4.7(typescript@5.8.3)
specifier: ^0.5.1
version: 0.5.1(typescript@5.8.3)
'@welshman/signer':
specifier: ^0.4.7
version: 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
specifier: ^0.5.1
version: 0.5.1(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/store':
specifier: ^0.4.7
version: 0.4.7(typescript@5.8.3)
specifier: ^0.5.1
version: 0.5.1(typescript@5.8.3)
'@welshman/util':
specifier: ^0.4.7
version: 0.4.7(typescript@5.8.3)
specifier: ^0.5.1
version: 0.5.1(typescript@5.8.3)
compressorjs:
specifier: ^1.2.1
version: 1.2.1
@@ -754,6 +757,11 @@ packages:
'@capacitor/core@7.2.0':
resolution: {integrity: sha512-2zCnA6RJeZ9ec4470o8QMZEQTWpekw9FNoqm5TLc10jeCrhvHVI8MPgxdZVc3mOdFlyieYu4AS1fNxSqbS57Pw==}
'@capacitor/filesystem@7.1.4':
resolution: {integrity: sha512-BdUnOVulHAtruW2GeC9o7e9LO5aFcVYqNn3dLypJSOck/WipUFNfI0QWoUS0FVGeqBbDJgFGi3zjXJx0lzbDkA==}
peerDependencies:
'@capacitor/core': '>=7.0.0'
'@capacitor/ios@7.2.0':
resolution: {integrity: sha512-MQgRZcXZpbpjN83bjkGrzQd7s3XeHBZplmWf38/msF/siMGJKLrXNmNzmmPIWA5Xpi/aH6UoJFk1wXuU2U+zMg==}
peerDependencies:
@@ -774,6 +782,9 @@ packages:
peerDependencies:
'@capacitor/core': '>=7.0.0'
'@capacitor/synapse@1.0.4':
resolution: {integrity: sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==}
'@capawesome/capacitor-android-dark-mode-support@7.0.0':
resolution: {integrity: sha512-xLa988v9MEnFZj7Aje3v2vvYbs5U3zk8vaO24OioXUV1ZPjGy4R2VkdoKG8fTsSYRdF6s4/8DpCuos8JLJqpjw==}
peerDependencies:
@@ -1089,8 +1100,8 @@ packages:
'@noble/curves@1.2.0':
resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==}
'@noble/curves@1.9.2':
resolution: {integrity: sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==}
'@noble/curves@1.9.7':
resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==}
engines: {node: ^14.21.3 || >=16}
'@noble/hashes@1.3.1':
@@ -1419,77 +1430,77 @@ packages:
peerDependencies:
'@tiptap/pm': ^2.7.0
'@tiptap/extension-code-block@2.26.1':
resolution: {integrity: sha512-/TDDOwONl0qEUc4+B6V9NnWtSjz95eg7/8uCb8Y8iRbGvI9vT4/znRKofFxstvKmW4URu/H74/g0ywV57h0B+A==}
'@tiptap/extension-code-block@2.26.2':
resolution: {integrity: sha512-MJZ4QtziIWJ1zuSW2ogAHv+UHGk3DvGbVi+Dfmo0ybonXX7vRVHE+3qT7OcdTRBF+pC2oCnsjzqwFcGBP3BbZw==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
'@tiptap/extension-code@2.26.1':
resolution: {integrity: sha512-GU9deB1A/Tr4FMPu71CvlcjGKwRhGYz60wQ8m4aM+ELZcVIcZRa1ebR8bExRIEWnvRztQuyRiCQzw2N0xQJ1QQ==}
'@tiptap/extension-code@2.26.2':
resolution: {integrity: sha512-xnKJvzlAp75dheyaK5tLKAksHf9PtSr8a7OuPjf2IXS5K+QMtnwxx7KAHHijmecfWjLR0wyu9AvT/FWFfKi5LQ==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-document@2.26.1':
resolution: {integrity: sha512-2P2IZp1NRAE+21mRuFBiP3X2WKfZ6kUC23NJKpn8bcOamY3obYqCt0ltGPhE4eR8n8QAl2fI/3jIgjR07dC8ow==}
'@tiptap/extension-document@2.26.2':
resolution: {integrity: sha512-s0/P3A8zxWL/h3e20xWMTT/rcwD0+57I6mT9JgNBPtvhPePy8d698G6/qFK+x+GdIyjJylfsq2BrSE9H+QhIBg==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-dropcursor@2.26.1':
resolution: {integrity: sha512-JkDQU2ZYFOuT5mNYb8OiWGwD1HcjbtmX8tLNugQbToECmz9WvVPqJmn7V/q8VGpP81iEECz/IsyRmuf2kSD4uA==}
'@tiptap/extension-dropcursor@2.26.2':
resolution: {integrity: sha512-o5j4Gkurb/WBu1wP2tihYnZ8dENzmlxFWWMx++g6abEpn9xdud7VxHT5Ny7mBSBptI8uMwKT53weYC0on38n3g==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
'@tiptap/extension-gapcursor@2.26.1':
resolution: {integrity: sha512-KOiMZc3PwJS3hR0nSq5d0TJi2jkNZkLZElcT6pCEnhRHzPH6dRMu9GM5Jj798ZRUy0T9UFcKJalFZaDxnmRnpg==}
'@tiptap/extension-gapcursor@2.26.2':
resolution: {integrity: sha512-a68mi8V0mh058UrBIk23f50K5JGVeRZnF6ViptIleAD/Ny1K6VLjGCz6k190de+Tb9tnQLPEwwwDcy+ZnvCmYQ==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
'@tiptap/extension-hard-break@2.26.1':
resolution: {integrity: sha512-d6uStdNKi8kjPlHAyO59M6KGWATNwhLCD7dng0NXfwGndc22fthzIk/6j9F6ltQx30huy5qQram6j3JXwNACoA==}
'@tiptap/extension-hard-break@2.26.2':
resolution: {integrity: sha512-OLpeTey7p3ChyEsABLPvNv7rD/8E4k1JTt+H+MUjyL0dnrZuIWluckUJCJKnV8PhR9Mifngk1MTFUilpooiv1g==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-history@2.26.1':
resolution: {integrity: sha512-m6YR1gkkauIDo3PRl0gP+7Oc4n5OqDzcjVh6LvWREmZP8nmi94hfseYbqOXUb6RPHIc0JKF02eiRifT4MSd2nw==}
'@tiptap/extension-history@2.26.2':
resolution: {integrity: sha512-X/cu79AV5D2Z1QtuvKo/4/Rgl/Uti/n5V3QgCxFLQRCKTxHOCis+RlBCjBfOPztJX4T9QUE6lq20KqB47rsNwQ==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
'@tiptap/extension-image@2.26.1':
resolution: {integrity: sha512-96+MaYBJebQlR/ik5W72GLUfXdEoxFs+6jsoERxbM5qEdhb7TEnodBFtWZOwgDO27kFd6rSNZuW9r5KJNtljEg==}
'@tiptap/extension-image@2.26.2':
resolution: {integrity: sha512-3gK+ETLiWGAUdyPDXDheNJ38OgQabSzZJ+1nQo9KWjI7P3LQ7/ctxLtT+hAFpxX0qMK4bnu5vZaItSXxE3ZtpQ==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-link@2.26.1':
resolution: {integrity: sha512-7yfum5Jymkue/uOSTQPt2SmkZIdZx7t3QhZLqBU7R9ettkdSCBgEGok6N+scJM1R1Zes+maSckLm0JZw5BKYNA==}
'@tiptap/extension-link@2.26.2':
resolution: {integrity: sha512-rzYxx5wI1551ubPfW2pJ3V957cX/WAmbUI3q8Un+LlOsSmbddl+5BjlF5t/vl/pwaOv7FJAz9e29n877zkGOVQ==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
'@tiptap/extension-paragraph@2.26.1':
resolution: {integrity: sha512-UezvM9VDRAVJlX1tykgHWSD1g3MKfVMWWZ+Tg+PE4+kizOwoYkRWznVPgCAxjmyHajxpCKRXgqTZkOxjJ9Kjzg==}
'@tiptap/extension-paragraph@2.26.2':
resolution: {integrity: sha512-dccyffm95nNT9arjxGOyv4/cOPF4XS5Osylccp9KYLrmiSTXEuzVIYtMXhXbc0UUdABGvbFQWi88tRxgeDTkgA==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-placeholder@2.26.1':
resolution: {integrity: sha512-MBlqbkd+63btY7Qu+SqrXvWjPwooGZDsLTtl7jp52BczBl61cq9yygglt9XpM11TFMBdySgdLHBrLtQ0B7fBlw==}
'@tiptap/extension-placeholder@2.26.2':
resolution: {integrity: sha512-XBTDcpEo7Zo/1+RhGnRxA2TF0elQW7EayUcV+lJ3f7HQ5lrb5NTnakYc1ydeZ8Ih6vUqbK2CQUsESe3UWHHgHg==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
'@tiptap/extension-text@2.26.1':
resolution: {integrity: sha512-p2n8WVMd/2vckdJlol24acaTDIZAhI7qle5cM75bn01sOEZoFlSw6SwINOULrUCzNJsYb43qrLEibZb4j2LeQw==}
'@tiptap/extension-text@2.26.2':
resolution: {integrity: sha512-Rb8Le/Li+EixQNc/pGkEJpLjozTjWYP9glaYfnjPtRVw4tHcd7khVm5mer0TQjonbBUjVC1zSuXv9gifXOv6DQ==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm@2.12.0':
resolution: {integrity: sha512-TNzVwpeNzFfHAcYTOKqX9iU4fRxliyoZrCnERR+RRzeg7gWrXrCLubQt1WEx0sojMAfznshSL3M5HGsYjEbYwA==}
'@tiptap/suggestion@2.26.1':
resolution: {integrity: sha512-iNWJdQN7h01keNoVwyCsdI7ZX11YkrexZjCnutWK17Dd72s3NYVTmQXu7saftwddT4nDdlczNxAFosrt0zMhcg==}
'@tiptap/suggestion@2.26.2':
resolution: {integrity: sha512-BtigI3xOJQbdNh2OeKN5wQDI/EPGN4GXdpoMHhENZeXKrX6PvWNaqjyLVsV3QiLgv6P352nZWcgo51wPXY2JgQ==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
@@ -1648,41 +1659,41 @@ packages:
'@vite-pwa/assets-generator':
optional: true
'@welshman/app@0.4.7':
resolution: {integrity: sha512-JEgr3NhzDLeOoTSZ4+AESKhW+kwqYClLbLd6ccHV+Wa7hjoPJy2FlY3rDuppw9QSeTNCDEOvqCsjeWYz83lUBA==}
'@welshman/app@0.5.1':
resolution: {integrity: sha512-/7xRGdBQyCDh+av1yDSMEHeyQQy61IZATBUxaGT/0tjFObsBoiYtLSg6iWFr9b5eo9LYVi54EJOo1PX2PdzvTg==}
'@welshman/content@0.4.7':
resolution: {integrity: sha512-PF2FqiE3QUybl0CwwaEI2aGCZIis2rbdvBgeafgPiHW7fRYROxUZdtcMy+MyiRfiV40uoBcYwu/N6hHCRU7Pag==}
'@welshman/content@0.5.1':
resolution: {integrity: sha512-Ft41yd5fdNgn+ZGO21zV2SSuwBGaKUw1l9HfpPvFbf+Ef+PYtRclOCIkZ3uo4OSASJu06lbumnBChgfkGn6AxA==}
'@welshman/editor@0.4.7':
resolution: {integrity: sha512-K6XCLG+vVvVeEYPx+m/+Lfx/JO+NtvvIemCds6gFjY7ac+WaQuEQDP3kFGYQu69XiFXp/UZrIa+t7SezEpnU8A==}
'@welshman/editor@0.5.1':
resolution: {integrity: sha512-JuM2Sb9XaXLDNxEnmMVGzPZfkYXl8BicHXua4dETmhPuZV23UPTuv2m7y0K+yW56AStRnBtjgSzVanK/pJW5Xg==}
'@welshman/feeds@0.4.7':
resolution: {integrity: sha512-aZQuTUD4aSkL0s2BkjwEpo5KTd9BKf/XiOssQrltLdc8NIsz8RIO0XLgCpFb0/dmqHRoJEqU/plIBy7AlleRCQ==}
'@welshman/feeds@0.5.1':
resolution: {integrity: sha512-9QOJERYlkmlQsiQH0OHA0CvoaE1r8lC2GsWn5cXv1XoskyR5q3HghKRC9MUtyx/vnO45irmy9SdBkVHz7WqXjg==}
'@welshman/lib@0.4.7':
resolution: {integrity: sha512-VP3WO2ROo5pf2vHwnrdt6lQVTc8Eo52Ie+1/9ZzfTrSxtLrreSSxW3H+1oPDbHl3FXbDnQWdFWbxys6OxzKZWw==}
'@welshman/lib@0.5.1':
resolution: {integrity: sha512-XPttF23jzwwgdmd83d+AZPCxZbkoQfJhn6p+w3hr5gdvunmDp3xdBf+fegnQYv/HIDBMQOHzyM/XxUYmqNDheA==}
engines: {node: '>=12.0.0'}
'@welshman/net@0.4.7':
resolution: {integrity: sha512-S0dGVqNAfo5cvBxIuaI2oEX0JUs2FuzkOcYXkMNB1plWzi1mBcskNCBWfFf/zHJYaqUoYjZ/tDARy64lge9m/w==}
'@welshman/net@0.5.1':
resolution: {integrity: sha512-NSgAgiJhJvme9ov2/JP+ehuao/WMpjcQNFkshGaoe+PtzpwMsBSz9rEiwuPpZwsz3/edPdv8DgJwOzL6rQM+bw==}
'@welshman/relay@0.4.7':
resolution: {integrity: sha512-FkqYswNA3uT1NeJVHdEZ7p9jEPGCFMx4ci2y+h9o25rCDdxg3WUhqDSdc5d85sGTO0qG2pNnvNMfS/Du/nFlOA==}
'@welshman/relay@0.5.1':
resolution: {integrity: sha512-yyKvJt/3Vf2at7bqrGKPawHnuOVS1BKrMN8zyFLa49ntNi394+foWDkmMBEUZMdJBVVK5YVk/SQ0rJ2RkRj/gA==}
'@welshman/router@0.4.7':
resolution: {integrity: sha512-HnB1qrKGNxL8HtC6p47yHUnaDHevi+IKtqWEVCIFMRf17GwINBc3wp3+d3pu9KBBXItFZz1awABvZK+pNKQcgg==}
'@welshman/router@0.5.1':
resolution: {integrity: sha512-jH9pNSc8C1yYhEjN3Y0b8SMPg7V3Pqa0QNEKplk/TqdXoMCRQhM1aez+DXXwp7lA1q7GAqfViQ//0DMYrvAS6A==}
'@welshman/signer@0.4.7':
resolution: {integrity: sha512-V5Jdmblb2kPO5bAv1CzVrodZiwKpYYotmS6MFXfWmyrKKp+9B5KoMWzXt6yfd4HWYbEKEjLhbm1gzbckupW8Nw==}
'@welshman/signer@0.5.1':
resolution: {integrity: sha512-Ok8qUD7Mp7fHYXUxDnw0k0/fkiBxNomPXlt7EQ16RZ8ND9QayhP4U+MCXUGfIqzh89cPtwBnhZYofKsqaSWD0Q==}
peerDependencies:
nostr-signer-capacitor-plugin: ~0.0.4
'@welshman/store@0.4.7':
resolution: {integrity: sha512-8PniW1AOOYFtLRYMuay62taumW7zgwtBmouwoMh08fBQjLb+c90V4g2cEGVWoyvKXSLzQkQppPlaqYzdSDqgwg==}
'@welshman/store@0.5.1':
resolution: {integrity: sha512-wuQt/RGMk1dkOfImGfnCm+dDMI0+6mFIRXC17nC3v8TT/k0HUY8+JXPgYo9PP5E98YK3zFpmdeqFmxj4Q4puGA==}
'@welshman/util@0.4.7':
resolution: {integrity: sha512-FlmBiZeKlAEAAwyhu7cWtlfAxU3CWX7WQGn0NkCZaAjgGV3n8LIDjT1u9m1PmXirBT0+qFNGLWas3p72IQMLgg==}
'@welshman/util@0.5.1':
resolution: {integrity: sha512-8Hn0ONx2yc3l3D/7DMFkqIPlMU1b7CeMmEz+np43f5+X5y7iWJ3LGau2nJ6lSbIyM1WaxFl3++W691+bFPNz3Q==}
'@xml-tools/parser@1.0.11':
resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==}
@@ -3079,8 +3090,8 @@ packages:
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
hasBin: true
js-base64@3.7.7:
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
js-base64@3.7.8:
resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -3185,8 +3196,8 @@ packages:
linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
linkifyjs@4.3.1:
resolution: {integrity: sha512-DRSlB9DKVW04c4SUdGvKK5FR6be45lTU9M76JnngqPeeGDqPwYc0zdUErtsNVMtxPXgUWV4HbXbnC4sNyBxkYg==}
linkifyjs@4.3.2:
resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==}
load-json-file@4.0.0:
resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
@@ -3430,8 +3441,8 @@ packages:
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
engines: {node: '>=0.10.0'}
nostr-editor@1.0.0:
resolution: {integrity: sha512-+TL3G0m7WsXeEAitxzQhun7hyARxqRANjGIS2z9CBbniCGvT/Wz6YLgUnUysnBg3tmSgMZg5FWhaDPwfvdvbSw==}
nostr-editor@1.0.1:
resolution: {integrity: sha512-HXqXjxtIN0CcC7sLV5xYjEsQF0bFYLmNKxS75ya2yZGQ/z16U+uK6bb2Hd72QyqXlHXyWN0m24E5Gcws8/NhRQ==}
engines: {node: '>=18.16.1'}
peerDependencies:
'@tiptap/core': ^2.6.6
@@ -5601,6 +5612,11 @@ snapshots:
dependencies:
tslib: 2.8.1
'@capacitor/filesystem@7.1.4(@capacitor/core@7.2.0)':
dependencies:
'@capacitor/core': 7.2.0
'@capacitor/synapse': 1.0.4
'@capacitor/ios@7.2.0(@capacitor/core@7.2.0)':
dependencies:
'@capacitor/core': 7.2.0
@@ -5617,6 +5633,8 @@ snapshots:
dependencies:
'@capacitor/core': 7.2.0
'@capacitor/synapse@1.0.4': {}
'@capawesome/capacitor-android-dark-mode-support@7.0.0(@capacitor/core@7.2.0)':
dependencies:
'@capacitor/core': 7.2.0
@@ -5939,7 +5957,7 @@ snapshots:
dependencies:
'@noble/hashes': 1.3.2
'@noble/curves@1.9.2':
'@noble/curves@1.9.7':
dependencies:
'@noble/hashes': 1.8.0
@@ -6237,58 +6255,58 @@ snapshots:
dependencies:
'@tiptap/pm': 2.12.0
'@tiptap/extension-code-block@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
'@tiptap/extension-code-block@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
'@tiptap/extension-code@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
'@tiptap/extension-code@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/extension-document@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
'@tiptap/extension-document@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/extension-dropcursor@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
'@tiptap/extension-dropcursor@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
'@tiptap/extension-gapcursor@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
'@tiptap/extension-gapcursor@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
'@tiptap/extension-hard-break@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
'@tiptap/extension-hard-break@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/extension-history@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
'@tiptap/extension-history@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
'@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
'@tiptap/extension-image@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
'@tiptap/extension-link@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
linkifyjs: 4.3.1
linkifyjs: 4.3.2
'@tiptap/extension-paragraph@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
'@tiptap/extension-paragraph@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/extension-placeholder@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
'@tiptap/extension-placeholder@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
'@tiptap/extension-text@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
'@tiptap/extension-text@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
@@ -6313,7 +6331,7 @@ snapshots:
prosemirror-transform: 1.10.4
prosemirror-view: 1.39.3
'@tiptap/suggestion@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
'@tiptap/suggestion@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
@@ -6524,19 +6542,18 @@ snapshots:
optionalDependencies:
'@vite-pwa/assets-generator': 0.2.6
'@welshman/app@0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
'@welshman/app@0.5.1(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
dependencies:
'@types/throttle-debounce': 5.0.2
'@welshman/feeds': 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/lib': 0.4.7
'@welshman/net': 0.4.7(typescript@5.8.3)(ws@8.18.3)
'@welshman/relay': 0.4.7(typescript@5.8.3)
'@welshman/router': 0.4.7(typescript@5.8.3)
'@welshman/signer': 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/store': 0.4.7(typescript@5.8.3)
'@welshman/util': 0.4.7(typescript@5.8.3)
'@welshman/feeds': 0.5.1(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/lib': 0.5.1
'@welshman/net': 0.5.1(typescript@5.8.3)(ws@8.18.3)
'@welshman/relay': 0.5.1(typescript@5.8.3)
'@welshman/router': 0.5.1(typescript@5.8.3)
'@welshman/signer': 0.5.1(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/store': 0.5.1(typescript@5.8.3)
'@welshman/util': 0.5.1(typescript@5.8.3)
fuse.js: 7.1.0
idb: 8.0.2
svelte: 4.2.20
throttle-debounce: 5.0.2
transitivePeerDependencies:
@@ -6544,31 +6561,31 @@ snapshots:
- typescript
- ws
'@welshman/content@0.4.7(typescript@5.8.3)':
'@welshman/content@0.5.1(typescript@5.8.3)':
dependencies:
'@braintree/sanitize-url': 7.1.1
nostr-tools: 2.14.2(typescript@5.8.3)
transitivePeerDependencies:
- typescript
'@welshman/editor@0.4.7(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)':
'@welshman/editor@0.5.1(@tiptap/extension-image@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.2)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)':
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/extension-code': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-code-block': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-document': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-dropcursor': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-gapcursor': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-hard-break': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-history': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-paragraph': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-placeholder': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-text': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-code': 2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-code-block': 2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-document': 2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-dropcursor': 2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-gapcursor': 2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-hard-break': 2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-history': 2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-paragraph': 2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-placeholder': 2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-text': 2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/pm': 2.12.0
'@tiptap/suggestion': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@welshman/lib': 0.4.7
'@welshman/util': 0.4.7(typescript@5.8.3)
nostr-editor: 1.0.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(linkifyjs@4.3.1)(nostr-tools@2.14.2(typescript@5.8.3))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))
'@tiptap/suggestion': 2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@welshman/lib': 0.5.1
'@welshman/util': 0.5.1(typescript@5.8.3)
nostr-editor: 1.0.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-image@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(linkifyjs@4.3.2)(nostr-tools@2.14.2(typescript@5.8.3))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))
nostr-tools: 2.14.2(typescript@5.8.3)
tippy.js: 6.3.7
transitivePeerDependencies:
@@ -6582,79 +6599,79 @@ snapshots:
- tiptap-markdown
- typescript
'@welshman/feeds@0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
'@welshman/feeds@0.5.1(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
dependencies:
'@welshman/lib': 0.4.7
'@welshman/net': 0.4.7(typescript@5.8.3)(ws@8.18.3)
'@welshman/relay': 0.4.7(typescript@5.8.3)
'@welshman/router': 0.4.7(typescript@5.8.3)
'@welshman/signer': 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/util': 0.4.7(typescript@5.8.3)
'@welshman/lib': 0.5.1
'@welshman/net': 0.5.1(typescript@5.8.3)(ws@8.18.3)
'@welshman/relay': 0.5.1(typescript@5.8.3)
'@welshman/router': 0.5.1(typescript@5.8.3)
'@welshman/signer': 0.5.1(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
'@welshman/util': 0.5.1(typescript@5.8.3)
trava: 1.2.1
transitivePeerDependencies:
- nostr-signer-capacitor-plugin
- typescript
- ws
'@welshman/lib@0.4.7':
'@welshman/lib@0.5.1':
dependencies:
'@scure/base': 1.2.6
'@types/events': 3.0.3
events: 3.3.0
'@welshman/net@0.4.7(typescript@5.8.3)(ws@8.18.3)':
'@welshman/net@0.5.1(typescript@5.8.3)(ws@8.18.3)':
dependencies:
'@welshman/lib': 0.4.7
'@welshman/relay': 0.4.7(typescript@5.8.3)
'@welshman/util': 0.4.7(typescript@5.8.3)
'@welshman/lib': 0.5.1
'@welshman/relay': 0.5.1(typescript@5.8.3)
'@welshman/util': 0.5.1(typescript@5.8.3)
events: 3.3.0
isomorphic-ws: 5.0.0(ws@8.18.3)
transitivePeerDependencies:
- typescript
- ws
'@welshman/relay@0.4.7(typescript@5.8.3)':
'@welshman/relay@0.5.1(typescript@5.8.3)':
dependencies:
'@welshman/lib': 0.4.7
'@welshman/util': 0.4.7(typescript@5.8.3)
'@welshman/lib': 0.5.1
'@welshman/util': 0.5.1(typescript@5.8.3)
transitivePeerDependencies:
- typescript
'@welshman/router@0.4.7(typescript@5.8.3)':
'@welshman/router@0.5.1(typescript@5.8.3)':
dependencies:
'@welshman/lib': 0.4.7
'@welshman/relay': 0.4.7(typescript@5.8.3)
'@welshman/util': 0.4.7(typescript@5.8.3)
'@welshman/lib': 0.5.1
'@welshman/relay': 0.5.1(typescript@5.8.3)
'@welshman/util': 0.5.1(typescript@5.8.3)
transitivePeerDependencies:
- typescript
'@welshman/signer@0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
'@welshman/signer@0.5.1(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
dependencies:
'@noble/curves': 1.9.2
'@noble/curves': 1.9.7
'@noble/hashes': 1.8.0
'@welshman/lib': 0.4.7
'@welshman/net': 0.4.7(typescript@5.8.3)(ws@8.18.3)
'@welshman/util': 0.4.7(typescript@5.8.3)
'@welshman/lib': 0.5.1
'@welshman/net': 0.5.1(typescript@5.8.3)(ws@8.18.3)
'@welshman/util': 0.5.1(typescript@5.8.3)
nostr-signer-capacitor-plugin: 0.0.4(@capacitor/core@7.2.0)
nostr-tools: 2.14.2(typescript@5.8.3)
transitivePeerDependencies:
- typescript
- ws
'@welshman/store@0.4.7(typescript@5.8.3)':
'@welshman/store@0.5.1(typescript@5.8.3)':
dependencies:
'@welshman/lib': 0.4.7
'@welshman/relay': 0.4.7(typescript@5.8.3)
'@welshman/util': 0.4.7(typescript@5.8.3)
'@welshman/lib': 0.5.1
'@welshman/relay': 0.5.1(typescript@5.8.3)
'@welshman/util': 0.5.1(typescript@5.8.3)
svelte: 4.2.20
transitivePeerDependencies:
- typescript
'@welshman/util@0.4.7(typescript@5.8.3)':
'@welshman/util@0.5.1(typescript@5.8.3)':
dependencies:
'@types/ws': 8.18.1
'@welshman/lib': 0.4.7
js-base64: 3.7.7
'@welshman/lib': 0.5.1
js-base64: 3.7.8
nostr-tools: 2.14.2(typescript@5.8.3)
nostr-wasm: 0.1.0
transitivePeerDependencies:
@@ -8174,7 +8191,7 @@ snapshots:
jiti@1.21.7: {}
js-base64@3.7.7: {}
js-base64@3.7.8: {}
js-tokens@4.0.0: {}
@@ -8249,7 +8266,7 @@ snapshots:
dependencies:
uc.micro: 2.1.0
linkifyjs@4.3.1: {}
linkifyjs@4.3.2: {}
load-json-file@4.0.0:
dependencies:
@@ -8485,15 +8502,15 @@ snapshots:
normalize-range@0.1.2: {}
nostr-editor@1.0.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(linkifyjs@4.3.1)(nostr-tools@2.14.2(typescript@5.8.3))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))):
nostr-editor@1.0.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-image@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(linkifyjs@4.3.2)(nostr-tools@2.14.2(typescript@5.8.3))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))):
dependencies:
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
'@tiptap/extension-image': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-link': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/extension-image': 2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
'@tiptap/extension-link': 2.26.2(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
'@tiptap/pm': 2.12.0
js-base64: 3.7.7
js-base64: 3.7.8
light-bolt11-decoder: 3.2.0
linkifyjs: 4.3.1
linkifyjs: 4.3.2
nostr-tools: 2.14.2(typescript@5.8.3)
prosemirror-markdown: 1.13.2
prosemirror-model: 1.25.1
+4 -8
View File
@@ -62,6 +62,8 @@
--primary-content: oklch(var(--pc));
--secondary: oklch(var(--s));
--secondary-content: oklch(var(--sc));
--neutral: oklch(var(--n));
--neutral-content: oklch(var(--nc));
}
/* safe area insets */
@@ -215,12 +217,6 @@
@apply ellipsize;
}
@media (max-width: 639px) {
[data-tip]::before {
display: none;
}
}
.content-padding-x {
@apply px-4 sm:px-8 md:px-12;
}
@@ -278,8 +274,8 @@
}
.tiptap {
--tiptap-object-bg: var(--base-100);
--tiptap-object-fg: var(--base-content);
--tiptap-object-bg: var(--neutral);
--tiptap-object-fg: var(--neutral-content);
--tiptap-active-bg: var(--primary);
--tiptap-active-fg: var(--primary-content);
}
+49 -6
View File
@@ -3,6 +3,7 @@
import {getTagValue, getAddress} from "@welshman/util"
import {isRelayFeed, findFeed} from "@welshman/feeds"
import Inbox from "@assets/icons/inbox.svg?dataurl"
import Bell from "@assets/icons/bell.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
@@ -10,8 +11,16 @@
import AlertItem from "@app/components/AlertItem.svelte"
import {pushModal} from "@app/util/modal"
import {pushToast} from "@app/util/toast"
import {alerts, dmAlert, deriveAlertStatus, userInboxRelays, getAlertFeed} from "@app/core/state"
import {
alerts,
dmAlert,
deriveAlertStatus,
userInboxRelays,
getAlertFeed,
userSettingsValues,
} from "@app/core/state"
import {deleteAlert, createDmAlert} from "@app/core/commands"
import {clearBadges} from "../util/notifications"
type Props = {
url?: string
@@ -42,11 +51,11 @@
const uncheckDmAlert = async (message: string) => {
await sleep(100)
toggle.checked = false
directMessagesNotificationToggle.checked = false
pushToast({theme: "error", message})
}
const onToggle = async () => {
const onDirectMessagesNotificationToggle = async () => {
if ($dmAlert) {
deleteAlert($dmAlert)
} else {
@@ -64,7 +73,19 @@
}
}
let toggle: HTMLInputElement
const onShowBadgeOnUnreadToggle = async () => {
$userSettingsValues.show_notifications_badge = !$userSettingsValues.show_notifications_badge
if (!$userSettingsValues.show_notifications_badge) {
await clearBadges()
}
}
const onDirectMessagesNotificationSoundToggle = async () => {
$userSettingsValues.play_notification_sound = !$userSettingsValues.play_notification_sound
}
let directMessagesNotificationToggle: HTMLInputElement
</script>
<div class="col-4">
@@ -88,14 +109,36 @@
</div>
</div>
<div class="card2 bg-alt flex flex-col gap-4 shadow-xl">
<div class="flex items-center justify-between">
<strong class="flex items-center gap-3">
<Icon icon={Bell} />
Notifications
</strong>
</div>
<div class="flex justify-between">
<p>Notify me about new direct messages</p>
<input
type="checkbox"
class="toggle toggle-primary"
bind:this={toggle}
bind:this={directMessagesNotificationToggle}
checked={Boolean($dmAlert)}
oninput={onToggle} />
oninput={onDirectMessagesNotificationToggle} />
</div>
<div class="flex justify-between">
<p>Show badge for unread direct messages</p>
<input
type="checkbox"
class="toggle toggle-primary"
checked={Boolean($userSettingsValues.show_notifications_badge)}
oninput={onShowBadgeOnUnreadToggle} />
</div>
<div class="flex justify-between">
<p>Play sound for new direct messages</p>
<input
type="checkbox"
class="toggle toggle-primary"
checked={Boolean($userSettingsValues.play_notification_sound)}
oninput={onDirectMessagesNotificationSoundToggle} />
</div>
{#if $dmStatus}
{@const status = getTagValue("status", $dmStatus.tags) || "error"}
+2 -3
View File
@@ -10,11 +10,10 @@
import {makeEditor} from "@app/editor"
type Props = {
url?: string
onSubmit: (event: EventContent) => void
}
const {onSubmit, url}: Props = $props()
const {onSubmit}: Props = $props()
const autofocus = !isMobile
@@ -39,11 +38,11 @@
}
const editor = makeEditor({
url,
autofocus,
submit,
uploading,
aggressive: true,
encryptFiles: true,
})
</script>
+2 -4
View File
@@ -69,11 +69,11 @@
if (!parsed || hideMediaAtDepth <= depth) return false
if (isLink(parsed) && $userSettingsValues.show_media && isStartOrEnd(i)) {
if (isLink(parsed) && $userSettingsValues.show_media && isStartAndEnd(i)) {
return true
}
if ((isEvent(parsed) || isAddress(parsed)) && isStartOrEnd(i)) {
if ((isEvent(parsed) || isAddress(parsed)) && isStartAndEnd(i)) {
return true
}
@@ -95,8 +95,6 @@
const isStartAndEnd = (i: number) => isStart(i) && isEnd(i)
const isStartOrEnd = (i: number) => isStart(i) || isEnd(i)
const ignoreWarning = () => {
warning = null
}
+1 -5
View File
@@ -1,6 +1,5 @@
<script lang="ts">
import type {ParsedEmojiValue} from "@welshman/content"
import {imgproxy} from "@app/core/state"
export let value: ParsedEmojiValue
@@ -8,10 +7,7 @@
</script>
{#if value.url}
<img
{alt}
src={imgproxy(value.url, {w: 24, h: 24})}
class="-mt-0.5 inline h-[1em] min-w-[1em] align-middle" />
<img {alt} src={value.url} class="-mt-0.5 inline h-[1em] min-w-[1em] align-middle" />
{:else}
{alt}
{/if}
+2 -2
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import {ellipsize, displayUrl, postJson} from "@welshman/lib"
import {dufflepud, imgproxy} from "@app/core/state"
import {dufflepud} from "@app/core/state"
import {preventDefault, stopPropagation} from "@lib/html"
import Link from "@lib/components/Link.svelte"
import ContentLinkDetail from "@app/components/ContentLinkDetail.svelte"
@@ -51,7 +51,7 @@
<img
alt="Link preview"
onerror={onError}
src={imgproxy(preview.image)}
src={preview.image}
class="bg-alt max-h-72 rounded-t-box object-contain object-center" />
{/if}
<div class="flex flex-col gap-2 p-4">
@@ -1,10 +1,17 @@
<script lang="ts">
import {onMount, onDestroy} from "svelte"
import {displayUrl} from "@welshman/lib"
import {getTags, decryptFile, getTagValue, tagsFromIMeta} from "@welshman/util"
import {
getTags,
getBlob,
decryptFile,
getTagValue,
tagsFromIMeta,
makeBlossomAuthEvent,
} from "@welshman/util"
import {signer} from "@welshman/app"
import LinkRound from "@assets/icons/link-round.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import {imgproxy} from "@app/core/state"
const {value, event, ...props} = $props()
@@ -14,18 +21,34 @@
.map(tagsFromIMeta)
.find(meta => getTagValue("url", meta) === url) || event.tags
const hash = getTagValue("x", meta)
const key = getTagValue("decryption-key", meta)
const nonce = getTagValue("decryption-nonce", meta)
const algorithm = getTagValue("encryption-algorithm", meta)
const onError = () => {
hasError = true
const onError = async () => {
// If the image failed to load, try authenticating
if (hash && $signer) {
const server = new URL(url).origin
const template = makeBlossomAuthEvent({action: "get", server, hashes: [hash]})
const authEvent = await $signer.sign(template)
const res = await getBlob(server, hash, {authEvent})
if (res.status === 200) {
src = URL.createObjectURL(await res.blob())
} else {
hasError = true
}
} else {
hasError = true
}
}
let hasError = $state(false)
let src = $state(imgproxy(url))
let src = $state("")
onMount(async () => {
// If we have an encryption algorithm, fetch and decrypt
if (algorithm === "aes-gcm" && key && nonce) {
const response = await fetch(url)
@@ -35,6 +58,8 @@
src = URL.createObjectURL(new Blob([decryptedData]))
}
} else {
src = url
}
})
@@ -48,6 +73,6 @@
<Icon icon={LinkRound} size={3} class="inline-block" />
{displayUrl(url)}
</a>
{:else}
{:else if src}
<img alt="" {src} onerror={onError} {...props} />
{/if}
+1 -1
View File
@@ -35,7 +35,7 @@
{/snippet}
</CardButton>
</Button>
<Button onclick={signUp} class="dark:btn-neutral">
<Button onclick={signUp} class="btn-neutral">
<CardButton>
{#snippet icon()}
<div><Icon icon={AddCircle} size={7} /></div>
+11 -2
View File
@@ -32,9 +32,10 @@
onNostrConnect: async (response: Nip46ResponseWithResult) => {
const pubkey = await controller.broker.getPublicKey()
loginWithNip46(pubkey, controller.clientSecret, response.event.pubkey, SIGNER_RELAYS)
await loadUserData(pubkey)
loginWithNip46(pubkey, controller.clientSecret, response.event.pubkey, SIGNER_RELAYS)
setChecked("*")
clearModals()
},
@@ -48,13 +49,20 @@
try {
const {signerPubkey, connectSecret, relays} = Nip46Broker.parseBunkerUrl($bunker)
if (!signerPubkey || relays.length === 0) {
if (!signerPubkey) {
return pushToast({
theme: "error",
message: "Sorry, it looks like that's an invalid bunker link.",
})
}
if (relays.length === 0) {
return pushToast({
theme: "error",
message: "That bunker link does not include any relays.",
})
}
controller.loading.set(true)
const {clientSecret} = controller
@@ -91,6 +99,7 @@
}
const selectConnect = () => {
controller.loading.set(false)
mode = "connect"
}
+23 -6
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
import Server from "@assets/icons/server.svg?dataurl"
import Moon from "@assets/icons/moon.svg?dataurl"
import Settings from "@assets/icons/settings-minimalistic.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl"
import Exit from "@assets/icons/logout-3.svg?dataurl"
@@ -13,13 +14,16 @@
import LogOut from "@app/components/LogOut.svelte"
import {PLATFORM_NAME} from "@app/core/state"
import {pushModal} from "@app/util/modal"
import {theme} from "@app/util/theme"
const logout = () => pushModal(LogOut)
const toggleTheme = () => theme.set($theme === "dark" ? "light" : "dark")
</script>
<div class="column menu gap-2">
<Link replaceState href="/settings/profile">
<CardButton class="dark:btn-neutral">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={UserRounded} size={7} /></div>
{/snippet}
@@ -32,7 +36,7 @@
</CardButton>
</Link>
<Link replaceState href="/settings/alerts">
<CardButton class="dark:btn-neutral">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Bell} size={7} /></div>
{/snippet}
@@ -45,7 +49,7 @@
</CardButton>
</Link>
<Link replaceState href="/settings/wallet">
<CardButton class="dark:btn-neutral">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Wallet} size={7} /></div>
{/snippet}
@@ -58,7 +62,7 @@
</CardButton>
</Link>
<Link replaceState href="/settings/relays">
<CardButton class="dark:btn-neutral">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Server} size={7} /></div>
{/snippet}
@@ -71,7 +75,7 @@
</CardButton>
</Link>
<Link replaceState href="/settings/content">
<CardButton class="dark:btn-neutral">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Settings} size={7} /></div>
{/snippet}
@@ -83,8 +87,21 @@
{/snippet}
</CardButton>
</Link>
<Button onclick={toggleTheme}>
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Moon} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Theme</div>
{/snippet}
{#snippet info()}
<div>Switch between light and dark mode</div>
{/snippet}
</CardButton>
</Button>
<Link replaceState href="/settings/about">
<CardButton class="dark:btn-neutral">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Code2} size={7} /></div>
{/snippet}
+8 -12
View File
@@ -1,15 +1,11 @@
<script lang="ts">
import Login from "@assets/icons/login-3.svg?dataurl"
import Compass from "@assets/icons/compass.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Link from "@lib/components/Link.svelte"
import Divider from "@lib/components/Divider.svelte"
import CardButton from "@lib/components/CardButton.svelte"
import MenuSpacesItem from "@app/components/MenuSpacesItem.svelte"
import SpaceAdd from "@app/components/SpaceAdd.svelte"
import {userRoomsByUrl, PLATFORM_RELAYS} from "@app/core/state"
import {pushModal} from "@app/util/modal"
const addSpace = () => pushModal(SpaceAdd)
</script>
<div class="column menu gap-2">
@@ -22,18 +18,18 @@
{/each}
<Divider />
{/if}
<Button onclick={addSpace}>
<CardButton class="dark:btn-neutral">
<Link href="/discover">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Login} size={7} /></div>
<div><Icon icon={Compass} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Add a space</div>
<div>Explore Spaces</div>
{/snippet}
{#snippet info()}
<div>Join or create a new space</div>
<div>Join create, or browse spaces</div>
{/snippet}
</CardButton>
</Button>
</Link>
{/each}
</div>
+1 -1
View File
@@ -13,7 +13,7 @@
</script>
<Link replaceState href={path}>
<CardButton class="dark:btn-neutral">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><SpaceAvatar {url} /></div>
{/snippet}
@@ -0,0 +1,38 @@
<script lang="ts">
import {onMount} from "svelte"
import {userSettingsValues} from "@app/core/state"
import {notifications} from "../util/notifications"
let audioElement: HTMLAudioElement
let enabled = $state(false)
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
enabled = true
} else {
enabled = false
}
})
let notificationCount = $state($notifications.size)
const playSound = () => {
if (enabled && $userSettingsValues.play_notification_sound) {
audioElement.play()
}
}
onMount(() => {
audioElement.load()
notifications.subscribe(notifications => {
if (notifications.size > notificationCount) {
playSound()
}
notificationCount = notifications.size
})
})
</script>
<audio bind:this={audioElement} src="/new-notification-3-398649.mp3"></audio>
+3 -5
View File
@@ -18,7 +18,7 @@
import {makeSpacePath} from "@app/util/routes"
import {notifications} from "@app/util/notifications"
import Widget from "@assets/icons/widget.svg?dataurl"
import AddSquare from "@assets/icons/add-square.svg?dataurl"
import Compass from "@assets/icons/compass.svg?dataurl"
import Letter from "@assets/icons/letter.svg?dataurl"
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import HomeSmile from "@assets/icons/home-smile.svg?dataurl"
@@ -31,8 +31,6 @@
const {children}: Props = $props()
const addSpace = () => pushModal(SpaceAdd)
const showSpacesMenu = () => (spaceUrls.length > 0 ? pushModal(MenuSpaces) : pushModal(SpaceAdd))
const showOtherSpacesMenu = () => pushModal(MenuOtherSpaces, {urls: secondarySpaceUrls})
@@ -83,8 +81,8 @@
<Avatar icon={Widget} class="!h-10 !w-10" />
</PrimaryNavItem>
{/if}
<PrimaryNavItem title="Add Space" onclick={addSpace} class="tooltip-right">
<Avatar icon={AddSquare} class="!h-10 !w-10" />
<PrimaryNavItem title="Add a Space" href="/discover" class="tooltip-right">
<Avatar icon={Compass} class="!h-10 !w-10" />
</PrimaryNavItem>
{/each}
</div>
+5 -28
View File
@@ -1,22 +1,13 @@
<script lang="ts">
import {nthNe} from "@welshman/lib"
import type {Profile} from "@welshman/util"
import {
getTag,
makeEvent,
makeProfile,
editProfile,
createProfile,
isPublishedProfile,
uniqTags,
} from "@welshman/util"
import {Router} from "@welshman/router"
import {pubkey, profilesByPubkey, publishThunk} from "@welshman/app"
import {getTag, makeProfile} from "@welshman/util"
import {pubkey, profilesByPubkey} from "@welshman/app"
import Button from "@lib/components/Button.svelte"
import ProfileEditForm from "@app/components/ProfileEditForm.svelte"
import {clearModals} from "@app/util/modal"
import {pushToast} from "@app/util/toast"
import {PROTECTED, getMembershipUrls, userMembership} from "@app/core/state"
import {PROTECTED} from "@app/core/state"
import {updateProfile} from "../core/commands"
const profile = $profilesByPubkey.get($pubkey!) || makeProfile()
const shouldBroadcast = !getTag(PROTECTED, profile.event?.tags || [])
@@ -25,21 +16,7 @@
const back = () => history.back()
const onsubmit = ({profile, shouldBroadcast}: {profile: Profile; shouldBroadcast: boolean}) => {
const router = Router.get()
const template = isPublishedProfile(profile) ? editProfile(profile) : createProfile(profile)
const scenarios = [router.FromRelays(getMembershipUrls($userMembership))]
if (shouldBroadcast) {
scenarios.push(router.FromUser(), router.Index())
template.tags = template.tags.filter(nthNe(0, "-"))
} else {
template.tags = uniqTags([...template.tags, PROTECTED])
}
const event = makeEvent(template.kind, template)
const relays = router.merge(scenarios).getUrls()
publishThunk({event, relays})
updateProfile({profile, shouldBroadcast})
pushToast({message: "Your profile has been updated!"})
clearModals()
}
+2 -2
View File
@@ -26,8 +26,8 @@
})
</script>
<Button class="max-w-full {props.class}" onclick={copy}>
<div bind:this={wrapper} style={`height: ${height}px`}>
<Button class="flex w-full justify-center {props.class}" onclick={copy}>
<div bind:this={wrapper} class="w-md" style={`height: ${height}px`}>
<canvas
class="rounded-box"
bind:this={canvas}
+41 -23
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import {spec, prop, avg} from "@welshman/lib"
import {signerLog, SignerLogEntryStatus} from "@welshman/app"
import {session, SessionMethod, signerLog, SignerLogEntryStatus} from "@welshman/app"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import ClockCircle from "@assets/icons/clock-circle.svg?dataurl"
@@ -26,27 +26,45 @@
const logout = () => pushModal(LogOut)
</script>
<div class="card2 bg-alt flex flex-col gap-4">
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between">
<span class="text-xl font-bold">Signer Status</span>
<span class="flex items-center gap-2">
{#if isDisconnected}
<Icon icon={CloseCircle} class="text-error" size={4} /> Disconnected
{:else if recentFailure > 3}
<Icon icon={Danger} class="text-warning" size={4} /> Partial Failure
{:else if recentAvg > 1000 || recentPending > 3}
<Icon icon={ClockCircle} class="text-warning" size={4} /> Slow connection
{:else if recentSuccess === 0 && recentFailure > 0}{:else}
<Icon icon={CheckCircle} class="text-success" size={4} /> Ok
{/if}
</span>
{#if $session && $session.method !== SessionMethod.Anonymous}
<div class="card2 bg-alt flex flex-col gap-4">
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between">
<span class="text-xl font-bold">Signer Status</span>
<span class="flex items-center gap-2">
{#if isDisconnected}
<Icon icon={CloseCircle} class="text-error" size={4} /> Disconnected
{:else if recentFailure > 3}
<Icon icon={Danger} class="text-warning" size={4} /> Partial Failure
{:else if recentAvg > 1000 || recentPending > 3}
<Icon icon={ClockCircle} class="text-warning" size={4} /> Slow connection
{:else if recentSuccess === 0 && recentFailure > 0}{:else}
<Icon icon={CheckCircle} class="text-success" size={4} /> Ok
{/if}
</span>
</div>
<div class="flex justify-between text-sm opacity-75">
<p>
Logged in with
{#if $session.method === SessionMethod.Nip01}
private key
{:else if $session.method === SessionMethod.Nip07}
browser extension
{:else if $session.method === SessionMethod.Nip46}
remote signer
{:else if $session.method === SessionMethod.Nip55}
{$session.signer}
{:else if $session.method === SessionMethod.Pubkey}
public key (readonly)
{/if}
</p>
<p>
{success} requests succeeded, {failure} failed, {pending} pending
</p>
</div>
</div>
<p class="text-sm opacity-75">
{success} requests succeeded, {failure} failed, {pending} pending
</p>
{#if isDisconnected}
<Button class="btn btn-outline btn-error" onclick={logout}>Logout to Reconnect</Button>
{/if}
</div>
{#if isDisconnected}
<Button class="btn btn-outline btn-error" onclick={logout}>Logout to Reconnect</Button>
{/if}
</div>
{/if}
+4 -6
View File
@@ -1,7 +1,6 @@
<script lang="ts">
import {displayUrl} from "@welshman/lib"
import {AuthStatus} from "@welshman/net"
import {waitForThunkError} from "@welshman/app"
import {preventDefault} from "@lib/html"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
@@ -15,7 +14,7 @@
import SpaceJoinConfirm, {confirmSpaceJoin} from "@app/components/SpaceJoinConfirm.svelte"
import {pushToast} from "@app/util/toast"
import {pushModal} from "@app/util/modal"
import {publishJoinRequest} from "@app/core/commands"
import {checkRelayAccess} from "@app/core/commands"
import {deriveSocket} from "@app/core/state"
type Props = {
@@ -32,11 +31,10 @@
loading = true
try {
const thunk = publishJoinRequest({url, claim})
const error = await waitForThunkError(thunk)
const message = await checkRelayAccess(url, claim)
if (error) {
return pushToast({theme: "error", message: error, timeout: 30_000})
if (message) {
return pushToast({theme: "error", message, timeout: 30_000})
}
if ($socket.auth.status === AuthStatus.None) {
+3 -18
View File
@@ -1,9 +1,7 @@
<script lang="ts">
import Compass from "@assets/icons/compass-big.svg?dataurl"
import Login from "@assets/icons/login-3.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte"
import CardButton from "@lib/components/CardButton.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
@@ -25,21 +23,8 @@
<div>Spaces are places where communities come together to work, play, and hang out.</div>
{/snippet}
</ModalHeader>
<Link href="/discover">
<CardButton class="btn-primary">
{#snippet icon()}
<div><Icon icon={Compass} size={7} /></div>
{/snippet}
{#snippet title()}
<div>Discover spaces</div>
{/snippet}
{#snippet info()}
<div>Browse spaces on the discover page.</div>
{/snippet}
</CardButton>
</Link>
<Button onclick={startJoin}>
<CardButton class="dark:btn-neutral">
<CardButton class="btn-primary">
{#snippet icon()}
<div><Icon icon={Login} size={7} /></div>
{/snippet}
@@ -47,12 +32,12 @@
<div>Join a space</div>
{/snippet}
{#snippet info()}
<div>Enter an invite code or url to join an existing space.</div>
<div>Enter an invite link to join an existing space.</div>
{/snippet}
</CardButton>
</Button>
<Button onclick={startCreate}>
<CardButton class="dark:btn-neutral">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={AddCircle} size={7} /></div>
{/snippet}
+6 -5
View File
@@ -3,15 +3,15 @@
import {sleep, nthEq} from "@welshman/lib"
import {request} from "@welshman/net"
import {displayRelayUrl, AUTH_INVITE} from "@welshman/util"
import {slide} from "@lib/transition"
import LinkRound from "@assets/icons/link-round.svg?dataurl"
import Copy from "@assets/icons/copy.svg?dataurl"
import Spinner from "@lib/components/Spinner.svelte"
import Field from "@lib/components/Field.svelte"
import Button from "@lib/components/Button.svelte"
import LinkRound from "@assets/icons/link-round.svg?dataurl"
import Copy from "@assets/icons/copy.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import QRCode from "@app/components/QRCode.svelte"
import {clip} from "@app/util/toast"
import {PLATFORM_URL} from "@app/core/state"
@@ -62,11 +62,12 @@
</ModalHeader>
<div>
{#if loading}
<p class="center" out:slide>
<p class="center">
<Spinner {loading}>Requesting an invite link...</Spinner>
</p>
{:else}
<div in:slide>
<div class="flex flex-col items-center gap-6">
<QRCode code={invite} />
<Field>
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
+24 -10
View File
@@ -3,6 +3,7 @@
import {fade} from "@lib/transition"
import CompassBig from "@assets/icons/compass-big.svg?dataurl"
import NotesMinimalistic from "@assets/icons/notes-minimalistic.svg?dataurl"
import StarFallMinimalistic from "@assets/icons/star-fall-minimalistic.svg?dataurl"
import CalendarMinimalistic from "@assets/icons/calendar-minimalistic.svg?dataurl"
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import Lock from "@assets/icons/lock-keyhole.svg?dataurl"
@@ -14,7 +15,7 @@
import Button from "@lib/components/Button.svelte"
import RoomCreate from "@app/components/RoomCreate.svelte"
import ChannelName from "@app/components/ChannelName.svelte"
import {makeThreadPath, makeCalendarPath, makeRoomPath, makeSpacePath} from "@app/util/routes"
import {makeRoomPath, makeSpacePath} from "@app/util/routes"
import {
hasNip29,
deriveUserRooms,
@@ -34,8 +35,9 @@
const userRooms = deriveUserRooms(url)
const otherRooms = deriveOtherRooms(url)
const chatPath = makeSpacePath(url, "chat")
const threadsPath = makeThreadPath(url)
const calendarPath = makeCalendarPath(url)
const goalsPath = makeSpacePath(url, "goals")
const threadsPath = makeSpacePath(url, "threads")
const calendarPath = makeSpacePath(url, "calendar")
const addRoom = () => pushModal(RoomCreate, {url})
@@ -61,25 +63,37 @@
Quick Links
</h3>
<div class="flex flex-col gap-2">
<Link href={threadsPath} class="btn btn-primary w-full justify-start">
<Link href={goalsPath} class="btn btn-neutral w-full justify-start">
<div class="relative flex items-center gap-2">
<Icon icon={NotesMinimalistic} />
Threads
{#if $notifications.has(threadsPath)}
<Icon icon={StarFallMinimalistic} />
Goals
{#if $notifications.has(goalsPath)}
<div
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-primary-content"
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-neutral-content"
transition:fade>
</div>
{/if}
</div>
</Link>
<Link href={calendarPath} class="btn btn-secondary w-full justify-start">
<Link href={threadsPath} class="btn btn-neutral w-full justify-start">
<div class="relative flex items-center gap-2">
<Icon icon={NotesMinimalistic} />
Threads
{#if $notifications.has(threadsPath)}
<div
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-neutral-content"
transition:fade>
</div>
{/if}
</div>
</Link>
<Link href={calendarPath} class="btn btn-neutral w-full justify-start">
<div class="relative flex items-center gap-2">
<Icon icon={CalendarMinimalistic} />
Calendar
{#if $notifications.has(calendarPath)}
<div
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-primary-content"
class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-neutral-content"
transition:fade>
</div>
{/if}
+1 -2
View File
@@ -46,8 +46,7 @@
{#if showFailure}
{@const url = failedUrls[0]}
{@const status = $thunk.status[url]}
{@const message = $thunk.details[url]}
{@const {status, detail: message} = $thunk.results[url]}
<button
class="flex w-full justify-end px-1 text-xs {restProps.class}"
onclick={stopPropagation(noop)}>
@@ -0,0 +1,61 @@
<script lang="ts">
import {getWalletAddress} from "@welshman/util"
import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {updateProfile} from "@app/core/commands"
import {clearModals} from "@app/util/modal"
import {userProfile, session} from "@welshman/app"
import {makeProfile} from "@welshman/util"
const lud16 = getWalletAddress($session!.wallet!)
const confirm = async () => {
const profile = $userProfile || makeProfile()
loading = true
try {
await updateProfile({profile: {...profile, lud16}})
clearModals()
} finally {
loading = false
}
}
const cancel = () => {
clearModals()
}
let loading = $state(false)
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
Set as Receiving Address?
{/snippet}
</ModalHeader>
{#if $userProfile?.lud16}
<p>
Your current receiving address is different from the one provided by your connected wallet.
</p>
<p>
Would you like to update your receiving address to <span class="text-primary">{lud16}</span>?
</p>
{:else}
<p>
You don't currently have a receiving address set, which means other people can't send you
lightning payments.
</p>
<p>Would you like to use the one associated with your connected wallet?</p>
{/if}
<ModalFooter>
<Button class="btn btn-neutral" onclick={cancel} disabled={loading}>No, skip this</Button>
<Button class="btn btn-primary" onclick={confirm} disabled={loading}>
<Spinner {loading}>Yes, set as receiving address</Spinner>
</Button>
</ModalFooter>
</div>
+9 -3
View File
@@ -3,7 +3,7 @@
import {nwc} from "@getalby/sdk"
import {sleep, assoc} from "@welshman/lib"
import type {NWCInfo} from "@welshman/util"
import {pubkey, updateSession} from "@welshman/app"
import {pubkey, userProfile, updateSession} from "@welshman/app"
import Link from "@lib/components/Link.svelte"
import Cpu from "@assets/icons/cpu-bolt.svg?dataurl"
import Lock from "@assets/icons/lock-keyhole.svg?dataurl"
@@ -15,11 +15,13 @@
import Scanner from "@lib/components/Scanner.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Field from "@lib/components/Field.svelte"
import Divider from "@lib/components/Divider.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {getWebLn} from "@app/core/commands"
import {pushToast} from "@app/util/toast"
import {pushModal} from "@app/util/modal"
import WalletAsReceivingAddress from "@app/components/WalletAsReceivingAddress.svelte"
import Divider from "@src/lib/components/Divider.svelte"
const back = () => history.back()
@@ -76,6 +78,10 @@
await sleep(400)
back()
if (info.lud16 && info.lud16 !== $userProfile?.lud16) {
pushModal(WalletAsReceivingAddress)
}
}
} catch (e) {
console.error(e)
@@ -109,7 +115,7 @@
<div>Connect a Wallet</div>
{/snippet}
{#snippet info()}
Use Nostr Wallet Connect to send Bitcoin payments over Bolt.
Use Nostr Wallet Connect to send Bitcoin payments over lightning.
{/snippet}
</ModalHeader>
{#if getWebLn()}
@@ -0,0 +1,99 @@
<script lang="ts">
import {getWalletAddress} from "@welshman/util"
import {session, userProfile} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte"
import Wallet from "@assets/icons/wallet.svg?dataurl"
import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
import {updateProfile} from "@app/core/commands"
import {pushToast} from "@app/util/toast"
const back = () => history.back()
let address = $state($userProfile?.lud16 || "")
let isLoading = $state(false)
const walletLud16 = $derived($session?.wallet ? getWalletAddress($session.wallet) : undefined)
const useWalletAddress = () => {
if (walletLud16) {
address = walletLud16
}
}
const save = async () => {
isLoading = true
try {
await updateProfile({
profile: {
...$userProfile,
lud06: undefined,
lud16: address.trim() || undefined,
},
})
back()
} catch (error) {
pushToast({theme: "error", message: "Failed to update profile"})
} finally {
isLoading = false
}
}
</script>
<div class="column gap-4">
<ModalHeader>
{#snippet title()}
Update Lightning Address
{/snippet}
{#snippet info()}
Update your lightning address for receiving payments.
{/snippet}
</ModalHeader>
<div class="column gap-4">
<div class="column gap-2">
<span> Lightning Address </span>
<input
type="text"
placeholder="user@domain.com"
bind:value={address}
class="input input-bordered flex w-full"
disabled={isLoading} />
<p class="text-xs opacity-75">
You can enter one manually or use your connected wallet's address (if available). Leave
empty to remove your lightning address
</p>
</div>
{#if walletLud16 && walletLud16 !== address}
<div class="card bg-base-200 p-4">
<div class="flex items-center justify-between gap-3">
<div class="column gap-1">
<div class="flex items-center gap-2">
<Icon icon={Wallet} size={4} />
<span class="text-sm font-medium">Wallet Address</span>
</div>
<p class="text-xs opacity-75">{walletLud16}</p>
</div>
<Button class="btn btn-outline btn-sm" onclick={useWalletAddress} disabled={isLoading}>
Use This
</Button>
</div>
</div>
{/if}
</div>
<ModalFooter>
<Button class="btn btn-neutral" onclick={back} disabled={isLoading}>Cancel</Button>
<Button class="btn btn-primary" onclick={save} disabled={isLoading}>
{#if isLoading}
<span class="loading loading-spinner loading-sm"></span>
{:else}
<Icon icon={CheckCircle} />
{/if}
Save Changes
</Button>
</ModalFooter>
</div>
+116 -41
View File
@@ -3,6 +3,7 @@ import * as nip19 from "nostr-tools/nip19"
import {get} from "svelte/store"
import type {Override, MakeOptional} from "@welshman/lib"
import {
first,
sha256,
randomId,
append,
@@ -16,12 +17,15 @@ import {
parseJson,
fromPairs,
last,
simpleCache,
normalizeUrl,
nthNe,
} from "@welshman/lib"
import {decrypt, Nip01Signer} from "@welshman/signer"
import type {UploadTask} from "@welshman/editor"
import type {Feed} from "@welshman/feeds"
import {makeIntersectionFeed, feedFromFilters, makeRelayFeed} from "@welshman/feeds"
import type {TrustedEvent, EventContent} from "@welshman/util"
import type {TrustedEvent, EventContent, Profile} from "@welshman/util"
import {
WRAP,
DELETE,
@@ -57,8 +61,13 @@ import {
getTagValue,
getTagValues,
uploadBlob,
canUploadBlob,
encryptFile,
makeBlossomAuthEvent,
isPublishedProfile,
editProfile,
createProfile,
uniqTags,
} from "@welshman/util"
import {Pool, AuthStatus, SocketStatus} from "@welshman/net"
import {Router} from "@welshman/router"
@@ -75,7 +84,6 @@ import {
userInboxRelaySelections,
nip44EncryptToSelf,
loadRelay,
clearStorage,
dropSession,
tagEventForComment,
tagEventForQuote,
@@ -92,15 +100,17 @@ import {
INDEXER_RELAYS,
NOTIFIER_PUBKEY,
NOTIFIER_RELAY,
DEFAULT_BLOSSOM_SERVERS,
userRoomsByUrl,
userSettingsValues,
canDecrypt,
ensureUnwrapped,
userInboxRelays,
getMembershipUrls,
} from "@app/core/state"
import {loadAlertStatuses} from "@app/core/requests"
import {platform, platformName, getPushInfo} from "@app/util/push"
import {preferencesStorageProvider} from "@src/lib/storage"
import {preferencesStorageProvider, collectionStorageProvider} from "@src/lib/storage"
// Utils
@@ -143,10 +153,10 @@ export const logout = async () => {
dropSession($pubkey)
}
await clearStorage()
localStorage.clear()
await preferencesStorageProvider.clear()
await collectionStorageProvider.clear()
}
// Synchronization
@@ -158,7 +168,7 @@ export const broadcastUserData = async (relays: string[]) => {
for (const event of events) {
if (isSignedEvent(event)) {
await publishThunk({event, relays}).result
await publishThunk({event, relays}).complete
}
}
}
@@ -660,17 +670,53 @@ export const enableGiftWraps = () => {
// File upload
export const getBlossomServer = () => {
export const normalizeBlossomUrl = (url: string) => normalizeUrl(url.replace(/^ws/, "http"))
export const hasBlossomSupport = simpleCache(async ([url]: [string]) => {
const server = normalizeBlossomUrl(url)
const $signer = signer.get() || Nip01Signer.ephemeral()
const headers: Record<string, string> = {
"X-Content-Type": "text/plain",
"X-Content-Length": "1",
"X-SHA-256": "73cb3858a687a8494ca3323053016282f3dad39d42cf62ca4e79dda2aac7d9ac",
}
try {
const authEvent = await $signer.sign(makeBlossomAuthEvent({action: "upload", server}))
const res = await canUploadBlob(server, {authEvent, headers})
return res.status === 200
} catch (e) {
if (!String(e).match(/Failed to fetch|NetworkError/)) {
console.error(e)
}
}
return false
})
export type GetBlossomServerOptions = {
url?: string
}
export const getBlossomServer = async (options: GetBlossomServerOptions = {}) => {
if (options.url) {
if (await hasBlossomSupport(options.url)) {
return normalizeBlossomUrl(options.url)
}
}
const userUrls = getTagValues("server", getListTags(userBlossomServers.get()))
for (const url of userUrls) {
return url.replace(/^ws/, "http")
return normalizeBlossomUrl(url)
}
return "https://cdn.satellite.earth"
return first(DEFAULT_BLOSSOM_SERVERS)!
}
export type UploadFileOptions = {
url?: string
encrypt?: boolean
}
@@ -680,35 +726,35 @@ export type UploadFileResult = {
}
export const uploadFile = async (file: File, options: UploadFileOptions = {}) => {
const {name, type} = file
if (!type.match("image/(webp|gif)")) {
file = await compressFile(file)
}
const tags: string[][] = []
if (options.encrypt) {
const {ciphertext, key, nonce, algorithm} = await encryptFile(file)
tags.push(
["decryption-key", key],
["decryption-nonce", nonce],
["encryption-algorithm", algorithm],
)
file = new File([new Blob([ciphertext])], name, {
type: "application/octet-stream",
})
}
const server = getBlossomServer()
const hashes = [await sha256(await file.arrayBuffer())]
const $signer = signer.get() || Nip01Signer.ephemeral()
const authTemplate = makeBlossomAuthEvent({action: "upload", server, hashes})
const authEvent = await $signer.sign(authTemplate)
try {
const {name, type} = file
if (!type.match("image/(webp|gif)")) {
file = await compressFile(file)
}
const tags: string[][] = []
if (options.encrypt) {
const {ciphertext, key, nonce, algorithm} = await encryptFile(file)
tags.push(
["decryption-key", key],
["decryption-nonce", nonce],
["encryption-algorithm", algorithm],
)
file = new File([new Blob([ciphertext])], name, {
type: "application/octet-stream",
})
}
const ext = "." + type.split("/")[1]
const server = await getBlossomServer(options)
const hashes = [await sha256(await file.arrayBuffer())]
const $signer = signer.get() || Nip01Signer.ephemeral()
const authTemplate = makeBlossomAuthEvent({action: "upload", server, hashes})
const authEvent = await $signer.sign(authTemplate)
const res = await uploadBlob(server, file, {authEvent})
const text = await res.text()
@@ -718,16 +764,45 @@ export const uploadFile = async (file: File, options: UploadFileOptions = {}) =>
return {error: text}
}
// Always append file extension if missing
if (new URL(url).pathname.split(".").length === 1) {
url += "." + type.split("/")[1]
// Always append correct file extension if we encrypted the file, or if it's missing
if (options.encrypt) {
url = url.replace(/\.\w+$/, "") + ext
} else if (new URL(url).pathname.split(".").length === 1) {
url += ext
}
const result = {...task, tags, url}
return {result}
} catch (e: any) {
console.error(e)
console.error("Error caught when uploading file:", e)
return {error: e.toString()}
}
}
// Update Profile
export const updateProfile = async ({
profile,
shouldBroadcast = !getTag(PROTECTED, profile.event?.tags || []),
}: {
profile: Profile
shouldBroadcast?: boolean
}) => {
const router = Router.get()
const template = isPublishedProfile(profile) ? editProfile(profile) : createProfile(profile)
const scenarios = [router.FromRelays(getMembershipUrls(userMembership.get()))]
if (shouldBroadcast) {
scenarios.push(router.FromUser(), router.Index())
template.tags = template.tags.filter(nthNe(0, "-"))
} else {
template.tags = uniqTags([...template.tags, PROTECTED])
}
const event = makeEvent(template.kind, template)
const relays = router.merge(scenarios).getUrls()
await publishThunk({event, relays}).complete
}
+24 -21
View File
@@ -140,14 +140,14 @@ export const PLATFORM_ACCENT = import.meta.env.VITE_PLATFORM_ACCENT
export const PLATFORM_DESCRIPTION = import.meta.env.VITE_PLATFORM_DESCRIPTION
export const DEFAULT_BLOSSOM_SERVERS = fromCsv(import.meta.env.VITE_DEFAULT_BLOSSOM_SERVERS)
export const BURROW_URL = import.meta.env.VITE_BURROW_URL
export const DEFAULT_PUBKEYS = import.meta.env.VITE_DEFAULT_PUBKEYS
export const DUFFLEPUD_URL = "https://dufflepud.onrender.com"
export const IMGPROXY_URL = "https://imgproxy.coracle.social"
export const NIP46_PERMS =
"nip44_encrypt,nip44_decrypt," +
[CLIENT_AUTH, AUTH_JOIN, MESSAGE, THREAD, COMMENT, ROOMS, WRAP, REACTION, ZAP_REQUEST]
@@ -178,20 +178,6 @@ export const colors = [
export const dufflepud = (path: string) => DUFFLEPUD_URL + "/" + path
export const imgproxy = (url: string, {w = 640, h = 1024} = {}) => {
if (!url || url.match("gif$")) {
return url
}
url = url.split("?")[0]
try {
return url ? `${IMGPROXY_URL}/x/s:${w}:${h}/${btoa(url)}` : url
} catch (e) {
return url
}
}
export const entityLink = (entity: string) => `https://coracle.social/${entity}`
export const pubkeyLink = (pubkey: string, relays = Router.get().FromPubkeys([pubkey]).getUrls()) =>
@@ -336,6 +322,8 @@ export type SettingsValues = {
report_errors: boolean
send_delay: number
font_size: number
play_notification_sound: boolean
show_notifications_badge: boolean
}
export type Settings = {
@@ -349,8 +337,10 @@ export const defaultSettings = {
trusted_relays: [],
report_usage: true,
report_errors: true,
send_delay: 3000,
send_delay: 0,
font_size: 1,
play_notification_sound: true,
show_notifications_badge: true,
}
export const settings = deriveEventsMapped<Settings>(repository, {
@@ -405,9 +395,13 @@ export const alerts = withGetter(
filters: [{kinds: [ALERT_EMAIL, ALERT_WEB, ALERT_IOS, ALERT_ANDROID]}],
itemToEvent: item => item.event,
eventToItem: async event => {
const tags = parseJson(await decrypt(signer.get(), NOTIFIER_PUBKEY, event.content))
const $signer = signer.get()
return {event, tags}
if ($signer) {
const tags = parseJson(await decrypt($signer, NOTIFIER_PUBKEY, event.content))
return {event, tags}
}
},
}),
)
@@ -435,9 +429,13 @@ export const alertStatuses = withGetter(
filters: [{kinds: [ALERT_STATUS]}],
itemToEvent: item => item.event,
eventToItem: async event => {
const tags = parseJson(await decrypt(signer.get(), NOTIFIER_PUBKEY, event.content))
const $signer = signer.get()
return {event, tags}
if ($signer) {
const tags = parseJson(await decrypt($signer, NOTIFIER_PUBKEY, event.content))
return {event, tags}
}
},
}),
)
@@ -527,6 +525,11 @@ export const chats = derived(
const messagesByChatId = new Map<string, TrustedEvent[]>()
for (const message of $messages) {
// Filter out messages we sent but aren't addressed to the user
if (!getPubkeyTagValues(message.wrap?.tags || []).includes($pubkey!)) {
continue
}
const chatId = makeChatId(getPubkeyTagValues(message.tags).concat(message.pubkey))
pushToMapKey(messagesByChatId, chatId, message)
+4 -1
View File
@@ -11,6 +11,7 @@ import {uploadFile} from "@app/core/commands"
import {pushToast} from "@app/util/toast"
export const makeEditor = async ({
encryptFiles = false,
aggressive = false,
autofocus = false,
charCount,
@@ -21,6 +22,7 @@ export const makeEditor = async ({
uploading,
wordCount,
}: {
encryptFiles?: boolean
aggressive?: boolean
autofocus?: boolean
charCount?: Writable<number>
@@ -51,7 +53,8 @@ export const makeEditor = async ({
},
fileUpload: {
config: {
upload: (attrs: FileAttributes) => uploadFile(attrs.file, {encrypt: true}),
upload: (attrs: FileAttributes) =>
uploadFile(attrs.file, {url, encrypt: encryptFiles}),
onDrop: () => uploading?.set(true),
onComplete: () => uploading?.set(false),
onUploadError(currentEditor, task) {
+30 -2
View File
@@ -1,4 +1,4 @@
import {derived} from "svelte/store"
import {derived, get} from "svelte/store"
import {synced, throttled} from "@welshman/store"
import {pubkey, relaysByUrl} from "@welshman/app"
import {prop, spec, identity, now, groupBy} from "@welshman/lib"
@@ -12,8 +12,16 @@ import {
makeSpaceChatPath,
makeRoomPath,
} from "@app/util/routes"
import {chats, hasNip29, getUrlsForEvent, userRoomsByUrl, repositoryStore} from "@app/core/state"
import {
chats,
hasNip29,
getUrlsForEvent,
userRoomsByUrl,
repositoryStore,
userSettingsValues,
} from "@app/core/state"
import {preferencesStorageProvider} from "@src/lib/storage"
import {Badge} from "@capawesome/capacitor-badge"
// Checked state
@@ -150,3 +158,23 @@ export const notifications = derived(
return paths
},
)
export const badgeCount = derived(notifications, notifications => {
return notifications.size
})
export const handleBadgeCountChanges = async (count: number) => {
if (get(userSettingsValues).show_notifications_badge) {
try {
await Badge.set({count})
} catch (err) {
// failed to set badge
}
} else {
await clearBadges()
}
}
export const clearBadges = async () => {
await Badge.clear()
}
+214
View File
@@ -0,0 +1,214 @@
import {on, throttle, fromPairs, batch, sortBy, concat} from "@welshman/lib"
import {throttled, freshness} from "@welshman/store"
import {
PROFILE,
FOLLOWS,
MUTES,
RELAYS,
BLOSSOM_SERVERS,
INBOX_RELAYS,
ROOMS,
APP_DATA,
ALERT_STATUS,
ALERT_EMAIL,
ALERT_WEB,
ALERT_IOS,
ALERT_ANDROID,
EVENT_TIME,
THREAD,
MESSAGE,
DIRECT_MESSAGE,
DIRECT_MESSAGE_FILE,
} from "@welshman/util"
import type {Zapper, TrustedEvent} from "@welshman/util"
import type {RepositoryUpdate} from "@welshman/relay"
import type {Handle, Relay} from "@welshman/app"
import {
plaintext,
tracker,
relays,
repository,
handles,
zappers,
onZapper,
onHandle,
} from "@welshman/app"
import {collectionStorageProvider} from "@lib/storage"
const syncEvents = async () => {
repository.load(await collectionStorageProvider.get<TrustedEvent>("events"))
const rankEvent = (event: TrustedEvent) => {
switch (event.kind) {
case PROFILE:
return 1
case FOLLOWS:
return 1
case MUTES:
return 1
case RELAYS:
return 1
case BLOSSOM_SERVERS:
return 1
case INBOX_RELAYS:
return 1
case ROOMS:
return 1
case APP_DATA:
return 1
case ALERT_STATUS:
return 1
case ALERT_EMAIL:
return 1
case ALERT_WEB:
return 1
case ALERT_IOS:
return 1
case ALERT_ANDROID:
return 1
case EVENT_TIME:
return 0.9
case THREAD:
return 0.9
case MESSAGE:
return 0.9
case DIRECT_MESSAGE:
return 0.9
case DIRECT_MESSAGE_FILE:
return 0.9
default:
return 0
}
}
return on(
repository,
"update",
batch(3000, async (updates: RepositoryUpdate[]) => {
let added: TrustedEvent[] = []
const removed = new Set<string>()
for (const update of updates) {
for (const event of update.added) {
if (rankEvent(event) > 0) {
added.push(event)
removed.delete(event.id)
}
}
for (const id of update.removed) {
added = added.filter(event => !update.removed.has(event.id))
removed.add(id)
}
}
if (added.length > 0) {
let events = concat(await collectionStorageProvider.get<TrustedEvent>("events"), added)
// If we're well above our retention limit, drop lowest-ranked events
if (events.length > 15_000) {
events = sortBy(e => -rankEvent(e), events).slice(10_000)
}
await collectionStorageProvider.set("events", events)
}
}),
)
}
const syncTracker = async () => {
const relaysById = new Map<string, Set<string>>()
for (const [id, relays] of await collectionStorageProvider.get<[string, string[]]>("tracker")) {
relaysById.set(id, new Set(relays))
}
tracker.load(relaysById)
let p = Promise.resolve()
const updateOne = batch(3000, (ids: string[]) => {
p = p.then(() => {
collectionStorageProvider.add(
"tracker",
ids.map(id => [id, Array.from(tracker.getRelays(id))]),
)
})
})
const updateAll = throttle(3000, () => {
p = p.then(() => {
collectionStorageProvider.set(
"tracker",
Array.from(tracker.relaysById.entries()).map(([id, relays]) => [id, Array.from(relays)]),
)
})
})
tracker.on("add", updateOne)
tracker.on("remove", updateOne)
tracker.on("load", updateAll)
tracker.on("clear", updateAll)
return () => {
tracker.off("add", updateOne)
tracker.off("remove", updateOne)
tracker.off("load", updateAll)
tracker.off("clear", updateAll)
}
}
const syncRelays = async () => {
relays.set(await collectionStorageProvider.get<Relay>("relays"))
return throttled(3000, relays).subscribe($relays => {
collectionStorageProvider.set("relays", $relays)
})
}
const syncHandles = async () => {
handles.set(await collectionStorageProvider.get<Handle>("handles"))
return onHandle(
batch(3000, async $handles => {
await collectionStorageProvider.add("handles", $handles)
}),
)
}
const syncZappers = async () => {
zappers.set(await collectionStorageProvider.get<Zapper>("zappers"))
return onZapper(
batch(3000, async $zappers => {
await collectionStorageProvider.add("zappers", $zappers)
}),
)
}
const syncFreshness = async () => {
freshness.set(fromPairs(await collectionStorageProvider.get<[string, number]>("freshness")))
return throttled(3000, freshness).subscribe($freshness => {
collectionStorageProvider.set("freshness", Object.entries($freshness))
})
}
const syncPlaintext = async () => {
plaintext.set(fromPairs(await collectionStorageProvider.get<[string, string]>("plaintext")))
return throttled(3000, plaintext).subscribe($plaintext => {
collectionStorageProvider.set("plaintext", Object.entries($plaintext))
})
}
export const syncDataStores = () =>
Promise.all([
syncEvents(),
syncTracker(),
syncRelays(),
syncHandles(),
syncZappers(),
syncFreshness(),
syncPlaintext(),
])

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Before

Width:  |  Height:  |  Size: 723 B

After

Width:  |  Height:  |  Size: 723 B

Before

Width:  |  Height:  |  Size: 919 B

After

Width:  |  Height:  |  Size: 919 B

Before

Width:  |  Height:  |  Size: 826 B

After

Width:  |  Height:  |  Size: 826 B

Before

Width:  |  Height:  |  Size: 630 B

After

Width:  |  Height:  |  Size: 630 B

Before

Width:  |  Height:  |  Size: 805 B

After

Width:  |  Height:  |  Size: 805 B

Before

Width:  |  Height:  |  Size: 597 B

After

Width:  |  Height:  |  Size: 597 B

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 826 B

After

Width:  |  Height:  |  Size: 826 B

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 780 B

After

Width:  |  Height:  |  Size: 780 B

Before

Width:  |  Height:  |  Size: 814 B

After

Width:  |  Height:  |  Size: 814 B

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before

Width:  |  Height:  |  Size: 787 B

After

Width:  |  Height:  |  Size: 787 B

Before

Width:  |  Height:  |  Size: 556 B

After

Width:  |  Height:  |  Size: 556 B

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 992 B

After

Width:  |  Height:  |  Size: 992 B

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 687 B

After

Width:  |  Height:  |  Size: 687 B

Before

Width:  |  Height:  |  Size: 659 B

After

Width:  |  Height:  |  Size: 659 B

Before

Width:  |  Height:  |  Size: 773 B

After

Width:  |  Height:  |  Size: 773 B

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 527 B

After

Width:  |  Height:  |  Size: 527 B

Before

Width:  |  Height:  |  Size: 959 B

After

Width:  |  Height:  |  Size: 959 B

Before

Width:  |  Height:  |  Size: 268 B

After

Width:  |  Height:  |  Size: 268 B

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 906 B

After

Width:  |  Height:  |  Size: 906 B

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

+1 -1
View File
@@ -49,7 +49,7 @@
</script>
<div class="flex flex-col gap-2" role="list">
{#each value as item, index (item)}
{#each value as item, index}
<div
class="flex items-center gap-2"
draggable="true"
+86 -6
View File
@@ -1,7 +1,10 @@
import {type StorageProvider} from "@welshman/store"
import {Preferences} from "@capacitor/preferences"
import {Encoding, Filesystem, Directory} from "@capacitor/filesystem"
export class PreferencesStorageProvider implements StorageProvider {
p = Promise.resolve()
get = async <T>(key: string): Promise<T | undefined> => {
const result = await Preferences.get({key})
if (!result.value) return undefined
@@ -12,17 +15,94 @@ export class PreferencesStorageProvider implements StorageProvider {
}
}
p = Promise.resolve()
set = async <T>(key: string, value: T): Promise<void> => {
this.p = this.p.then(async () => await Preferences.set({key, value: JSON.stringify(value)}))
this.p = this.p.then(() => Preferences.set({key, value: JSON.stringify(value)}))
await this.p
}
clear = async () => {
this.p = this.p.then(() => Preferences.clear())
await this.p
}
}
export const preferencesStorageProvider = new PreferencesStorageProvider()
export class CollectionStorageProvider implements StorageProvider {
p = Promise.resolve()
get = async <T>(key: string): Promise<T[]> => {
try {
const file = await Filesystem.readFile({
path: key + ".json",
directory: Directory.Data,
encoding: Encoding.UTF8,
})
const items: T[] = []
for (const line of file.data.toString().split("\n")) {
try {
items.push(JSON.parse(line))
} catch (e) {
// pass
}
}
return items
} catch (err) {
// file doesn't exist, or isn't valid json
return []
}
}
set = async <T>(key: string, value: T[]): Promise<void> => {
this.p = this.p.then(async () => {
await Filesystem.writeFile({
path: key + ".json",
directory: Directory.Data,
encoding: Encoding.UTF8,
data: value.map(v => JSON.stringify(v)).join("\n"),
})
})
await this.p
}
add = async <T>(key: string, value: T[]): Promise<void> => {
this.p = this.p.then(async () => {
await Filesystem.appendFile({
path: key + ".json",
directory: Directory.Data,
encoding: Encoding.UTF8,
data: "\n" + value.map(v => JSON.stringify(v)).join("\n"),
})
})
await this.p
}
clear = async (): Promise<void> => {
await Preferences.clear()
this.p = Promise.resolve()
this.p = this.p.then(async () => {
try {
const res = await Filesystem.readdir({path: "./", directory: Directory.Data})
await Promise.all(
res.files.map(file =>
Filesystem.deleteFile({
path: file.name + ".json",
directory: Directory.Data,
}),
),
)
} catch (e) {
// Directory might not have been created yet
}
})
await this.p
}
}
// singleton instance of PreferencesStorageProvider
export const preferencesStorageProvider = new PreferencesStorageProvider()
export const collectionStorageProvider = new CollectionStorageProvider()
+18 -64
View File
@@ -24,27 +24,7 @@
WEEK,
} from "@welshman/lib"
import type {TrustedEvent, StampedEvent} from "@welshman/util"
import {
WRAP,
ALERT_STATUS,
ALERT_EMAIL,
ALERT_WEB,
ALERT_IOS,
ALERT_ANDROID,
EVENT_TIME,
APP_DATA,
THREAD,
MESSAGE,
INBOX_RELAYS,
DIRECT_MESSAGE,
DIRECT_MESSAGE_FILE,
MUTES,
FOLLOWS,
PROFILE,
RELAYS,
BLOSSOM_SERVERS,
ROOMS,
} from "@welshman/util"
import {WRAP} from "@welshman/util"
import {Nip46Broker, makeSecret} from "@welshman/signer"
import type {Socket, RelayMessage, ClientMessage} from "@welshman/net"
import {
@@ -61,8 +41,6 @@
} from "@welshman/net"
import {
loadRelay,
db,
initStorage,
repository,
pubkey,
session,
@@ -70,10 +48,8 @@
signer,
signerLog,
dropSession,
defaultStorageAdapters,
loginWithNip01,
loginWithNip46,
EventsStorageAdapter,
loadRelaySelections,
SignerLogEntryStatus,
} from "@welshman/app"
@@ -107,8 +83,13 @@
import {initializePushNotifications} from "@app/util/push"
import * as commands from "@app/core/commands"
import * as requests from "@app/core/requests"
import * as notifications from "@app/util/notifications"
import * as appState from "@app/core/state"
import * as notifications from "@app/util/notifications"
import * as storage from "@app/util/storage"
import NewNotificationSound from "@src/app/components/NewNotificationSound.svelte"
// Migration: delete old indexeddb database
indexedDB?.deleteDatabase("flotilla")
// Migration: old nostrtalk instance used different sessions
if ($session && !$signer) {
@@ -122,6 +103,8 @@
const ready = $state(defer<void>())
let initialized = false
onMount(async () => {
Object.assign(window, {
get,
@@ -239,7 +222,8 @@
document.documentElement.style["font-size"] = `${$userSettingsValues.font_size}rem`
})
if (!db) {
if (!initialized) {
initialized = true
setupTracking()
setupAnalytics()
@@ -278,44 +262,10 @@
storage: preferencesStorageProvider,
})
await initStorage("flotilla", 8, {
...defaultStorageAdapters,
events: new EventsStorageAdapter({
name: "events",
limit: 10_000,
repository,
rankEvent: (e: TrustedEvent) => {
if (
[
PROFILE,
FOLLOWS,
MUTES,
RELAYS,
BLOSSOM_SERVERS,
INBOX_RELAYS,
ROOMS,
APP_DATA,
ALERT_STATUS,
ALERT_EMAIL,
ALERT_WEB,
ALERT_IOS,
ALERT_ANDROID,
].includes(e.kind)
) {
return 1
}
if (
[EVENT_TIME, THREAD, MESSAGE, DIRECT_MESSAGE, DIRECT_MESSAGE_FILE].includes(e.kind)
) {
return 0.9
}
return 0
},
}),
})
// Sync application data (relay, events, etc)
await storage.syncDataStores()
// Wait 300 ms for any throttled stores to finish
sleep(300).then(() => ready.resolve())
defaultSocketPolicies.push(
@@ -465,6 +415,9 @@
},
)
// subscribe to badge count for changes
notifications.badgeCount.subscribe(notifications.handleBadgeCountChanges)
// Listen for signer errors, report to user via toast
signerLog.subscribe(
throttle(10_000, $log => {
@@ -504,5 +457,6 @@
</AppContainer>
<ModalContainer />
<div class="tippy-target"></div>
<NewNotificationSound />
</div>
{/await}
+77 -28
View File
@@ -1,24 +1,44 @@
<script lang="ts">
import {onMount} from "svelte"
import {dec} from "@welshman/lib"
import {ROOMS} from "@welshman/util"
import {debounce} from "throttle-debounce"
import {dec, tryCatch} from "@welshman/lib"
import {ROOMS, normalizeRelayUrl, isRelayUrl} from "@welshman/util"
import {Router} from "@welshman/router"
import {load} from "@welshman/net"
import type {Relay} from "@welshman/app"
import {relays, createSearch, loadRelay, loadRelaySelections} from "@welshman/app"
import {createScroller} from "@lib/html"
import {fly} from "@lib/transition"
import QrCode from "@assets/icons/qr-code.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Page from "@lib/components/Page.svelte"
import Scanner from "@lib/components/Scanner.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import PageHeader from "@lib/components/PageHeader.svelte"
import ContentSearch from "@lib/components/ContentSearch.svelte"
import SpaceAdd from "@app/components/SpaceAdd.svelte"
import SpaceInviteAccept from "@app/components/SpaceInviteAccept.svelte"
import RelaySummary from "@app/components/RelaySummary.svelte"
import SpaceCheck from "@app/components/SpaceCheck.svelte"
import {getMembershipUrls, loadMembership, defaultPubkeys, membersByUrl} from "@app/core/state"
import {pushModal} from "@app/util/modal"
const openMenu = () => pushModal(SpaceAdd)
const termUrl = $derived(tryCatch(() => normalizeRelayUrl(term)) || "")
const toggleScanner = () => {
showScanner = !showScanner
}
const onScan = debounce(1000, async (data: string) => {
showScanner = false
pushModal(SpaceInviteAccept, {invite: data})
})
const discoverRelays = () =>
Promise.all([
load({
@@ -37,7 +57,7 @@
const relaySearch = $derived(
createSearch(
$relays.filter(r => $membersByUrl.has(r.url)),
$relays.filter(r => $membersByUrl.has(r.url) && r.url !== termUrl),
{
getValue: (relay: Relay) => relay.url,
sortFn: ({score, item}) => {
@@ -59,6 +79,7 @@
let term = $state("")
let limit = $state(20)
let showScanner = $state(false)
let element: Element
onMount(() => {
@@ -76,30 +97,58 @@
</script>
<Page class="cw-full">
<div class="content column gap-4" bind:this={element}>
<PageHeader>
{#snippet title()}
Discover Spaces
{/snippet}
{#snippet info()}
Find communities all across the nostr network
{/snippet}
</PageHeader>
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon={Magnifier} />
<input bind:value={term} class="grow" type="text" placeholder="Search for spaces..." />
</label>
{#each relaySearch.searchOptions(term).slice(0, limit) as relay (relay.url)}
<Button
class="card2 bg-alt shadow-xl transition-all hover:shadow-2xl hover:dark:brightness-[1.1]"
onclick={() => openSpace(relay.url)}>
<RelaySummary url={relay.url} />
</Button>
{/each}
{#await discoverRelays()}
<div class="flex justify-center py-20" out:fly>
<Spinner loading>Looking for spaces...</Spinner>
<ContentSearch>
{#snippet input()}
<div class="flex flex-col gap-2">
<PageHeader>
{#snippet title()}
Discover Spaces
{/snippet}
{#snippet info()}
Find communities all across the nostr network
{/snippet}
</PageHeader>
<div class="row-2 min-w-0 flex-grow items-center">
<label class="input input-bordered flex flex-grow items-center gap-2">
<Icon icon={Magnifier} />
<input bind:value={term} class="grow" type="text" placeholder="Search for spaces..." />
<Button onclick={toggleScanner} class="center">
<Icon icon={QrCode} />
</Button>
</label>
<Button class="btn btn-primary" onclick={openMenu}>
<Icon icon={AddCircle} />
</Button>
</div>
{#if showScanner}
<Scanner onscan={onScan} />
{/if}
</div>
{/await}
</div>
{/snippet}
{#snippet content()}
<div class="col-2 scroll-container" bind:this={element}>
{#key termUrl}
{#if isRelayUrl(termUrl)}
<Button
class="card2 bg-alt shadow-xl transition-all hover:shadow-2xl hover:dark:brightness-[1.1]"
onclick={() => openSpace(termUrl)}>
<RelaySummary url={termUrl} />
</Button>
{/if}
{/key}
{#each relaySearch.searchOptions(term).slice(0, limit) as relay (relay.url)}
<Button
class="card2 bg-alt shadow-xl transition-all hover:shadow-2xl hover:dark:brightness-[1.1]"
onclick={() => openSpace(relay.url)}>
<RelaySummary url={relay.url} />
</Button>
{/each}
{#await discoverRelays()}
<div class="flex justify-center py-20" out:fly>
<Spinner loading>Looking for spaces...</Spinner>
</div>
{/await}
</div>
{/snippet}
</ContentSearch>
</Page>
+3 -3
View File
@@ -32,7 +32,7 @@
<h1 class="mb-4 text-center text-5xl font-bold uppercase">{PLATFORM_NAME}</h1>
<div class="col-3">
<Button onclick={addSpace}>
<CardButton class="dark:btn-neutral">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={AddCircle} size={7} /></div>
{/snippet}
@@ -45,7 +45,7 @@
</CardButton>
</Button>
<Link href="/discover">
<CardButton class="dark:btn-neutral">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={Compass} size={7} /></div>
{/snippet}
@@ -58,7 +58,7 @@
</CardButton>
</Link>
<Button onclick={openChat}>
<CardButton class="dark:btn-neutral">
<CardButton class="btn-neutral">
{#snippet icon()}
<div><Icon icon={ChatRound} size={7} /></div>
{/snippet}
+1 -3
View File
@@ -61,9 +61,7 @@
</div>
</div>
</div>
<Button
class="center btn btn-circle -mr-4 -mt-4 h-12 w-12 dark:btn-neutral"
onclick={startEdit}>
<Button class="center btn btn-circle btn-neutral -mr-4 -mt-4 h-12 w-12" onclick={startEdit}>
<Icon icon={PenNewSquare} />
</Button>
</div>
+3 -3
View File
@@ -50,7 +50,7 @@
</script>
<div class="content column gap-4">
<Collapse class="card2 bg-alt column gap-4">
<Collapse class="card2 bg-alt column gap-4 shadow-xl">
{#snippet title()}
<h2 class="flex items-center gap-3 text-xl">
<Icon icon={Globus} />
@@ -83,7 +83,7 @@
</Button>
</div>
</Collapse>
<Collapse class="card2 bg-alt column gap-4">
<Collapse class="card2 bg-alt column gap-4 shadow-xl">
{#snippet title()}
<h2 class="flex items-center gap-3 text-xl">
<Icon icon={Inbox} />
@@ -115,7 +115,7 @@
</Button>
</div>
</Collapse>
<Collapse class="card2 bg-alt column gap-4">
<Collapse class="card2 bg-alt column gap-4 shadow-xl">
{#snippet title()}
<h2 class="flex items-center gap-3 text-xl">
<Icon icon={Mailbox} />
+31 -2
View File
@@ -1,22 +1,32 @@
<script lang="ts">
import {nwc} from "@getalby/sdk"
import {LOCALE} from "@welshman/lib"
import {displayRelayUrl, fromMsats} from "@welshman/util"
import {session} from "@welshman/app"
import {displayRelayUrl, isNWCWallet, fromMsats} from "@welshman/util"
import {session, pubkey, profilesByPubkey} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import WalletConnect from "@app/components/WalletConnect.svelte"
import WalletDisconnect from "@app/components/WalletDisconnect.svelte"
import WalletUpdateReceivingAddress from "@app/components/WalletUpdateReceivingAddress.svelte"
import {pushModal} from "@app/util/modal"
import {getWebLn} from "@app/core/commands"
import Wallet2 from "@assets/icons/wallet.svg?dataurl"
import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import InfoCircle from "@assets/icons/info-circle.svg?dataurl"
const connect = () => pushModal(WalletConnect)
const disconnect = () => pushModal(WalletDisconnect)
const updateReceivingAddress = () => pushModal(WalletUpdateReceivingAddress)
const profile = $derived($profilesByPubkey.get($pubkey || ""))
const profileLightningAddress = $derived(profile?.lud16)
const walletLud16 = $derived(
$session?.wallet && isNWCWallet($session.wallet) ? $session.wallet.info.lud16 : undefined,
)
</script>
<div class="content column gap-4">
@@ -89,4 +99,23 @@
{/if}
</div>
</div>
<div
class="card2 bg-alt flex flex-col shadow-xl"
class:gap-6={profileLightningAddress && walletLud16 && profile?.lud16 !== walletLud16}>
<div class="flex items-center justify-between">
<strong>Lightning Address</strong>
<div class="flex items-center gap-2">
<span class={profileLightningAddress ? "" : "text-warning"}>
{profileLightningAddress ? profileLightningAddress : "Not set"}
</span>
<Button class="btn btn-neutral btn-xs ml-3" onclick={updateReceivingAddress}>Update</Button>
</div>
</div>
{#if profileLightningAddress && walletLud16 && profile?.lud16 !== walletLud16}
<div class="card2 bg-alt flex items-center gap-2 text-xs">
<Icon icon={InfoCircle} size={4} />
Your profile has a different lightning address than your connected wallet.
</div>
{/if}
</div>
</div>
+4
View File
@@ -22,6 +22,7 @@
userRoomsByUrl,
} from "@app/core/state"
import {pullConservatively} from "@app/core/requests"
import {hasBlossomSupport} from "@app/core/commands"
import {notifications} from "@app/util/notifications"
type Props = {
@@ -66,6 +67,9 @@
}
})
// Prime our cache so we can upload images quicker
hasBlossomSupport(url)
// Load group meta, threads, calendar events, comments, and recent messages
// for user rooms to help with a quick page transition
pullConservatively({
Binary file not shown.
+2
View File
@@ -36,6 +36,8 @@ export default {
},
light: {
...themes["winter"],
neutral: '#F2F7FF',
warning: '#FD8D0B',
primary: process.env.VITE_PLATFORM_ACCENT,
"primary-content": process.env.VITE_PLATFORM_ACCENT_CONTENT || "#EAE7FF",
secondary: process.env.VITE_PLATFORM_SECONDARY,