Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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,5 +1,18 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
# 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 24
|
||||||
versionName "1.2.2"
|
versionName "1.2.3"
|
||||||
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.
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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.3;
|
||||||
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.3;
|
||||||
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>
|
||||||
|
|||||||
+12
-12
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "flotilla",
|
"name": "flotilla",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
@@ -56,17 +56,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.3",
|
||||||
"@welshman/content": "^0.4.0",
|
"@welshman/content": "^0.4.3",
|
||||||
"@welshman/editor": "^0.4.0",
|
"@welshman/editor": "^0.4.3",
|
||||||
"@welshman/feeds": "^0.4.0",
|
"@welshman/feeds": "^0.4.3",
|
||||||
"@welshman/lib": "^0.4.0",
|
"@welshman/lib": "^0.4.3",
|
||||||
"@welshman/net": "^0.4.0",
|
"@welshman/net": "^0.4.3",
|
||||||
"@welshman/relay": "^0.4.0",
|
"@welshman/relay": "^0.4.3",
|
||||||
"@welshman/router": "^0.4.0",
|
"@welshman/router": "^0.4.3",
|
||||||
"@welshman/signer": "^0.4.0",
|
"@welshman/signer": "^0.4.3",
|
||||||
"@welshman/store": "^0.4.0",
|
"@welshman/store": "^0.4.3",
|
||||||
"@welshman/util": "^0.4.0",
|
"@welshman/util": "^0.4.3",
|
||||||
"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
+87
-86
@@ -63,38 +63,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.3
|
||||||
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.3(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.3
|
||||||
version: 0.4.0(typescript@5.8.3)
|
version: 0.4.3(typescript@5.8.3)
|
||||||
'@welshman/editor':
|
'@welshman/editor':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.3
|
||||||
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.3(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)
|
||||||
'@welshman/feeds':
|
'@welshman/feeds':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.3
|
||||||
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.3(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.3
|
||||||
version: 0.4.0
|
version: 0.4.3
|
||||||
'@welshman/net':
|
'@welshman/net':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.3
|
||||||
version: 0.4.0(typescript@5.8.3)(ws@8.18.3)
|
version: 0.4.3(typescript@5.8.3)(ws@8.18.3)
|
||||||
'@welshman/relay':
|
'@welshman/relay':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.3
|
||||||
version: 0.4.0(typescript@5.8.3)
|
version: 0.4.3(typescript@5.8.3)
|
||||||
'@welshman/router':
|
'@welshman/router':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.3
|
||||||
version: 0.4.0(typescript@5.8.3)
|
version: 0.4.3(typescript@5.8.3)
|
||||||
'@welshman/signer':
|
'@welshman/signer':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.3
|
||||||
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.3(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.3
|
||||||
version: 0.4.0(typescript@5.8.3)
|
version: 0.4.3(typescript@5.8.3)
|
||||||
'@welshman/util':
|
'@welshman/util':
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.3
|
||||||
version: 0.4.0(typescript@5.8.3)
|
version: 0.4.3(typescript@5.8.3)
|
||||||
compressorjs:
|
compressorjs:
|
||||||
specifier: ^1.2.1
|
specifier: ^1.2.1
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
@@ -1632,41 +1632,41 @@ packages:
|
|||||||
'@vite-pwa/assets-generator':
|
'@vite-pwa/assets-generator':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@welshman/app@0.4.0':
|
'@welshman/app@0.4.3':
|
||||||
resolution: {integrity: sha512-LTlqbuiRFYAdwXIUYPOxaAusjhlj2ZgZlAuyEpQoBwNTyD7TUaTXj0kA5pbQZLFXWYuqDmrDB14Nl1zzBJBESQ==}
|
resolution: {integrity: sha512-bnanLtSsX45gqdFKlCZr4IRBMQo+7TXycduI9ffgj/9rEu+94rKIEjUVOnbZF8+hhtaqL/Eypvjz2+N33O1mUQ==}
|
||||||
|
|
||||||
'@welshman/content@0.4.0':
|
'@welshman/content@0.4.3':
|
||||||
resolution: {integrity: sha512-3pWxr0Byc/Asmvlnq5UchkT0yeaGg63xTEk9fVJyzIrphIxn5bboaIixEw7y2w2lggFaqHgx+DFrulmhdJ9dXQ==}
|
resolution: {integrity: sha512-5QxfY+QOBfrEHF76/feJ1Oxvp/SSkvp091b3AhxbcuvLnPOfaaUqAHmfktaM3XhAZF2T2bGDICV/vO7eUghz+Q==}
|
||||||
|
|
||||||
'@welshman/editor@0.4.0':
|
'@welshman/editor@0.4.3':
|
||||||
resolution: {integrity: sha512-aIt/t+pMs2XKWZ6wN58jdPWlN9MXVdK1rccKk6Z54ckarCzB4B7usSZvstwMMkmZra/HPLOaWw5KXqhDR1YiUA==}
|
resolution: {integrity: sha512-R03W3XfOFqougx2qJrfHchpVA445Rt7zYQ0hzNo8oFLjWCMG9flvNXY5n7imPGTGthNaUm+s87dDLbkxpL1m/A==}
|
||||||
|
|
||||||
'@welshman/feeds@0.4.0':
|
'@welshman/feeds@0.4.3':
|
||||||
resolution: {integrity: sha512-fwQ4eDzEtcSxFj2LKps6XYFXuZv6lFXKDTq+Nvs5tNYYJUbv/Cz4x3aLQo2ivInz9gAMOLmgpIgNCxkzMqCnoQ==}
|
resolution: {integrity: sha512-176IpUPpvYl0pLXNnHRL2pWAx0C1XJ2b7BSxAXw/CC77Dyn857AFL5tO6YnZSQg5/RtH3WlldMtY2UsJF0fybg==}
|
||||||
|
|
||||||
'@welshman/lib@0.4.0':
|
'@welshman/lib@0.4.3':
|
||||||
resolution: {integrity: sha512-1GPQ2X1FT2R55KWPKhDs+ZK/EkVpeMkVwWdSXC88w+YCoUop00keFm7P452kQKgA/lixNURJSyeWgfI2tUdpkQ==}
|
resolution: {integrity: sha512-wOfrdHfoA+WQwFI54lvVUoRrnZJNTeYHPYlKA+g+wKN1iS4rvpsKi+echWNkzRN8WcHT53qymVhgHEoqqDfOXA==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
'@welshman/net@0.4.0':
|
'@welshman/net@0.4.3':
|
||||||
resolution: {integrity: sha512-QBU5dsALCr9V51lIyNseUDIfvjCJo6VFWe6G1gkJ1PQGh5rgJNZJWCaD430PDpCKsufv2JIkCVYZG5xYZgxzMg==}
|
resolution: {integrity: sha512-JBnOAYCP1LgEXuPrM1G/QrKwUClXxTmyj+Ksdf41Scr8XLxPPpAh1HQ2RirCekydGdw6Ax6SxzUzN69Hru3vew==}
|
||||||
|
|
||||||
'@welshman/relay@0.4.0':
|
'@welshman/relay@0.4.3':
|
||||||
resolution: {integrity: sha512-5zTPSDPhMR2v55hotQf4JO3XgBXEws4k/xChAbYZDfUwxG7HQxmDM2n56aFrKqgI3w+qp8l1lx/1KeksKvBiWw==}
|
resolution: {integrity: sha512-UI+IAwEkicaiu8DhBmy81NVPFkHSUVjymbXZsxaCZuT86tJQvtr5cpf0/f/blh9Ncl8Gf1qmjj3A0Q4WnM6Z2A==}
|
||||||
|
|
||||||
'@welshman/router@0.4.0':
|
'@welshman/router@0.4.3':
|
||||||
resolution: {integrity: sha512-ccpx9QrJ7Uq3CI7r/PyBOwO0G/2xknKXN0xLW995hta1Z1bUzYWhz+C9YvoceDPHCUPaZotBCgdmpY9oOiYqHg==}
|
resolution: {integrity: sha512-ptWsxTkxIstELFMxkgOHrK65JvRUIN6q3HC/nu9Xy590tohZ3NeQUCfA7ujFo8LfymRavx/gdMwqOfePvgwHUQ==}
|
||||||
|
|
||||||
'@welshman/signer@0.4.0':
|
'@welshman/signer@0.4.3':
|
||||||
resolution: {integrity: sha512-I+4l1gmSBVQkFtu6Bm5aAxsFXlE5oXeCsUX+GSsTb0Pg1e4FnTMgeaI3xM8tcCLma4EK+3mD7Yi9MaZMSYX8YQ==}
|
resolution: {integrity: sha512-WQduEU5arxaHDza6NfbRMBBh9PqCsmD/a32fsnY/+eaID9eUtABu9s/NM2ZWu2rQWi1VeQ95RSxAZWZOzgUmqw==}
|
||||||
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.3':
|
||||||
resolution: {integrity: sha512-/gIX1hTTGPkhFlMm91oY7khqriIZwnNIFs3leWIbJGWXLd4pd4fMFM7bKuOTuEsnHkB9thkeXurAdYMquhlHOw==}
|
resolution: {integrity: sha512-D1MgedZROr+7X+O+2xH+ZV/5W1CmkmEpceiDQfxLTmgrihNGsnBf7Ub+b6DwdzCrVZNgh3wVByASZe4ppjV9LA==}
|
||||||
|
|
||||||
'@welshman/util@0.4.0':
|
'@welshman/util@0.4.3':
|
||||||
resolution: {integrity: sha512-UiJyqXeWEx0s83M0AD/bN5ylvpCfYUSjLepb0QxxZpBPnZgMj07oO1OS4967QFuePSvmjTQNcxy3ABm8aagxGg==}
|
resolution: {integrity: sha512-DN/Au5e17cNBGvuuiJAM0cVe1XSvFbiZvZ+WZK9K2ACNVK2WccIywAGYTAP3buZZIvnpt7Af+60qmhMQSBD+/g==}
|
||||||
|
|
||||||
'@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 +4165,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==}
|
||||||
@@ -6499,17 +6500,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.3(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.3(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.3
|
||||||
'@welshman/net': 0.4.0(typescript@5.8.3)(ws@8.18.3)
|
'@welshman/net': 0.4.3(typescript@5.8.3)(ws@8.18.3)
|
||||||
'@welshman/relay': 0.4.0(typescript@5.8.3)
|
'@welshman/relay': 0.4.3(typescript@5.8.3)
|
||||||
'@welshman/router': 0.4.0(typescript@5.8.3)
|
'@welshman/router': 0.4.3(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.3(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.3(typescript@5.8.3)
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.3(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 +6520,14 @@ snapshots:
|
|||||||
- typescript
|
- typescript
|
||||||
- ws
|
- ws
|
||||||
|
|
||||||
'@welshman/content@0.4.0(typescript@5.8.3)':
|
'@welshman/content@0.4.3(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.3(@tiptap/extension-image@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(@tiptap/extension-link@2.26.1(@tiptap/core@2.12.0(@tiptap/pm@2.12.0))(@tiptap/pm@2.12.0))(linkifyjs@4.3.1)(prosemirror-markdown@1.13.2)(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.3)(tiptap-markdown@0.8.10(@tiptap/core@2.12.0(@tiptap/pm@2.12.0)))(typescript@5.8.3)':
|
||||||
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 +6542,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.3
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.3(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 +6558,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.3(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.3
|
||||||
'@welshman/net': 0.4.0(typescript@5.8.3)(ws@8.18.3)
|
'@welshman/net': 0.4.3(typescript@5.8.3)(ws@8.18.3)
|
||||||
'@welshman/relay': 0.4.0(typescript@5.8.3)
|
'@welshman/relay': 0.4.3(typescript@5.8.3)
|
||||||
'@welshman/router': 0.4.0(typescript@5.8.3)
|
'@welshman/router': 0.4.3(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.3(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.3(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.3':
|
||||||
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.3(typescript@5.8.3)(ws@8.18.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@welshman/lib': 0.4.0
|
'@welshman/lib': 0.4.3
|
||||||
'@welshman/relay': 0.4.0(typescript@5.8.3)
|
'@welshman/relay': 0.4.3(typescript@5.8.3)
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.3(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.3(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@welshman/lib': 0.4.0
|
'@welshman/lib': 0.4.3
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.3(typescript@5.8.3)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
'@welshman/router@0.4.0(typescript@5.8.3)':
|
'@welshman/router@0.4.3(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@welshman/lib': 0.4.0
|
'@welshman/lib': 0.4.3
|
||||||
'@welshman/relay': 0.4.0(typescript@5.8.3)
|
'@welshman/relay': 0.4.3(typescript@5.8.3)
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.3(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.3(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.3
|
||||||
'@welshman/net': 0.4.0(typescript@5.8.3)(ws@8.18.3)
|
'@welshman/net': 0.4.3(typescript@5.8.3)(ws@8.18.3)
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.3(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.3(typescript@5.8.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@welshman/lib': 0.4.0
|
'@welshman/lib': 0.4.3
|
||||||
'@welshman/relay': 0.4.0(typescript@5.8.3)
|
'@welshman/relay': 0.4.3(typescript@5.8.3)
|
||||||
'@welshman/util': 0.4.0(typescript@5.8.3)
|
'@welshman/util': 0.4.3(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.3(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.3
|
||||||
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 {
|
||||||
|
|||||||
@@ -27,12 +27,12 @@
|
|||||||
userMembership,
|
userMembership,
|
||||||
NOTIFIER_PUBKEY,
|
NOTIFIER_PUBKEY,
|
||||||
NOTIFIER_RELAY,
|
NOTIFIER_RELAY,
|
||||||
} from "@app/state"
|
} from "@app/core/state"
|
||||||
import {loadAlertStatuses, requestRelayClaim} from "@app/requests"
|
import {loadAlertStatuses, requestRelayClaim} from "@app/core/requests"
|
||||||
import {publishAlert, attemptAuth} from "@app/commands"
|
import {publishAlert, attemptAuth} from "@app/core/commands"
|
||||||
import type {AlertParams} from "@app/commands"
|
import type {AlertParams} from "@app/core/commands"
|
||||||
import {platform, platformName, canSendPushNotifications, getPushInfo} from "@app/push"
|
import {platform, platformName, canSendPushNotifications, getPushInfo} from "@app/util/push"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url?: string
|
url?: string
|
||||||
@@ -60,7 +60,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,6 +109,7 @@
|
|||||||
loading = true
|
loading = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const claim = url ? await requestRelayClaim(url) : undefined
|
||||||
const claims = claim ? {[url]: claim} : {}
|
const claims = claim ? {[url]: claim} : {}
|
||||||
const feed = makeIntersectionFeed(feedFromFilters(filters), makeRelayFeed(url))
|
const feed = makeIntersectionFeed(feedFromFilters(filters), makeRelayFeed(url))
|
||||||
const description = `for ${displayList(display)} on ${displayRelayUrl(url)}`
|
const description = `for ${displayList(display)} on ${displayRelayUrl(url)}`
|
||||||
@@ -179,14 +179,6 @@
|
|||||||
if (!canSendPushNotifications()) {
|
if (!canSendPushNotifications()) {
|
||||||
channel = "email"
|
channel = "email"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url) {
|
|
||||||
requestRelayClaim(url).then(code => {
|
|
||||||
if (code) {
|
|
||||||
claim = code
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -268,22 +260,6 @@
|
|||||||
</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="alt-arrow-left" />
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<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 {NOTIFIER_RELAY, NOTIFIER_PUBKEY} from "@app/core/state"
|
||||||
import {publishDelete} from "@app/commands"
|
import {publishDelete} from "@app/core/commands"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
alert: Alert
|
alert: Alert
|
||||||
@@ -12,7 +12,10 @@
|
|||||||
const {alert}: Props = $props()
|
const {alert}: Props = $props()
|
||||||
|
|
||||||
const confirm = () => {
|
const confirm = () => {
|
||||||
publishDelete({event: alert.event, relays: [NOTIFIER_RELAY], tags: [["p", NOTIFIER_PUBKEY]]})
|
const relays = [NOTIFIER_RELAY]
|
||||||
|
const tags = [["p", NOTIFIER_PUBKEY]]
|
||||||
|
|
||||||
|
publishDelete({event: alert.event, relays, tags, protect: false})
|
||||||
pushToast({message: "Your alert has been deleted!"})
|
pushToast({message: "Your alert has been deleted!"})
|
||||||
history.back()
|
history.back()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
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 type {Alert} from "@app/core/state"
|
||||||
import {deriveAlertStatus} from "@app/state"
|
import {deriveAlertStatus} from "@app/core/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
alert: Alert
|
alert: Alert
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
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 {alerts} from "@app/core/state"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url?: string
|
url?: string
|
||||||
|
|||||||
@@ -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,30 @@
|
|||||||
<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 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>
|
||||||
@@ -20,7 +34,10 @@
|
|||||||
{#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="cpu" />
|
||||||
<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="qr-code" />
|
||||||
|
</Button>
|
||||||
</label>
|
</label>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet info()}
|
{#snippet info()}
|
||||||
@@ -30,3 +47,6 @@
|
|||||||
</p>
|
</p>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Field>
|
</Field>
|
||||||
|
{#if showScanner}
|
||||||
|
<Scanner onscan={onScan} />
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
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"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
url,
|
url,
|
||||||
@@ -22,14 +22,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">
|
||||||
|
|||||||
@@ -13,9 +13,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 +33,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 +66,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]})
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -8,16 +8,16 @@
|
|||||||
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 +30,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 +42,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 +80,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}
|
||||||
|
|||||||
@@ -2,12 +2,19 @@
|
|||||||
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 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">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
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"
|
||||||
|
|
||||||
const {url, event, onClick} = $props()
|
const {url, event, onClick} = $props()
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
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"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
@@ -20,9 +20,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})
|
||||||
|
|||||||
@@ -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,16 @@
|
|||||||
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 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 +46,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,
|
userSettingValues,
|
||||||
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 +125,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: $userSettingValues.send_delay + ms(i)}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushToast({
|
||||||
|
timeout: 30_000,
|
||||||
|
children: {
|
||||||
|
component: ThunkToast,
|
||||||
|
props: {thunk: mergeThunks(thunks)},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
clearParent()
|
clearParent()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +170,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 +182,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(() => {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
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 {canDecrypt, PLATFORM_NAME, ensureUnwrapped} from "@app/core/state"
|
||||||
import {clearModals} from "@app/modal"
|
import {clearModals} from "@app/util/modal"
|
||||||
|
|
||||||
const {next} = $props()
|
const {next} = $props()
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
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 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"
|
||||||
|
|
||||||
const startChat = () => pushModal(ChatStart, {}, {replaceState: true})
|
const startChat = () => pushModal(ChatStart, {}, {replaceState: true})
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,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 +37,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 +59,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}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import {sendWrapped} from "@welshman/app"
|
import {sendWrapped} from "@welshman/app"
|
||||||
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,7 +14,7 @@
|
|||||||
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">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
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"
|
||||||
|
|
||||||
const {event, pubkeys, popover, replyTo} = $props()
|
const {event, pubkeys, popover, replyTo} = $props()
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
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"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pubkeys: string[]
|
pubkeys: string[]
|
||||||
@@ -20,7 +20,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})
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
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()
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -31,7 +31,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, userSettingValues} from "@app/core/state"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
event: any
|
event: any
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
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 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()
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
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()
|
||||||
|
|
||||||
|
|||||||
@@ -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,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
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()
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
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,12 +28,12 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
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 +22,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>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
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 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()
|
||||||
|
|||||||
@@ -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,15 @@
|
|||||||
<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 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 +20,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,6 +42,14 @@
|
|||||||
<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>
|
||||||
@@ -61,6 +78,22 @@
|
|||||||
</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">
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
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"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
|
|||||||
@@ -7,13 +7,15 @@
|
|||||||
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 +25,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({
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
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()
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -7,9 +7,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 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()
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -10,12 +10,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 +50,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}),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
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()
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -6,8 +6,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)
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,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()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<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"
|
||||||
@@ -8,17 +9,24 @@
|
|||||||
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 +38,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 +53,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 +82,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="alt-arrow-left" />
|
||||||
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="alt-arrow-right" />
|
||||||
<Spinner loading={controller.loading}>Next</Spinner>
|
</Button>
|
||||||
<Icon icon="alt-arrow-right" />
|
{/if}
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -12,11 +12,17 @@
|
|||||||
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
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
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()
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
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>
|
||||||
|
|||||||
@@ -26,10 +26,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()
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
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()
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
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
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
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>
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import {page} from "$app/stores"
|
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 {modals, 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) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
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,
|
||||||
|
|||||||
@@ -6,17 +6,25 @@
|
|||||||
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">
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
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()
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,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
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
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
|
||||||
|
|||||||
@@ -13,10 +13,10 @@
|
|||||||
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"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?: Snippet
|
children?: Snippet
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
</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="add-square" class="!h-10 !w-10" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
{/each}
|
{/each}
|
||||||
</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()
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import Avatar from "@lib/components/Avatar.svelte"
|
||||||
import WotScore from "@app/components/WotScore.svelte"
|
import WotScore from "@app/components/WotScore.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"
|
||||||
import {clip} from "@app/toast"
|
import {clip} from "@app/util/toast"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
import {repository, loadRelaySelections} from "@welshman/app"
|
import {repository, loadRelaySelections} from "@welshman/app"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ProfileSpaces from "@app/components/ProfileSpaces.svelte"
|
import ProfileSpaces from "@app/components/ProfileSpaces.svelte"
|
||||||
import {membershipsByPubkey} from "@app/state"
|
import {membershipsByPubkey} from "@app/core/state"
|
||||||
import {goToEvent} from "@app/routes"
|
import {goToEvent} from "@app/util/routes"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
|
|||||||
@@ -16,9 +16,9 @@
|
|||||||
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 {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {logout} from "@app/commands"
|
import {logout} from "@app/core/commands"
|
||||||
import {INDEXER_RELAYS, PLATFORM_NAME, userMembership, getMembershipUrls} from "@app/state"
|
import {INDEXER_RELAYS, PLATFORM_NAME, userMembership, getMembershipUrls} from "@app/core/state"
|
||||||
|
|
||||||
let progress: number | undefined = $state(undefined)
|
let progress: number | undefined = $state(undefined)
|
||||||
let confirmText = $state("")
|
let confirmText = $state("")
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
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 ChatEnable from "@app/components/ChatEnable.svelte"
|
import ChatEnable from "@app/components/ChatEnable.svelte"
|
||||||
import {canDecrypt, pubkeyLink} from "@app/state"
|
import {canDecrypt, pubkeyLink} from "@app/core/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
import {makeChatPath} from "@app/routes"
|
import {makeChatPath} from "@app/util/routes"
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
import {pubkey, profilesByPubkey, publishThunk} from "@welshman/app"
|
import {pubkey, profilesByPubkey, publishThunk} from "@welshman/app"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ProfileEditForm from "@app/components/ProfileEditForm.svelte"
|
import ProfileEditForm from "@app/components/ProfileEditForm.svelte"
|
||||||
import {clearModals} from "@app/modal"
|
import {clearModals} from "@app/util/modal"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {PROTECTED, getMembershipUrls, userMembership} from "@app/state"
|
import {PROTECTED, getMembershipUrls, userMembership} from "@app/core/state"
|
||||||
|
|
||||||
const profile = $profilesByPubkey.get($pubkey!) || makeProfile()
|
const profile = $profilesByPubkey.get($pubkey!) || makeProfile()
|
||||||
const shouldBroadcast = !getTag(PROTECTED, profile.event?.tags || [])
|
const shouldBroadcast = !getTag(PROTECTED, profile.event?.tags || [])
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import InputProfilePicture from "@lib/components/InputProfilePicture.svelte"
|
import InputProfilePicture from "@lib/components/InputProfilePicture.svelte"
|
||||||
import InfoHandle from "@app/components/InfoHandle.svelte"
|
import InfoHandle from "@app/components/InfoHandle.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
type Values = {
|
type Values = {
|
||||||
profile: Profile
|
profile: Profile
|
||||||
@@ -18,11 +18,11 @@
|
|||||||
type Props = {
|
type Props = {
|
||||||
initialValues: Values
|
initialValues: Values
|
||||||
onsubmit: (values: Values) => void
|
onsubmit: (values: Values) => void
|
||||||
hideAddress?: boolean
|
isSignup?: boolean
|
||||||
footer: Snippet
|
footer: Snippet
|
||||||
}
|
}
|
||||||
|
|
||||||
const {initialValues, hideAddress, onsubmit, footer}: Props = $props()
|
const {initialValues, isSignup, onsubmit, footer}: Props = $props()
|
||||||
|
|
||||||
const values = $state(initialValues)
|
const values = $state(initialValues)
|
||||||
|
|
||||||
@@ -32,9 +32,25 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="col-4" onsubmit={preventDefault(submit)}>
|
<form class="col-4" onsubmit={preventDefault(submit)}>
|
||||||
<div class="flex justify-center py-2">
|
{#if isSignup}
|
||||||
<InputProfilePicture bind:file bind:url={values.profile.picture} />
|
<div class="grid grid-cols-2">
|
||||||
</div>
|
<div class="flex flex-col gap-2">
|
||||||
|
<p class="text-2xl">Create a Profile</p>
|
||||||
|
<p class="text-sm">
|
||||||
|
Give people something to go on — but remember, privacy matters! Be careful about sharing
|
||||||
|
sensitive information.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center justify-center gap-2">
|
||||||
|
<InputProfilePicture bind:file bind:url={values.profile.picture} />
|
||||||
|
<p class="text-xs">Upload an Avatar</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="flex items-center justify-center py-4">
|
||||||
|
<InputProfilePicture bind:file bind:url={values.profile.picture} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<Field>
|
<Field>
|
||||||
{#snippet label()}
|
{#snippet label()}
|
||||||
<p>Username</p>
|
<p>Username</p>
|
||||||
@@ -63,7 +79,7 @@
|
|||||||
Give a brief introduction to why you're here.
|
Give a brief introduction to why you're here.
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Field>
|
</Field>
|
||||||
{#if !hideAddress}
|
{#if !isSignup}
|
||||||
<Field>
|
<Field>
|
||||||
{#snippet label()}
|
{#snippet label()}
|
||||||
<p>Nostr Address</p>
|
<p>Nostr Address</p>
|
||||||
@@ -82,19 +98,24 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
</Field>
|
</Field>
|
||||||
{/if}
|
{/if}
|
||||||
<FieldInline>
|
{#if !isSignup}
|
||||||
{#snippet label()}
|
<FieldInline>
|
||||||
<p>Broadcast Profile</p>
|
{#snippet label()}
|
||||||
{/snippet}
|
<p>Broadcast Profile</p>
|
||||||
{#snippet input()}
|
{/snippet}
|
||||||
<input type="checkbox" class="toggle toggle-primary" bind:checked={values.shouldBroadcast} />
|
{#snippet input()}
|
||||||
{/snippet}
|
<input
|
||||||
{#snippet info()}
|
type="checkbox"
|
||||||
<p>
|
class="toggle toggle-primary"
|
||||||
If enabled, changes will be published to the broader nostr network in addition to spaces you
|
bind:checked={values.shouldBroadcast} />
|
||||||
are a member of.
|
{/snippet}
|
||||||
</p>
|
{#snippet info()}
|
||||||
{/snippet}
|
<p>
|
||||||
</FieldInline>
|
If enabled, changes will be published to the broader nostr network in addition to spaces
|
||||||
|
you are a member of.
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</FieldInline>
|
||||||
|
{/if}
|
||||||
{@render footer()}
|
{@render footer()}
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
import Field from "@lib/components/Field.svelte"
|
import Field from "@lib/components/Field.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 {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 {logout} from "@app/commands"
|
import {logout} from "@app/core/commands"
|
||||||
|
|
||||||
const email = $session?.email
|
const email = $session?.email
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ProfileName from "@app/components/ProfileName.svelte"
|
import ProfileName from "@app/components/ProfileName.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
import ProfileSuggestion from "@app/editor/ProfileSuggestion.svelte"
|
import ProfileSuggestion from "@app/editor/ProfileSuggestion.svelte"
|
||||||
import ProfileName from "@app/components/ProfileName.svelte"
|
import ProfileName from "@app/components/ProfileName.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: string[]
|
value: string[]
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
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 {makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
import {getMembershipUrls, membershipsByPubkey} from "@app/state"
|
import {getMembershipUrls, membershipsByPubkey} from "@app/core/state"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import QRCode from "qrcode"
|
import QRCode from "qrcode"
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "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 {code, ...props} = $props()
|
const {code, ...props} = $props()
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Reaction from "@app/components/Reaction.svelte"
|
import Reaction from "@app/components/Reaction.svelte"
|
||||||
import EventReportDetails from "@app/components/EventReportDetails.svelte"
|
import EventReportDetails from "@app/components/EventReportDetails.svelte"
|
||||||
import {REACTION_KINDS} from "@app/state"
|
import {REACTION_KINDS} from "@app/core/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
event: TrustedEvent
|
event: TrustedEvent
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
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 {hasNip29, loadChannel} from "@app/state"
|
import {hasNip29, loadChannel} from "@app/core/state"
|
||||||
import {makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/util/routes"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
const {url} = $props()
|
const {url} = $props()
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Capacitor} from "@capacitor/core"
|
|
||||||
import {postJson} from "@welshman/lib"
|
import {postJson} from "@welshman/lib"
|
||||||
import {isMobile, preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
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"
|
||||||
@@ -9,22 +8,11 @@
|
|||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import LogIn from "@app/components/LogIn.svelte"
|
import LogIn from "@app/components/LogIn.svelte"
|
||||||
import InfoNostr from "@app/components/InfoNostr.svelte"
|
import InfoNostr from "@app/components/InfoNostr.svelte"
|
||||||
import SignUpKey from "@app/components/SignUpKey.svelte"
|
import SignUpProfile from "@app/components/SignUpProfile.svelte"
|
||||||
import SignUpSuccess from "@app/components/SignUpSuccess.svelte"
|
import SignUpSuccess from "@app/components/SignUpSuccess.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
import {BURROW_URL, PLATFORM_NAME, PLATFORM_ACCENT} from "@app/state"
|
import {BURROW_URL, PLATFORM_NAME} from "@app/core/state"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
an: PLATFORM_NAME,
|
|
||||||
ac: window.location.origin,
|
|
||||||
at: isMobile ? "android" : "web",
|
|
||||||
aa: PLATFORM_ACCENT.slice(1),
|
|
||||||
am: "dark",
|
|
||||||
asf: "yes",
|
|
||||||
})
|
|
||||||
|
|
||||||
const nstart = `https://start.njump.me/?${params.toString()}`
|
|
||||||
|
|
||||||
const login = () => pushModal(LogIn)
|
const login = () => pushModal(LogIn)
|
||||||
|
|
||||||
@@ -50,7 +38,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const useKey = () => pushModal(SignUpKey)
|
const next = () => pushModal(SignUpProfile)
|
||||||
|
|
||||||
let email = $state("")
|
let email = $state("")
|
||||||
let password = $state("")
|
let password = $state("")
|
||||||
@@ -61,8 +49,8 @@
|
|||||||
<h1 class="heading">Sign up with Nostr</h1>
|
<h1 class="heading">Sign up with Nostr</h1>
|
||||||
<p class="m-auto max-w-sm text-center">
|
<p class="m-auto max-w-sm text-center">
|
||||||
{PLATFORM_NAME} is built using the
|
{PLATFORM_NAME} is built using the
|
||||||
<Button class="link" onclick={() => pushModal(InfoNostr)}>nostr protocol</Button>, which allows
|
<Button class="link" onclick={() => pushModal(InfoNostr)}>nostr protocol</Button>, which gives
|
||||||
you to own your social identity.
|
users control over their digital identity using <strong>cryptographic key pairs</strong>.
|
||||||
</p>
|
</p>
|
||||||
{#if BURROW_URL}
|
{#if BURROW_URL}
|
||||||
<FieldInline>
|
<FieldInline>
|
||||||
@@ -98,17 +86,10 @@
|
|||||||
</p>
|
</p>
|
||||||
<Divider>Or</Divider>
|
<Divider>Or</Divider>
|
||||||
{/if}
|
{/if}
|
||||||
{#if Capacitor.isNativePlatform()}
|
<Button onclick={next} class="btn {email || password ? 'btn-neutral' : 'btn-primary'}">
|
||||||
<Button onclick={useKey} class="btn {email || password ? 'btn-neutral' : 'btn-primary'}">
|
<Icon icon="key" />
|
||||||
<Icon icon="key" />
|
Generate a key
|
||||||
Generate a key
|
</Button>
|
||||||
</Button>
|
|
||||||
{:else}
|
|
||||||
<a href={nstart} class="btn {email || password ? 'btn-neutral' : 'btn-primary'}">
|
|
||||||
<Icon icon="square-share-line" />
|
|
||||||
Create an account on Nstart
|
|
||||||
</a>
|
|
||||||
{/if}
|
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
Already have an account?
|
Already have an account?
|
||||||
<Button class="link" onclick={login}>Log in instead</Button>
|
<Button class="link" onclick={login}>Log in instead</Button>
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type {Profile} from "@welshman/util"
|
||||||
|
import {createProfile, PROFILE, makeEvent} from "@welshman/util"
|
||||||
|
import {publishThunk, loginWithNip01} from "@welshman/app"
|
||||||
|
import {preventDefault} from "@lib/html"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
|
import {PROTECTED} from "@app/core/state"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secret: string
|
||||||
|
profile: Profile
|
||||||
|
}
|
||||||
|
|
||||||
|
const {secret, profile}: Props = $props()
|
||||||
|
|
||||||
|
const back = () => history.back()
|
||||||
|
|
||||||
|
const next = () => {
|
||||||
|
const template = createProfile(profile)
|
||||||
|
|
||||||
|
// Start out protected by default
|
||||||
|
template.tags.push(PROTECTED)
|
||||||
|
|
||||||
|
const event = makeEvent(PROFILE, template)
|
||||||
|
|
||||||
|
// Log in first, then publish
|
||||||
|
loginWithNip01(secret)
|
||||||
|
|
||||||
|
// Don't publish anywhere yet, wait until they join a space
|
||||||
|
publishThunk({event, relays: []})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form class="column gap-4" onsubmit={preventDefault(next)}>
|
||||||
|
<ModalHeader>
|
||||||
|
{#snippet title()}
|
||||||
|
<div>You're all set!</div>
|
||||||
|
{/snippet}
|
||||||
|
</ModalHeader>
|
||||||
|
<p>
|
||||||
|
You've created your profile, saved your keys, and now you're ready to start chatting — all
|
||||||
|
without asking permission!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
From your dashboard, you can use invite links, discover community spaces, and keep up-to-date on
|
||||||
|
groups you've already joined. Click below to get started!
|
||||||
|
</p>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button class="btn btn-link" onclick={back}>
|
||||||
|
<Icon icon="alt-arrow-left" />
|
||||||
|
Go back
|
||||||
|
</Button>
|
||||||
|
<Button class="btn btn-primary" type="submit">
|
||||||
|
<Icon icon="home-smile" />
|
||||||
|
Go to Dashboard
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</form>
|
||||||
@@ -1,81 +1,118 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import {nsecEncode} from "nostr-tools/nip19"
|
||||||
import {encrypt} from "nostr-tools/nip49"
|
import {encrypt} from "nostr-tools/nip49"
|
||||||
import {hexToBytes} from "@welshman/lib"
|
import {hexToBytes} from "@welshman/lib"
|
||||||
import {makeSecret} from "@welshman/signer"
|
import {makeSecret} from "@welshman/signer"
|
||||||
|
import type {Profile} from "@welshman/util"
|
||||||
import {preventDefault, downloadText} from "@lib/html"
|
import {preventDefault, downloadText} from "@lib/html"
|
||||||
import Link from "@lib/components/Link.svelte"
|
|
||||||
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"
|
||||||
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 SignUpKeyConfirm from "@app/components/SignUpKeyConfirm.svelte"
|
import SignUpComplete from "@app/components/SignUpComplete.svelte"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
profile: Profile
|
||||||
|
}
|
||||||
|
|
||||||
|
const {profile}: Props = $props()
|
||||||
|
|
||||||
const secret = makeSecret()
|
const secret = makeSecret()
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const next = () => {
|
const downloadKey = () => {
|
||||||
if (password.length < 12) {
|
if (usePassword) {
|
||||||
return pushToast({
|
if (password.length < 12) {
|
||||||
theme: "error",
|
return pushToast({
|
||||||
message: "Passwords must be at least 12 characters long.",
|
theme: "error",
|
||||||
})
|
message: "Your password must be at least 12 characters long.",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const ncryptsec = encrypt(hexToBytes(secret), password)
|
||||||
|
|
||||||
|
downloadText("Nostr Secret Key.txt", ncryptsec)
|
||||||
|
} else {
|
||||||
|
const nsec = nsecEncode(hexToBytes(secret))
|
||||||
|
|
||||||
|
downloadText("Nostr Secret Key.txt", nsec)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ncryptsec = encrypt(hexToBytes(secret), password)
|
didDownload = true
|
||||||
|
|
||||||
downloadText("Nostr Secret Key.txt", ncryptsec)
|
|
||||||
|
|
||||||
pushModal(SignUpKeyConfirm, {secret, ncryptsec})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let password = ""
|
const next = () => {
|
||||||
|
pushModal(SignUpComplete, {profile, secret})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPasswordChange = () => {
|
||||||
|
didDownload = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleUsePassword = () => {
|
||||||
|
usePassword = !usePassword
|
||||||
|
didDownload = false
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = $state("")
|
||||||
|
let usePassword = $state(false)
|
||||||
|
let didDownload = $state(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="column gap-4" onsubmit={preventDefault(next)}>
|
<form class="column gap-4" onsubmit={preventDefault(next)}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
{#snippet title()}
|
{#snippet title()}
|
||||||
<div>Welcome to Nostr!</div>
|
<div>Your Keys are Ready!</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<p>
|
<p>
|
||||||
<Link external href="https://nostr.com/" class="link">Nostr</Link> is way to build social apps that
|
A cryptographic key pair has two parts: your <strong>public key</strong> identifies your
|
||||||
talk to each other. Users own their social identity instead of renting it from a tech company, and
|
account, while your <strong>private key</strong> acts sort of like a master password.
|
||||||
can take it with them.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
This means that instead of using a password to log in, you generate a <strong
|
Securing your private key is very important, so make sure to take the time to save your key in a
|
||||||
>secret key</strong>
|
secure place (like a password manager).
|
||||||
which gives you full control over your account.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
{#if usePassword}
|
||||||
Keeping this key safe is very important, so we encourage you to download an encrypted copy. To
|
<Field>
|
||||||
do this, go ahead and fill in the password you'd like to use to secure your key below.
|
{#snippet label()}
|
||||||
</p>
|
Password*
|
||||||
<Field>
|
{/snippet}
|
||||||
{#snippet label()}
|
{#snippet input()}
|
||||||
Password*
|
<label class="input input-bordered flex w-full items-center gap-2">
|
||||||
{/snippet}
|
<Icon icon="key" />
|
||||||
{#snippet input()}
|
<input bind:value={password} onchange={onPasswordChange} class="grow" type="password" />
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
</label>
|
||||||
<Icon icon="key" />
|
{/snippet}
|
||||||
<input bind:value={password} class="grow" type="password" />
|
{#snippet info()}
|
||||||
</label>
|
<p>Passwords should be at least 12 characters long. Write this down!</p>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet info()}
|
</Field>
|
||||||
<p>Passwords should be at least 12 characters long. Write this down!</p>
|
{/if}
|
||||||
{/snippet}
|
<div class="flex flex-col">
|
||||||
</Field>
|
<Button class="btn {didDownload ? 'btn-neutral' : 'btn-primary'}" onclick={downloadKey}>
|
||||||
|
Download my key
|
||||||
|
<Icon icon="arrow-down" />
|
||||||
|
</Button>
|
||||||
|
<Button class="btn btn-link no-underline" onclick={toggleUsePassword}>
|
||||||
|
{#if usePassword}
|
||||||
|
Nevermind, I want to download the plain version
|
||||||
|
{:else}
|
||||||
|
I want to download an encrypted version
|
||||||
|
{/if}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button class="btn btn-link" onclick={back}>
|
<Button class="btn btn-link" onclick={back}>
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon="alt-arrow-left" />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button class="btn btn-primary" type="submit">
|
<Button disabled={!didDownload} class="btn btn-primary" type="submit">
|
||||||
Download my key
|
Continue
|
||||||
<Icon icon="alt-arrow-right" />
|
<Icon icon="alt-arrow-right" />
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {preventDefault, copyToClipboard} from "@lib/html"
|
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
|
||||||
import Field from "@lib/components/Field.svelte"
|
|
||||||
import Button from "@lib/components/Button.svelte"
|
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
|
||||||
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
|
||||||
import SignUpProfile from "@app/components/SignUpProfile.svelte"
|
|
||||||
import {pushToast} from "@app/toast"
|
|
||||||
import {pushModal} from "@app/modal"
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
secret: string
|
|
||||||
ncryptsec: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const {secret, ncryptsec}: Props = $props()
|
|
||||||
|
|
||||||
const back = () => history.back()
|
|
||||||
|
|
||||||
const copy = () => {
|
|
||||||
copyToClipboard(ncryptsec)
|
|
||||||
pushToast({message: "Your secret key has been copied to your clipboard!"})
|
|
||||||
}
|
|
||||||
|
|
||||||
const next = () => {
|
|
||||||
pushModal(SignUpProfile, {secret})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<form class="column gap-4" onsubmit={preventDefault(next)}>
|
|
||||||
<ModalHeader>
|
|
||||||
{#snippet title()}
|
|
||||||
<div>Download your key</div>
|
|
||||||
{/snippet}
|
|
||||||
</ModalHeader>
|
|
||||||
<p>
|
|
||||||
Great! We've encrypted your secret key and saved it to your device. If that didn't work, or if
|
|
||||||
you'd rather save your key somewhere else, you can find the encrypted version below:
|
|
||||||
</p>
|
|
||||||
<Field>
|
|
||||||
{#snippet label()}
|
|
||||||
Encrypted Secret Key
|
|
||||||
{/snippet}
|
|
||||||
{#snippet input()}
|
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
|
||||||
<Icon icon="key" />
|
|
||||||
<input value={ncryptsec} class="ellipsize grow" />
|
|
||||||
<Button onclick={copy} class="flex items-center">
|
|
||||||
<Icon icon="copy" />
|
|
||||||
</Button>
|
|
||||||
</label>
|
|
||||||
{/snippet}
|
|
||||||
</Field>
|
|
||||||
<ModalFooter>
|
|
||||||
<Button class="btn btn-link" onclick={back}>
|
|
||||||
<Icon icon="alt-arrow-left" />
|
|
||||||
Go back
|
|
||||||
</Button>
|
|
||||||
<Button class="btn btn-primary" type="submit">
|
|
||||||
Fill out your profile
|
|
||||||
<Icon icon="alt-arrow-right" />
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</form>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user