Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 16a3ba2a9b | |||
| 7c11eb8947 | |||
| 6bdc8d4d9f | |||
| b9048936ba | |||
| b9620f4443 | |||
| f2249fe592 | |||
| fd42a0e8d4 | |||
| 37d52ba35f | |||
| 3037323dc0 | |||
| 5301ef876d | |||
| aa054d8b1a | |||
| 3655790e5f | |||
| 6cca823ed4 | |||
| 18a383edab | |||
| 43da7d628e | |||
| 2fae3ca248 | |||
| d99ada44f5 | |||
| cb0119b9b8 | |||
| dac9ef8e4e | |||
| 528917b90e | |||
| a22db78967 | |||
| 5718510779 | |||
| f877dc7fbe | |||
| df03fb1116 |
@@ -13,5 +13,6 @@ VITE_INDEXER_RELAYS=wss://purplepag.es/,wss://relay.damus.io/,wss://relay.nostr.
|
||||
VITE_SIGNER_RELAYS=wss://relay.nsec.app/,wss://bucket.coracle.social/
|
||||
VITE_NOTIFIER_PUBKEY=27b7c2ed89ef78322114225ea3ebf5f72c7767c2528d4d0c1854d039c00085df
|
||||
VITE_NOTIFIER_RELAY=wss://anchor.coracle.social/
|
||||
VITE_VAPID_PUBLIC_KEY=
|
||||
VITE_GLITCHTIP_API_KEY=
|
||||
GLITCHTIP_AUTH_TOKEN=
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
name: Build and Publish Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
REGISTRY: docker.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
# 1.2.0
|
||||
|
||||
* Fix sort order of thread comments
|
||||
* Fix link display when no title is available
|
||||
* Fix making profiles non-protected
|
||||
* Replace bunker url with relay claims for notifier auth
|
||||
* Add push notifications on all platforms
|
||||
* Add "mark all as read" on desktop
|
||||
* Re-design space dashboard
|
||||
|
||||
# 1.1.1
|
||||
|
||||
* Add chat quick link
|
||||
|
||||
# 1.1.0
|
||||
|
||||
* Add better theming support
|
||||
|
||||
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "social.flotilla"
|
||||
minSdk rootProject.ext.minSdkVersion
|
||||
targetSdk rootProject.ext.targetSdkVersion
|
||||
versionCode 19
|
||||
versionName "1.1.0"
|
||||
versionCode 21
|
||||
versionName "1.2.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
|
||||
@@ -12,6 +12,8 @@ dependencies {
|
||||
implementation project(':capacitor-community-safe-area')
|
||||
implementation project(':capacitor-app')
|
||||
implementation project(':capacitor-keyboard')
|
||||
implementation project(':capacitor-push-notifications')
|
||||
implementation project(':capawesome-capacitor-badge')
|
||||
implementation project(':nostr-signer-capacitor-plugin')
|
||||
|
||||
}
|
||||
|
||||
@@ -34,4 +34,5 @@
|
||||
<!-- Permissions -->
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
</manifest>
|
||||
|
||||
@@ -11,5 +11,11 @@ project(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacito
|
||||
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')
|
||||
|
||||
include ':capacitor-push-notifications'
|
||||
project(':capacitor-push-notifications').projectDir = new File('../node_modules/.pnpm/@capacitor+push-notifications@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/push-notifications/android')
|
||||
|
||||
include ':capawesome-capacitor-badge'
|
||||
project(':capawesome-capacitor-badge').projectDir = new File('../node_modules/.pnpm/@capawesome+capacitor-badge@7.0.1_@capacitor+core@7.2.0/node_modules/@capawesome/capacitor-badge/android')
|
||||
|
||||
include ':nostr-signer-capacitor-plugin'
|
||||
project(':nostr-signer-capacitor-plugin').projectDir = new File('../node_modules/.pnpm/nostr-signer-capacitor-plugin@0.0.4_@capacitor+core@7.2.0/node_modules/nostr-signer-capacitor-plugin/android')
|
||||
|
||||
@@ -15,6 +15,10 @@ const config: CapacitorConfig = {
|
||||
style: "DARK",
|
||||
resizeOnFullScreen: true,
|
||||
},
|
||||
Badge: {
|
||||
persist: true,
|
||||
autoClear: true
|
||||
},
|
||||
},
|
||||
// Use this for live reload https://capacitorjs.com/docs/guides/live-reload
|
||||
// server: {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
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>"; };
|
||||
50379B222058CBB4000EE86E /* capacitor.config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = capacitor.config.json; sourceTree = "<group>"; };
|
||||
@@ -57,6 +58,7 @@
|
||||
504EC2FB1FED79650016851F = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
051414282E0CC28400BE0BC8 /* Flotilla Chat.entitlements */,
|
||||
504EC3061FED79650016851F /* App */,
|
||||
504EC3051FED79650016851F /* Products */,
|
||||
7F8756D8B27F46E3366F6CEA /* Pods */,
|
||||
@@ -349,16 +351,17 @@
|
||||
baseConfigurationReference = 7B9FA71C362B734D9F965709 /* Pods-Flotilla Chat.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
CURRENT_PROJECT_VERSION = 13;
|
||||
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.1.0;
|
||||
MARKETING_VERSION = 1.2.0;
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -374,16 +377,17 @@
|
||||
baseConfigurationReference = 1F53EE54954731A2328CBC4B /* Pods-Flotilla Chat.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 12;
|
||||
CURRENT_PROJECT_VERSION = 13;
|
||||
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.1.0;
|
||||
MARKETING_VERSION = 1.2.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
||||
@@ -46,4 +46,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||
// Enable push notifications https://capacitorjs.com/docs/apis/push-notifications
|
||||
NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
|
||||
// Enable push notifications https://capacitorjs.com/docs/apis/push-notifications
|
||||
NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,5 +49,9 @@
|
||||
<true/>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?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>aps-environment</key>
|
||||
<string>development</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -14,6 +14,8 @@ def capacitor_pods
|
||||
pod 'CapacitorCommunitySafeArea', :path => '../../node_modules/.pnpm/@capacitor-community+safe-area@7.0.0-alpha.1_@capacitor+core@7.2.0/node_modules/@capacitor-community/safe-area'
|
||||
pod 'CapacitorApp', :path => '../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/app'
|
||||
pod 'CapacitorKeyboard', :path => '../../node_modules/.pnpm/@capacitor+keyboard@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/keyboard'
|
||||
pod 'CapacitorPushNotifications', :path => '../../node_modules/.pnpm/@capacitor+push-notifications@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/push-notifications'
|
||||
pod 'CapawesomeCapacitorBadge', :path => '../../node_modules/.pnpm/@capawesome+capacitor-badge@7.0.1_@capacitor+core@7.2.0/node_modules/@capawesome/capacitor-badge'
|
||||
pod 'NostrSignerCapacitorPlugin', :path => '../../node_modules/.pnpm/nostr-signer-capacitor-plugin@0.0.4_@capacitor+core@7.2.0/node_modules/nostr-signer-capacitor-plugin'
|
||||
end
|
||||
|
||||
|
||||
+14
-13
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "flotilla",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
@@ -45,6 +45,8 @@
|
||||
"@capacitor/core": "^7.0.1",
|
||||
"@capacitor/ios": "^7.0.0",
|
||||
"@capacitor/keyboard": "^7.0.0",
|
||||
"@capacitor/push-notifications": "^7.0.1",
|
||||
"@capawesome/capacitor-badge": "^7.0.1",
|
||||
"@poppanator/sveltekit-svg": "^4.2.1",
|
||||
"@sentry/browser": "^8.35.0",
|
||||
"@sveltejs/adapter-static": "^3.0.4",
|
||||
@@ -52,18 +54,17 @@
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@vite-pwa/assets-generator": "^0.2.6",
|
||||
"@vite-pwa/sveltekit": "^0.6.6",
|
||||
"@welshman/app": "^0.3.4",
|
||||
"@welshman/content": "^0.3.4",
|
||||
"@welshman/dvm": "^0.3.4",
|
||||
"@welshman/editor": "^0.3.4",
|
||||
"@welshman/feeds": "^0.3.4",
|
||||
"@welshman/lib": "^0.3.4",
|
||||
"@welshman/net": "^0.3.4",
|
||||
"@welshman/relay": "^0.3.4",
|
||||
"@welshman/router": "^0.3.4",
|
||||
"@welshman/signer": "^0.3.4",
|
||||
"@welshman/store": "^0.3.4",
|
||||
"@welshman/util": "^0.3.4",
|
||||
"@welshman/app": "^0.3.8",
|
||||
"@welshman/content": "^0.3.8",
|
||||
"@welshman/editor": "^0.3.8",
|
||||
"@welshman/feeds": "^0.3.8",
|
||||
"@welshman/lib": "^0.3.8",
|
||||
"@welshman/net": "^0.3.8",
|
||||
"@welshman/relay": "^0.3.8",
|
||||
"@welshman/router": "^0.3.8",
|
||||
"@welshman/signer": "^0.3.8",
|
||||
"@welshman/store": "^0.3.8",
|
||||
"@welshman/util": "^0.3.8",
|
||||
"compressorjs": "^1.2.1",
|
||||
"daisyui": "^4.12.10",
|
||||
"date-picker-svelte": "^2.13.0",
|
||||
|
||||
Generated
+176
-169
@@ -29,6 +29,12 @@ importers:
|
||||
'@capacitor/keyboard':
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.1(@capacitor/core@7.2.0)
|
||||
'@capacitor/push-notifications':
|
||||
specifier: ^7.0.1
|
||||
version: 7.0.1(@capacitor/core@7.2.0)
|
||||
'@capawesome/capacitor-badge':
|
||||
specifier: ^7.0.1
|
||||
version: 7.0.1(@capacitor/core@7.2.0)
|
||||
'@poppanator/sveltekit-svg':
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(rollup@2.79.2)(svelte@5.25.10)(svgo@3.3.2)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0))
|
||||
@@ -51,41 +57,38 @@ importers:
|
||||
specifier: ^0.6.6
|
||||
version: 0.6.8(@sveltejs/kit@2.20.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.25.10)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0)))(svelte@5.25.10)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0)))(@vite-pwa/assets-generator@0.2.6)(vite-plugin-pwa@0.21.2(@vite-pwa/assets-generator@0.2.6)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0))(workbox-build@7.3.0)(workbox-window@7.3.0))
|
||||
'@welshman/app':
|
||||
specifier: ^0.3.4
|
||||
version: 0.3.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)
|
||||
specifier: ^0.3.8
|
||||
version: 0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
||||
'@welshman/content':
|
||||
specifier: ^0.3.4
|
||||
version: 0.3.4(typescript@5.8.3)
|
||||
'@welshman/dvm':
|
||||
specifier: ^0.3.4
|
||||
version: 0.3.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)
|
||||
specifier: ^0.3.8
|
||||
version: 0.3.8(typescript@5.8.3)
|
||||
'@welshman/editor':
|
||||
specifier: ^0.3.4
|
||||
version: 0.3.4(@tiptap/extension-image@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)
|
||||
specifier: ^0.3.8
|
||||
version: 0.3.8(@tiptap/extension-image@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)
|
||||
'@welshman/feeds':
|
||||
specifier: ^0.3.4
|
||||
version: 0.3.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)
|
||||
specifier: ^0.3.8
|
||||
version: 0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
||||
'@welshman/lib':
|
||||
specifier: ^0.3.4
|
||||
version: 0.3.4
|
||||
specifier: ^0.3.8
|
||||
version: 0.3.8
|
||||
'@welshman/net':
|
||||
specifier: ^0.3.4
|
||||
version: 0.3.4(typescript@5.8.3)(ws@8.18.2)
|
||||
specifier: ^0.3.8
|
||||
version: 0.3.8(typescript@5.8.3)(ws@8.18.3)
|
||||
'@welshman/relay':
|
||||
specifier: ^0.3.4
|
||||
version: 0.3.4(typescript@5.8.3)
|
||||
specifier: ^0.3.8
|
||||
version: 0.3.8(typescript@5.8.3)
|
||||
'@welshman/router':
|
||||
specifier: ^0.3.4
|
||||
version: 0.3.4(typescript@5.8.3)
|
||||
specifier: ^0.3.8
|
||||
version: 0.3.8(typescript@5.8.3)
|
||||
'@welshman/signer':
|
||||
specifier: ^0.3.4
|
||||
version: 0.3.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)
|
||||
specifier: ^0.3.8
|
||||
version: 0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
||||
'@welshman/store':
|
||||
specifier: ^0.3.4
|
||||
version: 0.3.4(typescript@5.8.3)
|
||||
specifier: ^0.3.8
|
||||
version: 0.3.8(typescript@5.8.3)
|
||||
'@welshman/util':
|
||||
specifier: ^0.3.4
|
||||
version: 0.3.4(typescript@5.8.3)
|
||||
specifier: ^0.3.8
|
||||
version: 0.3.8(typescript@5.8.3)
|
||||
compressorjs:
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1
|
||||
@@ -743,6 +746,16 @@ packages:
|
||||
peerDependencies:
|
||||
'@capacitor/core': '>=7.0.0'
|
||||
|
||||
'@capacitor/push-notifications@7.0.1':
|
||||
resolution: {integrity: sha512-nSHsMSrTHX5pOkX1Khse75/uvSx/JTcXG+9aT6a66CvzalH6MCs0ha8Jv+xu0k9xW8caO+qSUMjfj5Oy82Uxmw==}
|
||||
peerDependencies:
|
||||
'@capacitor/core': '>=7.0.0'
|
||||
|
||||
'@capawesome/capacitor-badge@7.0.1':
|
||||
resolution: {integrity: sha512-jhVieRRVLgGO1NU7PW8uWZmf3WD4IsYUlkrJ82KuoRgLFx1tbJGwHU1ro0sUJmEwfLO9vldhBnJJ/J5nHrjbQQ==}
|
||||
peerDependencies:
|
||||
'@capacitor/core': '>=7.0.0'
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -1370,77 +1383,77 @@ packages:
|
||||
peerDependencies:
|
||||
'@tiptap/pm': ^2.7.0
|
||||
|
||||
'@tiptap/extension-code-block@2.14.0':
|
||||
resolution: {integrity: sha512-LRYYZeh8U2XgfTsJ4houB9s9cVRt7PRfVa4MaCeOYKfowVOKQh67yV5oom8Azk9XrMPkPxDmMmdPAEPxeVYFvw==}
|
||||
'@tiptap/extension-code-block@2.23.0':
|
||||
resolution: {integrity: sha512-p8iizp5nQBBhYPrIgBVwEqcDnc2fFRAZCXy/xjmAk2kKOhB7NYe3+1yrbFcQKVAdaUFxG+BRj2WxNDeeRt5tJA==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
'@tiptap/pm': ^2.7.0
|
||||
|
||||
'@tiptap/extension-code@2.14.0':
|
||||
resolution: {integrity: sha512-kyo02mnzqgwXayMcyRA/fHQgb+nMmQQpIt1irZwjtEoFZshA7NnY/6b5SJmRcxQ4/X4r2Y2Ha2sWmOcEkLmt4A==}
|
||||
'@tiptap/extension-code@2.23.0':
|
||||
resolution: {integrity: sha512-Ip/5+kNoqrxYPHLnZMf7i6wfjjRuR5QgfC3IR3Mk1WQM1JGXCLL+uUjTUxKXFUj28hjSJfsmVbTUhoVvgZEWfw==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
|
||||
'@tiptap/extension-document@2.14.0':
|
||||
resolution: {integrity: sha512-qwEgpPIJ3AgXdEtRTr88hODbXRdt14VAwLj27PTSqexB5V7Ra1Jy7iQDhqRwBCoUomVywBsWYxkSuDisSRG+9w==}
|
||||
'@tiptap/extension-document@2.23.0':
|
||||
resolution: {integrity: sha512-kuRPqH0UdjZ4RcnpPELsu1N8LqeixEin+mv5eaQJI/aZ6rFq+kcY4UZF3C7q56Rat5r9CgHBiZbD0t5l6E3gdA==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
|
||||
'@tiptap/extension-dropcursor@2.14.0':
|
||||
resolution: {integrity: sha512-FIh5cdPuoPKvZ0GqSKhzMZGixm05ac3hSgqhMNCBZmXX459qBUI9CvDl/uzSnY9koBDeLVV3HYMthWQQLSXl9A==}
|
||||
'@tiptap/extension-dropcursor@2.23.0':
|
||||
resolution: {integrity: sha512-m2LzkJpipHLPEllD3MXZQMssu7Xng7YJOJ8ZNDkF0uUkXljwh7G0ROjGNKUlV8/dqoCVmJIZIyF6t9saQwTTbA==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
'@tiptap/pm': ^2.7.0
|
||||
|
||||
'@tiptap/extension-gapcursor@2.14.0':
|
||||
resolution: {integrity: sha512-as+SqC39FRshw4Fm1XVlrdSXveiusf5xiC4nuefLmXsUxO7Yx67x8jS0/VQbxWTLHZ6R1YEW8prLtnxGmVLCAQ==}
|
||||
'@tiptap/extension-gapcursor@2.23.0':
|
||||
resolution: {integrity: sha512-SpYsDtMiVwqcSB84g714PrnHo985R5UiIaGngef6iMNy/0xjKcO0tj/feu0WwJDuSj22Opzlnb/Ld/D4Va27Ng==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
'@tiptap/pm': ^2.7.0
|
||||
|
||||
'@tiptap/extension-hard-break@2.14.0':
|
||||
resolution: {integrity: sha512-A8c8n8881iBq3AusNqibh6Hloybr+FgYdg4Lg4jNxbbEaL0WhyLFge1bWlGVpbHXFqdv5YldMUAu6Rop3FhNvw==}
|
||||
'@tiptap/extension-hard-break@2.23.0':
|
||||
resolution: {integrity: sha512-OpNBEYv9HDUPo8SgvmI5oPd0b+xmdadtFyL7t4lxhYar8n5NDYubaXYgbKcdJfXvUxEeGwdc3ePnTFpsF0mrYw==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
|
||||
'@tiptap/extension-history@2.14.0':
|
||||
resolution: {integrity: sha512-/qnOHQFCEPfkb3caykqd+sqzEC2gx30EQB/mM7+5kIG7CQy7XXaGjFAEaqzE1xJ783Q2E7GVk4JxWM+3NhYSLw==}
|
||||
'@tiptap/extension-history@2.23.0':
|
||||
resolution: {integrity: sha512-W+2bZ/02nm56g/wmEaSx9QcdZ8mHjoFyc8MKf54Mrzi+nIdNjsNreKrn1yCp683CGEPd8DLadDFkz0o13N+rxA==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
'@tiptap/pm': ^2.7.0
|
||||
|
||||
'@tiptap/extension-image@2.14.0':
|
||||
resolution: {integrity: sha512-pYCUzZBgsxIvVGTzuW03cPz6PIrAo26xpoxqq4W090uMVoK0SgY5W5y0IqCdw4QyLkJ2/oNSFNc2EP9jVi1CcQ==}
|
||||
'@tiptap/extension-image@2.23.0':
|
||||
resolution: {integrity: sha512-/rW2+a21VBGBv5c/78CVW8XA7bThSqE3FqcBtWyq8IxZoe8Hj9+Jac7FcB2YR3aY0BeHwso474e1RuVr1iYBKQ==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
|
||||
'@tiptap/extension-link@2.14.0':
|
||||
resolution: {integrity: sha512-fsqW7eRD2xoD6xy7eFrNPAdIuZ3eicA4jKC45Vcft/Xky0DJoIehlVBLxsPbfmv3f27EBrtPkg5+msLXkLyzJA==}
|
||||
'@tiptap/extension-link@2.23.0':
|
||||
resolution: {integrity: sha512-D+ethAE8+2f7RH7kqS+//EsC2wNblhmssJYVE0hCXM5BKIBixjs8eCOAvLbJsw0u/5LqFYjsyAimTqa4hD5uvg==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
'@tiptap/pm': ^2.7.0
|
||||
|
||||
'@tiptap/extension-paragraph@2.14.0':
|
||||
resolution: {integrity: sha512-bsQesVpgvDS2e+wr2fp59QO7rWRp2FqcJvBafwXS3Br9U5Mx3eFYryx4wC7cUnhlhUwX5pmaoA7zISgV9dZDgg==}
|
||||
'@tiptap/extension-paragraph@2.23.0':
|
||||
resolution: {integrity: sha512-MXhRkb741UOcJp2evG/H0MY3WJQnX7z8PsejmJbJXOHBrS/Esxq0AlrDAjuFhbfAnJwYiWQ1lk6ucvKV6DhFuQ==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
|
||||
'@tiptap/extension-placeholder@2.14.0':
|
||||
resolution: {integrity: sha512-xzfjHvuukbch4i5O/5uyS2K2QgNEaMKi6e6GExTTgVwnFjKfJmgTqee33tt5JCqSItBvtSZlU3SX/vpiaIof+w==}
|
||||
'@tiptap/extension-placeholder@2.23.0':
|
||||
resolution: {integrity: sha512-I5RQk0qn6nj7l7z4mWKIxjO2nluvKsm00W2CbC75b4YcScBfsMInHQdjN2s+W8xuF0zquhwVITxA+Bmn4zynqg==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
'@tiptap/pm': ^2.7.0
|
||||
|
||||
'@tiptap/extension-text@2.14.0':
|
||||
resolution: {integrity: sha512-rHny566nGZHq61zRLwQ9BPG55W/O+eDKwUJl+LhrLiVWwzpvAl9QQYixtoxJKOY48VK41PKwxe3bgDYgNs/Fhg==}
|
||||
'@tiptap/extension-text@2.23.0':
|
||||
resolution: {integrity: sha512-hF+CU1H4B4UgqjBXXPPaACVZdSGuMH0TDYTd7h403qUAIBKkYbjuan7laQpiT0qnF0Dg+sGgvmGcd4H1tTBM8g==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
|
||||
'@tiptap/pm@2.12.0':
|
||||
resolution: {integrity: sha512-TNzVwpeNzFfHAcYTOKqX9iU4fRxliyoZrCnERR+RRzeg7gWrXrCLubQt1WEx0sojMAfznshSL3M5HGsYjEbYwA==}
|
||||
|
||||
'@tiptap/suggestion@2.14.0':
|
||||
resolution: {integrity: sha512-AXzEw0KYIyg5id8gz5geIffnBtkZqan5MWe29rGo3gXTfKH+Ik8tWbZdnlMVheycsUCllrymDRei4zw9DqVqkQ==}
|
||||
'@tiptap/suggestion@2.23.0':
|
||||
resolution: {integrity: sha512-WUUGADu8ZezXZ4hXZWdfGcfoitB5tiBrc2u1oXqqL8VmJJedhY4MdWUPYqgh3359tAI2yJWmv+gPabX361gBEA==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
'@tiptap/pm': ^2.7.0
|
||||
@@ -1596,44 +1609,41 @@ packages:
|
||||
'@vite-pwa/assets-generator':
|
||||
optional: true
|
||||
|
||||
'@welshman/app@0.3.4':
|
||||
resolution: {integrity: sha512-pb1I53hWog6plLVyOx6biN0uiB7zdheDqN2hWfb/uUKKzD484u+Yk3p5H2S6VaJnSbgNHnPuuJnjVstts1rhLg==}
|
||||
'@welshman/app@0.3.8':
|
||||
resolution: {integrity: sha512-WmvqB8Z/qPN0dJerf3QTp5MniZQGXToNXJWorEFA3LQwBJWAovPFfdq8xoNGe9tWwYl0m9Rt/ObZf3gFlX28Cw==}
|
||||
|
||||
'@welshman/content@0.3.4':
|
||||
resolution: {integrity: sha512-kuyykt5SK4vHUciJBooqfLDI/HBxekZPK3qVgmKlpVf8cMRmaSOGTjsKljINgFsT/3J5z7wZI5AzQR/2FBo2ZQ==}
|
||||
'@welshman/content@0.3.8':
|
||||
resolution: {integrity: sha512-ic7imQR0cpolUlwnWVfUqiIo9zkOt6DS2M92BD4Y/mCLGrUMzlUw0/NE5TzBJ6dSywVh8/aBBOTWotzpmbttKg==}
|
||||
|
||||
'@welshman/dvm@0.3.4':
|
||||
resolution: {integrity: sha512-39uSVco5VZmYEE+BZ3Lx/hGVM/HzGfnlEMR0fysh4BqfMm3Po9KXfOLy4YDMoHt7Ai/rC4z/HdJ81F3jHhJZjw==}
|
||||
'@welshman/editor@0.3.8':
|
||||
resolution: {integrity: sha512-XZ/cXEM3MIwhR7CZvboH2askm9dZJ9cH7/CS4Asd3Q/OaaPUrCTCoacEpR1z3raMMOz0TiDn+BgjGqrHYU/58Q==}
|
||||
|
||||
'@welshman/editor@0.3.4':
|
||||
resolution: {integrity: sha512-fpUnacyZvbtytadtVwV4CEm1rGHgp8xWHQB7pZ0m/5bxlAHT1p/cL5a83CjdrVbu3+HMzBfgV4k6ldhH1XnJaQ==}
|
||||
'@welshman/feeds@0.3.8':
|
||||
resolution: {integrity: sha512-Rjf36Eng22PMY9p1yepMDfXH2dlCPg5yBVidu8WArFWLgxSv67DCxAsDlHkRd/mJD5DARFDob1WZP9bUsljJBw==}
|
||||
|
||||
'@welshman/feeds@0.3.4':
|
||||
resolution: {integrity: sha512-w4GLcWOxTUlx9MBbqHfee0ePWW8oyMGaf+Hey35FNsKcBvxiO58uW9j6BBvlu9yacthw0RGa/91giJn3zZzx1Q==}
|
||||
|
||||
'@welshman/lib@0.3.4':
|
||||
resolution: {integrity: sha512-37WDPZHsUFBLKdlkQXKAVuEDZB3lzQOkfIG0MMy7uZsg8+zR4VoA28g9B1vAKGky9lmWequHMPNigPRjC7RHQA==}
|
||||
'@welshman/lib@0.3.8':
|
||||
resolution: {integrity: sha512-fbq94UkyoC7kieAlWsDzH6zgZt+GVhkh1RiFvtE4iGug3bIPxh1QmmvO70EHlFkiEDcYAFEEHd9OMm6/JRc97Q==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
'@welshman/net@0.3.4':
|
||||
resolution: {integrity: sha512-btC7eXwEA8wfzaCsq8mdFIfbX61JeoyI3am53+L23ujbJQ9aqCciTA6EYEa2JzYB2HdydmCzdZrP/DTSjHN6bw==}
|
||||
'@welshman/net@0.3.8':
|
||||
resolution: {integrity: sha512-DWNL+BGmOGCfXdYOnpd3IJ7IuCskftUfXnjBo8F0rkHWUWAE6Q1zTJmdJG3kDVDoWl3X4XBfxkl4WGckF9pSkw==}
|
||||
|
||||
'@welshman/relay@0.3.4':
|
||||
resolution: {integrity: sha512-XjZY6XQXvNVFr3KC4O2Mlj+LHg/xJuQnhAHUYemvtJ9BcxvY01fq4ESadyiJklCjqNabsCPz76o+qFweZmym7Q==}
|
||||
'@welshman/relay@0.3.8':
|
||||
resolution: {integrity: sha512-vWUOxvG4WV0+EsC/BYoCF9L2W/qml4TIAnGHmWpDt9jTS82kIQt3cu2+3uJs9IZli+etRJ3xYJLVvfzfQqBaig==}
|
||||
|
||||
'@welshman/router@0.3.4':
|
||||
resolution: {integrity: sha512-ej4EjXWNj6srj6xnzcmFqhGXg6asTByPHPxQCkPG6HDOMVmG8GejlcatvjkzB4ppfdwYCz+jyJvjq30JBHmlbQ==}
|
||||
'@welshman/router@0.3.8':
|
||||
resolution: {integrity: sha512-3Gn3yjMbQ9sQ8qsX5bjUtTSKkwdOREqGVzcQiBq1FRATh42ih06Opu5/t8ujcMRMhV3w/02ckfZJ46DF34ozWQ==}
|
||||
|
||||
'@welshman/signer@0.3.4':
|
||||
resolution: {integrity: sha512-kg2d4dQutRYvuuiCFC2I0UZfEHu+71t6o2g+jMtJ6d55dSa6+xUMCQ0/8Ua0AMOc/D7tJ1HLvtbmOjsehKkkKQ==}
|
||||
'@welshman/signer@0.3.8':
|
||||
resolution: {integrity: sha512-L3hAJlS0smS4uSC5b1xskHDbrkuCykXr5b6Z9aNV9MJjczbb5pCuYjHf9lCvcmqaz4NN4kcksXlLZgxMbE/5Jg==}
|
||||
peerDependencies:
|
||||
nostr-signer-capacitor-plugin: ~0.0.4
|
||||
|
||||
'@welshman/store@0.3.4':
|
||||
resolution: {integrity: sha512-AYDLTA7+ptbJYfAVRQ5lC/l65FtkAj7lKSadUguSf+R10JClNeYPWqQ6RTRpvyZ9qn9QetuYx8ecLfm8BxC7Ow==}
|
||||
'@welshman/store@0.3.8':
|
||||
resolution: {integrity: sha512-SHKF9RjcvoqcduHDleKSjhubrS10f5XmcU2kBPFy/U9G7hi4M5f4HQa46Kt7Mf4OHuofnjRw+4RLopkqjFL3ow==}
|
||||
|
||||
'@welshman/util@0.3.4':
|
||||
resolution: {integrity: sha512-sBznEqAPlNu9CZQSNFPcL5kNOIoWYfUTY/lyXQXLiYhMorXQmVGXS4TT7knS41oKiLgkUK98T0DvjMr4iJd3/g==}
|
||||
'@welshman/util@0.3.8':
|
||||
resolution: {integrity: sha512-+doowqtIjUChPGdmGNopO6bAvC/0LkF2zKaEKPHnIfDAsw4gAJyuAvIlS/PUv8tY6scXORGeGMnoBC0Htd3Ovg==}
|
||||
|
||||
'@xml-tools/parser@1.0.11':
|
||||
resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==}
|
||||
@@ -3381,8 +3391,8 @@ packages:
|
||||
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
nostr-editor-coracle-workaround@0.0.4-pre.18:
|
||||
resolution: {integrity: sha512-QAoySZ9uOsR7C4nnVbcEpVgT0vLxwZxlYhE2NsJzmeoK7nTgkcHFaZkn/QMlUem3qjT8AolW8X5TrRdnZ5eIZQ==}
|
||||
nostr-editor@1.0.0:
|
||||
resolution: {integrity: sha512-+TL3G0m7WsXeEAitxzQhun7hyARxqRANjGIS2z9CBbniCGvT/Wz6YLgUnUysnBg3tmSgMZg5FWhaDPwfvdvbSw==}
|
||||
engines: {node: '>=18.16.1'}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.6.6
|
||||
@@ -3390,10 +3400,11 @@ packages:
|
||||
'@tiptap/extension-link': ^2.6.6
|
||||
'@tiptap/pm': ^2.6.6
|
||||
linkifyjs: ^4.1.3
|
||||
nostr-tools: ^2.14.2
|
||||
nostr-tools: ~2.14.2
|
||||
prosemirror-markdown: ^1.13.0
|
||||
prosemirror-model: ^1.22.3
|
||||
prosemirror-state: ^1.4.3
|
||||
prosemirror-view: ^1.39.3
|
||||
tiptap-markdown: ^0.8.10
|
||||
|
||||
nostr-signer-capacitor-plugin@0.0.4:
|
||||
@@ -4687,8 +4698,8 @@ packages:
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
ws@8.18.2:
|
||||
resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==}
|
||||
ws@8.18.3:
|
||||
resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
@@ -5547,6 +5558,14 @@ snapshots:
|
||||
dependencies:
|
||||
'@capacitor/core': 7.2.0
|
||||
|
||||
'@capacitor/push-notifications@7.0.1(@capacitor/core@7.2.0)':
|
||||
dependencies:
|
||||
'@capacitor/core': 7.2.0
|
||||
|
||||
'@capawesome/capacitor-badge@7.0.1(@capacitor/core@7.2.0)':
|
||||
dependencies:
|
||||
'@capacitor/core': 7.2.0
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
@@ -6150,58 +6169,58 @@ snapshots:
|
||||
dependencies:
|
||||
'@tiptap/pm': 2.12.0
|
||||
|
||||
'@tiptap/extension-code-block@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
|
||||
'@tiptap/extension-code-block@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||
'@tiptap/pm': 2.12.0
|
||||
|
||||
'@tiptap/extension-code@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
|
||||
'@tiptap/extension-code@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||
|
||||
'@tiptap/extension-document@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
|
||||
'@tiptap/extension-document@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||
|
||||
'@tiptap/extension-dropcursor@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
|
||||
'@tiptap/extension-dropcursor@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||
'@tiptap/pm': 2.12.0
|
||||
|
||||
'@tiptap/extension-gapcursor@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
|
||||
'@tiptap/extension-gapcursor@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||
'@tiptap/pm': 2.12.0
|
||||
|
||||
'@tiptap/extension-hard-break@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
|
||||
'@tiptap/extension-hard-break@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||
|
||||
'@tiptap/extension-history@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
|
||||
'@tiptap/extension-history@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||
'@tiptap/pm': 2.12.0
|
||||
|
||||
'@tiptap/extension-image@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
|
||||
'@tiptap/extension-image@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||
|
||||
'@tiptap/extension-link@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
|
||||
'@tiptap/extension-link@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||
'@tiptap/pm': 2.12.0
|
||||
linkifyjs: 4.3.1
|
||||
|
||||
'@tiptap/extension-paragraph@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
|
||||
'@tiptap/extension-paragraph@2.23.0(@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.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
|
||||
'@tiptap/extension-placeholder@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||
'@tiptap/pm': 2.12.0
|
||||
|
||||
'@tiptap/extension-text@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
|
||||
'@tiptap/extension-text@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||
|
||||
@@ -6226,7 +6245,7 @@ snapshots:
|
||||
prosemirror-transform: 1.10.4
|
||||
prosemirror-view: 1.39.3
|
||||
|
||||
'@tiptap/suggestion@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
|
||||
'@tiptap/suggestion@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||
'@tiptap/pm': 2.12.0
|
||||
@@ -6435,18 +6454,17 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@vite-pwa/assets-generator': 0.2.6
|
||||
|
||||
'@welshman/app@0.3.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)':
|
||||
'@welshman/app@0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
|
||||
dependencies:
|
||||
'@types/throttle-debounce': 5.0.2
|
||||
'@welshman/dvm': 0.3.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)
|
||||
'@welshman/feeds': 0.3.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)
|
||||
'@welshman/lib': 0.3.4
|
||||
'@welshman/net': 0.3.4(typescript@5.8.3)(ws@8.18.2)
|
||||
'@welshman/relay': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/router': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/signer': 0.3.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)
|
||||
'@welshman/store': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/util': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/feeds': 0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
||||
'@welshman/lib': 0.3.8
|
||||
'@welshman/net': 0.3.8(typescript@5.8.3)(ws@8.18.3)
|
||||
'@welshman/relay': 0.3.8(typescript@5.8.3)
|
||||
'@welshman/router': 0.3.8(typescript@5.8.3)
|
||||
'@welshman/signer': 0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
||||
'@welshman/store': 0.3.8(typescript@5.8.3)
|
||||
'@welshman/util': 0.3.8(typescript@5.8.3)
|
||||
fuse.js: 7.1.0
|
||||
idb: 8.0.2
|
||||
svelte: 4.2.20
|
||||
@@ -6456,43 +6474,31 @@ snapshots:
|
||||
- typescript
|
||||
- ws
|
||||
|
||||
'@welshman/content@0.3.4(typescript@5.8.3)':
|
||||
'@welshman/content@0.3.8(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@braintree/sanitize-url': 7.1.1
|
||||
nostr-tools: 2.14.2(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@welshman/dvm@0.3.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)':
|
||||
dependencies:
|
||||
'@noble/hashes': 1.8.0
|
||||
'@welshman/lib': 0.3.4
|
||||
'@welshman/net': 0.3.4(typescript@5.8.3)(ws@8.18.2)
|
||||
'@welshman/signer': 0.3.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)
|
||||
'@welshman/util': 0.3.4(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- nostr-signer-capacitor-plugin
|
||||
- typescript
|
||||
- ws
|
||||
|
||||
'@welshman/editor@0.3.4(@tiptap/extension-image@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)':
|
||||
'@welshman/editor@0.3.8(@tiptap/extension-image@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||
'@tiptap/extension-code': 2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
||||
'@tiptap/extension-code-block': 2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||
'@tiptap/extension-document': 2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
||||
'@tiptap/extension-dropcursor': 2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||
'@tiptap/extension-gapcursor': 2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||
'@tiptap/extension-hard-break': 2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
||||
'@tiptap/extension-history': 2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||
'@tiptap/extension-paragraph': 2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
||||
'@tiptap/extension-placeholder': 2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||
'@tiptap/extension-text': 2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
||||
'@tiptap/extension-code': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
||||
'@tiptap/extension-code-block': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||
'@tiptap/extension-document': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
||||
'@tiptap/extension-dropcursor': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||
'@tiptap/extension-gapcursor': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||
'@tiptap/extension-hard-break': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
||||
'@tiptap/extension-history': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||
'@tiptap/extension-paragraph': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
||||
'@tiptap/extension-placeholder': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||
'@tiptap/extension-text': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
||||
'@tiptap/pm': 2.12.0
|
||||
'@tiptap/suggestion': 2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||
'@welshman/lib': 0.3.4
|
||||
'@welshman/util': 0.3.4(typescript@5.8.3)
|
||||
nostr-editor-coracle-workaround: 0.0.4-pre.18(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-image@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(linkifyjs@4.3.1)(nostr-tools@2.14.2(typescript@5.8.3))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))
|
||||
'@tiptap/suggestion': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||
'@welshman/lib': 0.3.8
|
||||
'@welshman/util': 0.3.8(typescript@5.8.3)
|
||||
nostr-editor: 1.0.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-image@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(linkifyjs@4.3.1)(nostr-tools@2.14.2(typescript@5.8.3))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))
|
||||
nostr-tools: 2.14.2(typescript@5.8.3)
|
||||
tippy.js: 6.3.7
|
||||
transitivePeerDependencies:
|
||||
@@ -6502,82 +6508,82 @@ snapshots:
|
||||
- prosemirror-markdown
|
||||
- prosemirror-model
|
||||
- prosemirror-state
|
||||
- prosemirror-view
|
||||
- tiptap-markdown
|
||||
- typescript
|
||||
|
||||
'@welshman/feeds@0.3.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)':
|
||||
'@welshman/feeds@0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
|
||||
dependencies:
|
||||
'@welshman/dvm': 0.3.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)
|
||||
'@welshman/lib': 0.3.4
|
||||
'@welshman/net': 0.3.4(typescript@5.8.3)(ws@8.18.2)
|
||||
'@welshman/relay': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/router': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/signer': 0.3.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)
|
||||
'@welshman/util': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/lib': 0.3.8
|
||||
'@welshman/net': 0.3.8(typescript@5.8.3)(ws@8.18.3)
|
||||
'@welshman/relay': 0.3.8(typescript@5.8.3)
|
||||
'@welshman/router': 0.3.8(typescript@5.8.3)
|
||||
'@welshman/signer': 0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
||||
'@welshman/util': 0.3.8(typescript@5.8.3)
|
||||
trava: 1.2.1
|
||||
transitivePeerDependencies:
|
||||
- nostr-signer-capacitor-plugin
|
||||
- typescript
|
||||
- ws
|
||||
|
||||
'@welshman/lib@0.3.4':
|
||||
'@welshman/lib@0.3.8':
|
||||
dependencies:
|
||||
'@scure/base': 1.2.6
|
||||
'@types/events': 3.0.3
|
||||
events: 3.3.0
|
||||
|
||||
'@welshman/net@0.3.4(typescript@5.8.3)(ws@8.18.2)':
|
||||
'@welshman/net@0.3.8(typescript@5.8.3)(ws@8.18.3)':
|
||||
dependencies:
|
||||
'@welshman/lib': 0.3.4
|
||||
'@welshman/relay': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/util': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/lib': 0.3.8
|
||||
'@welshman/relay': 0.3.8(typescript@5.8.3)
|
||||
'@welshman/util': 0.3.8(typescript@5.8.3)
|
||||
events: 3.3.0
|
||||
isomorphic-ws: 5.0.0(ws@8.18.2)
|
||||
isomorphic-ws: 5.0.0(ws@8.18.3)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
- ws
|
||||
|
||||
'@welshman/relay@0.3.4(typescript@5.8.3)':
|
||||
'@welshman/relay@0.3.8(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@welshman/lib': 0.3.4
|
||||
'@welshman/util': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/lib': 0.3.8
|
||||
'@welshman/util': 0.3.8(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@welshman/router@0.3.4(typescript@5.8.3)':
|
||||
'@welshman/router@0.3.8(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@welshman/lib': 0.3.4
|
||||
'@welshman/relay': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/util': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/lib': 0.3.8
|
||||
'@welshman/relay': 0.3.8(typescript@5.8.3)
|
||||
'@welshman/util': 0.3.8(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@welshman/signer@0.3.4(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.2)':
|
||||
'@welshman/signer@0.3.8(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
|
||||
dependencies:
|
||||
'@noble/curves': 1.9.2
|
||||
'@noble/hashes': 1.8.0
|
||||
'@welshman/lib': 0.3.4
|
||||
'@welshman/net': 0.3.4(typescript@5.8.3)(ws@8.18.2)
|
||||
'@welshman/util': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/lib': 0.3.8
|
||||
'@welshman/net': 0.3.8(typescript@5.8.3)(ws@8.18.3)
|
||||
'@welshman/util': 0.3.8(typescript@5.8.3)
|
||||
nostr-signer-capacitor-plugin: 0.0.4(@capacitor/core@7.2.0)
|
||||
nostr-tools: 2.14.2(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
- ws
|
||||
|
||||
'@welshman/store@0.3.4(typescript@5.8.3)':
|
||||
'@welshman/store@0.3.8(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@welshman/lib': 0.3.4
|
||||
'@welshman/relay': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/util': 0.3.4(typescript@5.8.3)
|
||||
'@welshman/lib': 0.3.8
|
||||
'@welshman/relay': 0.3.8(typescript@5.8.3)
|
||||
'@welshman/util': 0.3.8(typescript@5.8.3)
|
||||
svelte: 4.2.20
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@welshman/util@0.3.4(typescript@5.8.3)':
|
||||
'@welshman/util@0.3.8(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@types/ws': 8.18.1
|
||||
'@welshman/lib': 0.3.4
|
||||
'@welshman/lib': 0.3.8
|
||||
js-base64: 3.7.7
|
||||
nostr-tools: 2.14.2(typescript@5.8.3)
|
||||
nostr-wasm: 0.1.0
|
||||
@@ -8075,9 +8081,9 @@ snapshots:
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
isomorphic-ws@5.0.0(ws@8.18.2):
|
||||
isomorphic-ws@5.0.0(ws@8.18.3):
|
||||
dependencies:
|
||||
ws: 8.18.2
|
||||
ws: 8.18.3
|
||||
|
||||
jackspeak@3.4.3:
|
||||
dependencies:
|
||||
@@ -8409,11 +8415,11 @@ snapshots:
|
||||
|
||||
normalize-range@0.1.2: {}
|
||||
|
||||
nostr-editor-coracle-workaround@0.0.4-pre.18(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-image@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(linkifyjs@4.3.1)(nostr-tools@2.14.2(typescript@5.8.3))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))):
|
||||
nostr-editor@1.0.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-image@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(linkifyjs@4.3.1)(nostr-tools@2.14.2(typescript@5.8.3))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))):
|
||||
dependencies:
|
||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||
'@tiptap/extension-image': 2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
||||
'@tiptap/extension-link': 2.14.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||
'@tiptap/extension-image': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
||||
'@tiptap/extension-link': 2.23.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||
'@tiptap/pm': 2.12.0
|
||||
js-base64: 3.7.7
|
||||
light-bolt11-decoder: 3.2.0
|
||||
@@ -8422,6 +8428,7 @@ snapshots:
|
||||
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-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0):
|
||||
@@ -9924,7 +9931,7 @@ snapshots:
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
ws@8.18.2: {}
|
||||
ws@8.18.3: {}
|
||||
|
||||
xcode@3.0.1:
|
||||
dependencies:
|
||||
|
||||
+83
-75
@@ -1,6 +1,6 @@
|
||||
import * as nip19 from "nostr-tools/nip19"
|
||||
import {get} from "svelte/store"
|
||||
import {randomId, poll, uniq, equals, TIMEZONE, LOCALE} from "@welshman/lib"
|
||||
import {randomId, flatten, poll, uniq, equals, TIMEZONE, LOCALE} from "@welshman/lib"
|
||||
import type {Feed} from "@welshman/feeds"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {
|
||||
@@ -12,14 +12,14 @@ import {
|
||||
FOLLOWS,
|
||||
REACTION,
|
||||
AUTH_JOIN,
|
||||
GROUP_JOIN,
|
||||
GROUP_LEAVE,
|
||||
GROUP_CREATE,
|
||||
GROUP_EDIT_META,
|
||||
GROUPS,
|
||||
ROOMS,
|
||||
COMMENT,
|
||||
ALERT_EMAIL,
|
||||
ALERT_WEB,
|
||||
ALERT_IOS,
|
||||
ALERT_ANDROID,
|
||||
isSignedEvent,
|
||||
createEvent,
|
||||
makeEvent,
|
||||
displayProfile,
|
||||
normalizeRelayUrl,
|
||||
makeList,
|
||||
@@ -55,11 +55,9 @@ import {
|
||||
getThunkError,
|
||||
} from "@welshman/app"
|
||||
import {
|
||||
tagRoom,
|
||||
PROTECTED,
|
||||
userMembership,
|
||||
INDEXER_RELAYS,
|
||||
ALERT,
|
||||
NOTIFIER_PUBKEY,
|
||||
NOTIFIER_RELAY,
|
||||
userRoomsByUrl,
|
||||
@@ -126,38 +124,10 @@ export const broadcastUserData = async (relays: string[]) => {
|
||||
}
|
||||
}
|
||||
|
||||
// NIP 29 stuff
|
||||
|
||||
export const createRoom = (url: string, room: string) => {
|
||||
const event = createEvent(GROUP_CREATE, {tags: [tagRoom(room, url)]})
|
||||
|
||||
return publishThunk({event, relays: [url]})
|
||||
}
|
||||
|
||||
export const editRoom = (url: string, room: string, meta: Record<string, string>) => {
|
||||
const event = createEvent(GROUP_EDIT_META, {
|
||||
tags: [tagRoom(room, url), ...Object.entries(meta)],
|
||||
})
|
||||
|
||||
return publishThunk({event, relays: [url]})
|
||||
}
|
||||
|
||||
export const joinRoom = (url: string, room: string) => {
|
||||
const event = createEvent(GROUP_JOIN, {tags: [tagRoom(room, url)]})
|
||||
|
||||
return publishThunk({event, relays: [url]})
|
||||
}
|
||||
|
||||
export const leaveRoom = (url: string, room: string) => {
|
||||
const event = createEvent(GROUP_LEAVE, {tags: [tagRoom(room, url)]})
|
||||
|
||||
return publishThunk({event, relays: [url]})
|
||||
}
|
||||
|
||||
// List updates
|
||||
|
||||
export const addSpaceMembership = async (url: string) => {
|
||||
const list = get(userMembership) || makeList({kind: GROUPS})
|
||||
const list = get(userMembership) || makeList({kind: ROOMS})
|
||||
const event = await addToListPublicly(list, ["r", url]).reconcile(nip44EncryptToSelf)
|
||||
const relays = uniq([...Router.get().FromUser().getUrls(), ...getRelayTagValues(event.tags)])
|
||||
|
||||
@@ -165,7 +135,7 @@ export const addSpaceMembership = async (url: string) => {
|
||||
}
|
||||
|
||||
export const removeSpaceMembership = async (url: string) => {
|
||||
const list = get(userMembership) || makeList({kind: GROUPS})
|
||||
const list = get(userMembership) || makeList({kind: ROOMS})
|
||||
const pred = (t: string[]) => t[t[0] === "r" ? 1 : 2] === url
|
||||
const event = await removeFromListByPredicate(list, pred).reconcile(nip44EncryptToSelf)
|
||||
const relays = uniq([url, ...Router.get().FromUser().getUrls(), ...getRelayTagValues(event.tags)])
|
||||
@@ -174,7 +144,7 @@ export const removeSpaceMembership = async (url: string) => {
|
||||
}
|
||||
|
||||
export const addRoomMembership = async (url: string, room: string) => {
|
||||
const list = get(userMembership) || makeList({kind: GROUPS})
|
||||
const list = get(userMembership) || makeList({kind: ROOMS})
|
||||
const newTags = [
|
||||
["r", url],
|
||||
["group", room, url],
|
||||
@@ -186,7 +156,7 @@ export const addRoomMembership = async (url: string, room: string) => {
|
||||
}
|
||||
|
||||
export const removeRoomMembership = async (url: string, room: string) => {
|
||||
const list = get(userMembership) || makeList({kind: GROUPS})
|
||||
const list = get(userMembership) || makeList({kind: ROOMS})
|
||||
const pred = (t: string[]) => equals(["group", room, url], t.slice(0, 3))
|
||||
const event = await removeFromListByPredicate(list, pred).reconcile(nip44EncryptToSelf)
|
||||
const relays = uniq([url, ...Router.get().FromUser().getUrls(), ...getRelayTagValues(event.tags)])
|
||||
@@ -207,7 +177,7 @@ export const setRelayPolicy = (url: string, read: boolean, write: boolean) => {
|
||||
}
|
||||
|
||||
return publishThunk({
|
||||
event: createEvent(list.kind, {tags}),
|
||||
event: makeEvent(list.kind, {tags}),
|
||||
relays: [
|
||||
url,
|
||||
...INDEXER_RELAYS,
|
||||
@@ -229,7 +199,7 @@ export const setInboxRelayPolicy = (url: string, enabled: boolean) => {
|
||||
}
|
||||
|
||||
return publishThunk({
|
||||
event: createEvent(list.kind, {tags}),
|
||||
event: makeEvent(list.kind, {tags}),
|
||||
relays: [
|
||||
...INDEXER_RELAYS,
|
||||
...Router.get().FromUser().getUrls(),
|
||||
@@ -241,13 +211,18 @@ export const setInboxRelayPolicy = (url: string, enabled: boolean) => {
|
||||
|
||||
// Relay access
|
||||
|
||||
export const attemptAuth = (url: string) =>
|
||||
Pool.get()
|
||||
.get(url)
|
||||
.auth.attemptAuth(e => signer.get()?.sign(e))
|
||||
|
||||
export const checkRelayAccess = async (url: string, claim = "") => {
|
||||
const socket = Pool.get().get(url)
|
||||
|
||||
await socket.auth.attemptAuth(e => signer.get()?.sign(e))
|
||||
await attemptAuth(url)
|
||||
|
||||
const thunk = publishThunk({
|
||||
event: createEvent(AUTH_JOIN, {tags: [["claim", claim]]}),
|
||||
event: makeEvent(AUTH_JOIN, {tags: [["claim", claim]]}),
|
||||
relays: [url],
|
||||
})
|
||||
|
||||
@@ -266,6 +241,11 @@ export const checkRelayAccess = async (url: string, claim = "") => {
|
||||
// Ignore messages about the relay ignoring ours
|
||||
if (error?.startsWith("mute: ")) return
|
||||
|
||||
// Ignore rejected empty claims
|
||||
if (!claim && error?.includes("invite code")) {
|
||||
return `failed to request access to relay`
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
}
|
||||
@@ -297,7 +277,7 @@ export const checkRelayAuth = async (url: string, timeout = 3000) => {
|
||||
const socket = Pool.get().get(url)
|
||||
const okStatuses = [AuthStatus.None, AuthStatus.Ok]
|
||||
|
||||
await socket.auth.attemptAuth(e => signer.get()?.sign(e))
|
||||
await attemptAuth(url)
|
||||
|
||||
// Only raise an error if it's not a timeout.
|
||||
// If it is, odds are the problem is with our signer, not the relay
|
||||
@@ -324,20 +304,26 @@ export const attemptRelayAccess = async (url: string, claim = "") => {
|
||||
|
||||
// Actions
|
||||
|
||||
export const makeDelete = ({event}: {event: TrustedEvent}) => {
|
||||
const tags = [["k", String(event.kind)], ...tagEvent(event)]
|
||||
export const makeDelete = ({event, tags = []}: {event: TrustedEvent; tags?: string[][]}) => {
|
||||
const thisTags = [["k", String(event.kind)], ...tagEvent(event), ...tags]
|
||||
const groupTag = getTag("h", event.tags)
|
||||
|
||||
if (groupTag) {
|
||||
tags.push(PROTECTED)
|
||||
tags.push(groupTag)
|
||||
thisTags.push(PROTECTED, groupTag)
|
||||
}
|
||||
|
||||
return createEvent(DELETE, {tags})
|
||||
return makeEvent(DELETE, {tags: thisTags})
|
||||
}
|
||||
|
||||
export const publishDelete = ({relays, event}: {relays: string[]; event: TrustedEvent}) =>
|
||||
publishThunk({event: makeDelete({event}), relays})
|
||||
export const publishDelete = ({
|
||||
relays,
|
||||
event,
|
||||
tags = [],
|
||||
}: {
|
||||
relays: string[]
|
||||
event: TrustedEvent
|
||||
tags?: string[][]
|
||||
}) => publishThunk({event: makeDelete({event, tags}), relays})
|
||||
|
||||
export type ReportParams = {
|
||||
event: TrustedEvent
|
||||
@@ -351,7 +337,7 @@ export const makeReport = ({event, reason, content}: ReportParams) => {
|
||||
["e", event.id, reason],
|
||||
]
|
||||
|
||||
return createEvent(REPORT, {content, tags})
|
||||
return makeEvent(REPORT, {content, tags})
|
||||
}
|
||||
|
||||
export const publishReport = ({
|
||||
@@ -378,7 +364,7 @@ export const makeReaction = ({content, event, tags: paramTags = []}: ReactionPar
|
||||
tags.push(groupTag)
|
||||
}
|
||||
|
||||
return createEvent(REACTION, {content, tags})
|
||||
return makeEvent(REACTION, {content, tags})
|
||||
}
|
||||
|
||||
export const publishReaction = ({relays, ...params}: ReactionParams & {relays: string[]}) =>
|
||||
@@ -391,42 +377,64 @@ export type CommentParams = {
|
||||
}
|
||||
|
||||
export const makeComment = ({event, content, tags = []}: CommentParams) =>
|
||||
createEvent(COMMENT, {content, tags: [...tags, ...tagEventForComment(event)]})
|
||||
makeEvent(COMMENT, {content, tags: [...tags, ...tagEventForComment(event)]})
|
||||
|
||||
export const publishComment = ({relays, ...params}: CommentParams & {relays: string[]}) =>
|
||||
publishThunk({event: makeComment(params), relays})
|
||||
|
||||
export type AlertParams = {
|
||||
feed: Feed
|
||||
cron: string
|
||||
email: string
|
||||
bunker: string
|
||||
secret: string
|
||||
description: string
|
||||
claims: Record<string, string>
|
||||
email?: {
|
||||
cron: string
|
||||
email: string
|
||||
handler: string[]
|
||||
}
|
||||
web?: {
|
||||
endpoint: string
|
||||
p256dh: string
|
||||
auth: string
|
||||
}
|
||||
ios?: {
|
||||
device_token: string
|
||||
bundle_identifier: string
|
||||
}
|
||||
android?: {
|
||||
device_token: string
|
||||
}
|
||||
}
|
||||
|
||||
export const makeAlert = async ({cron, email, feed, bunker, secret, description}: AlertParams) => {
|
||||
export const makeAlert = async (params: AlertParams) => {
|
||||
const tags = [
|
||||
["feed", JSON.stringify(feed)],
|
||||
["cron", cron],
|
||||
["email", email],
|
||||
["feed", JSON.stringify(params.feed)],
|
||||
["locale", LOCALE],
|
||||
["timezone", TIMEZONE],
|
||||
["description", description],
|
||||
["channel", "email"],
|
||||
[
|
||||
"handler",
|
||||
"31990:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:1737058597050",
|
||||
"wss://relay.nostr.band/",
|
||||
"web",
|
||||
],
|
||||
["description", params.description],
|
||||
]
|
||||
|
||||
if (bunker) {
|
||||
tags.push(["nip46", secret, bunker])
|
||||
for (const [relay, claim] of Object.entries(params.claims)) {
|
||||
tags.push(["claim", relay, claim])
|
||||
}
|
||||
|
||||
return createEvent(ALERT, {
|
||||
let kind: number
|
||||
if (params.email) {
|
||||
kind = ALERT_EMAIL
|
||||
tags.push(...Object.entries(params.email).map(flatten))
|
||||
} else if (params.web) {
|
||||
kind = ALERT_WEB
|
||||
tags.push(...Object.entries(params.web).map(flatten))
|
||||
} else if (params.ios) {
|
||||
kind = ALERT_IOS
|
||||
tags.push(...Object.entries(params.ios).map(flatten))
|
||||
} else if (params.android) {
|
||||
kind = ALERT_ANDROID
|
||||
tags.push(...Object.entries(params.android).map(flatten))
|
||||
} else {
|
||||
throw new Error("Alert has invalid params")
|
||||
}
|
||||
|
||||
return makeEvent(kind, {
|
||||
content: await signer.get().nip44.encrypt(NOTIFIER_PUBKEY, JSON.stringify(tags)),
|
||||
tags: [
|
||||
["d", randomId()],
|
||||
|
||||
+172
-106
@@ -1,24 +1,56 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {preventDefault} from "@lib/html"
|
||||
import {randomInt, displayList, TIMEZONE, identity} from "@welshman/lib"
|
||||
import {displayRelayUrl, getTagValue, THREAD, MESSAGE, EVENT_TIME, COMMENT} from "@welshman/util"
|
||||
import {decrypt} from "@welshman/signer"
|
||||
import {randomInt, parseJson, fromPairs, displayList, TIMEZONE, identity} from "@welshman/lib"
|
||||
import {
|
||||
displayRelayUrl,
|
||||
getTagValue,
|
||||
getAddress,
|
||||
THREAD,
|
||||
MESSAGE,
|
||||
EVENT_TIME,
|
||||
COMMENT,
|
||||
} from "@welshman/util"
|
||||
import type {Filter} from "@welshman/util"
|
||||
import type {Nip46ResponseWithResult} from "@welshman/signer"
|
||||
import {makeIntersectionFeed, makeRelayFeed, feedFromFilters} from "@welshman/feeds"
|
||||
import {pubkey} from "@welshman/app"
|
||||
import {pubkey, signer, getThunkError} from "@welshman/app"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import InfoBunker from "@app/components/InfoBunker.svelte"
|
||||
import BunkerConnect, {BunkerConnectController} from "@app/components/BunkerConnect.svelte"
|
||||
import {alerts, getMembershipUrls, getMembershipRoomsByUrl, userMembership} from "@app/state"
|
||||
import {loadAlertStatuses} from "@app/requests"
|
||||
import {publishAlert} from "@app/commands"
|
||||
import {
|
||||
alerts,
|
||||
getMembershipUrls,
|
||||
userMembership,
|
||||
NOTIFIER_PUBKEY,
|
||||
NOTIFIER_RELAY,
|
||||
} from "@app/state"
|
||||
import {loadAlertStatuses, requestRelayClaim} from "@app/requests"
|
||||
import {publishAlert, attemptAuth} from "@app/commands"
|
||||
import type {AlertParams} from "@app/commands"
|
||||
import {platform, platformName, canSendPushNotifications, getPushInfo} from "@app/push"
|
||||
import {pushToast} from "@app/toast"
|
||||
import {pushModal} from "@app/modal"
|
||||
|
||||
type Props = {
|
||||
url?: string
|
||||
channel?: string
|
||||
notifyChat?: boolean
|
||||
notifyThreads?: boolean
|
||||
notifyCalendar?: boolean
|
||||
hideSpaceField?: boolean
|
||||
}
|
||||
|
||||
let {
|
||||
url = "",
|
||||
channel = "email",
|
||||
notifyChat = true,
|
||||
notifyThreads = true,
|
||||
notifyCalendar = true,
|
||||
hideSpaceField = false,
|
||||
}: Props = $props()
|
||||
|
||||
const timezoneOffset = parseInt(TIMEZONE.slice(3)) / 100
|
||||
const minute = randomInt(0, 59)
|
||||
@@ -26,49 +58,22 @@
|
||||
const WEEKLY = `0 ${minute} ${hour} * * 1`
|
||||
const DAILY = `0 ${minute} ${hour} * * *`
|
||||
|
||||
let loading = false
|
||||
let cron = WEEKLY
|
||||
let email = $alerts.map(a => getTagValue("email", a.tags)).filter(identity)[0] || ""
|
||||
let relay = ""
|
||||
let bunker = ""
|
||||
let secret = ""
|
||||
let notifyThreads = true
|
||||
let notifyCalendar = true
|
||||
let notifyChat = false
|
||||
let showBunker = false
|
||||
let loading = $state(false)
|
||||
let cron = $state(WEEKLY)
|
||||
let claim = $state("")
|
||||
let email = $state($alerts.map(a => getTagValue("email", a.tags)).filter(identity)[0] || "")
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
const controller = new BunkerConnectController({
|
||||
onNostrConnect: (response: Nip46ResponseWithResult) => {
|
||||
bunker = controller.broker.getBunkerUrl()
|
||||
secret = controller.broker.params.clientSecret
|
||||
showBunker = false
|
||||
},
|
||||
})
|
||||
|
||||
const connectBunker = () => {
|
||||
showBunker = true
|
||||
}
|
||||
|
||||
const hideBunker = () => {
|
||||
showBunker = false
|
||||
}
|
||||
|
||||
const clearBunker = () => {
|
||||
bunker = ""
|
||||
secret = ""
|
||||
}
|
||||
|
||||
const submit = async () => {
|
||||
if (!email.includes("@")) {
|
||||
if (channel === "email" && !email.includes("@")) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: "Please provide an email address",
|
||||
})
|
||||
}
|
||||
|
||||
if (!relay) {
|
||||
if (!url) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: "Please select a space",
|
||||
@@ -99,22 +104,69 @@
|
||||
|
||||
if (notifyChat) {
|
||||
display.push("chat")
|
||||
filters.push({
|
||||
kinds: [MESSAGE],
|
||||
"#h": getMembershipRoomsByUrl(relay, $userMembership),
|
||||
})
|
||||
filters.push({kinds: [MESSAGE]})
|
||||
}
|
||||
|
||||
loading = true
|
||||
|
||||
try {
|
||||
const cadence = cron?.endsWith("1") ? "Weekly" : "Daily"
|
||||
const description = `${cadence} alert for ${displayList(display)} on ${displayRelayUrl(relay)}, sent via email.`
|
||||
const feed = makeIntersectionFeed(feedFromFilters(filters), makeRelayFeed(relay))
|
||||
const thunk = await publishAlert({cron, email, feed, bunker, secret, description})
|
||||
const claims = claim ? {[url]: claim} : {}
|
||||
const feed = makeIntersectionFeed(feedFromFilters(filters), makeRelayFeed(url))
|
||||
const description = `for ${displayList(display)} on ${displayRelayUrl(url)}`
|
||||
const params: AlertParams = {feed, claims, description}
|
||||
|
||||
await thunk.result
|
||||
await loadAlertStatuses($pubkey!)
|
||||
if (channel === "email") {
|
||||
const cadence = cron?.endsWith("1") ? "Weekly" : "Daily"
|
||||
|
||||
params.description = `${cadence} alert ${description}, sent via email.`
|
||||
params.email = {
|
||||
cron,
|
||||
email,
|
||||
handler: [
|
||||
"31990:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:1737058597050",
|
||||
"wss://relay.nostr.band/",
|
||||
"web",
|
||||
],
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
// @ts-ignore
|
||||
params[platform] = await getPushInfo()
|
||||
params.description = `${platformName} push notification ${description}.`
|
||||
} catch (e: any) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: String(e),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't do this we'll get an event rejection
|
||||
await attemptAuth(NOTIFIER_RELAY)
|
||||
|
||||
const thunk = await publishAlert(params)
|
||||
const error = await getThunkError(thunk)
|
||||
|
||||
if (error) {
|
||||
return pushToast({
|
||||
theme: "error",
|
||||
message: `Failed to send your alert to the notification server (${error}).`,
|
||||
})
|
||||
}
|
||||
|
||||
// Fetch our new status to make sure it's active
|
||||
const address = getAddress(thunk.event)
|
||||
const statusEvents = await loadAlertStatuses($pubkey!)
|
||||
const statusEvent = statusEvents.find(event => getTagValue("d", event.tags) === address)
|
||||
const statusTags = statusEvent
|
||||
? parseJson(await decrypt(signer.get(), NOTIFIER_PUBKEY, statusEvent.content))
|
||||
: []
|
||||
const {status = "error", message = "Your alert was not activated"}: Record<string, string> =
|
||||
fromPairs(statusTags)
|
||||
|
||||
if (status === "error") {
|
||||
return pushToast({theme: "error", message})
|
||||
}
|
||||
|
||||
pushToast({message: "Your alert has been successfully created!"})
|
||||
back()
|
||||
@@ -122,6 +174,20 @@
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!canSendPushNotifications()) {
|
||||
channel = "email"
|
||||
}
|
||||
|
||||
if (url) {
|
||||
requestRelayClaim(url).then(code => {
|
||||
if (code) {
|
||||
claim = code
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<form class="column gap-4" onsubmit={preventDefault(submit)}>
|
||||
@@ -130,13 +196,20 @@
|
||||
Add an Alert
|
||||
{/snippet}
|
||||
</ModalHeader>
|
||||
{#if showBunker}
|
||||
<div class="card2 flex flex-col items-center gap-4 bg-base-300">
|
||||
<p>Scan using a nostr signer, or click to copy.</p>
|
||||
<BunkerConnect {controller} />
|
||||
<Button class="btn btn-neutral btn-sm" onclick={hideBunker}>Cancel</Button>
|
||||
</div>
|
||||
{:else}
|
||||
{#if canSendPushNotifications()}
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Alert Type*</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<select bind:value={channel} class="select select-bordered">
|
||||
<option value="email">Email Digest</option>
|
||||
<option value="push">Push Notification</option>
|
||||
</select>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
{/if}
|
||||
{#if channel === "email"}
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Email Address*</p>
|
||||
@@ -158,12 +231,14 @@
|
||||
</select>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
{/if}
|
||||
{#if !hideSpaceField}
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Space*</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<select bind:value={relay} class="select select-bordered">
|
||||
<select bind:value={url} class="select select-bordered">
|
||||
<option value="" disabled selected>Choose a space URL</option>
|
||||
{#each getMembershipUrls($userMembership) as url (url)}
|
||||
<option value={url}>{displayRelayUrl(url)}</option>
|
||||
@@ -171,59 +246,50 @@
|
||||
</select>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Notifications*</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<div class="flex items-center justify-end gap-4">
|
||||
<span class="flex gap-3">
|
||||
<input type="checkbox" class="checkbox" bind:checked={notifyThreads} />
|
||||
Threads
|
||||
</span>
|
||||
<span class="flex gap-3">
|
||||
<input type="checkbox" class="checkbox" bind:checked={notifyCalendar} />
|
||||
Calendar
|
||||
</span>
|
||||
<span class="flex gap-3">
|
||||
<input type="checkbox" class="checkbox" bind:checked={notifyChat} />
|
||||
Chat
|
||||
</span>
|
||||
</div>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
<div class="card2 flex flex-col gap-3 bg-base-300">
|
||||
<div class="flex items-center justify-between">
|
||||
<strong>Connect a Bunker</strong>
|
||||
<span class="flex items-center gap-2 text-sm" class:text-primary={bunker}>
|
||||
{#if bunker}
|
||||
<Icon icon="check-circle" size={5} />
|
||||
Connected
|
||||
{:else}
|
||||
<Icon icon="close-circle" size={5} />
|
||||
Not Connected
|
||||
{/if}
|
||||
{/if}
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Notifications*</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<div class="flex items-center justify-end gap-4">
|
||||
<span class="flex gap-3">
|
||||
<input type="checkbox" class="checkbox" bind:checked={notifyThreads} />
|
||||
Threads
|
||||
</span>
|
||||
<span class="flex gap-3">
|
||||
<input type="checkbox" class="checkbox" bind:checked={notifyCalendar} />
|
||||
Calendar
|
||||
</span>
|
||||
<span class="flex gap-3">
|
||||
<input type="checkbox" class="checkbox" bind:checked={notifyChat} />
|
||||
Chat
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-sm">
|
||||
Required for receiving alerts about spaces with access controls. You can get one from your
|
||||
<Button class="text-primary" onclick={() => pushModal(InfoBunker)}>remote signer app</Button
|
||||
>.
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Invite Code</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<label class="input input-bordered flex w-full items-center gap-2">
|
||||
<input bind:value={claim} />
|
||||
</label>
|
||||
{/snippet}
|
||||
{#snippet info()}
|
||||
<p>
|
||||
To get notifications from private spaces, please provide an invite code which grants access
|
||||
to the space.
|
||||
</p>
|
||||
{#if bunker}
|
||||
<Button class="btn btn-neutral btn-sm flex-grow" onclick={clearBunker}>Disconnect</Button>
|
||||
{:else}
|
||||
<Button class="btn btn-primary btn-sm w-full flex-grow" onclick={connectBunker}
|
||||
>Connect</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
<ModalFooter>
|
||||
<Button class="btn btn-link" onclick={back}>
|
||||
<Icon icon="alt-arrow-left" />
|
||||
Go back
|
||||
</Button>
|
||||
<Button type="submit" class="btn btn-primary" disabled={loading || showBunker}>
|
||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||
<Spinner {loading}>Confirm</Spinner>
|
||||
<Icon icon="alt-arrow-right" />
|
||||
</Button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Confirm from "@lib/components/Confirm.svelte"
|
||||
import type {Alert} from "@app/state"
|
||||
import {NOTIFIER_RELAY} from "@app/state"
|
||||
import {NOTIFIER_RELAY, NOTIFIER_PUBKEY} from "@app/state"
|
||||
import {publishDelete} from "@app/commands"
|
||||
import {pushToast} from "@app/toast"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
const {alert}: Props = $props()
|
||||
|
||||
const confirm = () => {
|
||||
publishDelete({event: alert.event, relays: [NOTIFIER_RELAY]})
|
||||
publishDelete({event: alert.event, relays: [NOTIFIER_RELAY], tags: [["p", NOTIFIER_PUBKEY]]})
|
||||
pushToast({message: "Your alert has been deleted!"})
|
||||
history.back()
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import {parseJson, nthEq} from "@welshman/lib"
|
||||
import {parseJson} from "@welshman/lib"
|
||||
import {displayFeeds} from "@welshman/feeds"
|
||||
import {getAddress, getTagValue, getTagValues} from "@welshman/util"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import AlertDelete from "@app/components/AlertDelete.svelte"
|
||||
import type {Alert} from "@app/state"
|
||||
import {alertStatuses} from "@app/state"
|
||||
import {deriveAlertStatus} from "@app/state"
|
||||
import {pushModal} from "@app/modal"
|
||||
|
||||
type Props = {
|
||||
@@ -15,8 +15,7 @@
|
||||
|
||||
const {alert}: Props = $props()
|
||||
|
||||
const address = $derived(getAddress(alert.event))
|
||||
const status = $derived($alertStatuses.find(s => s.event.tags.some(nthEq(1, address))))
|
||||
const status = deriveAlertStatus(getAddress(alert.event))
|
||||
const cron = $derived(getTagValue("cron", alert.tags))
|
||||
const channel = $derived(getTagValue("channel", alert.tags))
|
||||
const feeds = $derived(getTagValues("feed", alert.tags))
|
||||
@@ -39,24 +38,24 @@
|
||||
</Button>
|
||||
<div class="flex-inline gap-1">{description}</div>
|
||||
</div>
|
||||
{#if status}
|
||||
{@const statusText = getTagValue("status", status.tags) || "error"}
|
||||
{#if $status}
|
||||
{@const statusText = getTagValue("status", $status.tags) || "error"}
|
||||
{#if statusText === "ok"}
|
||||
<span
|
||||
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-base-content px-3 py-1 text-sm"
|
||||
data-tip={getTagValue("message", status.tags)}>
|
||||
data-tip={getTagValue("message", $status.tags)}>
|
||||
Active
|
||||
</span>
|
||||
{:else if statusText === "pending"}
|
||||
<span
|
||||
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-base-content border-yellow-500 px-3 py-1 text-sm text-yellow-500"
|
||||
data-tip={getTagValue("message", status.tags)}>
|
||||
data-tip={getTagValue("message", $status.tags)}>
|
||||
Pending
|
||||
</span>
|
||||
{:else}
|
||||
<span
|
||||
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-error px-3 py-1 text-sm text-error"
|
||||
data-tip={getTagValue("message", status.tags)}>
|
||||
data-tip={getTagValue("message", $status.tags)}>
|
||||
{statusText.replace("-", " ").replace(/^(.)/, x => x.toUpperCase())}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {pubkey} from "@welshman/app"
|
||||
import {getTagValue} from "@welshman/util"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import AlertAdd from "@app/components/AlertAdd.svelte"
|
||||
import AlertItem from "@app/components/AlertItem.svelte"
|
||||
import {loadAlertStatuses, loadAlerts} from "@app/requests"
|
||||
import {pushModal} from "@app/modal"
|
||||
import {alerts} from "@app/state"
|
||||
|
||||
const startAlert = () => pushModal(AlertAdd)
|
||||
type Props = {
|
||||
url?: string
|
||||
channel?: string
|
||||
hideSpaceField?: boolean
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
loadAlertStatuses($pubkey!)
|
||||
loadAlerts($pubkey!)
|
||||
})
|
||||
const {url = "", channel = "push", hideSpaceField = false}: Props = $props()
|
||||
|
||||
const startAlert = () => pushModal(AlertAdd, {url, channel, hideSpaceField})
|
||||
|
||||
const filteredAlerts = $derived(
|
||||
url ? $alerts.filter(a => getTagValue("feed", a.tags)?.includes(url)) : $alerts,
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="card2 bg-alt flex flex-col gap-6 shadow-xl">
|
||||
@@ -29,10 +34,10 @@
|
||||
</Button>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
{#each $alerts as alert (alert.event.id)}
|
||||
{#each filteredAlerts as alert (alert.event.id)}
|
||||
<AlertItem {alert} />
|
||||
{:else}
|
||||
<p class="text-center opacity-75 py-12">No alerts found</p>
|
||||
<p class="text-center opacity-75 py-12">Nothing here yet!</p>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type {Snippet} from "svelte"
|
||||
import {writable} from "svelte/store"
|
||||
import {randomId, HOUR} from "@welshman/lib"
|
||||
import {createEvent, EVENT_TIME} from "@welshman/util"
|
||||
import {makeEvent, EVENT_TIME} from "@welshman/util"
|
||||
import {publishThunk} from "@welshman/app"
|
||||
import {preventDefault} from "@lib/html"
|
||||
import {daysBetween} from "@lib/util"
|
||||
@@ -63,7 +63,7 @@
|
||||
}
|
||||
|
||||
const ed = await editor
|
||||
const event = createEvent(EVENT_TIME, {
|
||||
const event = makeEvent(EVENT_TIME, {
|
||||
content: ed.getText({blockSeparator: "\n"}).trim(),
|
||||
tags: [
|
||||
["d", initialValues?.d || randomId()],
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import type {TrustedEvent, EventTemplate, EventContent} from "@welshman/util"
|
||||
import {parse, isLink} from "@welshman/content"
|
||||
import {
|
||||
createEvent,
|
||||
makeEvent,
|
||||
tagsFromIMeta,
|
||||
getTags,
|
||||
DIRECT_MESSAGE,
|
||||
@@ -97,7 +97,7 @@
|
||||
content = content.trim()
|
||||
|
||||
if (content) {
|
||||
templates.push(createEvent(kind, {content, tags: [...tags, ...ptags]}))
|
||||
templates.push(makeEvent(kind, {content, tags: [...tags, ...ptags]}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
||||
import EventActivity from "@app/components/EventActivity.svelte"
|
||||
import EventActions from "@app/components/EventActions.svelte"
|
||||
import {publishDelete, publishReaction} from "@app/commands"
|
||||
import {makeThreadPath} from "@app/routes"
|
||||
|
||||
interface Props {
|
||||
url: any
|
||||
event: any
|
||||
showActivity?: boolean
|
||||
}
|
||||
|
||||
const {url, event, showActivity = false}: Props = $props()
|
||||
|
||||
const path = makeThreadPath(url, event.id)
|
||||
|
||||
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
|
||||
|
||||
const createReaction = (template: EventContent) =>
|
||||
publishReaction({...template, event, relays: [url]})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<div class="flex flex-grow flex-wrap justify-end gap-2">
|
||||
<ReactionSummary {url} {event} {deleteReaction} {createReaction} reactionClass="tooltip-left" />
|
||||
<ThunkStatusOrDeleted {event} />
|
||||
{#if showActivity}
|
||||
<EventActivity {url} {path} {event} />
|
||||
{/if}
|
||||
<EventActions {url} {event} noun="Comment" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {ellipsize, postJson} from "@welshman/lib"
|
||||
import {ellipsize, displayUrl, postJson} from "@welshman/lib"
|
||||
import {dufflepud, imgproxy} from "@app/state"
|
||||
import {preventDefault, stopPropagation} from "@lib/html"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
@@ -54,13 +54,11 @@
|
||||
src={imgproxy(preview.image)}
|
||||
class="bg-alt max-h-72 object-contain object-center" />
|
||||
{/if}
|
||||
{#if preview.title}
|
||||
<div class="flex flex-col gap-2 p-4">
|
||||
<strong class="overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
>{preview.title}</strong>
|
||||
<p>{ellipsize(preview.description, 140)}</p>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-col gap-2 p-4">
|
||||
<strong class="overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
>{preview.title || displayUrl(url)}</strong>
|
||||
<p>{ellipsize(preview.description, 140)}</p>
|
||||
</div>
|
||||
</div>
|
||||
{:catch}
|
||||
<p class="bg-alt p-12 text-center leading-normal">
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {removeNil} from "@welshman/lib"
|
||||
import type {ProfilePointer} from "@welshman/content"
|
||||
import {displayProfile} from "@welshman/util"
|
||||
import {deriveProfile} from "@welshman/app"
|
||||
import {deriveProfileDisplay} from "@welshman/app"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||
import {pushModal} from "@app/modal"
|
||||
@@ -14,11 +13,11 @@
|
||||
|
||||
const {value, url}: Props = $props()
|
||||
|
||||
const profile = deriveProfile(value.pubkey, removeNil([url]))
|
||||
const display = deriveProfileDisplay(value.pubkey, removeNil([url]))
|
||||
|
||||
const openProfile = () => pushModal(ProfileDetail, {pubkey: value.pubkey, url})
|
||||
</script>
|
||||
|
||||
<Button onclick={openProfile} class="link-content">
|
||||
@{displayProfile($profile)}
|
||||
@{$display}
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<script lang="ts">
|
||||
import {formatTimestamp} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Content from "@app/components/Content.svelte"
|
||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||
import ProfileCircles from "@app/components/ProfileCircles.svelte"
|
||||
import {goToEvent} from "@app/routes"
|
||||
import {displayChannel} from "@app/state"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
room?: string
|
||||
events: TrustedEvent[]
|
||||
latest: TrustedEvent
|
||||
earliest: TrustedEvent
|
||||
participants: string[]
|
||||
}
|
||||
|
||||
const {url, room, events, latest, earliest, participants}: Props = $props()
|
||||
</script>
|
||||
|
||||
<Button class="card2 bg-alt" onclick={() => goToEvent(earliest)}>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex items-start gap-3">
|
||||
<ProfileCircle pubkey={earliest.pubkey} size={10} />
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-2 text-sm opacity-70">
|
||||
{#if room}
|
||||
<span class="font-medium text-blue-400">
|
||||
#{displayChannel(url, room)}
|
||||
</span>
|
||||
<span class="opacity-50">•</span>
|
||||
{/if}
|
||||
<span>{formatTimestamp(earliest.created_at)}</span>
|
||||
</div>
|
||||
<Content minimalQuote minLength={100} maxLength={400} event={earliest} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-13 flex items-center justify-between">
|
||||
<div class="flex gap-1">
|
||||
<Icon icon="alt-arrow-left" />
|
||||
<span class="text-sm opacity-70">
|
||||
{events.length}
|
||||
{events.length === 1 ? "message" : "messages"}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<ProfileCircles pubkeys={participants} size={6} />
|
||||
<span class="text-sm opacity-70">
|
||||
{participants.length}
|
||||
{participants.length === 1 ? "participant" : "participants"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{#if latest !== earliest}
|
||||
<Button class="card2 bg-alt" onclick={() => goToEvent(latest)}>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2 text-sm opacity-70">
|
||||
<ProfileCircle pubkey={latest.pubkey} size={5} />
|
||||
<span class="font-medium">Latest reply:</span>
|
||||
</div>
|
||||
<span class="text-xs opacity-50">
|
||||
{formatTimestamp(latest.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
<Content minimalQuote minLength={100} maxLength={400} event={latest} />
|
||||
</div>
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</Button>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import {displayRelayUrl, getTagValue} from "@welshman/util"
|
||||
import {deriveRelay} from "@welshman/app"
|
||||
import {fly} from "@lib/transition"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
@@ -13,6 +13,8 @@
|
||||
import SpaceExit from "@app/components/SpaceExit.svelte"
|
||||
import SpaceJoin from "@app/components/SpaceJoin.svelte"
|
||||
import ProfileList from "@app/components/ProfileList.svelte"
|
||||
import AlertAdd from "@app/components/AlertAdd.svelte"
|
||||
import Alerts from "@app/components/Alerts.svelte"
|
||||
import RoomCreate from "@app/components/RoomCreate.svelte"
|
||||
import MenuSpaceRoomItem from "@app/components/MenuSpaceRoomItem.svelte"
|
||||
import InfoMissingRooms from "@app/components/InfoMissingRooms.svelte"
|
||||
@@ -23,6 +25,7 @@
|
||||
deriveUserRooms,
|
||||
deriveOtherRooms,
|
||||
hasNip29,
|
||||
alerts,
|
||||
} from "@app/state"
|
||||
import {notifications} from "@app/notifications"
|
||||
import {pushModal} from "@app/modal"
|
||||
@@ -36,6 +39,7 @@
|
||||
const calendarPath = makeSpacePath(url, "calendar")
|
||||
const userRooms = deriveUserRooms(url)
|
||||
const otherRooms = deriveOtherRooms(url)
|
||||
const hasAlerts = $derived($alerts.some(a => getTagValue("feed", a.tags)?.includes(url)))
|
||||
|
||||
const openMenu = () => {
|
||||
showMenu = true
|
||||
@@ -62,6 +66,13 @@
|
||||
|
||||
const addRoom = () => pushModal(RoomCreate, {url}, {replaceState})
|
||||
|
||||
const manageAlerts = () => {
|
||||
const component = hasAlerts ? Alerts : AlertAdd
|
||||
const params = {url, channel: "push", hideSpaceField: true}
|
||||
|
||||
pushModal(component, params, {replaceState})
|
||||
}
|
||||
|
||||
let showMenu = $state(false)
|
||||
let replaceState = $state(false)
|
||||
let element: Element | undefined = $state()
|
||||
@@ -75,18 +86,20 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:this={element}>
|
||||
<SecondaryNavSection class="max-h-screen">
|
||||
<div bind:this={element} class="flex h-full flex-col justify-between">
|
||||
<SecondaryNavSection>
|
||||
<div>
|
||||
<SecondaryNavItem class="w-full !justify-between" onclick={openMenu}>
|
||||
<strong class="ellipsize">{displayRelayUrl(url)}</strong>
|
||||
<strong class="ellipsize flex items-center gap-3">
|
||||
{displayRelayUrl(url)}
|
||||
</strong>
|
||||
<Icon icon="alt-arrow-down" />
|
||||
</SecondaryNavItem>
|
||||
{#if showMenu}
|
||||
<Popover hideOnClick onClose={toggleMenu}>
|
||||
<ul
|
||||
transition:fly
|
||||
class="menu absolute z-popover mt-2 w-full rounded-box bg-base-100 p-2 shadow-xl">
|
||||
class="menu absolute z-popover mt-2 w-full gap-1 rounded-box bg-base-100 p-2 shadow-xl">
|
||||
<li>
|
||||
<Button onclick={showMembers}>
|
||||
<Icon icon="user-rounded" />
|
||||
@@ -171,4 +184,10 @@
|
||||
{/if}
|
||||
</div>
|
||||
</SecondaryNavSection>
|
||||
<div class="p-4">
|
||||
<button class="btn btn-neutral btn-sm w-full" onclick={manageAlerts}>
|
||||
<Icon icon="bell" />
|
||||
Manage Alerts
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import type {Filter} from "@welshman/util"
|
||||
import {deriveEvents} from "@welshman/store"
|
||||
import {formatTimestampRelative} from "@welshman/lib"
|
||||
import {NOTE, GROUPS, MESSAGE, THREAD, COMMENT, getRelayTags, getListTags} from "@welshman/util"
|
||||
import {NOTE, ROOMS, MESSAGE, THREAD, COMMENT, getRelayTags, getListTags} from "@welshman/util"
|
||||
import {repository, loadRelaySelections} from "@welshman/app"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ProfileSpaces from "@app/components/ProfileSpaces.svelte"
|
||||
@@ -35,7 +35,7 @@
|
||||
// Load groups and at least one note, regardless of time frame
|
||||
load({
|
||||
filters: [
|
||||
{authors: [pubkey], kinds: [GROUPS]},
|
||||
{authors: [pubkey], kinds: [ROOMS]},
|
||||
{authors: [pubkey], limit: 1, kinds: [NOTE, MESSAGE, THREAD, COMMENT]},
|
||||
],
|
||||
relays: Router.get().FromPubkeys([pubkey]).getUrls(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {chunk, sleep, uniq} from "@welshman/lib"
|
||||
import {
|
||||
createEvent,
|
||||
makeEvent,
|
||||
createProfile,
|
||||
PROFILE,
|
||||
DELETE,
|
||||
@@ -36,8 +36,8 @@
|
||||
}
|
||||
|
||||
const chunks = chunk(500, repository.query([{authors: [$pubkey!]}]))
|
||||
const profileEvent = createEvent(PROFILE, createProfile({name: "[deleted]"}))
|
||||
const vanishEvent = createEvent(62, {tags: [["relay", "ALL_RELAYS"]]})
|
||||
const profileEvent = makeEvent(PROFILE, createProfile({name: "[deleted]"}))
|
||||
const vanishEvent = makeEvent(62, {tags: [["relay", "ALL_RELAYS"]]})
|
||||
const denominator = chunks.length + 2
|
||||
const relays = uniq([
|
||||
...INDEXER_RELAYS,
|
||||
@@ -75,7 +75,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
await publishThunk({relays, event: createEvent(DELETE, {tags})})
|
||||
await publishThunk({relays, event: makeEvent(DELETE, {tags})})
|
||||
|
||||
await incrementProgress()
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import {nthNe} from "@welshman/lib"
|
||||
import type {Profile} from "@welshman/util"
|
||||
import {
|
||||
getTag,
|
||||
createEvent,
|
||||
makeEvent,
|
||||
makeProfile,
|
||||
editProfile,
|
||||
createProfile,
|
||||
@@ -24,16 +25,19 @@
|
||||
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 relays = [...getMembershipUrls($userMembership)]
|
||||
const scenarios = [router.FromRelays(getMembershipUrls($userMembership))]
|
||||
|
||||
if (shouldBroadcast) {
|
||||
relays.push(...Router.get().FromUser().getUrls())
|
||||
scenarios.push(router.FromUser(), router.Index())
|
||||
template.tags = template.tags.filter(nthNe(0, "-"))
|
||||
} else {
|
||||
template.tags = uniqTags([...template.tags, PROTECTED])
|
||||
}
|
||||
|
||||
const event = createEvent(template.kind, template)
|
||||
const event = makeEvent(template.kind, template)
|
||||
const relays = router.merge(scenarios).getUrls()
|
||||
|
||||
publishThunk({event, relays})
|
||||
pushToast({message: "Your profile has been updated!"})
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import {goto} from "$app/navigation"
|
||||
import {randomId} from "@welshman/lib"
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import {deriveRelay, getThunkError} from "@welshman/app"
|
||||
import {uniqBy, nth} from "@welshman/lib"
|
||||
import {displayRelayUrl, makeRoomMeta} from "@welshman/util"
|
||||
import {deriveRelay, getThunkError, createRoom, editRoom, joinRoom} from "@welshman/app"
|
||||
import {preventDefault} from "@lib/html"
|
||||
import Field from "@lib/components/Field.svelte"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
@@ -11,25 +11,26 @@
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import {hasNip29, loadChannel} from "@app/state"
|
||||
import {createRoom, editRoom, joinRoom} from "@app/commands"
|
||||
import {makeSpacePath} from "@app/routes"
|
||||
import {pushToast} from "@app/toast"
|
||||
|
||||
const {url} = $props()
|
||||
|
||||
const room = randomId()
|
||||
const room = makeRoomMeta()
|
||||
const relay = deriveRelay(url)
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
const tryCreate = async () => {
|
||||
room.tags = uniqBy(nth(0), [...room.tags, ["name", name]])
|
||||
|
||||
const createMessage = await getThunkError(createRoom(url, room))
|
||||
|
||||
if (createMessage && !createMessage.match(/^duplicate:|already a member/)) {
|
||||
return pushToast({theme: "error", message: createMessage})
|
||||
}
|
||||
|
||||
const editMessage = await getThunkError(editRoom(url, room, {name}))
|
||||
const editMessage = await getThunkError(editRoom(url, room))
|
||||
|
||||
if (editMessage) {
|
||||
return pushToast({theme: "error", message: editMessage})
|
||||
@@ -41,9 +42,9 @@
|
||||
return pushToast({theme: "error", message: joinMessage})
|
||||
}
|
||||
|
||||
await loadChannel(url, room)
|
||||
await loadChannel(url, room.id)
|
||||
|
||||
goto(makeSpacePath(url, room))
|
||||
goto(makeSpacePath(url, room.id))
|
||||
}
|
||||
|
||||
const create = async () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type {Profile} from "@welshman/util"
|
||||
import {PROFILE, createProfile, makeProfile, createEvent} from "@welshman/util"
|
||||
import {PROFILE, createProfile, makeProfile, makeEvent} from "@welshman/util"
|
||||
import {loginWithNip01, publishThunk} from "@welshman/app"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import ProfileEditForm from "@app/components/ProfileEditForm.svelte"
|
||||
@@ -18,7 +18,7 @@
|
||||
}
|
||||
|
||||
const onsubmit = ({profile, shouldBroadcast}: {profile: Profile; shouldBroadcast: boolean}) => {
|
||||
const event = createEvent(PROFILE, createProfile(profile))
|
||||
const event = makeEvent(PROFILE, createProfile(profile))
|
||||
const relays = shouldBroadcast ? INDEXER_RELAYS : []
|
||||
|
||||
loginWithNip01(secret)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||
import {clearModals} from "@app/modal"
|
||||
import {addSpaceMembership} from "@app/commands"
|
||||
import {addSpaceMembership, broadcastUserData} from "@app/commands"
|
||||
|
||||
const {url} = $props()
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
const tryJoin = async () => {
|
||||
await addSpaceMembership(url)
|
||||
|
||||
broadcastUserData([url])
|
||||
clearModals()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
<script module lang="ts">
|
||||
import {goto} from "$app/navigation"
|
||||
import {ROOM_META} from "@welshman/util"
|
||||
import {load} from "@welshman/net"
|
||||
import {makeSpacePath} from "@app/routes"
|
||||
import {addSpaceMembership} from "@app/commands"
|
||||
import {addSpaceMembership, broadcastUserData} from "@app/commands"
|
||||
import {pushToast} from "@app/toast"
|
||||
|
||||
export const confirmSpaceJoin = async (url: string) => {
|
||||
await addSpaceMembership(url)
|
||||
|
||||
goto(makeSpacePath(url), {replaceState: true})
|
||||
const path = makeSpacePath(url)
|
||||
|
||||
if (window.location.pathname === path) {
|
||||
load({
|
||||
relays: [url],
|
||||
filters: [{kinds: [ROOM_META]}],
|
||||
})
|
||||
}
|
||||
|
||||
broadcastUserData([url])
|
||||
|
||||
goto(path, {replaceState: true})
|
||||
|
||||
pushToast({
|
||||
message: "Welcome to the space!",
|
||||
|
||||
@@ -6,7 +6,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} from "@app/routes"
|
||||
import {makeThreadPath, makeCalendarPath, makeRoomPath, makeSpacePath} from "@app/routes"
|
||||
import {
|
||||
hasNip29,
|
||||
deriveUserRooms,
|
||||
@@ -25,6 +25,7 @@
|
||||
const relay = deriveRelay(url)
|
||||
const userRooms = deriveUserRooms(url)
|
||||
const otherRooms = deriveOtherRooms(url)
|
||||
const chatPath = makeSpacePath(url, "chat")
|
||||
const threadsPath = makeThreadPath(url)
|
||||
const calendarPath = makeCalendarPath(url)
|
||||
|
||||
@@ -76,34 +77,46 @@
|
||||
{/if}
|
||||
</div>
|
||||
</Link>
|
||||
{#if $userRooms.length + $otherRooms.length > 10}
|
||||
<label class="input input-sm input-bordered flex flex-grow items-center gap-2">
|
||||
<Icon icon="magnifer" size={4} />
|
||||
<input bind:value={term} class="grow" type="text" placeholder="Search rooms..." />
|
||||
</label>
|
||||
{/if}
|
||||
{#each filteredRooms() as room (room)}
|
||||
{@const roomPath = makeRoomPath(url, room)}
|
||||
{@const channel = $channelsById.get(makeChannelId(url, room))}
|
||||
<Link href={roomPath} class="btn btn-neutral btn-sm relative w-full justify-start">
|
||||
<div class="flex min-w-0 items-center gap-2 overflow-hidden text-nowrap">
|
||||
{#if channel?.closed || channel?.private}
|
||||
<Icon icon="lock" size={4} />
|
||||
{:else}
|
||||
<Icon icon="hashtag" />
|
||||
{/if}
|
||||
<ChannelName {url} {room} />
|
||||
</div>
|
||||
{#if $notifications.has(roomPath)}
|
||||
<div class="absolute right-1 top-1 h-2 w-2 rounded-full bg-primary" transition:fade></div>
|
||||
{/if}
|
||||
</Link>
|
||||
{/each}
|
||||
{#if hasNip29($relay)}
|
||||
{#if $userRooms.length + $otherRooms.length > 10}
|
||||
<label class="input input-sm input-bordered flex flex-grow items-center gap-2">
|
||||
<Icon icon="magnifer" size={4} />
|
||||
<input bind:value={term} class="grow" type="text" placeholder="Search rooms..." />
|
||||
</label>
|
||||
{/if}
|
||||
{#each filteredRooms() as room (room)}
|
||||
{@const roomPath = makeRoomPath(url, room)}
|
||||
{@const channel = $channelsById.get(makeChannelId(url, room))}
|
||||
<Link href={roomPath} class="btn btn-neutral btn-sm relative w-full justify-start">
|
||||
<div class="flex min-w-0 items-center gap-2 overflow-hidden text-nowrap">
|
||||
{#if channel?.closed || channel?.private}
|
||||
<Icon icon="lock" size={4} />
|
||||
{:else}
|
||||
<Icon icon="hashtag" />
|
||||
{/if}
|
||||
<ChannelName {url} {room} />
|
||||
</div>
|
||||
{#if $notifications.has(roomPath)}
|
||||
<div class="absolute right-1 top-1 h-2 w-2 rounded-full bg-primary" transition:fade>
|
||||
</div>
|
||||
{/if}
|
||||
</Link>
|
||||
{/each}
|
||||
<Button onclick={addRoom} class="btn btn-neutral btn-sm w-full justify-start">
|
||||
<Icon icon="add-circle" />
|
||||
Create Room
|
||||
</Button>
|
||||
{:else}
|
||||
<Link href={chatPath} class="btn btn-neutral w-full justify-start">
|
||||
<div class="relative flex items-center gap-2">
|
||||
<Icon icon="chat-round" />
|
||||
Chat
|
||||
{#if $notifications.has(chatPath)}
|
||||
<div class="absolute -right-3 -top-1 h-2 w-2 rounded-full bg-primary" transition:fade>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Link>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
<script lang="ts">
|
||||
import {derived} from "svelte/store"
|
||||
import {groupBy, ago, MONTH, first, last, uniq, avg, overlappingPairs} from "@welshman/lib"
|
||||
import {formatTimestamp} from "@welshman/lib"
|
||||
import {MESSAGE, getTagValue} from "@welshman/util"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Content from "@app/components/Content.svelte"
|
||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||
import ProfileCircles from "@app/components/ProfileCircles.svelte"
|
||||
import ConversationCard from "@app/components/ConversationCard.svelte"
|
||||
import {deriveEventsForUrl} from "@app/state"
|
||||
import {goToEvent} from "@app/routes"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
@@ -76,54 +72,25 @@
|
||||
</h3>
|
||||
<div class="flex flex-col gap-4">
|
||||
{#if $conversations.length === 0}
|
||||
<div class="py-8 text-center opacity-70">
|
||||
<p>No recent conversations</p>
|
||||
</div>
|
||||
{#if $messages.length > 0}
|
||||
{@const events = $messages.slice(0, 1)}
|
||||
{@const event = events[0]}
|
||||
{@const room = getTagValue("h", event.tags)}
|
||||
<ConversationCard
|
||||
{url}
|
||||
{room}
|
||||
{events}
|
||||
latest={event}
|
||||
earliest={event}
|
||||
participants={[event.pubkey]} />
|
||||
{:else}
|
||||
<div class="py-8 text-center opacity-70">
|
||||
<p>No recent conversations</p>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
{#each $conversations.slice(0, limit) as { room, events, latest, earliest, participants } (latest.id)}
|
||||
<Button class="card2 bg-alt" onclick={() => goToEvent(earliest)}>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex items-start gap-3">
|
||||
<ProfileCircle pubkey={earliest.pubkey} size={10} />
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-2 text-sm opacity-70">
|
||||
<span class="font-medium text-blue-400">#{room}</span>
|
||||
<span class="opacity-50">•</span>
|
||||
<span>{formatTimestamp(earliest.created_at)}</span>
|
||||
</div>
|
||||
<Content minimalQuote minLength={100} maxLength={400} event={earliest} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-13 flex items-center justify-between">
|
||||
<div class="flex gap-1">
|
||||
<Icon icon="alt-arrow-left" />
|
||||
<span class="text-sm opacity-70">
|
||||
{events.length} messages
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<ProfileCircles pubkeys={participants} size={6} />
|
||||
<span class="text-sm opacity-70">
|
||||
{participants.length} participants
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button class="card2 bg-alt" onclick={() => goToEvent(latest)}>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2 text-sm opacity-70">
|
||||
<ProfileCircle pubkey={latest.pubkey} size={5} />
|
||||
<span class="font-medium">Latest reply:</span>
|
||||
</div>
|
||||
<span class="text-xs opacity-50">
|
||||
{formatTimestamp(latest.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
<Content minimalQuote minLength={100} maxLength={400} event={latest} />
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</Button>
|
||||
<ConversationCard {url} {room} {events} {latest} {earliest} {participants} />
|
||||
{/each}
|
||||
{#if $conversations.length > limit}
|
||||
<Button class="btn btn-primary" onclick={viewMore}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import {writable} from "svelte/store"
|
||||
import {createEvent, THREAD} from "@welshman/util"
|
||||
import {makeEvent, THREAD} from "@welshman/util"
|
||||
import {publishThunk} from "@welshman/app"
|
||||
import {isMobile, preventDefault} from "@lib/html"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
publishThunk({
|
||||
relays: [url],
|
||||
event: createEvent(THREAD, {content, tags}),
|
||||
event: makeEvent(THREAD, {content, tags}),
|
||||
})
|
||||
|
||||
history.back()
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
makeChatPath,
|
||||
makeThreadPath,
|
||||
makeCalendarPath,
|
||||
makeSpaceChatPath,
|
||||
makeRoomPath,
|
||||
} from "@app/routes"
|
||||
import {chats, getUrlsForEvent, userRoomsByUrl, repositoryStore} from "@app/state"
|
||||
@@ -75,8 +76,10 @@ export const notifications = derived(
|
||||
const spacePath = makeSpacePath(url)
|
||||
const threadPath = makeThreadPath(url)
|
||||
const calendarPath = makeCalendarPath(url)
|
||||
const messagesPath = makeSpaceChatPath(url)
|
||||
const threadEvents = allThreadEvents.filter(e => $getUrlsForEvent(e.id).includes(url))
|
||||
const calendarEvents = allCalendarEvents.filter(e => $getUrlsForEvent(e.id).includes(url))
|
||||
const messagesEvents = allMessageEvents.filter(e => $getUrlsForEvent(e.id).includes(url))
|
||||
|
||||
if (hasNotification(threadPath, threadEvents[0])) {
|
||||
paths.add(spacePath)
|
||||
@@ -88,6 +91,11 @@ export const notifications = derived(
|
||||
paths.add(calendarPath)
|
||||
}
|
||||
|
||||
if (hasNotification(messagesPath, messagesEvents[0])) {
|
||||
paths.add(spacePath)
|
||||
paths.add(messagesPath)
|
||||
}
|
||||
|
||||
const commentsByThreadId = groupBy(
|
||||
e => getTagValue("E", e.tags),
|
||||
threadEvents.filter(spec({kind: COMMENT})),
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
import * as nip19 from "nostr-tools/nip19"
|
||||
import {Capacitor} from "@capacitor/core"
|
||||
import type {ActionPerformed, RegistrationError, Token} from "@capacitor/push-notifications"
|
||||
import {PushNotifications} from "@capacitor/push-notifications"
|
||||
import {parseJson, poll} from "@welshman/lib"
|
||||
import {isSignedEvent} from "@welshman/util"
|
||||
import {goto} from "$app/navigation"
|
||||
import {ucFirst} from "@lib/util"
|
||||
import {VAPID_PUBLIC_KEY} from "@app/state"
|
||||
|
||||
export const platform = Capacitor.getPlatform()
|
||||
|
||||
export const platformName = platform === "ios" ? "iOS" : ucFirst(platform)
|
||||
|
||||
export const initializePushNotifications = () => {
|
||||
if (platform === "web") return
|
||||
|
||||
PushNotifications.addListener("pushNotificationActionPerformed", (action: ActionPerformed) => {
|
||||
const event = parseJson(action.notification.data.event)
|
||||
const parsedRelays = parseJson(action.notification.data.relays)
|
||||
const relays = Array.isArray(parsedRelays) ? parsedRelays : []
|
||||
|
||||
if (isSignedEvent(event)) {
|
||||
goto("/" + nip19.neventEncode({id: event.id, relays}))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const canSendPushNotifications = () => ["web", "android", "ios"].includes(platform)
|
||||
|
||||
export const getWebPushInfo = async () => {
|
||||
if (!("serviceWorker" in navigator)) {
|
||||
throw new Error("Service Worker not supported")
|
||||
}
|
||||
|
||||
if (!("PushManager" in window)) {
|
||||
throw new Error("Push messaging not supported")
|
||||
}
|
||||
|
||||
if (Notification.permission === "denied") {
|
||||
throw new Error("Push notifications are blocked")
|
||||
}
|
||||
|
||||
if (Notification.permission !== "granted") {
|
||||
const permission = await Notification.requestPermission()
|
||||
|
||||
if (permission !== "granted") {
|
||||
throw new Error("Push notification permission denied")
|
||||
}
|
||||
}
|
||||
|
||||
const registration = await navigator.serviceWorker.ready
|
||||
let subscription = await registration.pushManager.getSubscription()
|
||||
|
||||
if (!subscription) {
|
||||
subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: VAPID_PUBLIC_KEY,
|
||||
})
|
||||
}
|
||||
|
||||
const {keys} = subscription.toJSON()
|
||||
|
||||
if (!keys) {
|
||||
throw new Error(`Failed to get push info: no keys were returned`)
|
||||
}
|
||||
|
||||
return {
|
||||
endpoint: subscription.endpoint,
|
||||
p256dh: keys.p256dh,
|
||||
auth: keys.auth,
|
||||
}
|
||||
}
|
||||
|
||||
export type PushInfo = {
|
||||
device_token: string
|
||||
bundle_identifier?: string
|
||||
}
|
||||
|
||||
export const getCapacitorPushInfo = async () => {
|
||||
let status = await PushNotifications.checkPermissions()
|
||||
|
||||
if (status.receive === "prompt") {
|
||||
status = await PushNotifications.requestPermissions()
|
||||
}
|
||||
|
||||
if (status.receive !== "granted") {
|
||||
throw new Error("Failed to register for push notifications")
|
||||
}
|
||||
|
||||
let device_token = ""
|
||||
let error = "Failed to register for push notifications"
|
||||
|
||||
PushNotifications.addListener("registration", (token: Token) => {
|
||||
device_token = token.value
|
||||
})
|
||||
|
||||
PushNotifications.addListener("registrationError", (_error: RegistrationError) => {
|
||||
error = _error.error
|
||||
})
|
||||
|
||||
await PushNotifications.register()
|
||||
await poll({
|
||||
condition: () => Boolean(device_token),
|
||||
signal: AbortSignal.timeout(5000),
|
||||
})
|
||||
|
||||
if (!device_token) {
|
||||
throw new Error(error)
|
||||
}
|
||||
|
||||
const info: PushInfo = {device_token}
|
||||
|
||||
if (platform === "ios") {
|
||||
info.bundle_identifier = "social.flotilla"
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
export const getPushInfo = (): Promise<Record<string, string>> => {
|
||||
switch (platform) {
|
||||
case "web":
|
||||
return getWebPushInfo()
|
||||
case "ios":
|
||||
case "android":
|
||||
return getCapacitorPushInfo()
|
||||
default:
|
||||
throw new Error(`Invalid push platform: ${platform}`)
|
||||
}
|
||||
}
|
||||
+28
-4
@@ -13,13 +13,22 @@ import {
|
||||
sortBy,
|
||||
assoc,
|
||||
now,
|
||||
isNotNil,
|
||||
filterVals,
|
||||
fromPairs,
|
||||
} from "@welshman/lib"
|
||||
import {
|
||||
MESSAGE,
|
||||
DELETE,
|
||||
THREAD,
|
||||
EVENT_TIME,
|
||||
AUTH_INVITE,
|
||||
COMMENT,
|
||||
ALERT_EMAIL,
|
||||
ALERT_WEB,
|
||||
ALERT_IOS,
|
||||
ALERT_ANDROID,
|
||||
ALERT_STATUS,
|
||||
matchFilters,
|
||||
getTagValues,
|
||||
getTagValue,
|
||||
@@ -48,8 +57,6 @@ import {
|
||||
import {createScroller} from "@lib/html"
|
||||
import {daysBetween} from "@lib/util"
|
||||
import {
|
||||
ALERT,
|
||||
ALERT_STATUS,
|
||||
NOTIFIER_RELAY,
|
||||
INDEXER_RELAYS,
|
||||
getDefaultPubkeys,
|
||||
@@ -343,7 +350,7 @@ export const makeCalendarFeed = ({
|
||||
export const loadAlerts = (pubkey: string) =>
|
||||
load({
|
||||
relays: [NOTIFIER_RELAY],
|
||||
filters: [{kinds: [ALERT], authors: [pubkey]}],
|
||||
filters: [{kinds: [ALERT_EMAIL, ALERT_WEB, ALERT_IOS, ALERT_ANDROID], authors: [pubkey]}],
|
||||
})
|
||||
|
||||
export const loadAlertStatuses = (pubkey: string) =>
|
||||
@@ -359,7 +366,7 @@ export const listenForNotifications = () => {
|
||||
|
||||
for (const [url, allRooms] of userRoomsByUrl.get()) {
|
||||
// Limit how many rooms we load at a time, since we have to send a separate filter
|
||||
// for each one due to nip 29 breaking postel's law
|
||||
// for each one due to relay29 being picky
|
||||
const rooms = shuffle(Array.from(allRooms)).slice(0, 30)
|
||||
|
||||
load({
|
||||
@@ -367,6 +374,7 @@ export const listenForNotifications = () => {
|
||||
relays: [url],
|
||||
filters: [
|
||||
{kinds: [THREAD], limit: 1},
|
||||
{kinds: [MESSAGE], limit: 1},
|
||||
{kinds: [COMMENT], "#K": [String(THREAD)], limit: 1},
|
||||
...rooms.map(room => ({kinds: [MESSAGE], "#h": [room], limit: 1})),
|
||||
],
|
||||
@@ -377,6 +385,7 @@ export const listenForNotifications = () => {
|
||||
relays: [url],
|
||||
filters: [
|
||||
{kinds: [THREAD], since: now()},
|
||||
{kinds: [MESSAGE], since: now()},
|
||||
{kinds: [COMMENT], "#K": [String(THREAD)], since: now()},
|
||||
...rooms.map(room => ({kinds: [MESSAGE], "#h": [room], since: now()})),
|
||||
],
|
||||
@@ -430,3 +439,18 @@ export const discoverRelays = (lists: List[]) =>
|
||||
.filter(isShareableRelayUrl)
|
||||
.map(url => loadRelay(url)),
|
||||
)
|
||||
|
||||
export const requestRelayClaim = async (url: string) => {
|
||||
const filters = [{kinds: [AUTH_INVITE], limit: 1}]
|
||||
const events = await load({filters, relays: [url]})
|
||||
|
||||
if (events.length > 0) {
|
||||
return getTagValue("claim", events[0].tags)
|
||||
}
|
||||
}
|
||||
|
||||
export const requestRelayClaims = async (urls: string[]) =>
|
||||
filterVals(
|
||||
isNotNil,
|
||||
fromPairs(await Promise.all(urls.map(async url => [url, await requestRelayClaim(url)]))),
|
||||
)
|
||||
|
||||
+24
-16
@@ -35,6 +35,8 @@ export const makeChatPath = (pubkeys: string[]) => `/chat/${makeChatId(pubkeys)}
|
||||
|
||||
export const makeRoomPath = (url: string, room: string) => `/spaces/${encodeRelay(url)}/${room}`
|
||||
|
||||
export const makeSpaceChatPath = (url: string) => makeRoomPath(url, "chat")
|
||||
|
||||
export const makeThreadPath = (url: string, eventId?: string) =>
|
||||
makeSpacePath(url, "threads", eventId)
|
||||
|
||||
@@ -61,34 +63,40 @@ export const getPrimaryNavItemIndex = ($page: Page) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const goToMessage = async (url: string, room: string | undefined, id: string) => {
|
||||
await goto(room ? makeRoomPath(url, room) : makeSpacePath(url, "chat"))
|
||||
await sleep(300)
|
||||
|
||||
return scrollToEvent(id)
|
||||
}
|
||||
|
||||
export const goToEvent = async (event: TrustedEvent) => {
|
||||
export const goToEvent = async (event: TrustedEvent, options: Record<string, any> = {}) => {
|
||||
if (event.kind === DIRECT_MESSAGE || event.kind === DIRECT_MESSAGE_FILE) {
|
||||
return await scrollToEvent(event.id)
|
||||
await scrollToEvent(event.id)
|
||||
}
|
||||
|
||||
const urls = Array.from(tracker.getRelays(event.id))
|
||||
const path = await getEventPath(event, urls)
|
||||
|
||||
if (path.includes("://")) {
|
||||
window.open(path)
|
||||
} else {
|
||||
goto(path, options)
|
||||
|
||||
await sleep(300)
|
||||
await scrollToEvent(event.id)
|
||||
}
|
||||
}
|
||||
|
||||
export const getEventPath = async (event: TrustedEvent, urls: string[]) => {
|
||||
const room = getTagValue(ROOM, event.tags)
|
||||
|
||||
if (urls.length > 0) {
|
||||
const url = urls[0]
|
||||
|
||||
if (event.kind === THREAD) {
|
||||
return goto(makeThreadPath(url, event.id))
|
||||
return makeThreadPath(url, event.id)
|
||||
}
|
||||
|
||||
if (event.kind === EVENT_TIME) {
|
||||
return goto(makeCalendarPath(url, event.id))
|
||||
return makeCalendarPath(url, event.id)
|
||||
}
|
||||
|
||||
if (event.kind === MESSAGE) {
|
||||
return goToMessage(url, room, event.id)
|
||||
return room ? makeRoomPath(url, room) : makeSpacePath(url, "chat")
|
||||
}
|
||||
|
||||
const kind = event.tags.find(nthEq(0, "K"))?.[1]
|
||||
@@ -96,18 +104,18 @@ export const goToEvent = async (event: TrustedEvent) => {
|
||||
|
||||
if (id && kind) {
|
||||
if (parseInt(kind) === THREAD) {
|
||||
return goto(makeThreadPath(url, id))
|
||||
return makeThreadPath(url, id)
|
||||
}
|
||||
|
||||
if (parseInt(kind) === EVENT_TIME) {
|
||||
return goto(makeCalendarPath(url, id))
|
||||
return makeCalendarPath(url, id)
|
||||
}
|
||||
|
||||
if (parseInt(kind) === MESSAGE) {
|
||||
return goToMessage(url, room, id)
|
||||
return room ? makeRoomPath(url, room) : makeSpacePath(url, "chat")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.open(entityLink(nip19.neventEncode({id: event.id, relays: urls})))
|
||||
return entityLink(nip19.neventEncode({id: event.id, relays: urls}))
|
||||
}
|
||||
|
||||
+53
-38
@@ -23,7 +23,14 @@ import {
|
||||
} from "@welshman/lib"
|
||||
import type {Socket} from "@welshman/net"
|
||||
import {Pool, load, AuthStateEvent, SocketEvent} from "@welshman/net"
|
||||
import {collection, custom} from "@welshman/store"
|
||||
import {
|
||||
collection,
|
||||
custom,
|
||||
deriveEvents,
|
||||
deriveEventsMapped,
|
||||
withGetter,
|
||||
synced,
|
||||
} from "@welshman/store"
|
||||
import {
|
||||
getIdFilters,
|
||||
WRAP,
|
||||
@@ -33,14 +40,19 @@ import {
|
||||
ZAP_RESPONSE,
|
||||
DIRECT_MESSAGE,
|
||||
DIRECT_MESSAGE_FILE,
|
||||
GROUP_META,
|
||||
ROOM_META,
|
||||
MESSAGE,
|
||||
GROUPS,
|
||||
ROOMS,
|
||||
THREAD,
|
||||
COMMENT,
|
||||
GROUP_JOIN,
|
||||
GROUP_ADD_USER,
|
||||
GROUP_REMOVE_USER,
|
||||
ROOM_JOIN,
|
||||
ROOM_ADD_USER,
|
||||
ROOM_REMOVE_USER,
|
||||
ALERT_EMAIL,
|
||||
ALERT_WEB,
|
||||
ALERT_IOS,
|
||||
ALERT_ANDROID,
|
||||
ALERT_STATUS,
|
||||
getGroupTags,
|
||||
getRelayTagValues,
|
||||
getPubkeyTagValues,
|
||||
@@ -51,6 +63,7 @@ import {
|
||||
asDecryptedEvent,
|
||||
normalizeRelayUrl,
|
||||
getTag,
|
||||
getTagValue,
|
||||
getTagValues,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent, SignedEvent, PublishedList, List, Filter} from "@welshman/util"
|
||||
@@ -76,7 +89,6 @@ import {
|
||||
appContext,
|
||||
} from "@welshman/app"
|
||||
import type {Thunk, Relay} from "@welshman/app"
|
||||
import {deriveEvents, deriveEventsMapped, withGetter, synced} from "@welshman/store"
|
||||
|
||||
export const fromCsv = (s: string) => (s || "").split(",").filter(identity)
|
||||
|
||||
@@ -84,14 +96,12 @@ export const ROOM = "h"
|
||||
|
||||
export const PROTECTED = ["-"]
|
||||
|
||||
export const ALERT = 32830
|
||||
|
||||
export const ALERT_STATUS = 32831
|
||||
|
||||
export const NOTIFIER_PUBKEY = import.meta.env.VITE_NOTIFIER_PUBKEY
|
||||
|
||||
export const NOTIFIER_RELAY = import.meta.env.VITE_NOTIFIER_RELAY
|
||||
|
||||
export const VAPID_PUBLIC_KEY = import.meta.env.VITE_VAPID_PUBLIC_KEY
|
||||
|
||||
export const INDEXER_RELAYS = fromCsv(import.meta.env.VITE_INDEXER_RELAYS)
|
||||
|
||||
export const SIGNER_RELAYS = fromCsv(import.meta.env.VITE_SIGNER_RELAYS)
|
||||
@@ -124,7 +134,7 @@ export const REACTION_KINDS = [REACTION, ZAP_RESPONSE]
|
||||
|
||||
export const NIP46_PERMS =
|
||||
"nip44_encrypt,nip44_decrypt," +
|
||||
[CLIENT_AUTH, AUTH_JOIN, MESSAGE, THREAD, COMMENT, GROUPS, WRAP, REACTION]
|
||||
[CLIENT_AUTH, AUTH_JOIN, MESSAGE, THREAD, COMMENT, ROOMS, WRAP, REACTION]
|
||||
.map(k => `sign_event:${k}`)
|
||||
.join(",")
|
||||
|
||||
@@ -171,8 +181,6 @@ export const entityLink = (entity: string) => `https://coracle.social/${entity}`
|
||||
export const pubkeyLink = (pubkey: string, relays = Router.get().FromPubkeys([pubkey]).getUrls()) =>
|
||||
entityLink(nip19.nprofileEncode({pubkey, relays}))
|
||||
|
||||
export const tagRoom = (room: string, url: string) => [ROOM, room]
|
||||
|
||||
export const getDefaultPubkeys = () => {
|
||||
const appPubkeys = DEFAULT_PUBKEYS.split(",")
|
||||
const userPubkeys = shuffle(getPubkeyTagValues(getListTags(get(userFollows))))
|
||||
@@ -345,15 +353,17 @@ export type Alert = {
|
||||
tags: string[][]
|
||||
}
|
||||
|
||||
export const alerts = deriveEventsMapped<Alert>(repository, {
|
||||
filters: [{kinds: [ALERT]}],
|
||||
itemToEvent: item => item.event,
|
||||
eventToItem: async event => {
|
||||
const tags = parseJson(await decrypt(signer.get(), NOTIFIER_PUBKEY, event.content))
|
||||
export const alerts = withGetter(
|
||||
deriveEventsMapped<Alert>(repository, {
|
||||
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))
|
||||
|
||||
return {event, tags}
|
||||
},
|
||||
})
|
||||
return {event, tags}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
// Alert Statuses
|
||||
|
||||
@@ -362,15 +372,20 @@ export type AlertStatus = {
|
||||
tags: string[][]
|
||||
}
|
||||
|
||||
export const alertStatuses = deriveEventsMapped<AlertStatus>(repository, {
|
||||
filters: [{kinds: [ALERT_STATUS]}],
|
||||
itemToEvent: item => item.event,
|
||||
eventToItem: async event => {
|
||||
const tags = parseJson(await decrypt(signer.get(), NOTIFIER_PUBKEY, event.content))
|
||||
export const alertStatuses = withGetter(
|
||||
deriveEventsMapped<AlertStatus>(repository, {
|
||||
filters: [{kinds: [ALERT_STATUS]}],
|
||||
itemToEvent: item => item.event,
|
||||
eventToItem: async event => {
|
||||
const tags = parseJson(await decrypt(signer.get(), NOTIFIER_PUBKEY, event.content))
|
||||
|
||||
return {event, tags}
|
||||
},
|
||||
})
|
||||
return {event, tags}
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
export const deriveAlertStatus = (address: string) =>
|
||||
derived(alertStatuses, statuses => statuses.find(s => getTagValue("d", s.event.tags) === address))
|
||||
|
||||
// Membership
|
||||
|
||||
@@ -399,7 +414,7 @@ export const getMembershipRoomsByUrl = (url: string, list?: List) =>
|
||||
sort(getGroupTags(getListTags(list)).filter(nthEq(2, url)).map(nth(1)))
|
||||
|
||||
export const memberships = deriveEventsMapped<PublishedList>(repository, {
|
||||
filters: [{kinds: [GROUPS]}],
|
||||
filters: [{kinds: [ROOMS]}],
|
||||
itemToEvent: item => item.event,
|
||||
eventToItem: (event: TrustedEvent) => readList(asDecryptedEvent(event)),
|
||||
})
|
||||
@@ -412,7 +427,7 @@ export const {
|
||||
name: "memberships",
|
||||
store: memberships,
|
||||
getKey: list => list.event.pubkey,
|
||||
load: makeOutboxLoader(GROUPS),
|
||||
load: makeOutboxLoader(ROOMS),
|
||||
})
|
||||
|
||||
// Chats
|
||||
@@ -510,7 +525,7 @@ export const splitChannelId = (id: string) => id.split("'")
|
||||
export const hasNip29 = (relay?: Relay) =>
|
||||
relay?.profile?.supported_nips?.map?.(String)?.includes?.("29")
|
||||
|
||||
export const channelEvents = deriveEvents(repository, {filters: [{kinds: [GROUP_META]}]})
|
||||
export const channelEvents = deriveEvents(repository, {filters: [{kinds: [ROOM_META]}]})
|
||||
|
||||
export const channels = derived(
|
||||
[channelEvents, getUrlsForEvent],
|
||||
@@ -559,7 +574,7 @@ export const {
|
||||
|
||||
await load({
|
||||
relays: [url],
|
||||
filters: [{kinds: [GROUP_META], "#d": [room]}],
|
||||
filters: [{kinds: [ROOM_META], "#d": [room]}],
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -645,22 +660,22 @@ export const deriveUserMembershipStatus = (url: string, room: string) =>
|
||||
[
|
||||
pubkey,
|
||||
deriveEventsForUrl(url, [
|
||||
{kinds: [GROUP_JOIN, GROUP_ADD_USER, GROUP_REMOVE_USER], "#h": [room]},
|
||||
{kinds: [ROOM_JOIN, ROOM_ADD_USER, ROOM_REMOVE_USER], "#h": [room]},
|
||||
]),
|
||||
],
|
||||
([$pubkey, $events]) => {
|
||||
let status = MembershipStatus.Initial
|
||||
|
||||
for (const event of $events) {
|
||||
if (event.kind === GROUP_JOIN && event.pubkey === $pubkey) {
|
||||
if (event.kind === ROOM_JOIN && event.pubkey === $pubkey) {
|
||||
status = MembershipStatus.Pending
|
||||
}
|
||||
|
||||
if (event.kind === GROUP_REMOVE_USER && getTagValues("p", event.tags).includes($pubkey!)) {
|
||||
if (event.kind === ROOM_REMOVE_USER && getTagValues("p", event.tags).includes($pubkey!)) {
|
||||
break
|
||||
}
|
||||
|
||||
if (event.kind === GROUP_ADD_USER && getTagValues("p", event.tags).includes($pubkey!)) {
|
||||
if (event.kind === ROOM_ADD_USER && getTagValues("p", event.tags).includes($pubkey!)) {
|
||||
return MembershipStatus.Granted
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.7491 9.70957V9.00497C18.7491 5.13623 15.7274 2 12 2C8.27256 2 5.25087 5.13623 5.25087 9.00497V9.70957C5.25087 10.5552 5.00972 11.3818 4.5578 12.0854L3.45036 13.8095C2.43882 15.3843 3.21105 17.5249 4.97036 18.0229C9.57274 19.3257 14.4273 19.3257 19.0296 18.0229C20.789 17.5249 21.5612 15.3843 20.5496 13.8095L19.4422 12.0854C18.9903 11.3818 18.7491 10.5552 18.7491 9.70957Z" stroke="#1C274C" stroke-width="1.5"/>
|
||||
<path d="M7.5 19C8.15503 20.7478 9.92246 22 12 22C14.0775 22 15.845 20.7478 16.5 19" stroke="#1C274C" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 674 B |
@@ -9,6 +9,7 @@
|
||||
import {switcher} from "@welshman/lib"
|
||||
import AddSquare from "@assets/icons/Add Square.svg?dataurl"
|
||||
import ArrowsALogout2 from "@assets/icons/Arrows ALogout 2.svg?dataurl"
|
||||
import Bell from "@assets/icons/Bell.svg?dataurl"
|
||||
import Bookmark from "@assets/icons/Bookmark.svg?dataurl"
|
||||
import BillList from "@assets/icons/Bill List.svg?dataurl"
|
||||
import Code2 from "@assets/icons/Code 2.svg?dataurl"
|
||||
@@ -108,6 +109,7 @@
|
||||
const data = switcher(icon, {
|
||||
"add-square": AddSquare,
|
||||
"arrows-a-logout-2": ArrowsALogout2,
|
||||
bell: Bell,
|
||||
bookmark: Bookmark,
|
||||
"bill-list": BillList,
|
||||
"code-2": Code2,
|
||||
|
||||
+5
-2
@@ -102,6 +102,7 @@ export const isIntersecting = async (element: Element) =>
|
||||
|
||||
export const scrollToEvent = async (id: string, attempts = 3): Promise<boolean> => {
|
||||
const element = document.querySelector(`[data-event="${id}"]`) as any
|
||||
const elements = Array.from(document.querySelectorAll("[data-event]"))
|
||||
|
||||
if (element) {
|
||||
element.scrollIntoView({behavior: "smooth", block: "center"})
|
||||
@@ -116,8 +117,8 @@ export const scrollToEvent = async (id: string, attempts = 3): Promise<boolean>
|
||||
}, 800 + 400)
|
||||
|
||||
return true
|
||||
} else {
|
||||
const lastElement = last(Array.from(document.querySelectorAll("[data-event]")))
|
||||
} else if (elements.length > 0) {
|
||||
const lastElement = last(elements)
|
||||
|
||||
if (lastElement && !isIntersecting(lastElement)) {
|
||||
lastElement.scrollIntoView({behavior: "smooth", block: "center"})
|
||||
@@ -131,4 +132,6 @@ export const scrollToEvent = async (id: string, attempts = 3): Promise<boolean>
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
} from "@welshman/app"
|
||||
import * as lib from "@welshman/lib"
|
||||
import * as util from "@welshman/util"
|
||||
import * as feeds from "@welshman/feeds"
|
||||
import * as router from "@welshman/router"
|
||||
import * as welshmanSigner from "@welshman/signer"
|
||||
import * as net from "@welshman/net"
|
||||
import * as app from "@welshman/app"
|
||||
@@ -61,6 +63,7 @@
|
||||
} from "@app/state"
|
||||
import {loadUserData, listenForNotifications} from "@app/requests"
|
||||
import {theme} from "@app/theme"
|
||||
import {initializePushNotifications} from "@app/push"
|
||||
import * as commands from "@app/commands"
|
||||
import * as requests from "@app/requests"
|
||||
import * as notifications from "@app/notifications"
|
||||
@@ -71,6 +74,9 @@
|
||||
dropSession($session.pubkey)
|
||||
}
|
||||
|
||||
// Initialize push notification handler asap
|
||||
initializePushNotifications()
|
||||
|
||||
const {children} = $props()
|
||||
|
||||
const ready = $state(defer<void>())
|
||||
@@ -81,7 +87,9 @@
|
||||
nip19,
|
||||
...lib,
|
||||
...welshmanSigner,
|
||||
...router,
|
||||
...util,
|
||||
...feeds,
|
||||
...net,
|
||||
...app,
|
||||
...appState,
|
||||
@@ -90,6 +98,13 @@
|
||||
...notifications,
|
||||
})
|
||||
|
||||
// Listen for navigation messages from service worker
|
||||
navigator.serviceWorker?.addEventListener("message", event => {
|
||||
if (event.data && event.data.type === "NAVIGATE") {
|
||||
goto(event.data.url)
|
||||
}
|
||||
})
|
||||
|
||||
// Nstart login
|
||||
if (window.location.hash?.startsWith("#nostr-login")) {
|
||||
const params = new URLSearchParams(window.location.hash.slice(1))
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
import {onMount} from "svelte"
|
||||
import * as nip19 from "nostr-tools/nip19"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {Address, getIdFilters, getTagValue} from "@welshman/util"
|
||||
import {Address, getIdFilters} from "@welshman/util"
|
||||
import {LOCAL_RELAY_URL} from "@welshman/relay"
|
||||
import {load} from "@welshman/net"
|
||||
import {page} from "$app/stores"
|
||||
import {goto} from "$app/navigation"
|
||||
import {scrollToEvent} from "@lib/html"
|
||||
import Spinner from "@lib/components/Spinner.svelte"
|
||||
import {makeRoomPath, makeThreadPath} from "@app/routes"
|
||||
import {goToEvent} from "@app/routes"
|
||||
|
||||
const {bech32} = $page.params
|
||||
|
||||
@@ -22,19 +22,11 @@
|
||||
let found = false
|
||||
|
||||
load({
|
||||
relays: data.relays,
|
||||
relays: [LOCAL_RELAY_URL, ...data.relays],
|
||||
filters: getIdFilters([type === "nevent" ? data.id : Address.fromNaddr(bech32).toString()]),
|
||||
onEvent: (event: TrustedEvent) => {
|
||||
found = true
|
||||
|
||||
if (event.kind === 9) {
|
||||
goto(makeRoomPath(data.relays[0], getTagValue("h", event.tags)!), {replaceState: true})
|
||||
scrollToEvent(event.id)
|
||||
} else if (event.kind === 11) {
|
||||
goto(makeThreadPath(data.relays[0], event.id), {replaceState: true})
|
||||
} else {
|
||||
goto("/", {replaceState: true})
|
||||
}
|
||||
goToEvent(event, {replaceState: true})
|
||||
},
|
||||
onClose: () => {
|
||||
if (!found) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type {Snippet} from "svelte"
|
||||
import {page} from "$app/stores"
|
||||
import {WRAP} from "@welshman/util"
|
||||
import {Router} from "@welshman/router"
|
||||
@@ -10,18 +11,19 @@
|
||||
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
|
||||
import SecondaryNavHeader from "@lib/components/SecondaryNavHeader.svelte"
|
||||
import SecondaryNavSection from "@lib/components/SecondaryNavSection.svelte"
|
||||
import ChatStart from "@app/components/ChatStart.svelte"
|
||||
import ChatMenu from "@app/components/ChatMenu.svelte"
|
||||
import ChatItem from "@app/components/ChatItem.svelte"
|
||||
import {chatSearch} from "@app/state"
|
||||
import {pullConservatively} from "@app/requests"
|
||||
import {pushModal} from "@app/modal"
|
||||
interface Props {
|
||||
children?: import("svelte").Snippet
|
||||
|
||||
type Props = {
|
||||
children?: Snippet
|
||||
}
|
||||
|
||||
const {children}: Props = $props()
|
||||
|
||||
const startChat = () => pushModal(ChatStart)
|
||||
const openMenu = () => pushModal(ChatMenu)
|
||||
|
||||
const promise = pullConservatively({
|
||||
filters: [{kinds: [WRAP], "#p": [$pubkey!]}],
|
||||
@@ -37,8 +39,8 @@
|
||||
<SecondaryNavSection>
|
||||
<SecondaryNavHeader>
|
||||
Chats
|
||||
<Button onclick={startChat}>
|
||||
<Icon icon="add-circle" />
|
||||
<Button onclick={openMenu}>
|
||||
<Icon icon="menu-dots" />
|
||||
</Button>
|
||||
</SecondaryNavHeader>
|
||||
</SecondaryNavSection>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import ContentSearch from "@lib/components/ContentSearch.svelte"
|
||||
import ChatItem from "@app/components/ChatItem.svelte"
|
||||
import ChatStart from "@app/components/ChatStart.svelte"
|
||||
import ChatMenuMobile from "@app/components/ChatMenuMobile.svelte"
|
||||
import ChatMenu from "@app/components/ChatMenu.svelte"
|
||||
import {chatSearch} from "@app/state"
|
||||
import {pushModal} from "@app/modal"
|
||||
import {setChecked} from "@app/notifications"
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
const startChat = () => pushModal(ChatStart)
|
||||
|
||||
const openMenu = () => pushModal(ChatMenuMobile)
|
||||
const openMenu = () => pushModal(ChatMenu)
|
||||
|
||||
const chats = $derived($chatSearch.searchOptions(term))
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
import {addToMapKey, dec, gt} from "@welshman/lib"
|
||||
import {GROUPS} from "@welshman/util"
|
||||
import {ROOMS} from "@welshman/util"
|
||||
import {Router} from "@welshman/router"
|
||||
import {load} from "@welshman/net"
|
||||
import type {Relay} from "@welshman/app"
|
||||
@@ -29,7 +29,7 @@
|
||||
const discoverRelays = () =>
|
||||
Promise.all([
|
||||
load({
|
||||
filters: [{kinds: [GROUPS]}],
|
||||
filters: [{kinds: [ROOMS]}],
|
||||
relays: Router.get().Index().getUrls(),
|
||||
}),
|
||||
...getDefaultPubkeys().map(async pubkey => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import {
|
||||
getListTags,
|
||||
tagger,
|
||||
createEvent,
|
||||
makeEvent,
|
||||
getPubkeyTagValues,
|
||||
getTagValues,
|
||||
MUTES,
|
||||
@@ -38,17 +38,17 @@
|
||||
const relays = Router.get().FromUser().getUrls()
|
||||
|
||||
publishThunk({
|
||||
event: createEvent(SETTINGS, {content}),
|
||||
event: makeEvent(SETTINGS, {content}),
|
||||
relays,
|
||||
})
|
||||
|
||||
publishThunk({
|
||||
event: createEvent(MUTES, {tags: mutedPubkeys.map(tagPubkey)}),
|
||||
event: makeEvent(MUTES, {tags: mutedPubkeys.map(tagPubkey)}),
|
||||
relays,
|
||||
})
|
||||
|
||||
publishThunk({
|
||||
event: createEvent(BLOSSOM_SERVERS, {tags: blossomServers.map(tagger("server"))}),
|
||||
event: makeEvent(BLOSSOM_SERVERS, {tags: blossomServers.map(tagger("server"))}),
|
||||
relays,
|
||||
})
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import {onMount} from "svelte"
|
||||
import {page} from "$app/stores"
|
||||
import {ago, MONTH} from "@welshman/lib"
|
||||
import {GROUP_META, EVENT_TIME, THREAD, COMMENT, MESSAGE} from "@welshman/util"
|
||||
import {ROOM_META, EVENT_TIME, THREAD, COMMENT, MESSAGE} from "@welshman/util"
|
||||
import Page from "@lib/components/Page.svelte"
|
||||
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
|
||||
import MenuSpace from "@app/components/MenuSpace.svelte"
|
||||
@@ -61,7 +61,7 @@
|
||||
pullConservatively({
|
||||
relays,
|
||||
filters: [
|
||||
{kinds: [GROUP_META]},
|
||||
{kinds: [ROOM_META]},
|
||||
{kinds: [THREAD, EVENT_TIME], since},
|
||||
{kinds: [COMMENT], "#K": [String(THREAD), String(EVENT_TIME)], since},
|
||||
...rooms.map(room => ({kinds: [MESSAGE], "#h": [room], since})),
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
import {request} from "@welshman/net"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {
|
||||
createEvent,
|
||||
makeEvent,
|
||||
makeRoomMeta,
|
||||
MESSAGE,
|
||||
DELETE,
|
||||
REACTION,
|
||||
GROUP_ADD_USER,
|
||||
GROUP_REMOVE_USER,
|
||||
ROOM_ADD_USER,
|
||||
ROOM_REMOVE_USER,
|
||||
} from "@welshman/util"
|
||||
import {pubkey, publishThunk, getThunkError} from "@welshman/app"
|
||||
import {pubkey, publishThunk, getThunkError, joinRoom, leaveRoom} from "@welshman/app"
|
||||
import {slide, fade, fly} from "@lib/transition"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
@@ -32,20 +33,13 @@
|
||||
userRoomsByUrl,
|
||||
userSettingValues,
|
||||
decodeRelay,
|
||||
tagRoom,
|
||||
getEventsForUrl,
|
||||
deriveUserMembershipStatus,
|
||||
deriveChannel,
|
||||
MembershipStatus,
|
||||
} from "@app/state"
|
||||
import {setChecked, checked} from "@app/notifications"
|
||||
import {
|
||||
joinRoom,
|
||||
leaveRoom,
|
||||
addRoomMembership,
|
||||
removeRoomMembership,
|
||||
prependParent,
|
||||
} from "@app/commands"
|
||||
import {addRoomMembership, removeRoomMembership, prependParent} from "@app/commands"
|
||||
import {PROTECTED} from "@app/state"
|
||||
import {makeFeed} from "@app/requests"
|
||||
import {popKey} from "@app/implicit"
|
||||
@@ -68,7 +62,7 @@
|
||||
joining = true
|
||||
|
||||
try {
|
||||
const message = await getThunkError(joinRoom(url, room))
|
||||
const message = await getThunkError(joinRoom(url, makeRoomMeta({id: room})))
|
||||
|
||||
if (message && !message.startsWith("duplicate:")) {
|
||||
return pushToast({theme: "error", message})
|
||||
@@ -84,7 +78,7 @@
|
||||
const leave = async () => {
|
||||
leaving = true
|
||||
try {
|
||||
const message = await getThunkError(leaveRoom(url, room))
|
||||
const message = await getThunkError(leaveRoom(url, makeRoomMeta({id: room})))
|
||||
|
||||
if (message && !message.startsWith("duplicate:")) {
|
||||
pushToast({theme: "error", message})
|
||||
@@ -108,7 +102,7 @@
|
||||
}
|
||||
|
||||
const onSubmit = ({content, tags}: EventContent) => {
|
||||
tags.push(tagRoom(room, url))
|
||||
tags.push(["h", room])
|
||||
tags.push(PROTECTED)
|
||||
|
||||
let template = {content, tags}
|
||||
@@ -123,7 +117,7 @@
|
||||
|
||||
publishThunk({
|
||||
relays: [url],
|
||||
event: createEvent(MESSAGE, template),
|
||||
event: makeEvent(MESSAGE, template),
|
||||
delay: $userSettingValues.send_delay,
|
||||
})
|
||||
|
||||
@@ -251,7 +245,7 @@
|
||||
relays: [url],
|
||||
filters: [
|
||||
{
|
||||
kinds: [GROUP_ADD_USER, GROUP_REMOVE_USER],
|
||||
kinds: [ROOM_ADD_USER, ROOM_REMOVE_USER],
|
||||
"#p": [$pubkey!],
|
||||
"#h": [room],
|
||||
limit: 10,
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
const url = decodeRelay($page.params.relay)
|
||||
|
||||
const createEvent = () => pushModal(CalendarEventCreate, {url})
|
||||
const makeEvent = () => pushModal(CalendarEventCreate, {url})
|
||||
|
||||
const getStart = (event: TrustedEvent) => parseInt(getTagValue("start", event.tags) || "")
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
{/snippet}
|
||||
{#snippet action()}
|
||||
<div class="row-2">
|
||||
<Button class="btn btn-primary btn-sm" onclick={createEvent}>
|
||||
<Button class="btn btn-primary btn-sm" onclick={makeEvent}>
|
||||
<Icon icon="calendar-add" />
|
||||
Create an Event
|
||||
</Button>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import type {Readable} from "svelte/store"
|
||||
import {now, formatTimestampAsDate} from "@welshman/lib"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {createEvent, MESSAGE, DELETE, REACTION} from "@welshman/util"
|
||||
import {makeEvent, MESSAGE, DELETE, REACTION} from "@welshman/util"
|
||||
import {pubkey, publishThunk} from "@welshman/app"
|
||||
import {slide, fade, fly} from "@lib/transition"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
publishThunk({
|
||||
relays: [url],
|
||||
event: createEvent(MESSAGE, template),
|
||||
event: makeEvent(MESSAGE, template),
|
||||
delay: $userSettingValues.send_delay,
|
||||
})
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import NoteCard from "@app/components/NoteCard.svelte"
|
||||
import MenuSpaceButton from "@app/components/MenuSpaceButton.svelte"
|
||||
import ThreadActions from "@app/components/ThreadActions.svelte"
|
||||
import CommentActions from "@app/components/CommentActions.svelte"
|
||||
import EventReply from "@app/components/EventReply.svelte"
|
||||
import {deriveEvent, decodeRelay} from "@app/state"
|
||||
import {setChecked} from "@app/notifications"
|
||||
@@ -90,11 +91,11 @@
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{#each sortBy(e => -e.created_at, $replies).slice(0, showAll ? undefined : 4) as reply (reply.id)}
|
||||
{#each sortBy(e => e.created_at, $replies).slice(0, showAll ? undefined : 4) as reply (reply.id)}
|
||||
<NoteCard event={reply} {url} class="card2 bg-alt z-feature w-full">
|
||||
<div class="col-3 ml-12">
|
||||
<Content showEntire event={reply} {url} />
|
||||
<ThreadActions event={reply} {url} />
|
||||
<CommentActions event={reply} {url} />
|
||||
</div>
|
||||
</NoteCard>
|
||||
{/each}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/* global clients */
|
||||
|
||||
import * as nip19 from "nostr-tools/nip19"
|
||||
|
||||
self.addEventListener("install", event => {
|
||||
self.skipWaiting()
|
||||
})
|
||||
|
||||
self.addEventListener("activate", event => {
|
||||
event.waitUntil(self.clients.claim())
|
||||
})
|
||||
|
||||
self.addEventListener("push", e => {
|
||||
console.log("Service Worker: Push event received", e)
|
||||
|
||||
let url = "/"
|
||||
let title = "New activity"
|
||||
let body = "You have a new message"
|
||||
|
||||
try {
|
||||
const data = e.data?.json()
|
||||
|
||||
if (data?.event) {
|
||||
url += nip19.neventEncode({
|
||||
id: data.event.id,
|
||||
relays: data.relays || [],
|
||||
})
|
||||
}
|
||||
|
||||
if (data?.title) {
|
||||
title = data.title
|
||||
}
|
||||
|
||||
if (data?.body) {
|
||||
body = data.body
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Service Worker: Failed to parse push data", e)
|
||||
}
|
||||
|
||||
e.waitUntil(
|
||||
self.registration.showNotification(title, {
|
||||
body,
|
||||
data: {url},
|
||||
icon: "/pwa-192x192.png",
|
||||
badge: "/pwa-64x64.png",
|
||||
tag: "flotilla-notification",
|
||||
requireInteraction: false,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
self.addEventListener("notificationclick", e => {
|
||||
console.log("Service Worker: Notification click event", e)
|
||||
|
||||
e.notification.close()
|
||||
|
||||
if (e.action === "close") {
|
||||
return
|
||||
}
|
||||
|
||||
// Default action or 'open' action
|
||||
const url = e.notification.data?.url
|
||||
|
||||
e.waitUntil(
|
||||
clients
|
||||
.matchAll({
|
||||
type: "window",
|
||||
includeUncontrolled: true,
|
||||
})
|
||||
.then(clientList => {
|
||||
// Check if app is already open and send navigation message
|
||||
for (const client of clientList) {
|
||||
if (client.url.includes(location.origin)) {
|
||||
client.postMessage({
|
||||
type: "NAVIGATE",
|
||||
url: url,
|
||||
})
|
||||
|
||||
return client.focus()
|
||||
}
|
||||
}
|
||||
|
||||
// Open new window if app is not open
|
||||
if (clients.openWindow) {
|
||||
return clients.openWindow(url)
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user