Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c9b3f23df | |||
| e0d83608be | |||
| a0301d599b | |||
| 7dcaa0e8d7 | |||
| 129f49bcc7 | |||
| fc3b68c390 | |||
| 52c7df8a15 | |||
| ce1c4dd488 | |||
| fc6a1a3819 | |||
| 69bd6d0e70 | |||
| 6d383d54e8 | |||
| 998c48b1d3 | |||
| 7217d122b5 | |||
| 1c37c5bb3d | |||
| e8f785b558 | |||
| c94d314f6d | |||
| 2672a8f922 | |||
| 8a8d80d692 | |||
| 95698813c6 | |||
| 4001e877b4 | |||
| 99defc6d79 | |||
| a94883089e | |||
| 5ea4aeb75c | |||
| 456d111925 | |||
| 837ae4b38e | |||
| ffbcbf86c3 | |||
| bcda637192 | |||
| 72c7dd6126 | |||
| a2a4b3599f | |||
| 4955a4f16c | |||
| bb1ff4fb11 | |||
| b81f7c9ed3 | |||
| 689cfb6d45 | |||
| 9da3141650 | |||
| e4fe18df2f | |||
| ba80ebac63 | |||
| d4943daa82 | |||
| cde03ec0fe | |||
| 4f6c08f8a2 | |||
| 38e0fc53ad | |||
| 2a30ca5306 | |||
| 4a4ea13bef | |||
| 239bd3f31a | |||
| 831ec05012 | |||
| 0cc0598287 | |||
| 0a5bc618c2 | |||
| 069904f07a | |||
| 03b42c8276 | |||
| 8697cc23be | |||
| 69e1f97e72 |
+1
-1
@@ -4,7 +4,7 @@ VITE_PLATFORM_URL=https://flotilla.social
|
|||||||
VITE_PLATFORM_TERMS=https://flotilla.social/terms
|
VITE_PLATFORM_TERMS=https://flotilla.social/terms
|
||||||
VITE_PLATFORM_PRIVACY=https://flotilla.social/privacy
|
VITE_PLATFORM_PRIVACY=https://flotilla.social/privacy
|
||||||
VITE_PLATFORM_NAME=Flotilla
|
VITE_PLATFORM_NAME=Flotilla
|
||||||
VITE_PLATFORM_LOGO=static/flotilla.png
|
VITE_PLATFORM_LOGO=static/logo.png
|
||||||
VITE_PLATFORM_RELAYS=
|
VITE_PLATFORM_RELAYS=
|
||||||
VITE_PLATFORM_ACCENT="#7161FF"
|
VITE_PLATFORM_ACCENT="#7161FF"
|
||||||
VITE_PLATFORM_SECONDARY="#EB5E28"
|
VITE_PLATFORM_SECONDARY="#EB5E28"
|
||||||
|
|||||||
@@ -1,5 +1,30 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
# 1.2.4
|
||||||
|
|
||||||
|
* Add direct message alerts
|
||||||
|
* Add alert settings page
|
||||||
|
* Add instructions to key download
|
||||||
|
* Add option that allows relays to strip signatures
|
||||||
|
* Detect relays that mostly refuse to serve requests
|
||||||
|
* Compress and upload profile images
|
||||||
|
* Use system theme by default
|
||||||
|
* Switch icon set, refactor how they're included
|
||||||
|
* Use capacitor's preferences for storage instead of localStorage
|
||||||
|
|
||||||
|
# 1.2.3
|
||||||
|
|
||||||
|
* Add `created_at` to event info dialog
|
||||||
|
* Add signer status to profile page
|
||||||
|
* Re-work bunker login flow
|
||||||
|
* Add in-app onboarding flow
|
||||||
|
* Only protect events if relay authenticates
|
||||||
|
* Filter out non-global chats from global chat
|
||||||
|
* Improve publish status indicator
|
||||||
|
* Fix encrypted upload content type
|
||||||
|
* Add relays to event details dialog
|
||||||
|
* Add universal link handler for apps
|
||||||
|
|
||||||
# 1.2.2
|
# 1.2.2
|
||||||
|
|
||||||
* Fix phantom chat notifications
|
* Fix phantom chat notifications
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
## Project Overview
|
|
||||||
|
|
||||||
Flotilla is a Discord-like Nostr client that operates on the concept of "relays as groups/spaces." Built with SvelteKit 2.5 and Svelte 5, it provides messaging, threads, calendar events, and social features across Nostr relays.
|
|
||||||
|
|
||||||
## Important Patterns
|
|
||||||
|
|
||||||
### Finding Code
|
|
||||||
- Prefer navigating from one file to the next following imports when possible
|
|
||||||
- If search is necessary, use `ack`, not `grep` or `rg`.
|
|
||||||
|
|
||||||
### Nostr Event Handling
|
|
||||||
- Prefer seconds to milliseconds when handling nostr events.
|
|
||||||
|
|
||||||
### Styling Conventions
|
|
||||||
- When styling html, prefer flex/gap classes over margin or space-y classes.
|
|
||||||
|
|
||||||
### Room/space memberships
|
|
||||||
|
|
||||||
Memberships are surfaced as "bookmarks" to the user.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import {membershipsByPubkey, getMembershipUrls} from '@app/state'
|
|
||||||
|
|
||||||
const spaces = getMembershipUrls($membershipsByPubkey.get(pubkey))
|
|
||||||
const rooms = getMembershipRooms($membershipsByPubkey.get(pubkey))
|
|
||||||
```
|
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# Contributing guidelines
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Flotilla is a svelte/typescript/capacitor project. It's intended to be an alternative to Discord for Nostr users. A high-quality UX is a priority, with an emphasis on well-tested, intuitive designs, and robust implementations.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Run `pnpm run dev` to get a dev server, and `pnpm run check:watch` to watch for typescript errors. When you're ready to commit, a pre-commit hook will run to lint and typecheck your work. To run the project on Android or iOS, use Android Studio or Xcode.
|
||||||
|
|
||||||
|
The `master` branch is automatically deployed to production, so always work on feature branches based on the `dev` branch. This project frequently uses unreleased versions of [welshman](https://welshman.coracle.social), using `pnpm` link to hotlink a local copy of the code. To set that up, clone welshman to the parent directory of your `coracle` client, then add `link:../welshman/packages/packagename` to the `pnpm.overrides` section of your `package.json`. Below is a nodejs script that will do that automatically for you:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'))
|
||||||
|
|
||||||
|
packageJson.pnpm.overrides = Object.keys(packageJson.dependencies)
|
||||||
|
.filter(pkg => pkg.startsWith('@welshman/'))
|
||||||
|
.reduce((acc, pkg) => {
|
||||||
|
const packageName = pkg.split('/')[1]
|
||||||
|
acc[pkg] = `link:../welshman/packages/${packageName}`
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
fs.writeFileSync('./package.json', JSON.stringify(packageJson, null, 2) + '\n')
|
||||||
|
|
||||||
|
console.log('Added welshman package overrides.')
|
||||||
|
```
|
||||||
|
|
||||||
|
Be sure to avoid committing overrides to either `package.json` or `pnpm-lock.yaml`. These overrides can generally be added, installed, and removed, and will persist until another `pnpm install` command gets run.
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
The main parts of the application are as follows:
|
||||||
|
|
||||||
|
- `static` - static assets like fonts, images, etc.
|
||||||
|
- `src/assets` - svgs for use as icons.
|
||||||
|
- `src/lib` - general purpose components and utilities.
|
||||||
|
- `src/app/core/state` - environment variables, constants, custom stores, and some utilities derived from them.
|
||||||
|
- `src/app/core/requests` - utilities related to loading data from the nostr network.
|
||||||
|
- `src/app/core/commands` - utilities related to publishing nostr events and uploading media to blossom servers.
|
||||||
|
- `src/app/utils` - other application logic, including stuff related to modals, routing, etc.
|
||||||
|
- `src/app/editor` - configuration for `@welshman/editor` for use in various app views.
|
||||||
|
- `src/app/components` - reusable components that depend on other `app` stuff.
|
||||||
|
- `src/routes` - file-based routing interpreted by sveltekit.
|
||||||
|
|
||||||
|
Application organization is based on an acyclic dependency graph:
|
||||||
|
|
||||||
|
- `routes` can depend on anything
|
||||||
|
- `app/components` can depend on anything in `app` or `lib`
|
||||||
|
- `app/utils` and `app/core` can only depend on `lib`
|
||||||
|
- `lib` (and everything else) can depend only on external libraries
|
||||||
|
|
||||||
|
The main stylistic/organizational rule when working in this project is that imports should be sorted based on the dependency graph. Third-party libraries should come first, then `lib`, then `app`.
|
||||||
|
|
||||||
|
## System Architecture
|
||||||
|
|
||||||
|
Flotilla's architecture generally mirrors the file structure. State is stored using Svelte `store`s provided either by `@welshman/app` or by `app/core/state`, allowing for idiomatic svelte 4 usage (svelte 5 runes are [ghey](https://habla.news/u/hodlbod@coracle.social/1739830562159) and not allowed outside of UI components).
|
||||||
|
|
||||||
|
State is then synchronized to local storage or indexeddb using storage helpers provided by welshman in `routes/+layout.svelte`. Other top level synchronization logic generally belongs there.
|
||||||
|
|
||||||
|
`app/core/state` contains all environment variables, constants, custom stores, and utilities derived from them. Most stores are `derived` from our event `repository` using `deriveEventsMapped`, which efficiently queries the repository and maps events to custom data structures. Some of these data structures are provided by `welshman`, and some are defined in `app/core/state`. In either case, they can always be mapped back to an event, which is important for updating replaceables without dropping unknown data.
|
||||||
|
|
||||||
|
Here are a few important domain objects:
|
||||||
|
|
||||||
|
- Spaces are relays used as community groups. Their `url`s are core to a lot of data and components, and are frequently passed around from place to place.
|
||||||
|
- Chats are direct message conversations. There is currently some ambiguity in routing, since relays that don't support NIP 29 also have a "chat" tab, which uses vanilla NIP-C7.
|
||||||
|
- NIP 29 groups are variously called "rooms" and "channels". Conventionally, a "room" is a group id, while a "channel" as an object representing the group's metadata.
|
||||||
|
- "Alerts" are records of requests the user has made to be notified, following [this NIP](https://github.com/nostr-protocol/nips/pull/1796)
|
||||||
|
|
||||||
|
`app/core/requests` contains utilities related to loading data from the nostr network. This might include feed manager utilities, loaders, or listeners.
|
||||||
|
|
||||||
|
`app/core/commands` contains utilities related to publishing nostr events and uploading media to blossom servers. This also includes utilities related to sending lighting payments, authenticating with relays, or probing relay policy. Event creation should generally be split into `make` functions which build the event, and `publish` functions which publish the event using `publishThunk`.
|
||||||
|
|
||||||
|
Any of these utilities can be included either in `app/components` or `routes`. Crucial to keep in mind is that nearly all global state runs through welshman's `repository` in a unidirectional way. To update state, run `publishThunk`, which immediately publishes the event to the local repository. State can be read from the repository using `deriveEventsMapped` or other utilities provided by welshman like `deriveProfile`.
|
||||||
|
|
||||||
|
Thunks are designed to reduce UI latency, handling signatures and delayed sending the background. In most cases, thunk status should be displayed to the user so that they can cancel sending or address errors.
|
||||||
|
|
||||||
|
Toast, modals, and sidebar dialogs are controlled in `app/util/modal` and `app/util/toast`. In both cases, component objects can be passed along with parameters, but care has to be taken that the calling component either doesn't unmount before the modal (as when one modal replaces another), or that `$state.snapshot` is appropriately called on any state runes. These components frequently run into weird svelte compiler bugs too, in which case you may have to do some silly things to cope.
|
||||||
|
|
||||||
|
## Issues and Pull Requests
|
||||||
|
|
||||||
|
All work by contributors should be done against an issue. If there is no issue for the work you're doing, please open one or ask the project owner to open one. All PRs should be opened against the `dev` branch (unless for hotfixes).
|
||||||
|
|
||||||
|
## Communication
|
||||||
|
|
||||||
|
Discussion about development is done [on Flotilla](https://app.flotilla.social/spaces/internal.coracle.social). The group is currently closed, so please let me know if you'd like access.
|
||||||
|
|
||||||
|
## Project License
|
||||||
|
|
||||||
|
This project is licensed under the MIT license. By contributing, you agree to waive all intellectual property rights to your contributions to this project.
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ If you're deploying a custom version of flotilla, be sure to remove the `plausib
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
Run `pnpm run dev` to get a dev server, and `pnpm run check:watch` to watch for typescript errors. When you're ready to commit, a pre-commit hook will run to lint and typecheck your work.
|
See [./CONTRIBUTING.md](CONTRIBUTING.md).
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ android {
|
|||||||
applicationId "social.flotilla"
|
applicationId "social.flotilla"
|
||||||
minSdk rootProject.ext.minSdkVersion
|
minSdk rootProject.ext.minSdkVersion
|
||||||
targetSdk rootProject.ext.targetSdkVersion
|
targetSdk rootProject.ext.targetSdkVersion
|
||||||
versionCode 23
|
versionCode 25
|
||||||
versionName "1.2.2"
|
versionName "1.2.4"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ dependencies {
|
|||||||
implementation project(':capacitor-community-safe-area')
|
implementation project(':capacitor-community-safe-area')
|
||||||
implementation project(':capacitor-app')
|
implementation project(':capacitor-app')
|
||||||
implementation project(':capacitor-keyboard')
|
implementation project(':capacitor-keyboard')
|
||||||
|
implementation project(':capacitor-preferences')
|
||||||
implementation project(':capacitor-push-notifications')
|
implementation project(':capacitor-push-notifications')
|
||||||
|
implementation project(':capawesome-capacitor-android-dark-mode-support')
|
||||||
implementation project(':capawesome-capacitor-badge')
|
implementation project(':capawesome-capacitor-badge')
|
||||||
implementation project(':nostr-signer-capacitor-plugin')
|
implementation project(':nostr-signer-capacitor-plugin')
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,13 @@
|
|||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter android:autoVerify="true">
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="https" android:host="app.flotilla.social" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
<item name="windowActionBar">false</item>
|
<item name="windowActionBar">false</item>
|
||||||
<item name="windowNoTitle">true</item>
|
<item name="windowNoTitle">true</item>
|
||||||
<item name="android:background">@null</item>
|
<item name="android:background">@null</item>
|
||||||
|
<item name="android:windowOptOutEdgeToEdgeEnforcement" tools:ignore="NewApi">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,15 @@ project(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacito
|
|||||||
include ':capacitor-keyboard'
|
include ':capacitor-keyboard'
|
||||||
project(':capacitor-keyboard').projectDir = new File('../node_modules/.pnpm/@capacitor+keyboard@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/keyboard/android')
|
project(':capacitor-keyboard').projectDir = new File('../node_modules/.pnpm/@capacitor+keyboard@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/keyboard/android')
|
||||||
|
|
||||||
|
include ':capacitor-preferences'
|
||||||
|
project(':capacitor-preferences').projectDir = new File('../node_modules/.pnpm/@capacitor+preferences@7.0.2_@capacitor+core@7.2.0/node_modules/@capacitor/preferences/android')
|
||||||
|
|
||||||
include ':capacitor-push-notifications'
|
include ':capacitor-push-notifications'
|
||||||
project(':capacitor-push-notifications').projectDir = new File('../node_modules/.pnpm/@capacitor+push-notifications@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/push-notifications/android')
|
project(':capacitor-push-notifications').projectDir = new File('../node_modules/.pnpm/@capacitor+push-notifications@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/push-notifications/android')
|
||||||
|
|
||||||
|
include ':capawesome-capacitor-android-dark-mode-support'
|
||||||
|
project(':capawesome-capacitor-android-dark-mode-support').projectDir = new File('../node_modules/.pnpm/@capawesome+capacitor-android-dark-mode-support@7.0.0_@capacitor+core@7.2.0/node_modules/@capawesome/capacitor-android-dark-mode-support/android')
|
||||||
|
|
||||||
include ':capawesome-capacitor-badge'
|
include ':capawesome-capacitor-badge'
|
||||||
project(':capawesome-capacitor-badge').projectDir = new File('../node_modules/.pnpm/@capawesome+capacitor-badge@7.0.1_@capacitor+core@7.2.0/node_modules/@capawesome/capacitor-badge/android')
|
project(':capawesome-capacitor-badge').projectDir = new File('../node_modules/.pnpm/@capawesome+capacitor-badge@7.0.1_@capacitor+core@7.2.0/node_modules/@capawesome/capacitor-badge/android')
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ const config: CapacitorConfig = {
|
|||||||
server: {
|
server: {
|
||||||
androidScheme: "https"
|
androidScheme: "https"
|
||||||
},
|
},
|
||||||
|
android: {
|
||||||
|
adjustMarginsForEdgeToEdge: false,
|
||||||
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
SplashScreen: {
|
SplashScreen: {
|
||||||
androidSplashResourceName: "splash"
|
androidSplashResourceName: "splash"
|
||||||
|
|||||||
@@ -354,14 +354,14 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 16;
|
CURRENT_PROJECT_VERSION = 17;
|
||||||
DEVELOPMENT_TEAM = S26U9DYW3A;
|
DEVELOPMENT_TEAM = S26U9DYW3A;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 1.2.2;
|
MARKETING_VERSION = 1.2.4;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -380,14 +380,14 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Flotilla Chat.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 16;
|
CURRENT_PROJECT_VERSION = 17;
|
||||||
DEVELOPMENT_TEAM = S26U9DYW3A;
|
DEVELOPMENT_TEAM = S26U9DYW3A;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
INFOPLIST_KEY_CFBundleDisplayName = "Flotilla Chat";
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 1.2.2;
|
MARKETING_VERSION = 1.2.4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
|||||||
@@ -4,5 +4,9 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>development</string>
|
<string>development</string>
|
||||||
|
<key>com.apple.developer.associated-domains</key>
|
||||||
|
<array>
|
||||||
|
<string>applinks:app.flotilla.social</string>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ def capacitor_pods
|
|||||||
pod 'CapacitorCommunitySafeArea', :path => '../../node_modules/.pnpm/@capacitor-community+safe-area@7.0.0-alpha.1_@capacitor+core@7.2.0/node_modules/@capacitor-community/safe-area'
|
pod 'CapacitorCommunitySafeArea', :path => '../../node_modules/.pnpm/@capacitor-community+safe-area@7.0.0-alpha.1_@capacitor+core@7.2.0/node_modules/@capacitor-community/safe-area'
|
||||||
pod 'CapacitorApp', :path => '../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/app'
|
pod 'CapacitorApp', :path => '../../node_modules/.pnpm/@capacitor+app@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/app'
|
||||||
pod 'CapacitorKeyboard', :path => '../../node_modules/.pnpm/@capacitor+keyboard@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/keyboard'
|
pod 'CapacitorKeyboard', :path => '../../node_modules/.pnpm/@capacitor+keyboard@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/keyboard'
|
||||||
|
pod 'CapacitorPreferences', :path => '../../node_modules/.pnpm/@capacitor+preferences@7.0.2_@capacitor+core@7.2.0/node_modules/@capacitor/preferences'
|
||||||
pod 'CapacitorPushNotifications', :path => '../../node_modules/.pnpm/@capacitor+push-notifications@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/push-notifications'
|
pod 'CapacitorPushNotifications', :path => '../../node_modules/.pnpm/@capacitor+push-notifications@7.0.1_@capacitor+core@7.2.0/node_modules/@capacitor/push-notifications'
|
||||||
pod 'CapawesomeCapacitorBadge', :path => '../../node_modules/.pnpm/@capawesome+capacitor-badge@7.0.1_@capacitor+core@7.2.0/node_modules/@capawesome/capacitor-badge'
|
pod 'CapawesomeCapacitorBadge', :path => '../../node_modules/.pnpm/@capawesome+capacitor-badge@7.0.1_@capacitor+core@7.2.0/node_modules/@capawesome/capacitor-badge'
|
||||||
pod 'NostrSignerCapacitorPlugin', :path => '../../node_modules/.pnpm/nostr-signer-capacitor-plugin@0.0.4_@capacitor+core@7.2.0/node_modules/nostr-signer-capacitor-plugin'
|
pod 'NostrSignerCapacitorPlugin', :path => '../../node_modules/.pnpm/nostr-signer-capacitor-plugin@0.0.4_@capacitor+core@7.2.0/node_modules/nostr-signer-capacitor-plugin'
|
||||||
|
|||||||
+14
-12
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "flotilla",
|
"name": "flotilla",
|
||||||
"version": "1.2.2",
|
"version": "1.2.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
@@ -45,7 +45,9 @@
|
|||||||
"@capacitor/core": "^7.0.1",
|
"@capacitor/core": "^7.0.1",
|
||||||
"@capacitor/ios": "^7.0.0",
|
"@capacitor/ios": "^7.0.0",
|
||||||
"@capacitor/keyboard": "^7.0.0",
|
"@capacitor/keyboard": "^7.0.0",
|
||||||
|
"@capacitor/preferences": "^7.0.2",
|
||||||
"@capacitor/push-notifications": "^7.0.1",
|
"@capacitor/push-notifications": "^7.0.1",
|
||||||
|
"@capawesome/capacitor-android-dark-mode-support": "^7.0.0",
|
||||||
"@capawesome/capacitor-badge": "^7.0.1",
|
"@capawesome/capacitor-badge": "^7.0.1",
|
||||||
"@getalby/sdk": "^5.1.0",
|
"@getalby/sdk": "^5.1.0",
|
||||||
"@poppanator/sveltekit-svg": "^4.2.1",
|
"@poppanator/sveltekit-svg": "^4.2.1",
|
||||||
@@ -56,17 +58,17 @@
|
|||||||
"@types/throttle-debounce": "^5.0.2",
|
"@types/throttle-debounce": "^5.0.2",
|
||||||
"@vite-pwa/assets-generator": "^0.2.6",
|
"@vite-pwa/assets-generator": "^0.2.6",
|
||||||
"@vite-pwa/sveltekit": "^0.6.6",
|
"@vite-pwa/sveltekit": "^0.6.6",
|
||||||
"@welshman/app": "^0.4.0",
|
"@welshman/app": "^0.4.7",
|
||||||
"@welshman/content": "^0.4.0",
|
"@welshman/content": "^0.4.7",
|
||||||
"@welshman/editor": "^0.4.0",
|
"@welshman/editor": "^0.4.7",
|
||||||
"@welshman/feeds": "^0.4.0",
|
"@welshman/feeds": "^0.4.7",
|
||||||
"@welshman/lib": "^0.4.0",
|
"@welshman/lib": "^0.4.7",
|
||||||
"@welshman/net": "^0.4.0",
|
"@welshman/net": "^0.4.7",
|
||||||
"@welshman/relay": "^0.4.0",
|
"@welshman/relay": "^0.4.7",
|
||||||
"@welshman/router": "^0.4.0",
|
"@welshman/router": "^0.4.7",
|
||||||
"@welshman/signer": "^0.4.0",
|
"@welshman/signer": "^0.4.7",
|
||||||
"@welshman/store": "^0.4.0",
|
"@welshman/store": "^0.4.7",
|
||||||
"@welshman/util": "^0.4.0",
|
"@welshman/util": "^0.4.7",
|
||||||
"compressorjs": "^1.2.1",
|
"compressorjs": "^1.2.1",
|
||||||
"daisyui": "^4.12.10",
|
"daisyui": "^4.12.10",
|
||||||
"date-picker-svelte": "^2.13.0",
|
"date-picker-svelte": "^2.13.0",
|
||||||
|
|||||||
Generated
+111
-86
@@ -29,9 +29,15 @@ importers:
|
|||||||
'@capacitor/keyboard':
|
'@capacitor/keyboard':
|
||||||
specifier: ^7.0.0
|
specifier: ^7.0.0
|
||||||
version: 7.0.1(@capacitor/core@7.2.0)
|
version: 7.0.1(@capacitor/core@7.2.0)
|
||||||
|
'@capacitor/preferences':
|
||||||
|
specifier: ^7.0.2
|
||||||
|
version: 7.0.2(@capacitor/core@7.2.0)
|
||||||
'@capacitor/push-notifications':
|
'@capacitor/push-notifications':
|
||||||
specifier: ^7.0.1
|
specifier: ^7.0.1
|
||||||
version: 7.0.1(@capacitor/core@7.2.0)
|
version: 7.0.1(@capacitor/core@7.2.0)
|
||||||
|
'@capawesome/capacitor-android-dark-mode-support':
|
||||||
|
specifier: ^7.0.0
|
||||||
|
version: 7.0.0(@capacitor/core@7.2.0)
|
||||||
'@capawesome/capacitor-badge':
|
'@capawesome/capacitor-badge':
|
||||||
specifier: ^7.0.1
|
specifier: ^7.0.1
|
||||||
version: 7.0.1(@capacitor/core@7.2.0)
|
version: 7.0.1(@capacitor/core@7.2.0)
|
||||||
@@ -63,38 +69,38 @@ importers:
|
|||||||
specifier: ^0.6.6
|
specifier: ^0.6.6
|
||||||
version: 0.6.8(@sveltejs/kit@2.20.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.25.10)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0)))(svelte@5.25.10)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0)))(@vite-pwa/assets-generator@0.2.6)(vite-plugin-pwa@0.21.2(@vite-pwa/assets-generator@0.2.6)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0))(workbox-build@7.3.0)(workbox-window@7.3.0))
|
version: 0.6.8(@sveltejs/kit@2.20.5(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.25.10)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0)))(svelte@5.25.10)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0)))(@vite-pwa/assets-generator@0.2.6)(vite-plugin-pwa@0.21.2(@vite-pwa/assets-generator@0.2.6)(vite@5.4.17(@types/node@22.14.0)(terser@5.39.0))(workbox-build@7.3.0)(workbox-window@7.3.0))
|
||||||
'@welshman/app':
|
'@welshman/app':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.7
|
||||||
version: 0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
version: 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
||||||
'@welshman/content':
|
'@welshman/content':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.7
|
||||||
version: 0.4.0(typescript@5.8.3)
|
version: 0.4.7(typescript@5.8.3)
|
||||||
'@welshman/editor':
|
'@welshman/editor':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.7
|
||||||
version: 0.4.0(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)
|
version: 0.4.7(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)
|
||||||
'@welshman/feeds':
|
'@welshman/feeds':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.7
|
||||||
version: 0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
version: 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
||||||
'@welshman/lib':
|
'@welshman/lib':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.7
|
||||||
version: 0.4.0
|
version: 0.4.7
|
||||||
'@welshman/net':
|
'@welshman/net':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.7
|
||||||
version: 0.4.0(typescript@5.8.3)(ws@8.18.3)
|
version: 0.4.7(typescript@5.8.3)(ws@8.18.3)
|
||||||
'@welshman/relay':
|
'@welshman/relay':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.7
|
||||||
version: 0.4.0(typescript@5.8.3)
|
version: 0.4.7(typescript@5.8.3)
|
||||||
'@welshman/router':
|
'@welshman/router':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.7
|
||||||
version: 0.4.0(typescript@5.8.3)
|
version: 0.4.7(typescript@5.8.3)
|
||||||
'@welshman/signer':
|
'@welshman/signer':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.7
|
||||||
version: 0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
version: 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
||||||
'@welshman/store':
|
'@welshman/store':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.7
|
||||||
version: 0.4.0(typescript@5.8.3)
|
version: 0.4.7(typescript@5.8.3)
|
||||||
'@welshman/util':
|
'@welshman/util':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.7
|
||||||
version: 0.4.0(typescript@5.8.3)
|
version: 0.4.7(typescript@5.8.3)
|
||||||
compressorjs:
|
compressorjs:
|
||||||
specifier: ^1.2.1
|
specifier: ^1.2.1
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
@@ -758,11 +764,21 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@capacitor/core': '>=7.0.0'
|
'@capacitor/core': '>=7.0.0'
|
||||||
|
|
||||||
|
'@capacitor/preferences@7.0.2':
|
||||||
|
resolution: {integrity: sha512-JVCy0/oc6RsRencLOZ8rMqjNxAlHs7awPJU/MXqangsJ48oO2PnYGHfCvci6WgIJlqyC0QhvWZaO1BR1lVkHWQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@capacitor/core': '>=7.0.0'
|
||||||
|
|
||||||
'@capacitor/push-notifications@7.0.1':
|
'@capacitor/push-notifications@7.0.1':
|
||||||
resolution: {integrity: sha512-nSHsMSrTHX5pOkX1Khse75/uvSx/JTcXG+9aT6a66CvzalH6MCs0ha8Jv+xu0k9xW8caO+qSUMjfj5Oy82Uxmw==}
|
resolution: {integrity: sha512-nSHsMSrTHX5pOkX1Khse75/uvSx/JTcXG+9aT6a66CvzalH6MCs0ha8Jv+xu0k9xW8caO+qSUMjfj5Oy82Uxmw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@capacitor/core': '>=7.0.0'
|
'@capacitor/core': '>=7.0.0'
|
||||||
|
|
||||||
|
'@capawesome/capacitor-android-dark-mode-support@7.0.0':
|
||||||
|
resolution: {integrity: sha512-xLa988v9MEnFZj7Aje3v2vvYbs5U3zk8vaO24OioXUV1ZPjGy4R2VkdoKG8fTsSYRdF6s4/8DpCuos8JLJqpjw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@capacitor/core': '>=7.0.0'
|
||||||
|
|
||||||
'@capawesome/capacitor-badge@7.0.1':
|
'@capawesome/capacitor-badge@7.0.1':
|
||||||
resolution: {integrity: sha512-jhVieRRVLgGO1NU7PW8uWZmf3WD4IsYUlkrJ82KuoRgLFx1tbJGwHU1ro0sUJmEwfLO9vldhBnJJ/J5nHrjbQQ==}
|
resolution: {integrity: sha512-jhVieRRVLgGO1NU7PW8uWZmf3WD4IsYUlkrJ82KuoRgLFx1tbJGwHU1ro0sUJmEwfLO9vldhBnJJ/J5nHrjbQQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1632,41 +1648,41 @@ packages:
|
|||||||
'@vite-pwa/assets-generator':
|
'@vite-pwa/assets-generator':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@welshman/app@0.4.0':
|
'@welshman/app@0.4.7':
|
||||||
resolution: {integrity: sha512-LTlqbuiRFYAdwXIUYPOxaAusjhlj2ZgZlAuyEpQoBwNTyD7TUaTXj0kA5pbQZLFXWYuqDmrDB14Nl1zzBJBESQ==}
|
resolution: {integrity: sha512-JEgr3NhzDLeOoTSZ4+AESKhW+kwqYClLbLd6ccHV+Wa7hjoPJy2FlY3rDuppw9QSeTNCDEOvqCsjeWYz83lUBA==}
|
||||||
|
|
||||||
'@welshman/content@0.4.0':
|
'@welshman/content@0.4.7':
|
||||||
resolution: {integrity: sha512-3pWxr0Byc/Asmvlnq5UchkT0yeaGg63xTEk9fVJyzIrphIxn5bboaIixEw7y2w2lggFaqHgx+DFrulmhdJ9dXQ==}
|
resolution: {integrity: sha512-PF2FqiE3QUybl0CwwaEI2aGCZIis2rbdvBgeafgPiHW7fRYROxUZdtcMy+MyiRfiV40uoBcYwu/N6hHCRU7Pag==}
|
||||||
|
|
||||||
'@welshman/editor@0.4.0':
|
'@welshman/editor@0.4.7':
|
||||||
resolution: {integrity: sha512-aIt/t+pMs2XKWZ6wN58jdPWlN9MXVdK1rccKk6Z54ckarCzB4B7usSZvstwMMkmZra/HPLOaWw5KXqhDR1YiUA==}
|
resolution: {integrity: sha512-K6XCLG+vVvVeEYPx+m/+Lfx/JO+NtvvIemCds6gFjY7ac+WaQuEQDP3kFGYQu69XiFXp/UZrIa+t7SezEpnU8A==}
|
||||||
|
|
||||||
'@welshman/feeds@0.4.0':
|
'@welshman/feeds@0.4.7':
|
||||||
resolution: {integrity: sha512-fwQ4eDzEtcSxFj2LKps6XYFXuZv6lFXKDTq+Nvs5tNYYJUbv/Cz4x3aLQo2ivInz9gAMOLmgpIgNCxkzMqCnoQ==}
|
resolution: {integrity: sha512-aZQuTUD4aSkL0s2BkjwEpo5KTd9BKf/XiOssQrltLdc8NIsz8RIO0XLgCpFb0/dmqHRoJEqU/plIBy7AlleRCQ==}
|
||||||
|
|
||||||
'@welshman/lib@0.4.0':
|
'@welshman/lib@0.4.7':
|
||||||
resolution: {integrity: sha512-1GPQ2X1FT2R55KWPKhDs+ZK/EkVpeMkVwWdSXC88w+YCoUop00keFm7P452kQKgA/lixNURJSyeWgfI2tUdpkQ==}
|
resolution: {integrity: sha512-VP3WO2ROo5pf2vHwnrdt6lQVTc8Eo52Ie+1/9ZzfTrSxtLrreSSxW3H+1oPDbHl3FXbDnQWdFWbxys6OxzKZWw==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
'@welshman/net@0.4.0':
|
'@welshman/net@0.4.7':
|
||||||
resolution: {integrity: sha512-QBU5dsALCr9V51lIyNseUDIfvjCJo6VFWe6G1gkJ1PQGh5rgJNZJWCaD430PDpCKsufv2JIkCVYZG5xYZgxzMg==}
|
resolution: {integrity: sha512-S0dGVqNAfo5cvBxIuaI2oEX0JUs2FuzkOcYXkMNB1plWzi1mBcskNCBWfFf/zHJYaqUoYjZ/tDARy64lge9m/w==}
|
||||||
|
|
||||||
'@welshman/relay@0.4.0':
|
'@welshman/relay@0.4.7':
|
||||||
resolution: {integrity: sha512-5zTPSDPhMR2v55hotQf4JO3XgBXEws4k/xChAbYZDfUwxG7HQxmDM2n56aFrKqgI3w+qp8l1lx/1KeksKvBiWw==}
|
resolution: {integrity: sha512-FkqYswNA3uT1NeJVHdEZ7p9jEPGCFMx4ci2y+h9o25rCDdxg3WUhqDSdc5d85sGTO0qG2pNnvNMfS/Du/nFlOA==}
|
||||||
|
|
||||||
'@welshman/router@0.4.0':
|
'@welshman/router@0.4.7':
|
||||||
resolution: {integrity: sha512-ccpx9QrJ7Uq3CI7r/PyBOwO0G/2xknKXN0xLW995hta1Z1bUzYWhz+C9YvoceDPHCUPaZotBCgdmpY9oOiYqHg==}
|
resolution: {integrity: sha512-HnB1qrKGNxL8HtC6p47yHUnaDHevi+IKtqWEVCIFMRf17GwINBc3wp3+d3pu9KBBXItFZz1awABvZK+pNKQcgg==}
|
||||||
|
|
||||||
'@welshman/signer@0.4.0':
|
'@welshman/signer@0.4.7':
|
||||||
resolution: {integrity: sha512-I+4l1gmSBVQkFtu6Bm5aAxsFXlE5oXeCsUX+GSsTb0Pg1e4FnTMgeaI3xM8tcCLma4EK+3mD7Yi9MaZMSYX8YQ==}
|
resolution: {integrity: sha512-V5Jdmblb2kPO5bAv1CzVrodZiwKpYYotmS6MFXfWmyrKKp+9B5KoMWzXt6yfd4HWYbEKEjLhbm1gzbckupW8Nw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
nostr-signer-capacitor-plugin: ~0.0.4
|
nostr-signer-capacitor-plugin: ~0.0.4
|
||||||
|
|
||||||
'@welshman/store@0.4.0':
|
'@welshman/store@0.4.7':
|
||||||
resolution: {integrity: sha512-/gIX1hTTGPkhFlMm91oY7khqriIZwnNIFs3leWIbJGWXLd4pd4fMFM7bKuOTuEsnHkB9thkeXurAdYMquhlHOw==}
|
resolution: {integrity: sha512-8PniW1AOOYFtLRYMuay62taumW7zgwtBmouwoMh08fBQjLb+c90V4g2cEGVWoyvKXSLzQkQppPlaqYzdSDqgwg==}
|
||||||
|
|
||||||
'@welshman/util@0.4.0':
|
'@welshman/util@0.4.7':
|
||||||
resolution: {integrity: sha512-UiJyqXeWEx0s83M0AD/bN5ylvpCfYUSjLepb0QxxZpBPnZgMj07oO1OS4967QFuePSvmjTQNcxy3ABm8aagxGg==}
|
resolution: {integrity: sha512-FlmBiZeKlAEAAwyhu7cWtlfAxU3CWX7WQGn0NkCZaAjgGV3n8LIDjT1u9m1PmXirBT0+qFNGLWas3p72IQMLgg==}
|
||||||
|
|
||||||
'@xml-tools/parser@1.0.11':
|
'@xml-tools/parser@1.0.11':
|
||||||
resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==}
|
resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==}
|
||||||
@@ -4165,6 +4181,7 @@ packages:
|
|||||||
source-map@0.8.0-beta.0:
|
source-map@0.8.0-beta.0:
|
||||||
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
|
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
deprecated: The work that was done in this beta branch won't be included in future versions
|
||||||
|
|
||||||
sourcemap-codec@1.4.8:
|
sourcemap-codec@1.4.8:
|
||||||
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
|
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
|
||||||
@@ -5592,10 +5609,18 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@capacitor/core': 7.2.0
|
'@capacitor/core': 7.2.0
|
||||||
|
|
||||||
|
'@capacitor/preferences@7.0.2(@capacitor/core@7.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@capacitor/core': 7.2.0
|
||||||
|
|
||||||
'@capacitor/push-notifications@7.0.1(@capacitor/core@7.2.0)':
|
'@capacitor/push-notifications@7.0.1(@capacitor/core@7.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@capacitor/core': 7.2.0
|
'@capacitor/core': 7.2.0
|
||||||
|
|
||||||
|
'@capawesome/capacitor-android-dark-mode-support@7.0.0(@capacitor/core@7.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@capacitor/core': 7.2.0
|
||||||
|
|
||||||
'@capawesome/capacitor-badge@7.0.1(@capacitor/core@7.2.0)':
|
'@capawesome/capacitor-badge@7.0.1(@capacitor/core@7.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@capacitor/core': 7.2.0
|
'@capacitor/core': 7.2.0
|
||||||
@@ -6499,17 +6524,17 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@vite-pwa/assets-generator': 0.2.6
|
'@vite-pwa/assets-generator': 0.2.6
|
||||||
|
|
||||||
'@welshman/app@0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
|
'@welshman/app@0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/throttle-debounce': 5.0.2
|
'@types/throttle-debounce': 5.0.2
|
||||||
'@welshman/feeds': 0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
'@welshman/feeds': 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
||||||
'@welshman/lib': 0.4.0
|
'@welshman/lib': 0.4.7
|
||||||
'@welshman/net': 0.4.0(typescript@5.8.3)(ws@8.18.3)
|
'@welshman/net': 0.4.7(typescript@5.8.3)(ws@8.18.3)
|
||||||
'@welshman/relay': 0.4.0(typescript@5.8.3)
|
'@welshman/relay': 0.4.7(typescript@5.8.3)
|
||||||
'@welshman/router': 0.4.0(typescript@5.8.3)
|
'@welshman/router': 0.4.7(typescript@5.8.3)
|
||||||
'@welshman/signer': 0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
'@welshman/signer': 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
||||||
'@welshman/store': 0.4.0(typescript@5.8.3)
|
'@welshman/store': 0.4.7(typescript@5.8.3)
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.7(typescript@5.8.3)
|
||||||
fuse.js: 7.1.0
|
fuse.js: 7.1.0
|
||||||
idb: 8.0.2
|
idb: 8.0.2
|
||||||
svelte: 4.2.20
|
svelte: 4.2.20
|
||||||
@@ -6519,14 +6544,14 @@ snapshots:
|
|||||||
- typescript
|
- typescript
|
||||||
- ws
|
- ws
|
||||||
|
|
||||||
'@welshman/content@0.4.0(typescript@5.8.3)':
|
'@welshman/content@0.4.7(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@braintree/sanitize-url': 7.1.1
|
'@braintree/sanitize-url': 7.1.1
|
||||||
nostr-tools: 2.14.2(typescript@5.8.3)
|
nostr-tools: 2.14.2(typescript@5.8.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
'@welshman/editor@0.4.0(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)':
|
'@welshman/editor@0.4.7(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
'@tiptap/core': 2.12.0(@tiptap/pm@2.12.0)
|
||||||
'@tiptap/extension-code': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
'@tiptap/extension-code': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
||||||
@@ -6541,8 +6566,8 @@ snapshots:
|
|||||||
'@tiptap/extension-text': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
'@tiptap/extension-text': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))
|
||||||
'@tiptap/pm': 2.12.0
|
'@tiptap/pm': 2.12.0
|
||||||
'@tiptap/suggestion': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
'@tiptap/suggestion': 2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)
|
||||||
'@welshman/lib': 0.4.0
|
'@welshman/lib': 0.4.7
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.7(typescript@5.8.3)
|
||||||
nostr-editor: 1.0.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(linkifyjs@4.3.1)(nostr-tools@2.14.2(typescript@5.8.3))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))
|
nostr-editor: 1.0.0(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0)(linkifyjs@4.3.1)(nostr-tools@2.14.2(typescript@5.8.3))(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))
|
||||||
nostr-tools: 2.14.2(typescript@5.8.3)
|
nostr-tools: 2.14.2(typescript@5.8.3)
|
||||||
tippy.js: 6.3.7
|
tippy.js: 6.3.7
|
||||||
@@ -6557,78 +6582,78 @@ snapshots:
|
|||||||
- tiptap-markdown
|
- tiptap-markdown
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
'@welshman/feeds@0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
|
'@welshman/feeds@0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@welshman/lib': 0.4.0
|
'@welshman/lib': 0.4.7
|
||||||
'@welshman/net': 0.4.0(typescript@5.8.3)(ws@8.18.3)
|
'@welshman/net': 0.4.7(typescript@5.8.3)(ws@8.18.3)
|
||||||
'@welshman/relay': 0.4.0(typescript@5.8.3)
|
'@welshman/relay': 0.4.7(typescript@5.8.3)
|
||||||
'@welshman/router': 0.4.0(typescript@5.8.3)
|
'@welshman/router': 0.4.7(typescript@5.8.3)
|
||||||
'@welshman/signer': 0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
'@welshman/signer': 0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.7(typescript@5.8.3)
|
||||||
trava: 1.2.1
|
trava: 1.2.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- nostr-signer-capacitor-plugin
|
- nostr-signer-capacitor-plugin
|
||||||
- typescript
|
- typescript
|
||||||
- ws
|
- ws
|
||||||
|
|
||||||
'@welshman/lib@0.4.0':
|
'@welshman/lib@0.4.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@scure/base': 1.2.6
|
'@scure/base': 1.2.6
|
||||||
'@types/events': 3.0.3
|
'@types/events': 3.0.3
|
||||||
events: 3.3.0
|
events: 3.3.0
|
||||||
|
|
||||||
'@welshman/net@0.4.0(typescript@5.8.3)(ws@8.18.3)':
|
'@welshman/net@0.4.7(typescript@5.8.3)(ws@8.18.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@welshman/lib': 0.4.0
|
'@welshman/lib': 0.4.7
|
||||||
'@welshman/relay': 0.4.0(typescript@5.8.3)
|
'@welshman/relay': 0.4.7(typescript@5.8.3)
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.7(typescript@5.8.3)
|
||||||
events: 3.3.0
|
events: 3.3.0
|
||||||
isomorphic-ws: 5.0.0(ws@8.18.3)
|
isomorphic-ws: 5.0.0(ws@8.18.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
- ws
|
- ws
|
||||||
|
|
||||||
'@welshman/relay@0.4.0(typescript@5.8.3)':
|
'@welshman/relay@0.4.7(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@welshman/lib': 0.4.0
|
'@welshman/lib': 0.4.7
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.7(typescript@5.8.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
'@welshman/router@0.4.0(typescript@5.8.3)':
|
'@welshman/router@0.4.7(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@welshman/lib': 0.4.0
|
'@welshman/lib': 0.4.7
|
||||||
'@welshman/relay': 0.4.0(typescript@5.8.3)
|
'@welshman/relay': 0.4.7(typescript@5.8.3)
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.7(typescript@5.8.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
'@welshman/signer@0.4.0(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
|
'@welshman/signer@0.4.7(nostr-signer-capacitor-plugin@0.0.4(@capacitor/core@7.2.0))(typescript@5.8.3)(ws@8.18.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@noble/curves': 1.9.2
|
'@noble/curves': 1.9.2
|
||||||
'@noble/hashes': 1.8.0
|
'@noble/hashes': 1.8.0
|
||||||
'@welshman/lib': 0.4.0
|
'@welshman/lib': 0.4.7
|
||||||
'@welshman/net': 0.4.0(typescript@5.8.3)(ws@8.18.3)
|
'@welshman/net': 0.4.7(typescript@5.8.3)(ws@8.18.3)
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.7(typescript@5.8.3)
|
||||||
nostr-signer-capacitor-plugin: 0.0.4(@capacitor/core@7.2.0)
|
nostr-signer-capacitor-plugin: 0.0.4(@capacitor/core@7.2.0)
|
||||||
nostr-tools: 2.14.2(typescript@5.8.3)
|
nostr-tools: 2.14.2(typescript@5.8.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
- ws
|
- ws
|
||||||
|
|
||||||
'@welshman/store@0.4.0(typescript@5.8.3)':
|
'@welshman/store@0.4.7(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@welshman/lib': 0.4.0
|
'@welshman/lib': 0.4.7
|
||||||
'@welshman/relay': 0.4.0(typescript@5.8.3)
|
'@welshman/relay': 0.4.7(typescript@5.8.3)
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.7(typescript@5.8.3)
|
||||||
svelte: 4.2.20
|
svelte: 4.2.20
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
'@welshman/util@0.4.0(typescript@5.8.3)':
|
'@welshman/util@0.4.7(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/ws': 8.18.1
|
'@types/ws': 8.18.1
|
||||||
'@welshman/lib': 0.4.0
|
'@welshman/lib': 0.4.7
|
||||||
js-base64: 3.7.7
|
js-base64: 3.7.7
|
||||||
nostr-tools: 2.14.2(typescript@5.8.3)
|
nostr-tools: 2.14.2(typescript@5.8.3)
|
||||||
nostr-wasm: 0.1.0
|
nostr-wasm: 0.1.0
|
||||||
|
|||||||
+6
-6
@@ -46,10 +46,10 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
font-family: Lato;
|
font-family: Lato;
|
||||||
--sait: env(safe-area-inset-top);
|
--sait: var(--safe-area-inset-top, env(safe-area-inset-top));
|
||||||
--saib: env(safe-area-inset-bottom);
|
--saib: var(--safe-area-inset-bottom, env(safe-area-inset-bottom));
|
||||||
--sail: env(safe-area-inset-left);
|
--sail: var(--safe-area-inset-left, env(safe-area-inset-left));
|
||||||
--sair: env(safe-area-inset-right);
|
--sair: var(--safe-area-inset-right, env(safe-area-inset-right));
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme] {
|
[data-theme] {
|
||||||
@@ -160,11 +160,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card2 {
|
.card2 {
|
||||||
@apply rounded-box p-6 text-base-content;
|
@apply rounded-box p-4 text-base-content sm:p-6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card2.card2-sm {
|
.card2.card2-sm {
|
||||||
@apply p-4 text-base-content;
|
@apply p-2 text-base-content sm:p-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
|
|||||||
@@ -1,38 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
import {decrypt} from "@welshman/signer"
|
import {randomInt, displayList, TIMEZONE, identity} from "@welshman/lib"
|
||||||
import {randomInt, parseJson, fromPairs, displayList, TIMEZONE, identity} from "@welshman/lib"
|
import {displayRelayUrl, getTagValue, THREAD, MESSAGE, EVENT_TIME, COMMENT} from "@welshman/util"
|
||||||
import {
|
|
||||||
displayRelayUrl,
|
|
||||||
getTagValue,
|
|
||||||
getAddress,
|
|
||||||
THREAD,
|
|
||||||
MESSAGE,
|
|
||||||
EVENT_TIME,
|
|
||||||
COMMENT,
|
|
||||||
} from "@welshman/util"
|
|
||||||
import type {Filter} from "@welshman/util"
|
import type {Filter} from "@welshman/util"
|
||||||
import {makeIntersectionFeed, makeRelayFeed, feedFromFilters} from "@welshman/feeds"
|
import {makeIntersectionFeed, makeRelayFeed, feedFromFilters} from "@welshman/feeds"
|
||||||
import {pubkey, signer, getThunkError} from "@welshman/app"
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
|
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import {
|
import {alerts, getMembershipUrls, userMembership} from "@app/core/state"
|
||||||
alerts,
|
import {requestRelayClaim} from "@app/core/requests"
|
||||||
getMembershipUrls,
|
import {createAlert} from "@app/core/commands"
|
||||||
userMembership,
|
import {canSendPushNotifications} from "@app/util/push"
|
||||||
NOTIFIER_PUBKEY,
|
import {pushToast} from "@app/util/toast"
|
||||||
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"
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url?: string
|
url?: string
|
||||||
@@ -60,7 +45,6 @@
|
|||||||
|
|
||||||
let loading = $state(false)
|
let loading = $state(false)
|
||||||
let cron = $state(WEEKLY)
|
let cron = $state(WEEKLY)
|
||||||
let claim = $state("")
|
|
||||||
let email = $state($alerts.map(a => getTagValue("email", a.tags)).filter(identity)[0] || "")
|
let email = $state($alerts.map(a => getTagValue("email", a.tags)).filter(identity)[0] || "")
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
@@ -110,66 +94,21 @@
|
|||||||
loading = true
|
loading = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const claims = claim ? {[url]: claim} : {}
|
const claim = url ? await requestRelayClaim(url) : undefined
|
||||||
const feed = makeIntersectionFeed(feedFromFilters(filters), makeRelayFeed(url))
|
|
||||||
const description = `for ${displayList(display)} on ${displayRelayUrl(url)}`
|
|
||||||
const params: AlertParams = {feed, claims, description}
|
|
||||||
|
|
||||||
if (channel === "email") {
|
const {error} = await createAlert({
|
||||||
const cadence = cron?.endsWith("1") ? "Weekly" : "Daily"
|
feed: makeIntersectionFeed(feedFromFilters(filters), makeRelayFeed(url)),
|
||||||
|
claims: claim ? {[url]: claim} : {},
|
||||||
params.description = `${cadence} alert ${description}, sent via email.`
|
description: `for ${displayList(display)} on ${displayRelayUrl(url)}`,
|
||||||
params.email = {
|
email: channel === "email" ? {cron, email} : undefined,
|
||||||
cron,
|
})
|
||||||
email,
|
|
||||||
handler: [
|
|
||||||
"31990:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:1737058597050",
|
|
||||||
"wss://relay.nostr.band/",
|
|
||||||
"web",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
// @ts-ignore
|
|
||||||
params[platform] = await getPushInfo()
|
|
||||||
params.description = `${platformName} push notification ${description}.`
|
|
||||||
} catch (e: any) {
|
|
||||||
return pushToast({
|
|
||||||
theme: "error",
|
|
||||||
message: String(e),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't do this we'll get an event rejection
|
|
||||||
await attemptAuth(NOTIFIER_RELAY)
|
|
||||||
|
|
||||||
const thunk = await publishAlert(params)
|
|
||||||
const error = await getThunkError(thunk)
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return pushToast({
|
pushToast({theme: "error", message: error})
|
||||||
theme: "error",
|
} else {
|
||||||
message: `Failed to send your alert to the notification server (${error}).`,
|
pushToast({message: "Your alert has been successfully created!"})
|
||||||
})
|
back()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch our new status to make sure it's active
|
|
||||||
const address = getAddress(thunk.event)
|
|
||||||
const statusEvents = await loadAlertStatuses($pubkey!)
|
|
||||||
const statusEvent = statusEvents.find(event => getTagValue("d", event.tags) === address)
|
|
||||||
const statusTags = statusEvent
|
|
||||||
? parseJson(await decrypt(signer.get(), NOTIFIER_PUBKEY, statusEvent.content))
|
|
||||||
: []
|
|
||||||
const {status = "error", message = "Your alert was not activated"}: Record<string, string> =
|
|
||||||
fromPairs(statusTags)
|
|
||||||
|
|
||||||
if (status === "error") {
|
|
||||||
return pushToast({theme: "error", message})
|
|
||||||
}
|
|
||||||
|
|
||||||
pushToast({message: "Your alert has been successfully created!"})
|
|
||||||
back()
|
|
||||||
} finally {
|
} finally {
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
@@ -179,14 +118,6 @@
|
|||||||
if (!canSendPushNotifications()) {
|
if (!canSendPushNotifications()) {
|
||||||
channel = "email"
|
channel = "email"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url) {
|
|
||||||
requestRelayClaim(url).then(code => {
|
|
||||||
if (code) {
|
|
||||||
claim = code
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -195,6 +126,9 @@
|
|||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
Add an Alert
|
Add an Alert
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
{#snippet info()}
|
||||||
|
Enable notifications to keep up to date on activity you care about.
|
||||||
|
{/snippet}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
{#if canSendPushNotifications()}
|
{#if canSendPushNotifications()}
|
||||||
<FieldInline>
|
<FieldInline>
|
||||||
@@ -268,30 +202,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</FieldInline>
|
</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>
|
|
||||||
{/snippet}
|
|
||||||
</FieldInline>
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back}>
|
<Button class="btn btn-link" onclick={back}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||||
<Spinner {loading}>Confirm</Spinner>
|
<Spinner {loading}>Confirm</Spinner>
|
||||||
<Icon icon="alt-arrow-right" />
|
<Icon icon={AltArrowRight} />
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Confirm from "@lib/components/Confirm.svelte"
|
import Confirm from "@lib/components/Confirm.svelte"
|
||||||
import type {Alert} from "@app/state"
|
import type {Alert} from "@app/core/state"
|
||||||
import {NOTIFIER_RELAY, NOTIFIER_PUBKEY} from "@app/state"
|
import {deleteAlert} from "@app/core/commands"
|
||||||
import {publishDelete} from "@app/commands"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {pushToast} from "@app/toast"
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
alert: Alert
|
alert: Alert
|
||||||
@@ -12,7 +11,7 @@
|
|||||||
const {alert}: Props = $props()
|
const {alert}: Props = $props()
|
||||||
|
|
||||||
const confirm = () => {
|
const confirm = () => {
|
||||||
publishDelete({event: alert.event, relays: [NOTIFIER_RELAY], tags: [["p", NOTIFIER_PUBKEY]]})
|
deleteAlert(alert)
|
||||||
pushToast({message: "Your alert has been deleted!"})
|
pushToast({message: "Your alert has been deleted!"})
|
||||||
history.back()
|
history.back()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {parseJson} from "@welshman/lib"
|
import {parseJson} from "@welshman/lib"
|
||||||
import {displayFeeds} from "@welshman/feeds"
|
import {displayFeeds} from "@welshman/feeds"
|
||||||
import {getAddress, getTagValue, getTagValues} from "@welshman/util"
|
import {getTagValue, getTagValues} from "@welshman/util"
|
||||||
|
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import AlertDelete from "@app/components/AlertDelete.svelte"
|
import AlertDelete from "@app/components/AlertDelete.svelte"
|
||||||
import type {Alert} from "@app/state"
|
import AlertStatus from "@app/components/AlertStatus.svelte"
|
||||||
import {deriveAlertStatus} from "@app/state"
|
import type {Alert} from "@app/core/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
alert: Alert
|
alert: Alert
|
||||||
@@ -15,7 +16,6 @@
|
|||||||
|
|
||||||
const {alert}: Props = $props()
|
const {alert}: Props = $props()
|
||||||
|
|
||||||
const status = deriveAlertStatus(getAddress(alert.event))
|
|
||||||
const cron = $derived(getTagValue("cron", alert.tags))
|
const cron = $derived(getTagValue("cron", alert.tags))
|
||||||
const channel = $derived(getTagValue("channel", alert.tags))
|
const channel = $derived(getTagValue("channel", alert.tags))
|
||||||
const feeds = $derived(getTagValues("feed", alert.tags))
|
const feeds = $derived(getTagValues("feed", alert.tags))
|
||||||
@@ -34,36 +34,9 @@
|
|||||||
<div class="flex items-start justify-between gap-4">
|
<div class="flex items-start justify-between gap-4">
|
||||||
<div class="flex items-start gap-4">
|
<div class="flex items-start gap-4">
|
||||||
<Button class="py-1" onclick={startDelete}>
|
<Button class="py-1" onclick={startDelete}>
|
||||||
<Icon icon="trash-bin-2" />
|
<Icon icon={TrashBin2} />
|
||||||
</Button>
|
</Button>
|
||||||
<div class="flex-inline gap-1">{description}</div>
|
<div class="flex-inline gap-1">{description}</div>
|
||||||
</div>
|
</div>
|
||||||
{#if $status}
|
<AlertStatus {alert} />
|
||||||
{@const statusText = getTagValue("status", $status.tags) || "error"}
|
|
||||||
{#if statusText === "ok"}
|
|
||||||
<span
|
|
||||||
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-base-content px-3 py-1 text-sm"
|
|
||||||
data-tip={getTagValue("message", $status.tags)}>
|
|
||||||
Active
|
|
||||||
</span>
|
|
||||||
{:else if statusText === "pending"}
|
|
||||||
<span
|
|
||||||
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-base-content border-yellow-500 px-3 py-1 text-sm text-yellow-500"
|
|
||||||
data-tip={getTagValue("message", $status.tags)}>
|
|
||||||
Pending
|
|
||||||
</span>
|
|
||||||
{:else}
|
|
||||||
<span
|
|
||||||
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-error px-3 py-1 text-sm text-error"
|
|
||||||
data-tip={getTagValue("message", $status.tags)}>
|
|
||||||
{statusText.replace("-", " ").replace(/^(.)/, x => x.toUpperCase())}
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
{:else}
|
|
||||||
<span
|
|
||||||
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-error px-3 py-1 text-sm text-error"
|
|
||||||
data-tip="The notification server did not respond to your request.">
|
|
||||||
Inactive
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {getAddress, getTagValue} from "@welshman/util"
|
||||||
|
import type {Alert} from "@app/core/state"
|
||||||
|
import {deriveAlertStatus} from "@app/core/state"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
alert: Alert
|
||||||
|
}
|
||||||
|
|
||||||
|
const {alert}: Props = $props()
|
||||||
|
|
||||||
|
const status = deriveAlertStatus(getAddress(alert.event))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $status}
|
||||||
|
{@const statusText = getTagValue("status", $status.tags) || "error"}
|
||||||
|
{#if statusText === "ok"}
|
||||||
|
<span
|
||||||
|
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-base-content px-3 py-1 text-sm"
|
||||||
|
data-tip={getTagValue("message", $status.tags)}>
|
||||||
|
Active
|
||||||
|
</span>
|
||||||
|
{:else if statusText === "pending"}
|
||||||
|
<span
|
||||||
|
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-base-content border-yellow-500 px-3 py-1 text-sm text-yellow-500"
|
||||||
|
data-tip={getTagValue("message", $status.tags)}>
|
||||||
|
Pending
|
||||||
|
</span>
|
||||||
|
{:else}
|
||||||
|
<span
|
||||||
|
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-error px-3 py-1 text-sm text-error"
|
||||||
|
data-tip={getTagValue("message", $status.tags)}>
|
||||||
|
{statusText.replace("-", " ").replace(/^(.)/, x => x.toUpperCase())}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<span
|
||||||
|
class="tooltip tooltip-left cursor-pointer rounded-full border border-solid border-error px-3 py-1 text-sm text-error"
|
||||||
|
data-tip="The notification server did not respond to your request.">
|
||||||
|
Inactive
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {getTagValue} from "@welshman/util"
|
import {sleep} from "@welshman/lib"
|
||||||
|
import {getTagValue, getAddress} from "@welshman/util"
|
||||||
|
import {isRelayFeed, findFeed} from "@welshman/feeds"
|
||||||
|
import Inbox from "@assets/icons/inbox.svg?dataurl"
|
||||||
|
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import AlertAdd from "@app/components/AlertAdd.svelte"
|
import AlertAdd from "@app/components/AlertAdd.svelte"
|
||||||
import AlertItem from "@app/components/AlertItem.svelte"
|
import AlertItem from "@app/components/AlertItem.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
import {alerts} from "@app/state"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
import {alerts, dmAlert, deriveAlertStatus, userInboxRelays, getAlertFeed} from "@app/core/state"
|
||||||
|
import {deleteAlert, createDmAlert} from "@app/core/commands"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url?: string
|
url?: string
|
||||||
@@ -15,29 +21,92 @@
|
|||||||
|
|
||||||
const {url = "", channel = "push", hideSpaceField = false}: Props = $props()
|
const {url = "", channel = "push", hideSpaceField = false}: Props = $props()
|
||||||
|
|
||||||
const startAlert = () => pushModal(AlertAdd, {url, channel, hideSpaceField})
|
const dmStatus = $derived($dmAlert ? deriveAlertStatus(getAddress($dmAlert.event)) : undefined)
|
||||||
|
|
||||||
const filteredAlerts = $derived(
|
const filteredAlerts = $derived(
|
||||||
url ? $alerts.filter(a => getTagValue("feed", a.tags)?.includes(url)) : $alerts,
|
$alerts.filter(alert => {
|
||||||
|
const feed = getAlertFeed(alert)
|
||||||
|
|
||||||
|
// Skip non-feeds and DM alerts
|
||||||
|
if (!feed || alert === $dmAlert) return false
|
||||||
|
|
||||||
|
// If we have a space url, only match feeds for this space
|
||||||
|
if (url) return findFeed(feed, f => isRelayFeed(f) && f.includes(url))
|
||||||
|
|
||||||
|
return true
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const startAlert = () => pushModal(AlertAdd, {url, channel, hideSpaceField})
|
||||||
|
|
||||||
|
const uncheckDmAlert = async (message: string) => {
|
||||||
|
await sleep(100)
|
||||||
|
|
||||||
|
toggle.checked = false
|
||||||
|
pushToast({theme: "error", message})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onToggle = async () => {
|
||||||
|
if ($dmAlert) {
|
||||||
|
deleteAlert($dmAlert)
|
||||||
|
} else {
|
||||||
|
if ($userInboxRelays.length === 0) {
|
||||||
|
return uncheckDmAlert("Please set up your messaging relays before enabling alerts.")
|
||||||
|
}
|
||||||
|
|
||||||
|
const {error} = await createDmAlert()
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return uncheckDmAlert(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
pushToast({message: "Your alert has been successfully created!"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let toggle: HTMLInputElement
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card2 bg-alt flex flex-col gap-6 shadow-xl">
|
<div class="col-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="card2 bg-alt flex flex-col gap-6 shadow-xl">
|
||||||
<strong class="flex items-center gap-3">
|
<div class="flex items-center justify-between">
|
||||||
<Icon icon="inbox" />
|
<strong class="flex items-center gap-3">
|
||||||
Alerts
|
<Icon icon={Inbox} />
|
||||||
</strong>
|
Alerts
|
||||||
<Button class="btn btn-primary btn-sm" onclick={startAlert}>
|
</strong>
|
||||||
<Icon icon="add-circle" />
|
<Button class="btn btn-primary btn-sm" onclick={startAlert}>
|
||||||
Add Alert
|
<Icon icon={AddCircle} />
|
||||||
</Button>
|
Add Alert
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
{#each filteredAlerts as alert (alert.event.id)}
|
||||||
|
<AlertItem {alert} />
|
||||||
|
{:else}
|
||||||
|
<p class="text-center opacity-75 py-12">Nothing here yet!</p>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="card2 bg-alt flex flex-col gap-4 shadow-xl">
|
||||||
{#each filteredAlerts as alert (alert.event.id)}
|
<div class="flex justify-between">
|
||||||
<AlertItem {alert} />
|
<p>Notify me about new direct messages</p>
|
||||||
{:else}
|
<input
|
||||||
<p class="text-center opacity-75 py-12">Nothing here yet!</p>
|
type="checkbox"
|
||||||
{/each}
|
class="toggle toggle-primary"
|
||||||
|
bind:this={toggle}
|
||||||
|
checked={Boolean($dmAlert)}
|
||||||
|
oninput={onToggle} />
|
||||||
|
</div>
|
||||||
|
{#if $dmStatus}
|
||||||
|
{@const status = getTagValue("status", $dmStatus.tags) || "error"}
|
||||||
|
{#if status !== "ok"}
|
||||||
|
<div class="alert alert-error border border-solid border-error bg-transparent text-error">
|
||||||
|
<p>
|
||||||
|
{getTagValue("message", $dmStatus.tags) ||
|
||||||
|
"The notification server did not respond to your request."}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
import PrimaryNav from "@app/components/PrimaryNav.svelte"
|
import PrimaryNav from "@app/components/PrimaryNav.svelte"
|
||||||
import EmailConfirm from "@app/components/EmailConfirm.svelte"
|
import EmailConfirm from "@app/components/EmailConfirm.svelte"
|
||||||
import PasswordReset from "@app/components/PasswordReset.svelte"
|
import PasswordReset from "@app/components/PasswordReset.svelte"
|
||||||
import {BURROW_URL} from "@app/state"
|
import {BURROW_URL} from "@app/core/state"
|
||||||
import {modals, pushModal} from "@app/modal"
|
import {modals, pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: Snippet
|
children: Snippet
|
||||||
|
|||||||
@@ -1,79 +1,25 @@
|
|||||||
<script module lang="ts">
|
|
||||||
import type {Nip46ResponseWithResult} from "@welshman/signer"
|
|
||||||
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
|
||||||
import {NIP46_PERMS, PLATFORM_URL, PLATFORM_NAME, PLATFORM_LOGO, SIGNER_RELAYS} from "@app/state"
|
|
||||||
|
|
||||||
export class BunkerConnectController {
|
|
||||||
url = $state("")
|
|
||||||
bunker = $state("")
|
|
||||||
loading = $state(false)
|
|
||||||
clientSecret = makeSecret()
|
|
||||||
abortController = new AbortController()
|
|
||||||
broker = new Nip46Broker({clientSecret: this.clientSecret, relays: SIGNER_RELAYS})
|
|
||||||
onNostrConnect: (response: Nip46ResponseWithResult) => void
|
|
||||||
|
|
||||||
constructor({onNostrConnect}: {onNostrConnect: (response: Nip46ResponseWithResult) => void}) {
|
|
||||||
this.onNostrConnect = onNostrConnect
|
|
||||||
}
|
|
||||||
|
|
||||||
async start() {
|
|
||||||
this.url = await this.broker.makeNostrconnectUrl({
|
|
||||||
perms: NIP46_PERMS,
|
|
||||||
url: PLATFORM_URL,
|
|
||||||
name: PLATFORM_NAME,
|
|
||||||
image: PLATFORM_LOGO,
|
|
||||||
})
|
|
||||||
|
|
||||||
let response
|
|
||||||
try {
|
|
||||||
response = await this.broker.waitForNostrconnect(this.url, this.abortController.signal)
|
|
||||||
} catch (errorResponse: any) {
|
|
||||||
if (errorResponse?.error) {
|
|
||||||
pushToast({
|
|
||||||
theme: "error",
|
|
||||||
message: `Received error from signer: ${errorResponse.error}`,
|
|
||||||
})
|
|
||||||
} else if (errorResponse) {
|
|
||||||
console.error(errorResponse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response) {
|
|
||||||
this.loading = true
|
|
||||||
this.onNostrConnect(response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
this.broker.cleanup()
|
|
||||||
this.abortController.abort()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount, onDestroy} from "svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import {slideAndFade} from "@lib/transition"
|
|
||||||
import QRCode from "@app/components/QRCode.svelte"
|
import QRCode from "@app/components/QRCode.svelte"
|
||||||
import {pushToast} from "@app/toast"
|
import type {Nip46Controller} from "@app/util/nip46"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
controller: BunkerConnectController
|
controller: Nip46Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
const {controller}: Props = $props()
|
const {controller}: Props = $props()
|
||||||
|
const {url, loading} = controller
|
||||||
onMount(() => {
|
|
||||||
controller.start()
|
|
||||||
})
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
controller.stop()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if controller.url}
|
{#if $url}
|
||||||
<div class="flex justify-center" out:slideAndFade>
|
{#if $loading}
|
||||||
<QRCode code={controller.url} />
|
<div class="flex justify-center">
|
||||||
</div>
|
<Spinner loading>Establishing connection...</Spinner>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="flex flex-col items-center gap-2">
|
||||||
|
<QRCode code={$url} />
|
||||||
|
<p class="text-sm opacity-75">Scan with your signer to log in, or click to copy.</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,16 +1,32 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {pushModal} from "@app/modal"
|
import {debounce} from "throttle-debounce"
|
||||||
import InfoBunker from "@app/components/InfoBunker.svelte"
|
import Scanner from "@lib/components/Scanner.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Field from "@lib/components/Field.svelte"
|
import Field from "@lib/components/Field.svelte"
|
||||||
|
import CpuBolt from "@assets/icons/cpu-bolt.svg?dataurl"
|
||||||
|
import QrCode from "@assets/icons/qr-code.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import InfoBunker from "@app/components/InfoBunker.svelte"
|
||||||
|
import type {Nip46Controller} from "@app/util/nip46"
|
||||||
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
bunker: string
|
controller: Nip46Controller
|
||||||
loading: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let {loading, bunker = $bindable("")}: Props = $props()
|
const {controller}: Props = $props()
|
||||||
|
const {loading, bunker} = controller
|
||||||
|
|
||||||
|
const toggleScanner = () => {
|
||||||
|
showScanner = !showScanner
|
||||||
|
}
|
||||||
|
|
||||||
|
const onScan = debounce(1000, async (data: string) => {
|
||||||
|
showScanner = false
|
||||||
|
$bunker = data
|
||||||
|
})
|
||||||
|
|
||||||
|
let showScanner = $state(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field>
|
<Field>
|
||||||
@@ -19,8 +35,11 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
<Icon icon="cpu" />
|
<Icon icon={CpuBolt} />
|
||||||
<input disabled={loading} bind:value={bunker} class="grow" placeholder="bunker://" />
|
<input disabled={$loading} bind:value={$bunker} class="grow" placeholder="bunker://" />
|
||||||
|
<Button onclick={toggleScanner}>
|
||||||
|
<Icon icon={QrCode} />
|
||||||
|
</Button>
|
||||||
</label>
|
</label>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet info()}
|
{#snippet info()}
|
||||||
@@ -30,3 +49,6 @@
|
|||||||
</p>
|
</p>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Field>
|
</Field>
|
||||||
|
{#if showScanner}
|
||||||
|
<Scanner onscan={onScan} />
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -8,9 +8,10 @@
|
|||||||
import EventActivity from "@app/components/EventActivity.svelte"
|
import EventActivity from "@app/components/EventActivity.svelte"
|
||||||
import EventActions from "@app/components/EventActions.svelte"
|
import EventActions from "@app/components/EventActions.svelte"
|
||||||
import CalendarEventEdit from "@app/components/CalendarEventEdit.svelte"
|
import CalendarEventEdit from "@app/components/CalendarEventEdit.svelte"
|
||||||
import {publishDelete, publishReaction} from "@app/commands"
|
import {publishDelete, publishReaction, canEnforceNip70} from "@app/core/commands"
|
||||||
import {makeCalendarPath} from "@app/routes"
|
import {makeCalendarPath} from "@app/util/routes"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
import Pen2 from "@assets/icons/pen-2.svg?dataurl"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
url,
|
url,
|
||||||
@@ -22,14 +23,17 @@
|
|||||||
showActivity?: boolean
|
showActivity?: boolean
|
||||||
} = $props()
|
} = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const path = makeCalendarPath(url, event.id)
|
const path = makeCalendarPath(url, event.id)
|
||||||
|
|
||||||
const editEvent = () => pushModal(CalendarEventEdit, {url, event})
|
const editEvent = () => pushModal(CalendarEventEdit, {url, event})
|
||||||
|
|
||||||
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
|
const deleteReaction = async (event: TrustedEvent) =>
|
||||||
|
publishDelete({relays: [url], event, protect: await shouldProtect})
|
||||||
|
|
||||||
const createReaction = (template: EventContent) =>
|
const createReaction = async (template: EventContent) =>
|
||||||
publishReaction({...template, event, relays: [url]})
|
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||||
@@ -44,7 +48,7 @@
|
|||||||
{#if event.pubkey === $pubkey}
|
{#if event.pubkey === $pubkey}
|
||||||
<li>
|
<li>
|
||||||
<Button onclick={editEvent}>
|
<Button onclick={editEvent}>
|
||||||
<Icon size={4} icon="pen" />
|
<Icon size={4} icon={Pen2} />
|
||||||
Edit Event
|
Edit Event
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
import {publishThunk} from "@welshman/app"
|
import {publishThunk} from "@welshman/app"
|
||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
import {daysBetween} from "@lib/util"
|
import {daysBetween} from "@lib/util"
|
||||||
|
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
|
||||||
|
import MapPoint from "@assets/icons/map-point.svg?dataurl"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Field from "@lib/components/Field.svelte"
|
import Field from "@lib/components/Field.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
@@ -13,9 +16,10 @@
|
|||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import DateTimeInput from "@lib/components/DateTimeInput.svelte"
|
import DateTimeInput from "@lib/components/DateTimeInput.svelte"
|
||||||
import EditorContent from "@app/editor/EditorContent.svelte"
|
import EditorContent from "@app/editor/EditorContent.svelte"
|
||||||
import {PROTECTED} from "@app/state"
|
import {PROTECTED} from "@app/core/state"
|
||||||
import {makeEditor} from "@app/editor"
|
import {makeEditor} from "@app/editor"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
import {canEnforceNip70} from "@app/core/commands"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
@@ -32,6 +36,8 @@
|
|||||||
|
|
||||||
const {url, header, initialValues}: Props = $props()
|
const {url, header, initialValues}: Props = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const uploading = writable(false)
|
const uploading = writable(false)
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
@@ -63,19 +69,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ed = await editor
|
const ed = await editor
|
||||||
const event = makeEvent(EVENT_TIME, {
|
const content = ed.getText({blockSeparator: "\n"}).trim()
|
||||||
content: ed.getText({blockSeparator: "\n"}).trim(),
|
const tags = [
|
||||||
tags: [
|
["d", initialValues?.d || randomId()],
|
||||||
["d", initialValues?.d || randomId()],
|
["title", title],
|
||||||
["title", title],
|
["location", location || ""],
|
||||||
["location", location || ""],
|
["start", start.toString()],
|
||||||
["start", start.toString()],
|
["end", end.toString()],
|
||||||
["end", end.toString()],
|
...daysBetween(start, end).map(D => ["D", D.toString()]),
|
||||||
...daysBetween(start, end).map(D => ["D", D.toString()]),
|
...ed.storage.nostr.getEditorTags(),
|
||||||
...ed.storage.nostr.getEditorTags(),
|
]
|
||||||
PROTECTED,
|
|
||||||
],
|
if (await shouldProtect) {
|
||||||
})
|
tags.push(PROTECTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = makeEvent(EVENT_TIME, {content, tags})
|
||||||
|
|
||||||
pushToast({message: "Your event has been saved!"})
|
pushToast({message: "Your event has been saved!"})
|
||||||
publishThunk({event, relays: [url]})
|
publishThunk({event, relays: [url]})
|
||||||
@@ -125,7 +134,7 @@
|
|||||||
{#if $uploading}
|
{#if $uploading}
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="gallery-send" />
|
<Icon icon={GallerySend} />
|
||||||
{/if}
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,14 +162,14 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
<Icon icon="map-point" />
|
<Icon icon={MapPoint} />
|
||||||
<input bind:value={location} class="grow" type="text" />
|
<input bind:value={location} class="grow" type="text" />
|
||||||
</label>
|
</label>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Field>
|
</Field>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back}>
|
<Button class="btn btn-link" onclick={back}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" class="btn btn-primary" disabled={$uploading}>
|
<Button type="submit" class="btn btn-primary" disabled={$uploading}>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
formatTimestampAsTime,
|
formatTimestampAsTime,
|
||||||
} from "@welshman/lib"
|
} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
|
import ClockCircle from "@assets/icons/clock-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
<div class="flex flex-grow flex-wrap justify-between gap-2">
|
<div class="flex flex-grow flex-wrap justify-between gap-2">
|
||||||
<p class="text-xl">{meta.title || meta.name}</p>
|
<p class="text-xl">{meta.title || meta.name}</p>
|
||||||
<div class="flex items-center gap-2 text-sm">
|
<div class="flex items-center gap-2 text-sm">
|
||||||
<Icon icon="clock-circle" size={4} />
|
<Icon icon={ClockCircle} size={4} />
|
||||||
<span class="sm:hidden">{formatTimestampAsDate(start)}</span>
|
<span class="sm:hidden">{formatTimestampAsDate(start)}</span>
|
||||||
{formatTimestampAsTime(start)} — {isSingleDay
|
{formatTimestampAsTime(start)} — {isSingleDay
|
||||||
? formatTimestampAsTime(end)
|
? formatTimestampAsTime(end)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import CalendarEventActions from "@app/components/CalendarEventActions.svelte"
|
import CalendarEventActions from "@app/components/CalendarEventActions.svelte"
|
||||||
import CalendarEventHeader from "@app/components/CalendarEventHeader.svelte"
|
import CalendarEventHeader from "@app/components/CalendarEventHeader.svelte"
|
||||||
import ProfileLink from "@app/components/ProfileLink.svelte"
|
import ProfileLink from "@app/components/ProfileLink.svelte"
|
||||||
import {makeCalendarPath} from "@app/routes"
|
import {makeCalendarPath} from "@app/util/routes"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {fromPairs} from "@welshman/lib"
|
import {fromPairs} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
|
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
||||||
|
import MapPoint from "@assets/icons/map-point.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import ProfileLink from "@app/components/ProfileLink.svelte"
|
import ProfileLink from "@app/components/ProfileLink.svelte"
|
||||||
|
|
||||||
@@ -15,12 +17,12 @@
|
|||||||
|
|
||||||
<div class="flex min-w-0 flex-col gap-1 text-sm opacity-75">
|
<div class="flex min-w-0 flex-col gap-1 text-sm opacity-75">
|
||||||
<span class="flex items-center gap-1">
|
<span class="flex items-center gap-1">
|
||||||
<Icon icon="user-circle" size={4} />
|
<Icon icon={UserCircle} size={4} />
|
||||||
Posted by <ProfileLink pubkey={event.pubkey} {url} />
|
Posted by <ProfileLink pubkey={event.pubkey} {url} />
|
||||||
</span>
|
</span>
|
||||||
{#if meta.location}
|
{#if meta.location}
|
||||||
<span class="flex items-start gap-1">
|
<span class="flex items-start gap-1">
|
||||||
<Icon icon="map-point" class="mt-[2px]" size={4} />
|
<Icon icon={MapPoint} class="mt-[2px]" size={4} />
|
||||||
<span class="break-words">{meta.location}</span>
|
<span class="break-words">{meta.location}</span>
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import {writable} from "svelte/store"
|
import {writable} from "svelte/store"
|
||||||
import type {EventContent} from "@welshman/util"
|
import type {EventContent} from "@welshman/util"
|
||||||
import {isMobile, preventDefault} from "@lib/html"
|
import {isMobile, preventDefault} from "@lib/html"
|
||||||
|
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
|
||||||
|
import Plane from "@assets/icons/plane-2.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import EditorContent from "@app/editor/EditorContent.svelte"
|
import EditorContent from "@app/editor/EditorContent.svelte"
|
||||||
@@ -48,7 +50,7 @@
|
|||||||
{#if $uploading}
|
{#if $uploading}
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="gallery-send" />
|
<Icon icon={GallerySend} />
|
||||||
{/if}
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
<div class="chat-editor flex-grow overflow-hidden">
|
<div class="chat-editor flex-grow overflow-hidden">
|
||||||
@@ -59,6 +61,6 @@
|
|||||||
class="center tooltip tooltip-left absolute right-4 h-10 w-10 min-w-10 rounded-full"
|
class="center tooltip tooltip-left absolute right-4 h-10 w-10 min-w-10 rounded-full"
|
||||||
disabled={$uploading}
|
disabled={$uploading}
|
||||||
onclick={submit}>
|
onclick={submit}>
|
||||||
<Icon icon="plain" />
|
<Icon icon={Plane} />
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {displayProfileByPubkey} from "@welshman/app"
|
import {displayProfileByPubkey} from "@welshman/app"
|
||||||
import {slide} from "@lib/transition"
|
import {slide} from "@lib/transition"
|
||||||
|
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import NoteContent from "@app/components/NoteContent.svelte"
|
import NoteContent from "@app/components/NoteContent.svelte"
|
||||||
@@ -30,6 +31,6 @@
|
|||||||
expandMode="disabled" />
|
expandMode="disabled" />
|
||||||
{/key}
|
{/key}
|
||||||
<Button class="absolute right-2 top-2 cursor-pointer" onclick={clear}>
|
<Button class="absolute right-2 top-2 cursor-pointer" onclick={clear}>
|
||||||
<Icon icon="close-circle" />
|
<Icon icon={CloseCircle} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,19 +5,20 @@
|
|||||||
import {isMobile} from "@lib/html"
|
import {isMobile} from "@lib/html"
|
||||||
import TapTarget from "@lib/components/TapTarget.svelte"
|
import TapTarget from "@lib/components/TapTarget.svelte"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import Avatar from "@lib/components/Avatar.svelte"
|
||||||
|
import Reply from "@assets/icons/reply-2.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Content from "@app/components/Content.svelte"
|
import Content from "@app/components/Content.svelte"
|
||||||
import ThunkStatus from "@app/components/ThunkStatus.svelte"
|
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||||
import ChannelMessageZapButton from "@app/components/ChannelMessageZapButton.svelte"
|
import ChannelMessageZapButton from "@app/components/ChannelMessageZapButton.svelte"
|
||||||
import ChannelMessageEmojiButton from "@app/components/ChannelMessageEmojiButton.svelte"
|
import ChannelMessageEmojiButton from "@app/components/ChannelMessageEmojiButton.svelte"
|
||||||
import ChannelMessageMenuButton from "@app/components/ChannelMessageMenuButton.svelte"
|
import ChannelMessageMenuButton from "@app/components/ChannelMessageMenuButton.svelte"
|
||||||
import ChannelMessageMenuMobile from "@app/components/ChannelMessageMenuMobile.svelte"
|
import ChannelMessageMenuMobile from "@app/components/ChannelMessageMenuMobile.svelte"
|
||||||
import {colors, ENABLE_ZAPS} from "@app/state"
|
import {colors, ENABLE_ZAPS} from "@app/core/state"
|
||||||
import {publishDelete, publishReaction} from "@app/commands"
|
import {publishDelete, publishReaction, canEnforceNip70} from "@app/core/commands"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
url: string
|
url: string
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
const {url, event, replyTo = undefined, showPubkey = false, inert = false}: Props = $props()
|
const {url, event, replyTo = undefined, showPubkey = false, inert = false}: Props = $props()
|
||||||
|
|
||||||
const thunk = $thunks[event.id]
|
const thunk = $thunks[event.id]
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
const today = formatTimestampAsDate(now())
|
const today = formatTimestampAsDate(now())
|
||||||
const profile = deriveProfile(event.pubkey, [url])
|
const profile = deriveProfile(event.pubkey, [url])
|
||||||
const profileDisplay = deriveProfileDisplay(event.pubkey, [url])
|
const profileDisplay = deriveProfileDisplay(event.pubkey, [url])
|
||||||
@@ -41,10 +43,11 @@
|
|||||||
|
|
||||||
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey, url})
|
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey, url})
|
||||||
|
|
||||||
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
|
const deleteReaction = async (event: TrustedEvent) =>
|
||||||
|
publishDelete({relays: [url], event, protect: await shouldProtect})
|
||||||
|
|
||||||
const createReaction = (template: EventContent) =>
|
const createReaction = async (template: EventContent) =>
|
||||||
publishReaction({...template, event, relays: [url]})
|
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<TapTarget
|
<TapTarget
|
||||||
@@ -78,12 +81,12 @@
|
|||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<Content minimalQuote {event} {url} />
|
<Content minimalQuote {event} {url} />
|
||||||
{#if thunk}
|
{#if thunk}
|
||||||
<ThunkStatus {thunk} class="mt-2" />
|
<ThunkFailure showToastOnRetry {thunk} class="mt-2" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-2 ml-10 mt-1">
|
<div class="row-2 ml-10 mt-1 pl-1">
|
||||||
<ReactionSummary
|
<ReactionSummary
|
||||||
{url}
|
{url}
|
||||||
{event}
|
{event}
|
||||||
@@ -101,7 +104,7 @@
|
|||||||
<ChannelMessageEmojiButton {url} {event} />
|
<ChannelMessageEmojiButton {url} {event} />
|
||||||
{#if replyTo}
|
{#if replyTo}
|
||||||
<Button class="btn join-item btn-xs" onclick={reply}>
|
<Button class="btn join-item btn-xs" onclick={reply}>
|
||||||
<Icon icon="reply" size={4} />
|
<Icon icon={Reply} size={4} />
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
<ChannelMessageMenuButton {url} {event} />
|
<ChannelMessageMenuButton {url} {event} />
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {NativeEmoji} from "emoji-picker-element/shared"
|
import type {NativeEmoji} from "emoji-picker-element/shared"
|
||||||
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
||||||
|
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import {publishReaction} from "@app/commands"
|
import {publishReaction, canEnforceNip70} from "@app/core/commands"
|
||||||
|
|
||||||
const {url, event} = $props()
|
const {url, event} = $props()
|
||||||
|
|
||||||
const onEmoji = (emoji: NativeEmoji) =>
|
const shouldProtect = canEnforceNip70(url)
|
||||||
publishReaction({event, relays: [url], content: emoji.unicode})
|
|
||||||
|
const onEmoji = async (emoji: NativeEmoji) =>
|
||||||
|
publishReaction({
|
||||||
|
event,
|
||||||
|
relays: [url],
|
||||||
|
content: emoji.unicode,
|
||||||
|
protect: await shouldProtect,
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<EmojiButton {onEmoji} class="btn join-item btn-xs">
|
<EmojiButton {onEmoji} class="btn join-item btn-xs">
|
||||||
<Icon icon="smile-circle" size={4} />
|
<Icon icon={SmileCircle} size={4} />
|
||||||
</EmojiButton>
|
</EmojiButton>
|
||||||
|
|||||||
@@ -5,7 +5,10 @@
|
|||||||
import EventInfo from "@app/components/EventInfo.svelte"
|
import EventInfo from "@app/components/EventInfo.svelte"
|
||||||
import EventReport from "@app/components/EventReport.svelte"
|
import EventReport from "@app/components/EventReport.svelte"
|
||||||
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
|
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
import Code2 from "@assets/icons/code-2.svg?dataurl"
|
||||||
|
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
|
||||||
|
import Danger from "@assets/icons/danger.svg?dataurl"
|
||||||
|
|
||||||
const {url, event, onClick} = $props()
|
const {url, event, onClick} = $props()
|
||||||
|
|
||||||
@@ -28,21 +31,21 @@
|
|||||||
<ul class="menu whitespace-nowrap rounded-box bg-base-100 p-2 shadow-xl">
|
<ul class="menu whitespace-nowrap rounded-box bg-base-100 p-2 shadow-xl">
|
||||||
<li>
|
<li>
|
||||||
<Button onclick={showInfo}>
|
<Button onclick={showInfo}>
|
||||||
<Icon size={4} icon="code-2" />
|
<Icon size={4} icon={Code2} />
|
||||||
Message Details
|
Message Details
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
{#if event.pubkey === $pubkey}
|
{#if event.pubkey === $pubkey}
|
||||||
<li>
|
<li>
|
||||||
<Button onclick={showDelete} class="text-error">
|
<Button onclick={showDelete} class="text-error">
|
||||||
<Icon size={4} icon="trash-bin-2" />
|
<Icon size={4} icon={TrashBin2} />
|
||||||
Delete Message
|
Delete Message
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
{:else}
|
{:else}
|
||||||
<li>
|
<li>
|
||||||
<Button class="text-error" onclick={report}>
|
<Button class="text-error" onclick={report}>
|
||||||
<Icon size={4} icon="danger" />
|
<Icon size={4} icon={Danger} />
|
||||||
Report Content
|
Report Content
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {type Instance} from "tippy.js"
|
import {type Instance} from "tippy.js"
|
||||||
import {between} from "@welshman/lib"
|
import {between} from "@welshman/lib"
|
||||||
|
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Tippy from "@lib/components/Tippy.svelte"
|
import Tippy from "@lib/components/Tippy.svelte"
|
||||||
@@ -29,7 +30,7 @@
|
|||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<Button class="btn join-item btn-xs" onclick={open}>
|
<Button class="btn join-item btn-xs" onclick={open}>
|
||||||
<Icon icon="menu-dots" size={4} />
|
<Icon icon={MenuDots} size={4} />
|
||||||
</Button>
|
</Button>
|
||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
|
|||||||
@@ -8,9 +8,14 @@
|
|||||||
import ZapButton from "@app/components/ZapButton.svelte"
|
import ZapButton from "@app/components/ZapButton.svelte"
|
||||||
import EventInfo from "@app/components/EventInfo.svelte"
|
import EventInfo from "@app/components/EventInfo.svelte"
|
||||||
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
|
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
|
||||||
import {ENABLE_ZAPS} from "@app/state"
|
import {ENABLE_ZAPS} from "@app/core/state"
|
||||||
import {publishReaction} from "@app/commands"
|
import {publishReaction, canEnforceNip70} from "@app/core/commands"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
|
||||||
|
import Bolt from "@assets/icons/bolt.svg?dataurl"
|
||||||
|
import Reply from "@assets/icons/reply-2.svg?dataurl"
|
||||||
|
import Code2 from "@assets/icons/code-2.svg?dataurl"
|
||||||
|
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
@@ -20,9 +25,16 @@
|
|||||||
|
|
||||||
const {url, event, reply}: Props = $props()
|
const {url, event, reply}: Props = $props()
|
||||||
|
|
||||||
const onEmoji = ((event: TrustedEvent, url: string, emoji: NativeEmoji) => {
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
|
const onEmoji = (async (event: TrustedEvent, url: string, emoji: NativeEmoji) => {
|
||||||
history.back()
|
history.back()
|
||||||
publishReaction({event, relays: [url], content: emoji.unicode})
|
publishReaction({
|
||||||
|
event,
|
||||||
|
relays: [url],
|
||||||
|
content: emoji.unicode,
|
||||||
|
protect: await shouldProtect,
|
||||||
|
})
|
||||||
}).bind(undefined, event, url)
|
}).bind(undefined, event, url)
|
||||||
|
|
||||||
const showEmojiPicker = () => pushModal(EmojiPicker, {onClick: onEmoji}, {replaceState: true})
|
const showEmojiPicker = () => pushModal(EmojiPicker, {onClick: onEmoji}, {replaceState: true})
|
||||||
@@ -39,26 +51,26 @@
|
|||||||
|
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<Button class="btn btn-primary w-full" onclick={showEmojiPicker}>
|
<Button class="btn btn-primary w-full" onclick={showEmojiPicker}>
|
||||||
<Icon size={4} icon="smile-circle" />
|
<Icon size={4} icon={SmileCircle} />
|
||||||
Send Reaction
|
Send Reaction
|
||||||
</Button>
|
</Button>
|
||||||
{#if ENABLE_ZAPS}
|
{#if ENABLE_ZAPS}
|
||||||
<ZapButton replaceState {url} {event} class="btn btn-secondary w-full">
|
<ZapButton replaceState {url} {event} class="btn btn-secondary w-full">
|
||||||
<Icon size={4} icon="bolt" />
|
<Icon size={4} icon={Bolt} />
|
||||||
Send Zap
|
Send Zap
|
||||||
</ZapButton>
|
</ZapButton>
|
||||||
{/if}
|
{/if}
|
||||||
<Button class="btn btn-neutral w-full" onclick={sendReply}>
|
<Button class="btn btn-neutral w-full" onclick={sendReply}>
|
||||||
<Icon size={4} icon="reply" />
|
<Icon size={4} icon={Reply} />
|
||||||
Send Reply
|
Send Reply
|
||||||
</Button>
|
</Button>
|
||||||
<Button class="btn btn-neutral" onclick={showInfo}>
|
<Button class="btn btn-neutral" onclick={showInfo}>
|
||||||
<Icon size={4} icon="code-2" />
|
<Icon size={4} icon={Code2} />
|
||||||
Message Details
|
Message Details
|
||||||
</Button>
|
</Button>
|
||||||
{#if event.pubkey === $pubkey}
|
{#if event.pubkey === $pubkey}
|
||||||
<Button class="btn btn-neutral text-error" onclick={showDelete}>
|
<Button class="btn btn-neutral text-error" onclick={showDelete}>
|
||||||
<Icon size={4} icon="trash-bin-2" />
|
<Icon size={4} icon={TrashBin2} />
|
||||||
Delete Message
|
Delete Message
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Bolt from "@assets/icons/bolt.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import ZapButton from "@app/components/ZapButton.svelte"
|
import ZapButton from "@app/components/ZapButton.svelte"
|
||||||
|
|
||||||
@@ -6,5 +7,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ZapButton {url} {event} class="btn join-item btn-xs">
|
<ZapButton {url} {event} class="btn join-item btn-xs">
|
||||||
<Icon icon="bolt" size={4} />
|
<Icon icon={Bolt} size={4} />
|
||||||
</ZapButton>
|
</ZapButton>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {channelsById, makeChannelId} from "@app/state"
|
import {channelsById, makeChannelId} from "@app/core/state"
|
||||||
|
|
||||||
const {url, room} = $props()
|
const {url, room} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -21,15 +21,17 @@
|
|||||||
getTags,
|
getTags,
|
||||||
DIRECT_MESSAGE,
|
DIRECT_MESSAGE,
|
||||||
DIRECT_MESSAGE_FILE,
|
DIRECT_MESSAGE_FILE,
|
||||||
INBOX_RELAYS,
|
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
pubkey,
|
pubkey,
|
||||||
tagPubkey,
|
tagPubkey,
|
||||||
sendWrapped,
|
sendWrapped,
|
||||||
loadUsingOutbox,
|
mergeThunks,
|
||||||
|
loadInboxRelaySelections,
|
||||||
inboxRelaySelectionsByPubkey,
|
inboxRelaySelectionsByPubkey,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
|
import type {AbstractThunk} from "@welshman/app"
|
||||||
|
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
@@ -45,15 +47,17 @@
|
|||||||
import ChatMessage from "@app/components/ChatMessage.svelte"
|
import ChatMessage from "@app/components/ChatMessage.svelte"
|
||||||
import ChatCompose from "@app/components/ChatCompose.svelte"
|
import ChatCompose from "@app/components/ChatCompose.svelte"
|
||||||
import ChatComposeParent from "@app/components/ChatComposeParent.svelte"
|
import ChatComposeParent from "@app/components/ChatComposeParent.svelte"
|
||||||
|
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||||
import {
|
import {
|
||||||
INDEXER_RELAYS,
|
INDEXER_RELAYS,
|
||||||
userSettingValues,
|
userSettingsValues,
|
||||||
deriveChat,
|
deriveChat,
|
||||||
splitChatId,
|
splitChatId,
|
||||||
PLATFORM_NAME,
|
PLATFORM_NAME,
|
||||||
} from "@app/state"
|
} from "@app/core/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
import {prependParent} from "@app/commands"
|
import {prependParent} from "@app/core/commands"
|
||||||
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string
|
id: string
|
||||||
@@ -122,12 +126,23 @@
|
|||||||
|
|
||||||
// Split the message into multiple pieces so that we can use kind 15 to send images per nip 17
|
// Split the message into multiple pieces so that we can use kind 15 to send images per nip 17
|
||||||
// Sleep 1 second between each one to make sure timestamps are distinct
|
// Sleep 1 second between each one to make sure timestamps are distinct
|
||||||
|
const thunks: AbstractThunk[] = []
|
||||||
for (let i = 0; i < templates.length; i++) {
|
for (let i = 0; i < templates.length; i++) {
|
||||||
const template = templates[i]
|
const template = templates[i]
|
||||||
|
|
||||||
await sendWrapped({pubkeys, template, delay: $userSettingValues.send_delay + ms(i)})
|
thunks.push(
|
||||||
|
await sendWrapped({pubkeys, template, delay: $userSettingsValues.send_delay + ms(i)}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushToast({
|
||||||
|
timeout: 30_000,
|
||||||
|
children: {
|
||||||
|
component: ThunkToast,
|
||||||
|
props: {thunk: mergeThunks(thunks)},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
clearParent()
|
clearParent()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +171,7 @@
|
|||||||
id,
|
id,
|
||||||
type: "note",
|
type: "note",
|
||||||
value: event,
|
value: event,
|
||||||
showPubkey: created_at - previousCreatedAt > int(15, MINUTE) || previousPubkey !== pubkey,
|
showPubkey: created_at - previousCreatedAt > int(2, MINUTE) || previousPubkey !== pubkey,
|
||||||
})
|
})
|
||||||
|
|
||||||
previousDate = date
|
previousDate = date
|
||||||
@@ -168,13 +183,8 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Don't use loadInboxRelaySelection because we want to force reload
|
|
||||||
for (const pubkey of others) {
|
for (const pubkey of others) {
|
||||||
loadUsingOutbox({
|
loadInboxRelaySelections(pubkey, INDEXER_RELAYS, true)
|
||||||
pubkey,
|
|
||||||
kind: INBOX_RELAYS,
|
|
||||||
relays: INDEXER_RELAYS,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const observer = new ResizeObserver(() => {
|
const observer = new ResizeObserver(() => {
|
||||||
@@ -241,7 +251,7 @@
|
|||||||
<div
|
<div
|
||||||
class="row-2 badge badge-error badge-lg tooltip tooltip-left cursor-pointer"
|
class="row-2 badge badge-error badge-lg tooltip tooltip-left cursor-pointer"
|
||||||
data-tip="{count} {label} not configured.">
|
data-tip="{count} {label} not configured.">
|
||||||
<Icon icon="danger" />
|
<Icon icon={Danger} />
|
||||||
{count}
|
{count}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -255,7 +265,7 @@
|
|||||||
<div class="py-12">
|
<div class="py-12">
|
||||||
<div class="card2 col-2 m-auto max-w-md items-center text-center">
|
<div class="card2 col-2 m-auto max-w-md items-center text-center">
|
||||||
<p class="row-2 text-lg text-error">
|
<p class="row-2 text-lg text-error">
|
||||||
<Icon icon="danger" />
|
<Icon icon={Danger} />
|
||||||
Your inbox is not configured.
|
Your inbox is not configured.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -268,7 +278,7 @@
|
|||||||
<div class="py-12">
|
<div class="py-12">
|
||||||
<div class="card2 col-2 m-auto max-w-md items-center text-center">
|
<div class="card2 col-2 m-auto max-w-md items-center text-center">
|
||||||
<p class="row-2 text-lg text-error">
|
<p class="row-2 text-lg text-error">
|
||||||
<Icon icon="danger" />
|
<Icon icon={Danger} />
|
||||||
{missingInboxes.length}
|
{missingInboxes.length}
|
||||||
{missingInboxes.length > 1 ? "inboxes are" : "inbox is"} not configured.
|
{missingInboxes.length > 1 ? "inboxes are" : "inbox is"} not configured.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import {writable} from "svelte/store"
|
import {writable} from "svelte/store"
|
||||||
import type {EventContent} from "@welshman/util"
|
import type {EventContent} from "@welshman/util"
|
||||||
import {isMobile, preventDefault} from "@lib/html"
|
import {isMobile, preventDefault} from "@lib/html"
|
||||||
|
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
|
||||||
|
import Plane from "@assets/icons/plane-2.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import EditorContent from "@app/editor/EditorContent.svelte"
|
import EditorContent from "@app/editor/EditorContent.svelte"
|
||||||
@@ -54,7 +56,7 @@
|
|||||||
{#if $uploading}
|
{#if $uploading}
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="gallery-send" />
|
<Icon icon={GallerySend} />
|
||||||
{/if}
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
<div class="chat-editor flex-grow overflow-hidden">
|
<div class="chat-editor flex-grow overflow-hidden">
|
||||||
@@ -65,6 +67,6 @@
|
|||||||
class="center tooltip tooltip-left absolute right-4 h-10 w-10 min-w-10 rounded-full"
|
class="center tooltip tooltip-left absolute right-4 h-10 w-10 min-w-10 rounded-full"
|
||||||
disabled={$uploading}
|
disabled={$uploading}
|
||||||
onclick={submit}>
|
onclick={submit}>
|
||||||
<Icon icon="plain" />
|
<Icon icon={Plane} />
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {displayProfileByPubkey} from "@welshman/app"
|
import {displayProfileByPubkey} from "@welshman/app"
|
||||||
import {slide} from "@lib/transition"
|
import {slide} from "@lib/transition"
|
||||||
|
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import NoteContent from "@app/components/NoteContent.svelte"
|
import NoteContent from "@app/components/NoteContent.svelte"
|
||||||
@@ -30,6 +31,6 @@
|
|||||||
expandMode="disabled" />
|
expandMode="disabled" />
|
||||||
{/key}
|
{/key}
|
||||||
<Button class="absolute right-2 top-2 cursor-pointer" onclick={clear}>
|
<Button class="absolute right-2 top-2 cursor-pointer" onclick={clear}>
|
||||||
<Icon icon="close-circle" />
|
<Icon icon={CloseCircle} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {goto} from "$app/navigation"
|
import {goto} from "$app/navigation"
|
||||||
import {WRAP} from "@welshman/util"
|
|
||||||
import {repository} from "@welshman/app"
|
|
||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
|
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import {canDecrypt, PLATFORM_NAME, ensureUnwrapped} from "@app/state"
|
import {PLATFORM_NAME} from "@app/core/state"
|
||||||
import {clearModals} from "@app/modal"
|
import {enableGiftWraps} from "@app/core/commands"
|
||||||
|
import {clearModals} from "@app/util/modal"
|
||||||
|
|
||||||
const {next} = $props()
|
const {next} = $props()
|
||||||
|
|
||||||
@@ -18,12 +19,7 @@
|
|||||||
let loading = $state(false)
|
let loading = $state(false)
|
||||||
|
|
||||||
const enableChat = async () => {
|
const enableChat = async () => {
|
||||||
canDecrypt.set(true)
|
enableGiftWraps()
|
||||||
|
|
||||||
for (const event of repository.query([{kinds: [WRAP]}])) {
|
|
||||||
ensureUnwrapped(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
clearModals()
|
clearModals()
|
||||||
goto(nextUrl)
|
goto(nextUrl)
|
||||||
}
|
}
|
||||||
@@ -60,12 +56,12 @@
|
|||||||
</p>
|
</p>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back}>
|
<Button class="btn btn-link" onclick={back}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||||
<Spinner {loading}>Enable Messages</Spinner>
|
<Spinner {loading}>Enable Messages</Spinner>
|
||||||
<Icon icon="alt-arrow-right" />
|
<Icon icon={AltArrowRight} />
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
import ProfileName from "@app/components/ProfileName.svelte"
|
import ProfileName from "@app/components/ProfileName.svelte"
|
||||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
import ProfileCircles from "@app/components/ProfileCircles.svelte"
|
import ProfileCircles from "@app/components/ProfileCircles.svelte"
|
||||||
import {makeChatPath} from "@app/routes"
|
import {makeChatPath} from "@app/util/routes"
|
||||||
import {notifications} from "@app/notifications"
|
import {notifications} from "@app/util/notifications"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string
|
id: string
|
||||||
@@ -59,6 +59,11 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<p class="overflow-hidden text-ellipsis whitespace-nowrap text-sm">
|
<p class="overflow-hidden text-ellipsis whitespace-nowrap text-sm">
|
||||||
|
<span class="opacity-50">
|
||||||
|
{#if props.messages[0].pubkey === $pubkey}
|
||||||
|
You:
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
{props.messages[0].content}
|
{props.messages[0].content}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import {waitForThunkCompletion} from "@welshman/app"
|
||||||
|
import ChatSquare from "@assets/icons/chat-square.svg?dataurl"
|
||||||
|
import Check from "@assets/icons/check.svg?dataurl"
|
||||||
|
import Bell from "@assets/icons/bell.svg?dataurl"
|
||||||
|
import BellOff from "@assets/icons/bell-off.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import ChatStart from "@app/components/ChatStart.svelte"
|
import ChatStart from "@app/components/ChatStart.svelte"
|
||||||
import {setChecked} from "@app/notifications"
|
import {setChecked} from "@app/util/notifications"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
import {pushToast} from "@app/util/toast"
|
||||||
|
import {dmAlert, userInboxRelays} from "@app/core/state"
|
||||||
|
import {deleteAlert, createDmAlert} from "@app/core/commands"
|
||||||
|
|
||||||
const startChat = () => pushModal(ChatStart, {}, {replaceState: true})
|
const startChat = () => pushModal(ChatStart, {}, {replaceState: true})
|
||||||
|
|
||||||
@@ -11,15 +20,64 @@
|
|||||||
setChecked("/chat/*")
|
setChecked("/chat/*")
|
||||||
history.back()
|
history.back()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enableAlerts = async () => {
|
||||||
|
if ($userInboxRelays.length === 0) {
|
||||||
|
return pushToast({
|
||||||
|
theme: "error",
|
||||||
|
message: "Please set up your messaging relays before enabling alerts.",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
enablingAlert = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {error} = await createDmAlert()
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return pushToast({theme: "error", message: error})
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
enablingAlert = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const disableAlerts = async () => {
|
||||||
|
disablingAlert = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await waitForThunkCompletion(deleteAlert($dmAlert!))
|
||||||
|
} finally {
|
||||||
|
disablingAlert = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let enablingAlert = $state(false)
|
||||||
|
let disablingAlert = $state(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<Button class="btn btn-primary" onclick={startChat}>
|
<Button class="btn btn-primary" onclick={startChat}>
|
||||||
<Icon size={4} icon="add-circle" />
|
<Icon size={5} icon={ChatSquare} />
|
||||||
Start chat
|
Start chat
|
||||||
</Button>
|
</Button>
|
||||||
<Button class="btn btn-neutral" onclick={markAsRead}>
|
<Button class="btn btn-neutral" onclick={markAsRead}>
|
||||||
<Icon size={4} icon="check-circle" />
|
<Icon size={5} icon={Check} />
|
||||||
Mark all read
|
Mark all read
|
||||||
</Button>
|
</Button>
|
||||||
|
{#if (!enablingAlert && $dmAlert) || disablingAlert}
|
||||||
|
<Button class="btn btn-neutral" onclick={disableAlerts} disabled={disablingAlert}>
|
||||||
|
{#if !disablingAlert}
|
||||||
|
<Icon size={4} icon={BellOff} />
|
||||||
|
{/if}
|
||||||
|
<Spinner loading={disablingAlert}>Disable alerts</Spinner>
|
||||||
|
</Button>
|
||||||
|
{:else}
|
||||||
|
<Button class="btn btn-neutral" onclick={enableAlerts} disabled={enablingAlert}>
|
||||||
|
{#if !enablingAlert}
|
||||||
|
<Icon size={4} icon={Bell} />
|
||||||
|
{/if}
|
||||||
|
<Spinner loading={enablingAlert}>Enable alerts</Spinner>
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {thunks, pubkey, deriveProfile, deriveProfileDisplay, sendWrapped} from "@welshman/app"
|
import {thunks, pubkey, deriveProfile, deriveProfileDisplay, sendWrapped} from "@welshman/app"
|
||||||
import {isMobile} from "@lib/html"
|
import {isMobile} from "@lib/html"
|
||||||
|
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Tippy from "@lib/components/Tippy.svelte"
|
import Tippy from "@lib/components/Tippy.svelte"
|
||||||
@@ -11,13 +12,13 @@
|
|||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import Avatar from "@lib/components/Avatar.svelte"
|
||||||
import Content from "@app/components/Content.svelte"
|
import Content from "@app/components/Content.svelte"
|
||||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||||
import ThunkStatus from "@app/components/ThunkStatus.svelte"
|
import ThunkFailure from "@app/components/ThunkFailure.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
import ChatMessageMenu from "@app/components/ChatMessageMenu.svelte"
|
import ChatMessageMenu from "@app/components/ChatMessageMenu.svelte"
|
||||||
import ChatMessageMenuMobile from "@app/components/ChatMessageMenuMobile.svelte"
|
import ChatMessageMenuMobile from "@app/components/ChatMessageMenuMobile.svelte"
|
||||||
import {colors} from "@app/state"
|
import {colors} from "@app/core/state"
|
||||||
import {makeDelete, makeReaction} from "@app/commands"
|
import {makeDelete, makeReaction} from "@app/core/commands"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
event: TrustedEvent
|
event: TrustedEvent
|
||||||
@@ -37,10 +38,10 @@
|
|||||||
const reply = () => replyTo(event)
|
const reply = () => replyTo(event)
|
||||||
|
|
||||||
const deleteReaction = (event: TrustedEvent) =>
|
const deleteReaction = (event: TrustedEvent) =>
|
||||||
sendWrapped({template: makeDelete({event}), pubkeys})
|
sendWrapped({template: makeDelete({event, protect: false}), pubkeys})
|
||||||
|
|
||||||
const createReaction = (template: EventContent) =>
|
const createReaction = (template: EventContent) =>
|
||||||
sendWrapped({template: makeReaction({event, ...template}), pubkeys})
|
sendWrapped({template: makeReaction({event, protect: false, ...template}), pubkeys})
|
||||||
|
|
||||||
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey})
|
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey})
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if thunk}
|
{#if thunk}
|
||||||
<ThunkStatus {thunk} class="mt-1" />
|
<ThunkFailure showToastOnRetry {thunk} class="mt-1" />
|
||||||
{/if}
|
{/if}
|
||||||
<div
|
<div
|
||||||
data-event={event.id}
|
data-event={event.id}
|
||||||
@@ -87,7 +88,7 @@
|
|||||||
class="opacity-0 transition-all"
|
class="opacity-0 transition-all"
|
||||||
class:group-hover:opacity-100={!isMobile}
|
class:group-hover:opacity-100={!isMobile}
|
||||||
onclick={togglePopover}>
|
onclick={togglePopover}>
|
||||||
<Icon icon="menu-dots" size={4} />
|
<Icon icon={MenuDots} size={4} />
|
||||||
</button>
|
</button>
|
||||||
</Tippy>
|
</Tippy>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
import type {NativeEmoji} from "emoji-picker-element/shared"
|
import type {NativeEmoji} from "emoji-picker-element/shared"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {sendWrapped} from "@welshman/app"
|
import {sendWrapped} from "@welshman/app"
|
||||||
|
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
||||||
import {makeReaction} from "@app/commands"
|
import {makeReaction} from "@app/core/commands"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
event: TrustedEvent
|
event: TrustedEvent
|
||||||
@@ -14,9 +15,9 @@
|
|||||||
const {event, pubkeys}: Props = $props()
|
const {event, pubkeys}: Props = $props()
|
||||||
|
|
||||||
const onEmoji = (emoji: NativeEmoji) =>
|
const onEmoji = (emoji: NativeEmoji) =>
|
||||||
sendWrapped({template: makeReaction({event, content: emoji.unicode}), pubkeys})
|
sendWrapped({template: makeReaction({event, content: emoji.unicode, protect: false}), pubkeys})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<EmojiButton {onEmoji} class="btn join-item btn-xs">
|
<EmojiButton {onEmoji} class="btn join-item btn-xs">
|
||||||
<Icon icon="smile-circle" size={4} />
|
<Icon icon={SmileCircle} size={4} />
|
||||||
</EmojiButton>
|
</EmojiButton>
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ChatMessageEmojiButton from "@app/components/ChatMessageEmojiButton.svelte"
|
import ChatMessageEmojiButton from "@app/components/ChatMessageEmojiButton.svelte"
|
||||||
import EventInfo from "@app/components/EventInfo.svelte"
|
import EventInfo from "@app/components/EventInfo.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
import Reply from "@assets/icons/reply-2.svg?dataurl"
|
||||||
|
import Code2 from "@assets/icons/code-2.svg?dataurl"
|
||||||
|
|
||||||
const {event, pubkeys, popover, replyTo} = $props()
|
const {event, pubkeys, popover, replyTo} = $props()
|
||||||
|
|
||||||
@@ -19,10 +21,10 @@
|
|||||||
<ChatMessageEmojiButton {event} {pubkeys} />
|
<ChatMessageEmojiButton {event} {pubkeys} />
|
||||||
{#if replyTo}
|
{#if replyTo}
|
||||||
<Button class="btn join-item btn-xs" onclick={reply}>
|
<Button class="btn join-item btn-xs" onclick={reply}>
|
||||||
<Icon size={4} icon="reply" />
|
<Icon size={4} icon={Reply} />
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
<Button class="btn join-item btn-xs" onclick={showInfo}>
|
<Button class="btn join-item btn-xs" onclick={showInfo}>
|
||||||
<Icon size={4} icon="code-2" />
|
<Icon size={4} icon={Code2} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,9 +6,13 @@
|
|||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import EmojiPicker from "@lib/components/EmojiPicker.svelte"
|
import EmojiPicker from "@lib/components/EmojiPicker.svelte"
|
||||||
import EventInfo from "@app/components/EventInfo.svelte"
|
import EventInfo from "@app/components/EventInfo.svelte"
|
||||||
import {makeReaction} from "@app/commands"
|
import {makeReaction} from "@app/core/commands"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
import {clip} from "@app/toast"
|
import {clip} from "@app/util/toast"
|
||||||
|
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
|
||||||
|
import Reply from "@assets/icons/reply-2.svg?dataurl"
|
||||||
|
import Copy from "@assets/icons/copy.svg?dataurl"
|
||||||
|
import Code2 from "@assets/icons/code-2.svg?dataurl"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pubkeys: string[]
|
pubkeys: string[]
|
||||||
@@ -20,7 +24,7 @@
|
|||||||
|
|
||||||
const onEmoji = ((event: TrustedEvent, pubkeys: string[], emoji: NativeEmoji) => {
|
const onEmoji = ((event: TrustedEvent, pubkeys: string[], emoji: NativeEmoji) => {
|
||||||
history.back()
|
history.back()
|
||||||
sendWrapped({template: makeReaction({event, content: emoji.unicode}), pubkeys})
|
sendWrapped({template: makeReaction({event, content: emoji.unicode, protect: false}), pubkeys})
|
||||||
}).bind(undefined, event, pubkeys)
|
}).bind(undefined, event, pubkeys)
|
||||||
|
|
||||||
const showEmojiPicker = () => pushModal(EmojiPicker, {onClick: onEmoji}, {replaceState: true})
|
const showEmojiPicker = () => pushModal(EmojiPicker, {onClick: onEmoji}, {replaceState: true})
|
||||||
@@ -40,19 +44,19 @@
|
|||||||
|
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<Button class="btn btn-primary w-full" onclick={showEmojiPicker}>
|
<Button class="btn btn-primary w-full" onclick={showEmojiPicker}>
|
||||||
<Icon size={4} icon="smile-circle" />
|
<Icon size={4} icon={SmileCircle} />
|
||||||
Send Reaction
|
Send Reaction
|
||||||
</Button>
|
</Button>
|
||||||
<Button class="btn btn-neutral w-full" onclick={sendReply}>
|
<Button class="btn btn-neutral w-full" onclick={sendReply}>
|
||||||
<Icon size={4} icon="reply" />
|
<Icon size={4} icon={Reply} />
|
||||||
Send Reply
|
Send Reply
|
||||||
</Button>
|
</Button>
|
||||||
<Button class="btn btn-neutral w-full" onclick={copyText}>
|
<Button class="btn btn-neutral w-full" onclick={copyText}>
|
||||||
<Icon size={4} icon="copy" />
|
<Icon size={4} icon={Copy} />
|
||||||
Copy Text
|
Copy Text
|
||||||
</Button>
|
</Button>
|
||||||
<Button class="btn btn-neutral" onclick={showInfo}>
|
<Button class="btn btn-neutral" onclick={showInfo}>
|
||||||
<Icon size={4} icon="code-2" />
|
<Icon size={4} icon={Code2} />
|
||||||
Message Details
|
Message Details
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,11 +9,13 @@
|
|||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
import Field from "@lib/components/Field.svelte"
|
import Field from "@lib/components/Field.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
|
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import ProfileMultiSelect from "@app/components/ProfileMultiSelect.svelte"
|
import ProfileMultiSelect from "@app/components/ProfileMultiSelect.svelte"
|
||||||
import {makeChatPath} from "@app/routes"
|
import {makeChatPath} from "@app/util/routes"
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
@@ -67,12 +69,12 @@
|
|||||||
</Field>
|
</Field>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back}>
|
<Button class="btn btn-link" onclick={back}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" class="btn btn-primary" disabled={pubkeys.length === 0}>
|
<Button type="submit" class="btn btn-primary" disabled={pubkeys.length === 0}>
|
||||||
Create Chat
|
Create Chat
|
||||||
<Icon icon="alt-arrow-right" />
|
<Icon icon={AltArrowRight} />
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
||||||
import EventActivity from "@app/components/EventActivity.svelte"
|
import EventActivity from "@app/components/EventActivity.svelte"
|
||||||
import EventActions from "@app/components/EventActions.svelte"
|
import EventActions from "@app/components/EventActions.svelte"
|
||||||
import {publishDelete, publishReaction} from "@app/commands"
|
import {publishDelete, publishReaction, canEnforceNip70} from "@app/core/commands"
|
||||||
import {makeThreadPath} from "@app/routes"
|
import {makeThreadPath} from "@app/util/routes"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
url: any
|
url: any
|
||||||
@@ -15,12 +15,15 @@
|
|||||||
|
|
||||||
const {url, event, showActivity = false}: Props = $props()
|
const {url, event, showActivity = false}: Props = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const path = makeThreadPath(url, event.id)
|
const path = makeThreadPath(url, event.id)
|
||||||
|
|
||||||
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
|
const deleteReaction = async (event: TrustedEvent) =>
|
||||||
|
publishDelete({relays: [url], event, protect: await shouldProtect})
|
||||||
|
|
||||||
const createReaction = (template: EventContent) =>
|
const createReaction = async (template: EventContent) =>
|
||||||
publishReaction({...template, event, relays: [url]})
|
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
} from "@welshman/content"
|
} from "@welshman/content"
|
||||||
import {preventDefault, stopPropagation} from "@lib/html"
|
import {preventDefault, stopPropagation} from "@lib/html"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ContentToken from "@app/components/ContentToken.svelte"
|
import ContentToken from "@app/components/ContentToken.svelte"
|
||||||
@@ -31,7 +32,7 @@
|
|||||||
import ContentQuote from "@app/components/ContentQuote.svelte"
|
import ContentQuote from "@app/components/ContentQuote.svelte"
|
||||||
import ContentTopic from "@app/components/ContentTopic.svelte"
|
import ContentTopic from "@app/components/ContentTopic.svelte"
|
||||||
import ContentMention from "@app/components/ContentMention.svelte"
|
import ContentMention from "@app/components/ContentMention.svelte"
|
||||||
import {entityLink, userSettingValues} from "@app/state"
|
import {entityLink, userSettingsValues} from "@app/core/state"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
event: any
|
event: any
|
||||||
@@ -68,7 +69,7 @@
|
|||||||
|
|
||||||
if (!parsed || hideMediaAtDepth <= depth) return false
|
if (!parsed || hideMediaAtDepth <= depth) return false
|
||||||
|
|
||||||
if (isLink(parsed) && $userSettingValues.show_media && isStartOrEnd(i)) {
|
if (isLink(parsed) && $userSettingsValues.show_media && isStartOrEnd(i)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let warning = $state(
|
let warning = $state(
|
||||||
$userSettingValues.hide_sensitive && event.tags.find(nthEq(0, "content-warning"))?.[1],
|
$userSettingsValues.hide_sensitive && event.tags.find(nthEq(0, "content-warning"))?.[1],
|
||||||
)
|
)
|
||||||
|
|
||||||
const shortContent = $derived(
|
const shortContent = $derived(
|
||||||
@@ -122,7 +123,7 @@
|
|||||||
<div class="relative">
|
<div class="relative">
|
||||||
{#if warning}
|
{#if warning}
|
||||||
<div class="card2 card2-sm bg-alt row-2">
|
<div class="card2 card2-sm bg-alt row-2">
|
||||||
<Icon icon="danger" />
|
<Icon icon={Danger} />
|
||||||
<p>
|
<p>
|
||||||
This note has been flagged by the author as "{warning}".<br />
|
This note has been flagged by the author as "{warning}".<br />
|
||||||
<Button class="link" onclick={ignoreWarning}>Show anyway</Button>
|
<Button class="link" onclick={ignoreWarning}>Show anyway</Button>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {ParsedEmojiValue} from "@welshman/content"
|
import type {ParsedEmojiValue} from "@welshman/content"
|
||||||
import {imgproxy} from "@app/state"
|
import {imgproxy} from "@app/core/state"
|
||||||
|
|
||||||
export let value: ParsedEmojiValue
|
export let value: ParsedEmojiValue
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {ellipsize, displayUrl, postJson} from "@welshman/lib"
|
import {ellipsize, displayUrl, postJson} from "@welshman/lib"
|
||||||
import {dufflepud, imgproxy} from "@app/state"
|
import {dufflepud, imgproxy} from "@app/core/state"
|
||||||
import {preventDefault, stopPropagation} from "@lib/html"
|
import {preventDefault, stopPropagation} from "@lib/html"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import ContentLinkDetail from "@app/components/ContentLinkDetail.svelte"
|
import ContentLinkDetail from "@app/components/ContentLinkDetail.svelte"
|
||||||
import ContentLinkBlockImage from "@app/components/ContentLinkBlockImage.svelte"
|
import ContentLinkBlockImage from "@app/components/ContentLinkBlockImage.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
const {value, event} = $props()
|
const {value, event} = $props()
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
import {onMount, onDestroy} from "svelte"
|
import {onMount, onDestroy} from "svelte"
|
||||||
import {displayUrl} from "@welshman/lib"
|
import {displayUrl} from "@welshman/lib"
|
||||||
import {getTags, decryptFile, getTagValue, tagsFromIMeta} from "@welshman/util"
|
import {getTags, decryptFile, getTagValue, tagsFromIMeta} from "@welshman/util"
|
||||||
|
import LinkRound from "@assets/icons/link-round.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import {imgproxy} from "@app/state"
|
import {imgproxy} from "@app/core/state"
|
||||||
|
|
||||||
const {value, event, ...props} = $props()
|
const {value, event, ...props} = $props()
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@
|
|||||||
|
|
||||||
{#if hasError}
|
{#if hasError}
|
||||||
<a href={url} class="link-content whitespace-nowrap">
|
<a href={url} class="link-content whitespace-nowrap">
|
||||||
<Icon icon="link-round" size={3} class="inline-block" />
|
<Icon icon={LinkRound} size={3} class="inline-block" />
|
||||||
{displayUrl(url)}
|
{displayUrl(url)}
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {displayUrl} from "@welshman/lib"
|
import {displayUrl} from "@welshman/lib"
|
||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
|
import LinkRound from "@assets/icons/link-round.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import ContentLinkDetail from "@app/components/ContentLinkDetail.svelte"
|
import ContentLinkDetail from "@app/components/ContentLinkDetail.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
const {value} = $props()
|
const {value} = $props()
|
||||||
|
|
||||||
@@ -16,12 +17,12 @@
|
|||||||
{#if url.match(/\.(jpe?g|png|gif|webp)$/)}
|
{#if url.match(/\.(jpe?g|png|gif|webp)$/)}
|
||||||
<!-- Use a real link so people can copy the href -->
|
<!-- Use a real link so people can copy the href -->
|
||||||
<a href={url} class="link-content whitespace-nowrap" onclick={preventDefault(expand)}>
|
<a href={url} class="link-content whitespace-nowrap" onclick={preventDefault(expand)}>
|
||||||
<Icon icon="link-round" size={3} class="inline-block" />
|
<Icon icon={LinkRound} size={3} class="inline-block" />
|
||||||
{displayUrl(url)}
|
{displayUrl(url)}
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<Link external href={url} class="link-content whitespace-nowrap">
|
<Link external href={url} class="link-content whitespace-nowrap">
|
||||||
<Icon icon="link-round" size={3} class="inline-block" />
|
<Icon icon={LinkRound} size={3} class="inline-block" />
|
||||||
{displayUrl(url)}
|
{displayUrl(url)}
|
||||||
</Link>
|
</Link>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import {deriveProfileDisplay} from "@welshman/app"
|
import {deriveProfileDisplay} from "@welshman/app"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: ProfilePointer
|
value: ProfilePointer
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import NoteCard from "@app/components/NoteCard.svelte"
|
import NoteCard from "@app/components/NoteCard.svelte"
|
||||||
import NoteContent from "@app/components/NoteContent.svelte"
|
import NoteContent from "@app/components/NoteContent.svelte"
|
||||||
import {deriveEvent, entityLink} from "@app/state"
|
import {deriveEvent, entityLink} from "@app/core/state"
|
||||||
import {goToEvent} from "@app/routes"
|
import {goToEvent} from "@app/util/routes"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: any
|
value: any
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Bolt from "@assets/icons/bolt.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import {clip} from "@app/toast"
|
import {clip} from "@app/util/toast"
|
||||||
|
|
||||||
const {value} = $props()
|
const {value} = $props()
|
||||||
|
|
||||||
@@ -9,6 +10,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button onclick={copy} class="link-content">
|
<Button onclick={copy} class="link-content">
|
||||||
<Icon icon="bolt" size={3} class="inline-block translate-y-px" />
|
<Icon icon={Bolt} size={3} class="inline-block translate-y-px" />
|
||||||
{value.slice(0, 16)}...
|
{value.slice(0, 16)}...
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {formatTimestamp} from "@welshman/lib"
|
import {formatTimestamp} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Content from "@app/components/Content.svelte"
|
import Content from "@app/components/Content.svelte"
|
||||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
import ProfileCircles from "@app/components/ProfileCircles.svelte"
|
import ProfileCircles from "@app/components/ProfileCircles.svelte"
|
||||||
import {goToEvent} from "@app/routes"
|
import {goToEvent} from "@app/util/routes"
|
||||||
import {displayChannel} from "@app/state"
|
import {displayChannel} from "@app/core/state"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
@@ -28,19 +29,19 @@
|
|||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<div class="flex items-center gap-2 text-sm opacity-70">
|
<div class="flex items-center gap-2 text-sm opacity-70">
|
||||||
{#if room}
|
{#if room}
|
||||||
<span class="font-medium text-blue-400">
|
<span class="truncate font-medium text-blue-400">
|
||||||
#{displayChannel(url, room)}
|
#{displayChannel(url, room)}
|
||||||
</span>
|
</span>
|
||||||
<span class="opacity-50">•</span>
|
<span class="opacity-50">•</span>
|
||||||
{/if}
|
{/if}
|
||||||
<span>{formatTimestamp(earliest.created_at)}</span>
|
<span class="text-nowrap">{formatTimestamp(earliest.created_at)}</span>
|
||||||
</div>
|
</div>
|
||||||
<Content minimalQuote minLength={100} maxLength={400} event={earliest} />
|
<Content minimalQuote minLength={100} maxLength={400} event={earliest} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-13 flex items-center justify-between">
|
<div class="ml-13 flex items-center justify-between">
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon={AltArrowLeft} />
|
||||||
<span class="text-sm opacity-70">
|
<span class="text-sm opacity-70">
|
||||||
{events.length}
|
{events.length}
|
||||||
{events.length === 1 ? "message" : "messages"}
|
{events.length === 1 ? "message" : "messages"}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import LogInPassword from "@app/components/LogInPassword.svelte"
|
import LogInPassword from "@app/components/LogInPassword.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
import {BURROW_URL} from "@app/state"
|
import {BURROW_URL} from "@app/core/state"
|
||||||
|
|
||||||
const {email, confirm_token} = $props()
|
const {email, confirm_token} = $props()
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,17 @@
|
|||||||
import type {Instance} from "tippy.js"
|
import type {Instance} from "tippy.js"
|
||||||
import type {NativeEmoji} from "emoji-picker-element/shared"
|
import type {NativeEmoji} from "emoji-picker-element/shared"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
|
import Bolt from "@assets/icons/bolt.svg?dataurl"
|
||||||
|
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
|
||||||
|
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Tippy from "@lib/components/Tippy.svelte"
|
import Tippy from "@lib/components/Tippy.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ZapButton from "@app/components/ZapButton.svelte"
|
import ZapButton from "@app/components/ZapButton.svelte"
|
||||||
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
||||||
import EventMenu from "@app/components/EventMenu.svelte"
|
import EventMenu from "@app/components/EventMenu.svelte"
|
||||||
import {ENABLE_ZAPS} from "@app/state"
|
import {ENABLE_ZAPS} from "@app/core/state"
|
||||||
import {publishReaction} from "@app/commands"
|
import {publishReaction, canEnforceNip70} from "@app/core/commands"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
@@ -22,12 +25,19 @@
|
|||||||
|
|
||||||
const {url, noun, event, hideZap, customActions}: Props = $props()
|
const {url, noun, event, hideZap, customActions}: Props = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const showPopover = () => popover?.show()
|
const showPopover = () => popover?.show()
|
||||||
|
|
||||||
const hidePopover = () => popover?.hide()
|
const hidePopover = () => popover?.hide()
|
||||||
|
|
||||||
const onEmoji = (emoji: NativeEmoji) =>
|
const onEmoji = async (emoji: NativeEmoji) =>
|
||||||
publishReaction({event, content: emoji.unicode, relays: [url]})
|
publishReaction({
|
||||||
|
event,
|
||||||
|
content: emoji.unicode,
|
||||||
|
relays: [url],
|
||||||
|
protect: await shouldProtect,
|
||||||
|
})
|
||||||
|
|
||||||
let popover: Instance | undefined = $state()
|
let popover: Instance | undefined = $state()
|
||||||
</script>
|
</script>
|
||||||
@@ -35,11 +45,11 @@
|
|||||||
<Button class="join rounded-full">
|
<Button class="join rounded-full">
|
||||||
{#if ENABLE_ZAPS && !hideZap}
|
{#if ENABLE_ZAPS && !hideZap}
|
||||||
<ZapButton {url} {event} class="btn join-item btn-neutral btn-xs">
|
<ZapButton {url} {event} class="btn join-item btn-neutral btn-xs">
|
||||||
<Icon icon="bolt" size={4} />
|
<Icon icon={Bolt} size={4} />
|
||||||
</ZapButton>
|
</ZapButton>
|
||||||
{/if}
|
{/if}
|
||||||
<EmojiButton {onEmoji} class="btn join-item btn-neutral btn-xs">
|
<EmojiButton {onEmoji} class="btn join-item btn-neutral btn-xs">
|
||||||
<Icon icon="smile-circle" size={4} />
|
<Icon icon={SmileCircle} size={4} />
|
||||||
</EmojiButton>
|
</EmojiButton>
|
||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
@@ -47,7 +57,7 @@
|
|||||||
props={{url, noun, event, customActions, onClick: hidePopover}}
|
props={{url, noun, event, customActions, onClick: hidePopover}}
|
||||||
params={{trigger: "manual", interactive: true}}>
|
params={{trigger: "manual", interactive: true}}>
|
||||||
<Button class="btn join-item btn-neutral btn-xs" onclick={showPopover}>
|
<Button class="btn join-item btn-neutral btn-xs" onclick={showPopover}>
|
||||||
<Icon icon="menu-dots" size={4} />
|
<Icon icon={MenuDots} size={4} />
|
||||||
</Button>
|
</Button>
|
||||||
</Tippy>
|
</Tippy>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
import {deriveEvents} from "@welshman/store"
|
import {deriveEvents} from "@welshman/store"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {repository} from "@welshman/app"
|
import {repository} from "@welshman/app"
|
||||||
import {notifications} from "@app/notifications"
|
import {notifications} from "@app/util/notifications"
|
||||||
|
import Reply from "@assets/icons/reply-2.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
|
||||||
const {url, path, event}: {url: string; path: string; event: TrustedEvent} = $props()
|
const {url, path, event}: {url: string; path: string; event: TrustedEvent} = $props()
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex-inline btn btn-neutral btn-xs gap-1 rounded-full">
|
<div class="flex-inline btn btn-neutral btn-xs gap-1 rounded-full">
|
||||||
<Icon icon="reply" />
|
<Icon icon={Reply} />
|
||||||
<span>{$replies.length} {$replies.length === 1 ? "reply" : "replies"}</span>
|
<span>{$replies.length} {$replies.length === 1 ? "reply" : "replies"}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn btn-neutral btn-xs relative hidden rounded-full sm:flex">
|
<div class="btn btn-neutral btn-xs relative hidden rounded-full sm:flex">
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import Confirm from "@lib/components/Confirm.svelte"
|
import Confirm from "@lib/components/Confirm.svelte"
|
||||||
import {publishDelete} from "@app/commands"
|
import {publishDelete, canEnforceNip70} from "@app/core/commands"
|
||||||
import {clearModals} from "@app/modal"
|
import {clearModals} from "@app/util/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
@@ -11,8 +11,10 @@
|
|||||||
|
|
||||||
const {url, event}: Props = $props()
|
const {url, event}: Props = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const confirm = async () => {
|
const confirm = async () => {
|
||||||
await publishDelete({event, relays: [url]})
|
await publishDelete({event, relays: [url], protect: await shouldProtect})
|
||||||
|
|
||||||
clearModals()
|
clearModals()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as nip19 from "nostr-tools/nip19"
|
import * as nip19 from "nostr-tools/nip19"
|
||||||
import {Router} from "@welshman/router"
|
import {Router} from "@welshman/router"
|
||||||
|
import {LOCALE, secondsToDate} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
|
import {displayRelayUrl} from "@welshman/util"
|
||||||
|
import FileText from "@assets/icons/file-text.svg?dataurl"
|
||||||
|
import Copy from "@assets/icons/copy.svg?dataurl"
|
||||||
|
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import {clip} from "@app/toast"
|
import {trackerStore} from "@app/core/state"
|
||||||
|
import {clip} from "@app/util/toast"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url?: string
|
url?: string
|
||||||
@@ -17,11 +23,17 @@
|
|||||||
|
|
||||||
const relays = url ? [url] : Router.get().Event(event).getUrls()
|
const relays = url ? [url] : Router.get().Event(event).getUrls()
|
||||||
const nevent1 = nip19.neventEncode({...event, relays})
|
const nevent1 = nip19.neventEncode({...event, relays})
|
||||||
|
const seenOn = $trackerStore.getRelays(event.id)
|
||||||
const npub1 = nip19.npubEncode(event.pubkey)
|
const npub1 = nip19.npubEncode(event.pubkey)
|
||||||
const json = JSON.stringify(event, null, 2)
|
const json = JSON.stringify(event, null, 2)
|
||||||
const copyLink = () => clip(nevent1)
|
const copyLink = () => clip(nevent1)
|
||||||
const copyPubkey = () => clip(npub1)
|
const copyPubkey = () => clip(npub1)
|
||||||
const copyJson = () => clip(json)
|
const copyJson = () => clip(json)
|
||||||
|
|
||||||
|
const formatter = new Intl.DateTimeFormat(LOCALE, {
|
||||||
|
dateStyle: "long",
|
||||||
|
timeStyle: "long",
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column gap-4">
|
<div class="column gap-4">
|
||||||
@@ -33,16 +45,24 @@
|
|||||||
<div>The full details of this event are shown below.</div>
|
<div>The full details of this event are shown below.</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
<FieldInline>
|
||||||
|
{#snippet label()}
|
||||||
|
<p>Created At</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<p>{formatter.format(secondsToDate(event.created_at))}</p>
|
||||||
|
{/snippet}
|
||||||
|
</FieldInline>
|
||||||
<FieldInline>
|
<FieldInline>
|
||||||
{#snippet label()}
|
{#snippet label()}
|
||||||
<p>Event Link</p>
|
<p>Event Link</p>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
<Icon icon="file" />
|
<Icon icon={FileText} />
|
||||||
<input type="text" class="ellipsize min-w-0 grow" value={nevent1} />
|
<input type="text" class="ellipsize min-w-0 grow" value={nevent1} />
|
||||||
<Button onclick={copyLink} class="flex items-center">
|
<Button onclick={copyLink} class="flex items-center">
|
||||||
<Icon icon="copy" />
|
<Icon icon={Copy} />
|
||||||
</Button>
|
</Button>
|
||||||
</label>
|
</label>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
@@ -53,19 +73,35 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
<Icon icon="user-circle" />
|
<Icon icon={UserCircle} />
|
||||||
<input type="text" class="ellipsize min-w-0 grow" value={npub1} />
|
<input type="text" class="ellipsize min-w-0 grow" value={npub1} />
|
||||||
<Button onclick={copyPubkey} class="flex items-center">
|
<Button onclick={copyPubkey} class="flex items-center">
|
||||||
<Icon icon="copy" />
|
<Icon icon={Copy} />
|
||||||
</Button>
|
</Button>
|
||||||
</label>
|
</label>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</FieldInline>
|
</FieldInline>
|
||||||
|
{#if !url && seenOn.size > 0}
|
||||||
|
<FieldInline>
|
||||||
|
{#snippet label()}
|
||||||
|
<p>Seen On</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
{#each seenOn as url, i (url)}
|
||||||
|
<span class="bg-alt badge flex gap-1">
|
||||||
|
{displayRelayUrl(url)}
|
||||||
|
</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/snippet}
|
||||||
|
</FieldInline>
|
||||||
|
{/if}
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<pre class="card2 card2-sm bg-alt overflow-auto text-xs"><code>{json}</code></pre>
|
<pre class="card2 card2-sm bg-alt overflow-auto text-xs"><code>{json}</code></pre>
|
||||||
<p class="absolute right-2 top-2 flex flex-grow items-center justify-between">
|
<p class="absolute right-2 top-2 flex flex-grow items-center justify-between">
|
||||||
<Button onclick={copyJson} class="btn btn-neutral btn-sm flex items-center">
|
<Button onclick={copyJson} class="btn btn-neutral btn-sm flex items-center">
|
||||||
<Icon icon="copy" /> Copy
|
<Icon icon={Copy} /> Copy
|
||||||
</Button>
|
</Button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,11 @@
|
|||||||
import EventReport from "@app/components/EventReport.svelte"
|
import EventReport from "@app/components/EventReport.svelte"
|
||||||
import EventShare from "@app/components/EventShare.svelte"
|
import EventShare from "@app/components/EventShare.svelte"
|
||||||
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
|
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
import ShareCircle from "@assets/icons/share-circle.svg?dataurl"
|
||||||
|
import Code2 from "@assets/icons/code-2.svg?dataurl"
|
||||||
|
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
|
||||||
|
import Danger from "@assets/icons/danger.svg?dataurl"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
@@ -43,14 +47,14 @@
|
|||||||
{#if isRoot}
|
{#if isRoot}
|
||||||
<li>
|
<li>
|
||||||
<Button onclick={share}>
|
<Button onclick={share}>
|
||||||
<Icon size={4} icon="share-circle" />
|
<Icon size={4} icon={ShareCircle} />
|
||||||
Share to Chat
|
Share to Chat
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
<li>
|
<li>
|
||||||
<Button onclick={showInfo}>
|
<Button onclick={showInfo}>
|
||||||
<Icon size={4} icon="code-2" />
|
<Icon size={4} icon={Code2} />
|
||||||
{noun} Details
|
{noun} Details
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
@@ -58,14 +62,14 @@
|
|||||||
{#if event.pubkey === $pubkey}
|
{#if event.pubkey === $pubkey}
|
||||||
<li>
|
<li>
|
||||||
<Button onclick={showDelete} class="text-error">
|
<Button onclick={showDelete} class="text-error">
|
||||||
<Icon size={4} icon="trash-bin-2" />
|
<Icon size={4} icon={TrashBin2} />
|
||||||
Delete {noun}
|
Delete {noun}
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
{:else}
|
{:else}
|
||||||
<li>
|
<li>
|
||||||
<Button class="text-error" onclick={report}>
|
<Button class="text-error" onclick={report}>
|
||||||
<Icon size={4} icon="danger" />
|
<Icon size={4} icon={Danger} />
|
||||||
Report Content
|
Report Content
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -3,17 +3,20 @@
|
|||||||
import {writable} from "svelte/store"
|
import {writable} from "svelte/store"
|
||||||
import {isMobile, preventDefault} from "@lib/html"
|
import {isMobile, preventDefault} from "@lib/html"
|
||||||
import {fly} from "@lib/transition"
|
import {fly} from "@lib/transition"
|
||||||
|
import Paperclip from "@assets/icons/paperclip-2.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import EditorContent from "@app/editor/EditorContent.svelte"
|
import EditorContent from "@app/editor/EditorContent.svelte"
|
||||||
import {publishComment} from "@app/commands"
|
import {publishComment, canEnforceNip70} from "@app/core/commands"
|
||||||
import {PROTECTED} from "@app/state"
|
import {PROTECTED} from "@app/core/state"
|
||||||
import {makeEditor} from "@app/editor"
|
import {makeEditor} from "@app/editor"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
const {url, event, onClose, onSubmit} = $props()
|
const {url, event, onClose, onSubmit} = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const uploading = writable(false)
|
const uploading = writable(false)
|
||||||
|
|
||||||
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
|
const selectFiles = () => editor.then(ed => ed.commands.selectFiles())
|
||||||
@@ -23,7 +26,11 @@
|
|||||||
|
|
||||||
const ed = await editor
|
const ed = await editor
|
||||||
const content = ed.getText({blockSeparator: "\n"}).trim()
|
const content = ed.getText({blockSeparator: "\n"}).trim()
|
||||||
const tags = [...ed.storage.nostr.getEditorTags(), PROTECTED]
|
const tags = ed.storage.nostr.getEditorTags()
|
||||||
|
|
||||||
|
if (await shouldProtect) {
|
||||||
|
tags.push(PROTECTED)
|
||||||
|
}
|
||||||
|
|
||||||
if (!content) {
|
if (!content) {
|
||||||
return pushToast({
|
return pushToast({
|
||||||
@@ -75,7 +82,7 @@
|
|||||||
{#if $uploading}
|
{#if $uploading}
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="paperclip" size={3} />
|
<Icon icon={Paperclip} size={3} />
|
||||||
{/if}
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Field from "@lib/components/Field.svelte"
|
import Field from "@lib/components/Field.svelte"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
|
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {publishReport} from "@app/commands"
|
import {publishReport} from "@app/core/commands"
|
||||||
|
|
||||||
const {url, event} = $props()
|
const {url, event} = $props()
|
||||||
|
|
||||||
@@ -78,12 +80,12 @@
|
|||||||
</Field>
|
</Field>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back}>
|
<Button class="btn btn-link" onclick={back}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||||
<Spinner {loading}>Send Report</Spinner>
|
<Spinner {loading}>Send Report</Spinner>
|
||||||
<Icon icon="alt-arrow-right" />
|
<Icon icon={AltArrowRight} />
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -6,18 +6,20 @@
|
|||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Profile from "@app/components/Profile.svelte"
|
import Profile from "@app/components/Profile.svelte"
|
||||||
import {publishDelete} from "@app/commands"
|
import {publishDelete, canEnforceNip70} from "@app/core/commands"
|
||||||
|
|
||||||
const {url, event} = $props()
|
const {url, event} = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const reports = deriveEvents(repository, {
|
const reports = deriveEvents(repository, {
|
||||||
filters: [{kinds: [REPORT], "#e": [event.id]}],
|
filters: [{kinds: [REPORT], "#e": [event.id]}],
|
||||||
})
|
})
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const deleteReport = (report: TrustedEvent) => {
|
const deleteReport = async (report: TrustedEvent) => {
|
||||||
publishDelete({event: report, relays: [url]})
|
publishDelete({event: report, relays: [url], protect: await shouldProtect})
|
||||||
|
|
||||||
if ($reports.length === 0) {
|
if ($reports.length === 0) {
|
||||||
history.back()
|
history.back()
|
||||||
|
|||||||
@@ -2,14 +2,16 @@
|
|||||||
import {goto} from "$app/navigation"
|
import {goto} from "$app/navigation"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
|
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import ChannelName from "@app/components/ChannelName.svelte"
|
import ChannelName from "@app/components/ChannelName.svelte"
|
||||||
import {channelsByUrl} from "@app/state"
|
import {channelsByUrl} from "@app/core/state"
|
||||||
import {makeRoomPath} from "@app/routes"
|
import {makeRoomPath} from "@app/util/routes"
|
||||||
import {setKey} from "@app/implicit"
|
import {setKey} from "@lib/implicit"
|
||||||
|
|
||||||
const {url, noun, event}: {url: string; noun: string; event: TrustedEvent} = $props()
|
const {url, noun, event}: {url: string; noun: string; event: TrustedEvent} = $props()
|
||||||
|
|
||||||
@@ -50,12 +52,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back}>
|
<Button class="btn btn-link" onclick={back}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" class="btn btn-primary" disabled={!selection}>
|
<Button type="submit" class="btn btn-primary" disabled={!selection}>
|
||||||
Share {noun}
|
Share {noun}
|
||||||
<Icon icon="alt-arrow-right" />
|
<Icon icon={AltArrowRight} />
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
import ThunkStatusOrDeleted from "@app/components/ThunkStatusOrDeleted.svelte"
|
||||||
import EventActivity from "@app/components/EventActivity.svelte"
|
import EventActivity from "@app/components/EventActivity.svelte"
|
||||||
import EventActions from "@app/components/EventActions.svelte"
|
import EventActions from "@app/components/EventActions.svelte"
|
||||||
import {publishDelete, publishReaction} from "@app/commands"
|
import {publishDelete, publishReaction, canEnforceNip70} from "@app/core/commands"
|
||||||
import {makeGoalPath} from "@app/routes"
|
import {makeGoalPath} from "@app/util/routes"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
url: any
|
url: any
|
||||||
@@ -15,12 +15,15 @@
|
|||||||
|
|
||||||
const {url, event, showActivity = false}: Props = $props()
|
const {url, event, showActivity = false}: Props = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const path = makeGoalPath(url, event.id)
|
const path = makeGoalPath(url, event.id)
|
||||||
|
|
||||||
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
|
const deleteReaction = async (event: TrustedEvent) =>
|
||||||
|
publishDelete({relays: [url], event, protect: await shouldProtect})
|
||||||
|
|
||||||
const createReaction = (template: EventContent) =>
|
const createReaction = async (template: EventContent) =>
|
||||||
publishReaction({...template, event, relays: [url]})
|
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
import {makeEvent, ZAP_GOAL} from "@welshman/util"
|
import {makeEvent, ZAP_GOAL} from "@welshman/util"
|
||||||
import {publishThunk} from "@welshman/app"
|
import {publishThunk} from "@welshman/app"
|
||||||
import {isMobile, preventDefault} from "@lib/html"
|
import {isMobile, preventDefault} from "@lib/html"
|
||||||
|
import Paperclip from "@assets/icons/paperclip-2.svg?dataurl"
|
||||||
|
import Bolt from "@assets/icons/bolt.svg?dataurl"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Field from "@lib/components/Field.svelte"
|
import Field from "@lib/components/Field.svelte"
|
||||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||||
@@ -10,12 +13,15 @@
|
|||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import EditorContent from "@app/editor/EditorContent.svelte"
|
import EditorContent from "@app/editor/EditorContent.svelte"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {PROTECTED} from "@app/state"
|
import {PROTECTED} from "@app/core/state"
|
||||||
import {makeEditor} from "@app/editor"
|
import {makeEditor} from "@app/editor"
|
||||||
|
import {canEnforceNip70} from "@app/core/commands"
|
||||||
|
|
||||||
const {url} = $props()
|
const {url} = $props()
|
||||||
|
|
||||||
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const uploading = writable(false)
|
const uploading = writable(false)
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
@@ -47,9 +53,12 @@
|
|||||||
["summary", summary],
|
["summary", summary],
|
||||||
["amount", String(amount)],
|
["amount", String(amount)],
|
||||||
["relays", url],
|
["relays", url],
|
||||||
PROTECTED,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if (await shouldProtect) {
|
||||||
|
tags.push(PROTECTED)
|
||||||
|
}
|
||||||
|
|
||||||
publishThunk({
|
publishThunk({
|
||||||
relays: [url],
|
relays: [url],
|
||||||
event: makeEvent(ZAP_GOAL, {content, tags}),
|
event: makeEvent(ZAP_GOAL, {content, tags}),
|
||||||
@@ -108,7 +117,7 @@
|
|||||||
{#if $uploading}
|
{#if $uploading}
|
||||||
<span class="loading loading-spinner loading-xs"></span>
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="paperclip" size={3} />
|
<Icon icon={Paperclip} size={3} />
|
||||||
{/if}
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -120,7 +129,7 @@
|
|||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<div class="flex flex-grow justify-end">
|
<div class="flex flex-grow justify-end">
|
||||||
<label class="input input-bordered flex items-center gap-2">
|
<label class="input input-bordered flex items-center gap-2">
|
||||||
<Icon icon="bolt" />
|
<Icon icon={Bolt} />
|
||||||
<input bind:value={amount} type="number" class="w-28" />
|
<input bind:value={amount} type="number" class="w-28" />
|
||||||
<p class="opacity-50">sats</p>
|
<p class="opacity-50">sats</p>
|
||||||
</label>
|
</label>
|
||||||
@@ -138,7 +147,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back}>
|
<Button class="btn btn-link" onclick={back}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" class="btn btn-primary">Create Goal</Button>
|
<Button type="submit" class="btn btn-primary">Create Goal</Button>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import ProfileLink from "@app/components/ProfileLink.svelte"
|
import ProfileLink from "@app/components/ProfileLink.svelte"
|
||||||
import GoalActions from "@app/components/GoalActions.svelte"
|
import GoalActions from "@app/components/GoalActions.svelte"
|
||||||
import GoalSummary from "@app/components/GoalSummary.svelte"
|
import GoalSummary from "@app/components/GoalSummary.svelte"
|
||||||
import {makeGoalPath} from "@app/routes"
|
import {makeGoalPath} from "@app/util/routes"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util"
|
import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util"
|
||||||
import {deriveEventsMapped} from "@welshman/store"
|
import {deriveEventsMapped} from "@welshman/store"
|
||||||
import {repository, getValidZap} from "@welshman/app"
|
import {repository, getValidZap} from "@welshman/app"
|
||||||
|
import Bolt from "@assets/icons/bolt.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import ZapButton from "@app/components/ZapButton.svelte"
|
import ZapButton from "@app/components/ZapButton.svelte"
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<progress class="progress progress-primary" value={zapAmount} max={goalAmount}></progress>
|
<progress class="progress progress-primary" value={zapAmount} max={goalAmount}></progress>
|
||||||
<ZapButton {url} {event} class="btn btn-primary lg:m-auto lg:px-20">
|
<ZapButton {url} {event} class="btn btn-primary lg:m-auto lg:px-20">
|
||||||
<Icon icon="bolt" />
|
<Icon icon={Bolt} />
|
||||||
Contribute to this goal
|
Contribute to this goal
|
||||||
</ZapButton>
|
</ZapButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import {PLATFORM_NAME} from "@app/state"
|
import {PLATFORM_NAME} from "@app/core/state"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column gap-4">
|
<div class="column gap-4">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import {PLATFORM_NAME} from "@app/state"
|
import {PLATFORM_NAME} from "@app/core/state"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column gap-4">
|
<div class="column gap-4">
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {session} from "@welshman/app"
|
import {session} from "@welshman/app"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
|
import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import ProfileEject from "@app/components/ProfileEject.svelte"
|
import ProfileEject from "@app/components/ProfileEject.svelte"
|
||||||
import {PLATFORM_NAME} from "@app/state"
|
import {PLATFORM_NAME} from "@app/core/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
@@ -27,8 +29,8 @@
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
On <Link external href="https://nostr.com/">Nostr</Link>, <strong>you</strong> control your own
|
On <Link external href="https://nostr.com/">Nostr</Link>, <strong>you</strong> control your own
|
||||||
identity and social data, through the magic of crytography. The basic idea is that you have a
|
identity and social data, through the magic of cryptography. The basic idea is that you have a
|
||||||
<strong>public key</strong>, which acts as your user id, and a
|
<strong>public key</strong>, which acts as your user ID, and a
|
||||||
<strong>private key</strong> which allows you to prove your identity.
|
<strong>private key</strong> which allows you to prove your identity.
|
||||||
</p>
|
</p>
|
||||||
{#if $session?.email}
|
{#if $session?.email}
|
||||||
@@ -39,11 +41,11 @@
|
|||||||
<p>If you'd like to switch to self-custody, please click below to get started.</p>
|
<p>If you'd like to switch to self-custody, please click below to get started.</p>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back}>
|
<Button class="btn btn-link" onclick={back}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button class="btn btn-primary" onclick={startEject}>
|
<Button class="btn btn-primary" onclick={startEject}>
|
||||||
<Icon icon="check-circle" />
|
<Icon icon={CheckCircle} />
|
||||||
I want to hold my own keys
|
I want to hold my own keys
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import {PLATFORM_NAME} from "@app/state"
|
import {PLATFORM_NAME} from "@app/core/state"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column gap-4">
|
<div class="column gap-4">
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
|
|
||||||
|
const back = () => history.back()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="column gap-4">
|
||||||
|
<ModalHeader>
|
||||||
|
{#snippet title()}
|
||||||
|
<div>What are digital signatures?</div>
|
||||||
|
{/snippet}
|
||||||
|
</ModalHeader>
|
||||||
|
<p>
|
||||||
|
Most online services ask their users to trust them that they're being honest, and they usually
|
||||||
|
are. However, traditional social media platforms have the ability to <strong
|
||||||
|
>create forged content</strong> that can appear to be genuinely authored, but which are actually
|
||||||
|
counterfeit.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
On <Link external href="https://nostr.com/">Nostr</Link>, all your content is authenticated
|
||||||
|
using <strong>digital signatures</strong>, which cryptographically tie a particular person to a
|
||||||
|
given post or message.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The result is that you don't normally have to trust service providers not to tamper with the
|
||||||
|
information flowing through the network — instead, your client software can prove that a given
|
||||||
|
piece of data is authentic.
|
||||||
|
</p>
|
||||||
|
<Button class="btn btn-primary" onclick={back}>Got it</Button>
|
||||||
|
</div>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {deriveZapperForPubkey} from "@welshman/app"
|
import {deriveZapperForPubkey} from "@welshman/app"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
@@ -29,7 +30,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back}>
|
<Button class="btn btn-link" onclick={back}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
|||||||
+35
-22
@@ -1,7 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {randomId} from "@welshman/lib"
|
import {randomId, call} from "@welshman/lib"
|
||||||
import {preventDefault, stopPropagation} from "@lib/html"
|
import {preventDefault, stopPropagation, compressFile} from "@lib/html"
|
||||||
|
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
|
||||||
|
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
|
||||||
|
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import {uploadFile} from "@app/core/commands"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
file?: File | undefined
|
file?: File | undefined
|
||||||
@@ -24,14 +28,14 @@
|
|||||||
active = false
|
active = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDrop = (e: any) => {
|
const onDrop = async (e: any) => {
|
||||||
active = false
|
active = false
|
||||||
|
|
||||||
file = e.dataTransfer.files[0]
|
file = await compressFile(e.dataTransfer.files[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChange = (e: any) => {
|
const onChange = async (e: any) => {
|
||||||
file = e.target.files[0]
|
file = await compressFile(e.target.files[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClear = () => {
|
const onClear = () => {
|
||||||
@@ -44,20 +48,29 @@
|
|||||||
let initialUrl = $state(url)
|
let initialUrl = $state(url)
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (file) {
|
call(async () => {
|
||||||
const reader = new FileReader()
|
if (file) {
|
||||||
|
const {result} = await uploadFile(file)
|
||||||
|
|
||||||
reader.addEventListener(
|
if (result?.url) {
|
||||||
"load",
|
url = result.url
|
||||||
() => {
|
} else {
|
||||||
url = reader.result as string
|
const reader = new FileReader()
|
||||||
},
|
|
||||||
false,
|
reader.addEventListener(
|
||||||
)
|
"load",
|
||||||
reader.readAsDataURL(file)
|
() => {
|
||||||
} else {
|
url = reader.result as string
|
||||||
url = initialUrl
|
},
|
||||||
}
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
|
reader.readAsDataURL(file)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
url = initialUrl
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -84,14 +97,14 @@
|
|||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
onmousedown={stopPropagation(onClear)}
|
onmousedown={stopPropagation(onClear)}
|
||||||
ontouchstart={stopPropagation(onClear)}>
|
ontouchstart={stopPropagation(onClear)}>
|
||||||
<Icon icon="close-circle" class="scale-150 !bg-base-300" />
|
<Icon icon={CloseCircle} class="scale-150 !bg-base-300" />
|
||||||
</span>
|
</span>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="add-circle" class="scale-150 !bg-base-300" />
|
<Icon icon={AddCircle} class="scale-150 !bg-base-300" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if !url}
|
{#if !url}
|
||||||
<Icon icon="gallery-send" size={7} />
|
<Icon icon={GallerySend} size={7} />
|
||||||
{/if}
|
{/if}
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Login from "@assets/icons/login-3.svg?dataurl"
|
||||||
|
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
@@ -6,8 +8,8 @@
|
|||||||
import CardButton from "@lib/components/CardButton.svelte"
|
import CardButton from "@lib/components/CardButton.svelte"
|
||||||
import LogIn from "@app/components/LogIn.svelte"
|
import LogIn from "@app/components/LogIn.svelte"
|
||||||
import SignUp from "@app/components/SignUp.svelte"
|
import SignUp from "@app/components/SignUp.svelte"
|
||||||
import {PLATFORM_TERMS, PLATFORM_PRIVACY, PLATFORM_NAME} from "@app/state"
|
import {PLATFORM_TERMS, PLATFORM_PRIVACY, PLATFORM_NAME} from "@app/core/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
const logIn = () => pushModal(LogIn)
|
const logIn = () => pushModal(LogIn)
|
||||||
|
|
||||||
@@ -21,9 +23,9 @@
|
|||||||
<p class="text-center">The chat app built for self-hosted communities.</p>
|
<p class="text-center">The chat app built for self-hosted communities.</p>
|
||||||
</div>
|
</div>
|
||||||
<Button onclick={logIn}>
|
<Button onclick={logIn}>
|
||||||
<CardButton class="!btn-primary">
|
<CardButton class="btn-primary">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><Icon icon="login-2" size={7} /></div>
|
<div><Icon icon={Login} size={7} /></div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>Log in</div>
|
<div>Log in</div>
|
||||||
@@ -33,10 +35,10 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
</CardButton>
|
</CardButton>
|
||||||
</Button>
|
</Button>
|
||||||
<Button onclick={signUp}>
|
<Button onclick={signUp} class="dark:btn-neutral">
|
||||||
<CardButton>
|
<CardButton>
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><Icon icon="add-circle" size={7} /></div>
|
<div><Icon icon={AddCircle} size={7} /></div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>Create an account</div>
|
<div>Create an account</div>
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
import {Capacitor} from "@capacitor/core"
|
import {Capacitor} from "@capacitor/core"
|
||||||
import {getNip07, getNip55, Nip55Signer} from "@welshman/signer"
|
import {getNip07, getNip55, Nip55Signer} from "@welshman/signer"
|
||||||
import {addSession, type Session, makeNip07Session, makeNip55Session} from "@welshman/app"
|
import {addSession, type Session, makeNip07Session, makeNip55Session} from "@welshman/app"
|
||||||
|
import Widget from "@assets/icons/widget-2.svg?dataurl"
|
||||||
|
import Key from "@assets/icons/key-minimalistic.svg?dataurl"
|
||||||
|
import Cpu from "@assets/icons/cpu-bolt.svg?dataurl"
|
||||||
|
import Compass from "@assets/icons/compass-big.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
@@ -10,11 +14,11 @@
|
|||||||
import InfoNostr from "@app/components/InfoNostr.svelte"
|
import InfoNostr from "@app/components/InfoNostr.svelte"
|
||||||
import LogInBunker from "@app/components/LogInBunker.svelte"
|
import LogInBunker from "@app/components/LogInBunker.svelte"
|
||||||
import LogInPassword from "@app/components/LogInPassword.svelte"
|
import LogInPassword from "@app/components/LogInPassword.svelte"
|
||||||
import {pushModal, clearModals} from "@app/modal"
|
import {pushModal, clearModals} from "@app/util/modal"
|
||||||
import {PLATFORM_NAME, BURROW_URL} from "@app/state"
|
import {PLATFORM_NAME, BURROW_URL} from "@app/core/state"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {loadUserData} from "@app/requests"
|
import {loadUserData} from "@app/core/requests"
|
||||||
import {setChecked} from "@app/notifications"
|
import {setChecked} from "@app/util/notifications"
|
||||||
|
|
||||||
let signers: any[] = $state([])
|
let signers: any[] = $state([])
|
||||||
let loading: string | undefined = $state()
|
let loading: string | undefined = $state()
|
||||||
@@ -96,7 +100,7 @@
|
|||||||
{#if loading === "nip07"}
|
{#if loading === "nip07"}
|
||||||
<span class="loading loading-spinner mr-3"></span>
|
<span class="loading loading-spinner mr-3"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="widget" />
|
<Icon icon={Widget} />
|
||||||
{/if}
|
{/if}
|
||||||
Log in with Extension
|
Log in with Extension
|
||||||
</Button>
|
</Button>
|
||||||
@@ -116,7 +120,7 @@
|
|||||||
{#if loading === "password"}
|
{#if loading === "password"}
|
||||||
<span class="loading loading-spinner mr-3"></span>
|
<span class="loading loading-spinner mr-3"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="key" />
|
<Icon icon={Key} />
|
||||||
{/if}
|
{/if}
|
||||||
Log in with Password
|
Log in with Password
|
||||||
</Button>
|
</Button>
|
||||||
@@ -125,7 +129,7 @@
|
|||||||
onclick={loginWithBunker}
|
onclick={loginWithBunker}
|
||||||
{disabled}
|
{disabled}
|
||||||
class="btn {hasSigner || BURROW_URL ? 'btn-neutral' : 'btn-primary'}">
|
class="btn {hasSigner || BURROW_URL ? 'btn-neutral' : 'btn-primary'}">
|
||||||
<Icon icon="cpu" />
|
<Icon icon={Cpu} />
|
||||||
Log in with Remote Signer
|
Log in with Remote Signer
|
||||||
</Button>
|
</Button>
|
||||||
{#if BURROW_URL && hasSigner}
|
{#if BURROW_URL && hasSigner}
|
||||||
@@ -133,7 +137,7 @@
|
|||||||
{#if loading === "password"}
|
{#if loading === "password"}
|
||||||
<span class="loading loading-spinner mr-3"></span>
|
<span class="loading loading-spinner mr-3"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="key" />
|
<Icon icon={Key} />
|
||||||
{/if}
|
{/if}
|
||||||
Log in with Password
|
Log in with Password
|
||||||
</Button>
|
</Button>
|
||||||
@@ -144,7 +148,7 @@
|
|||||||
{disabled}
|
{disabled}
|
||||||
href="https://nostrapps.com#signers"
|
href="https://nostrapps.com#signers"
|
||||||
class="btn {hasSigner || BURROW_URL ? '' : 'btn-neutral'}">
|
class="btn {hasSigner || BURROW_URL ? '' : 'btn-neutral'}">
|
||||||
<Icon icon="compass" />
|
<Icon icon={Compass} />
|
||||||
Browse Signer Apps
|
Browse Signer Apps
|
||||||
</Link>
|
</Link>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,24 +1,34 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import {onMount, onDestroy} from "svelte"
|
||||||
import type {Nip46ResponseWithResult} from "@welshman/signer"
|
import type {Nip46ResponseWithResult} from "@welshman/signer"
|
||||||
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
import {Nip46Broker, makeSecret} from "@welshman/signer"
|
||||||
import {loginWithNip01, loginWithNip46} from "@welshman/app"
|
import {loginWithNip01, loginWithNip46} from "@welshman/app"
|
||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
|
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import BunkerConnect, {BunkerConnectController} from "@app/components/BunkerConnect.svelte"
|
import BunkerConnect from "@app/components/BunkerConnect.svelte"
|
||||||
import BunkerUrl from "@app/components/BunkerUrl.svelte"
|
import BunkerUrl from "@app/components/BunkerUrl.svelte"
|
||||||
import {loadUserData} from "@app/requests"
|
import {Nip46Controller} from "@app/util/nip46"
|
||||||
import {clearModals} from "@app/modal"
|
import {loadUserData} from "@app/core/requests"
|
||||||
import {setChecked} from "@app/notifications"
|
import {clearModals} from "@app/util/modal"
|
||||||
import {pushToast} from "@app/toast"
|
import {setChecked} from "@app/util/notifications"
|
||||||
import {SIGNER_RELAYS, NIP46_PERMS} from "@app/state"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
import {SIGNER_RELAYS, NIP46_PERMS} from "@app/core/state"
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => {
|
||||||
|
if (mode === "connect") {
|
||||||
|
selectBunker()
|
||||||
|
} else {
|
||||||
|
history.back()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const controller = new BunkerConnectController({
|
const controller = new Nip46Controller({
|
||||||
onNostrConnect: async (response: Nip46ResponseWithResult) => {
|
onNostrConnect: async (response: Nip46ResponseWithResult) => {
|
||||||
const pubkey = await controller.broker.getPublicKey()
|
const pubkey = await controller.broker.getPublicKey()
|
||||||
|
|
||||||
@@ -30,13 +40,13 @@
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const {loading, bunker} = controller
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
if (controller.loading) return
|
if ($loading) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {signerPubkey, connectSecret, relays} = Nip46Broker.parseBunkerUrl(controller.bunker)
|
const {signerPubkey, connectSecret, relays} = Nip46Broker.parseBunkerUrl($bunker)
|
||||||
|
|
||||||
console.log({signerPubkey, connectSecret, relays})
|
|
||||||
|
|
||||||
if (!signerPubkey || relays.length === 0) {
|
if (!signerPubkey || relays.length === 0) {
|
||||||
return pushToast({
|
return pushToast({
|
||||||
@@ -45,7 +55,7 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.loading = true
|
controller.loading.set(true)
|
||||||
|
|
||||||
const {clientSecret} = controller
|
const {clientSecret} = controller
|
||||||
const broker = new Nip46Broker({relays, clientSecret, signerPubkey})
|
const broker = new Nip46Broker({relays, clientSecret, signerPubkey})
|
||||||
@@ -74,42 +84,65 @@
|
|||||||
message: "Something went wrong, please try again!",
|
message: "Something went wrong, please try again!",
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
controller.loading = false
|
controller.loading.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
clearModals()
|
clearModals()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectConnect = () => {
|
||||||
|
mode = "connect"
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectBunker = () => {
|
||||||
|
mode = "bunker"
|
||||||
|
}
|
||||||
|
|
||||||
|
let mode: string = $state("bunker")
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
// For testing and for play store reviewers
|
// For testing and for play store reviewers
|
||||||
if (controller.bunker === "reviewkey") {
|
if ($bunker === "reviewkey") {
|
||||||
loginWithNip01(makeSecret())
|
loginWithNip01(makeSecret())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
controller.start()
|
||||||
|
})
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
controller.stop()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
|
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>Log In</div>
|
<div>Log In with a Signer</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet info()}
|
{#snippet info()}
|
||||||
<div>Connect your signer by scanning the QR code below or pasting a bunker link.</div>
|
<div>Using a remote signer app helps you keep your keys safe.</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<BunkerConnect {controller} />
|
<div class:hidden={mode !== "bunker"}></div>
|
||||||
<BunkerUrl loading={controller.loading} bind:bunker={controller.bunker} />
|
{#if mode === "connect"}
|
||||||
|
<BunkerConnect {controller} />
|
||||||
|
{:else}
|
||||||
|
<BunkerUrl {controller} />
|
||||||
|
<Button class="btn {$bunker ? 'btn-neutral' : 'btn-primary'}" onclick={selectConnect}
|
||||||
|
>Log in with a QR code instead</Button>
|
||||||
|
{/if}
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back} disabled={controller.loading}>
|
<Button class="btn btn-link" onclick={back} disabled={$loading}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
{#if mode === "bunker"}
|
||||||
type="submit"
|
<Button type="submit" class="btn btn-primary" disabled={$loading || !$bunker}>
|
||||||
class="btn btn-primary"
|
<Spinner loading={$loading}>Next</Spinner>
|
||||||
disabled={controller.loading || !controller.bunker}>
|
<Icon icon={AltArrowRight} />
|
||||||
<Spinner loading={controller.loading}>Next</Spinner>
|
</Button>
|
||||||
<Icon icon="alt-arrow-right" />
|
{/if}
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -8,15 +8,25 @@
|
|||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||||
|
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||||
|
import Key from "@assets/icons/key-minimalistic.svg?dataurl"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
|
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import PasswordResetRequest from "@app/components/PasswordResetRequest.svelte"
|
import PasswordResetRequest from "@app/components/PasswordResetRequest.svelte"
|
||||||
import {loadUserData} from "@app/requests"
|
import {loadUserData} from "@app/core/requests"
|
||||||
import {clearModals, pushModal} from "@app/modal"
|
import {clearModals, pushModal} from "@app/util/modal"
|
||||||
import {setChecked} from "@app/notifications"
|
import {setChecked} from "@app/util/notifications"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {NIP46_PERMS, BURROW_URL, PLATFORM_URL, PLATFORM_NAME, PLATFORM_LOGO} from "@app/state"
|
import {
|
||||||
|
NIP46_PERMS,
|
||||||
|
BURROW_URL,
|
||||||
|
PLATFORM_URL,
|
||||||
|
PLATFORM_NAME,
|
||||||
|
PLATFORM_LOGO,
|
||||||
|
} from "@app/core/state"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
email?: string
|
email?: string
|
||||||
@@ -115,7 +125,7 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
<Icon icon="user-rounded" />
|
<Icon icon={UserRounded} />
|
||||||
<input bind:value={email} />
|
<input bind:value={email} />
|
||||||
</label>
|
</label>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
@@ -126,7 +136,7 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
<Icon icon="key" />
|
<Icon icon={Key} />
|
||||||
<input bind:value={password} type="password" />
|
<input bind:value={password} type="password" />
|
||||||
</label>
|
</label>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
@@ -138,12 +148,12 @@
|
|||||||
</p>
|
</p>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back} disabled={loading}>
|
<Button class="btn btn-link" onclick={back} disabled={loading}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" class="btn btn-primary" disabled={loading || !email || !password}>
|
<Button type="submit" class="btn btn-primary" disabled={loading || !email || !password}>
|
||||||
<Spinner {loading}>Next</Spinner>
|
<Spinner {loading}>Next</Spinner>
|
||||||
<Icon icon="alt-arrow-right" />
|
<Icon icon={AltArrowRight} />
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import {logout} from "@app/commands"
|
import {logout} from "@app/core/commands"
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
<p class="text-center">Your local database will be cleared.</p>
|
<p class="text-center">Your local database will be cleared.</p>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back}>
|
<Button class="btn btn-link" onclick={back}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||||
|
|||||||
@@ -1,20 +1,27 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||||
|
import Server from "@assets/icons/server.svg?dataurl"
|
||||||
|
import Settings from "@assets/icons/settings-minimalistic.svg?dataurl"
|
||||||
|
import Code2 from "@assets/icons/code-2.svg?dataurl"
|
||||||
|
import Exit from "@assets/icons/logout-3.svg?dataurl"
|
||||||
|
import Bell from "@assets/icons/bell.svg?dataurl"
|
||||||
|
import Wallet from "@assets/icons/wallet.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import CardButton from "@lib/components/CardButton.svelte"
|
import CardButton from "@lib/components/CardButton.svelte"
|
||||||
import LogOut from "@app/components/LogOut.svelte"
|
import LogOut from "@app/components/LogOut.svelte"
|
||||||
import {PLATFORM_NAME} from "@app/state"
|
import {PLATFORM_NAME} from "@app/core/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
const logout = () => pushModal(LogOut)
|
const logout = () => pushModal(LogOut)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column menu gap-2">
|
<div class="column menu gap-2">
|
||||||
<Link replaceState href="/settings/profile">
|
<Link replaceState href="/settings/profile">
|
||||||
<CardButton>
|
<CardButton class="dark:btn-neutral">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><Icon icon="user-rounded" size={7} /></div>
|
<div><Icon icon={UserRounded} size={7} /></div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>Profile</div>
|
<div>Profile</div>
|
||||||
@@ -24,10 +31,36 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
</CardButton>
|
</CardButton>
|
||||||
</Link>
|
</Link>
|
||||||
<Link replaceState href="/settings/relays">
|
<Link replaceState href="/settings/alerts">
|
||||||
<CardButton>
|
<CardButton class="dark:btn-neutral">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><Icon icon="server" size={7} /></div>
|
<div><Icon icon={Bell} size={7} /></div>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet title()}
|
||||||
|
<div>Alerts</div>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet info()}
|
||||||
|
<div>Set up email digests and push notifications</div>
|
||||||
|
{/snippet}
|
||||||
|
</CardButton>
|
||||||
|
</Link>
|
||||||
|
<Link replaceState href="/settings/wallet">
|
||||||
|
<CardButton class="dark:btn-neutral">
|
||||||
|
{#snippet icon()}
|
||||||
|
<div><Icon icon={Wallet} size={7} /></div>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet title()}
|
||||||
|
<div>Wallet</div>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet info()}
|
||||||
|
<div>Connect a bitcoin wallet for sending social tips</div>
|
||||||
|
{/snippet}
|
||||||
|
</CardButton>
|
||||||
|
</Link>
|
||||||
|
<Link replaceState href="/settings/relays">
|
||||||
|
<CardButton class="dark:btn-neutral">
|
||||||
|
{#snippet icon()}
|
||||||
|
<div><Icon icon={Server} size={7} /></div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>Relays</div>
|
<div>Relays</div>
|
||||||
@@ -37,10 +70,10 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
</CardButton>
|
</CardButton>
|
||||||
</Link>
|
</Link>
|
||||||
<Link replaceState href="/settings">
|
<Link replaceState href="/settings/content">
|
||||||
<CardButton>
|
<CardButton class="dark:btn-neutral">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><Icon icon="settings" size={7} /></div>
|
<div><Icon icon={Settings} size={7} /></div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>Settings</div>
|
<div>Settings</div>
|
||||||
@@ -51,9 +84,9 @@
|
|||||||
</CardButton>
|
</CardButton>
|
||||||
</Link>
|
</Link>
|
||||||
<Link replaceState href="/settings/about">
|
<Link replaceState href="/settings/about">
|
||||||
<CardButton>
|
<CardButton class="dark:btn-neutral">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><Icon icon="code-2" size={7} /></div>
|
<div><Icon icon={Code2} size={7} /></div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>About</div>
|
<div>About</div>
|
||||||
@@ -64,6 +97,6 @@
|
|||||||
</CardButton>
|
</CardButton>
|
||||||
</Link>
|
</Link>
|
||||||
<Button onclick={logout} class="btn btn-neutral">
|
<Button onclick={logout} class="btn btn-neutral">
|
||||||
<Icon icon="exit" /> Log Out
|
<Icon icon={Exit} /> Log Out
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
import {displayRelayUrl, getTagValue} from "@welshman/util"
|
import {displayRelayUrl, getTagValue} from "@welshman/util"
|
||||||
import {deriveRelay} from "@welshman/app"
|
import {deriveRelay} from "@welshman/app"
|
||||||
import {fly} from "@lib/transition"
|
import {fly} from "@lib/transition"
|
||||||
|
import AltArrowDown from "@assets/icons/alt-arrow-down.svg?dataurl"
|
||||||
|
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||||
|
import LinkRound from "@assets/icons/link-round.svg?dataurl"
|
||||||
|
import Exit from "@assets/icons/logout-3.svg?dataurl"
|
||||||
|
import Login from "@assets/icons/login-3.svg?dataurl"
|
||||||
|
import HomeSmile from "@assets/icons/home-smile.svg?dataurl"
|
||||||
|
import StarFallMinimalistic from "@assets/icons/star-fall-minimalistic-2.svg?dataurl"
|
||||||
|
import NotesMinimalistic from "@assets/icons/notes-minimalistic.svg?dataurl"
|
||||||
|
import CalendarMinimalistic from "@assets/icons/calendar-minimalistic.svg?dataurl"
|
||||||
|
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
|
||||||
|
import ChatRound from "@assets/icons/chat-round.svg?dataurl"
|
||||||
|
import Bell from "@assets/icons/bell.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Popover from "@lib/components/Popover.svelte"
|
import Popover from "@lib/components/Popover.svelte"
|
||||||
@@ -26,10 +38,10 @@
|
|||||||
deriveOtherRooms,
|
deriveOtherRooms,
|
||||||
hasNip29,
|
hasNip29,
|
||||||
alerts,
|
alerts,
|
||||||
} from "@app/state"
|
} from "@app/core/state"
|
||||||
import {notifications} from "@app/notifications"
|
import {notifications} from "@app/util/notifications"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
import {makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
|
|
||||||
const {url} = $props()
|
const {url} = $props()
|
||||||
|
|
||||||
@@ -92,7 +104,7 @@
|
|||||||
<strong class="ellipsize flex items-center gap-3">
|
<strong class="ellipsize flex items-center gap-3">
|
||||||
{displayRelayUrl(url)}
|
{displayRelayUrl(url)}
|
||||||
</strong>
|
</strong>
|
||||||
<Icon icon="alt-arrow-down" />
|
<Icon icon={AltArrowDown} />
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
{#if showMenu}
|
{#if showMenu}
|
||||||
<Popover hideOnClick onClose={toggleMenu}>
|
<Popover hideOnClick onClose={toggleMenu}>
|
||||||
@@ -101,25 +113,25 @@
|
|||||||
class="menu absolute z-popover mt-2 w-full gap-1 rounded-box bg-base-100 p-2 shadow-xl">
|
class="menu absolute z-popover mt-2 w-full gap-1 rounded-box bg-base-100 p-2 shadow-xl">
|
||||||
<li>
|
<li>
|
||||||
<Button onclick={showMembers}>
|
<Button onclick={showMembers}>
|
||||||
<Icon icon="user-rounded" />
|
<Icon icon={UserRounded} />
|
||||||
View Members ({members.length})
|
View Members ({members.length})
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Button onclick={createInvite}>
|
<Button onclick={createInvite}>
|
||||||
<Icon icon="link-round" />
|
<Icon icon={LinkRound} />
|
||||||
Create Invite
|
Create Invite
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
{#if $userRoomsByUrl.has(url)}
|
{#if $userRoomsByUrl.has(url)}
|
||||||
<Button onclick={leaveSpace} class="text-error">
|
<Button onclick={leaveSpace} class="text-error">
|
||||||
<Icon icon="exit" />
|
<Icon icon={Exit} />
|
||||||
Leave Space
|
Leave Space
|
||||||
</Button>
|
</Button>
|
||||||
{:else}
|
{:else}
|
||||||
<Button onclick={joinSpace} class="bg-primary text-primary-content">
|
<Button onclick={joinSpace} class="bg-primary text-primary-content">
|
||||||
<Icon icon="login-2" />
|
<Icon icon={Login} />
|
||||||
Join Space
|
Join Space
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -130,27 +142,27 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex max-h-[calc(100vh-150px)] min-h-0 flex-col gap-1 overflow-auto">
|
<div class="flex max-h-[calc(100vh-150px)] min-h-0 flex-col gap-1 overflow-auto">
|
||||||
<SecondaryNavItem {replaceState} href={makeSpacePath(url)}>
|
<SecondaryNavItem {replaceState} href={makeSpacePath(url)}>
|
||||||
<Icon icon="home-smile" /> Home
|
<Icon icon={HomeSmile} /> Home
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
{#if ENABLE_ZAPS}
|
{#if ENABLE_ZAPS}
|
||||||
<SecondaryNavItem
|
<SecondaryNavItem
|
||||||
{replaceState}
|
{replaceState}
|
||||||
href={goalsPath}
|
href={goalsPath}
|
||||||
notification={$notifications.has(goalsPath)}>
|
notification={$notifications.has(goalsPath)}>
|
||||||
<Icon icon="star-fall-minimalistic-2" /> Goals
|
<Icon icon={StarFallMinimalistic} /> Goals
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
{/if}
|
{/if}
|
||||||
<SecondaryNavItem
|
<SecondaryNavItem
|
||||||
{replaceState}
|
{replaceState}
|
||||||
href={threadsPath}
|
href={threadsPath}
|
||||||
notification={$notifications.has(threadsPath)}>
|
notification={$notifications.has(threadsPath)}>
|
||||||
<Icon icon="notes-minimalistic" /> Threads
|
<Icon icon={NotesMinimalistic} /> Threads
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
<SecondaryNavItem
|
<SecondaryNavItem
|
||||||
{replaceState}
|
{replaceState}
|
||||||
href={calendarPath}
|
href={calendarPath}
|
||||||
notification={$notifications.has(calendarPath)}>
|
notification={$notifications.has(calendarPath)}>
|
||||||
<Icon icon="calendar-minimalistic" /> Calendar
|
<Icon icon={CalendarMinimalistic} /> Calendar
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
{#if hasNip29($relay)}
|
{#if hasNip29($relay)}
|
||||||
{#if $userRooms.length > 0}
|
{#if $userRooms.length > 0}
|
||||||
@@ -174,7 +186,7 @@
|
|||||||
<MenuSpaceRoomItem {replaceState} {url} {room} />
|
<MenuSpaceRoomItem {replaceState} {url} {room} />
|
||||||
{/each}
|
{/each}
|
||||||
<SecondaryNavItem {replaceState} onclick={addRoom}>
|
<SecondaryNavItem {replaceState} onclick={addRoom}>
|
||||||
<Icon icon="add-circle" />
|
<Icon icon={AddCircle} />
|
||||||
Create room
|
Create room
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -182,14 +194,14 @@
|
|||||||
{replaceState}
|
{replaceState}
|
||||||
href={chatPath}
|
href={chatPath}
|
||||||
notification={$notifications.has(chatPath)}>
|
notification={$notifications.has(chatPath)}>
|
||||||
<Icon icon="chat-round" /> Chat
|
<Icon icon={ChatRound} /> Chat
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</SecondaryNavSection>
|
</SecondaryNavSection>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<button class="btn btn-neutral btn-sm w-full" onclick={manageAlerts}>
|
<button class="btn btn-neutral btn-sm w-full" onclick={manageAlerts}>
|
||||||
<Icon icon="bell" />
|
<Icon icon={Bell} />
|
||||||
Manage Alerts
|
Manage Alerts
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import MenuSpace from "@app/components/MenuSpace.svelte"
|
import MenuSpace from "@app/components/MenuSpace.svelte"
|
||||||
import {notifications} from "@app/notifications"
|
import {notifications} from "@app/util/notifications"
|
||||||
import {makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
import {pushDrawer} from "@app/modal"
|
import {pushDrawer} from "@app/util/modal"
|
||||||
|
|
||||||
const {url} = $props()
|
const {url} = $props()
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button onclick={openMenu} class="btn btn-neutral btn-sm relative md:hidden">
|
<Button onclick={openMenu} class="btn btn-neutral btn-sm relative md:hidden">
|
||||||
<Icon icon="menu-dots" />
|
<Icon icon={MenuDots} />
|
||||||
{#if $notifications.has(path)}
|
{#if $notifications.has(path)}
|
||||||
<div class="absolute right-0 top-0 -mr-1 -mt-1 h-2 w-2 rounded-full bg-primary"></div>
|
<div class="absolute right-0 top-0 -mr-1 -mt-1 h-2 w-2 rounded-full bg-primary"></div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Lock from "@assets/icons/lock-keyhole.svg?dataurl"
|
||||||
|
import Hashtag from "@assets/icons/hashtag.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
|
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
|
||||||
import ChannelName from "@app/components/ChannelName.svelte"
|
import ChannelName from "@app/components/ChannelName.svelte"
|
||||||
import {makeRoomPath} from "@app/routes"
|
import {makeRoomPath} from "@app/util/routes"
|
||||||
import {deriveChannel} from "@app/state"
|
import {deriveChannel} from "@app/core/state"
|
||||||
import {notifications} from "@app/notifications"
|
import {notifications} from "@app/util/notifications"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
url: any
|
url: any
|
||||||
@@ -24,9 +26,9 @@
|
|||||||
{replaceState}
|
{replaceState}
|
||||||
notification={notify ? $notifications.has(path) : false}>
|
notification={notify ? $notifications.has(path) : false}>
|
||||||
{#if $channel?.closed || $channel?.private}
|
{#if $channel?.closed || $channel?.private}
|
||||||
<Icon icon="lock" size={4} />
|
<Icon icon={Lock} size={4} />
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="hashtag" />
|
<Icon icon={Hashtag} />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="min-w-0 overflow-hidden text-ellipsis">
|
<div class="min-w-0 overflow-hidden text-ellipsis">
|
||||||
<ChannelName {url} {room} />
|
<ChannelName {url} {room} />
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Login from "@assets/icons/login-3.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Divider from "@lib/components/Divider.svelte"
|
import Divider from "@lib/components/Divider.svelte"
|
||||||
import CardButton from "@lib/components/CardButton.svelte"
|
import CardButton from "@lib/components/CardButton.svelte"
|
||||||
import MenuSpacesItem from "@app/components/MenuSpacesItem.svelte"
|
import MenuSpacesItem from "@app/components/MenuSpacesItem.svelte"
|
||||||
import SpaceAdd from "@app/components/SpaceAdd.svelte"
|
import SpaceAdd from "@app/components/SpaceAdd.svelte"
|
||||||
import {userRoomsByUrl, PLATFORM_RELAYS} from "@app/state"
|
import {userRoomsByUrl, PLATFORM_RELAYS} from "@app/core/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
const addSpace = () => pushModal(SpaceAdd)
|
const addSpace = () => pushModal(SpaceAdd)
|
||||||
</script>
|
</script>
|
||||||
@@ -22,9 +23,9 @@
|
|||||||
<Divider />
|
<Divider />
|
||||||
{/if}
|
{/if}
|
||||||
<Button onclick={addSpace}>
|
<Button onclick={addSpace}>
|
||||||
<CardButton>
|
<CardButton class="dark:btn-neutral">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><Icon icon="login-2" size={7} /></div>
|
<div><Icon icon={Login} size={7} /></div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>Add a space</div>
|
<div>Add a space</div>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
import SpaceAvatar from "@app/components/SpaceAvatar.svelte"
|
import SpaceAvatar from "@app/components/SpaceAvatar.svelte"
|
||||||
import RelayName from "@app/components/RelayName.svelte"
|
import RelayName from "@app/components/RelayName.svelte"
|
||||||
import RelayDescription from "@app/components/RelayDescription.svelte"
|
import RelayDescription from "@app/components/RelayDescription.svelte"
|
||||||
import {makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
import {notifications} from "@app/notifications"
|
import {notifications} from "@app/util/notifications"
|
||||||
|
|
||||||
const {url} = $props()
|
const {url} = $props()
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link replaceState href={path}>
|
<Link replaceState href={path}>
|
||||||
<CardButton>
|
<CardButton class="dark:btn-neutral">
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><SpaceAvatar {url} /></div>
|
<div><SpaceAvatar {url} /></div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {page} from "$app/stores"
|
|
||||||
import Drawer from "@lib/components/Drawer.svelte"
|
import Drawer from "@lib/components/Drawer.svelte"
|
||||||
import Dialog from "@lib/components/Dialog.svelte"
|
import Dialog from "@lib/components/Dialog.svelte"
|
||||||
import {modals, clearModals} from "@app/modal"
|
import {modal, clearModals} from "@app/util/modal"
|
||||||
|
|
||||||
const onKeyDown = (e: any) => {
|
const onKeyDown = (e: any) => {
|
||||||
if (e.code === "Escape" && e.target === document.body) {
|
if (e.code === "Escape" && e.target === document.body) {
|
||||||
@@ -10,22 +9,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hash = $derived($page.url.hash.slice(1))
|
const m = $derived($modal)
|
||||||
const modal = $derived($modals[hash])
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window onkeydown={onKeyDown} />
|
<svelte:window onkeydown={onKeyDown} />
|
||||||
|
|
||||||
{#if modal?.options?.drawer}
|
{#if m?.options?.drawer}
|
||||||
<Drawer onClose={clearModals} {...modal.options}>
|
<Drawer onClose={clearModals} {...m.options}>
|
||||||
{#key modal.id}
|
{#key m.id}
|
||||||
<modal.component {...modal.props} />
|
<m.component {...m.props} />
|
||||||
{/key}
|
{/key}
|
||||||
</Drawer>
|
</Drawer>
|
||||||
{:else if modal}
|
{:else if m}
|
||||||
<Dialog onClose={clearModals} {...modal.options}>
|
<Dialog onClose={clearModals} {...m.options}>
|
||||||
{#key modal.id}
|
{#key m.id}
|
||||||
<modal.component {...modal.props} />
|
<m.component {...m.props} />
|
||||||
{/key}
|
{/key}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -8,11 +8,12 @@
|
|||||||
import {Router} from "@welshman/router"
|
import {Router} from "@welshman/router"
|
||||||
import {userMutes} from "@welshman/app"
|
import {userMutes} from "@welshman/app"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Profile from "@app/components/Profile.svelte"
|
import Profile from "@app/components/Profile.svelte"
|
||||||
import ProfileName from "@app/components/ProfileName.svelte"
|
import ProfileName from "@app/components/ProfileName.svelte"
|
||||||
import {entityLink} from "@app/state"
|
import {entityLink} from "@app/core/state"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
event,
|
event,
|
||||||
@@ -44,7 +45,7 @@
|
|||||||
{#if muted}
|
{#if muted}
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="row-2 relative">
|
<div class="row-2 relative">
|
||||||
<Icon icon="danger" class="mt-1" />
|
<Icon icon={Danger} class="mt-1" />
|
||||||
<p>You have muted this person.</p>
|
<p>You have muted this person.</p>
|
||||||
</div>
|
</div>
|
||||||
<Button class="link ml-8" onclick={ignoreMute}>Show anyway</Button>
|
<Button class="link ml-8" onclick={ignoreMute}>Show anyway</Button>
|
||||||
|
|||||||
@@ -1,22 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {NativeEmoji} from "emoji-picker-element/shared"
|
import type {NativeEmoji} from "emoji-picker-element/shared"
|
||||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
|
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
||||||
import NoteContent from "@app/components/NoteContent.svelte"
|
import NoteContent from "@app/components/NoteContent.svelte"
|
||||||
import NoteCard from "@app/components/NoteCard.svelte"
|
import NoteCard from "@app/components/NoteCard.svelte"
|
||||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||||
import {publishDelete, publishReaction} from "@app/commands"
|
import {publishDelete, publishReaction, canEnforceNip70} from "@app/core/commands"
|
||||||
|
|
||||||
const {url, event} = $props()
|
const {url, event} = $props()
|
||||||
|
|
||||||
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const createReaction = (template: EventContent) =>
|
const deleteReaction = async (event: TrustedEvent) =>
|
||||||
publishReaction({...template, event, relays: [url]})
|
publishDelete({relays: [url], event, protect: await shouldProtect})
|
||||||
|
|
||||||
const onEmoji = (emoji: NativeEmoji) =>
|
const createReaction = async (template: EventContent) =>
|
||||||
publishReaction({event, content: emoji.unicode, relays: [url]})
|
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||||
|
|
||||||
|
const onEmoji = async (emoji: NativeEmoji) =>
|
||||||
|
publishReaction({
|
||||||
|
event,
|
||||||
|
content: emoji.unicode,
|
||||||
|
relays: [url],
|
||||||
|
protect: await shouldProtect,
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<NoteCard {event} {url} class="card2 bg-alt">
|
<NoteCard {event} {url} class="card2 bg-alt">
|
||||||
@@ -24,7 +33,7 @@
|
|||||||
<div class="flex w-full justify-between gap-2">
|
<div class="flex w-full justify-between gap-2">
|
||||||
<ReactionSummary {url} {event} {deleteReaction} {createReaction} reactionClass="tooltip-right">
|
<ReactionSummary {url} {event} {deleteReaction} {createReaction} reactionClass="tooltip-right">
|
||||||
<EmojiButton {onEmoji} class="btn btn-neutral btn-xs h-[26px] rounded-box">
|
<EmojiButton {onEmoji} class="btn btn-neutral btn-xs h-[26px] rounded-box">
|
||||||
<Icon icon="smile-circle" size={4} />
|
<Icon icon={SmileCircle} size={4} />
|
||||||
</EmojiButton>
|
</EmojiButton>
|
||||||
</ReactionSummary>
|
</ReactionSummary>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,13 +3,15 @@
|
|||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
|
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||||
|
import Key from "@assets/icons/key-minimalistic.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import LogInPassword from "@app/components/LogInPassword.svelte"
|
import LogInPassword from "@app/components/LogInPassword.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {BURROW_URL} from "@app/state"
|
import {BURROW_URL} from "@app/core/state"
|
||||||
|
|
||||||
const {email, reset_token} = $props()
|
const {email, reset_token} = $props()
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
<Icon icon="user-rounded" />
|
<Icon icon={UserRounded} />
|
||||||
<input readonly value={email} class="grow" />
|
<input readonly value={email} class="grow" />
|
||||||
</label>
|
</label>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
@@ -60,7 +62,7 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
<Icon icon="key" />
|
<Icon icon={Key} />
|
||||||
<input bind:value={password} class="grow" type="password" />
|
<input bind:value={password} class="grow" type="password" />
|
||||||
</label>
|
</label>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {postJson, sleep} from "@welshman/lib"
|
import {postJson, sleep} from "@welshman/lib"
|
||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
|
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||||
@@ -8,9 +10,9 @@
|
|||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
import LogInPassword from "@app/components/LogInPassword.svelte"
|
import LogInPassword from "@app/components/LogInPassword.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {BURROW_URL} from "@app/state"
|
import {BURROW_URL} from "@app/core/state"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
email: string
|
email: string
|
||||||
@@ -55,7 +57,7 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
<Icon icon="user-rounded" />
|
<Icon icon={UserRounded} />
|
||||||
<input bind:value={email} class="grow" />
|
<input bind:value={email} class="grow" />
|
||||||
</label>
|
</label>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
@@ -65,7 +67,7 @@
|
|||||||
</FieldInline>
|
</FieldInline>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back}>
|
<Button class="btn btn-link" onclick={back}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon={AltArrowLeft} />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Profile from "@app/components/Profile.svelte"
|
import Profile from "@app/components/Profile.svelte"
|
||||||
import ProfileInfo from "@app/components/ProfileInfo.svelte"
|
import ProfileInfo from "@app/components/ProfileInfo.svelte"
|
||||||
import ProfileBadges from "@app/components/ProfileBadges.svelte"
|
import ProfileBadges from "@app/components/ProfileBadges.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
@@ -21,14 +22,14 @@
|
|||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<Profile {pubkey} {url} />
|
<Profile {pubkey} {url} />
|
||||||
<Button onclick={openProfile} class="btn btn-primary hidden sm:flex">
|
<Button onclick={openProfile} class="btn btn-primary hidden sm:flex">
|
||||||
<Icon icon="user-circle" />
|
<Icon icon={UserCircle} />
|
||||||
View Profile
|
View Profile
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<ProfileInfo {pubkey} {url} />
|
<ProfileInfo {pubkey} {url} />
|
||||||
<ProfileBadges {pubkey} {url} />
|
<ProfileBadges {pubkey} {url} />
|
||||||
<Button onclick={openProfile} class="btn btn-primary sm:hidden">
|
<Button onclick={openProfile} class="btn btn-primary sm:hidden">
|
||||||
<Icon icon="user-circle" />
|
<Icon icon={UserCircle} />
|
||||||
View Profile
|
View Profile
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,10 +13,17 @@
|
|||||||
import MenuOtherSpaces from "@app/components/MenuOtherSpaces.svelte"
|
import MenuOtherSpaces from "@app/components/MenuOtherSpaces.svelte"
|
||||||
import MenuSettings from "@app/components/MenuSettings.svelte"
|
import MenuSettings from "@app/components/MenuSettings.svelte"
|
||||||
import PrimaryNavItemSpace from "@app/components/PrimaryNavItemSpace.svelte"
|
import PrimaryNavItemSpace from "@app/components/PrimaryNavItemSpace.svelte"
|
||||||
import {userRoomsByUrl, canDecrypt, PLATFORM_RELAYS, PLATFORM_LOGO} from "@app/state"
|
import {userRoomsByUrl, canDecrypt, PLATFORM_RELAYS, PLATFORM_LOGO} from "@app/core/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
import {makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
import {notifications} from "@app/notifications"
|
import {notifications} from "@app/util/notifications"
|
||||||
|
import Widget from "@assets/icons/widget.svg?dataurl"
|
||||||
|
import AddSquare from "@assets/icons/add-square.svg?dataurl"
|
||||||
|
import Letter from "@assets/icons/letter.svg?dataurl"
|
||||||
|
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
||||||
|
import HomeSmile from "@assets/icons/home-smile.svg?dataurl"
|
||||||
|
import SettingsMinimalistic from "@assets/icons/settings-minimalistic.svg?dataurl"
|
||||||
|
import Settings from "@assets/icons/settings.svg?dataurl"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?: Snippet
|
children?: Snippet
|
||||||
@@ -55,7 +62,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="ml-sai mt-sai mb-sai relative z-nav hidden w-14 flex-shrink-0 bg-base-200 pt-4 md:block">
|
class="ml-sai mt-sai mb-sai relative z-nav hidden w-14 flex-shrink-0 bg-base-200 pt-4 md:block">
|
||||||
<div class="flex h-full flex-col justify-between">
|
<div class="flex h-full flex-col" class:justify-between={PLATFORM_RELAYS.length === 0}>
|
||||||
<div>
|
<div>
|
||||||
{#each PLATFORM_RELAYS as url (url)}
|
{#each PLATFORM_RELAYS as url (url)}
|
||||||
<PrimaryNavItemSpace {url} />
|
<PrimaryNavItemSpace {url} />
|
||||||
@@ -73,14 +80,17 @@
|
|||||||
class="tooltip-right"
|
class="tooltip-right"
|
||||||
onclick={showOtherSpacesMenu}
|
onclick={showOtherSpacesMenu}
|
||||||
notification={otherSpaceNotifications}>
|
notification={otherSpaceNotifications}>
|
||||||
<Avatar icon="widget" class="!h-10 !w-10" />
|
<Avatar icon={Widget} class="!h-10 !w-10" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
{/if}
|
{/if}
|
||||||
<PrimaryNavItem title="Add Space" onclick={addSpace} class="tooltip-right">
|
<PrimaryNavItem title="Add Space" onclick={addSpace} class="tooltip-right">
|
||||||
<Avatar icon="settings-minimalistic" class="!h-10 !w-10" />
|
<Avatar icon={AddSquare} class="!h-10 !w-10" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{#if PLATFORM_RELAYS.length > 0}
|
||||||
|
<Divider />
|
||||||
|
{/if}
|
||||||
<div>
|
<div>
|
||||||
<PrimaryNavItem
|
<PrimaryNavItem
|
||||||
title="Settings"
|
title="Settings"
|
||||||
@@ -94,10 +104,10 @@
|
|||||||
onclick={openChat}
|
onclick={openChat}
|
||||||
class="tooltip-right"
|
class="tooltip-right"
|
||||||
notification={$notifications.has("/chat")}>
|
notification={$notifications.has("/chat")}>
|
||||||
<Avatar icon="letter" class="!h-10 !w-10" />
|
<Avatar icon={Letter} class="!h-10 !w-10" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
<PrimaryNavItem title="Search" href="/people" class="tooltip-right">
|
<PrimaryNavItem title="Search" href="/people" class="tooltip-right">
|
||||||
<Avatar icon="magnifer" class="!h-10 !w-10" />
|
<Avatar icon={Magnifier} class="!h-10 !w-10" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,25 +122,25 @@
|
|||||||
<div class="content-padding-x content-sizing flex justify-between px-2">
|
<div class="content-padding-x content-sizing flex justify-between px-2">
|
||||||
<div class="flex gap-2 sm:gap-8">
|
<div class="flex gap-2 sm:gap-8">
|
||||||
<PrimaryNavItem title="Home" href="/home">
|
<PrimaryNavItem title="Home" href="/home">
|
||||||
<Avatar icon="home-smile" class="!h-10 !w-10" />
|
<Avatar icon={HomeSmile} class="!h-10 !w-10" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
<PrimaryNavItem
|
<PrimaryNavItem
|
||||||
title="Messages"
|
title="Messages"
|
||||||
onclick={openChat}
|
onclick={openChat}
|
||||||
notification={$notifications.has("/chat")}>
|
notification={$notifications.has("/chat")}>
|
||||||
<Avatar icon="letter" class="!h-10 !w-10" />
|
<Avatar icon={Letter} class="!h-10 !w-10" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
{#if PLATFORM_RELAYS.length !== 1}
|
{#if PLATFORM_RELAYS.length !== 1}
|
||||||
<PrimaryNavItem
|
<PrimaryNavItem
|
||||||
title="Spaces"
|
title="Spaces"
|
||||||
onclick={showSpacesMenu}
|
onclick={showSpacesMenu}
|
||||||
notification={anySpaceNotifications}>
|
notification={anySpaceNotifications}>
|
||||||
<Avatar icon="settings-minimalistic" class="!h-10 !w-10" />
|
<Avatar icon={SettingsMinimalistic} class="!h-10 !w-10" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<PrimaryNavItem title="Settings" onclick={showSettingsMenu}>
|
<PrimaryNavItem title="Settings" onclick={showSettingsMenu}>
|
||||||
<Avatar icon="settings" src={$userProfile?.picture} class="!h-10 !w-10" />
|
<Avatar icon={Settings} src={$userProfile?.picture} class="!h-10 !w-10" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import {displayRelayUrl} from "@welshman/util"
|
import {displayRelayUrl} from "@welshman/util"
|
||||||
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
|
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
|
||||||
import SpaceAvatar from "@app/components/SpaceAvatar.svelte"
|
import SpaceAvatar from "@app/components/SpaceAvatar.svelte"
|
||||||
import {makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
import {notifications} from "@app/notifications"
|
import {notifications} from "@app/util/notifications"
|
||||||
|
|
||||||
const {url} = $props()
|
const {url} = $props()
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user