Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b14c3ab345 | |||
| 823058e335 | |||
| 60ec6924f3 | |||
| 18fc895fcb | |||
| 42295159a0 | |||
| db408ac30d | |||
| 1ced5689c3 | |||
| 263a803875 | |||
| 58afb8fa0c | |||
| 4aaa19ea1b | |||
| 2f9010cd13 | |||
| 12fcdfcd4f | |||
| 317ab57ed2 | |||
| 52ef67740a | |||
| 68ebd32e15 | |||
| e94aa3c119 | |||
| 4d10fe7cc0 | |||
| 841928783b | |||
| 6e5e1a0846 | |||
| d57f4747a6 | |||
| 94a0077b09 | |||
| f2eb04adff | |||
| d4d5979a35 | |||
| dde6e54657 | |||
| 698a7513b8 |
@@ -7,8 +7,11 @@ VITE_PLATFORM_NAME=Flotilla
|
|||||||
VITE_PLATFORM_LOGO=static/flotilla.png
|
VITE_PLATFORM_LOGO=static/flotilla.png
|
||||||
VITE_PLATFORM_RELAY=
|
VITE_PLATFORM_RELAY=
|
||||||
VITE_PLATFORM_ACCENT="#7161FF"
|
VITE_PLATFORM_ACCENT="#7161FF"
|
||||||
|
VITE_PLATFORM_SECONDARY="#EB5E28"
|
||||||
VITE_PLATFORM_DESCRIPTION="Flotilla is nostr — for communities."
|
VITE_PLATFORM_DESCRIPTION="Flotilla is nostr — for communities."
|
||||||
VITE_INDEXER_RELAYS=wss://purplepag.es/,wss://relay.damus.io/,wss://relay.nostr.band/
|
VITE_INDEXER_RELAYS=wss://purplepag.es/,wss://relay.damus.io/,wss://relay.nostr.band/
|
||||||
VITE_SIGNER_RELAYS=wss://relay.nsec.app/,wss://bucket.coracle.social/
|
VITE_SIGNER_RELAYS=wss://relay.nsec.app/,wss://bucket.coracle.social/
|
||||||
|
VITE_NOTIFIER_PUBKEY=27b7c2ed89ef78322114225ea3ebf5f72c7767c2528d4d0c1854d039c00085df
|
||||||
|
VITE_NOTIFIER_RELAY=wss://anchor.coracle.social/
|
||||||
VITE_GLITCHTIP_API_KEY=
|
VITE_GLITCHTIP_API_KEY=
|
||||||
GLITCHTIP_AUTH_TOKEN=
|
GLITCHTIP_AUTH_TOKEN=
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
# Env
|
# Env
|
||||||
.env.local
|
.env
|
||||||
|
|
||||||
# Vite
|
# Vite
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
|
|||||||
@@ -1,5 +1,32 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
# 1.0.4
|
||||||
|
|
||||||
|
* Fix thunk status click handler
|
||||||
|
* Remove duplicate dependencies
|
||||||
|
* Improve navigation on white-labeled instances
|
||||||
|
* Add setting for font size
|
||||||
|
|
||||||
|
# 1.0.3
|
||||||
|
|
||||||
|
* Add light theme
|
||||||
|
* Use correct alerts server
|
||||||
|
* Ignore relay errors for claims
|
||||||
|
* Fix inline code blocks
|
||||||
|
* Add custom emoji parsing and display
|
||||||
|
|
||||||
|
# 1.0.2
|
||||||
|
|
||||||
|
* Fix add relay button
|
||||||
|
* Fix safe inset areas
|
||||||
|
* Better rendering for errors from relays
|
||||||
|
* Improve remote signer login
|
||||||
|
|
||||||
|
# 1.0.1
|
||||||
|
|
||||||
|
* Fix relay images in nav
|
||||||
|
* Fix relay nav overflow
|
||||||
|
|
||||||
# 1.0.0
|
# 1.0.0
|
||||||
|
|
||||||
* Add alerts via Anchor
|
* Add alerts via Anchor
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ To run your own Flotilla, it's as simple as:
|
|||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
|
|
||||||
You can also optionally create an `.env.local` file and populate it with the following environment variables (see `.env` for examples):
|
You can also optionally create an `.env` file and populate it with the following environment variables (see `.env` for examples):
|
||||||
|
|
||||||
- `VITE_DEFAULT_PUBKEYS` - A comma-separated list of hex pubkeys for bootstrapping web of trust.
|
- `VITE_DEFAULT_PUBKEYS` - A comma-separated list of hex pubkeys for bootstrapping web of trust.
|
||||||
- `VITE_PLATFORM_URL` - The url where the app will be hosted. This is only used for build-time population of meta tags.
|
- `VITE_PLATFORM_URL` - The url where the app will be hosted. This is only used for build-time population of meta tags.
|
||||||
@@ -38,7 +38,7 @@ First, create an `A` record with your DNS provider pointing to the IP of your se
|
|||||||
|
|
||||||
Next install `nginx`, `git`, and `certbot`. If you're on a debian- or ubuntu-based distro, run `sudo apt-get update && sudo apt-get install nginx git certbot python3-certbot-nginx`.
|
Next install `nginx`, `git`, and `certbot`. If you're on a debian- or ubuntu-based distro, run `sudo apt-get update && sudo apt-get install nginx git certbot python3-certbot-nginx`.
|
||||||
|
|
||||||
Now, create a new user where your code will be stored, clone the repository, fill in your `.env.local` file, and build the app.
|
Now, create a new user where your code will be stored, clone the repository, fill in your `.env` file, and build the app.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Replace with your password
|
# Replace with your password
|
||||||
@@ -67,7 +67,7 @@ nvm install
|
|||||||
nvm use
|
nvm use
|
||||||
pnpm i
|
pnpm i
|
||||||
|
|
||||||
# Optionally create and populate .env.local to suit your use case
|
# Optionally create and populate .env to suit your use case
|
||||||
|
|
||||||
# Build the app
|
# Build the app
|
||||||
NODE_OPTIONS=--max_old_space_size=16384 pnpm run build
|
NODE_OPTIONS=--max_old_space_size=16384 pnpm run build
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ android {
|
|||||||
compileSdk rootProject.ext.compileSdkVersion
|
compileSdk rootProject.ext.compileSdkVersion
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "social.flotilla"
|
applicationId "social.flotilla"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdk rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdk rootProject.ext.targetSdkVersion
|
||||||
versionCode 14
|
versionCode 18
|
||||||
versionName "1.0.0"
|
versionName "1.0.4"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ ext {
|
|||||||
compileSdkVersion = 35
|
compileSdkVersion = 35
|
||||||
targetSdkVersion = 35
|
targetSdkVersion = 35
|
||||||
androidxActivityVersion = '1.9.2'
|
androidxActivityVersion = '1.9.2'
|
||||||
androidxAppCompatVersion = '1.7.0'
|
//https://github.com/ionic-team/capacitor/issues/7866
|
||||||
|
// androidxAppCompatVersion = '1.7.0'
|
||||||
|
androidxAppCompatVersion = '1.6.1'
|
||||||
androidxCoordinatorLayoutVersion = '1.2.0'
|
androidxCoordinatorLayoutVersion = '1.2.0'
|
||||||
androidxCoreVersion = '1.15.0'
|
androidxCoreVersion = '1.15.0'
|
||||||
androidxFragmentVersion = '1.8.4'
|
androidxFragmentVersion = '1.8.4'
|
||||||
|
|||||||
Executable
+19
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Fetch tags and set to env vars
|
||||||
|
git fetch --prune --unshallow --tags
|
||||||
|
git describe --tags --abbrev=0
|
||||||
|
export VITE_BUILD_VERSION=$RENDER_GIT_COMMIT
|
||||||
|
export VITE_BUILD_HASH=$RENDER_GIT_COMMIT
|
||||||
|
|
||||||
|
# Remove link overrides
|
||||||
|
node remove-pnpm-overrides.js package.json
|
||||||
|
|
||||||
|
# When CI=true as it is on render.com, removing link overrides breaks the lockfile
|
||||||
|
pnpm i --no-frozen-lockfile
|
||||||
|
|
||||||
|
# Rebuild sharp
|
||||||
|
pnpm rebuild
|
||||||
|
|
||||||
|
# The build runs out of memory at times
|
||||||
|
NODE_OPTIONS=--max_old_space_size=16384 pnpm run build
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
temp_env=$(declare -p -x)
|
temp_env=$(declare -p -x)
|
||||||
|
|
||||||
if [ -f .env ]; then
|
if [ -f .env.template ]; then
|
||||||
source .env
|
source .env.template
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f .env.local ]; then
|
if [ -f .env ]; then
|
||||||
source .env.local
|
source .env
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Avoid overwriting env vars provided directly
|
# Avoid overwriting env vars provided directly
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ const config: CapacitorConfig = {
|
|||||||
},
|
},
|
||||||
// Use this for live reload https://capacitorjs.com/docs/guides/live-reload
|
// Use this for live reload https://capacitorjs.com/docs/guides/live-reload
|
||||||
// server: {
|
// server: {
|
||||||
// url: "http://192.168.1.250:1847",
|
// url: "http://192.168.1.115:1847",
|
||||||
// cleartext: true
|
// cleartext: true
|
||||||
// },
|
// },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -351,14 +351,14 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 8;
|
CURRENT_PROJECT_VERSION = 11;
|
||||||
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.0.0;
|
MARKETING_VERSION = 1.0.4;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
@@ -376,14 +376,14 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 8;
|
CURRENT_PROJECT_VERSION = 11;
|
||||||
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.0.0;
|
MARKETING_VERSION = 1.0.4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
PRODUCT_BUNDLE_IDENTIFIER = social.flotilla;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
|||||||
+8
-8
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "flotilla",
|
"name": "flotilla",
|
||||||
"version": "1.0.0",
|
"version": "1.0.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
@@ -51,18 +51,18 @@
|
|||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
"@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.2.3",
|
"@welshman/app": "^0.2.5",
|
||||||
"@welshman/content": "^0.2.0",
|
"@welshman/content": "^0.2.2",
|
||||||
"@welshman/dvm": "^0.2.0",
|
"@welshman/dvm": "^0.2.0",
|
||||||
"@welshman/editor": "^0.2.0",
|
"@welshman/editor": "^0.2.4",
|
||||||
"@welshman/feeds": "^0.2.2",
|
"@welshman/feeds": "^0.2.2",
|
||||||
"@welshman/lib": "^0.2.1",
|
"@welshman/lib": "^0.2.2",
|
||||||
"@welshman/net": "^0.2.2",
|
"@welshman/net": "^0.2.3",
|
||||||
"@welshman/relay": "^0.2.0",
|
"@welshman/relay": "^0.2.0",
|
||||||
"@welshman/router": "^0.2.0",
|
"@welshman/router": "^0.2.0",
|
||||||
"@welshman/signer": "^0.2.1",
|
"@welshman/signer": "^0.2.3",
|
||||||
"@welshman/store": "^0.2.0",
|
"@welshman/store": "^0.2.0",
|
||||||
"@welshman/util": "^0.2.2",
|
"@welshman/util": "^0.2.3",
|
||||||
"daisyui": "^4.12.10",
|
"daisyui": "^4.12.10",
|
||||||
"date-picker-svelte": "^2.13.0",
|
"date-picker-svelte": "^2.13.0",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import dotenv from "dotenv"
|
import dotenv from "dotenv"
|
||||||
import {defineConfig, minimalPreset as preset} from "@vite-pwa/assets-generator/config"
|
import {defineConfig, minimalPreset as preset} from "@vite-pwa/assets-generator/config"
|
||||||
|
|
||||||
dotenv.config({path: ".env.local"})
|
|
||||||
dotenv.config({path: ".env"})
|
dotenv.config({path: ".env"})
|
||||||
|
dotenv.config({path: ".env.template"})
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
preset,
|
preset,
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// This script is necessary for installing stuff on a host, since our links don't exist there.
|
||||||
|
|
||||||
|
import fs from "fs"
|
||||||
|
|
||||||
|
const pkgName = process.argv[2]
|
||||||
|
|
||||||
|
if (!pkgName?.endsWith("package.json")) {
|
||||||
|
console.log("File passed was not a package.json file")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(pkgName, "utf8"))
|
||||||
|
|
||||||
|
if (pkg.pnpm && pkg.pnpm.overrides) {
|
||||||
|
// Use $package notation to make sure we only get one copy of each welshman dependency
|
||||||
|
// TODO: move welshman to a single package to straighten all this out.
|
||||||
|
for (const k of Object.keys(pkg.pnpm.overrides)) {
|
||||||
|
pkg.pnpm.overrides[k] = '$' + k
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(pkgName, JSON.stringify(pkg, null, 2) + "\n")
|
||||||
|
console.log("Removed pnpm.overrides from package.json")
|
||||||
|
} else {
|
||||||
|
console.log("No pnpm.overrides found in package.json")
|
||||||
|
}
|
||||||
+95
-41
@@ -46,6 +46,14 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
font-family: Lato;
|
font-family: Lato;
|
||||||
|
--sait: env(safe-area-inset-top);
|
||||||
|
--saib: env(safe-area-inset-bottom);
|
||||||
|
--sail: env(safe-area-inset-left);
|
||||||
|
--sair: env(safe-area-inset-right);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme] {
|
||||||
|
@apply bg-base-300;
|
||||||
--base-100: oklch(var(--b1));
|
--base-100: oklch(var(--b1));
|
||||||
--base-200: oklch(var(--b2));
|
--base-200: oklch(var(--b2));
|
||||||
--base-300: oklch(var(--b3));
|
--base-300: oklch(var(--b3));
|
||||||
@@ -56,56 +64,80 @@
|
|||||||
--secondary-content: oklch(var(--sc));
|
--secondary-content: oklch(var(--sc));
|
||||||
}
|
}
|
||||||
|
|
||||||
:root,
|
/* safe area insets */
|
||||||
body,
|
|
||||||
html {
|
|
||||||
@apply bg-base-300;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ios */
|
@layer components {
|
||||||
|
.pt-sai {
|
||||||
|
padding-top: var(--sait);
|
||||||
|
}
|
||||||
|
|
||||||
.sait {
|
.pr-sai {
|
||||||
padding-top: env(safe-area-inset-top);
|
padding-right: var(--sair);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sair {
|
.pb-sai {
|
||||||
padding-right: env(safe-area-inset-right);
|
padding-bottom: var(--saib);
|
||||||
}
|
}
|
||||||
|
|
||||||
.saib {
|
.pl-sai {
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
padding-left: var(--sail);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sail {
|
.px-sai {
|
||||||
padding-left: env(safe-area-inset-left);
|
@apply pl-sai pr-sai;
|
||||||
}
|
}
|
||||||
|
|
||||||
.saix {
|
.py-sai {
|
||||||
@apply sail sair;
|
@apply pt-sai pb-sai;
|
||||||
}
|
}
|
||||||
|
|
||||||
.saiy {
|
.p-sai {
|
||||||
@apply sait saib;
|
@apply py-sai px-sai;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sai {
|
.mt-sai {
|
||||||
@apply saiy saix;
|
padding-top: var(--sait);
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-sai {
|
.mr-sai {
|
||||||
top: env(safe-area-inset-top);
|
padding-right: var(--sair);
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-sai {
|
.mb-sai {
|
||||||
right: env(safe-area-inset-right);
|
padding-bottom: var(--saib);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-sai {
|
.ml-sai {
|
||||||
bottom: env(safe-area-inset-bottom);
|
padding-left: var(--sail);
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-sai {
|
.mx-sai {
|
||||||
left: env(safe-area-inset-left);
|
@apply ml-sai mr-sai;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-sai {
|
||||||
|
@apply mt-sai mb-sai;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-sai {
|
||||||
|
@apply my-sai mx-sai;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-sai {
|
||||||
|
top: var(--sait);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-sai {
|
||||||
|
right: var(--sair);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-sai {
|
||||||
|
bottom: var(--saib);
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-sai {
|
||||||
|
left: var(--sail);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* utilities */
|
/* utilities */
|
||||||
@@ -259,6 +291,14 @@ html {
|
|||||||
--tiptap-active-fg: var(--base-content);
|
--tiptap-active-fg: var(--base-content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tiptap-suggestions__item {
|
||||||
|
@apply border-l-2 border-solid border-base-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tiptap-suggestions__selected {
|
||||||
|
@apply border-primary;
|
||||||
|
}
|
||||||
|
|
||||||
.tiptap {
|
.tiptap {
|
||||||
@apply max-h-[350px] overflow-y-auto p-2 px-4;
|
@apply max-h-[350px] overflow-y-auto p-2 px-4;
|
||||||
}
|
}
|
||||||
@@ -294,6 +334,16 @@ html {
|
|||||||
color: var(--base-content);
|
color: var(--base-content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* content rendered by welshman/content */
|
||||||
|
|
||||||
|
.welshman-content a {
|
||||||
|
@apply link;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welshman-content-error a {
|
||||||
|
@apply underline;
|
||||||
|
}
|
||||||
|
|
||||||
/* date input */
|
/* date input */
|
||||||
|
|
||||||
.picker {
|
.picker {
|
||||||
@@ -335,11 +385,15 @@ progress[value]::-webkit-progress-value {
|
|||||||
/* content width for fixed elements */
|
/* content width for fixed elements */
|
||||||
|
|
||||||
.cw {
|
.cw {
|
||||||
@apply w-full md:w-[calc(100%-18.5rem)];
|
@apply w-full md:left-[18.5rem] md:w-[calc(100%-18.5rem-var(--sair))];
|
||||||
|
}
|
||||||
|
|
||||||
|
.cw-full {
|
||||||
|
@apply w-full md:left-[4rem] md:w-[calc(100%-4rem-var(--sair))];
|
||||||
}
|
}
|
||||||
|
|
||||||
.cb {
|
.cb {
|
||||||
@apply saib bottom-14 md:bottom-0;
|
@apply md:bottom-sai bottom-[calc(var(--saib)+3.5rem)];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* chat view */
|
/* chat view */
|
||||||
@@ -349,5 +403,5 @@ progress[value]::-webkit-progress-value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat__scroll-down {
|
.chat__scroll-down {
|
||||||
@apply saib fixed bottom-28 right-4 md:bottom-16;
|
@apply fixed bottom-28 right-4 md:bottom-16;
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-12
@@ -1,6 +1,6 @@
|
|||||||
import * as nip19 from "nostr-tools/nip19"
|
import * as nip19 from "nostr-tools/nip19"
|
||||||
import {get} from "svelte/store"
|
import {get} from "svelte/store"
|
||||||
import {randomId, ifLet, poll, uniq, equals, TIMEZONE, LOCALE} from "@welshman/lib"
|
import {randomId, poll, uniq, equals, TIMEZONE, LOCALE} from "@welshman/lib"
|
||||||
import type {Feed} from "@welshman/feeds"
|
import type {Feed} from "@welshman/feeds"
|
||||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
@@ -259,25 +259,30 @@ export const setInboxRelayPolicy = (url: string, enabled: boolean) => {
|
|||||||
export const checkRelayAccess = async (url: string, claim = "") => {
|
export const checkRelayAccess = async (url: string, claim = "") => {
|
||||||
const socket = Pool.get().get(url)
|
const socket = Pool.get().get(url)
|
||||||
|
|
||||||
await socket.auth.attemptAuth(signer.get().sign)
|
await socket.auth.attemptAuth(e => signer.get()?.sign(e))
|
||||||
|
|
||||||
const thunk = publishThunk({
|
const thunk = publishThunk({
|
||||||
event: createEvent(AUTH_JOIN, {tags: [["claim", claim]]}),
|
event: createEvent(AUTH_JOIN, {tags: [["claim", claim]]}),
|
||||||
relays: [url],
|
relays: [url],
|
||||||
})
|
})
|
||||||
|
|
||||||
ifLet(await getThunkError(thunk), error => {
|
const error = await getThunkError(thunk)
|
||||||
|
|
||||||
|
if (error) {
|
||||||
const message =
|
const message =
|
||||||
socket.auth.details?.replace(/^.*: /, "") ||
|
socket.auth.details?.replace(/^\w+: /, "") ||
|
||||||
error?.replace(/^.*: /, "") ||
|
error?.replace(/^\w+: /, "") ||
|
||||||
"join request rejected"
|
"join request rejected"
|
||||||
|
|
||||||
// If it's a strict NIP 29 relay don't worry about requesting access
|
// If it's a strict NIP 29 relay don't worry about requesting access
|
||||||
// TODO: remove this if relay29 ever gets less strict
|
// TODO: remove this if relay29 ever gets less strict
|
||||||
if (message !== "missing group (`h`) tag") {
|
if (message === "missing group (`h`) tag") return
|
||||||
return `Failed to join relay (${message})`
|
|
||||||
}
|
// Ignore messages about the relay ignoring ours
|
||||||
})
|
if (error?.startsWith("mute: ")) return
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkRelayProfile = async (url: string) => {
|
export const checkRelayProfile = async (url: string) => {
|
||||||
@@ -307,7 +312,7 @@ export const checkRelayAuth = async (url: string, timeout = 3000) => {
|
|||||||
const socket = Pool.get().get(url)
|
const socket = Pool.get().get(url)
|
||||||
const okStatuses = [AuthStatus.None, AuthStatus.Ok]
|
const okStatuses = [AuthStatus.None, AuthStatus.Ok]
|
||||||
|
|
||||||
await socket.auth.attemptAuth(signer.get().sign)
|
await socket.auth.attemptAuth(e => signer.get()?.sign(e))
|
||||||
|
|
||||||
// Only raise an error if it's not a timeout.
|
// Only raise an error if it's not a timeout.
|
||||||
// If it is, odds are the problem is with our signer, not the relay
|
// If it is, odds are the problem is with our signer, not the relay
|
||||||
@@ -375,10 +380,12 @@ export const publishReport = ({
|
|||||||
export type ReactionParams = {
|
export type ReactionParams = {
|
||||||
event: TrustedEvent
|
event: TrustedEvent
|
||||||
content: string
|
content: string
|
||||||
|
tags?: string[][]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const makeReaction = ({event, content}: ReactionParams) => {
|
export const makeReaction = ({content, event, tags: paramTags = []}: ReactionParams) => {
|
||||||
const tags = tagEventForReaction(event)
|
const tags = [...paramTags, ...tagEventForReaction(event)]
|
||||||
|
|
||||||
const groupTag = getTag("h", event.tags)
|
const groupTag = getTag("h", event.tags)
|
||||||
|
|
||||||
if (groupTag) {
|
if (groupTag) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
loading = $state(false)
|
loading = $state(false)
|
||||||
clientSecret = makeSecret()
|
clientSecret = makeSecret()
|
||||||
abortController = new AbortController()
|
abortController = new AbortController()
|
||||||
broker = Nip46Broker.get({clientSecret: this.clientSecret, relays: SIGNER_RELAYS})
|
broker = new Nip46Broker({clientSecret: this.clientSecret, relays: SIGNER_RELAYS})
|
||||||
onNostrConnect: (response: Nip46ResponseWithResult) => void
|
onNostrConnect: (response: Nip46ResponseWithResult) => void
|
||||||
|
|
||||||
constructor({onNostrConnect}: {onNostrConnect: (response: Nip46ResponseWithResult) => void}) {
|
constructor({onNostrConnect}: {onNostrConnect: (response: Nip46ResponseWithResult) => void}) {
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
|
this.broker.cleanup()
|
||||||
this.abortController.abort()
|
this.abortController.abort()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {pubkey} from "@welshman/app"
|
import {pubkey} from "@welshman/app"
|
||||||
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"
|
||||||
@@ -26,20 +26,15 @@
|
|||||||
|
|
||||||
const editEvent = () => pushModal(CalendarEventEdit, {url, event})
|
const editEvent = () => pushModal(CalendarEventEdit, {url, event})
|
||||||
|
|
||||||
const onReactionClick = (content: string, events: TrustedEvent[]) => {
|
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
|
||||||
const reaction = events.find(e => e.pubkey === $pubkey)
|
|
||||||
|
|
||||||
if (reaction) {
|
const createReaction = (template: EventContent) =>
|
||||||
publishDelete({relays: [url], event: reaction})
|
publishReaction({...template, event, relays: [url]})
|
||||||
} else {
|
|
||||||
publishReaction({event, content, relays: [url]})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||||
<div class="flex flex-grow flex-wrap justify-end gap-2">
|
<div class="flex flex-grow flex-wrap justify-end gap-2">
|
||||||
<ReactionSummary {url} {event} {onReactionClick} reactionClass="tooltip-left" />
|
<ReactionSummary {url} {event} {deleteReaction} {createReaction} reactionClass="tooltip-left" />
|
||||||
<ThunkStatusOrDeleted {event} />
|
<ThunkStatusOrDeleted {event} />
|
||||||
{#if showActivity}
|
{#if showActivity}
|
||||||
<EventActivity {url} {path} {event} />
|
<EventActivity {url} {path} {event} />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {hash, now, formatTimestampAsTime, formatTimestampAsDate} from "@welshman/lib"
|
import {hash, now, formatTimestampAsTime, formatTimestampAsDate} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {thunks, pubkey, deriveProfile, deriveProfileDisplay} from "@welshman/app"
|
import {thunks, deriveProfile, deriveProfileDisplay} from "@welshman/app"
|
||||||
import {isMobile} from "@lib/html"
|
import {isMobile} from "@lib/html"
|
||||||
import TapTarget from "@lib/components/TapTarget.svelte"
|
import TapTarget from "@lib/components/TapTarget.svelte"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import Avatar from "@lib/components/Avatar.svelte"
|
||||||
@@ -41,15 +41,10 @@
|
|||||||
|
|
||||||
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey, url})
|
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey, url})
|
||||||
|
|
||||||
const onReactionClick = (content: string, events: TrustedEvent[]) => {
|
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
|
||||||
const reaction = events.find(e => e.pubkey === $pubkey)
|
|
||||||
|
|
||||||
if (reaction) {
|
const createReaction = (template: EventContent) =>
|
||||||
publishDelete({relays: [url], event: reaction})
|
publishReaction({...template, event, relays: [url]})
|
||||||
} else {
|
|
||||||
publishReaction({event, content, relays: [url]})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<TapTarget
|
<TapTarget
|
||||||
@@ -89,7 +84,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row-2 ml-10 mt-1">
|
<div class="row-2 ml-10 mt-1">
|
||||||
<ReactionSummary {url} {event} {onReactionClick} reactionClass="tooltip-right" />
|
<ReactionSummary
|
||||||
|
{url}
|
||||||
|
{event}
|
||||||
|
{deleteReaction}
|
||||||
|
{createReaction}
|
||||||
|
reactionClass="tooltip-right" />
|
||||||
</div>
|
</div>
|
||||||
{#if !isMobile}
|
{#if !isMobile}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -126,9 +126,11 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
observer.observe(chatCompose!)
|
observer.observe(chatCompose!)
|
||||||
|
observer.observe(dynamicPadding!)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
observer.unobserve(chatCompose!)
|
observer.unobserve(chatCompose!)
|
||||||
|
observer.unobserve(dynamicPadding!)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {type Instance} from "tippy.js"
|
import {type Instance} from "tippy.js"
|
||||||
import {hash, formatTimestampAsTime} from "@welshman/lib"
|
import {hash, formatTimestampAsTime} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {thunks, pubkey, deriveProfile, deriveProfileDisplay, sendWrapped} from "@welshman/app"
|
import {thunks, pubkey, deriveProfile, deriveProfileDisplay, sendWrapped} from "@welshman/app"
|
||||||
import {isMobile} from "@lib/html"
|
import {isMobile} from "@lib/html"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
@@ -36,12 +36,11 @@
|
|||||||
|
|
||||||
const reply = () => replyTo(event)
|
const reply = () => replyTo(event)
|
||||||
|
|
||||||
const onReactionClick = async (content: string, events: TrustedEvent[]) => {
|
const deleteReaction = (event: TrustedEvent) =>
|
||||||
const reaction = events.find(e => e.pubkey === $pubkey)
|
sendWrapped({template: makeDelete({event}), pubkeys})
|
||||||
const template = reaction ? makeDelete({event: reaction}) : makeReaction({event, content})
|
|
||||||
|
|
||||||
await sendWrapped({template, pubkeys})
|
const createReaction = (template: EventContent) =>
|
||||||
}
|
sendWrapped({template: makeReaction({event, ...template}), pubkeys})
|
||||||
|
|
||||||
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey})
|
const openProfile = () => pushModal(ProfileDetail, {pubkey: event.pubkey})
|
||||||
|
|
||||||
@@ -120,7 +119,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</TapTarget>
|
</TapTarget>
|
||||||
<div class="row-2 z-feature -mt-4 ml-4">
|
<div class="row-2 z-feature -mt-4 ml-4">
|
||||||
<ReactionSummary {event} {onReactionClick} noTooltip />
|
<ReactionSummary {event} {deleteReaction} {createReaction} noTooltip />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
truncate,
|
truncate,
|
||||||
renderAsHtml,
|
renderAsHtml,
|
||||||
isText,
|
isText,
|
||||||
|
isEmoji,
|
||||||
isTopic,
|
isTopic,
|
||||||
isCode,
|
isCode,
|
||||||
isCashu,
|
isCashu,
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import ContentToken from "@app/components/ContentToken.svelte"
|
import ContentToken from "@app/components/ContentToken.svelte"
|
||||||
|
import ContentEmoji from "@app/components/ContentEmoji.svelte"
|
||||||
import ContentCode from "@app/components/ContentCode.svelte"
|
import ContentCode from "@app/components/ContentCode.svelte"
|
||||||
import ContentLinkInline from "@app/components/ContentLinkInline.svelte"
|
import ContentLinkInline from "@app/components/ContentLinkInline.svelte"
|
||||||
import ContentLinkBlock from "@app/components/ContentLinkBlock.svelte"
|
import ContentLinkBlock from "@app/components/ContentLinkBlock.svelte"
|
||||||
@@ -133,6 +135,8 @@
|
|||||||
<ContentNewline value={parsed.value} />
|
<ContentNewline value={parsed.value} />
|
||||||
{:else if isTopic(parsed)}
|
{:else if isTopic(parsed)}
|
||||||
<ContentTopic value={parsed.value} />
|
<ContentTopic value={parsed.value} />
|
||||||
|
{:else if isEmoji(parsed)}
|
||||||
|
<ContentEmoji value={parsed.value} />
|
||||||
{:else if isCode(parsed)}
|
{:else if isCode(parsed)}
|
||||||
<ContentCode
|
<ContentCode
|
||||||
value={parsed.value}
|
value={parsed.value}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type {ParsedEmojiValue} from "@welshman/content"
|
||||||
|
import {imgproxy} from "@app/state"
|
||||||
|
|
||||||
|
export let value: ParsedEmojiValue
|
||||||
|
|
||||||
|
const alt = `:${value.name}:`
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if value.url}
|
||||||
|
<img
|
||||||
|
{alt}
|
||||||
|
src={imgproxy(value.url, {w: 24, h: 24})}
|
||||||
|
class="-mt-0.5 inline h-[1em] min-w-[1em] align-middle" />
|
||||||
|
{:else}
|
||||||
|
{alt}
|
||||||
|
{/if}
|
||||||
@@ -46,12 +46,13 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const {clientSecret} = controller
|
const {clientSecret} = controller
|
||||||
const broker = Nip46Broker.get({relays, clientSecret, signerPubkey})
|
const broker = new Nip46Broker({relays, clientSecret, signerPubkey})
|
||||||
const result = await broker.connect(connectSecret, NIP46_PERMS)
|
const result = await broker.connect(connectSecret, NIP46_PERMS)
|
||||||
const pubkey = await broker.getPublicKey()
|
const pubkey = await broker.getPublicKey()
|
||||||
|
|
||||||
// TODO: remove ack result
|
// TODO: remove ack result
|
||||||
if (pubkey && ["ack", connectSecret].includes(result)) {
|
if (pubkey && ["ack", connectSecret].includes(result)) {
|
||||||
|
broker.cleanup()
|
||||||
controller.stop()
|
controller.stop()
|
||||||
|
|
||||||
await loadUserData(pubkey)
|
await loadUserData(pubkey)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
? [normalizeRelayUrl("ws://" + stripProtocol(BURROW_URL))]
|
? [normalizeRelayUrl("ws://" + stripProtocol(BURROW_URL))]
|
||||||
: [normalizeRelayUrl(BURROW_URL)]
|
: [normalizeRelayUrl(BURROW_URL)]
|
||||||
|
|
||||||
const broker = Nip46Broker.get({clientSecret, relays})
|
const broker = new Nip46Broker({clientSecret, relays})
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
await loadUserData(pubkey)
|
await loadUserData(pubkey)
|
||||||
|
|
||||||
addSession({...session, email})
|
addSession({...session, email})
|
||||||
|
broker.cleanup()
|
||||||
setChecked("*")
|
setChecked("*")
|
||||||
clearModals()
|
clearModals()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import MenuSpacesItem from "@app/components/MenuSpacesItem.svelte"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
urls: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const {urls}: Props = $props()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="column menu gap-2">
|
||||||
|
{#each urls as url (url)}
|
||||||
|
<MenuSpacesItem {url} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
@@ -5,35 +5,30 @@
|
|||||||
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_RELAY} from "@app/state"
|
import {userRoomsByUrl} from "@app/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
const addSpace = () => pushModal(SpaceAdd)
|
const addSpace = () => pushModal(SpaceAdd)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column menu gap-2">
|
<div class="column menu gap-2">
|
||||||
{#if PLATFORM_RELAY}
|
{#if $userRoomsByUrl.size > 0}
|
||||||
<MenuSpacesItem url={PLATFORM_RELAY} />
|
|
||||||
<Divider />
|
|
||||||
{:else if $userRoomsByUrl.size > 0}
|
|
||||||
{#each $userRoomsByUrl.keys() as url (url)}
|
{#each $userRoomsByUrl.keys() as url (url)}
|
||||||
<MenuSpacesItem {url} />
|
<MenuSpacesItem {url} />
|
||||||
{/each}
|
{/each}
|
||||||
<Divider />
|
<Divider />
|
||||||
{/if}
|
{/if}
|
||||||
{#if !PLATFORM_RELAY}
|
<Button onclick={addSpace}>
|
||||||
<Button onclick={addSpace}>
|
<CardButton>
|
||||||
<CardButton>
|
{#snippet icon()}
|
||||||
{#snippet icon()}
|
<div><Icon icon="login-2" size={7} /></div>
|
||||||
<div><Icon icon="login-2" size={7} /></div>
|
{/snippet}
|
||||||
{/snippet}
|
{#snippet title()}
|
||||||
{#snippet title()}
|
<div>Add a space</div>
|
||||||
<div>Add a space</div>
|
{/snippet}
|
||||||
{/snippet}
|
{#snippet info()}
|
||||||
{#snippet info()}
|
<div>Join or create a new space</div>
|
||||||
<div>Join or create a new space</div>
|
{/snippet}
|
||||||
{/snippet}
|
</CardButton>
|
||||||
</CardButton>
|
</Button>
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {NativeEmoji} from "emoji-picker-element/shared"
|
import type {NativeEmoji} from "emoji-picker-element/shared"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {pubkey} 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 NoteContent from "@app/components/NoteContent.svelte"
|
import NoteContent from "@app/components/NoteContent.svelte"
|
||||||
@@ -11,15 +10,10 @@
|
|||||||
|
|
||||||
const {url, event} = $props()
|
const {url, event} = $props()
|
||||||
|
|
||||||
const onReactionClick = (content: string, events: TrustedEvent[]) => {
|
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
|
||||||
const reaction = events.find(e => e.pubkey === $pubkey)
|
|
||||||
|
|
||||||
if (reaction) {
|
const createReaction = (template: EventContent) =>
|
||||||
publishDelete({relays: [url], event: reaction})
|
publishReaction({...template, event, relays: [url]})
|
||||||
} else {
|
|
||||||
publishReaction({event, content, relays: [url]})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onEmoji = (emoji: NativeEmoji) =>
|
const onEmoji = (emoji: NativeEmoji) =>
|
||||||
publishReaction({event, content: emoji.unicode, relays: [url]})
|
publishReaction({event, content: emoji.unicode, relays: [url]})
|
||||||
@@ -28,7 +22,7 @@
|
|||||||
<NoteCard {event} {url} class="card2 bg-alt">
|
<NoteCard {event} {url} class="card2 bg-alt">
|
||||||
<NoteContent {event} expandMode="inline" />
|
<NoteContent {event} expandMode="inline" />
|
||||||
<div class="flex w-full justify-between gap-2">
|
<div class="flex w-full justify-between gap-2">
|
||||||
<ReactionSummary {url} {event} {onReactionClick} reactionClass="tooltip-right">
|
<ReactionSummary {url} {event} {deleteReaction} {createReaction} reactionClass="tooltip-right">
|
||||||
<EmojiButton {onEmoji} class="btn btn-neutral btn-xs h-[26px] rounded-box">
|
<EmojiButton {onEmoji} class="btn btn-neutral btn-xs h-[26px] rounded-box">
|
||||||
<Icon icon="smile-circle" size={4} />
|
<Icon icon="smile-circle" size={4} />
|
||||||
</EmojiButton>
|
</EmojiButton>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import Profile from "@app/components/Profile.svelte"
|
import Profile from "@app/components/Profile.svelte"
|
||||||
import ProfileInfo from "@app/components/ProfileInfo.svelte"
|
import ProfileInfo from "@app/components/ProfileInfo.svelte"
|
||||||
import {makeChatPath} from "@app/routes"
|
import {pubkeyLink} from "@app/state"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
@@ -37,9 +37,9 @@
|
|||||||
<div class="card2 bg-alt col-2 shadow-xl">
|
<div class="card2 bg-alt col-2 shadow-xl">
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<Profile {pubkey} {url} />
|
<Profile {pubkey} {url} />
|
||||||
<Link class="btn btn-primary hidden sm:flex" href={makeChatPath([pubkey])}>
|
<Link external href={pubkeyLink(pubkey)} class="btn btn-primary hidden sm:flex">
|
||||||
<Icon icon="letter" />
|
<Icon icon="user-circle" />
|
||||||
Start a Chat
|
See Complete Profile
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<ProfileInfo {pubkey} {url} />
|
<ProfileInfo {pubkey} {url} />
|
||||||
@@ -48,8 +48,8 @@
|
|||||||
Last active {formatTimestampRelative($events[0].created_at)}
|
Last active {formatTimestampRelative($events[0].created_at)}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<Link class="btn btn-primary sm:hidden" href={makeChatPath([pubkey])}>
|
<Link external href={pubkeyLink(pubkey)} class="btn btn-primary sm:hidden">
|
||||||
<Icon icon="letter" />
|
<Icon icon="user-circle" />
|
||||||
Start a Chat
|
See Complete Profile
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type {Snippet} from "svelte"
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import {goto} from "$app/navigation"
|
import {goto} from "$app/navigation"
|
||||||
|
import {splitAt} from "@welshman/lib"
|
||||||
import {userProfile} from "@welshman/app"
|
import {userProfile} from "@welshman/app"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import Avatar from "@lib/components/Avatar.svelte"
|
||||||
import Divider from "@lib/components/Divider.svelte"
|
import Divider from "@lib/components/Divider.svelte"
|
||||||
@@ -8,34 +10,51 @@
|
|||||||
import SpaceAdd from "@app/components/SpaceAdd.svelte"
|
import SpaceAdd from "@app/components/SpaceAdd.svelte"
|
||||||
import ChatEnable from "@app/components/ChatEnable.svelte"
|
import ChatEnable from "@app/components/ChatEnable.svelte"
|
||||||
import MenuSpaces from "@app/components/MenuSpaces.svelte"
|
import MenuSpaces from "@app/components/MenuSpaces.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_RELAY, PLATFORM_LOGO} from "@app/state"
|
import {userRoomsByUrl, canDecrypt, PLATFORM_RELAY, PLATFORM_LOGO} from "@app/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
import {makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/routes"
|
||||||
import {notifications} from "@app/notifications"
|
import {notifications} from "@app/notifications"
|
||||||
interface Props {
|
|
||||||
children?: import("svelte").Snippet
|
type Props = {
|
||||||
|
children?: Snippet
|
||||||
}
|
}
|
||||||
|
|
||||||
const {children}: Props = $props()
|
const {children}: Props = $props()
|
||||||
|
|
||||||
const addSpace = () => pushModal(SpaceAdd)
|
const addSpace = () => pushModal(SpaceAdd)
|
||||||
|
|
||||||
const showSpacesMenu = () => (spacePaths.length > 0 ? pushModal(MenuSpaces) : pushModal(SpaceAdd))
|
const showSpacesMenu = () => (spaceUrls.length > 0 ? pushModal(MenuSpaces) : pushModal(SpaceAdd))
|
||||||
|
|
||||||
|
const showOtherSpacesMenu = () => pushModal(MenuOtherSpaces, {urls: secondarySpaceUrls})
|
||||||
|
|
||||||
const showSettingsMenu = () => pushModal(MenuSettings)
|
const showSettingsMenu = () => pushModal(MenuSettings)
|
||||||
|
|
||||||
const openChat = () => ($canDecrypt ? goto("/chat") : pushModal(ChatEnable, {next: "/chat"}))
|
const openChat = () => ($canDecrypt ? goto("/chat") : pushModal(ChatEnable, {next: "/chat"}))
|
||||||
|
|
||||||
|
const hasNotification = (url: string) => {
|
||||||
|
const path = makeSpacePath(url)
|
||||||
|
|
||||||
|
return !$page.url.pathname.startsWith(path) && $notifications.has(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
let windowHeight = $state(0)
|
||||||
|
|
||||||
|
const itemHeight = 56
|
||||||
|
const navPadding = 6 * itemHeight
|
||||||
|
const itemLimit = $derived((windowHeight - navPadding) / itemHeight)
|
||||||
const spaceUrls = $derived(Array.from($userRoomsByUrl.keys()))
|
const spaceUrls = $derived(Array.from($userRoomsByUrl.keys()))
|
||||||
const spacePaths = $derived(spaceUrls.map(url => makeSpacePath(url)))
|
const [primarySpaceUrls, secondarySpaceUrls] = $derived(splitAt(itemLimit, spaceUrls))
|
||||||
const anySpaceNotifications = $derived(
|
const anySpaceNotifications = $derived(spaceUrls.some(hasNotification))
|
||||||
spacePaths.some(path => !$page.url.pathname.startsWith(path) && $notifications.has(path)),
|
const otherSpaceNotifications = $derived(secondarySpaceUrls.some(hasNotification))
|
||||||
)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="sail sait saib relative z-nav hidden w-14 flex-shrink-0 bg-base-200 pt-4 md:block">
|
<svelte:window bind:innerHeight={windowHeight} />
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="ml-sai mt-sai mb-sai relative z-nav hidden w-14 flex-shrink-0 bg-base-200 pt-4 md:block">
|
||||||
<div class="flex h-full flex-col justify-between">
|
<div class="flex h-full flex-col justify-between">
|
||||||
<div>
|
<div>
|
||||||
{#if PLATFORM_RELAY}
|
{#if PLATFORM_RELAY}
|
||||||
@@ -45,9 +64,18 @@
|
|||||||
<Avatar src={PLATFORM_LOGO} class="!h-10 !w-10" />
|
<Avatar src={PLATFORM_LOGO} class="!h-10 !w-10" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
<Divider />
|
<Divider />
|
||||||
{#each spaceUrls as url (url)}
|
{#each primarySpaceUrls as url (url)}
|
||||||
<PrimaryNavItemSpace {url} />
|
<PrimaryNavItemSpace {url} />
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if secondarySpaceUrls.length > 0}
|
||||||
|
<PrimaryNavItem
|
||||||
|
title="Other Spaces"
|
||||||
|
class="tooltip-right"
|
||||||
|
onclick={showOtherSpacesMenu}
|
||||||
|
notification={otherSpaceNotifications}>
|
||||||
|
<Avatar icon="widget" class="!h-10 !w-10" />
|
||||||
|
</PrimaryNavItem>
|
||||||
|
{/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="settings-minimalistic" class="!h-10 !w-10" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
@@ -78,7 +106,7 @@
|
|||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|
||||||
<!-- a little extra something for ios -->
|
<!-- a little extra something for ios -->
|
||||||
<div class="fixed bottom-0 left-0 right-0 z-nav h-14 bg-base-100 md:hidden"></div>
|
<div class="fixed bottom-0 left-0 right-0 z-nav h-[var(--saib)] bg-base-100 md:hidden"></div>
|
||||||
<div
|
<div
|
||||||
class="border-top bottom-sai fixed left-0 right-0 z-nav h-14 border border-base-200 bg-base-100 md:hidden">
|
class="border-top bottom-sai fixed left-0 right-0 z-nav h-14 border border-base-200 bg-base-100 md:hidden">
|
||||||
<div class="content-padding-x content-sizing flex justify-between px-2">
|
<div class="content-padding-x content-sizing flex justify-between px-2">
|
||||||
@@ -92,9 +120,14 @@
|
|||||||
notification={$notifications.has("/chat")}>
|
notification={$notifications.has("/chat")}>
|
||||||
<Avatar icon="letter" class="!h-10 !w-10" />
|
<Avatar icon="letter" class="!h-10 !w-10" />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
<PrimaryNavItem title="Spaces" onclick={showSpacesMenu} notification={anySpaceNotifications}>
|
{#if !PLATFORM_RELAY}
|
||||||
<Avatar icon="settings-minimalistic" class="!h-10 !w-10" />
|
<PrimaryNavItem
|
||||||
</PrimaryNavItem>
|
title="Spaces"
|
||||||
|
onclick={showSpacesMenu}
|
||||||
|
notification={anySpaceNotifications}>
|
||||||
|
<Avatar icon="settings-minimalistic" class="!h-10 !w-10" />
|
||||||
|
</PrimaryNavItem>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<PrimaryNavItem title="Settings" onclick={showSettingsMenu}>
|
<PrimaryNavItem title="Settings" onclick={showSettingsMenu}>
|
||||||
<Avatar icon="settings" src={$userProfile?.picture} class="!h-10 !w-10" />
|
<Avatar icon="settings" src={$userProfile?.picture} class="!h-10 !w-10" />
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ProfileInfo {pubkey} {url} />
|
<ProfileInfo {pubkey} {url} />
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button onclick={back} class="btn btn-link">
|
<Button onclick={back} class="hidden md:btn md:btn-link">
|
||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon="alt-arrow-left" />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -45,7 +45,8 @@
|
|||||||
e => e.id,
|
e => e.id,
|
||||||
sortBy(e => -e.created_at, buffer),
|
sortBy(e => -e.created_at, buffer),
|
||||||
)
|
)
|
||||||
events = [...events, ...buffer.splice(0, 5)]
|
|
||||||
|
events = uniqBy(e => e.id, [...events, ...buffer.splice(0, 5)])
|
||||||
|
|
||||||
if (buffer.length < 50) {
|
if (buffer.length < 50) {
|
||||||
ctrl.load(50)
|
ctrl.load(50)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import {clip} from "@app/toast"
|
import {clip} from "@app/toast"
|
||||||
|
|
||||||
const {code} = $props()
|
const {code, ...props} = $props()
|
||||||
|
|
||||||
let canvas: Element | undefined = $state()
|
let canvas: Element | undefined = $state()
|
||||||
let wrapper: Element | undefined = $state()
|
let wrapper: Element | undefined = $state()
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button class="max-w-full" onclick={copy}>
|
<Button class="max-w-full {props.class}" onclick={copy}>
|
||||||
<div bind:this={wrapper} style={`height: ${height}px`}>
|
<div bind:this={wrapper} style={`height: ${height}px`}>
|
||||||
<canvas
|
<canvas
|
||||||
class="rounded-box"
|
class="rounded-box"
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {parse, isEmoji, renderAsHtml} from "@welshman/content"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import ContentEmoji from "@app/components/ContentEmoji.svelte"
|
||||||
|
|
||||||
|
export let event
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if event.content === "+" || event.content === ""}
|
||||||
|
<Icon icon="heart" />
|
||||||
|
{:else if event.content === "-"}
|
||||||
|
<Icon icon="thumbs-down" />
|
||||||
|
{:else}
|
||||||
|
{#each parse(event) as parsed}
|
||||||
|
{#if isEmoji(parsed)}
|
||||||
|
<ContentEmoji value={parsed.value} />
|
||||||
|
{:else}
|
||||||
|
{@html renderAsHtml(parsed)}
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
@@ -2,20 +2,29 @@
|
|||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import type {Snippet} from "svelte"
|
import type {Snippet} from "svelte"
|
||||||
import {groupBy, uniq, uniqBy, batch, displayList} from "@welshman/lib"
|
import {groupBy, uniq, uniqBy, batch, displayList} from "@welshman/lib"
|
||||||
import {REACTION, getReplyFilters, getTag, REPORT, DELETE} from "@welshman/util"
|
import {
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
REACTION,
|
||||||
|
getReplyFilters,
|
||||||
|
getEmojiTags,
|
||||||
|
getEmojiTag,
|
||||||
|
getTag,
|
||||||
|
REPORT,
|
||||||
|
DELETE,
|
||||||
|
} from "@welshman/util"
|
||||||
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {deriveEvents} from "@welshman/store"
|
import {deriveEvents} from "@welshman/store"
|
||||||
import {load} from "@welshman/net"
|
import {load} from "@welshman/net"
|
||||||
import {pubkey, repository, displayProfileByPubkey} from "@welshman/app"
|
import {pubkey, repository, displayProfileByPubkey} from "@welshman/app"
|
||||||
import {isMobile, preventDefault, stopPropagation} from "@lib/html"
|
import {isMobile, preventDefault, stopPropagation} from "@lib/html"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Reaction from "@app/components/Reaction.svelte"
|
||||||
import EventReportDetails from "@app/components/EventReportDetails.svelte"
|
import EventReportDetails from "@app/components/EventReportDetails.svelte"
|
||||||
import {displayReaction} from "@app/state"
|
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
event: any
|
event: TrustedEvent
|
||||||
onReactionClick: any
|
deleteReaction: (event: TrustedEvent) => void
|
||||||
|
createReaction: (event: EventContent) => void
|
||||||
url?: string
|
url?: string
|
||||||
reactionClass?: string
|
reactionClass?: string
|
||||||
noTooltip?: boolean
|
noTooltip?: boolean
|
||||||
@@ -24,7 +33,8 @@
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
event,
|
event,
|
||||||
onReactionClick,
|
deleteReaction,
|
||||||
|
createReaction,
|
||||||
url = "",
|
url = "",
|
||||||
reactionClass = "",
|
reactionClass = "",
|
||||||
noTooltip = false,
|
noTooltip = false,
|
||||||
@@ -39,14 +49,31 @@
|
|||||||
filters: [{kinds: [REACTION], "#e": [event.id]}],
|
filters: [{kinds: [REACTION], "#e": [event.id]}],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const onReactionClick = (events: TrustedEvent[]) => {
|
||||||
|
const reaction = events.find(e => e.pubkey === $pubkey)
|
||||||
|
|
||||||
|
if (reaction) {
|
||||||
|
deleteReaction(reaction)
|
||||||
|
} else {
|
||||||
|
const [event] = events
|
||||||
|
|
||||||
|
createReaction({
|
||||||
|
content: event.content,
|
||||||
|
tags: getEmojiTags(event.content.replace(/:/g, ""), event.tags),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onReportClick = () => pushModal(EventReportDetails, {url, event})
|
const onReportClick = () => pushModal(EventReportDetails, {url, event})
|
||||||
|
|
||||||
const reportReasons = $derived(uniq($reports.map(e => getTag("e", e.tags)?.[2])))
|
const reportReasons = $derived(uniq($reports.map(e => getTag("e", e.tags)?.[2])))
|
||||||
|
|
||||||
|
const getReactionKey = (e: TrustedEvent) => getEmojiTag(e.content, e.tags)?.join("") || e.content
|
||||||
|
|
||||||
const groupedReactions = $derived(
|
const groupedReactions = $derived(
|
||||||
groupBy(
|
groupBy(
|
||||||
e => e.content,
|
getReactionKey,
|
||||||
uniqBy(e => e.pubkey + e.content, $reactions),
|
uniqBy(e => `${e.pubkey}${getReactionKey(e)}`, $reactions),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -78,7 +105,7 @@
|
|||||||
{#if url && $reports.length > 0}
|
{#if url && $reports.length > 0}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-tip="{`This content has been reported as "${displayList(reportReasons)}".`}}"
|
data-tip={`This content has been reported as "${displayList(reportReasons)}".`}
|
||||||
class="btn btn-error btn-xs tooltip-right flex items-center gap-1 rounded-full"
|
class="btn btn-error btn-xs tooltip-right flex items-center gap-1 rounded-full"
|
||||||
class:tooltip={!noTooltip && !isMobile}
|
class:tooltip={!noTooltip && !isMobile}
|
||||||
onclick={stopPropagation(preventDefault(onReportClick))}>
|
onclick={stopPropagation(preventDefault(onReportClick))}>
|
||||||
@@ -86,12 +113,12 @@
|
|||||||
<span>{$reports.length}</span>
|
<span>{$reports.length}</span>
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
{#each groupedReactions.entries() as [content, events]}
|
{#each groupedReactions.entries() as [key, events]}
|
||||||
{@const pubkeys = events.map(e => e.pubkey)}
|
{@const pubkeys = events.map(e => e.pubkey)}
|
||||||
{@const isOwn = $pubkey && pubkeys.includes($pubkey)}
|
{@const isOwn = $pubkey && pubkeys.includes($pubkey)}
|
||||||
{@const info = displayList(pubkeys.map(pubkey => displayProfileByPubkey(pubkey)))}
|
{@const info = displayList(pubkeys.map(pubkey => displayProfileByPubkey(pubkey)))}
|
||||||
{@const tooltip = `${info} reacted ${displayReaction(content)}`}
|
{@const tooltip = `${info} reacted`}
|
||||||
{@const onClick = () => onReactionClick(content, events)}
|
{@const onClick = () => onReactionClick(events)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-tip={tooltip}
|
data-tip={tooltip}
|
||||||
@@ -101,7 +128,7 @@
|
|||||||
class:border-solid={isOwn}
|
class:border-solid={isOwn}
|
||||||
class:border-primary={isOwn}
|
class:border-primary={isOwn}
|
||||||
onclick={stopPropagation(preventDefault(onClick))}>
|
onclick={stopPropagation(preventDefault(onClick))}>
|
||||||
<span>{displayReaction(content)}</span>
|
<Reaction event={events[0]} />
|
||||||
{#if events.length > 1}
|
{#if events.length > 1}
|
||||||
<span>{events.length}</span>
|
<span>{events.length}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
>{displayUrl($relay.profile.contact)}</Link>
|
>{displayUrl($relay.profile.contact)}</Link>
|
||||||
•
|
•
|
||||||
{/if}
|
{/if}
|
||||||
{#if $relay?.profile?.supported_nips}
|
{#if Array.isArray($relay?.profile?.supported_nips)}
|
||||||
<span
|
<span
|
||||||
class="tooltip cursor-pointer underline"
|
class="tooltip cursor-pointer underline"
|
||||||
data-tip="NIPs supported: {$relay.profile.supported_nips.join(', ')}">
|
data-tip="NIPs supported: {$relay.profile.supported_nips.join(', ')}">
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {displayRelayUrl} from "@welshman/util"
|
import {displayRelayUrl} from "@welshman/util"
|
||||||
|
import {parse, renderAsHtml} from "@welshman/content"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Field from "@lib/components/Field.svelte"
|
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
|
import {ucFirst} from "@lib/util"
|
||||||
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/toast"
|
||||||
@@ -15,8 +16,8 @@
|
|||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const joinRelay = async (claim: string) => {
|
const joinRelay = async () => {
|
||||||
const error = await attemptRelayAccess(url, claim)
|
const error = await attemptRelayAccess(url)
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return pushToast({theme: "error", message: error})
|
return pushToast({theme: "error", message: error})
|
||||||
@@ -33,13 +34,12 @@
|
|||||||
loading = true
|
loading = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await joinRelay(claim)
|
await joinRelay()
|
||||||
} finally {
|
} finally {
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let claim = $state("")
|
|
||||||
let loading = $state(false)
|
let loading = $state(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -53,32 +53,17 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<p>
|
<p>
|
||||||
We received an error from the relay indicating you don't have access to {displayRelayUrl(url)}.
|
We received an error from the relay indicating you don't have access to {displayRelayUrl(url)}:
|
||||||
</p>
|
</p>
|
||||||
<p class="border-l border-solid border-error pl-4 text-error">
|
<p class="bg-alt card2 welshman-content">
|
||||||
{error}
|
{@html renderAsHtml(parse({content: ucFirst(error)}))}
|
||||||
</p>
|
</p>
|
||||||
<p>If you have one, you can try entering an invite code below to request access.</p>
|
|
||||||
<Field>
|
|
||||||
{#snippet label()}
|
|
||||||
<p>Invite code</p>
|
|
||||||
{/snippet}
|
|
||||||
{#snippet input()}
|
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
|
||||||
<Icon icon="link-round" />
|
|
||||||
<input bind:value={claim} class="grow" type="text" />
|
|
||||||
</label>
|
|
||||||
{/snippet}
|
|
||||||
{#snippet info()}
|
|
||||||
<p>Enter an invite code provided to you by the admin of the relay.</p>
|
|
||||||
{/snippet}
|
|
||||||
</Field>
|
|
||||||
<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 type="submit" class="btn btn-primary" disabled={!claim || loading}>
|
<Button type="submit" class="btn btn-primary" disabled={loading}>
|
||||||
<Spinner {loading}>Request Access</Spinner>
|
<Spinner {loading}>Request Access</Spinner>
|
||||||
<Icon icon="alt-arrow-right" />
|
<Icon icon="alt-arrow-right" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
const error = await attemptRelayAccess(url, claim)
|
const error = await attemptRelayAccess(url, claim)
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return pushToast({theme: "error", message: error})
|
return pushToast({theme: "error", message: error, timeout: 30_000})
|
||||||
}
|
}
|
||||||
|
|
||||||
const socket = Pool.get().get(url)
|
const socket = Pool.get().get(url)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {pubkey} from "@welshman/app"
|
|
||||||
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
import ReactionSummary from "@app/components/ReactionSummary.svelte"
|
||||||
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"
|
||||||
@@ -18,20 +17,15 @@
|
|||||||
|
|
||||||
const path = makeThreadPath(url, event.id)
|
const path = makeThreadPath(url, event.id)
|
||||||
|
|
||||||
const onReactionClick = (content: string, events: TrustedEvent[]) => {
|
const deleteReaction = (event: TrustedEvent) => publishDelete({relays: [url], event})
|
||||||
const reaction = events.find(e => e.pubkey === $pubkey)
|
|
||||||
|
|
||||||
if (reaction) {
|
const createReaction = (template: EventContent) =>
|
||||||
publishDelete({relays: [url], event: reaction})
|
publishReaction({...template, event, relays: [url]})
|
||||||
} else {
|
|
||||||
publishReaction({event, content, relays: [url]})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||||
<div class="flex flex-grow flex-wrap justify-end gap-2">
|
<div class="flex flex-grow flex-wrap justify-end gap-2">
|
||||||
<ReactionSummary {url} {event} {onReactionClick} reactionClass="tooltip-left" />
|
<ReactionSummary {url} {event} {deleteReaction} {createReaction} reactionClass="tooltip-left" />
|
||||||
<ThunkStatusOrDeleted {event} />
|
<ThunkStatusOrDeleted {event} />
|
||||||
{#if showActivity}
|
{#if showActivity}
|
||||||
<EventActivity {url} {path} {event} />
|
<EventActivity {url} {path} {event} />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {nth} from "@welshman/lib"
|
import {stopPropagation} from "svelte/legacy"
|
||||||
|
import {nth, noop} from "@welshman/lib"
|
||||||
import {PublishStatus} from "@welshman/net"
|
import {PublishStatus} from "@welshman/net"
|
||||||
import {
|
import {
|
||||||
MergedThunk,
|
MergedThunk,
|
||||||
@@ -60,9 +61,11 @@
|
|||||||
{@const url = failedUrls[0]}
|
{@const url = failedUrls[0]}
|
||||||
{@const status = $thunk.status[url]}
|
{@const status = $thunk.status[url]}
|
||||||
{@const message = $thunk.details[url]}
|
{@const message = $thunk.details[url]}
|
||||||
<div class="flex justify-end px-1 text-xs {restProps.class}">
|
<button
|
||||||
|
class="flex w-full justify-end px-1 text-xs {restProps.class}"
|
||||||
|
onclick={stopPropagation(noop)}>
|
||||||
<Tippy
|
<Tippy
|
||||||
class="flex items-center {restProps.class}"
|
class="flex items-center"
|
||||||
component={ThunkStatusDetail}
|
component={ThunkStatusDetail}
|
||||||
props={{url, message, status, retry}}
|
props={{url, message, status, retry}}
|
||||||
params={{interactive: true}}>
|
params={{interactive: true}}>
|
||||||
@@ -73,10 +76,10 @@
|
|||||||
</span>
|
</span>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Tippy>
|
</Tippy>
|
||||||
</div>
|
</button>
|
||||||
{:else if showPending}
|
{:else if showPending}
|
||||||
<div class="flex justify-end px-1 text-xs {restProps.class}">
|
<div class="flex w-full justify-end px-1 text-xs {restProps.class}">
|
||||||
<span class="flex items-center gap-1 {restProps.class}">
|
<span class="flex items-center gap-1">
|
||||||
<span class="loading loading-spinner mx-1 h-3 w-3 translate-y-px"></span>
|
<span class="loading loading-spinner mx-1 h-3 w-3 translate-y-px"></span>
|
||||||
<span class="opacity-50">Sending...</span>
|
<span class="opacity-50">Sending...</span>
|
||||||
<button
|
<button
|
||||||
@@ -84,7 +87,7 @@
|
|||||||
class="underline transition-all"
|
class="underline transition-all"
|
||||||
class:link={canCancel}
|
class:link={canCancel}
|
||||||
class:opacity-25={!canCancel}
|
class:opacity-25={!canCancel}
|
||||||
onclick={abort}>
|
onclick={stopPropagation(abort)}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import {parse, renderAsHtml} from "@welshman/content"
|
||||||
import {fly} from "@lib/transition"
|
import {fly} from "@lib/transition"
|
||||||
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"
|
||||||
@@ -7,7 +8,7 @@
|
|||||||
|
|
||||||
{#if $toast}
|
{#if $toast}
|
||||||
{@const theme = $toast.theme || "info"}
|
{@const theme = $toast.theme || "info"}
|
||||||
<div transition:fly class="toast z-toast">
|
<div transition:fly class="bottom-sai right-sai toast z-toast">
|
||||||
{#key $toast.id}
|
{#key $toast.id}
|
||||||
<div
|
<div
|
||||||
role="alert"
|
role="alert"
|
||||||
@@ -15,7 +16,9 @@
|
|||||||
class:bg-base-100={theme === "info"}
|
class:bg-base-100={theme === "info"}
|
||||||
class:text-base-content={theme === "info"}
|
class:text-base-content={theme === "info"}
|
||||||
class:alert-error={theme === "error"}>
|
class:alert-error={theme === "error"}>
|
||||||
{$toast.message}
|
<p class="welshman-content-error">
|
||||||
|
{@html renderAsHtml(parse({content: $toast.message}))}
|
||||||
|
</p>
|
||||||
<Button class="flex items-center opacity-75" onclick={() => popToast($toast.id)}>
|
<Button class="flex items-center opacity-75" onclick={() => popToast($toast.id)}>
|
||||||
<Icon icon="close-circle" />
|
<Icon icon="close-circle" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
+24
-19
@@ -11,26 +11,29 @@ import {makeMentionNodeView} from "./MentionNodeView"
|
|||||||
import ProfileSuggestion from "./ProfileSuggestion.svelte"
|
import ProfileSuggestion from "./ProfileSuggestion.svelte"
|
||||||
|
|
||||||
export const hasBlossomSupport = simpleCache(async ([url]: [string]) => {
|
export const hasBlossomSupport = simpleCache(async ([url]: [string]) => {
|
||||||
try {
|
const $signer = signer.get()
|
||||||
const event = await signer.get()!.sign(
|
const headers: Record<string, string> = {
|
||||||
makeEvent(BLOSSOM_AUTH, {
|
"X-Content-Type": "text/plain",
|
||||||
tags: [
|
"X-Content-Length": "1",
|
||||||
["t", "upload"],
|
"X-SHA-256": "73cb3858a687a8494ca3323053016282f3dad39d42cf62ca4e79dda2aac7d9ac",
|
||||||
["server", url],
|
}
|
||||||
["expiration", String(now() + 30)],
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
const res = await fetch(normalizeUrl(url) + "/upload", {
|
try {
|
||||||
method: "head",
|
if ($signer) {
|
||||||
headers: {
|
const event = await signer.get().sign(
|
||||||
Authorization: `Nostr ${btoa(JSON.stringify(event))}`,
|
makeEvent(BLOSSOM_AUTH, {
|
||||||
"X-Content-Type": "text/plain",
|
tags: [
|
||||||
"X-Content-Length": "1",
|
["t", "upload"],
|
||||||
"X-SHA-256": "73cb3858a687a8494ca3323053016282f3dad39d42cf62ca4e79dda2aac7d9ac",
|
["server", url],
|
||||||
},
|
["expiration", String(now() + 30)],
|
||||||
})
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
headers.Authorization = `Nostr ${btoa(JSON.stringify(event))}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(normalizeUrl(url) + "/upload", {method: "head", headers})
|
||||||
|
|
||||||
return res.status === 200
|
return res.status === 200
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -38,6 +41,8 @@ export const hasBlossomSupport = simpleCache(async ([url]: [string]) => {
|
|||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getUploadUrl = async (spaceUrl?: string) => {
|
export const getUploadUrl = async (spaceUrl?: string) => {
|
||||||
|
|||||||
+7
-6
@@ -79,10 +79,9 @@ export const ALERT = 32830
|
|||||||
|
|
||||||
export const ALERT_STATUS = 32831
|
export const ALERT_STATUS = 32831
|
||||||
|
|
||||||
export const NOTIFIER_PUBKEY = "27b7c2ed89ef78322114225ea3ebf5f72c7767c2528d4d0c1854d039c00085df"
|
export const NOTIFIER_PUBKEY = import.meta.env.VITE_NOTIFIER_PUBKEY
|
||||||
|
|
||||||
// export const NOTIFIER_RELAY = 'wss://notifier.flotilla.social/'
|
export const NOTIFIER_RELAY = import.meta.env.VITE_NOTIFIER_RELAY
|
||||||
export const NOTIFIER_RELAY = "ws://localhost:4738/"
|
|
||||||
|
|
||||||
export const INDEXER_RELAYS = fromCsv(import.meta.env.VITE_INDEXER_RELAYS)
|
export const INDEXER_RELAYS = fromCsv(import.meta.env.VITE_INDEXER_RELAYS)
|
||||||
|
|
||||||
@@ -297,6 +296,7 @@ export type Settings = {
|
|||||||
report_usage: boolean
|
report_usage: boolean
|
||||||
report_errors: boolean
|
report_errors: boolean
|
||||||
send_delay: number
|
send_delay: number
|
||||||
|
font_size: number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,6 +306,7 @@ export const defaultSettings = {
|
|||||||
report_usage: true,
|
report_usage: true,
|
||||||
report_errors: true,
|
report_errors: true,
|
||||||
send_delay: 3000,
|
send_delay: 3000,
|
||||||
|
font_size: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const settings = deriveEventsMapped<Settings>(repository, {
|
export const settings = deriveEventsMapped<Settings>(repository, {
|
||||||
@@ -485,7 +486,7 @@ export const messages = derived(
|
|||||||
export const groupMeta = deriveEvents(repository, {filters: [{kinds: [GROUP_META]}]})
|
export const groupMeta = deriveEvents(repository, {filters: [{kinds: [GROUP_META]}]})
|
||||||
|
|
||||||
export const hasNip29 = (relay?: Relay) =>
|
export const hasNip29 = (relay?: Relay) =>
|
||||||
relay?.profile?.supported_nips?.map(String)?.includes("29")
|
relay?.profile?.supported_nips?.map?.(String)?.includes?.("29")
|
||||||
|
|
||||||
// Channels
|
// Channels
|
||||||
|
|
||||||
@@ -629,11 +630,11 @@ export const userRoomsByUrl = withGetter(
|
|||||||
const $userRoomsByUrl = new Map<string, Set<string>>()
|
const $userRoomsByUrl = new Map<string, Set<string>>()
|
||||||
|
|
||||||
for (const [_, room, url] of getGroupTags(tags)) {
|
for (const [_, room, url] of getGroupTags(tags)) {
|
||||||
addToMapKey($userRoomsByUrl, url, room)
|
addToMapKey($userRoomsByUrl, normalizeRelayUrl(url), room)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const url of getRelayTagValues(tags)) {
|
for (const url of getRelayTagValues(tags)) {
|
||||||
addToMapKey($userRoomsByUrl, url, GENERAL)
|
addToMapKey($userRoomsByUrl, normalizeRelayUrl(url), GENERAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
return $userRoomsByUrl
|
return $userRoomsByUrl
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.99986 12H8.00887M12.0044 12H12.0134M15.9908 12H15.9999" stroke="#1C274C" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<circle cx="12" cy="12" r="10" stroke="#1C274C" stroke-width="1.5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 323 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M21.0672 11.8568L20.4253 11.469L21.0672 11.8568ZM12.1432 2.93276L11.7553 2.29085V2.29085L12.1432 2.93276ZM21.25 12C21.25 17.1086 17.1086 21.25 12 21.25V22.75C17.9371 22.75 22.75 17.9371 22.75 12H21.25ZM12 21.25C6.89137 21.25 2.75 17.1086 2.75 12H1.25C1.25 17.9371 6.06294 22.75 12 22.75V21.25ZM2.75 12C2.75 6.89137 6.89137 2.75 12 2.75V1.25C6.06294 1.25 1.25 6.06294 1.25 12H2.75ZM15.5 14.25C12.3244 14.25 9.75 11.6756 9.75 8.5H8.25C8.25 12.5041 11.4959 15.75 15.5 15.75V14.25ZM20.4253 11.469C19.4172 13.1373 17.5882 14.25 15.5 14.25V15.75C18.1349 15.75 20.4407 14.3439 21.7092 12.2447L20.4253 11.469ZM9.75 8.5C9.75 6.41182 10.8627 4.5828 12.531 3.57467L11.7553 2.29085C9.65609 3.5593 8.25 5.86509 8.25 8.5H9.75ZM12 2.75C11.9115 2.75 11.8077 2.71008 11.7324 2.63168C11.6686 2.56527 11.6538 2.50244 11.6503 2.47703C11.6461 2.44587 11.6482 2.35557 11.7553 2.29085L12.531 3.57467C13.0342 3.27065 13.196 2.71398 13.1368 2.27627C13.0754 1.82126 12.7166 1.25 12 1.25V2.75ZM21.7092 12.2447C21.6444 12.3518 21.5541 12.3539 21.523 12.3497C21.4976 12.3462 21.4347 12.3314 21.3683 12.2676C21.2899 12.1923 21.25 12.0885 21.25 12H22.75C22.75 11.2834 22.1787 10.9246 21.7237 10.8632C21.286 10.804 20.7293 10.9658 20.4253 11.469L21.7092 12.2447Z" fill="#1C274C"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -8,15 +8,13 @@
|
|||||||
const {...props}: Props = $props()
|
const {...props}: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="col-2 content-padding-t content-padding-x h-full {props.class}">
|
<div class="content-padding-t content-padding-x flex h-full flex-col gap-1 {props.class}">
|
||||||
<div class="z-feature">
|
<div class="z-feature">
|
||||||
<div class="content-sizing">
|
<div class="content-sizing">
|
||||||
{@render props.input?.()}
|
{@render props.input?.()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="scroll-container overflow-auto pt-2">
|
<div class="scroll-container content-sizing overflow-auto pt-2">
|
||||||
<div class="content-sizing">
|
{@render props.content?.()}
|
||||||
{@render props.content?.()}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type {Snippet} from "svelte"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children?: import("svelte").Snippet
|
children?: Snippet
|
||||||
}
|
}
|
||||||
|
|
||||||
const {children}: Props = $props()
|
const {children, ...props}: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center gap-2 p-2 text-xs uppercase opacity-50">
|
<div class="flex items-center gap-2 p-2 text-xs uppercase opacity-50">
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
onclick={onClose}>
|
onclick={onClose}>
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
class="scroll-container saiy sair absolute bottom-0 right-0 top-0 w-80 overflow-auto bg-base-200 text-base-content lg:w-96"
|
class="scroll-container py-sai pr-sair absolute bottom-0 right-0 top-0 w-80 overflow-auto bg-base-200 text-base-content lg:w-96"
|
||||||
transition:translate={{axis: "x", duration: 300}}>
|
transition:translate={{axis: "x", duration: 300}}>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,20 +3,28 @@
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label?: Snippet
|
label?: Snippet
|
||||||
|
secondary?: Snippet
|
||||||
input?: Snippet
|
input?: Snippet
|
||||||
info?: Snippet
|
info?: Snippet
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
||||||
const {label, input, info, ...props}: Props = $props()
|
const {label, secondary, input, info, ...props}: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 {props.class}">
|
<div class="flex flex-col gap-2 {props.class}">
|
||||||
{#if label}
|
<div class="flex items-center justify-between">
|
||||||
<label class="flex items-center gap-2 font-bold">
|
{#if label}
|
||||||
{@render label()}
|
<label class="flex items-center gap-2 font-bold">
|
||||||
</label>
|
{@render label()}
|
||||||
{/if}
|
</label>
|
||||||
|
{/if}
|
||||||
|
{#if secondary}
|
||||||
|
<label class="flex items-center gap-2">
|
||||||
|
{@render secondary()}
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{@render input?.()}
|
{@render input?.()}
|
||||||
{#if info}
|
{#if info}
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
|
|||||||
@@ -58,6 +58,8 @@
|
|||||||
import Mailbox from "@assets/icons/Mailbox.svg?dataurl"
|
import Mailbox from "@assets/icons/Mailbox.svg?dataurl"
|
||||||
import MapPoint from "@assets/icons/Map Point.svg?dataurl"
|
import MapPoint from "@assets/icons/Map Point.svg?dataurl"
|
||||||
import MenuDots from "@assets/icons/Menu Dots.svg?dataurl"
|
import MenuDots from "@assets/icons/Menu Dots.svg?dataurl"
|
||||||
|
import MenuDotsCircle from "@assets/icons/Menu Dots Circle.svg?dataurl"
|
||||||
|
import Moon from "@assets/icons/Moon.svg?dataurl"
|
||||||
import NotesMinimalistic from "@assets/icons/Notes Minimalistic.svg?dataurl"
|
import NotesMinimalistic from "@assets/icons/Notes Minimalistic.svg?dataurl"
|
||||||
import Pallete2 from "@assets/icons/Pallete 2.svg?dataurl"
|
import Pallete2 from "@assets/icons/Pallete 2.svg?dataurl"
|
||||||
import Paperclip from "@assets/icons/Paperclip.svg?dataurl"
|
import Paperclip from "@assets/icons/Paperclip.svg?dataurl"
|
||||||
@@ -149,6 +151,8 @@
|
|||||||
mailbox: Mailbox,
|
mailbox: Mailbox,
|
||||||
"map-point": MapPoint,
|
"map-point": MapPoint,
|
||||||
"menu-dots": MenuDots,
|
"menu-dots": MenuDots,
|
||||||
|
"menu-dots-circle": MenuDotsCircle,
|
||||||
|
moon: Moon,
|
||||||
"notes-minimalistic": NotesMinimalistic,
|
"notes-minimalistic": NotesMinimalistic,
|
||||||
"pallete-2": Pallete2,
|
"pallete-2": Pallete2,
|
||||||
paperclip: Paperclip,
|
paperclip: Paperclip,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="sait saib sair scroll-container mb-14 max-h-screen flex-grow overflow-auto bg-base-200 md:mb-0 {props.class}">
|
data-component="Page"
|
||||||
|
class="scroll-container bottom-sai top-sai cw fixed mb-14 overflow-auto bg-base-200 md:mb-0 {props.class}">
|
||||||
{@render props.children?.()}
|
{@render props.children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
const {...props}: Props = $props()
|
const {...props}: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="sait cw fixed top-2 z-feature rounded-xl px-2 pt-2 {props.class}">
|
<div data-component="PageBar" class="cw top-sai fixed z-feature p-2">
|
||||||
<div
|
<div
|
||||||
class="flex min-h-12 items-center justify-between gap-4 rounded-xl bg-base-100 px-4 shadow-xl">
|
class="flex min-h-12 items-center justify-between gap-4 rounded-xl rounded-xl bg-base-100 px-4 shadow-xl">
|
||||||
<div class="ellipsize flex items-center gap-4 whitespace-nowrap">
|
<div class="ellipsize flex items-center gap-4 whitespace-nowrap">
|
||||||
{@render props.icon?.()}
|
{@render props.icon?.()}
|
||||||
{@render props.title?.()}
|
{@render props.title?.()}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
<div
|
<div
|
||||||
{...props}
|
{...props}
|
||||||
bind:this={element}
|
bind:this={element}
|
||||||
class="scroll-container saib cw fixed top-12 h-[calc(100%-6.5rem)] overflow-y-auto overflow-x-hidden md:h-[calc(100%-3rem)] {props.class}">
|
data-component="PageContent"
|
||||||
|
class="scroll-container cw md:bottom-sai fixed bottom-[calc(var(--saib)+3.5rem)] top-[calc(var(--sait)+3rem)] overflow-y-auto overflow-x-hidden {props.class}">
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="sail sait saib hidden max-h-screen w-60 flex-shrink-0 flex-col gap-1 bg-base-300 md:flex">
|
class="ml-sai mt-sai mb-sai hidden max-h-screen w-60 flex-shrink-0 flex-col gap-1 bg-base-300 md:flex">
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,3 +15,5 @@ export const nsecDecode = (nsec: string) => {
|
|||||||
export const day = (seconds: number) => Math.floor(seconds / DAY)
|
export const day = (seconds: number) => Math.floor(seconds / DAY)
|
||||||
|
|
||||||
export const daysBetween = (start: number, end: number) => [...range(start, end, DAY)].map(day)
|
export const daysBetween = (start: number, end: number) => [...range(start, end, DAY)].map(day)
|
||||||
|
|
||||||
|
export const ucFirst = (s: string) => s.slice(0, 1).toUpperCase() + s.slice(1)
|
||||||
|
|||||||
@@ -51,9 +51,15 @@
|
|||||||
import {setupTracking} from "@app/tracking"
|
import {setupTracking} from "@app/tracking"
|
||||||
import {setupAnalytics} from "@app/analytics"
|
import {setupAnalytics} from "@app/analytics"
|
||||||
import {nsecDecode} from "@lib/util"
|
import {nsecDecode} from "@lib/util"
|
||||||
import {theme} from "@app/theme"
|
import {
|
||||||
import {INDEXER_RELAYS, userMembership, ensureUnwrapped, canDecrypt} from "@app/state"
|
INDEXER_RELAYS,
|
||||||
|
userMembership,
|
||||||
|
userSettingValues,
|
||||||
|
ensureUnwrapped,
|
||||||
|
canDecrypt,
|
||||||
|
} from "@app/state"
|
||||||
import {loadUserData, listenForNotifications} from "@app/requests"
|
import {loadUserData, listenForNotifications} from "@app/requests"
|
||||||
|
import {theme} from "@app/theme"
|
||||||
import * as commands from "@app/commands"
|
import * as commands from "@app/commands"
|
||||||
import * as requests from "@app/requests"
|
import * as requests from "@app/requests"
|
||||||
import * as notifications from "@app/notifications"
|
import * as notifications from "@app/notifications"
|
||||||
@@ -96,7 +102,7 @@
|
|||||||
if (login?.startsWith("bunker://")) {
|
if (login?.startsWith("bunker://")) {
|
||||||
const clientSecret = makeSecret()
|
const clientSecret = makeSecret()
|
||||||
const {signerPubkey, connectSecret, relays} = Nip46Broker.parseBunkerUrl(login)
|
const {signerPubkey, connectSecret, relays} = Nip46Broker.parseBunkerUrl(login)
|
||||||
const broker = Nip46Broker.get({relays, clientSecret, signerPubkey})
|
const broker = new Nip46Broker({relays, clientSecret, signerPubkey})
|
||||||
const result = await broker.connect(connectSecret, appState.NIP46_PERMS)
|
const result = await broker.connect(connectSecret, appState.NIP46_PERMS)
|
||||||
const pubkey = await broker.getPublicKey()
|
const pubkey = await broker.getPublicKey()
|
||||||
|
|
||||||
@@ -105,6 +111,7 @@
|
|||||||
await loadUserData(pubkey)
|
await loadUserData(pubkey)
|
||||||
|
|
||||||
loginWithNip46(pubkey, clientSecret, signerPubkey, relays)
|
loginWithNip46(pubkey, clientSecret, signerPubkey, relays)
|
||||||
|
broker.cleanup()
|
||||||
success = true
|
success = true
|
||||||
}
|
}
|
||||||
} else if (login) {
|
} else if (login) {
|
||||||
@@ -120,6 +127,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync theme
|
||||||
|
theme.subscribe($theme => {
|
||||||
|
document.body.setAttribute("data-theme", $theme)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Sync font size
|
||||||
|
userSettingValues.subscribe($userSettingValues => {
|
||||||
|
// @ts-ignore
|
||||||
|
document.documentElement.style["font-size"] = `${$userSettingValues.font_size}rem`
|
||||||
|
})
|
||||||
|
|
||||||
if (!db) {
|
if (!db) {
|
||||||
setupTracking()
|
setupTracking()
|
||||||
setupAnalytics()
|
setupAnalytics()
|
||||||
@@ -227,9 +245,9 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
{#await ready}
|
{#await ready}
|
||||||
<div data-theme={$theme}></div>
|
<div></div>
|
||||||
{:then}
|
{:then}
|
||||||
<div data-theme={$theme}>
|
<div>
|
||||||
<AppContainer>
|
<AppContainer>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</AppContainer>
|
</AppContainer>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
</CardButton>
|
</CardButton>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/people">
|
<Link href="/chat">
|
||||||
<CardButton>
|
<CardButton>
|
||||||
{#snippet icon()}
|
{#snippet icon()}
|
||||||
<div><Icon icon="chat-round" size={7} /></div>
|
<div><Icon icon="chat-round" size={7} /></div>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Page>
|
<Page class="cw-full">
|
||||||
<ContentSearch>
|
<ContentSearch>
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<label class="row-2 input input-bordered">
|
<label class="row-2 input input-bordered">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type {Snippet} from "svelte"
|
||||||
import {fly} from "@lib/transition"
|
import {fly} from "@lib/transition"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Page from "@lib/components/Page.svelte"
|
import Page from "@lib/components/Page.svelte"
|
||||||
@@ -7,13 +8,17 @@
|
|||||||
import SecondaryNavSection from "@lib/components/SecondaryNavSection.svelte"
|
import SecondaryNavSection from "@lib/components/SecondaryNavSection.svelte"
|
||||||
import LogOut from "@app/components/LogOut.svelte"
|
import LogOut from "@app/components/LogOut.svelte"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
interface Props {
|
import {theme} from "@app/theme"
|
||||||
children?: import("svelte").Snippet
|
|
||||||
|
type Props = {
|
||||||
|
children?: Snippet
|
||||||
}
|
}
|
||||||
|
|
||||||
const {children}: Props = $props()
|
const {children}: Props = $props()
|
||||||
|
|
||||||
const logout = () => pushModal(LogOut)
|
const logout = () => pushModal(LogOut)
|
||||||
|
|
||||||
|
const toggleTheme = () => theme.set($theme === "dark" ? "light" : "dark")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SecondaryNav>
|
<SecondaryNav>
|
||||||
@@ -34,11 +39,16 @@
|
|||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
</div>
|
</div>
|
||||||
<div in:fly|local={{delay: 150}}>
|
<div in:fly|local={{delay: 150}}>
|
||||||
|
<SecondaryNavItem onclick={toggleTheme}>
|
||||||
|
<Icon icon="moon" /> Theme
|
||||||
|
</SecondaryNavItem>
|
||||||
|
</div>
|
||||||
|
<div in:fly|local={{delay: 200}}>
|
||||||
<SecondaryNavItem href="/settings/about">
|
<SecondaryNavItem href="/settings/about">
|
||||||
<Icon icon="info-square" /> About
|
<Icon icon="info-square" /> About
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
</div>
|
</div>
|
||||||
<div in:fly|local={{delay: 200}}>
|
<div in:fly|local={{delay: 250}}>
|
||||||
<SecondaryNavItem class="text-error hover:text-error" onclick={logout}>
|
<SecondaryNavItem class="text-error hover:text-error" onclick={logout}>
|
||||||
<Icon icon="exit" /> Log Out
|
<Icon icon="exit" /> Log Out
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
|
|||||||
@@ -58,8 +58,6 @@
|
|||||||
let settings = $state({...$userSettingValues})
|
let settings = $state({...$userSettingValues})
|
||||||
let mutedPubkeys = $state(getPubkeyTagValues(getListTags($userMutes)))
|
let mutedPubkeys = $state(getPubkeyTagValues(getListTags($userMutes)))
|
||||||
let blossomServers = $state(getTagValues("server", getListTags($userBlossomServers)))
|
let blossomServers = $state(getTagValues("server", getListTags($userBlossomServers)))
|
||||||
|
|
||||||
$inspect(blossomServers)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="content column gap-4" {onsubmit}>
|
<form class="content column gap-4" {onsubmit}>
|
||||||
@@ -168,6 +166,24 @@
|
|||||||
<p>Choose a media server type and url for files you upload to {PLATFORM_NAME}.</p>
|
<p>Choose a media server type and url for files you upload to {PLATFORM_NAME}.</p>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Field>
|
</Field>
|
||||||
|
<strong class="text-lg">Accessibility</strong>
|
||||||
|
<Field>
|
||||||
|
{#snippet label()}
|
||||||
|
<p>Font size</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet secondary()}
|
||||||
|
<p>{Math.round(settings.font_size * 100)}%</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<input
|
||||||
|
class="range range-primary"
|
||||||
|
type="range"
|
||||||
|
min="0.8"
|
||||||
|
max="1.3"
|
||||||
|
step="0.05"
|
||||||
|
bind:value={settings.font_size} />
|
||||||
|
{/snippet}
|
||||||
|
</Field>
|
||||||
<div class="mt-4 flex flex-row items-center justify-between gap-4">
|
<div class="mt-4 flex flex-row items-center justify-between gap-4">
|
||||||
<Button class="btn btn-neutral" onclick={reset}>Discard Changes</Button>
|
<Button class="btn btn-neutral" onclick={reset}>Discard Changes</Button>
|
||||||
<Button type="submit" class="btn btn-primary">Save Changes</Button>
|
<Button type="submit" class="btn btn-primary">Save Changes</Button>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import {Capacitor} from "@capacitor/core"
|
||||||
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 Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
@@ -19,13 +20,15 @@
|
|||||||
<p class="text-center text-2xl">Thanks for using</p>
|
<p class="text-center text-2xl">Thanks for using</p>
|
||||||
<h1 class="mb-4 text-center text-5xl font-bold uppercase">{PLATFORM_NAME}</h1>
|
<h1 class="mb-4 text-center text-5xl font-bold uppercase">{PLATFORM_NAME}</h1>
|
||||||
<div class="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
<div class="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
||||||
<div class="card2 bg-alt flex flex-col gap-2 text-center shadow-2xl">
|
{#if Capacitor.getPlatform() !== "ios"}
|
||||||
<h3 class="text-2xl sm:h-12">Donate</h3>
|
<div class="card2 bg-alt flex flex-col gap-2 text-center shadow-2xl">
|
||||||
<p class="sm:h-16">Funds will be used to support development.</p>
|
<h3 class="text-2xl sm:h-12">Donate</h3>
|
||||||
<Link external href="https://geyser.fund/project/flotilla" class="btn btn-primary">
|
<p class="sm:h-16">Funds will be used to support development.</p>
|
||||||
Support the Developer
|
<Link external href="https://geyser.fund/project/flotilla" class="btn btn-primary">
|
||||||
</Link>
|
Support the Developer
|
||||||
</div>
|
</Link>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="card2 bg-alt flex flex-col gap-2 text-center shadow-2xl">
|
<div class="card2 bg-alt flex flex-col gap-2 text-center shadow-2xl">
|
||||||
<h3 class="text-2xl sm:h-12">Get in touch</h3>
|
<h3 class="text-2xl sm:h-12">Get in touch</h3>
|
||||||
<p class="sm:h-16">Having problems? Let us know.</p>
|
<p class="sm:h-16">Having problems? Let us know.</p>
|
||||||
|
|||||||
@@ -112,7 +112,7 @@
|
|||||||
<span class="ellipsize">Requires PoW {limitation?.min_pow_difficulty}</span>
|
<span class="ellipsize">Requires PoW {limitation?.min_pow_difficulty}</span>
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
{#if supported_nips}
|
{#if Array.isArray(supported_nips)}
|
||||||
<p class="badge badge-neutral">
|
<p class="badge badge-neutral">
|
||||||
<span class="ellipsize">NIPs: {supported_nips.join(", ")}</span>
|
<span class="ellipsize">NIPs: {supported_nips.join(", ")}</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -189,8 +189,8 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{#if pubkey}
|
{#if pubkey}
|
||||||
|
<Divider>Recent posts from the relay admin</Divider>
|
||||||
<div class="hidden flex-col gap-2" class:!flex={relayAdminEvents.length > 0}>
|
<div class="hidden flex-col gap-2" class:!flex={relayAdminEvents.length > 0}>
|
||||||
<Divider>Recent posts from the relay admin</Divider>
|
|
||||||
<ProfileFeed hideLoading {url} {pubkey} bind:events={relayAdminEvents} />
|
<ProfileFeed hideLoading {url} {pubkey} bind:events={relayAdminEvents} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
|
|
||||||
const {room = GENERAL} = $page.params
|
const {room = GENERAL} = $page.params
|
||||||
|
const mounted = now()
|
||||||
const lastChecked = $checked[$page.url.pathname]
|
const lastChecked = $checked[$page.url.pathname]
|
||||||
const url = decodeRelay($page.params.relay)
|
const url = decodeRelay($page.params.relay)
|
||||||
const filter = {kinds: [MESSAGE], "#h": [room]}
|
const filter = {kinds: [MESSAGE], "#h": [room]}
|
||||||
@@ -170,7 +171,8 @@
|
|||||||
!newMessagesSeen &&
|
!newMessagesSeen &&
|
||||||
adjustedLastChecked &&
|
adjustedLastChecked &&
|
||||||
event.pubkey !== $pubkey &&
|
event.pubkey !== $pubkey &&
|
||||||
event.created_at > adjustedLastChecked
|
event.created_at > adjustedLastChecked &&
|
||||||
|
event.created_at < mounted
|
||||||
) {
|
) {
|
||||||
elements.push({type: "new-messages", id: "new-messages"})
|
elements.push({type: "new-messages", id: "new-messages"})
|
||||||
newMessagesSeen = true
|
newMessagesSeen = true
|
||||||
@@ -213,13 +215,17 @@
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const observer = new ResizeObserver(() => {
|
const observer = new ResizeObserver(() => {
|
||||||
dynamicPadding!.style.minHeight = `${chatCompose!.offsetHeight}px`
|
if (dynamicPadding && chatCompose) {
|
||||||
|
dynamicPadding!.style.minHeight = `${chatCompose!.offsetHeight}px`
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
observer.observe(chatCompose!)
|
observer.observe(chatCompose!)
|
||||||
|
observer.observe(dynamicPadding!)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
observer.unobserve(chatCompose!)
|
observer.unobserve(chatCompose!)
|
||||||
|
observer.unobserve(dynamicPadding!)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+10
-1
@@ -2,8 +2,8 @@ import {config} from "dotenv"
|
|||||||
import daisyui from "daisyui"
|
import daisyui from "daisyui"
|
||||||
import themes from "daisyui/src/theming/themes"
|
import themes from "daisyui/src/theming/themes"
|
||||||
|
|
||||||
config({path: ".env.local"})
|
|
||||||
config({path: ".env"})
|
config({path: ".env"})
|
||||||
|
config({path: ".env.template"})
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
@@ -30,6 +30,15 @@ export default {
|
|||||||
...themes["dark"],
|
...themes["dark"],
|
||||||
primary: process.env.VITE_PLATFORM_ACCENT,
|
primary: process.env.VITE_PLATFORM_ACCENT,
|
||||||
"primary-content": "#EAE7FF",
|
"primary-content": "#EAE7FF",
|
||||||
|
secondary: process.env.VITE_PLATFORM_SECONDARY,
|
||||||
|
"secondary-content": "#EAE7FF",
|
||||||
|
},
|
||||||
|
light: {
|
||||||
|
...themes["winter"],
|
||||||
|
primary: process.env.VITE_PLATFORM_ACCENT,
|
||||||
|
"primary-content": "#EAE7FF",
|
||||||
|
secondary: process.env.VITE_PLATFORM_SECONDARY,
|
||||||
|
"secondary-content": "#EAE7FF",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
+1
-1
@@ -4,8 +4,8 @@ import {SvelteKitPWA} from "@vite-pwa/sveltekit"
|
|||||||
import {sveltekit} from "@sveltejs/kit/vite"
|
import {sveltekit} from "@sveltejs/kit/vite"
|
||||||
import svg from "@poppanator/sveltekit-svg"
|
import svg from "@poppanator/sveltekit-svg"
|
||||||
|
|
||||||
config({path: ".env.local"})
|
|
||||||
config({path: ".env"})
|
config({path: ".env"})
|
||||||
|
config({path: ".env.template"})
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
server: {
|
||||||
|
|||||||
Reference in New Issue
Block a user