Compare commits

..

1 Commits
main ... main

Author SHA1 Message Date
a4eecd9fe2 use correct ecash symbol 2023-10-10 14:22:05 +00:00
880 changed files with 33525 additions and 68307 deletions

View File

@ -1,10 +0,0 @@
**/node_modules
**/.pnp.*
**/.yarn/*
!**/.yarn/patches
!**/.yarn/plugins
!**/.yarn/releases
!**/.yarn/sdks
!**/.yarn/versions
**/.idea
**/target

View File

@ -12,24 +12,18 @@ trigger:
metadata: metadata:
namespace: git namespace: git
steps: steps:
- name: Fetch tags
image: alpine/git
commands:
- git fetch --tags
- name: Build site - name: Build site
image: node:current image: node:current-bullseye
volumes: volumes:
- name: cache - name: cache
path: /cache path: /cache
environment: environment:
YARN_CACHE_FOLDER: /cache/.yarn-docker YARN_CACHE_FOLDER: /cache/.yarn-docker
NODE_CONFIG_ENV: default
commands: commands:
- apt update && apt install -y git
- yarn install - yarn install
- yarn build - yarn build
- name: build docker image - name: build docker image
image: docker image: r.j3ss.co/img
privileged: true privileged: true
volumes: volumes:
- name: cache - name: cache
@ -38,11 +32,9 @@ steps:
TOKEN: TOKEN:
from_secret: docker_hub from_secret: docker_hub
commands: commands:
- dockerd & - img login -u voidic -p $TOKEN
- docker login -u voidic -p $TOKEN - img build -t voidic/snort:latest --platform linux/amd64,linux/arm64 -f Dockerfile.prebuilt .
- docker buildx create --name mybuilder --bootstrap --use - img push voidic/snort:latest
- docker buildx build -t voidic/snort:latest --platform linux/amd64,linux/arm64 --push -f Dockerfile.prebuilt .
- kill $(cat /var/run/docker.pid)
volumes: volumes:
- name: cache - name: cache
claim: claim:
@ -57,13 +49,12 @@ metadata:
namespace: git namespace: git
steps: steps:
- name: Test/Lint - name: Test/Lint
image: node:current image: node:current-bullseye
volumes: volumes:
- name: cache - name: cache
path: /cache path: /cache
environment: environment:
YARN_CACHE_FOLDER: /cache/.yarn-test YARN_CACHE_FOLDER: /cache/.yarn-test
NODE_CONFIG_ENV: default
commands: commands:
- yarn install - yarn install
- yarn build - yarn build
@ -89,13 +80,12 @@ metadata:
namespace: git namespace: git
steps: steps:
- name: Push/Pull translations - name: Push/Pull translations
image: node:current image: node:current-bullseye
volumes: volumes:
- name: cache - name: cache
path: /cache path: /cache
environment: environment:
YARN_CACHE_FOLDER: /cache/.yarn-translations YARN_CACHE_FOLDER: /cache/.yarn-translations
NODE_CONFIG_ENV: default
TOKEN: TOKEN:
from_secret: gitea from_secret: gitea
CTOKEN: CTOKEN:
@ -130,24 +120,18 @@ trigger:
metadata: metadata:
namespace: git namespace: git
steps: steps:
- name: Fetch tags
image: alpine/git
commands:
- git fetch --tags
- name: Build site - name: Build site
image: node:current image: node:current-bullseye
volumes: volumes:
- name: cache - name: cache
path: /cache path: /cache
environment: environment:
YARN_CACHE_FOLDER: /cache/.yarn-docker- YARN_CACHE_FOLDER: /cache/.yarn-docker-release
NODE_CONFIG_ENV: default
commands: commands:
- apt update && apt install -y git
- yarn install - yarn install
- yarn build - yarn build
- name: build docker image - name: build docker image
image: docker image: r.j3ss.co/img
privileged: true privileged: true
volumes: volumes:
- name: cache - name: cache
@ -156,11 +140,9 @@ steps:
TOKEN: TOKEN:
from_secret: docker_hub from_secret: docker_hub
commands: commands:
- dockerd & - img login -u voidic -p $TOKEN
- docker login -u voidic -p $TOKEN - img build -t voidic/snort:$DRONE_TAG --platform linux/amd64,linux/arm64 -f Dockerfile.prebuilt .
- docker buildx create --name mybuilder --bootstrap --use - img push voidic/snort:$DRONE_TAG
- docker buildx build -t voidic/snort:$DRONE_TAG --platform linux/amd64,linux/arm64 --push -f Dockerfile.prebuilt .
- kill $(cat /var/run/docker.pid)
volumes: volumes:
- name: cache - name: cache
claim: claim:

View File

@ -8,6 +8,42 @@ env:
DOCKER_CLI_EXPERIMENTAL: enabled DOCKER_CLI_EXPERIMENTAL: enabled
TAG_FMT: "^refs/tags/(((.?[0-9]+){3,4}))$" TAG_FMT: "^refs/tags/(((.?[0-9]+){3,4}))$"
jobs: jobs:
tauri_release:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
platform: [macos-latest, ubuntu-20.04, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-20.04'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev
- name: Rust setup
uses: dtolnay/rust-toolchain@stable
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: "./src-tauri -> target"
- name: Sync node version and setup cache
uses: actions/setup-node@v3
with:
node-version: "16"
cache: "yarn"
- name: Install frontend dependencies
run: yarn install
- name: Build the app
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tagName: ${{ github.ref_name }}
app: app:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
@ -37,9 +73,9 @@ jobs:
- name: Copy files - name: Copy files
run: |- run: |-
git clone --depth 1 --branch ${{ github.ref_name }} https://git.v0l.io/Kieran/snort_android.git git clone https://git.v0l.io/Kieran/snort_android.git
mkdir -p snort_android/app/src/main/assets/ mkdir -p snort_android/app/src/main/assets/
cp -r packages/app/build/* snort_android/app/src/main/assets/ cp packages/app/build/* snort_android/app/src/main/assets/
- name: Build AAB - name: Build AAB
working-directory: snort_android working-directory: snort_android
@ -56,8 +92,7 @@ jobs:
alias: ${{ secrets.KEY_ALIAS }} alias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }} keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }} keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
- name: Sign APK - name: Sign APK
uses: r0adkll/sign-android-release@v1 uses: r0adkll/sign-android-release@v1
with: with:
@ -66,8 +101,6 @@ jobs:
alias: ${{ secrets.KEY_ALIAS }} alias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }} keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }} keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: "33.0.0"
- name: Rename files - name: Rename files
run: |- run: |-
mkdir -p snort_android/app/release mkdir -p snort_android/app/release

4
.gitignore vendored
View File

@ -11,6 +11,4 @@ dist/
*.tgz *.tgz
*.log *.log
.DS_Store .DS_Store
.pnp* .pnp*
docs/
.wrangler/

View File

@ -4,5 +4,4 @@ build/
.github/ .github/
transifex.yml transifex.yml
dist/ dist/
src-tauri/ src-tauri/
target/

View File

@ -15,5 +15,5 @@
"typescript.tsdk": ".yarn/sdks/typescript/lib", "typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true, "typescript.enablePromptUseWorkspaceTsdk": true,
"eslint.nodePath": ".yarn/sdks", "eslint.nodePath": ".yarn/sdks",
"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs" "prettier.prettierPath": ".yarn/sdks/prettier/index.js"
} }

874
.yarn/releases/yarn-3.6.3.cjs vendored Executable file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,20 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/use-at-your-own-risk
require(absPnpApiPath).setup();
}
}
// Defer to the real eslint/use-at-your-own-risk your application uses
module.exports = absRequire(`eslint/use-at-your-own-risk`);

View File

@ -1,14 +1,6 @@
{ {
"name": "eslint", "name": "eslint",
"version": "8.53.0-sdk", "version": "8.48.0-sdk",
"main": "./lib/api.js", "main": "./lib/api.js",
"type": "commonjs", "type": "commonjs"
"bin": {
"eslint": "./bin/eslint.js"
},
"exports": {
"./package.json": "./package.json",
".": "./lib/api.js",
"./use-at-your-own-risk": "./lib/unsupported-api.js"
}
} }

View File

@ -1,20 +0,0 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {resolve} = require(`path`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absRequire = createRequire(absPnpApiPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require prettier/bin/prettier.cjs
require(absPnpApiPath).setup();
}
}
// Defer to the real prettier/bin/prettier.cjs your application uses
module.exports = absRequire(`prettier/bin/prettier.cjs`);

0
.yarn/sdks/prettier/index.cjs → .yarn/sdks/prettier/index.js vendored Normal file → Executable file
View File

View File

@ -1,7 +1,6 @@
{ {
"name": "prettier", "name": "prettier",
"version": "3.1.0-sdk", "version": "3.0.3-sdk",
"main": "./index.cjs", "main": "./index.js",
"type": "commonjs", "type": "commonjs"
"bin": "./bin/prettier.cjs"
} }

View File

@ -11,10 +11,10 @@ const absRequire = createRequire(absPnpApiPath);
if (existsSync(absPnpApiPath)) { if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) { if (!process.versions.pnp) {
// Setup the environment to be able to require typescript // Setup the environment to be able to require typescript/lib/typescript.js
require(absPnpApiPath).setup(); require(absPnpApiPath).setup();
} }
} }
// Defer to the real typescript your application uses // Defer to the real typescript/lib/typescript.js your application uses
module.exports = absRequire(`typescript`); module.exports = absRequire(`typescript/lib/typescript.js`);

View File

@ -2,9 +2,5 @@
"name": "typescript", "name": "typescript",
"version": "5.2.2-sdk", "version": "5.2.2-sdk",
"main": "./lib/typescript.js", "main": "./lib/typescript.js",
"type": "commonjs", "type": "commonjs"
"bin": {
"tsc": "./bin/tsc",
"tsserver": "./bin/tsserver"
}
} }

View File

@ -1,9 +1 @@
compressionLevel: mixed yarnPath: .yarn/releases/yarn-3.6.3.cjs
enableGlobalCache: false
npmScopes:
here:
npmRegistryServer: "https://repo.platform.here.com/artifactory/api/npm/maps-api-for-javascript/"
yarnPath: .yarn/releases/yarn-4.1.1.cjs

View File

@ -1,12 +1,12 @@
FROM node:current as build FROM node:19 as build
WORKDIR /src WORKDIR /app
RUN apt update \
&& apt install -y --no-install-recommends git \ COPY package.json yarn.lock .yarnrc.yml .
&& git clone --single-branch -b main https://git.v0l.io/Kieran/snort \ COPY .yarn .yarn
&& cd snort \ COPY packages packages
&& yarn --network-timeout 1000000 \ RUN yarn --network-timeout 1000000
&& yarn build RUN yarn build
FROM nginxinc/nginx-unprivileged:mainline-alpine FROM nginxinc/nginx-unprivileged:mainline-alpine
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /src/snort/packages/app/build /usr/share/nginx/html COPY --from=build /app/packages/app/build /usr/share/nginx/html

View File

@ -24,7 +24,7 @@ Snort supports the following NIP's:
- [x] NIP-25: Reactions - [x] NIP-25: Reactions
- [x] NIP-26: Delegated Event Signing (Display delegated signings only) - [x] NIP-26: Delegated Event Signing (Display delegated signings only)
- [x] NIP-27: Text note references - [x] NIP-27: Text note references
- [x] NIP-28: Public Chat - [ ] NIP-28: Public Chat
- [x] NIP-30: Custom Emoji - [x] NIP-30: Custom Emoji
- [x] NIP-31: Alt tag for unknown events - [x] NIP-31: Alt tag for unknown events
- [x] NIP-36: Sensitive Content - [x] NIP-36: Sensitive Content
@ -43,10 +43,9 @@ Snort supports the following NIP's:
- [x] NIP-59: Gift Wrap - [x] NIP-59: Gift Wrap
- [x] NIP-65: Relay List Metadata - [x] NIP-65: Relay List Metadata
- [x] NIP-75: Zap Goals - [x] NIP-75: Zap Goals
- [x] NIP-78: App specific data - [ ] NIP-78: App specific data
- [x] NIP-89: App handlers - [ ] NIP-89: App handlers
- [x] NIP-94: File Metadata - [x] NIP-94: File Metadata
- [x] NIP-96: HTTP File Storage Integration (Draft)
- [x] NIP-98: HTTP Auth - [x] NIP-98: HTTP Auth
### Running ### Running
@ -59,25 +58,12 @@ To run the application, use
$ yarn start $ yarn start
``` ```
To build the application and system packages, use To build the application and nostr package, use
``` ```
$ yarn build $ yarn build
``` ```
Tauri desktop application:
```
# install dependencies
yarn
# develop
yarn tauri dev
# build
yarn tauri build
```
### Translations ### Translations
[![Crowdin](https://badges.crowdin.net/snort/localized.svg)](https://crowdin.com/project/snort) [![Crowdin](https://badges.crowdin.net/snort/localized.svg)](https://crowdin.com/project/snort)
@ -87,7 +73,8 @@ Translations are managed on [Crowdin](https://crowdin.com/project/snort)
To extract translations run: To extract translations run:
```bash ```bash
yarn pre:commit yarn workspace @snort/app intl-extract
yarn workspace @snort/app intl-compile
``` ```
This will create the source file `packages/app/src/translations/en.json` This will create the source file `packages/app/src/translations/en.json`

View File

@ -1,30 +0,0 @@
# Reactions
## Problem
When presented with a feed of notes, either a timeline (social) or a live chat log (live stream chat)
how do you fetch the reactions to such notes and maintain realtime updates.
## Current solution
When a list of reactions is requested we use the expensive `buildDiff` operation to compute a
list of new (added) filters and send them to relays.
Usually if `leaveOpen` is specified (as it should be for realtime updates) this new trace will be sent
as a separate subscription causing exhasution.
Another side effect of this this approach is that over time (especially in live chat) the number of filters that get passed to `buildDiff` increases and so the computation time takes longer and causes jank (https://git.v0l.io/Kieran/zap.stream/issues/126).
There is also the question of updating the "root" query, since this is not updated, each independant query trace receives its own set of updates which is a problem of its own.
## Proposed solution (Live chat)
The ideal solution is to update only the "root" query as new filters are detected along with appending the current timestamp as the `since` value.
In this way only 1 subscription is maintained, the "root" query trace.
Each time a new set of filters is created from `buildDiff` we push the same `REQ` again with the new filters which **should** result in no new results from the relays as we expect there to be none `since` the current time is the time of the latest message.
## Proposed solution (Timeline)
TBD

View File

@ -1,11 +1,8 @@
server { server {
listen 8080 default_server; listen 80 default_server;
server_name _; server_name _;
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html; index index.html;
add_header Content-Security-Policy "default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src youtube.com www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com https://open.spotify.com https://player.twitch.tv https://embed.music.apple.com https://embed.wavlake.com https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; connect-src *; img-src * data: blob:; font-src 'self'; media-src * blob:; script-src 'self' 'wasm-unsafe-eval' https://platform.twitter.com https://embed.tidal.com https://challenges.cloudflare.com";
add_header Cross-Origin-Opener-Policy same-origin;
add_header Cross-Origin-Embedder-Policy require-corp;
location / { location / {
try_files $uri $uri/ /index.html =404; try_files $uri $uri/ /index.html =404;

View File

@ -1,50 +0,0 @@
interface Env {}
const HOST = "snort.social";
export const onRequest: PagesFunction<Env> = async context => {
const u = new URL(context.request.url);
const prefixes = ["npub1", "nprofile1", "naddr1", "nevent1", "note1"];
const isEntityPath = prefixes.some(
a => u.pathname.startsWith(`/${a}`) || u.pathname.startsWith(`/e/${a}`) || u.pathname.startsWith(`/p/${a}`),
);
const nostrAddress = u.pathname.match(/^\/([a-zA-Z0-9_]+)$/i);
const next = await context.next();
if (u.pathname != "/" && (isEntityPath || nostrAddress)) {
//console.log("Handeling path: ", u.pathname, isEntityPath, nostrAddress[1]);
try {
let id = u.pathname.split("/").at(-1);
if (!isEntityPath && nostrAddress) {
id = `${id}@${HOST}`;
}
const fetchApi = `https://nostr.api.v0l.io/api/v1/opengraph/${id}?canonical=${encodeURIComponent(
`https://${HOST}/%s`,
)}`;
console.log("Fetching tags from: ", fetchApi);
const rsp = await fetch(fetchApi, {
method: "POST",
body: await next.arrayBuffer(),
headers: {
"user-agent": `SnortFunctions/1.0 (https://${HOST})`,
"content-type": "text/html",
accept: "text/html",
},
});
if (rsp.ok) {
const body = await rsp.text();
if (body.length > 0) {
return new Response(body, {
headers: {
...Object.fromEntries(rsp.headers.entries()),
"cache-control": "no-cache",
},
});
}
}
} catch (e) {
console.error(e);
}
}
return next;
};

30
functions/e/[id].ts Normal file
View File

@ -0,0 +1,30 @@
interface Env {}
export const onRequest: PagesFunction<Env> = async context => {
const id = context.params.id as string;
const next = await context.next();
try {
const rsp = await fetch(`https://api.snort.social/api/v1/og/tag/e/${id}`, {
method: "POST",
body: await next.arrayBuffer(),
headers: {
"user-agent": "Snort-Functions/1.0 (https://snort.social)",
"content-type": "text/plain",
},
});
if (rsp.ok) {
const body = await rsp.text();
if (body.length > 0) {
return new Response(body, {
headers: {
"content-type": "text/html",
},
});
}
}
} catch {
// ignore
}
return next;
};

30
functions/p/[id].ts Normal file
View File

@ -0,0 +1,30 @@
interface Env {}
export const onRequest: PagesFunction<Env> = async context => {
const id = context.params.id as string;
const next = await context.next();
try {
const rsp = await fetch(`https://api.snort.social/api/v1/og/tag/p/${id}`, {
method: "POST",
body: await next.arrayBuffer(),
headers: {
"user-agent": "Snort-Functions/1.0 (https://snort.social)",
"content-type": "text/plain",
},
});
if (rsp.ok) {
const body = await rsp.text();
if (body.length > 0) {
return new Response(body, {
headers: {
"content-type": "text/html",
},
});
}
}
} catch {
// ignore
}
return next;
};

View File

@ -1,8 +0,0 @@
maintainers:
- npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
relays:
- wss://relay.snort.social/
- wss://pyramid.fiatjaf.com/
- wss://nos.lol/
- ws://skzzn6cimfdv5e2phjc4yr5v7ikbxtn5f7dkwn5c7v47tduzlbosqmqd.onion/

View File

@ -4,29 +4,22 @@
"packages/*" "packages/*"
], ],
"scripts": { "scripts": {
"build": "yarn workspace @snort/shared build && yarn workspace @snort/worker-relay build && yarn workspace @snort/system build && yarn workspace @snort/system-web build && yarn workspace @snort/system-react build && yarn workspace @snort/wallet build && yarn workspace @snort/app build", "build": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-web build && yarn workspace @snort/system-react build && yarn workspace @snort/app build",
"start": "yarn build && yarn workspace @snort/app start", "start": "yarn build && yarn workspace @snort/app start",
"test": "yarn build && yarn workspace @snort/app test && yarn workspace @snort/system test", "test": "yarn build && yarn workspace @snort/app test && yarn workspace @snort/system test",
"pre:commit": "yarn workspace @snort/app intl-extract && yarn workspace @snort/app intl-compile && yarn prettier --write .", "pre:commit": "yarn workspace @snort/app intl-extract && yarn workspace @snort/app intl-compile && yarn prettier --write ."
"push-prod": "git switch snort-prod && git merge --ff-only main && git push && git checkout main",
"docs": "typedoc --entryPointStrategy packages ./packages/* --exclude ./packages/app --exclude ./packages/webrtc-server --name snort.social"
}, },
"prettier": { "prettier": {
"printWidth": 120, "printWidth": 120,
"bracketSameLine": true, "bracketSameLine": true,
"arrowParens": "avoid", "arrowParens": "avoid"
"trailingComma": "all",
"endOfLine": "lf"
}, },
"packageManager": "yarn@4.1.1", "packageManager": "yarn@3.6.3",
"dependencies": { "dependencies": {
"@cloudflare/workers-types": "^4.20230307.0", "@cloudflare/workers-types": "^4.20230307.0",
"@tauri-apps/cli": "^1.2.3",
"eslint": "^8.48.0", "eslint": "^8.48.0",
"prettier": "^3.0.3", "prettier": "^3.0.3",
"typescript": "^5.2.2" "typescript": "^5.2.2"
},
"devDependencies": {
"@tauri-apps/cli": "^2.0.0-rc.14",
"typedoc": "^0.25.7"
} }
} }

View File

@ -0,0 +1,13 @@
module.exports = {
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
root: true,
ignorePatterns: ["build/", "*.test.ts", "*.js"],
env: {
browser: true,
worker: true,
commonjs: true,
node: false,
},
};

View File

@ -1,31 +0,0 @@
/* eslint-disable import/no-anonymous-default-export */
module.exports = {
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react/recommended"],
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint", "formatjs", "react-refresh", "simple-import-sort"],
rules: {
"react/react-in-jsx-scope": "off",
"react-hooks/exhaustive-deps": "off",
"react-refresh/only-export-components": "error",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"@typescript-eslint/no-unused-vars": "error",
"max-lines": ["warn", { max: 300, skipBlankLines: true, skipComments: true }],
},
overrides: [
{
files: ["*.tsx"],
rules: {
"max-lines": ["warn", { max: 200, skipBlankLines: true, skipComments: true }],
},
},
],
root: true,
ignorePatterns: ["build/", "*.test.ts", "*.js"],
env: {
browser: true,
worker: true,
commonjs: true,
node: false,
},
};

View File

@ -25,5 +25,3 @@ yarn-error.log*
.idea .idea
dist/ dist/
dev-dist/
.wrangler/

View File

@ -1,840 +0,0 @@
# v0.2.0
`+16,990,-9,649`
## Added
- Check notification settings page
- New settings page layout - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Community Leaders / Invite system - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Settings->Tools pages (Check follows relay health etc) - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- New wallet pages design - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Alby OAuth wallet connection - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Cashu wallet support (WIP) - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Followed by friends feed page - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Fuzzysearch profiles everywhere - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Worker Relay package `@snort/worker-relay` - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Replaces all previous caching objects, all caches are handled inside `@snort/system` via worker relay
- "View as user" button - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Play live streams directly in feed with embed iframe - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Negentropy v1 support - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
## Changed
- Hidden note styles & preferences - nostr:npub1cz2ve34nk0ukn0ph4yq2qx3ud8rfy5e0ak4epx42dn8gha0sdgpsgra9kv
- Keybinds for grid modal navigation - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Cache trending sections in browser - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Cache images / nostr.json in service worker - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Add dimensions to `imeta` tag for void.cat uploads - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Check event sigs in `@snort/system-wasm` - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Primary color scheme - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Note creator styles (removed hashtags input) - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Cache link preview results in memory - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Show only 1 task at a time in task list - nostr:
- Render media in reply to note creator - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Show top zappers inline with footer icons on notes - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Add more search relays - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Moved link previews and opengraph tagging to https://nostr.api.v0l.io - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
## Fixed
- Iris account error mesage - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Light theme color fixes - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Notifications page overflow - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
# v0.1.24
`+11,573,-3,010`
## Added
- 3 Column layout - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Fuzzy cache search - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Followed by on profile pages - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Show more on long notes - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Better error message page - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Media grid feed - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Mobile fixed footer - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Follow button on profile search results - nostr:npub17q5n2z8naw0xl6vu9lvt560lg33pdpe29k0k09umlfxm3vc4tqrq466f2y
- Invite codes (WIP Community Program) - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- `imeta` tag insertion for images - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Wallet settings page improvements - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Nostr Wallet Connect upgrade (balance + history) - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Schnorr sig check in WASM binary - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Autoplay videos in feed (muted) - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Followed by friends feed (a feed of your 2nd degree follows posts) - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- imgproxy image integrity check (sha256 from `imeta` passed to imgproxy) - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
## Changed
- Removed Twitter embed - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Removed attachment button on DM's - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Note broadcaster dialog changed to toast notification - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Removed npub link from profile (use QR button) - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Render image size from `imeta` tags - nostr:npub1v0lxxxxutpvrelsksy8cdhgfux9l6a42hsj2qzquu2zk7vc9qnkszrqj49
- Style fixes - nostr:npub1cz2ve34nk0ukn0ph4yq2qx3ud8rfy5e0ak4epx42dn8gha0sdgpsgra9kv
- Zap pool slider tweak - nostr:npub1ltx67888tz7lqnxlrg06x234vjnq349tcfyp52r0lstclp548mcqnuz40t
- New Malay translations - nostr:npub1cjtt3nywuflj65ftld4v7zzpg0qh3ergycjcym0956vf9eftv7esekxpmn
- Updated Persian translations - nostr:npub1cpazafytvafazxkjn43zjfwtfzatfz508r54f6z6a3rf2ws8223qc3xxpk
- Updated Finnish translations - nostr:npub1ust7u0v3qffejwhqee45r49zgcyewrcn99vdwkednd356c9resyqtnn3mj
- Updated French translations - nostr:npub1x8dzy9xegwmdk2vy30l8u08caspcqq2yzncxehdsa6kvnte9pr3qnt8pg4 & nostr:npub13w02l37gkjwv90lnklfet5653jj0p5ueu976v3dpda5afvxgw3uslcqdnv
- Updated German translations - nostr:npub19a6x8frkkn2660fw0flz74a7qg8c2jxk5v9p2rsh7tv5e6ftsq3sav63vp
- Updated Hungarian translations - nostr:npub1ww8kjxz2akn82qptdpl7glywnchhkx3x04hez3d3rye397turrhssenvtp
- Updated Swedish translations - nostr:npub19jk45jz45gczwfm22y9z69xhaex3nwg47dz84zw096xl6z62amkqj99rv7
- Updated Japanese translations - nostr:npub1wh69w45awqnlsxw7jt5tkymets87h6t4phplkx6ug2ht2qkssswswntjk0
## Fixed
- Longform note overlfow-x - nostr:npub1g53mukxnjkcmr94fhryzkqutdz2ukq4ks0gvy5af25rgmwsl4ngq43drvk
- Trim zap content - nostr:npub1u8lnhlw5usp3t9vmpz60ejpyt649z33hu82wc2hpv6m5xdqmuxhs46turz
---
# v0.1.23
## Added
- DeepL translate api (Automatic for PRO subscribers)
- Add nostr:nprofile1qqsydl97xpj74udw0qg5vkfyujyjxd3l706jd0t0w0turp93d0vvungfgfewr to contributors
- Proxy LN address type enabled on Nostr Address settings pages
- Infinite scrol on notifications page
- Default 0.5% ZapPool rate for Snort donation address
- Collect relay metrics in `@snort/system` for better relay selection algo in Outbox Model (NIP-65)
- New sign up / login flow!
- Topics / Mute words on sign up for easier onboarding
- Drag & Drop for uploads on note creator - nostr:nprofile1qqs8tchhwf5smv3r2g0vkswz58c837uu456x59m3dh380gtrhqzydeqz4wlka
- Mixin topics (hashtags) into timeline feed
- Language specific trending posts
- Show following info for hashtags
- Sync preferences to network (`NIP-78` support)
- Trending hashtags page
- Note creator hashtag input
- Top trending hashtags on note creator
- Social Graph - nostr:nprofile1qqsy2ga7trfetvd3j65m3jptqw9k39wtq2mg85xz2w542p5dhg06e5qpr9mhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv9uh8am0r
- New users relay list based off "close" relays
- `NIP-96` support for nostr native image/file uploaders
- Write replies/reactions to `p` tagged users read relays (Outbox model)
- Sync joined public chats (`NIP-28`) using `PublicChatList` kind `10_005`
## Changed
- Read/Write relays only on kind `10_002` (NIP-65)
- Removed `nostr.watch` code for adding new users to random relays
- Render kind `10_002` on profile relays tab
- `@snort/system` using eventemitter3 for triggering events
- Use latest `NIP-51` spec (Bookmarks/Interests/`NIP-28` PublicChatList)
- `nreq` support (Demo)
- Write profile/relays to blasters
- `@snort/system` automated outbox model (automatic fetching of relay metadata)
## Fixes
- Upgrade ephermal connection to non-ephemeral
- Remove relay tag from zaps (Some zap services dont support it)
- Fix zap parsing for goals
- Remove extra chars from quoted events to fix loading (`'s` etc)
- CSS Fixes for profile card on light theme
- Zap counting on replacable events
- `NIP-28` chats loading
- Overflowing modal UI
- Live stream widget layout with long titles
- Notifications marker has returned from its long slumber
---
# v0.1.22
## Fixes
- Note creator too wide on mobile
- Sending notes dialog duplicated when replying
---
# v0.1.21
## Added
- Add gradients to iris.to domain
- Render referenced kind-3 (ContactList) as pubkey list
- List feed page renders the posts of a given list `/list-feed/{naddr-of-nip51-list-or-nevent-of-kind3}`
- Respond to AUTH when expected (Requesting DM's/GiftWrap)
- Show reply counts on threads
- Quote Repost
- Signature checks can be enabled in preferences
- NIP-98 auth for void.cat / nostr.build file uploaders
- Add `E` tag for direct replies
- File upload progress bar (void.cat only)
- Long form modal for deck layout (WIP still)
- Video thumbnails using ImgProxy
- Renew subscriptions for X months
- Tailwind CSS migration @mmalmi
- Seasonal features
- Profile cards on hover for mentions
- Dropdown search results on search bar @mmalmi
- Renew subscription task on task list
## Changed
- Disable highligher.js code blocks (for now)
- Removed "Popular Accounts" from new user flow, replaced with "Snort Devs" only
- Moved "Show Preview" on note creator to preview toggle switch
- Premium subscription renamed to PRO
- Limit images in posts to 800px high
- Nostrplebs colors removed
## Fixed
- Use correct hostname when submitting analytics
- Disable WASM when not supported on device
- Typo on "Nostr Address" in account settings
- Hide expired user status on profiles
- Hide muted dm chats
- Hide blocked replies
---
# v0.1.20
## Added
- Highlight text in search results - @fernandoporazzi
- Iris/Snort build configuration - @mmalmi
- Iris free NIP-05 on Profile page - @mmalmi
- Image galleries on posts - @fernandoporazzi
- Close modal with ESC - @mmalmi
- Navigate image spotlight with LR direction keys - @mmalmi
- Spotlight preview profile/banner on click - @mmalmi
- Fetch profiles from HTTP cache (Iris) - @mmalmi
- Animal names for empty profile accounts (Iris) - @mmalmi
- Redirect to NIP-05 short links for iris/snort accounts - @mmalmi
- Code block highlighting - @fernandoporazzi
- Notification summary graph - @Kieran
- Profile hover cards - @Kieran
- Keyboard shortcuts for new post/focus/search - @fernandoporazzi
- Markdown rendering for long form content - @Kieran
- Show relay response when publishing - @Kieran
## Fixed
- Copy buttons on insecure context - @Kieran
---
# v0.1.19
## Added
- Highlight search results
## Fixes
- Copy to clipboard on insecure context (Umbrel)
---
# v0.1.16
## Fixes
- Login bugs
---
# v0.1.15
## Added
- User status on profile pages (Music only [NIP-38])
- Following mark on avatars, if you follow the pubkey you will see a green tick on their avatar
- Pin encryption, encrypted private key storage for nsec login
- Pubkey (readonly) logins hide buttons which cannot be used (reactions, reply, save profiles, dms etc)
- Muted words feature (phase 1)
- NIP-28 public chats
## Changed
- Styles changes for Content warnings
- Live stream embed styles
- Cashu token embed styles
- Snort Deck thread navigation in modal from timeline
- PoW miner moved to WASM module for faster hashing
## Fixed
- Profile link to dms
- Long form content loading and replies
- Search function restored
---
# v0.1.14
## Added
- Timeline cache: faster page loads and much lower data usage
- WASM module: Some code moved to Rust WASM module for faster execution
- Zap Splits: NIP-57.G
- New Languages:
- Finnish
- Dutch
- Portuguese Brazilian
## Changed
- Count polls by pubkey
---
# v0.1.13
# Added
- Snort V2 Design
- NIP-24 Encrypted secret chats (nsec login only)
- NIP-13 Proof of Work (POW)
- NIP-31 Alt tag spec for unknown event kinds
- Render mentioned zap goals (Kind 9041)
- Embed fonts in src (No more google fonts requests)
- Native key storage for Android app (`Nip7os` interface)
- Swahili translations
- Thai translations
# Changed
- PWA pre-cache setup (Faster PWA loading)
- Show note creator button on profile pages
# Fixed
- Umlauts in urls
- Reject events which don't match request filter
---
# v0.1.12
# Added
- nsecBunker support (connection string `bunker://<pubkey>?relay=wss://realy.com[#token]`)
# Changed
- New snort logo by Bitko
- Infinite scroll changed to manual action (temperarily to fix performance issues)
# Fixed
- Note to self containing all DMS
- Media spotlight disabled for poll options containing images
- Badge image sizes oversize when bypassing imgproxy due to loading error
---
# v0.1.11
## Added
- `@snort/system` package
- `@snort/system-react` package
- Live streaming page (NIP-102)
- Chat system refactor (adding new chat systems much easier now, NIP-29 first candidate)
- NIP-29 simple group chat support
## Fixed
- Profile links with incorrect hrp fixed in some places
- `naddr` event loading fixed
- Relay specific requests fixed (Global tab / Search page)
- NWC connection responds to AUTH requests now
https://git.v0l.io/Kieran/snort/compare/v0.1.10...v0.1.11
---
# v0.1.10
## Added
- Gossip model, query follows write relays for events
- @snort/system NPM package containing Snort core nostr code
- NIP-44 Encryption scheme support
- NIP-59 Gift Wrap support
## Fixed
- Unmarked thread events replies out of order
https://git.v0l.io/Kieran/snort/compare/v0.1.9...v0.1.10
---
# v0.1.9
## Added
- Discover tab, shows trending users/posts from nostr.build
- New DM styles
- Mentioned Zapstr tracks are previewed on Snort with player
- Custom emoji rendering in posts (NIP-30)
- Lanaguage selector on new user flow
- ZapPool, support nostr ecosystem by donating a percentage of your zaps
- Alby NWC link added to NWC connect page
- SemisolDev follow recommendations on Discover tab
- Pubkey lists (NIP-51) render inline when mentioned in notes
- Persian language
- OpenGraph Image/Video media rendered inside link preview box
- Option to zap everybody on mentioned pubkey list
- L402 support for inline media (paywall content)
## Changed
- Error page shows actual error message now, also a button to reset app cache
- Massivly improved profile loading
- Improved JS bundle size by ejecting CRA and using dynamic modules
- Switched to `@void-cat/api` package for void.cat uploads
---
# v0.1.8
## Added
- Tamil Language support
- Quoted notes are rendered embedded
- Multi-account support for subscribers
- Zapper key loading processing in background to speed up profile loading
- Export keys page added to settings
- NIP-94 support for rendering quoted file metadata events
- Interactions cache (zaps/likes/reports) for better UX
- Full screen image/video previews in modal
- Re-broadcast own events dialog
- Nostr wallet connect support
- Cashu token parsing preview with redeem link
- Trending notes/people tabs added to search page
## Changed
- Profile page loads only 200 latest notes, improving profile load times for accounts with less activity
- New user flow has been tweaked to be shorter with NIP5 & Twitter import steps removed
## Fixed
- Thread navigation without page reload
- NIP-42 functionality restored
- `a` tagged kind 1 replies render properly under root event
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.7...v0.1.8
---
# v0.1.7
## Added
- Per event zap targets by @v0l
- Content warning (NIP-36) support by @v0l
- Polls (NIP-69) by @v0l
- Snort subscriptions by @v0l
- NIP-94 File header support by @v0l
- Link previews by @ghobs91 & @v0l
- Cmd+Enter to post note by @v0l
- `nostr:` links (NIP-27) by @v0l
- Tending users on Search page by @ghobs91 & @v0l
## Changed
- Paste image upload by @vivganes
- Note creator note preview by @v0l
- Login private key input masking by @vivganes
## Fixed
- Fix note creator closing on thread when new replies load by @SamSamskies
- Follow hashtag tab highlighting by @SamSamskies
- Language dropdown defaults to Arabic by @vivganes
- Bookmarks showing reactions by @vivganes
- Single zapper on note only shows name by @vivganes
- Broken link previews show empty box by @vivganes
- Render jfif images by @v0l
## PR List
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/476
- `nostr` package: implement NIP-05 by @sistemd in https://github.com/v0l/snort/pull/474
- `nostr` package: NIP-09 event deletion by @sistemd in https://github.com/v0l/snort/pull/478
- fix #484 by @vivganes in https://github.com/v0l/snort/pull/486
- fix #485 by @vivganes in https://github.com/v0l/snort/pull/487
- Per event zap targets by @v0l in https://github.com/v0l/snort/pull/466
- feat: nip-36 by @v0l in https://github.com/v0l/snort/pull/497
- fix #496 by @vivganes in https://github.com/v0l/snort/pull/498
- use redux for NoteCreator state management by @SamSamskies in https://github.com/v0l/snort/pull/494
- fix #495 by @vivganes in https://github.com/v0l/snort/pull/499
- Polls (NIP-69) by @v0l in https://github.com/v0l/snort/pull/489
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/483
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/508
- add ability to paste image from clipboard by @vivganes in https://github.com/v0l/snort/pull/510
- Subscriptions by @v0l in https://github.com/v0l/snort/pull/506
- feat: multi-account system by @v0l in https://github.com/v0l/snort/pull/514
- fix followed tag active tab highlighting by @SamSamskies in https://github.com/v0l/snort/pull/516
- NIP-94 file headers by @v0l in https://github.com/v0l/snort/pull/488
- fix #517 by @vivganes in https://github.com/v0l/snort/pull/518
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/511
- `nostr` package: get tests passing in the browser by @sistemd in https://github.com/v0l/snort/pull/490
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/519
- Subscription handle by @v0l in https://github.com/v0l/snort/pull/522
## New Contributors
- @vivganes made their first contribution in https://github.com/v0l/snort/pull/486
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.6...v0.1.7
---
# v0.1.6
## 🏷️ Summary
- Snort NIP5 management page (for transfers to new pubkeys)
- Short links for Snort NIP5 owners (ie. https://snort.social/kieran)
## Other Changes
- Update Wavlake embed to support .com links by @blastshielddown in https://github.com/v0l/snort/pull/469
- Bug fixes for save profile & relay connection on clean browser
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.5...v0.1.6
---
# v0.1.5
## 🏷️ Short Summary
- Completely rebuilt "core" subscription management system
- Option to rewrite Twitter links to Nitter links
- Tarui app setup, Mac/Windows/Linux desktop apps (coming soon)
- OpenGraph tagging for profiles and events (Only for https://snort.social)
- NIP-27 `nostr:` link parsing
- Global tab full relay names
## What's Changed
- `nostr` package: add direct messages by @sistemd in https://github.com/v0l/snort/pull/399
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/445
- Display search property alongside host in relay name by @h3y6e in https://github.com/v0l/snort/pull/452
- Shorten long relay name by @h3y6e in https://github.com/v0l/snort/pull/455
- Nostr links by @v0l in https://github.com/v0l/snort/pull/461
- `nostr` package: vastly simplify the API by @sistemd in https://github.com/v0l/snort/pull/412
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/453
- Fix: invisible <option> text in dark theme by @jiftechnify in https://github.com/v0l/snort/pull/454
- add setting for rewriting twitter links to nitter by @w3irdrobot in https://github.com/v0l/snort/pull/459
- RequestBuilder / Core Refactor by @v0l in https://github.com/v0l/snort/pull/326
- Tauri setup by @v0l in https://github.com/v0l/snort/pull/462
- Prevents adding ws relay when over https by @ivanacostarubio in https://github.com/v0l/snort/pull/463
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/465
- OpenGraph tag injection by @v0l in https://github.com/v0l/snort/pull/470
## New Contributors
- @jiftechnify made their first contribution in https://github.com/v0l/snort/pull/454
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.4...v0.1.5
---
# v0.1.4
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.3...v0.1.4
---
# v0.1.3
## What's Changed
- only replace note ID when note ID starts with `@` character by @SamSamskies in https://github.com/v0l/snort/pull/441
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.2...v0.1.3
---
# v0.1.2
## What's Changed
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/309
- UI bugs by @verbiricha in https://github.com/v0l/snort/pull/301
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/311
- Add build command to readme by @joshr4 in https://github.com/v0l/snort/pull/300
- fix(fotter-actions): add highlighting and min-width by @fernandolguevara in https://github.com/v0l/snort/pull/312
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/313
- `nostr` package part 1 by @fcked in https://github.com/v0l/snort/pull/315
- Reduce space between the texts for selecting relays by @h3y6e in https://github.com/v0l/snort/pull/316
- fix(profile): convert page id to npub bech32 by @fernandolguevara in https://github.com/v0l/snort/pull/322
- Improve overflow menu button by @joshr4 in https://github.com/v0l/snort/pull/304
- German translations for snort by @gandlafbtc in https://github.com/v0l/snort/pull/323
- fix: send all relays when zapping by @verbiricha in https://github.com/v0l/snort/pull/324
- Add default page selector by @jacany in https://github.com/v0l/snort/pull/321
- UI fixes by @verbiricha in https://github.com/v0l/snort/pull/318
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/319
- feat: render kind 1 reposts by @kphrx in https://github.com/v0l/snort/pull/314
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/330
- Use inner note content as comment by @Semisol in https://github.com/v0l/snort/pull/333
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/331
- Fix stale relays by @SamSamskies in https://github.com/v0l/snort/pull/337
- Feed cache rework by @v0l in https://github.com/v0l/snort/pull/339
- fix long zap comment text overflow by @SamSamskies in https://github.com/v0l/snort/pull/344
- fix links in parentheses by @SamSamskies in https://github.com/v0l/snort/pull/347
- Revert "Merge pull request #347 from v0l/fix-links-in-parentheses" by @SamSamskies in https://github.com/v0l/snort/pull/350
- Update thread detection to not include mentions by @w3irdrobot in https://github.com/v0l/snort/pull/351
- Small settings page stuff by @w3irdrobot in https://github.com/v0l/snort/pull/353
- Change message unread color to purple by @w3irdrobot in https://github.com/v0l/snort/pull/354
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/356
- Remove unread message dot when messages all read by @w3irdrobot in https://github.com/v0l/snort/pull/355
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/359
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/362
- `nostr` package part 2 by @fcked in https://github.com/v0l/snort/pull/346
- feat: add search page field autofocus by @lujakob in https://github.com/v0l/snort/pull/363
- fix URL parsing edge cases by @SamSamskies in https://github.com/v0l/snort/pull/360
- Fast Zaps ⚡ by @v0l in https://github.com/v0l/snort/pull/370
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/364
- Feat/add spinner to button by @lujakob in https://github.com/v0l/snort/pull/368
- Update mark all read dm button to be disabled when no unreads by @w3irdrobot in https://github.com/v0l/snort/pull/373
- `nostr` package part 3 by @fcked in https://github.com/v0l/snort/pull/365
- LNDHub/LNC wallet by @v0l in https://github.com/v0l/snort/pull/219
- Proposal: Remove SVGs from JSX by @enjikaka in https://github.com/v0l/snort/pull/382
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/374
- add Nostr Nests embed by @SamSamskies in https://github.com/v0l/snort/pull/377
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/387
- fix icons by @h3y6e in https://github.com/v0l/snort/pull/392
- Fix broken note links by @SamSamskies in https://github.com/v0l/snort/pull/380
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/391
- fix(BackButton): vertical align styles by @lujakob in https://github.com/v0l/snort/pull/397
- feat(note): open note in new tab on cmd press by @lujakob in https://github.com/v0l/snort/pull/395
- fix(skeleton): dark theme styles by @lujakob in https://github.com/v0l/snort/pull/393
- fix HyperText matching by @mattn in https://github.com/v0l/snort/pull/405
- Makes entire note clickable by @d-r-w in https://github.com/v0l/snort/pull/371
- render webm links as inline videos by @SamSamskies in https://github.com/v0l/snort/pull/410
- render embed for youtube live links by @SamSamskies in https://github.com/v0l/snort/pull/407
- do not render reposts of badge award events in timelines by @SamSamskies in https://github.com/v0l/snort/pull/406
- `nostr` package: use `EventEmitter` by @fcked in https://github.com/v0l/snort/pull/384
- `nostr` pacakge: implement basic NIP-20 `OK` functionality by @fcked in https://github.com/v0l/snort/pull/385
- feat: read nip-58 badges by @verbiricha in https://github.com/v0l/snort/pull/394
- Add Wavlake embed by @blastshielddown in https://github.com/v0l/snort/pull/416
- display search results on page load if query in url by @SamSamskies in https://github.com/v0l/snort/pull/415
- Fix event mention bug by @SamSamskies in https://github.com/v0l/snort/pull/421
- fix NaN when parsing empty string by @SamSamskies in https://github.com/v0l/snort/pull/422
- NIP06 support by @w3irdrobot in https://github.com/v0l/snort/pull/425
- Added key attr to TabSelectors to remove React warning by @w3irdrobot in https://github.com/v0l/snort/pull/424
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/426
- New Crowdin updates by @v0l in https://github.com/v0l/snort/pull/436
- Update Wavlake embed URL, add support for album & artist links by @blastshielddown in https://github.com/v0l/snort/pull/439
- build(deps): bump webpack from 5.75.0 to 5.76.1 by @dependabot in https://github.com/v0l/snort/pull/442
## New Contributors
- @joshr4 made their first contribution in https://github.com/v0l/snort/pull/300
- @gandlafbtc made their first contribution in https://github.com/v0l/snort/pull/323
- @jacany made their first contribution in https://github.com/v0l/snort/pull/321
- @kphrx made their first contribution in https://github.com/v0l/snort/pull/314
- @lujakob made their first contribution in https://github.com/v0l/snort/pull/363
- @mattn made their first contribution in https://github.com/v0l/snort/pull/405
- @d-r-w made their first contribution in https://github.com/v0l/snort/pull/371
- @blastshielddown made their first contribution in https://github.com/v0l/snort/pull/416
- @dependabot made their first contribution in https://github.com/v0l/snort/pull/442
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.1...v0.1.2
---
# v0.1.1
## What's Changed
- React Map Optimization, [missing map keys ] by @ahmedrowaihi in https://github.com/v0l/snort/pull/283
- Translate '/src/lang.json' in 'ar' by @transifex-integration in https://github.com/v0l/snort/pull/287
- HTML auto direction for specific textual content by @verbiricha in https://github.com/v0l/snort/pull/286
- Translate '/src/lang.json' in 'hu' by @transifex-integration in https://github.com/v0l/snort/pull/288
- feat: twitch embed by @v0l in https://github.com/v0l/snort/pull/289
- fix: don't show 0 if there is no description by @verbiricha in https://github.com/v0l/snort/pull/290
- feat: pinned notes and bookmarks by @verbiricha in https://github.com/v0l/snort/pull/255
- Translate '/src/lang.json' in 'ja' by @transifex-integration in https://github.com/v0l/snort/pull/293
- fix: set auto to whole text content instead of individual paragraphs by @verbiricha in https://github.com/v0l/snort/pull/292
- protocol handler by @v0l in https://github.com/v0l/snort/pull/164
- feat: read global from specific (paid) relays by @v0l in https://github.com/v0l/snort/pull/249
- SUPPORT RTL/LTR ON LOGIN PAGE by @ahmedrowaihi in https://github.com/v0l/snort/pull/291
- Add Apple Music embed by @SamSamskies in https://github.com/v0l/snort/pull/294
- Workspace with decoupled `nostr` package by @ennmichael in https://github.com/v0l/snort/pull/274
- Translate '/src/lang.json' in 'ar' by @transifex-integration in https://github.com/v0l/snort/pull/296
- UI fixes + counts on profile page tabs by @verbiricha in https://github.com/v0l/snort/pull/282
- Fix blackout when selecting global tab by @h3y6e in https://github.com/v0l/snort/pull/297
- Prevent profile text from overflowing flex container when it is too long by @h3y6e in https://github.com/v0l/snort/pull/298
## New Contributors
- @ahmedrowaihi made their first contribution in https://github.com/v0l/snort/pull/283
**Full Changelog**: https://github.com/v0l/snort/compare/v0.1.0...v0.1.1
---
# v0.1.0
## What's Changed
- Add global tab to Root by @p2pseed in https://github.com/v0l/snort/pull/3
- UI improvements by @verbiricha in https://github.com/v0l/snort/pull/4
- fix: dedupe thread pubkeys by @verbiricha in https://github.com/v0l/snort/pull/7
- Note creator improvement by @verbiricha in https://github.com/v0l/snort/pull/6
- fix: correctly follow user mention links by @verbiricha in https://github.com/v0l/snort/pull/5
- fix: force timeline rerender on tab change by @verbiricha in https://github.com/v0l/snort/pull/8
- feat: add mov to video files by @verbiricha in https://github.com/v0l/snort/pull/9
- feat: copy npub on profile by @verbiricha in https://github.com/v0l/snort/pull/10
- fix: display full lightning address, is trimmed if too long by @verbiricha in https://github.com/v0l/snort/pull/12
- feat: embed youtube videos by @verbiricha in https://github.com/v0l/snort/pull/13
- feat: add support for positive and negative reactions by @verbiricha in https://github.com/v0l/snort/pull/11
- feat: nip05 on profile page by @verbiricha in https://github.com/v0l/snort/pull/21
- UI improvements by @verbiricha in https://github.com/v0l/snort/pull/24
- Home tabs by @v0l in https://github.com/v0l/snort/pull/25
- fix: support m.youtube.com subdomain links by @verbiricha in https://github.com/v0l/snort/pull/27
- fix: use all available width for note creator text area by @verbiricha in https://github.com/v0l/snort/pull/28
- highlight hashtags by @verbiricha in https://github.com/v0l/snort/pull/29
- UI tweaks by @verbiricha in https://github.com/v0l/snort/pull/30
- Improve regexes by @verbiricha in https://github.com/v0l/snort/pull/32
- Shows QR code first by @ivanacostarubio in https://github.com/v0l/snort/pull/23
- feat: embed tweets by @verbiricha in https://github.com/v0l/snort/pull/33
- Nip5 shop by @v0l in https://github.com/v0l/snort/pull/50
- Activate snort.social NIP-5 service by @v0l in https://github.com/v0l/snort/pull/51
- feat: add avatar borders with color gradients to partner nip05 providers by @verbiricha in https://github.com/v0l/snort/pull/52
- DM's by @v0l in https://github.com/v0l/snort/pull/54
- feat: display banner in profile by @verbiricha in https://github.com/v0l/snort/pull/53
- add max width to details by @verbiricha in https://github.com/v0l/snort/pull/59
- Markdown by @verbiricha in https://github.com/v0l/snort/pull/55
- feat: mentions by @verbiricha in https://github.com/v0l/snort/pull/56
- Minor UI fixes by @verbiricha in https://github.com/v0l/snort/pull/63
- add user DB and cache nip-05 verifications by @verbiricha in https://github.com/v0l/snort/pull/65
- fix: adjust nip05 size by @verbiricha in https://github.com/v0l/snort/pull/66
- fix: dont display display_name as nip user when username is default by @verbiricha in https://github.com/v0l/snort/pull/67
- fix: don't retry errored verifications by @verbiricha in https://github.com/v0l/snort/pull/71
- UI improvements by @verbiricha in https://github.com/v0l/snort/pull/70
- refactor: TS by @v0l in https://github.com/v0l/snort/pull/69
- More TSX by @v0l in https://github.com/v0l/snort/pull/74
- Moar UI fixes by @verbiricha in https://github.com/v0l/snort/pull/73
- fix: autocomplete colors by @verbiricha in https://github.com/v0l/snort/pull/75
- feat: query for autocompletion using local db by @verbiricha in https://github.com/v0l/snort/pull/76
- fix: rerender user timeline on pubkey change by @verbiricha in https://github.com/v0l/snort/pull/77
- feat: follows you on profile page by @ivanacostarubio in https://github.com/v0l/snort/pull/64
- autocomplete improvements by @verbiricha in https://github.com/v0l/snort/pull/83
- filter for self dms by @LiranCohen in https://github.com/v0l/snort/pull/86
- Notifications by @v0l in https://github.com/v0l/snort/pull/88
- Theme by @verbiricha in https://github.com/v0l/snort/pull/87
- Modified self-dm to be a "Note to Self" by @LiranCohen in https://github.com/v0l/snort/pull/89
- note footer ordering by @verbiricha in https://github.com/v0l/snort/pull/91
- Hashtags by @v0l in https://github.com/v0l/snort/pull/92
- Make logo cursor a pointer by @w3irdrobot in https://github.com/v0l/snort/pull/99
- fix: active note colors by @verbiricha in https://github.com/v0l/snort/pull/102
- Tidal embeds by @v0l in https://github.com/v0l/snort/pull/95
- UI improvements by @verbiricha in https://github.com/v0l/snort/pull/103
- User preferences by @v0l in https://github.com/v0l/snort/pull/104
- Add note context menu by @v0l in https://github.com/v0l/snort/pull/105
- feat: soundcloud embed by @ivanacostarubio in https://github.com/v0l/snort/pull/112
- feat: Show latest by @v0l in https://github.com/v0l/snort/pull/113
- light theme fixes by @verbiricha in https://github.com/v0l/snort/pull/116
- add Karnage to contributors by @verbiricha in https://github.com/v0l/snort/pull/117
- feat: note mentions by @verbiricha in https://github.com/v0l/snort/pull/125
- Preferences & Profile changes by @FlannelDipole in https://github.com/v0l/snort/pull/126
- sort bug in the event that your pubkey is the 2nd item in the list by @LiranCohen in https://github.com/v0l/snort/pull/137
- UI updates by @verbiricha in https://github.com/v0l/snort/pull/135
- fix: hide note creator on send by @verbiricha in https://github.com/v0l/snort/pull/139
- fix: add bottom margin to thread by @verbiricha in https://github.com/v0l/snort/pull/140
- adds mixcloud by @ivanacostarubio in https://github.com/v0l/snort/pull/136
- feat: audio player by @verbiricha in https://github.com/v0l/snort/pull/146
- feat: in-memory fallback for storing user profiles by @verbiricha in https://github.com/v0l/snort/pull/110
- Make Markdown more interoperable by @fiatjaf in https://github.com/v0l/snort/pull/153
- fix: default to in-memory db only on db read fail by @verbiricha in https://github.com/v0l/snort/pull/155
- bug: logout reply by @ivanacostarubio in https://github.com/v0l/snort/pull/154
- Search by @v0l in https://github.com/v0l/snort/pull/143
- Nip42 (AUTH) by @v0l in https://github.com/v0l/snort/pull/144
- Muted list via NIP-51 by @verbiricha in https://github.com/v0l/snort/pull/151
- Add more relays (high performance) by @Semisol in https://github.com/v0l/snort/pull/149
- Show absolute time on hover by @wanacode in https://github.com/v0l/snort/pull/166
- nostr.build file uploads by @v0l in https://github.com/v0l/snort/pull/162
- New UI by @v0l in https://github.com/v0l/snort/pull/161
- fix: send d tags as list by @verbiricha in https://github.com/v0l/snort/pull/169
- Image proxy service by @v0l in https://github.com/v0l/snort/pull/174
- Translate notes by @v0l in https://github.com/v0l/snort/pull/179
- Use standard imgproxy by @v0l in https://github.com/v0l/snort/pull/180
- Fix races where Socket is closed before Websocket is created by @brugeman in https://github.com/v0l/snort/pull/186
- feat: nostrimg.com by @v0l in https://github.com/v0l/snort/pull/181
- Add Spotify embed by @SamSamskies in https://github.com/v0l/snort/pull/188
- Ln invoice styling by @verbiricha in https://github.com/v0l/snort/pull/187
- feed cache by @v0l in https://github.com/v0l/snort/pull/184
- bug: prepends https when missing from website by @ivanacostarubio in https://github.com/v0l/snort/pull/194
- Use the current embed player via TIDALs OEmbed API. by @enjikaka in https://github.com/v0l/snort/pull/191
- feat: zaps by @verbiricha in https://github.com/v0l/snort/pull/78
- display note zaps succintly by @verbiricha in https://github.com/v0l/snort/pull/196
- nostr-pub.semisol.dev is now atlas.nostr.land by @Semisol in https://github.com/v0l/snort/pull/198
- Zaps fixes by @verbiricha in https://github.com/v0l/snort/pull/199
- Note creator improvements by @verbiricha in https://github.com/v0l/snort/pull/193
- Settings page and UI tweaks by @verbiricha in https://github.com/v0l/snort/pull/200
- fix avatars by @verbiricha in https://github.com/v0l/snort/pull/203
- Skeleton component on timeline loading for better user experience by @leotuna in https://github.com/v0l/snort/pull/190
- Threads by @verbiricha in https://github.com/v0l/snort/pull/170
- fix: don't stream global feed in notifications tab by @verbiricha in https://github.com/v0l/snort/pull/207
- Zap modal by @verbiricha in https://github.com/v0l/snort/pull/209
- Add prettier formatting by @ennmichael in https://github.com/v0l/snort/pull/214
- react-intl spike by @SamSamskies in https://github.com/v0l/snort/pull/216
- Add support for zh and ja locales by @SamSamskies in https://github.com/v0l/snort/pull/218
- feat: reactions modal by @verbiricha in https://github.com/v0l/snort/pull/215
- Translate '/src/translations/en.json' in 'es' by @transifex-integration in https://github.com/v0l/snort/pull/224
- Translate '/src/translations/en.json' in 'ja' by @transifex-integration in https://github.com/v0l/snort/pull/227
- fix: allow zap comments by @verbiricha in https://github.com/v0l/snort/pull/229
- Eslint by @v0l in https://github.com/v0l/snort/pull/223
- Translate '/src/translations/en.json' in 'fr' by @transifex-integration in https://github.com/v0l/snort/pull/230
- add ability to use babel plugins without ejecting by @SamSamskies in https://github.com/v0l/snort/pull/225
- add prettier pre-commit hook by @SamSamskies in https://github.com/v0l/snort/pull/234
- oversight of intl by @h3y6e in https://github.com/v0l/snort/pull/231
- feat: new login page by @v0l in https://github.com/v0l/snort/pull/235
- feat: onboarding by @verbiricha in https://github.com/v0l/snort/pull/233
- Translate '/src/translations/en.json' in 'ja' by @transifex-integration in https://github.com/v0l/snort/pull/243
- Translate '/src/translations/en.json' in 'fr' by @transifex-integration in https://github.com/v0l/snort/pull/242
- Translate '/src/translations/en.json' in 'es' by @transifex-integration in https://github.com/v0l/snort/pull/241
- feat: break lang by @v0l in https://github.com/v0l/snort/pull/244
- fix(missing-event): avoid redirect by @fernandolguevara in https://github.com/v0l/snort/pull/246
- fix(content): render media content for current pubkey by @fernandolguevara in https://github.com/v0l/snort/pull/240
- remove follow button from reactions modal by @SamSamskies in https://github.com/v0l/snort/pull/247
- NIP-65: Relay list metada by @verbiricha in https://github.com/v0l/snort/pull/238
- Fix DM page UI by @SamSamskies in https://github.com/v0l/snort/pull/250
- Translate '/src/lang.json' in 'es' by @transifex-integration in https://github.com/v0l/snort/pull/252
- Translate '/src/lang.json' in 'es' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/258
- Translate '/src/lang.json' in 'fr' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/259
- Translate '/src/lang.json' in 'hu' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/260
- Translate '/src/lang.json' in 'ja' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/261
- Translate '/src/lang.json' in 'zh' [manual sync] by @transifex-integration in https://github.com/v0l/snort/pull/262
- Translate '/src/lang.json' in 'ja' by @transifex-integration in https://github.com/v0l/snort/pull/275
- Translate '/src/lang.json' in 'id' by @transifex-integration in https://github.com/v0l/snort/pull/277
- Translate '/src/lang.json' in 'zh' by @transifex-integration in https://github.com/v0l/snort/pull/278
- Translate '/src/lang.json' in 'es' by @transifex-integration in https://github.com/v0l/snort/pull/279
- Translate '/src/lang.json' in 'hu' by @transifex-integration in https://github.com/v0l/snort/pull/280
- Translate '/src/lang.json' in 'fr' by @transifex-integration in https://github.com/v0l/snort/pull/281
## New Contributors
- @p2pseed made their first contribution in https://github.com/v0l/snort/pull/3
- @v0l made their first contribution in https://github.com/v0l/snort/pull/25
- @ivanacostarubio made their first contribution in https://github.com/v0l/snort/pull/23
- @w3irdrobot made their first contribution in https://github.com/v0l/snort/pull/99
- @FlannelDipole made their first contribution in https://github.com/v0l/snort/pull/126
- @fiatjaf made their first contribution in https://github.com/v0l/snort/pull/153
- @Semisol made their first contribution in https://github.com/v0l/snort/pull/149
- @wanacode made their first contribution in https://github.com/v0l/snort/pull/166
- @SamSamskies made their first contribution in https://github.com/v0l/snort/pull/188
- @enjikaka made their first contribution in https://github.com/v0l/snort/pull/191
- @leotuna made their first contribution in https://github.com/v0l/snort/pull/190
- @transifex-integration made their first contribution in https://github.com/v0l/snort/pull/224
- @h3y6e made their first contribution in https://github.com/v0l/snort/pull/231
- @fernandolguevara made their first contribution in https://github.com/v0l/snort/pull/246
**Full Changelog**: https://github.com/v0l/snort/commits/v0.1.0

2
packages/app/_headers Normal file
View File

@ -0,0 +1,2 @@
/*
Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src youtube.com www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com https://open.spotify.com https://player.twitch.tv https://embed.music.apple.com https://nostrnests.com https://embed.wavlake.com https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; connect-src *; img-src * data: blob:; font-src 'self'; media-src * blob:; script-src 'self' 'wasm-unsafe-eval' https://analytics.v0l.io https://platform.twitter.com https://embed.tidal.com https://challenges.cloudflare.com;

View File

@ -1,11 +0,0 @@
{
"plugins": [
[
"formatjs",
{
"idInterpolationPattern": "[sha512:contenthash:base64:6]",
"ast": true
}
]
]
}

View File

@ -2,56 +2,12 @@
"appName": "Snort", "appName": "Snort",
"appNameCapitalized": "Snort", "appNameCapitalized": "Snort",
"appTitle": "Snort - Nostr", "appTitle": "Snort - Nostr",
"hostname": "snort.social",
"nip05Domain": "snort.social", "nip05Domain": "snort.social",
"icon": "/nostrich_512.png", "favicon": "public/favicon.ico",
"navLogo": null, "appleTouchIconUrl": "/nostrich_512.png",
"publicDir": "public/snort",
"httpCache": "", "httpCache": "",
"animalNamePlaceholders": false, "animalNamePlaceholders": false,
"defaultZapPoolFee": 1,
"features": { "features": {
"analytics": true, "subscriptions": true
"subscriptions": true, }
"deck": true,
"zapPool": true,
"communityLeaders": true,
"nostrAddress": true,
"pushNotifications": true
},
"signUp": {
"quickStart": false,
"defaultFollows": ["npub1sn0rtcjcf543gj4wsg7fa59s700d5ztys5ctj0g69g2x6802npjqhjjtws"]
},
"defaultPreferences": {
"hideMutedNotes": false,
"defaultRootTab": "following"
},
"media": {
"bypassImgProxyError": false,
"preferLargeMedia": true
},
"communityLeaders": {
"list": "naddr1qq4xc6tnw3ez6vp58y6rywpjxckngdtyxukngwr9vckkze33vcknzcnrxcenje35xqmn2cczyp3lucccm3v9s087z6qslpkap8schltk427zfgqgrn3g2menq5zw6qcyqqq82vqprpmhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv7rajfl"
},
"noteCreatorToast": false,
"hideFromNavbar": [],
"deckSubKind": 1,
"showPowIcon": true,
"eventLinkPrefix": "nevent",
"profileLinkPrefix": "nprofile",
"defaultRelays": {
"wss://relay.snort.social/": { "read": true, "write": true },
"wss://nostr.wine/": { "read": true, "write": false },
"wss://relay.damus.io/": { "read": true, "write": true },
"wss://nos.lol/": { "read": true, "write": true }
},
"alby": {
"clientId": "pohiJjPhQR",
"clientSecret": "GAl1YKLA3FveK1gLBYok"
},
"chatChannels": [
{ "type": "telegram", "value": "https://t.me/irismessenger" },
{ "type": "nip28", "value": "23286a4602ada10cc10200553bff62a110e8dc0eacddf73277395a89ddf26a09" }
]
} }

View File

@ -2,54 +2,12 @@
"appName": "iris", "appName": "iris",
"appNameCapitalized": "Iris", "appNameCapitalized": "Iris",
"appTitle": "iris", "appTitle": "iris",
"hostname": "iris.to",
"nip05Domain": "iris.to", "nip05Domain": "iris.to",
"icon": "/img/icon128.png", "favicon": "public/iris/favicon.ico",
"navLogo": "/img/icon128.png", "appleTouchIconUrl": "/img/apple-touch-icon.png",
"publicDir": "public/iris", "httpCache": "https://api.iris.to",
"httpCache": "",
"animalNamePlaceholders": true, "animalNamePlaceholders": true,
"defaultZapPoolFee": 0.5,
"features": { "features": {
"analytics": true, "subscriptions": false
"subscriptions": true,
"deck": true,
"zapPool": true,
"communityLeaders": true
},
"defaultPreferences": {
"hideMutedNotes": true,
"defaultRootTab": "for-you"
},
"signUp": {
"quickStart": true,
"defaultFollows": ["npub1wnwwcv0a8wx0m9stck34ajlwhzuua68ts8mw3kjvspn42dcfyjxs4n95l8"]
},
"media": {
"bypassImgProxyError": true,
"preferLargeMedia": true
},
"communityLeaders": {
"list": "naddr1qq4xc6tnw3ez6vp58y6rywpjxckngdtyxukngwr9vckkze33vcknzcnrxcenje35xqmn2cczyp3lucccm3v9s087z6qslpkap8schltk427zfgqgrn3g2menq5zw6qcyqqq82vqprpmhxue69uhhyetvv9ujuumwdae8gtnnda3kjctv7rajfl"
},
"noteCreatorToast": false,
"hideFromNavbar": [],
"eventLinkPrefix": "note",
"profileLinkPrefix": "npub",
"showPowIcon": false,
"defaultRelays": {
"wss://relay.snort.social/": { "read": true, "write": true },
"wss://nostr.wine/": { "read": true, "write": false },
"wss://eden.nostr.land/": { "read": true, "write": false },
"wss://relay.nostr.band/": { "read": true, "write": true },
"wss://relay.damus.io/": { "read": true, "write": true }
},
"chatChannels": [
{ "type": "telegram", "value": "https://t.me/irismessenger" },
{ "type": "nip28", "value": "23286a4602ada10cc10200553bff62a110e8dc0eacddf73277395a89ddf26a09" }
],
"alby": {
"clientId": "5rYcHDrlDb",
"clientSecret": "QAI3QmgiaPH3BfTMzzFd"
} }
} }

View File

@ -1,49 +0,0 @@
{
"appName": "めく",
"appNameCapitalized": "めく",
"appTitle": "めく",
"hostname": "meku.app",
"nip05Domain": "meku.app",
"icon": "/nostr.jpg",
"navLogo": null,
"publicDir": "public/nostr",
"httpCache": "",
"animalNamePlaceholders": false,
"defaultZapPoolFee": 0,
"features": {
"analytics": true,
"subscriptions": false,
"deck": false,
"zapPool": false,
"communityLeaders": false,
"nostrAddress": false,
"pushNotifications": true
},
"signUp": {
"quickStart": false,
"defaultFollows": []
},
"defaultPreferences": {
"hideMutedNotes": false,
"defaultRootTab": "following",
"language": "ja"
},
"media": {
"bypassImgProxyError": false,
"preferLargeMedia": true
},
"communityLeaders": null,
"noteCreatorToast": false,
"hideFromNavbar": [],
"deckSubKind": 1,
"showPowIcon": true,
"eventLinkPrefix": "nevent",
"profileLinkPrefix": "nprofile",
"defaultRelays": {
"wss://relay.nostr.wirednet.jp/": { "read": true, "write": true },
"wss://yabu.me/": { "read": true, "write": true },
"wss://nos.lol/": { "read": true, "write": true }
},
"alby": null,
"chatChannels": null
}

View File

@ -1,49 +0,0 @@
{
"appName": "Nostr",
"appNameCapitalized": "Nostr",
"appTitle": "Nostr",
"hostname": "nostr.com",
"nip05Domain": "nostr.com",
"icon": "/nostr.jpg",
"navLogo": null,
"publicDir": "public/nostr",
"httpCache": "",
"animalNamePlaceholders": false,
"defaultZapPoolFee": 0,
"features": {
"analytics": false,
"subscriptions": false,
"deck": false,
"zapPool": false,
"communityLeaders": false,
"nostrAddress": false,
"pushNotifications": false
},
"signUp": {
"quickStart": false,
"defaultFollows": []
},
"defaultPreferences": {
"hideMutedNotes": false,
"defaultRootTab": "following"
},
"media": {
"bypassImgProxyError": false,
"preferLargeMedia": true
},
"communityLeaders": null,
"noteCreatorToast": true,
"hideFromNavbar": [],
"deckSubKind": 1,
"showPowIcon": true,
"eventLinkPrefix": "nevent",
"profileLinkPrefix": "nprofile",
"defaultRelays": {
"wss://relay.snort.social/": { "read": true, "write": true },
"wss://nostr.wine/": { "read": true, "write": false },
"wss://eden.nostr.land/": { "read": true, "write": false },
"wss://nos.lol/": { "read": true, "write": true }
},
"alby": null,
"chatChannels": null
}

View File

@ -1,5 +1,4 @@
/// <reference types="@webbtc/webln-types" /> /// <reference types="@webbtc/webln-types" />
/// <reference types="vite/client" />
declare module "*.jpg" { declare module "*.jpg" {
const value: unknown; const value: unknown;
@ -31,11 +30,6 @@ declare module "translations/*.json" {
export default value; export default value;
} }
declare module "*.md" {
const value: string;
export default value;
}
declare module "emojilib" { declare module "emojilib" {
const value: Record<string, string>; const value: Record<string, string>;
export default value; export default value;
@ -45,68 +39,12 @@ declare const CONFIG: {
appName: string; appName: string;
appNameCapitalized: string; appNameCapitalized: string;
appTitle: string; appTitle: string;
hostname: string;
nip05Domain: string; nip05Domain: string;
icon: string; favicon: string;
navLogo: string | null; appleTouchIconUrl: string;
httpCache: string; httpCache: string;
animalNamePlaceholders: boolean; animalNamePlaceholders: boolean;
defaultZapPoolFee: number;
features: { features: {
analytics: boolean;
subscriptions: boolean; subscriptions: boolean;
deck: boolean;
zapPool: boolean;
communityLeaders: boolean;
nostrAddress: boolean;
pushNotifications: boolean;
}; };
defaultPreferences: {
hideMutedNotes: boolean;
defaultRootTab: "following" | "for-you";
};
signUp: {
quickStart: boolean;
defaultFollows: Array<string>;
};
media: {
bypassImgProxyError: boolean;
preferLargeMedia: boolean;
};
communityLeaders?: {
list: string;
};
// Filter urls from nav sidebar
hideFromNavbar: Array<string>;
// Limit deck to certain subscriber tier
deckSubKind?: number;
showDeck?: boolean;
// Create toast notifications when publishing notes
noteCreatorToast: boolean;
eventLinkPrefix: NostrPrefix;
profileLinkPrefix: NostrPrefix;
defaultRelays: Record<string, RelaySettings>;
showPowIcon: boolean;
// Alby wallet oAuth config
alby?: {
clientId: string;
clientSecret: string;
};
// public chat channels for site
chatChannels?: Array<{
type: "nip28" | "telegram";
value: string;
}>;
}; };
/**
* Build git hash
*/
declare const __SNORT_VERSION__: string;

View File

@ -0,0 +1,9 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
bail: true,
preset: "ts-jest",
testEnvironment: "jsdom",
roots: ["src"],
moduleDirectories: ["src", "node_modules"],
setupFiles: ["./src/setupTests.ts"],
};

View File

@ -1,75 +1,53 @@
{ {
"name": "@snort/app", "name": "@snort/app",
"version": "0.2.0", "version": "0.1.10",
"dependencies": { "dependencies": {
"@cashu/cashu-ts": "^1.0.0-rc.3", "@cashu/cashu-ts": "^0.6.1",
"@here/maps-api-for-javascript": "^1.50.0", "@lightninglabs/lnc-web": "^0.2.3-alpha",
"@livekit/components-react": "^2.5.4", "@noble/curves": "^1.0.0",
"@livekit/protocol": "^1.22.0", "@noble/hashes": "^1.2.0",
"@noble/curves": "^1.4.0", "@scure/base": "^1.1.1",
"@noble/hashes": "^1.4.0", "@scure/bip32": "^1.3.0",
"@scure/base": "^1.1.6", "@scure/bip39": "^1.1.1",
"@scure/bip32": "^1.5.0",
"@scure/bip39": "^1.4.0",
"@snort/shared": "workspace:*", "@snort/shared": "workspace:*",
"@snort/system": "workspace:*", "@snort/system": "workspace:*",
"@snort/system-react": "workspace:*", "@snort/system-react": "workspace:*",
"@snort/system-wasm": "workspace:*", "@snort/system-wasm": "workspace:*",
"@snort/system-web": "workspace:*", "@snort/system-web": "workspace:*",
"@snort/wallet": "workspace:*", "@szhsin/react-menu": "^3.3.1",
"@snort/worker-relay": "workspace:*", "@types/use-sync-external-store": "^0.0.4",
"@szhsin/react-menu": "^3.5.3", "@uidotdev/usehooks": "^2.3.1",
"@uidotdev/usehooks": "^2.4.1", "@void-cat/api": "^1.0.4",
"@void-cat/api": "^1.0.12",
"classnames": "^2.3.2",
"comlink": "^4.4.1",
"debug": "^4.3.4", "debug": "^4.3.4",
"dexie": "^3.2.4", "dexie": "^3.2.4",
"emojilib": "^3.0.10", "emojilib": "^3.0.10",
"eventemitter3": "^5.0.1",
"fuse.js": "^7.0.0",
"highlight.js": "^11.8.0", "highlight.js": "^11.8.0",
"latlon-geohash": "^2.0.0",
"light-bolt11-decoder": "^2.1.0", "light-bolt11-decoder": "^2.1.0",
"livekit-client": "^2.5.2",
"lottie-react": "^2.4.0",
"marked": "^9.1.0",
"marked-footnote": "^1.0.0",
"match-sorter": "^6.3.1", "match-sorter": "^6.3.1",
"qr-code-styling": "^1.6.0-rc.1", "qr-code-styling": "^1.6.0-rc.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-force-graph-3d": "^1.24.0",
"react-intersection-observer": "^9.4.1", "react-intersection-observer": "^9.4.1",
"react-intl": "^6.4.4", "react-intl": "^6.4.4",
"react-router-dom": "^6.5.0", "react-router-dom": "^6.5.0",
"react-tag-input-component": "^2.0.2",
"react-textarea-autosize": "^8.4.0", "react-textarea-autosize": "^8.4.0",
"react-twitter-embed": "^4.0.4",
"recharts": "^2.8.0", "recharts": "^2.8.0",
"three": "^0.157.0",
"tslib": "^2.7.0",
"typescript-lru-cache": "^2.0.0",
"use-long-press": "^3.2.0", "use-long-press": "^3.2.0",
"use-sync-external-store": "^1.2.0", "use-sync-external-store": "^1.2.0",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"workbox-cacheable-response": "^7.0.0",
"workbox-core": "^6.4.2", "workbox-core": "^6.4.2",
"workbox-expiration": "^7.0.0",
"workbox-precaching": "^7.0.0", "workbox-precaching": "^7.0.0",
"workbox-routing": "^6.4.2", "workbox-routing": "^6.4.2",
"workbox-strategies": "^6.4.2" "workbox-strategies": "^6.4.2"
}, },
"scripts": { "scripts": {
"start": "vite", "start": "webpack serve --node-env=development --mode=development",
"build": "yarn eslint --fix && vite build", "build": "webpack --node-env=production --mode=production",
"serve": "vite preview", "test": "jest --runInBand",
"test": "vitest run",
"test:watch": "vitest watch",
"intl-extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file src/lang.json --flatten true", "intl-extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file src/lang.json --flatten true",
"intl-compile": "formatjs compile src/lang.json --out-file src/translations/en.json", "intl-compile": "formatjs compile src/lang.json --out-file src/translations/en.json",
"eslint": "eslint .", "eslint": "eslint ."
"deploy:meku": "NODE_CONFIG_ENV=meku yarn build && npx wrangler pages deploy --project-name meku build/",
"deploy:notestr": "NODE_CONFIG_ENV=nostr yarn build && npx wrangler pages deploy --project-name nostr-generic build/"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
@ -91,46 +69,50 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.22.9",
"@babel/plugin-syntax-import-assertions": "^7.20.0",
"@babel/preset-env": "^7.21.5",
"@babel/preset-react": "^7.18.6",
"@babel/runtime": "^7.22.6",
"@formatjs/cli": "^6.1.3", "@formatjs/cli": "^6.1.3",
"@types/config": "^3.3.3", "@formatjs/ts-transformer": "^3.13.3",
"@jest/globals": "^29.6.1",
"@types/debug": "^4.1.8", "@types/debug": "^4.1.8",
"@types/latlon-geohash": "^2.0.3", "@types/jest": "^29.5.1",
"@types/node": "^20.4.1", "@types/node": "^20.4.1",
"@types/react": "^18.0.26", "@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10", "@types/react-dom": "^18.0.10",
"@types/three": "^0.157.2",
"@types/use-sync-external-store": "^0.0.4",
"@types/uuid": "^9.0.2", "@types/uuid": "^9.0.2",
"@types/webscopeio__react-textarea-autocomplete": "^4.7.2", "@types/webscopeio__react-textarea-autocomplete": "^4.7.2",
"@types/webtorrent": "^0.109.3", "@types/webtorrent": "^0.109.3",
"@typescript-eslint/eslint-plugin": "^6.1.0", "@typescript-eslint/eslint-plugin": "^6.1.0",
"@typescript-eslint/parser": "^6.1.0", "@typescript-eslint/parser": "^6.1.0",
"@vitejs/plugin-react": "^4.2.0", "@webbtc/webln-types": "^1.0.10",
"@webbtc/webln-types": "^3.0.0", "@webpack-cli/generators": "^3.0.4",
"@webscopeio/react-textarea-autocomplete": "^4.9.2", "@webscopeio/react-textarea-autocomplete": "^4.9.2",
"@welldone-software/why-did-you-render": "^8.0.1", "babel-loader": "^9.1.3",
"autoprefixer": "^10.4.16",
"babel-plugin-formatjs": "^10.5.14",
"config": "^3.3.9", "config": "^3.3.9",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.7.3",
"css-minimizer-webpack-plugin": "^5.0.0",
"eslint": "^8.48.0", "eslint": "^8.48.0",
"eslint-config-react-app": "^7.0.1", "eslint-webpack-plugin": "^4.0.1",
"eslint-plugin-formatjs": "^4.11.3", "html-webpack-plugin": "^5.5.1",
"eslint-plugin-react": "^7.33.2", "jest": "^29.5.0",
"eslint-plugin-react-hooks": "^4.6.0", "jest-environment-jsdom": "^29.5.0",
"eslint-plugin-react-refresh": "^0.4.5", "mini-css-extract-plugin": "^2.7.5",
"eslint-plugin-simple-import-sort": "^10.0.0",
"postcss": "^8.4.31",
"postcss-preset-env": "^9.2.0",
"prettier": "2.8.3", "prettier": "2.8.3",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"rollup-plugin-visualizer": "^5.9.2", "source-map-loader": "^4.0.1",
"tailwindcss": "^3.3.3", "terser-webpack-plugin": "^5.3.9",
"tinybench": "^2.5.1", "tinybench": "^2.5.1",
"ts-jest": "^29.1.1",
"ts-loader": "^9.4.4",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^5.2.8", "webpack": "^5.88.2",
"vite-plugin-eslint": "^1.8.1", "webpack-bundle-analyzer": "^4.8.0",
"vite-plugin-pwa": "^0.19.2", "webpack-cli": "^5.1.4",
"vite-plugin-version-mark": "^0.0.10", "webpack-dev-server": "^4.15.1",
"vitest": "^0.34.6" "workbox-webpack-plugin": "^6.5.4"
} }
} }

View File

@ -1,3 +0,0 @@
module.exports = {
plugins: [require("tailwindcss"), require("autoprefixer")],
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -9,20 +9,23 @@
<symbol id="arrowUp" viewBox="0 0 12 12" fill="none"> <symbol id="arrowUp" viewBox="0 0 12 12" fill="none">
<path d="M5.99992 10.6673V1.33398M5.99992 1.33398L1.33325 6.00065M5.99992 1.33398L10.6666 6.00065" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round" /> <path d="M5.99992 10.6673V1.33398M5.99992 1.33398L1.33325 6.00065M5.99992 1.33398L10.6666 6.00065" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round" />
</symbol> </symbol>
<symbol id="attachment" viewBox="0 0 24 25" fill="none"> <symbol id="attachment" viewBox="0 0 21 22" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.1667 3.5C8.69391 3.5 7.5 4.69391 7.5 6.16667V17C7.5 19.4853 9.51472 21.5 12 21.5C14.4853 21.5 16.5 19.4853 16.5 17V5.75581C16.5 5.20353 16.9477 4.75581 17.5 4.75581C18.0523 4.75581 18.5 5.20353 18.5 5.75581V17C18.5 20.5899 15.5899 23.5 12 23.5C8.41015 23.5 5.5 20.5899 5.5 17V6.16667C5.5 3.58934 7.58934 1.5 10.1667 1.5C12.744 1.5 14.8333 3.58934 14.8333 6.16667V16.9457C14.8333 18.5105 13.5648 19.7791 12 19.7791C10.4352 19.7791 9.16667 18.5105 9.16667 16.9457V7.15116C9.16667 6.59888 9.61438 6.15116 10.1667 6.15116C10.719 6.15116 11.1667 6.59888 11.1667 7.15116V16.9457C11.1667 17.406 11.5398 17.7791 12 17.7791C12.4602 17.7791 12.8333 17.406 12.8333 16.9457V6.16667C12.8333 4.69391 11.6394 3.5 10.1667 3.5Z" fill="currentColor"/> <path d="M19.1525 9.89945L10.1369 18.9151C8.08662 20.9653 4.7625 20.9653 2.71225 18.9151C0.661997 16.8648 0.661998 13.5407 2.71225 11.4904L11.7279 2.47483C13.0947 1.108 15.3108 1.108 16.6776 2.47483C18.0444 3.84167 18.0444 6.05775 16.6776 7.42458L8.01555 16.0866C7.33213 16.7701 6.22409 16.7701 5.54068 16.0866C4.85726 15.4032 4.85726 14.2952 5.54068 13.6118L13.1421 6.01037" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</symbol> </symbol>
<symbol id="badge" viewBox="0 0 16 15" fill="none"> <symbol id="badge" viewBox="0 0 16 15" fill="none">
<path d="M6.00004 7.50065L7.33337 8.83398L10.3334 5.83398M11.9342 2.83299C12.0714 3.16501 12.3349 3.42892 12.6667 3.5667L13.8302 4.04864C14.1622 4.18617 14.426 4.44998 14.5636 4.78202C14.7011 5.11407 14.7011 5.48715 14.5636 5.81919L14.082 6.98185C13.9444 7.31404 13.9442 7.6875 14.0824 8.01953L14.5632 9.18185C14.6313 9.34631 14.6664 9.52259 14.6665 9.70062C14.6665 9.87865 14.6315 10.0549 14.5633 10.2194C14.4952 10.3839 14.3953 10.5333 14.2694 10.6592C14.1435 10.7851 13.9941 10.8849 13.8296 10.953L12.6669 11.4346C12.3349 11.5718 12.071 11.8354 11.9333 12.1672L11.4513 13.3307C11.3138 13.6627 11.05 13.9265 10.718 14.0641C10.3859 14.2016 10.0129 14.2016 9.68085 14.0641L8.51823 13.5825C8.18619 13.4453 7.81326 13.4455 7.48143 13.5832L6.31797 14.0645C5.98612 14.2017 5.61338 14.2016 5.28162 14.0642C4.94986 13.9267 4.68621 13.6632 4.54858 13.3316L4.06652 12.1677C3.92924 11.8357 3.66574 11.5718 3.33394 11.434L2.17048 10.9521C1.8386 10.8146 1.57488 10.5509 1.4373 10.2191C1.29971 9.88724 1.29953 9.51434 1.43678 9.18235L1.91835 8.01968C2.05554 7.68763 2.05526 7.31469 1.91757 6.98284L1.43669 5.81851C1.36851 5.65405 1.3334 5.47777 1.33337 5.29974C1.33335 5.12171 1.3684 4.94542 1.43652 4.78094C1.50465 4.61646 1.60452 4.46702 1.73042 4.34115C1.85632 4.21529 2.00579 4.11546 2.17028 4.04739L3.33291 3.5658C3.66462 3.42863 3.92836 3.16545 4.06624 2.83402L4.54816 1.67052C4.68569 1.33848 4.94949 1.07467 5.28152 0.937137C5.61355 0.7996 5.98662 0.7996 6.31865 0.937137L7.48127 1.41873C7.81331 1.55593 8.18624 1.55565 8.51808 1.41795L9.68202 0.937884C10.014 0.800424 10.387 0.800452 10.719 0.937962C11.0509 1.07547 11.3147 1.3392 11.4522 1.67116L11.9343 2.835L11.9342 2.83299Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round" /> <path d="M6.00004 7.50065L7.33337 8.83398L10.3334 5.83398M11.9342 2.83299C12.0714 3.16501 12.3349 3.42892 12.6667 3.5667L13.8302 4.04864C14.1622 4.18617 14.426 4.44998 14.5636 4.78202C14.7011 5.11407 14.7011 5.48715 14.5636 5.81919L14.082 6.98185C13.9444 7.31404 13.9442 7.6875 14.0824 8.01953L14.5632 9.18185C14.6313 9.34631 14.6664 9.52259 14.6665 9.70062C14.6665 9.87865 14.6315 10.0549 14.5633 10.2194C14.4952 10.3839 14.3953 10.5333 14.2694 10.6592C14.1435 10.7851 13.9941 10.8849 13.8296 10.953L12.6669 11.4346C12.3349 11.5718 12.071 11.8354 11.9333 12.1672L11.4513 13.3307C11.3138 13.6627 11.05 13.9265 10.718 14.0641C10.3859 14.2016 10.0129 14.2016 9.68085 14.0641L8.51823 13.5825C8.18619 13.4453 7.81326 13.4455 7.48143 13.5832L6.31797 14.0645C5.98612 14.2017 5.61338 14.2016 5.28162 14.0642C4.94986 13.9267 4.68621 13.6632 4.54858 13.3316L4.06652 12.1677C3.92924 11.8357 3.66574 11.5718 3.33394 11.434L2.17048 10.9521C1.8386 10.8146 1.57488 10.5509 1.4373 10.2191C1.29971 9.88724 1.29953 9.51434 1.43678 9.18235L1.91835 8.01968C2.05554 7.68763 2.05526 7.31469 1.91757 6.98284L1.43669 5.81851C1.36851 5.65405 1.3334 5.47777 1.33337 5.29974C1.33335 5.12171 1.3684 4.94542 1.43652 4.78094C1.50465 4.61646 1.60452 4.46702 1.73042 4.34115C1.85632 4.21529 2.00579 4.11546 2.17028 4.04739L3.33291 3.5658C3.66462 3.42863 3.92836 3.16545 4.06624 2.83402L4.54816 1.67052C4.68569 1.33848 4.94949 1.07467 5.28152 0.937137C5.61355 0.7996 5.98662 0.7996 6.31865 0.937137L7.48127 1.41873C7.81331 1.55593 8.18624 1.55565 8.51808 1.41795L9.68202 0.937884C10.014 0.800424 10.387 0.800452 10.719 0.937962C11.0509 1.07547 11.3147 1.3392 11.4522 1.67116L11.9343 2.835L11.9342 2.83299Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round" />
</symbol> </symbol>
<symbol id="bell" viewBox="0 0 20 23" fill="none">
<path d="M7.35419 20.5C8.05933 21.1224 8.98557 21.5 10 21.5C11.0145 21.5 11.9407 21.1224 12.6458 20.5M16 7.5C16 5.9087 15.3679 4.38258 14.2427 3.25736C13.1174 2.13214 11.5913 1.5 10 1.5C8.40872 1.5 6.8826 2.13214 5.75738 3.25736C4.63216 4.38258 4.00002 5.9087 4.00002 7.5C4.00002 10.5902 3.22049 12.706 2.34968 14.1054C1.61515 15.2859 1.24788 15.8761 1.26134 16.0408C1.27626 16.2231 1.31488 16.2926 1.46179 16.4016C1.59448 16.5 2.19261 16.5 3.38887 16.5H16.6112C17.8074 16.5 18.4056 16.5 18.5382 16.4016C18.6852 16.2926 18.7238 16.2231 18.7387 16.0408C18.7522 15.8761 18.3849 15.2859 17.6504 14.1054C16.7795 12.706 16 10.5902 16 7.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</symbol>
<symbol id="block" viewBox="0 0 20 20" fill="none"> <symbol id="block" viewBox="0 0 20 20" fill="none">
<path d="M4.10829 4.10768L15.8916 15.891M18.3333 9.99935C18.3333 14.6017 14.6023 18.3327 9.99996 18.3327C5.39759 18.3327 1.66663 14.6017 1.66663 9.99935C1.66663 5.39698 5.39759 1.66602 9.99996 1.66602C14.6023 1.66602 18.3333 5.39698 18.3333 9.99935Z" stroke="currentColor" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round" /> <path d="M4.10829 4.10768L15.8916 15.891M18.3333 9.99935C18.3333 14.6017 14.6023 18.3327 9.99996 18.3327C5.39759 18.3327 1.66663 14.6017 1.66663 9.99935C1.66663 5.39698 5.39759 1.66602 9.99996 1.66602C14.6023 1.66602 18.3333 5.39698 18.3333 9.99935Z" stroke="currentColor" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round" />
</symbol> </symbol>
<symbol id="bookmark" viewBox="0 0 12 14" fill="none"> <symbol id="bookmark" viewBox="0 0 12 14" fill="none">
<path d="M1.3335 4.2C1.3335 3.0799 1.3335 2.51984 1.55148 2.09202C1.74323 1.71569 2.04919 1.40973 2.42552 1.21799C2.85334 1 3.41339 1 4.5335 1H7.46683C8.58693 1 9.14699 1 9.57481 1.21799C9.95114 1.40973 10.2571 1.71569 10.4488 2.09202C10.6668 2.51984 10.6668 3.0799 10.6668 4.2V13L6.00016 10.3333L1.3335 13V4.2Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round" /> <path d="M1.3335 4.2C1.3335 3.0799 1.3335 2.51984 1.55148 2.09202C1.74323 1.71569 2.04919 1.40973 2.42552 1.21799C2.85334 1 3.41339 1 4.5335 1H7.46683C8.58693 1 9.14699 1 9.57481 1.21799C9.95114 1.40973 10.2571 1.71569 10.4488 2.09202C10.6668 2.51984 10.6668 3.0799 10.6668 4.2V13L6.00016 10.3333L1.3335 13V4.2Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round" />
</symbol> </symbol>
<symbol id="check" viewBox="0 0 24 25" fill="none"> <symbol id="check" viewBox="0 0 18 13" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.7071 5.79289C21.0976 6.18342 21.0976 6.81658 20.7071 7.20711L9.70711 18.2071C9.31658 18.5976 8.68342 18.5976 8.29289 18.2071L3.29289 13.2071C2.90237 12.8166 2.90237 12.1834 3.29289 11.7929C3.68342 11.4024 4.31658 11.4024 4.70711 11.7929L9 16.0858L19.2929 5.79289C19.6834 5.40237 20.3166 5.40237 20.7071 5.79289Z" fill="currentColor"/> <path d="M17 1L6 12L1 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</symbol> </symbol>
<symbol id="chevronDown" viewBox="0 0 24 24" fill="none"> <symbol id="chevronDown" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.29289 8.29289C5.68342 7.90237 6.31658 7.90237 6.70711 8.29289L12 13.5858L17.2929 8.29289C17.6834 7.90237 18.3166 7.90237 18.7071 8.29289C19.0976 8.68342 19.0976 9.31658 18.7071 9.70711L12.7071 15.7071C12.3166 16.0976 11.6834 16.0976 11.2929 15.7071L5.29289 9.70711C4.90237 9.31658 4.90237 8.68342 5.29289 8.29289Z" fill="currentColor" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M5.29289 8.29289C5.68342 7.90237 6.31658 7.90237 6.70711 8.29289L12 13.5858L17.2929 8.29289C17.6834 7.90237 18.3166 7.90237 18.7071 8.29289C19.0976 8.68342 19.0976 9.31658 18.7071 9.70711L12.7071 15.7071C12.3166 16.0976 11.6834 16.0976 11.2929 15.7071L5.29289 9.70711C4.90237 9.31658 4.90237 8.68342 5.29289 8.29289Z" fill="currentColor" />
@ -39,14 +42,9 @@
<symbol id="zapCircle" viewBox="0 0 33 32" fill="none"> <symbol id="zapCircle" viewBox="0 0 33 32" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5 1.33301C8.39986 1.33301 1.83337 7.8995 1.83337 15.9997C1.83337 24.0999 8.39986 30.6663 16.5 30.6663C24.6002 30.6663 31.1667 24.0999 31.1667 15.9997C31.1667 7.8995 24.6002 1.33301 16.5 1.33301ZM10.3155 16.3287L16.5 7.33301V13.9997H21.8056C22.4627 13.9997 22.7913 13.9997 22.9705 14.1364C23.1265 14.2555 23.2221 14.4372 23.2318 14.6333C23.243 14.8583 23.0569 15.1291 22.6845 15.6706L16.5 24.6663V17.9997H11.1944C10.5373 17.9997 10.2087 17.9997 10.0295 17.863C9.87353 17.7439 9.77791 17.5621 9.76818 17.3661C9.75699 17.141 9.94315 16.8702 10.3155 16.3287Z" fill="currentColor" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M16.5 1.33301C8.39986 1.33301 1.83337 7.8995 1.83337 15.9997C1.83337 24.0999 8.39986 30.6663 16.5 30.6663C24.6002 30.6663 31.1667 24.0999 31.1667 15.9997C31.1667 7.8995 24.6002 1.33301 16.5 1.33301ZM10.3155 16.3287L16.5 7.33301V13.9997H21.8056C22.4627 13.9997 22.7913 13.9997 22.9705 14.1364C23.1265 14.2555 23.2221 14.4372 23.2318 14.6333C23.243 14.8583 23.0569 15.1291 22.6845 15.6706L16.5 24.6663V17.9997H11.1944C10.5373 17.9997 10.2087 17.9997 10.0295 17.863C9.87353 17.7439 9.77791 17.5621 9.76818 17.3661C9.75699 17.141 9.94315 16.8702 10.3155 16.3287Z" fill="currentColor" />
</symbol> </symbol>
<symbol id="search" viewBox="0 0 20 21" fill="none">
<symbol id="search-outline" viewBox="0 0 20 21" fill="none">
<path d="M19 19.5L14.65 15.15M17 9.5C17 13.9183 13.4183 17.5 9 17.5C4.58172 17.5 1 13.9183 1 9.5C1 5.08172 4.58172 1.5 9 1.5C13.4183 1.5 17 5.08172 17 9.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> <path d="M19 19.5L14.65 15.15M17 9.5C17 13.9183 13.4183 17.5 9 17.5C4.58172 17.5 1 13.9183 1 9.5C1 5.08172 4.58172 1.5 9 1.5C13.4183 1.5 17 5.08172 17 9.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</symbol> </symbol>
<symbol id="search-solid" viewBox="0 0 20 21" fill="none">
<path d="M19 19.5L14.65 15.15M17 9.5C17 13.9183 13.4183 17.5 9 17.5C4.58172 17.5 1 13.9183 1 9.5C1 5.08172 4.58172 1.5 9 1.5C13.4183 1.5 17 5.08172 17 9.5Z" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
</symbol>
<symbol id="envelope" viewBox="0 0 22 19" fill="none"> <symbol id="envelope" viewBox="0 0 22 19" fill="none">
<path d="M1 4.5L9.16492 10.2154C9.82609 10.6783 10.1567 10.9097 10.5163 10.9993C10.8339 11.0785 11.1661 11.0785 11.4837 10.9993C11.8433 10.9097 12.1739 10.6783 12.8351 10.2154L21 4.5M5.8 17.5H16.2C17.8802 17.5 18.7202 17.5 19.362 17.173C19.9265 16.8854 20.3854 16.4265 20.673 15.862C21 15.2202 21 14.3802 21 12.7V6.3C21 4.61984 21 3.77976 20.673 3.13803C20.3854 2.57354 19.9265 2.1146 19.362 1.82698C18.7202 1.5 17.8802 1.5 16.2 1.5H5.8C4.11984 1.5 3.27976 1.5 2.63803 1.82698C2.07354 2.1146 1.6146 2.57354 1.32698 3.13803C1 3.77976 1 4.61984 1 6.3V12.7C1 14.3802 1 15.2202 1.32698 15.862C1.6146 16.4265 2.07354 16.8854 2.63803 17.173C3.27976 17.5 4.11984 17.5 5.8 17.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> <path d="M1 4.5L9.16492 10.2154C9.82609 10.6783 10.1567 10.9097 10.5163 10.9993C10.8339 11.0785 11.1661 11.0785 11.4837 10.9993C11.8433 10.9097 12.1739 10.6783 12.8351 10.2154L21 4.5M5.8 17.5H16.2C17.8802 17.5 18.7202 17.5 19.362 17.173C19.9265 16.8854 20.3854 16.4265 20.673 15.862C21 15.2202 21 14.3802 21 12.7V6.3C21 4.61984 21 3.77976 20.673 3.13803C20.3854 2.57354 19.9265 2.1146 19.362 1.82698C18.7202 1.5 17.8802 1.5 16.2 1.5H5.8C4.11984 1.5 3.27976 1.5 2.63803 1.82698C2.07354 2.1146 1.6146 2.57354 1.32698 3.13803C1 3.77976 1 4.61984 1 6.3V12.7C1 14.3802 1 15.2202 1.32698 15.862C1.6146 16.4265 2.07354 16.8854 2.63803 17.173C3.27976 17.5 4.11984 17.5 5.8 17.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</symbol> </symbol>
@ -132,8 +130,8 @@
<symbol id="pie-chart" viewBox="0 0 22 22" fill="none"> <symbol id="pie-chart" viewBox="0 0 22 22" fill="none">
<path d="M20.2104 14.8901C19.5742 16.3946 18.5792 17.7203 17.3123 18.7514C16.0454 19.7825 14.5452 20.4875 12.9428 20.8048C11.3405 21.1222 9.68483 21.0422 8.12055 20.5719C6.55627 20.1015 5.13103 19.2551 3.96942 18.1067C2.80782 16.9583 1.94522 15.5428 1.45704 13.984C0.968859 12.4252 0.869965 10.7706 1.169 9.1647C1.46804 7.55885 2.1559 6.0507 3.17245 4.7721C4.189 3.4935 5.50329 2.48339 7.0004 1.83007M20.2392 7.17323C20.6395 8.1397 20.8851 9.16143 20.9684 10.2009C20.989 10.4577 20.9993 10.5861 20.9483 10.7018C20.9057 10.7984 20.8213 10.8898 20.7284 10.94C20.6172 11.0001 20.4783 11.0001 20.2004 11.0001H11.8004C11.5204 11.0001 11.3804 11.0001 11.2734 10.9456C11.1793 10.8976 11.1028 10.8211 11.0549 10.7271C11.0004 10.6201 11.0004 10.4801 11.0004 10.2001V1.80007C11.0004 1.5222 11.0004 1.38327 11.0605 1.27205C11.1107 1.17915 11.2021 1.09476 11.2987 1.05216C11.4144 1.00117 11.5428 1.01146 11.7996 1.03205C12.839 1.11539 13.8608 1.36095 14.8272 1.76127C16.0405 2.26382 17.1429 3.00042 18.0715 3.929C19.0001 4.85759 19.7367 5.95998 20.2392 7.17323Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> <path d="M20.2104 14.8901C19.5742 16.3946 18.5792 17.7203 17.3123 18.7514C16.0454 19.7825 14.5452 20.4875 12.9428 20.8048C11.3405 21.1222 9.68483 21.0422 8.12055 20.5719C6.55627 20.1015 5.13103 19.2551 3.96942 18.1067C2.80782 16.9583 1.94522 15.5428 1.45704 13.984C0.968859 12.4252 0.869965 10.7706 1.169 9.1647C1.46804 7.55885 2.1559 6.0507 3.17245 4.7721C4.189 3.4935 5.50329 2.48339 7.0004 1.83007M20.2392 7.17323C20.6395 8.1397 20.8851 9.16143 20.9684 10.2009C20.989 10.4577 20.9993 10.5861 20.9483 10.7018C20.9057 10.7984 20.8213 10.8898 20.7284 10.94C20.6172 11.0001 20.4783 11.0001 20.2004 11.0001H11.8004C11.5204 11.0001 11.3804 11.0001 11.2734 10.9456C11.1793 10.8976 11.1028 10.8211 11.0549 10.7271C11.0004 10.6201 11.0004 10.4801 11.0004 10.2001V1.80007C11.0004 1.5222 11.0004 1.38327 11.0605 1.27205C11.1107 1.17915 11.2021 1.09476 11.2987 1.05216C11.4144 1.00117 11.5428 1.01146 11.7996 1.03205C12.839 1.11539 13.8608 1.36095 14.8272 1.76127C16.0405 2.26382 17.1429 3.00042 18.0715 3.929C19.0001 4.85759 19.7367 5.95998 20.2392 7.17323Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</symbol> </symbol>
<symbol id="diamond" viewBox="0 0 20 20" fill="none"> <symbol id="diamond" viewBox="0 0 22 20" fill="none">
<path d="M2.08295 7.5H17.9163M8.33295 2.5L6.66628 7.5L9.99961 17.0833L13.3329 7.5L11.6663 2.5M10.5118 16.8854L17.9773 7.92679C18.1038 7.77496 18.1671 7.69905 18.1912 7.6143C18.2126 7.53959 18.2126 7.46041 18.1912 7.38569C18.1671 7.30095 18.1038 7.22504 17.9773 7.07321L14.3662 2.73988C14.2927 2.6517 14.256 2.60762 14.2109 2.57592C14.171 2.54784 14.1265 2.52698 14.0794 2.51431C14.0262 2.5 13.9688 2.5 13.854 2.5H6.1452C6.03042 2.5 5.97303 2.5 5.91985 2.51431C5.87273 2.52698 5.82821 2.54784 5.7883 2.57592C5.74327 2.60762 5.70653 2.6517 5.63305 2.73988L2.02194 7.07321C1.89541 7.22504 1.83215 7.30095 1.80798 7.38569C1.78666 7.46041 1.78666 7.53959 1.80798 7.6143C1.83215 7.69904 1.89541 7.77496 2.02194 7.92679L9.48747 16.8854C9.66335 17.0965 9.75129 17.202 9.85657 17.2405C9.94894 17.2743 10.0503 17.2743 10.1427 17.2405C10.2479 17.202 10.3359 17.0965 10.5118 16.8854Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M1.49954 7H20.4995M8.99954 1L6.99954 7L10.9995 18.5L14.9995 7L12.9995 1M11.6141 18.2625L20.5727 7.51215C20.7246 7.32995 20.8005 7.23885 20.8295 7.13717C20.8551 7.04751 20.8551 6.95249 20.8295 6.86283C20.8005 6.76114 20.7246 6.67005 20.5727 6.48785L16.2394 1.28785C16.1512 1.18204 16.1072 1.12914 16.0531 1.09111C16.0052 1.05741 15.9518 1.03238 15.8953 1.01717C15.8314 1 15.7626 1 15.6248 1H6.37424C6.2365 1 6.16764 1 6.10382 1.01717C6.04728 1.03238 5.99385 1.05741 5.94596 1.09111C5.89192 1.12914 5.84783 1.18204 5.75966 1.28785L1.42633 6.48785C1.2745 6.67004 1.19858 6.76114 1.16957 6.86283C1.144 6.95249 1.144 7.04751 1.16957 7.13716C1.19858 7.23885 1.2745 7.32995 1.42633 7.51215L10.385 18.2625C10.596 18.5158 10.7015 18.6424 10.8279 18.6886C10.9387 18.7291 11.0603 18.7291 11.1712 18.6886C11.2975 18.6424 11.4031 18.5158 11.6141 18.2625Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</symbol> </symbol>
<symbol id="wallet" viewBox="0 0 22 18" fill="none"> <symbol id="wallet" viewBox="0 0 22 18" fill="none">
<path d="M19 6.5V4.2C19 3.0799 19 2.51984 18.782 2.09202C18.5903 1.7157 18.2843 1.40974 17.908 1.21799C17.4802 1 16.9201 1 15.8 1H4.2C3.0799 1 2.51984 1 2.09202 1.21799C1.7157 1.40973 1.40973 1.71569 1.21799 2.09202C1 2.51984 1 3.0799 1 4.2V13.8C1 14.9201 1 15.4802 1.21799 15.908C1.40973 16.2843 1.71569 16.5903 2.09202 16.782C2.51984 17 3.07989 17 4.2 17L15.8 17C16.9201 17 17.4802 17 17.908 16.782C18.2843 16.5903 18.5903 16.2843 18.782 15.908C19 15.4802 19 14.9201 19 13.8V11.5M14 9C14 8.53535 14 8.30302 14.0384 8.10982C14.1962 7.31644 14.8164 6.69624 15.6098 6.53843C15.803 6.5 16.0353 6.5 16.5 6.5H18.5C18.9647 6.5 19.197 6.5 19.3902 6.53843C20.1836 6.69624 20.8038 7.31644 20.9616 8.10982C21 8.30302 21 8.53535 21 9C21 9.46466 21 9.69698 20.9616 9.89018C20.8038 10.6836 20.1836 11.3038 19.3902 11.4616C19.197 11.5 18.9647 11.5 18.5 11.5H16.5C16.0353 11.5 15.803 11.5 15.6098 11.4616C14.8164 11.3038 14.1962 10.6836 14.0384 9.89018C14 9.69698 14 9.46465 14 9Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> <path d="M19 6.5V4.2C19 3.0799 19 2.51984 18.782 2.09202C18.5903 1.7157 18.2843 1.40974 17.908 1.21799C17.4802 1 16.9201 1 15.8 1H4.2C3.0799 1 2.51984 1 2.09202 1.21799C1.7157 1.40973 1.40973 1.71569 1.21799 2.09202C1 2.51984 1 3.0799 1 4.2V13.8C1 14.9201 1 15.4802 1.21799 15.908C1.40973 16.2843 1.71569 16.5903 2.09202 16.782C2.51984 17 3.07989 17 4.2 17L15.8 17C16.9201 17 17.4802 17 17.908 16.782C18.2843 16.5903 18.5903 16.2843 18.782 15.908C19 15.4802 19 14.9201 19 13.8V11.5M14 9C14 8.53535 14 8.30302 14.0384 8.10982C14.1962 7.31644 14.8164 6.69624 15.6098 6.53843C15.803 6.5 16.0353 6.5 16.5 6.5H18.5C18.9647 6.5 19.197 6.5 19.3902 6.53843C20.1836 6.69624 20.8038 7.31644 20.9616 8.10982C21 8.30302 21 8.53535 21 9C21 9.46466 21 9.69698 20.9616 9.89018C20.8038 10.6836 20.1836 11.3038 19.3902 11.4616C19.197 11.5 18.9647 11.5 18.5 11.5H16.5C16.0353 11.5 15.803 11.5 15.6098 11.4616C14.8164 11.3038 14.1962 10.6836 14.0384 9.89018C14 9.69698 14 9.46465 14 9Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
@ -172,23 +170,17 @@
<path d="M12 16.5H12.01M22.8064 5.70076C19.9595 3.09199 16.1656 1.5 11.9999 1.5C7.83414 1.5 4.04023 3.09199 1.19336 5.70076M4.73193 9.24297C6.67006 7.53566 9.21407 6.5 12 6.5C14.7859 6.5 17.3299 7.53566 19.268 9.24297M15.6983 12.7751C14.6792 11.9763 13.3952 11.5 11.9999 11.5C10.5835 11.5 9.28172 11.9908 8.25537 12.8116" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M12 16.5H12.01M22.8064 5.70076C19.9595 3.09199 16.1656 1.5 11.9999 1.5C7.83414 1.5 4.04023 3.09199 1.19336 5.70076M4.73193 9.24297C6.67006 7.53566 9.21407 6.5 12 6.5C14.7859 6.5 17.3299 7.53566 19.268 9.24297M15.6983 12.7751C14.6792 11.9763 13.3952 11.5 11.9999 11.5C10.5835 11.5 9.28172 11.9908 8.25537 12.8116" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol> </symbol>
<!-- V2 --> <!-- V2 -->
<symbol id="mail" viewBox="0 0 24 24" fill="none">
<symbol id="mail-solid" viewBox="0 0 24 24" fill="none">
<g> <g>
<path d="M2.13352 8.18144C1.83359 7.9672 1.68363 7.86008 1.55288 7.84617C1.35735 7.82537 1.16139 7.92622 1.06467 8.09741C0.999992 8.21189 0.999995 8.39416 1 8.75869V15.2413C0.999988 16.0463 0.999978 16.7106 1.04419 17.2518C1.09012 17.8139 1.18868 18.3306 1.43598 18.816C1.81947 19.5686 2.43139 20.1805 3.18404 20.564C3.66937 20.8113 4.18608 20.9099 4.74818 20.9558C5.28937 21 5.95372 21 6.75868 21H17.2413C18.0463 21 18.7106 21 19.2518 20.9558C19.8139 20.9099 20.3306 20.8113 20.816 20.564C21.5686 20.1805 22.1805 19.5686 22.564 18.816C22.8113 18.3306 22.9099 17.8139 22.9558 17.2518C23 16.7106 23 16.0463 23 15.2413V8.75868C23 8.58001 23 8.49068 22.9836 8.42829C22.9096 8.14627 22.603 7.98561 22.329 8.08531C22.2684 8.10737 22.1941 8.15886 22.0453 8.26184L14.3032 13.6219C13.7542 14.0032 13.2722 14.3379 12.7247 14.4706C12.2458 14.5867 11.7456 14.583 11.2685 14.4599C10.7229 14.3191 10.2459 13.9774 9.70265 13.5881L2.13352 8.18144Z" fill="currentColor"/> <path d="M2.13352 8.18144C1.83359 7.9672 1.68363 7.86008 1.55288 7.84617C1.35735 7.82537 1.16139 7.92622 1.06467 8.09741C0.999992 8.21189 0.999995 8.39416 1 8.75869V15.2413C0.999988 16.0463 0.999978 16.7106 1.04419 17.2518C1.09012 17.8139 1.18868 18.3306 1.43598 18.816C1.81947 19.5686 2.43139 20.1805 3.18404 20.564C3.66937 20.8113 4.18608 20.9099 4.74818 20.9558C5.28937 21 5.95372 21 6.75868 21H17.2413C18.0463 21 18.7106 21 19.2518 20.9558C19.8139 20.9099 20.3306 20.8113 20.816 20.564C21.5686 20.1805 22.1805 19.5686 22.564 18.816C22.8113 18.3306 22.9099 17.8139 22.9558 17.2518C23 16.7106 23 16.0463 23 15.2413V8.75868C23 8.58001 23 8.49068 22.9836 8.42829C22.9096 8.14627 22.603 7.98561 22.329 8.08531C22.2684 8.10737 22.1941 8.15886 22.0453 8.26184L14.3032 13.6219C13.7542 14.0032 13.2722 14.3379 12.7247 14.4706C12.2458 14.5867 11.7456 14.583 11.2685 14.4599C10.7229 14.3191 10.2459 13.9774 9.70265 13.5881L2.13352 8.18144Z" fill="currentColor"/>
<path d="M22.1328 5.76872C22.3174 5.64092 22.4097 5.57702 22.4664 5.47424C22.5104 5.39443 22.5356 5.26804 22.5255 5.17745C22.5125 5.06079 22.463 4.98377 22.3638 4.82973C21.9839 4.23964 21.4373 3.75256 20.816 3.43598C20.3306 3.18868 19.8139 3.09012 19.2518 3.04419C18.7106 2.99998 18.0463 2.99999 17.2413 3H6.7587C5.95374 2.99999 5.28937 2.99998 4.74818 3.04419C4.18608 3.09012 3.66937 3.18868 3.18404 3.43598C2.62501 3.72082 2.1418 4.1326 1.77436 4.63335C1.65877 4.79089 1.60097 4.86966 1.58192 4.98921C1.56703 5.08269 1.58783 5.21257 1.63116 5.29674C1.68657 5.40436 1.78269 5.47302 1.97493 5.61033L10.75 11.8783C11.4773 12.3977 11.6316 12.4881 11.7681 12.5233C11.9272 12.5644 12.0939 12.5656 12.2535 12.5269C12.3906 12.4937 12.5463 12.4056 13.281 11.8969L22.1328 5.76872Z" fill="currentColor"/> <path d="M22.1328 5.76872C22.3174 5.64092 22.4097 5.57702 22.4664 5.47424C22.5104 5.39443 22.5356 5.26804 22.5255 5.17745C22.5125 5.06079 22.463 4.98377 22.3638 4.82973C21.9839 4.23964 21.4373 3.75256 20.816 3.43598C20.3306 3.18868 19.8139 3.09012 19.2518 3.04419C18.7106 2.99998 18.0463 2.99999 17.2413 3H6.7587C5.95374 2.99999 5.28937 2.99998 4.74818 3.04419C4.18608 3.09012 3.66937 3.18868 3.18404 3.43598C2.62501 3.72082 2.1418 4.1326 1.77436 4.63335C1.65877 4.79089 1.60097 4.86966 1.58192 4.98921C1.56703 5.08269 1.58783 5.21257 1.63116 5.29674C1.68657 5.40436 1.78269 5.47302 1.97493 5.61033L10.75 11.8783C11.4773 12.3977 11.6316 12.4881 11.7681 12.5233C11.9272 12.5644 12.0939 12.5656 12.2535 12.5269C12.3906 12.4937 12.5463 12.4056 13.281 11.8969L22.1328 5.76872Z" fill="currentColor"/>
</g> </g>
</symbol> </symbol>
<svg id="mail-outline" viewBox="0 0 20 16" fill="none"> <symbol id="bell-02" viewBox="0 0 24 24" fill="none">
<path d="M1.66663 3.8335L8.47073 8.59636C9.0217 8.98205 9.29719 9.17489 9.59685 9.24959C9.86154 9.31557 10.1384 9.31557 10.4031 9.24959C10.7027 9.17489 10.9782 8.98205 11.5292 8.59637L18.3333 3.8335M5.66663 14.6668H14.3333C15.7334 14.6668 16.4335 14.6668 16.9683 14.3943C17.4387 14.1547 17.8211 13.7722 18.0608 13.3018C18.3333 12.767 18.3333 12.067 18.3333 10.6668V5.3335C18.3333 3.93336 18.3333 3.2333 18.0608 2.69852C17.8211 2.22811 17.4387 1.84566 16.9683 1.60598C16.4335 1.3335 15.7334 1.3335 14.3333 1.3335H5.66663C4.26649 1.3335 3.56643 1.3335 3.03165 1.60598C2.56124 1.84566 2.17879 2.22811 1.93911 2.69852C1.66663 3.2333 1.66663 3.93336 1.66663 5.3335V10.6668C1.66663 12.067 1.66663 12.767 1.93911 13.3018C2.17879 13.7722 2.56124 14.1547 3.03165 14.3943C3.56643 14.6668 4.26649 14.6668 5.66663 14.6668Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <g>
</svg> <path fill-rule="evenodd" clip-rule="evenodd" d="M8.99974 21C8.99974 20.4477 9.44745 20 9.99974 20H13.9997C14.552 20 14.9997 20.4477 14.9997 21C14.9997 21.5523 14.552 22 13.9997 22H9.99974C9.44745 22 8.99974 21.5523 8.99974 21Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.04999 3.05025C8.36275 1.7375 10.1432 1 11.9997 1C13.8563 1 15.6367 1.7375 16.9495 3.05025C18.2622 4.36301 18.9997 6.14349 18.9997 8C18.9997 10.9127 19.7317 12.8439 20.4991 14.0771L20.511 14.0962C20.8683 14.6704 21.1507 15.1243 21.3411 15.4547C21.4366 15.6202 21.5235 15.7797 21.5879 15.9215C21.62 15.9922 21.6559 16.079 21.684 16.1733C21.7073 16.2515 21.7517 16.4187 21.7351 16.6223C21.7239 16.7591 21.696 16.9928 21.5618 17.2343C21.4277 17.4758 21.244 17.623 21.1337 17.7047C20.8834 17.8904 20.596 17.9329 20.5001 17.947L20.4957 17.9477C20.3482 17.9695 20.181 17.9804 20.0122 17.9869C19.677 18 19.2128 18 18.6356 18H5.36388C4.78666 18 4.32252 18 3.98726 17.9869C3.81851 17.9804 3.65123 17.9695 3.50382 17.9477L3.49939 17.947C3.40347 17.9329 3.11604 17.8904 2.86574 17.7047C2.7555 17.623 2.57178 17.4758 2.43762 17.2343C2.30347 16.9928 2.27558 16.7591 2.26439 16.6223C2.24774 16.4187 2.29214 16.2515 2.31545 16.1733C2.34354 16.079 2.37948 15.9922 2.41161 15.9215C2.47598 15.7797 2.56291 15.6202 2.65833 15.4547C2.84876 15.1243 3.13124 14.6703 3.48856 14.0961L3.50035 14.0771C4.26773 12.8439 4.99974 10.9127 4.99974 8C4.99974 6.14348 5.73724 4.36301 7.04999 3.05025Z" fill="currentColor"/>
<symbol id="bell-solid" viewBox="0 0 24 24" fill="none"> </g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.99974 21C8.99974 20.4477 9.44745 20 9.99974 20H13.9997C14.552 20 14.9997 20.4477 14.9997 21C14.9997 21.5523 14.552 22 13.9997 22H9.99974C9.44745 22 8.99974 21.5523 8.99974 21Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.04999 3.05025C8.36275 1.7375 10.1432 1 11.9997 1C13.8563 1 15.6367 1.7375 16.9495 3.05025C18.2622 4.36301 18.9997 6.14349 18.9997 8C18.9997 10.9127 19.7317 12.8439 20.4991 14.0771L20.511 14.0962C20.8683 14.6704 21.1507 15.1243 21.3411 15.4547C21.4366 15.6202 21.5235 15.7797 21.5879 15.9215C21.62 15.9922 21.6559 16.079 21.684 16.1733C21.7073 16.2515 21.7517 16.4187 21.7351 16.6223C21.7239 16.7591 21.696 16.9928 21.5618 17.2343C21.4277 17.4758 21.244 17.623 21.1337 17.7047C20.8834 17.8904 20.596 17.9329 20.5001 17.947L20.4957 17.9477C20.3482 17.9695 20.181 17.9804 20.0122 17.9869C19.677 18 19.2128 18 18.6356 18H5.36388C4.78666 18 4.32252 18 3.98726 17.9869C3.81851 17.9804 3.65123 17.9695 3.50382 17.9477L3.49939 17.947C3.40347 17.9329 3.11604 17.8904 2.86574 17.7047C2.7555 17.623 2.57178 17.4758 2.43762 17.2343C2.30347 16.9928 2.27558 16.7591 2.26439 16.6223C2.24774 16.4187 2.29214 16.2515 2.31545 16.1733C2.34354 16.079 2.37948 15.9922 2.41161 15.9215C2.47598 15.7797 2.56291 15.6202 2.65833 15.4547C2.84876 15.1243 3.13124 14.6703 3.48856 14.0961L3.50035 14.0771C4.26773 12.8439 4.99974 10.9127 4.99974 8C4.99974 6.14348 5.73724 4.36301 7.04999 3.05025Z" fill="currentColor"/>
</symbol>
<symbol id="bell-outline" viewBox="0 0 20 20" fill="none">
<path d="M11.6667 17.4998H8.33339M15.0001 6.6665C15.0001 5.34042 14.4733 4.06865 13.5356 3.13097C12.5979 2.19329 11.3261 1.6665 10.0001 1.6665C8.67397 1.6665 7.4022 2.19329 6.46452 3.13097C5.52684 4.06865 5.00006 5.34042 5.00006 6.6665C5.00006 9.24166 4.35045 11.0048 3.62478 12.171C3.01266 13.1547 2.7066 13.6466 2.71783 13.7838C2.73025 13.9357 2.76244 13.9937 2.88487 14.0845C2.99544 14.1665 3.49388 14.1665 4.49077 14.1665H15.5093C16.5062 14.1665 17.0047 14.1665 17.1152 14.0845C17.2377 13.9937 17.2699 13.9357 17.2823 13.7838C17.2935 13.6466 16.9875 13.1547 16.3753 12.171C15.6497 11.0048 15.0001 9.24166 15.0001 6.6665Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol> </symbol>
<symbol id="user-v2" viewBox="0 0 20 20" fill="none"> <symbol id="user-v2" viewBox="0 0 20 20" fill="none">
<g> <g>
@ -355,114 +347,6 @@
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.0453 1.07385C11.0306 0.603671 8.91602 0.82887 7.04549 1.71284C5.17495 2.5968 3.65845 4.08754 2.74257 5.94266C1.82669 7.79778 1.5653 9.90817 2.00091 11.9307C2.43651 13.9532 3.54348 15.7689 5.14183 17.0825C6.74018 18.3961 8.7359 19.1304 10.8045 19.166C12.8731 19.2015 14.8929 18.5363 16.5354 17.2784C16.9008 16.9986 16.9702 16.4755 16.6904 16.1101C16.4105 15.7447 15.8875 15.6754 15.5221 15.9552C14.1782 16.9844 12.5256 17.5287 10.8331 17.4995C9.14066 17.4704 7.5078 16.8697 6.20006 15.7949C4.89232 14.7201 3.98661 13.2346 3.63021 11.5798C3.27381 9.92499 3.48767 8.1983 4.23703 6.68048C4.98638 5.16265 6.22716 3.94296 7.7576 3.21971C9.28804 2.49647 11.0181 2.31221 12.6666 2.69691C14.315 3.08161 15.7848 4.01263 16.837 5.33858C17.8892 6.66453 18.462 8.30748 18.4621 10.0002V10.8335C18.4621 11.2755 18.2865 11.6994 17.9739 12.012C17.6614 12.3245 17.2374 12.5001 16.7954 12.5001C16.3534 12.5001 15.9295 12.3245 15.6169 12.012C15.3043 11.6994 15.1288 11.2755 15.1288 10.8335V6.6668C15.1288 6.20656 14.7557 5.83347 14.2954 5.83347C13.8353 5.83347 13.4622 6.2064 13.4621 6.66651C12.7657 6.14343 11.9001 5.83348 10.9621 5.83348C8.6609 5.83348 6.79542 7.69896 6.79542 10.0001C6.79542 12.3013 8.6609 14.1668 10.9621 14.1668C12.2022 14.1668 13.3157 13.6251 14.079 12.7654C14.186 12.9159 14.3061 13.0582 14.4384 13.1905C15.0635 13.8156 15.9114 14.1668 16.7954 14.1668C17.6795 14.1668 18.5273 13.8156 19.1524 13.1905C19.7776 12.5654 20.1288 11.7175 20.1288 10.8335V10.0001C20.1286 7.93119 19.4286 5.92319 18.1426 4.30257C16.8565 2.68195 15.0601 1.54404 13.0453 1.07385ZM13.4621 9.99665V10.0036C13.4602 11.3827 12.3416 12.5001 10.9621 12.5001C9.58138 12.5001 8.46209 11.3809 8.46209 10.0001C8.46209 8.61943 9.58138 7.50014 10.9621 7.50014C12.3416 7.50014 13.4602 8.61754 13.4621 9.99665Z" fill="currentColor"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M13.0453 1.07385C11.0306 0.603671 8.91602 0.82887 7.04549 1.71284C5.17495 2.5968 3.65845 4.08754 2.74257 5.94266C1.82669 7.79778 1.5653 9.90817 2.00091 11.9307C2.43651 13.9532 3.54348 15.7689 5.14183 17.0825C6.74018 18.3961 8.7359 19.1304 10.8045 19.166C12.8731 19.2015 14.8929 18.5363 16.5354 17.2784C16.9008 16.9986 16.9702 16.4755 16.6904 16.1101C16.4105 15.7447 15.8875 15.6754 15.5221 15.9552C14.1782 16.9844 12.5256 17.5287 10.8331 17.4995C9.14066 17.4704 7.5078 16.8697 6.20006 15.7949C4.89232 14.7201 3.98661 13.2346 3.63021 11.5798C3.27381 9.92499 3.48767 8.1983 4.23703 6.68048C4.98638 5.16265 6.22716 3.94296 7.7576 3.21971C9.28804 2.49647 11.0181 2.31221 12.6666 2.69691C14.315 3.08161 15.7848 4.01263 16.837 5.33858C17.8892 6.66453 18.462 8.30748 18.4621 10.0002V10.8335C18.4621 11.2755 18.2865 11.6994 17.9739 12.012C17.6614 12.3245 17.2374 12.5001 16.7954 12.5001C16.3534 12.5001 15.9295 12.3245 15.6169 12.012C15.3043 11.6994 15.1288 11.2755 15.1288 10.8335V6.6668C15.1288 6.20656 14.7557 5.83347 14.2954 5.83347C13.8353 5.83347 13.4622 6.2064 13.4621 6.66651C12.7657 6.14343 11.9001 5.83348 10.9621 5.83348C8.6609 5.83348 6.79542 7.69896 6.79542 10.0001C6.79542 12.3013 8.6609 14.1668 10.9621 14.1668C12.2022 14.1668 13.3157 13.6251 14.079 12.7654C14.186 12.9159 14.3061 13.0582 14.4384 13.1905C15.0635 13.8156 15.9114 14.1668 16.7954 14.1668C17.6795 14.1668 18.5273 13.8156 19.1524 13.1905C19.7776 12.5654 20.1288 11.7175 20.1288 10.8335V10.0001C20.1286 7.93119 19.4286 5.92319 18.1426 4.30257C16.8565 2.68195 15.0601 1.54404 13.0453 1.07385ZM13.4621 9.99665V10.0036C13.4602 11.3827 12.3416 12.5001 10.9621 12.5001C9.58138 12.5001 8.46209 11.3809 8.46209 10.0001C8.46209 8.61943 9.58138 7.50014 10.9621 7.50014C12.3416 7.50014 13.4602 8.61754 13.4621 9.99665Z" fill="currentColor"/>
</g> </g>
</symbol> </symbol>
<symbol id="trash-01" viewBox="0 0 20 21" fill="none">
<g>
<path d="M13.3333 5.5013V4.83464C13.3333 3.90121 13.3333 3.4345 13.1517 3.07798C12.9919 2.76438 12.7369 2.50941 12.4233 2.34962C12.0668 2.16797 11.6001 2.16797 10.6667 2.16797H9.33333C8.39991 2.16797 7.9332 2.16797 7.57668 2.34962C7.26308 2.50941 7.00811 2.76438 6.84832 3.07798C6.66667 3.4345 6.66667 3.90121 6.66667 4.83464V5.5013M8.33333 10.0846V14.2513M11.6667 10.0846V14.2513M2.5 5.5013H17.5M15.8333 5.5013V14.8346C15.8333 16.2348 15.8333 16.9348 15.5608 17.4696C15.3212 17.94 14.9387 18.3225 14.4683 18.5622C13.9335 18.8346 13.2335 18.8346 11.8333 18.8346H8.16667C6.76654 18.8346 6.06647 18.8346 5.53169 18.5622C5.06129 18.3225 4.67883 17.94 4.43915 17.4696C4.16667 16.9348 4.16667 16.2348 4.16667 14.8346V5.5013" stroke="currentColor" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</symbol>
<symbol id="refresh-ccw-01" viewBox="0 0 20 21" fill="none">
<g>
<path d="M5.28415 5.78889C6.49163 4.58059 8.15782 3.83464 9.99983 3.83464C13.6817 3.83464 16.6665 6.8194 16.6665 10.5013C16.6665 14.1832 13.6817 17.168 9.99983 17.168C6.96172 17.168 4.39626 15.135 3.59364 12.3536C3.46603 11.9114 3.00412 11.6564 2.56193 11.784C2.11973 11.9116 1.86471 12.3735 1.99231 12.8157C2.99526 16.2914 6.19946 18.8346 9.99983 18.8346C14.6022 18.8346 18.3332 15.1037 18.3332 10.5013C18.3332 5.89893 14.6022 2.16797 9.99983 2.16797C7.69784 2.16797 5.61252 3.10246 4.10524 4.61079C3.57518 5.14121 3.00475 5.7994 2.49992 6.41325V3.83464C2.49992 3.3744 2.12682 3.0013 1.66659 3.0013C1.20635 3.0013 0.833252 3.3744 0.833252 3.83464V8.83464C0.833252 9.29487 1.20635 9.66797 1.66659 9.66797H6.66659C7.12682 9.66797 7.49992 9.29487 7.49992 8.83464C7.49992 8.3744 7.12682 8.0013 6.66659 8.0013H3.35886C3.92894 7.28583 4.64729 6.4262 5.28415 5.78889Z" fill="currentColor"/>
</g>
</symbol>
<symbol id="x" viewBox="0 0 24 25" fill="none">
<path d="M17.7071 8.20711C18.0976 7.81658 18.0976 7.18342 17.7071 6.79289C17.3166 6.40237 16.6834 6.40237 16.2929 6.79289L12 11.0858L7.70711 6.79289C7.31658 6.40237 6.68342 6.40237 6.29289 6.79289C5.90237 7.18342 5.90237 7.81658 6.29289 8.20711L10.5858 12.5L6.29289 16.7929C5.90237 17.1834 5.90237 17.8166 6.29289 18.2071C6.68342 18.5976 7.31658 18.5976 7.70711 18.2071L12 13.9142L16.2929 18.2071C16.6834 18.5976 17.3166 18.5976 17.7071 18.2071C18.0976 17.8166 18.0976 17.1834 17.7071 16.7929L13.4142 12.5L17.7071 8.20711Z" fill="currentColor"/>
</symbol>
<symbol id="settings-04" viewBox="0 0 20 20" fill="none">
<path d="M11.772 7.4987L2.50033 7.4987C2.04009 7.4987 1.66699 7.1256 1.66699 6.66536C1.66699 6.20513 2.04009 5.83203 2.50033 5.83203L11.772 5.83203C12.142 4.39434 13.4471 3.33203 15.0003 3.33203C16.8413 3.33203 18.3337 4.82442 18.3337 6.66536C18.3337 8.50631 16.8413 9.9987 15.0003 9.9987C13.4471 9.9987 12.142 8.93639 11.772 7.4987Z" fill="currentColor"/>
<path d="M5.00033 9.9987C3.15938 9.9987 1.66699 11.4911 1.66699 13.332C1.66699 15.173 3.15938 16.6654 5.00033 16.6654C6.55352 16.6654 7.85861 15.6031 8.22864 14.1654L17.5003 14.1654C17.9606 14.1654 18.3337 13.7923 18.3337 13.332C18.3337 12.8718 17.9606 12.4987 17.5003 12.4987L8.22864 12.4987C7.85861 11.061 6.55352 9.9987 5.00033 9.9987Z" fill="currentColor"/>
</symbol>
<symbol id="list" viewBox="0 0 20 20" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.66699 9.9987C6.66699 9.53846 7.04009 9.16536 7.50033 9.16536L17.5003 9.16537C17.9606 9.16537 18.3337 9.53846 18.3337 9.9987C18.3337 10.4589 17.9606 10.832 17.5003 10.832L7.50033 10.832C7.04009 10.832 6.66699 10.4589 6.66699 9.9987Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.66699 4.9987C6.66699 4.53846 7.04009 4.16536 7.50033 4.16536L17.5003 4.16537C17.9606 4.16537 18.3337 4.53846 18.3337 4.9987C18.3337 5.45894 17.9606 5.83203 17.5003 5.83203L7.50033 5.83203C7.04009 5.83203 6.66699 5.45894 6.66699 4.9987Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.66699 14.9987C6.66699 14.5385 7.04009 14.1654 7.50033 14.1654L17.5003 14.1654C17.9606 14.1654 18.3337 14.5385 18.3337 14.9987C18.3337 15.4589 17.9606 15.832 17.5003 15.832L7.50033 15.832C7.04009 15.832 6.66699 15.4589 6.66699 14.9987Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.66699 9.9987C1.66699 9.07822 2.41318 8.33203 3.33366 8.33203C4.25413 8.33203 5.00033 9.07822 5.00033 9.9987C5.00033 10.9192 4.25413 11.6654 3.33366 11.6654C2.41318 11.6654 1.66699 10.9192 1.66699 9.9987Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.66699 4.9987C1.66699 4.07822 2.41318 3.33203 3.33366 3.33203C4.25413 3.33203 5.00033 4.07822 5.00033 4.9987C5.00033 5.91917 4.25413 6.66536 3.33366 6.66536C2.41318 6.66536 1.66699 5.91917 1.66699 4.9987Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.66699 14.9987C1.66699 14.0782 2.41318 13.332 3.33366 13.332C4.25413 13.332 5.00033 14.0782 5.00033 14.9987C5.00033 15.9192 4.25413 16.6654 3.33366 16.6654C2.41318 16.6654 1.66699 15.9192 1.66699 14.9987Z" fill="currentColor"/>
</symbol>
<symbol id="hard-drive" viewBox="0 0 24 24" fill="none">
<path d="M8.81413 2.99982C7.88643 2.99919 7.18706 2.99872 6.54986 3.21851C5.98936 3.41184 5.47885 3.72735 5.05527 4.14222C4.57372 4.61386 4.26137 5.23961 3.84705 6.06964C3.21363 7.33653 2.55478 8.59437 1.92983 9.86687C1.74859 10.2359 1.65797 10.4204 1.68168 10.5755C1.70218 10.7097 1.77888 10.8328 1.89025 10.9103C2.01902 11 2.22697 11 2.64287 11H21.3572C21.7731 11 21.9811 11 22.1098 10.9103C22.2212 10.8328 22.2979 10.7097 22.3184 10.5755C22.3421 10.4204 22.2515 10.2359 22.0702 9.86686C21.4453 8.59437 20.7865 7.33653 20.153 6.06966C19.7387 5.23961 19.4264 4.61386 18.9448 4.14222C18.5212 3.72735 18.0107 3.41184 17.4502 3.21851C16.813 2.99872 16.1136 2.99919 15.1859 2.99982H8.81413Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.0001 14.5996C23.0001 14.0399 23.0001 13.76 22.8912 13.5461C22.7953 13.3579 22.6423 13.2049 22.4541 13.109C22.2402 13 21.9603 13 21.4004 13H2.59962C2.03978 13 1.75985 13 1.54593 13.109C1.3578 13.2049 1.20476 13.3579 1.10891 13.5461C0.999925 13.76 0.999951 14.0399 1 14.5996C1.00002 14.8135 1.00004 15.0273 1.00004 15.2412C1.00003 16.0462 1.00002 16.7105 1.04423 17.2517C1.09016 17.8138 1.18872 18.3305 1.43601 18.8159C1.81951 19.5685 2.43143 20.1804 3.18408 20.5639C3.66941 20.8112 4.18612 20.9098 4.74821 20.9557C5.2894 20.9999 5.95376 20.9999 6.75872 20.9999H17.2414C18.0463 20.9999 18.7107 20.9999 19.2519 20.9557C19.814 20.9098 20.3307 20.8112 20.816 20.5639C21.5687 20.1804 22.1806 19.5685 22.5641 18.8159C22.8114 18.3305 22.9099 17.8138 22.9558 17.2517C23.0001 16.7105 23.0001 16.0462 23 15.2412C23 15.0274 23.0001 14.8135 23.0001 14.5996ZM6.00002 15C5.44773 15 5.00002 15.4477 5.00002 16C5.00002 16.5523 5.44773 17 6.00002 17H10C10.5523 17 11 16.5523 11 16C11 15.4477 10.5523 15 10 15H6.00002Z" fill="currentColor"/>
</symbol>
<symbol id="wifi-off" viewBox="0 0 24 20" fill="none">
<path d="M3.70711 0.292893C3.31658 -0.0976311 2.68342 -0.0976311 2.29289 0.292893C1.90237 0.683418 1.90237 1.31658 2.29289 1.70711L4.05356 3.46777C2.76797 4.14869 1.58058 4.98947 0.517762 5.96339C0.110582 6.33652 0.0829746 6.96908 0.4561 7.37626C0.829225 7.78344 1.46179 7.81105 1.86897 7.43792C2.95836 6.43965 4.19519 5.60049 5.5426 4.95681L7.83025 7.24447C6.4361 7.76113 5.16466 8.52904 4.07093 9.49251C3.65651 9.85758 3.6165 10.4895 3.98157 10.9039C4.34664 11.3183 4.97854 11.3583 5.39296 10.9933C6.53546 9.98682 7.90822 9.23792 9.42113 8.83535L12.0863 11.5005C12.0576 11.5002 12.0288 11.5 11.9999 11.5C10.3484 11.5 8.82808 12.0732 7.63085 13.0306C7.19953 13.3755 7.1295 14.0048 7.47443 14.4361C7.81937 14.8675 8.44865 14.9375 8.87997 14.5926C8.93165 14.5512 8.98416 14.5109 9.03748 14.4716C9.06175 14.4573 9.08563 14.4418 9.10903 14.4252C9.95503 13.8241 10.9671 13.5012 12.0049 13.5012C12.8194 13.5012 13.618 13.7001 14.333 14.0763C14.5973 14.2161 14.8477 14.3789 15.0814 14.5621C15.138 14.6064 15.1978 14.6437 15.2598 14.674L20.2929 19.7071C20.6834 20.0976 21.3166 20.0976 21.7071 19.7071C22.0976 19.3166 22.0976 18.6834 21.7071 18.2929L3.70711 0.292893Z" fill="currentColor"/>
<path d="M10.3098 3.59816C10.8684 3.53482 11.4326 3.50269 11.9998 3.50269C15.652 3.50269 19.1788 4.83517 21.9186 7.2502C22.3329 7.6154 22.9648 7.57559 23.33 7.16129C23.6952 6.74698 23.6554 6.11507 23.2411 5.74987C20.136 3.01283 16.139 1.50269 11.9998 1.50269C11.357 1.50269 10.7176 1.53911 10.0845 1.61089C9.53569 1.67311 9.14126 2.16841 9.20348 2.71718C9.2657 3.26595 9.761 3.66038 10.3098 3.59816Z" fill="currentColor"/>
<path d="M15.6095 7.04529C15.0822 6.88101 14.5216 7.17528 14.3573 7.70256C14.193 8.22985 14.4873 8.79048 15.0146 8.95476C16.2585 9.34233 17.4241 9.97212 18.4401 10.8184C18.8645 11.1719 19.495 11.1144 19.8485 10.69C20.2019 10.2657 20.1445 9.6351 19.7201 9.28164C18.5009 8.26611 17.1022 7.51034 15.6095 7.04529Z" fill="currentColor"/>
<path d="M12 16.5C11.4477 16.5 11 16.9477 11 17.5C11 18.0523 11.4477 18.5 12 18.5H12.01C12.5623 18.5 13.01 18.0523 13.01 17.5C13.01 16.9477 12.5623 16.5 12.01 16.5H12Z" fill="currentColor"/>
</symbol>
<symbol id="notes" viewBox="0 0 24 24" fill="none">
<path d="M11.9998 4.33268C11.9998 4.09933 11.9998 3.98265 11.9544 3.89352C11.9145 3.81512 11.8507 3.75138 11.7723 3.71143C11.6832 3.66602 11.5667 3.66602 11.3338 3.66602C10.5464 3.66602 9.24474 3.66601 8.46542 3.66602C7.79461 3.66601 7.24098 3.666 6.78998 3.70285C6.32157 3.74112 5.89098 3.82326 5.48654 4.02933C4.85933 4.34891 4.34939 4.85885 4.02982 5.48605C3.82374 5.8905 3.7416 6.32108 3.70333 6.7895C3.66649 7.24049 3.66649 7.79412 3.6665 8.46492V15.5338C3.66649 16.2046 3.66649 16.7582 3.70333 17.2092C3.7416 17.6776 3.82374 18.1082 4.02982 18.5127C4.34939 19.1399 4.85933 19.6498 5.48654 19.9694C5.89098 20.1755 6.32157 20.2576 6.78998 20.2959C7.24097 20.3327 7.7946 20.3327 8.4654 20.3327H15.5343C16.2051 20.3327 16.7587 20.3327 17.2097 20.2959C17.6781 20.2576 18.1087 20.1755 18.5131 19.9694C19.1403 19.6498 19.6503 19.1399 19.9699 18.5127C20.1759 18.1082 20.2581 17.6776 20.2963 17.2092C20.3332 16.7582 20.3332 16.2046 20.3332 15.5338C20.3332 14.7545 20.3332 13.4528 20.3332 12.6653C20.3332 12.4324 20.3332 12.3159 20.2878 12.2268C20.2478 12.1484 20.1841 12.0847 20.1057 12.0447C20.0165 11.9993 19.8999 11.9993 19.6665 11.9993H16.7988C16.128 11.9993 15.5743 11.9993 15.1233 11.9625C14.6549 11.9242 14.2243 11.8421 13.8199 11.636C13.1927 11.3164 12.6827 10.8065 12.3632 10.1793C12.1571 9.77482 12.0749 9.34424 12.0367 8.87582C11.9998 8.42483 11.9998 7.8712 11.9998 7.2004V4.33268Z" fill="currentColor"/>
<path d="M18.5898 10.3326C18.8344 10.3326 18.9568 10.3326 19.057 10.2712C19.1985 10.1844 19.2832 9.97998 19.2443 9.81854C19.2169 9.70427 19.1371 9.62458 18.9776 9.46521L14.534 5.02162C14.3746 4.86204 14.2949 4.78226 14.1805 4.75478C14.0191 4.71599 13.8147 4.8006 13.728 4.94213C13.6665 5.04235 13.6665 5.1647 13.6665 5.40939V7.16597C13.6665 7.87978 13.6672 8.36503 13.6978 8.7401C13.7276 9.10545 13.7818 9.29229 13.8482 9.42262C14.008 9.73622 14.2629 9.99119 14.5765 10.151C14.7069 10.2174 14.8937 10.2715 15.259 10.3013C15.6341 10.332 16.1194 10.3326 16.8332 10.3326H18.5898Z" fill="currentColor"/>
</symbol>
<symbol id="settings-solid" viewBox="0 0 20 20" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.11838 17.9624L7.57461 16.7575C7.38087 16.3275 7.05367 15.9691 6.64008 15.7339C6.22626 15.4987 5.74753 15.3985 5.27241 15.4477L3.94194 15.5872C3.54593 15.6282 3.14634 15.5553 2.79152 15.3773C2.4367 15.1992 2.14184 14.9237 1.94263 14.584C1.74336 14.2443 1.64843 13.855 1.66935 13.4632C1.69026 13.0714 1.82614 12.694 2.06048 12.3769L2.84821 11.3105C3.12914 10.9298 3.28042 10.4713 3.28033 10.0007C3.28042 9.53002 3.12914 9.07147 2.84821 8.69084L2.06048 7.62445C1.82614 7.30726 1.69026 6.92988 1.66935 6.5381C1.64843 6.14632 1.74336 5.75698 1.94263 5.41732C2.14165 5.07745 2.43647 4.80177 2.79134 4.62369C3.14622 4.44562 3.5459 4.3728 3.94194 4.41408L5.27654 4.55361C5.75166 4.60278 6.23039 4.5026 6.64422 4.26741C7.05627 4.03155 7.38195 3.67323 7.57461 3.2438L8.11838 2.03889C8.28002 1.68022 8.54382 1.37547 8.87779 1.1616C9.21175 0.947727 9.60152 0.83392 9.99984 0.833984C10.3982 0.83392 10.7879 0.947727 11.1219 1.1616C11.4559 1.37547 11.7197 1.68022 11.8813 2.03889L12.4292 3.2438C12.6219 3.67323 12.9475 4.03155 13.3596 4.26741C13.7734 4.5026 14.2521 4.60278 14.7273 4.55361L16.0577 4.41408C16.4538 4.3728 16.8535 4.44562 17.2083 4.62369C17.5632 4.80177 17.858 5.07745 18.057 5.41732C18.2563 5.75698 18.3512 6.14632 18.3303 6.5381C18.3094 6.92988 18.1735 7.30726 17.9392 7.62445L17.1515 8.69084C16.8705 9.07147 16.7193 9.53002 16.7193 10.0007C16.717 10.4726 16.8668 10.933 17.1473 11.3156L17.9351 12.3819C18.1694 12.6991 18.3053 13.0765 18.3262 13.4683C18.3471 13.8601 18.2522 14.2494 18.0529 14.5891C17.8539 14.9289 17.5591 15.2046 17.2042 15.3827C16.8493 15.5608 16.4496 15.6336 16.0536 15.5923L14.7231 15.4528C14.248 15.4036 13.7693 15.5038 13.3555 15.739C12.9442 15.9735 12.6186 16.33 12.4251 16.7575L11.8813 17.9624C11.7197 18.3211 11.4559 18.6258 11.1219 18.8397C10.7879 19.0536 10.3982 19.1674 9.99984 19.1673C9.60152 19.1674 9.21175 19.0536 8.87779 18.8397C8.54382 18.6258 8.28002 18.3211 8.11838 17.9624ZM12.4998 10.0007C12.4998 11.3814 11.3805 12.5006 9.99984 12.5006C8.61913 12.5006 7.49984 11.3814 7.49984 10.0007C7.49984 8.61994 8.61913 7.50065 9.99984 7.50065C11.3805 7.50065 12.4998 8.61994 12.4998 10.0007Z" fill="currentColor"/>
</symbol>
<symbol id="settings-outline" viewBox="0 0 20 22" fill="none">
<path d="M7.39516 18.3711L7.97961 19.6856C8.15335 20.0768 8.43689 20.4093 8.79583 20.6426C9.15478 20.8759 9.57372 21.0001 10.0018 21C10.4299 21.0001 10.8489 20.8759 11.2078 20.6426C11.5668 20.4093 11.8503 20.0768 12.0241 19.6856L12.6085 18.3711C12.8165 17.9047 13.1665 17.5159 13.6085 17.26C14.0533 17.0034 14.5678 16.8941 15.0785 16.9478L16.5085 17.1C16.9342 17.145 17.3638 17.0656 17.7452 16.8713C18.1266 16.6771 18.4435 16.3763 18.6574 16.0056C18.8716 15.635 18.9736 15.2103 18.9511 14.7829C18.9286 14.3555 18.7826 13.9438 18.5307 13.5978L17.6841 12.4344C17.3826 12.0171 17.2215 11.5148 17.2241 11C17.224 10.4866 17.3866 9.98635 17.6885 9.57111L18.5352 8.40778C18.787 8.06175 18.9331 7.65007 18.9556 7.22267C18.978 6.79528 18.876 6.37054 18.6618 6C18.4479 5.62923 18.1311 5.32849 17.7496 5.13423C17.3682 4.93997 16.9386 4.86053 16.5129 4.90556L15.0829 5.05778C14.5723 5.11141 14.0577 5.00212 13.6129 4.74556C13.1701 4.48825 12.82 4.09736 12.6129 3.62889L12.0241 2.31444C11.8503 1.92317 11.5668 1.59072 11.2078 1.3574C10.8489 1.12408 10.4299 0.99993 10.0018 1C9.57372 0.99993 9.15478 1.12408 8.79583 1.3574C8.43689 1.59072 8.15335 1.92317 7.97961 2.31444L7.39516 3.62889C7.18809 4.09736 6.83804 4.48825 6.39516 4.74556C5.95038 5.00212 5.43583 5.11141 4.92516 5.05778L3.49072 4.90556C3.06505 4.86053 2.63546 4.93997 2.25403 5.13423C1.87261 5.32849 1.55574 5.62923 1.34183 6C1.12765 6.37054 1.02561 6.79528 1.0481 7.22267C1.07058 7.65007 1.21662 8.06175 1.4685 8.40778L2.31516 9.57111C2.61711 9.98635 2.7797 10.4866 2.77961 11C2.7797 11.5134 2.61711 12.0137 2.31516 12.4289L1.4685 13.5922C1.21662 13.9382 1.07058 14.3499 1.0481 14.7773C1.02561 15.2047 1.12765 15.6295 1.34183 16C1.55595 16.3706 1.87286 16.6712 2.25423 16.8654C2.6356 17.0596 3.06508 17.1392 3.49072 17.0944L4.92072 16.9422C5.43139 16.8886 5.94594 16.9979 6.39072 17.2544C6.83525 17.511 7.18693 17.902 7.39516 18.3711Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 14C11.6569 14 13 12.6569 13 11C13 9.34315 11.6569 8 10 8C8.34319 8 7.00004 9.34315 7.00004 11C7.00004 12.6569 8.34319 14 10 14Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="home-solid" viewBox="0 0 18 18" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.43553 0.113589C9.15028 0.0363557 8.84962 0.0363557 8.56438 0.113589C8.23324 0.203247 7.95445 0.422125 7.73194 0.596817L7.66975 0.645498L1.95307 5.09173C1.63921 5.3353 1.3627 5.54988 1.15665 5.82847C0.975827 6.07295 0.841122 6.34837 0.759154 6.64121C0.665754 6.97489 0.666132 7.3249 0.666561 7.72218L0.666619 13.8654C0.666604 14.3047 0.666592 14.6837 0.692095 14.9958C0.719012 15.3253 0.778442 15.6529 0.939104 15.9683C1.17879 16.4387 1.56124 16.8211 2.03164 17.0608C2.34696 17.2215 2.67464 17.2809 3.0041 17.3078C3.31624 17.3333 3.6952 17.3333 4.13448 17.3333H13.8654C14.3047 17.3333 14.6837 17.3333 14.9958 17.3078C15.3253 17.2809 15.6529 17.2215 15.9683 17.0608C16.4387 16.8211 16.8211 16.4387 17.0608 15.9683C17.2215 15.6529 17.2809 15.3253 17.3078 14.9958C17.3333 14.6837 17.3333 14.3047 17.3333 13.8654L17.3333 7.72219C17.3338 7.3249 17.3342 6.97489 17.2408 6.64121C17.1588 6.34837 17.0241 6.07295 16.8433 5.82847C16.6372 5.54988 16.3607 5.33529 16.0468 5.09172L10.3302 0.645498L10.268 0.59682C10.0455 0.422128 9.76667 0.203248 9.43553 0.113589ZM6.57867 10.4589C6.46395 10.0132 6.00963 9.74487 5.56392 9.85959C5.11821 9.97431 4.84989 10.4286 4.9646 10.8743C5.4271 12.6713 7.05731 14 8.99995 14C10.9426 14 12.5728 12.6713 13.0353 10.8743C13.15 10.4286 12.8817 9.97431 12.436 9.85959C11.9903 9.74487 11.536 10.0132 11.4212 10.4589C11.1437 11.5374 10.1637 12.3333 8.99995 12.3333C7.8362 12.3333 6.85624 11.5374 6.57867 10.4589Z" fill="currentColor"/>
</symbol>
<symbol id="home-outline" viewBox="0 0 18 19" fill="none">
<path d="M5.77168 11.6668C6.14172 13.1045 7.4468 14.1668 9 14.1668C10.5532 14.1668 11.8583 13.1045 12.2283 11.6668M8.18141 2.30345L2.52949 6.69939C2.15168 6.99324 1.96278 7.14017 1.82669 7.32417C1.70614 7.48716 1.61633 7.67077 1.56169 7.866C1.5 8.08639 1.5 8.3257 1.5 8.80433V14.8334C1.5 15.7669 1.5 16.2336 1.68166 16.5901C1.84144 16.9037 2.09641 17.1587 2.41002 17.3185C2.76654 17.5001 3.23325 17.5001 4.16667 17.5001H13.8333C14.7668 17.5001 15.2335 17.5001 15.59 17.3185C15.9036 17.1587 16.1586 16.9037 16.3183 16.5901C16.5 16.2336 16.5 15.7669 16.5 14.8334V8.80433C16.5 8.3257 16.5 8.08639 16.4383 7.866C16.3837 7.67077 16.2939 7.48716 16.1733 7.32417C16.0372 7.14017 15.8483 6.99324 15.4705 6.69939L9.81859 2.30345C9.52582 2.07574 9.37943 1.96189 9.21779 1.91812C9.07516 1.87951 8.92484 1.87951 8.78221 1.91812C8.62057 1.96189 8.47418 2.07574 8.18141 2.30345Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="sign-in" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 3C6.44772 3 6 3.44772 6 4C6 4.55228 6.44772 5 7 5H18C18.5523 5 19 5.44772 19 6V18C19 18.5523 18.5523 19 18 19H7C6.44772 19 6 19.4477 6 20C6 20.5523 6.44772 21 7 21H18C19.6569 21 21 19.6569 21 18V6C21 4.34315 19.6569 3 18 3H7ZM12.7071 7.29289C12.3166 6.90237 11.6834 6.90237 11.2929 7.29289C10.9024 7.68342 10.9024 8.31658 11.2929 8.70711L13.5858 11H4C3.44772 11 3 11.4477 3 12C3 12.5523 3.44772 13 4 13H13.5858L11.2929 15.2929C10.9024 15.6834 10.9024 16.3166 11.2929 16.7071C11.6834 17.0976 12.3166 17.0976 12.7071 16.7071L16.7071 12.7071C17.0976 12.3166 17.0976 11.6834 16.7071 11.2929L12.7071 7.29289Z" fill="currentColor"/>
</symbol>
<symbol id="deck-solid" viewBox="0 0 20 20" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.3011 1.66602H14.8653C15.3046 1.666 15.6836 1.66599 15.9957 1.69149C16.3251 1.71841 16.6528 1.77784 16.9681 1.9385C17.4386 2.17818 17.821 2.56064 18.0607 3.03104C18.2213 3.34636 18.2808 3.67404 18.3077 4.00349C18.3332 4.31564 18.3332 4.69462 18.3332 5.13392V14.8648C18.3332 15.3041 18.3332 15.6831 18.3077 15.9952C18.2808 16.3247 18.2213 16.6523 18.0607 16.9677C17.821 17.4381 17.4386 17.8205 16.9681 18.0602C16.6528 18.2209 16.3251 18.2803 15.9957 18.3072C15.6836 18.3327 15.3046 18.3327 14.8653 18.3327H14.301C13.8618 18.3327 13.4828 18.3327 13.1706 18.3072C12.8412 18.2803 12.5135 18.2209 12.1982 18.0602C11.7278 17.8205 11.3453 17.4381 11.1057 16.9677C10.945 16.6523 10.8856 16.3247 10.8586 15.9952C10.8331 15.6831 10.8332 15.3041 10.8332 14.8648V5.1339C10.8332 4.69461 10.8331 4.31564 10.8586 4.00349C10.8856 3.67404 10.945 3.34636 11.1057 3.03104C11.3453 2.56064 11.7278 2.17818 12.1982 1.9385C12.5135 1.77784 12.8412 1.71841 13.1706 1.69149C13.4828 1.66599 13.8618 1.666 14.3011 1.66602Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.13439 1.66602H5.69863C6.13792 1.666 6.51689 1.66599 6.82903 1.69149C7.15848 1.71841 7.48617 1.77784 7.80148 1.9385C8.27189 2.17818 8.65434 2.56064 8.89402 3.03104C9.05468 3.34636 9.11411 3.67404 9.14103 4.00349C9.16653 4.31564 9.16652 4.69462 9.16651 5.13392V14.8648C9.16652 15.3041 9.16653 15.6831 9.14103 15.9952C9.11411 16.3247 9.05468 16.6523 8.89402 16.9677C8.65434 17.4381 8.27189 17.8205 7.80148 18.0602C7.48617 18.2209 7.15848 18.2803 6.82903 18.3072C6.51689 18.3327 6.13793 18.3327 5.69864 18.3327H5.13437C4.69508 18.3327 4.31612 18.3327 4.00398 18.3072C3.67453 18.2803 3.34685 18.2209 3.03153 18.0602C2.56112 17.8205 2.17867 17.4381 1.93899 16.9677C1.77833 16.6523 1.7189 16.3247 1.69198 15.9952C1.66648 15.6831 1.66649 15.3041 1.6665 14.8648V5.1339C1.66649 4.69461 1.66648 4.31564 1.69198 4.00349C1.7189 3.67404 1.77833 3.34636 1.93899 3.03104C2.17867 2.56064 2.56112 2.17818 3.03153 1.9385C3.34685 1.77784 3.67453 1.71841 4.00398 1.69149C4.31613 1.66599 4.69509 1.666 5.13439 1.66602Z" fill="currentColor"/>
</symbol>
<symbol id="deck-outline" viewBox="0 0 18 18" fill="none">
<path d="M4.66667 1.5H4.16667C3.23325 1.5 2.76654 1.5 2.41002 1.68166C2.09641 1.84144 1.84144 2.09641 1.68166 2.41002C1.5 2.76654 1.5 3.23325 1.5 4.16667V13.8333C1.5 14.7668 1.5 15.2335 1.68166 15.59C1.84144 15.9036 2.09641 16.1586 2.41002 16.3183C2.76654 16.5 3.23325 16.5 4.16667 16.5H4.66667C5.60009 16.5 6.0668 16.5 6.42332 16.3183C6.73692 16.1586 6.99189 15.9036 7.15168 15.59C7.33333 15.2335 7.33333 14.7668 7.33333 13.8333V4.16667C7.33333 3.23325 7.33333 2.76654 7.15168 2.41002C6.99189 2.09641 6.73692 1.84144 6.42332 1.68166C6.0668 1.5 5.60009 1.5 4.66667 1.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.8333 1.5H13.3333C12.3999 1.5 11.9332 1.5 11.5767 1.68166C11.2631 1.84144 11.0081 2.09641 10.8483 2.41002C10.6667 2.76654 10.6667 3.23325 10.6667 4.16667V13.8333C10.6667 14.7668 10.6667 15.2335 10.8483 15.59C11.0081 15.9036 11.2631 16.1586 11.5767 16.3183C11.9332 16.5 12.3999 16.5 13.3333 16.5H13.8333C14.7668 16.5 15.2335 16.5 15.59 16.3183C15.9036 16.1586 16.1586 15.9036 16.3183 15.59C16.5 15.2335 16.5 14.7668 16.5 13.8333V4.16667C16.5 3.23325 16.5 2.76654 16.3183 2.41002C16.1586 2.09641 15.9036 1.84144 15.59 1.68166C15.2335 1.5 14.7668 1.5 13.8333 1.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="graph-outline" viewBox="0 0 20 22" fill="none">
<path d="M6.59 12.51L13.42 16.49M13.41 5.51L6.59 9.49M19 4C19 5.65685 17.6569 7 16 7C14.3431 7 13 5.65685 13 4C13 2.34315 14.3431 1 16 1C17.6569 1 19 2.34315 19 4ZM7 11C7 12.6569 5.65685 14 4 14C2.34315 14 1 12.6569 1 11C1 9.34315 2.34315 8 4 8C5.65685 8 7 9.34315 7 11ZM19 18C19 19.6569 17.6569 21 16 21C14.3431 21 13 19.6569 13 18C13 16.3431 14.3431 15 16 15C17.6569 15 19 16.3431 19 18Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="graph-solid" viewBox="0 0 24 24" fill="none">
<path d="M14 5C14 2.79086 15.7909 1 18 1C20.2091 1 22 2.79086 22 5C22 7.20914 20.2091 9 18 9C16.8885 9 15.883 8.54668 15.1581 7.81485L9.85034 10.9123C9.94784 11.2581 10 11.623 10 12C10 12.3768 9.9479 12.7415 9.8505 13.0871L15.1613 16.1819C15.886 15.452 16.8902 15 18 15C20.2091 15 22 16.7909 22 19C22 21.2091 20.2091 23 18 23C15.7909 23 14 21.2091 14 19C14 18.6214 14.0526 18.255 14.1509 17.9079L8.84235 14.8144C8.11742 15.5465 7.11167 16 6 16C3.79086 16 2 14.2091 2 12C2 9.79086 3.79086 8 6 8C7.11146 8 8.11703 8.45332 8.84193 9.18514L14.1497 6.08767C14.0522 5.74185 14 5.37701 14 5Z" fill="currentColor"/>
</symbol>
<symbol id="media" viewBox="0 0 18 18" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.80659 0.666994C3.87988 0.667127 4.95525 0.667127 6.02642 0.666994C6.23674 0.666969 6.44157 0.666945 6.61503 0.681118C6.80553 0.696682 7.03031 0.733404 7.25649 0.848652C7.5701 1.00844 7.82506 1.26341 7.98485 1.57701C8.1001 1.8032 8.13682 2.02798 8.15239 2.21847C8.16656 2.39194 8.16654 2.59677 8.16651 2.8071V6.0269C8.16654 6.23722 8.16656 6.44205 8.15239 6.61552C8.13682 6.80602 8.1001 7.03079 7.98485 7.25698C7.82506 7.57058 7.5701 7.82555 7.25649 7.98534C7.03031 8.10059 6.80553 8.13731 6.61503 8.15288C6.44156 8.16705 6.23673 8.16702 6.02641 8.167H2.80661C2.59628 8.16702 2.39145 8.16705 2.21798 8.15288C2.02749 8.13731 1.80271 8.10059 1.57652 7.98534C1.26292 7.82555 1.00795 7.57058 0.848164 7.25698C0.732916 7.03079 0.696193 6.80602 0.680629 6.61552C0.666457 6.44206 0.666481 6.23723 0.666506 6.02691C0.666507 6.01806 0.666508 6.0092 0.666508 6.00033V2.83366C0.666508 2.82479 0.666507 2.81593 0.666506 2.80708C0.666481 2.59676 0.666457 2.39194 0.680629 2.21847C0.696193 2.02798 0.732916 1.8032 0.848164 1.57701C1.00795 1.26341 1.26292 1.00844 1.57652 0.848652C1.80271 0.733404 2.02749 0.696682 2.21798 0.681118C2.39145 0.666945 2.59627 0.666969 2.80659 0.666994Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.80659 9.83366C3.87988 9.83379 4.95525 9.83379 6.02642 9.83366C6.23674 9.83364 6.44157 9.83361 6.61503 9.84778C6.80553 9.86335 7.03031 9.90007 7.25649 10.0153C7.5701 10.1751 7.82506 10.4301 7.98485 10.7437C8.1001 10.9699 8.13682 11.1946 8.15239 11.3851C8.16656 11.5586 8.16654 11.7634 8.16651 11.9738V15.1936C8.16654 15.4039 8.16656 15.6087 8.15239 15.7822C8.13682 15.9727 8.1001 16.1975 7.98485 16.4236C7.82506 16.7373 7.5701 16.9922 7.25649 17.152C7.03031 17.2673 6.80553 17.304 6.61503 17.3195C6.44156 17.3337 6.23673 17.3337 6.02641 17.3337H2.80661C2.59628 17.3337 2.39145 17.3337 2.21798 17.3195C2.02749 17.304 1.80271 17.2673 1.57652 17.152C1.26292 16.9922 1.00795 16.7373 0.848164 16.4236C0.732916 16.1975 0.696193 15.9727 0.680629 15.7822C0.666457 15.6087 0.666481 15.4039 0.666506 15.1936C0.666507 15.1847 0.666508 15.1759 0.666508 15.167V12.0003C0.666508 11.9915 0.666507 11.9826 0.666506 11.9737C0.666481 11.7634 0.666457 11.5586 0.680629 11.3851C0.696193 11.1946 0.732916 10.9699 0.848164 10.7437C1.00795 10.4301 1.26292 10.1751 1.57652 10.0153C1.80271 9.90007 2.02749 9.86335 2.21798 9.84778C2.39145 9.83361 2.59627 9.83364 2.80659 9.83366Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9733 0.666994C13.0465 0.667127 14.1219 0.667127 15.1931 0.666994C15.4034 0.666969 15.6082 0.666945 15.7817 0.681118C15.9722 0.696682 16.197 0.733404 16.4232 0.848652C16.7368 1.00844 16.9917 1.26341 17.1515 1.57701C17.2668 1.8032 17.3035 2.02798 17.3191 2.21847C17.3332 2.39194 17.3332 2.59677 17.3332 2.8071V6.0269C17.3332 6.23722 17.3332 6.44205 17.3191 6.61552C17.3035 6.80602 17.2668 7.03079 17.1515 7.25698C16.9917 7.57058 16.7368 7.82555 16.4232 7.98534C16.197 8.10059 15.9722 8.13731 15.7817 8.15288C15.6082 8.16705 15.4034 8.16702 15.1931 8.167H11.9733C11.763 8.16702 11.5581 8.16705 11.3847 8.15288C11.1942 8.13731 10.9694 8.10059 10.7432 7.98534C10.4296 7.82555 10.1746 7.57058 10.0148 7.25698C9.89958 7.03079 9.86286 6.80602 9.8473 6.61552C9.83312 6.44206 9.83315 6.23723 9.83317 6.02691C9.83317 6.01806 9.83317 6.0092 9.83317 6.00033V2.83366C9.83317 2.82479 9.83317 2.81593 9.83317 2.80708C9.83315 2.59676 9.83312 2.39194 9.8473 2.21847C9.86286 2.02798 9.89958 1.8032 10.0148 1.57701C10.1746 1.26341 10.4296 1.00844 10.7432 0.848652C10.9694 0.733404 11.1942 0.696682 11.3847 0.681118C11.5581 0.666945 11.7629 0.666969 11.9733 0.666994Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9733 9.83366C13.0465 9.83379 14.1219 9.83379 15.1931 9.83366C15.4034 9.83364 15.6082 9.83361 15.7817 9.84778C15.9722 9.86335 16.197 9.90007 16.4232 10.0153C16.7368 10.1751 16.9917 10.4301 17.1515 10.7437C17.2668 10.9699 17.3035 11.1946 17.3191 11.3851C17.3332 11.5586 17.3332 11.7634 17.3332 11.9738V15.1936C17.3332 15.4039 17.3332 15.6087 17.3191 15.7822C17.3035 15.9727 17.2668 16.1975 17.1515 16.4236C16.9917 16.7373 16.7368 16.9922 16.4232 17.152C16.197 17.2673 15.9722 17.304 15.7817 17.3195C15.6082 17.3337 15.4034 17.3337 15.1931 17.3337H11.9733C11.763 17.3337 11.5581 17.3337 11.3847 17.3195C11.1942 17.304 10.9694 17.2673 10.7432 17.152C10.4296 16.9922 10.1746 16.7373 10.0148 16.4236C9.89958 16.1975 9.86286 15.9727 9.8473 15.7822C9.83312 15.6087 9.83315 15.4039 9.83317 15.1936C9.83317 15.1847 9.83317 15.1759 9.83317 15.167V12.0003C9.83317 11.9915 9.83317 11.9826 9.83317 11.9737C9.83315 11.7634 9.83312 11.5586 9.8473 11.3851C9.86286 11.1946 9.89958 10.9699 10.0148 10.7437C10.1746 10.4301 10.4296 10.1751 10.7432 10.0153C10.9694 9.90007 11.1942 9.86335 11.3847 9.84778C11.5581 9.83361 11.7629 9.83364 11.9733 9.83366Z" fill="currentColor"/>
</symbol>
<symbol id="info-solid" viewBox="0 0 22 22" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 0C4.92487 0 0 4.92487 0 11C0 17.0751 4.92487 22 11 22C17.0751 22 22 17.0751 22 11C22 4.92487 17.0751 0 11 0ZM11 6C10.4477 6 10 6.44772 10 7C10 7.55228 10.4477 8 11 8H11.01C11.5623 8 12.01 7.55228 12.01 7C12.01 6.44772 11.5623 6 11.01 6H11ZM12 11C12 10.4477 11.5523 10 11 10C10.4477 10 10 10.4477 10 11V15C10 15.5523 10.4477 16 11 16C11.5523 16 12 15.5523 12 15V11Z" fill="currentColor"/>
</symbol>
<symbol id="info-outline" viewBox="0 0 22 22" fill="none">
<path d="M11 15V11M11 7H11.01M21 11C21 16.5228 16.5228 21 11 21C5.47715 21 1 16.5228 1 11C1 5.47715 5.47715 1 11 1C16.5228 1 21 5.47715 21 11Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="play-square-outline" viewBox="0 0 20 20" fill="none">
<path d="M7.5 6.96533C7.5 6.48805 7.5 6.24941 7.59974 6.11618C7.68666 6.00007 7.81971 5.92744 7.96438 5.9171C8.13038 5.90525 8.33112 6.03429 8.73261 6.29239L13.4532 9.32706C13.8016 9.55102 13.9758 9.663 14.0359 9.80539C14.0885 9.9298 14.0885 10.0702 14.0359 10.1946C13.9758 10.337 13.8016 10.449 13.4532 10.6729L8.73261 13.7076C8.33112 13.9657 8.13038 14.0948 7.96438 14.0829C7.81971 14.0726 7.68666 13.9999 7.59974 13.8838C7.5 13.7506 7.5 13.512 7.5 13.0347V6.96533Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1 5.8C1 4.11984 1 3.27976 1.32698 2.63803C1.6146 2.07354 2.07354 1.6146 2.63803 1.32698C3.27976 1 4.11984 1 5.8 1H14.2C15.8802 1 16.7202 1 17.362 1.32698C17.9265 1.6146 18.3854 2.07354 18.673 2.63803C19 3.27976 19 4.11984 19 5.8V14.2C19 15.8802 19 16.7202 18.673 17.362C18.3854 17.9265 17.9265 18.3854 17.362 18.673C16.7202 19 15.8802 19 14.2 19H5.8C4.11984 19 3.27976 19 2.63803 18.673C2.07354 18.3854 1.6146 17.9265 1.32698 17.362C1 16.7202 1 15.8802 1 14.2V5.8Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="tool" viewBox="0 0 22 22" fill="none">
<path d="M14.6314 6.63137C14.2353 6.23535 14.0373 6.03735 13.9631 5.80902C13.8979 5.60817 13.8979 5.39183 13.9631 5.19098C14.0373 4.96265 14.2353 4.76465 14.6314 4.36863L17.4697 1.53026C16.7165 1.18962 15.8804 1 15 1C11.6863 1 8.99998 3.68629 8.99998 7C8.99998 7.49104 9.05897 7.9683 9.17024 8.42509C9.2894 8.91424 9.34898 9.15882 9.33841 9.31333C9.32733 9.47509 9.30321 9.56115 9.22862 9.70511C9.15736 9.84262 9.02084 9.97914 8.7478 10.2522L2.49998 16.5C1.67156 17.3284 1.67156 18.6716 2.49998 19.5C3.32841 20.3284 4.67156 20.3284 5.49998 19.5L11.7478 13.2522C12.0208 12.9791 12.1574 12.8426 12.2949 12.7714C12.4388 12.6968 12.5249 12.6727 12.6867 12.6616C12.8412 12.651 13.0857 12.7106 13.5749 12.8297C14.0317 12.941 14.5089 13 15 13C18.3137 13 21 10.3137 21 7C21 6.11959 20.8104 5.28347 20.4697 4.53026L17.6314 7.36863C17.2353 7.76465 17.0373 7.96265 16.809 8.03684C16.6082 8.1021 16.3918 8.1021 16.191 8.03684C15.9626 7.96265 15.7646 7.76465 15.3686 7.36863L14.6314 6.63137Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="medical-cross" viewBox="0 0 24 24" fill="none">
<path d="M15 4.6C15 4.03995 15 3.75992 14.891 3.54601C14.7951 3.35785 14.6422 3.20487 14.454 3.10899C14.2401 3 13.9601 3 13.4 3H10.6C10.0399 3 9.75992 3 9.54601 3.10899C9.35785 3.20487 9.20487 3.35785 9.10899 3.54601C9 3.75992 9 4.03995 9 4.6V7.4C9 7.96005 9 8.24008 8.89101 8.45399C8.79513 8.64215 8.64215 8.79513 8.45399 8.89101C8.24008 9 7.96005 9 7.4 9H4.6C4.03995 9 3.75992 9 3.54601 9.10899C3.35785 9.20487 3.20487 9.35785 3.10899 9.54601C3 9.75992 3 10.0399 3 10.6V13.4C3 13.9601 3 14.2401 3.10899 14.454C3.20487 14.6422 3.35785 14.7951 3.54601 14.891C3.75992 15 4.03995 15 4.6 15H7.4C7.96005 15 8.24008 15 8.45399 15.109C8.64215 15.2049 8.79513 15.3578 8.89101 15.546C9 15.7599 9 16.0399 9 16.6V19.4C9 19.9601 9 20.2401 9.10899 20.454C9.20487 20.6422 9.35785 20.7951 9.54601 20.891C9.75992 21 10.0399 21 10.6 21H13.4C13.9601 21 14.2401 21 14.454 20.891C14.6422 20.7951 14.7951 20.6422 14.891 20.454C15 20.2401 15 19.9601 15 19.4V16.6C15 16.0399 15 15.7599 15.109 15.546C15.2049 15.3578 15.3578 15.2049 15.546 15.109C15.7599 15 16.0399 15 16.6 15H19.4C19.9601 15 20.2401 15 20.454 14.891C20.6422 14.7951 20.7951 14.6422 20.891 14.454C21 14.2401 21 13.9601 21 13.4V10.6C21 10.0399 21 9.75992 20.891 9.54601C20.7951 9.35785 20.6422 9.20487 20.454 9.10899C20.2401 9 19.9601 9 19.4 9L16.6 9C16.0399 9 15.7599 9 15.546 8.89101C15.3578 8.79513 15.2049 8.64215 15.109 8.45399C15 8.24008 15 7.96005 15 7.4V4.6Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="arrow-up-right" viewBox="0 0 24 24" fill="none">
<path d="M6 18L18 6M18 6H10M18 6V14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="sats" viewBox="0 0 24 25" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M21 12.5C21 13.6819 20.7672 14.8522 20.3149 15.9442C19.8626 17.0361 19.1997 18.0282 18.364 18.864C17.5282 19.6997 16.5361 20.3626 15.4442 20.8149C14.3522 21.2672 13.1819 21.5 12 21.5C10.8181 21.5 9.64778 21.2672 8.55585 20.8149C7.46392 20.3626 6.47177 19.6997 5.63604 18.864C4.80031 18.0282 4.13738 17.0361 3.68508 15.9442C3.23279 14.8522 3 13.6819 3 12.5C3 10.1131 3.94821 7.82387 5.63604 6.13604C7.32387 4.44821 9.61305 3.5 12 3.5C14.3869 3.5 16.6761 4.44821 18.364 6.13604C20.0518 7.82387 21 10.1131 21 12.5ZM8.693 9.242L16.33 11.305L16.667 9.843L9.029 7.78L8.693 9.242ZM14.219 6.192L13.813 7.966L12.365 7.574L12.772 5.8L14.219 6.192ZM11.227 19.2L11.635 17.426L10.187 17.035L9.779 18.809L11.227 19.2ZM15.648 14.266L8.011 12.2L8.347 10.738L15.984 12.804L15.648 14.266ZM7.332 15.156L14.97 17.22L15.306 15.758L7.668 13.694L7.332 15.156Z" fill="currentColor"/>
</symbol>
<symbol id="face-smile" viewBox="0 0 24 25" fill="none">
<path d="M8 14.5C8 14.5 9.5 16.5 12 16.5C14.5 16.5 16 14.5 16 14.5M15 9.5H15.01M9 9.5H9.01M22 12.5C22 18.0228 17.5228 22.5 12 22.5C6.47715 22.5 2 18.0228 2 12.5C2 6.97715 6.47715 2.5 12 2.5C17.5228 2.5 22 6.97715 22 12.5ZM15.5 9.5C15.5 9.77614 15.2761 10 15 10C14.7239 10 14.5 9.77614 14.5 9.5C14.5 9.22386 14.7239 9 15 9C15.2761 9 15.5 9.22386 15.5 9.5ZM9.5 9.5C9.5 9.77614 9.27614 10 9 10C8.72386 10 8.5 9.77614 8.5 9.5C8.5 9.22386 8.72386 9 9 9C9.27614 9 9.5 9.22386 9.5 9.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="bar-chart" viewBox="0 0 24 25" fill="none" >
<path d="M9 7.5H4.6C4.03995 7.5 3.75992 7.5 3.54601 7.60899C3.35785 7.70487 3.20487 7.85785 3.10899 8.04601C3 8.25992 3 8.53995 3 9.1V19.9C3 20.4601 3 20.7401 3.10899 20.954C3.20487 21.1422 3.35785 21.2951 3.54601 21.391C3.75992 21.5 4.03995 21.5 4.6 21.5H9M9 21.5H15M9 21.5L9 5.1C9 4.53995 9 4.25992 9.10899 4.04601C9.20487 3.85785 9.35785 3.70487 9.54601 3.60899C9.75992 3.5 10.0399 3.5 10.6 3.5L13.4 3.5C13.9601 3.5 14.2401 3.5 14.454 3.60899C14.6422 3.70487 14.7951 3.85785 14.891 4.04601C15 4.25992 15 4.53995 15 5.1V21.5M15 11.5H19.4C19.9601 11.5 20.2401 11.5 20.454 11.609C20.6422 11.7049 20.7951 11.8578 20.891 12.046C21 12.2599 21 12.5399 21 13.1V19.9C21 20.4601 21 20.7401 20.891 20.954C20.7951 21.1422 20.6422 21.2951 20.454 21.391C20.2401 21.5 19.9601 21.5 19.4 21.5H15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="expand" viewBox="0 0 24 24" fill="none">
<path d="M14 3C14 2.44772 14.4477 2 15 2H21C21.5523 2 22 2.44772 22 3V9C22 9.55229 21.5523 10 21 10C20.4477 10 20 9.55229 20 9V5.41421L14.7071 10.7071C14.3166 11.0976 13.6834 11.0976 13.2929 10.7071C12.9024 10.3166 12.9024 9.68342 13.2929 9.29289L18.5858 4H15C14.4477 4 14 3.55228 14 3Z" fill="currentColor" />
<path d="M5.41421 20L10.7071 14.7071C11.0976 14.3166 11.0976 13.6834 10.7071 13.2929C10.3166 12.9024 9.68342 12.9024 9.29289 13.2929L4 18.5858L4 15C4 14.4477 3.55229 14 3 14C2.44772 14 2 14.4477 2 15V21C2 21.5523 2.44772 22 3 22H9C9.55228 22 10 21.5523 10 21C10 20.4477 9.55228 20 9 20H5.41421Z" fill="currentColor" />
</symbol>
</defs> </defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View File

@ -2,23 +2,18 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=yes, viewport-fit=cover" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta name="description" content="Feature packed nostr client" /> <meta name="description" content="Feature packed nostr client" />
<meta <meta
name="keywords" name="keywords"
content="nostr snort fast decentralized social media censorship resistant open source software" /> content="nostr snort fast decentralized social media censorship resistant open source software" />
<link rel="preconnect" href="https://imgproxy.snort.social" /> <link rel="preconnect" href="https://imgproxy.snort.social" />
<link rel="apple-touch-icon" href="/img/apple-touch-icon.png" /> <link rel="apple-touch-icon" href="<%= htmlWebpackPlugin.options.templateParameters.appleTouchIconUrl %>" />
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<link rel="icon" href="/favicon.png" /> <title><%= htmlWebpackPlugin.options.templateParameters.appTitle %></title>
<title></title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body> </body>
</html> </html>

View File

@ -1,4 +0,0 @@
/*
Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src https://youtube.com https://www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com https://open.spotify.com https://player.twitch.tv https://embed.music.apple.com https://embed.wavlake.com https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; connect-src *; img-src * data: blob:; font-src 'self'; media-src * blob:; script-src 'self' 'wasm-unsafe-eval' https://platform.twitter.com https://embed.tidal.com https://challenges.cloudflare.com;
/service-worker.js
Cache-Control: max-age=604800, must-revalidate;

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -24,14 +24,5 @@
"protocol": "web+nostr", "protocol": "web+nostr",
"url": "/%s" "url": "/%s"
} }
],
"screenshots": [],
"display_override": ["fullscreen"],
"related_applications": [
{
"platform": "play",
"url": "https://play.google.com/store/apps/details?id=social.snort.app",
"id": "social.snort.app"
}
] ]
} }

View File

@ -1,2 +0,0 @@
/*
Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src https://youtube.com https://www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com https://open.spotify.com https://player.twitch.tv https://embed.music.apple.com https://embed.wavlake.com https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; connect-src *; img-src * data: blob:; font-src 'self'; media-src * blob:; script-src 'self' 'wasm-unsafe-eval' https://platform.twitter.com https://embed.tidal.com https://challenges.cloudflare.com;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 146 KiB

View File

Before

Width:  |  Height:  |  Size: 528 KiB

After

Width:  |  Height:  |  Size: 528 KiB

View File

Before

Width:  |  Height:  |  Size: 771 KiB

After

Width:  |  Height:  |  Size: 771 KiB

View File

@ -1,16 +0,0 @@
{
"applinks": {
"details": [
{
"appIDs": [
"snort.social.app"
]
}
]
},
"webcredentials": {
"apps": [
"snort.social.app"
]
}
}

View File

@ -5,8 +5,7 @@
"namespace": "android_app", "namespace": "android_app",
"package_name": "social.snort.app", "package_name": "social.snort.app",
"sha256_cert_fingerprints": [ "sha256_cert_fingerprints": [
"78:CE:8A:F7:C1:E2:30:12:77:55:BF:0E:86:E4:5C:BA:99:93:A0:D7:D7:42:F8:27:8B:C9:1B:AC:FC:8A:85:05", "78:CE:8A:F7:C1:E2:30:12:77:55:BF:0E:86:E4:5C:BA:99:93:A0:D7:D7:42:F8:27:8B:C9:1B:AC:FC:8A:85:05"
"FC:C1:CA:02:C0:81:81:0C:1F:EC:1E:38:CA:38:61:62:6B:6E:90:88:62:DE:4A:66:FC:EC:08:33:B6:94:EE:3C"
] ]
} }
} }

View File

@ -1,4 +0,0 @@
/*
Content-Security-Policy: default-src 'self'; manifest-src *; child-src 'none'; worker-src 'self'; frame-src https://youtube.com https://www.youtube.com https://platform.twitter.com https://embed.tidal.com https://w.soundcloud.com https://www.mixcloud.com https://open.spotify.com https://player.twitch.tv https://embed.music.apple.com https://embed.wavlake.com https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; connect-src *; img-src * data: blob:; font-src 'self'; media-src * blob:; script-src 'self' 'wasm-unsafe-eval' https://platform.twitter.com https://embed.tidal.com https://challenges.cloudflare.com;
/service-worker.js
Cache-Control: max-age=604800, must-revalidate;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

View File

@ -1,4 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
Sitemap: https://api.snort.social/api/v1/sitemap/index.xml

View File

@ -1,7 +1,6 @@
import { FeedCache } from "@snort/shared";
import { NostrEvent } from "@snort/system"; import { NostrEvent } from "@snort/system";
import { FeedCache } from "@snort/shared";
import { db } from "@/Db"; import { db } from "Db";
export class ChatCache extends FeedCache<NostrEvent> { export class ChatCache extends FeedCache<NostrEvent> {
constructor() { constructor() {
@ -27,8 +26,4 @@ export class ChatCache extends FeedCache<NostrEvent> {
takeSnapshot(): Array<NostrEvent> { takeSnapshot(): Array<NostrEvent> {
return [...this.cache.values()]; return [...this.cache.values()];
} }
async search() {
return <Array<NostrEvent>>[];
}
} }

View File

@ -1,16 +0,0 @@
import { ExternalStore } from "@snort/shared";
class CommunityLeadersStore extends ExternalStore<Array<string>> {
#leaders: Array<string> = [];
setLeaders(arr: Array<string>) {
this.#leaders = arr;
this.notifyChange();
}
takeSnapshot(): string[] {
return [...this.#leaders];
}
}
export const LeadersStore = new CommunityLeadersStore();

View File

@ -1,110 +0,0 @@
import { CachedTable, CacheEvents } from "@snort/shared";
import { CacheRelay, NostrEvent } from "@snort/system";
import { EventEmitter } from "eventemitter3";
export class EventCacheWorker extends EventEmitter<CacheEvents> implements CachedTable<NostrEvent> {
#relay: CacheRelay;
#keys = new Set<string>();
#cache = new Map<string, NostrEvent>();
constructor(relay: CacheRelay) {
super();
this.#relay = relay;
}
async preload() {
const ids = await this.#relay.query([
"REQ",
"preload-event-cache",
{
ids_only: true,
},
]);
this.#keys = new Set<string>(ids as unknown as Array<string>);
}
async search(q: string) {
const results = await this.#relay.query([
"REQ",
"events-search",
{
search: q,
},
]);
return results;
}
keysOnTable(): string[] {
return [...this.#keys];
}
getFromCache(key?: string | undefined): NostrEvent | undefined {
if (key) {
return this.#cache.get(key);
}
}
discover(ev: NostrEvent) {
this.#keys.add(this.key(ev));
}
async get(key?: string | undefined): Promise<NostrEvent | undefined> {
if (key) {
const res = await this.bulkGet([key]);
if (res.length > 0) {
return res[0];
}
}
}
async bulkGet(keys: string[]): Promise<NostrEvent[]> {
const results = await this.#relay.query([
"REQ",
"EventCacheWorker.bulkGet",
{
ids: keys,
},
]);
for (const ev of results) {
this.#cache.set(ev.id, ev);
}
return results;
}
async set(obj: NostrEvent): Promise<void> {
await this.#relay.event(obj);
this.#keys.add(obj.id);
}
async bulkSet(obj: NostrEvent[] | readonly NostrEvent[]): Promise<void> {
await Promise.all(
obj.map(async a => {
await this.#relay.event(a);
this.#keys.add(a.id);
}),
);
}
async update<TWithCreated extends NostrEvent & { created: number; loaded: number }>(
m: TWithCreated,
): Promise<"new" | "refresh" | "updated" | "no_change"> {
if (await this.#relay.event(m)) {
return "updated";
}
return "no_change";
}
async buffer(keys: string[]): Promise<string[]> {
const missing = keys.filter(a => !this.#keys.has(a));
const res = await this.bulkGet(missing);
return missing.filter(a => !res.some(b => this.key(b) === a));
}
key(of: NostrEvent): string {
return of.id;
}
snapshot(): NostrEvent[] {
return [...this.#cache.values()];
}
}

View File

@ -0,0 +1,42 @@
import { FeedCache } from "@snort/shared";
import { db, EventInteraction } from "Db";
import { LoginStore } from "Login";
import { sha256 } from "SnortUtils";
export class EventInteractionCache extends FeedCache<EventInteraction> {
constructor() {
super("EventInteraction", db.eventInteraction);
}
key(of: EventInteraction): string {
return sha256(of.event + of.by);
}
override async preload(): Promise<void> {
await super.preload();
const data = window.localStorage.getItem("zap-cache");
if (data) {
const toImport = [...new Set<string>(JSON.parse(data) as Array<string>)].map(a => {
const ret = {
event: a,
by: LoginStore.takeSnapshot().publicKey,
zapped: true,
reacted: false,
reposted: false,
} as EventInteraction;
ret.id = this.key(ret);
return ret;
});
await this.bulkSet(toImport);
console.debug(`Imported dumb-zap-cache events: `, toImport.length);
window.localStorage.removeItem("zap-cache");
}
await this.buffer([...this.onTable]);
}
takeSnapshot(): EventInteraction[] {
return [...this.cache.values()];
}
}

View File

@ -0,0 +1,126 @@
import debug from "debug";
import { EventKind, RequestBuilder, SystemInterface, TaggedNostrEvent } from "@snort/system";
import { unixNow, unixNowMs } from "@snort/shared";
import { db } from "Db";
import { RefreshFeedCache, TWithCreated } from "./RefreshFeedCache";
import { LoginSession } from "Login";
import { Day, Hour } from "Const";
const WindowSize = Hour * 6;
const MaxCacheWindow = Day * 7;
export class FollowsFeedCache extends RefreshFeedCache<TaggedNostrEvent> {
#kinds = [EventKind.TextNote, EventKind.Repost, EventKind.Polls];
#oldest?: number;
constructor() {
super("FollowsFeedCache", db.followsFeed);
}
key(of: TWithCreated<TaggedNostrEvent>): string {
return of.id;
}
takeSnapshot(): TWithCreated<TaggedNostrEvent>[] {
return [...this.cache.values()];
}
buildSub(session: LoginSession, rb: RequestBuilder): void {
const since = this.newest();
rb.withFilter()
.kinds(this.#kinds)
.authors(session.follows.item)
.since(since === 0 ? unixNow() - WindowSize : since);
}
async onEvent(evs: readonly TaggedNostrEvent[]): Promise<void> {
const filtered = evs.filter(a => this.#kinds.includes(a.kind));
if (filtered.length > 0) {
await this.bulkSet(filtered);
this.notifyChange(filtered.map(a => this.key(a)));
}
}
override async preload() {
const start = unixNowMs();
const keys = (await this.table?.toCollection().primaryKeys()) ?? [];
this.onTable = new Set<string>(keys.map(a => a as string));
// load only latest 10 posts, rest can be loaded on-demand
const latest = await this.table?.orderBy("created_at").reverse().limit(50).toArray();
latest?.forEach(v => this.cache.set(this.key(v), v));
// cleanup older than 7 days
await this.table
?.where("created_at")
.below(unixNow() - MaxCacheWindow)
.delete();
const oldest = await this.table?.orderBy("created_at").first();
this.#oldest = oldest?.created_at;
this.notifyChange(latest?.map(a => this.key(a)) ?? []);
debug(this.name)(`Loaded %d/%d in %d ms`, latest?.length ?? 0, keys.length, (unixNowMs() - start).toLocaleString());
}
async loadMore(system: SystemInterface, session: LoginSession, before: number) {
if (this.#oldest && before <= this.#oldest) {
const rb = new RequestBuilder(`${this.name}-loadmore`);
rb.withFilter()
.kinds(this.#kinds)
.authors(session.follows.item)
.until(before)
.since(before - WindowSize);
await system.Fetch(rb, async evs => {
await this.bulkSet(evs);
});
} else {
const latest = await this.table
?.where("created_at")
.between(before - WindowSize, before)
.reverse()
.sortBy("created_at");
latest?.forEach(v => {
const k = this.key(v);
this.cache.set(k, v);
this.onTable.add(k);
});
this.notifyChange(latest?.map(a => this.key(a)) ?? []);
}
}
/**
* Backfill cache with new follows
*/
async backFill(system: SystemInterface, keys: Array<string>) {
if (keys.length === 0) return;
const rb = new RequestBuilder(`${this.name}-backfill`);
rb.withFilter()
.kinds(this.#kinds)
.authors(keys)
.until(unixNow())
.since(this.#oldest ?? unixNow() - MaxCacheWindow);
await system.Fetch(rb, async evs => {
await this.bulkSet(evs);
});
}
/**
* Backfill cache based on follows list
*/
async backFillIfMissing(system: SystemInterface, keys: Array<string>) {
if (!this.#oldest) return;
const start = unixNowMs();
const everything = await this.table?.toArray();
if ((everything?.length ?? 0) > 0) {
const allKeys = new Set(everything?.map(a => a.pubkey));
const missingKeys = keys.filter(a => !allKeys.has(a));
await this.backFill(system, missingKeys);
debug(this.name)(`Backfilled %d keys in %d ms`, missingKeys.length, (unixNowMs() - start).toLocaleString());
}
}
}

View File

@ -1,9 +1,8 @@
import { EventKind, EventPublisher, TaggedNostrEvent } from "@snort/system"; import { EventKind, EventPublisher, RequestBuilder, TaggedNostrEvent } from "@snort/system";
import { UnwrappedGift, db } from "Db";
import { db, UnwrappedGift } from "@/Db"; import { findTag, unwrap } from "SnortUtils";
import { findTag, unwrap } from "@/Utils"; import { RefreshFeedCache } from "./RefreshFeedCache";
import { LoginSession, LoginSessionType } from "Login";
import { RefreshFeedCache, TWithCreated } from "./RefreshFeedCache";
export class GiftWrapCache extends RefreshFeedCache<UnwrappedGift> { export class GiftWrapCache extends RefreshFeedCache<UnwrappedGift> {
constructor() { constructor() {
@ -14,8 +13,11 @@ export class GiftWrapCache extends RefreshFeedCache<UnwrappedGift> {
return of.id; return of.id;
} }
buildSub(): void { buildSub(session: LoginSession, rb: RequestBuilder): void {
// not used const pubkey = session.publicKey;
if (pubkey && session.type === LoginSessionType.PrivateKey) {
rb.withFilter().kinds([EventKind.GiftWrap]).tag("p", [pubkey]).since(this.newest());
}
} }
takeSnapshot(): Array<UnwrappedGift> { takeSnapshot(): Array<UnwrappedGift> {
@ -53,8 +55,4 @@ export class GiftWrapCache extends RefreshFeedCache<UnwrappedGift> {
} }
await this.bulkSet(unwrapped); await this.bulkSet(unwrapped);
} }
search(): Promise<TWithCreated<UnwrappedGift>[]> {
throw new Error("Method not implemented.");
}
} }

View File

@ -0,0 +1,45 @@
import { EventKind, NostrEvent, RequestBuilder, TaggedNostrEvent } from "@snort/system";
import { RefreshFeedCache, TWithCreated } from "./RefreshFeedCache";
import { LoginSession } from "Login";
import { NostrEventForSession, db } from "Db";
import { Day } from "Const";
import { unixNow } from "@snort/shared";
export class NotificationsCache extends RefreshFeedCache<NostrEventForSession> {
#kinds = [EventKind.TextNote, EventKind.Reaction, EventKind.Repost, EventKind.ZapReceipt];
constructor() {
super("notifications", db.notifications);
}
buildSub(session: LoginSession, rb: RequestBuilder) {
if (session.publicKey) {
const newest = this.newest(v => v.tags.some(a => a[0] === "p" && a[1] === session.publicKey));
rb.withFilter()
.kinds(this.#kinds)
.tag("p", [session.publicKey])
.since(newest === 0 ? unixNow() - Day * 30 : newest);
}
}
async onEvent(evs: readonly TaggedNostrEvent[], pubKey: string) {
const filtered = evs.filter(a => this.#kinds.includes(a.kind) && a.tags.some(b => b[0] === "p"));
if (filtered.length > 0) {
await this.bulkSet(
filtered.map(v => ({
...v,
forSession: pubKey,
})),
);
this.notifyChange(filtered.map(v => this.key(v)));
}
}
key(of: TWithCreated<NostrEvent>): string {
return of.id;
}
takeSnapshot() {
return [...this.cache.values()];
}
}

View File

@ -0,0 +1,16 @@
import { Payment, db } from "Db";
import { FeedCache } from "@snort/shared";
export class Payments extends FeedCache<Payment> {
constructor() {
super("PaymentsCache", db.payments);
}
key(of: Payment): string {
return of.url;
}
takeSnapshot(): Array<Payment> {
return [...this.cache.values()];
}
}

View File

@ -1,123 +0,0 @@
import { CachedTable, CacheEvents, removeUndefined, unixNowMs, unwrap } from "@snort/shared";
import { CachedMetadata, CacheRelay, mapEventToProfile, NostrEvent } from "@snort/system";
import debug from "debug";
import { EventEmitter } from "eventemitter3";
export class ProfileCacheRelayWorker extends EventEmitter<CacheEvents> implements CachedTable<CachedMetadata> {
#relay: CacheRelay;
#keys = new Set<string>();
#cache = new Map<string, CachedMetadata>();
#log = debug("ProfileCacheRelayWorker");
constructor(relay: CacheRelay) {
super();
this.#relay = relay;
}
async preload() {
const start = unixNowMs();
const profiles = await this.#relay.query([
"REQ",
"profiles-preload",
{
kinds: [0],
},
]);
this.#cache = new Map<string, CachedMetadata>(profiles.map(a => [a.pubkey, unwrap(mapEventToProfile(a))]));
this.#keys = new Set<string>(this.#cache.keys());
this.#log(`Loaded %d/%d in %d ms`, this.#cache.size, this.#keys.size, (unixNowMs() - start).toLocaleString());
}
async search(q: string) {
const profiles = await this.#relay.query([
"REQ",
"profiles-search",
{
kinds: [0],
search: q,
},
]);
return removeUndefined(profiles.map(mapEventToProfile));
}
keysOnTable(): string[] {
return [...this.#keys];
}
getFromCache(key?: string | undefined) {
if (key) {
return this.#cache.get(key);
}
}
discover(ev: NostrEvent) {
if (ev.kind === 0) {
this.#keys.add(ev.pubkey);
}
}
async get(key?: string | undefined) {
if (key) {
const cached = this.getFromCache(key);
if (cached) {
return cached;
}
const res = await this.bulkGet([key]);
if (res.length > 0) {
return res[0];
}
}
}
async bulkGet(keys: string[]) {
if (keys.length === 0) return [];
const results = await this.#relay.query([
"REQ",
"ProfileCacheRelayWorker.bulkGet",
{
authors: keys,
kinds: [0],
},
]);
const mapped = removeUndefined(results.map(a => mapEventToProfile(a)));
for (const pf of mapped) {
this.#cache.set(this.key(pf), pf);
}
this.emit(
"change",
mapped.map(a => this.key(a)),
);
return mapped;
}
async set(obj: CachedMetadata) {
this.#keys.add(this.key(obj));
}
async bulkSet(obj: CachedMetadata[] | readonly CachedMetadata[]) {
const mapped = obj.map(a => this.key(a));
mapped.forEach(a => this.#keys.add(a));
// todo: store in cache
this.emit("change", mapped);
}
async update(): Promise<"new" | "refresh" | "updated" | "no_change"> {
// do nothing
return "refresh";
}
async buffer(keys: string[]) {
const missing = keys.filter(a => !this.#cache.has(a));
const res = await this.bulkGet(missing);
return missing.filter(a => !res.some(b => this.key(b) === a));
}
key(of: CachedMetadata) {
return of.pubkey;
}
snapshot() {
return [...this.#cache.values()];
}
}

View File

@ -1,7 +1,6 @@
import { FeedCache } from "@snort/shared"; import { FeedCache } from "@snort/shared";
import { EventPublisher, RequestBuilder, TaggedNostrEvent } from "@snort/system"; import { EventPublisher, RequestBuilder, TaggedNostrEvent } from "@snort/system";
import { LoginSession } from "Login";
import { LoginSession } from "@/Utils/Login";
export type TWithCreated<T> = (T | Readonly<T>) & { created_at: number }; export type TWithCreated<T> = (T | Readonly<T>) & { created_at: number };
@ -24,6 +23,7 @@ export abstract class RefreshFeedCache<T> extends FeedCache<TWithCreated<T>> {
override async preload(): Promise<void> { override async preload(): Promise<void> {
await super.preload(); await super.preload();
// load all dms to memory
await this.buffer([...this.onTable]); await this.buffer([...this.onTable]);
} }
} }

View File

@ -1,6 +0,0 @@
import { ParsedFragment } from "@snort/system";
import { LRUCache } from "typescript-lru-cache";
export const TextCache = new LRUCache<string, Array<ParsedFragment>>({
maxSize: 1000,
});

View File

@ -1,128 +0,0 @@
import { CachedTable, CacheEvents, removeUndefined, unixNowMs, unwrap } from "@snort/shared";
import { CacheRelay, EventKind, NostrEvent, UsersFollows } from "@snort/system";
import debug from "debug";
import { EventEmitter } from "eventemitter3";
export class UserFollowsWorker extends EventEmitter<CacheEvents> implements CachedTable<UsersFollows> {
#relay: CacheRelay;
#keys = new Set<string>();
#cache = new Map<string, UsersFollows>();
#log = debug("UserFollowsWorker");
constructor(relay: CacheRelay) {
super();
this.#relay = relay;
}
async preload() {
const start = unixNowMs();
const profiles = await this.#relay.query([
"REQ",
"profiles-preload",
{
kinds: [3],
},
]);
this.#cache = new Map<string, UsersFollows>(profiles.map(a => [a.pubkey, unwrap(mapEventToUserFollows(a))]));
this.#keys = new Set<string>(this.#cache.keys());
this.#log(`Loaded %d/%d in %d ms`, this.#cache.size, this.#keys.size, (unixNowMs() - start).toLocaleString());
}
async search(q: string) {
const results = await this.#relay.query([
"REQ",
"contacts-search",
{
kinds: [3],
search: q,
},
]);
return removeUndefined(results.map(mapEventToUserFollows));
}
keysOnTable(): string[] {
return [...this.#keys];
}
getFromCache(key?: string | undefined): UsersFollows | undefined {
if (key) {
return this.#cache.get(key);
}
}
discover(ev: NostrEvent) {
this.#keys.add(ev.pubkey);
}
async get(key?: string | undefined): Promise<UsersFollows | undefined> {
if (key) {
const res = await this.bulkGet([key]);
if (res.length > 0) {
return res[0];
}
}
}
async bulkGet(keys: string[]) {
if (keys.length === 0) return [];
const results = await this.#relay.query([
"REQ",
"UserFollowsWorker.bulkGet",
{
authors: keys,
kinds: [3],
},
]);
const mapped = removeUndefined(results.map(a => mapEventToUserFollows(a)));
for (const pf of mapped) {
this.#cache.set(this.key(pf), pf);
}
this.emit(
"change",
mapped.map(a => this.key(a)),
);
return mapped;
}
async set(obj: UsersFollows) {
this.#keys.add(this.key(obj));
}
async bulkSet(obj: UsersFollows[] | readonly UsersFollows[]) {
const mapped = obj.map(a => this.key(a));
mapped.forEach(a => this.#keys.add(a));
// todo: store in cache
this.emit("change", mapped);
}
async update(): Promise<"new" | "refresh" | "updated" | "no_change"> {
// do nothing
return "refresh";
}
async buffer(keys: string[]): Promise<string[]> {
const missing = keys.filter(a => !this.#keys.has(a));
const res = await this.bulkGet(missing);
return missing.filter(a => !res.some(b => this.key(b) === a));
}
key(of: UsersFollows): string {
return of.pubkey;
}
snapshot(): UsersFollows[] {
return [...this.#cache.values()];
}
}
export function mapEventToUserFollows(ev: NostrEvent): UsersFollows | undefined {
if (ev.kind !== EventKind.ContactList) return;
return {
pubkey: ev.pubkey,
loaded: unixNowMs(),
created: ev.created_at,
follows: ev.tags,
};
}

View File

@ -1,85 +1,35 @@
import { CacheRelay, Connection, ConnectionCacheRelay, RelayMetricCache, UserRelaysCache } from "@snort/system"; import { UserProfileCache, UserRelaysCache, RelayMetricCache } from "@snort/system";
import { SnortSystemDb } from "@snort/system-web"; import { SnortSystemDb } from "@snort/system-web";
import { WorkerRelayInterface } from "@snort/worker-relay";
import WorkerVite from "@snort/worker-relay/src/worker?worker";
import { EventCacheWorker } from "./EventCacheWorker"; import { EventInteractionCache } from "./EventInteractionCache";
import { ChatCache } from "./ChatCache";
import { Payments } from "./PaymentsCache";
import { GiftWrapCache } from "./GiftWrapCache"; import { GiftWrapCache } from "./GiftWrapCache";
import { ProfileCacheRelayWorker } from "./ProfileWorkerCache"; import { NotificationsCache } from "./Notifications";
import { UserFollowsWorker } from "./UserFollowsWorker"; import { FollowsFeedCache } from "./FollowsFeed";
const cacheRelay = localStorage.getItem("cache-relay");
const workerRelay = new WorkerRelayInterface(
import.meta.env.DEV ? new URL("@snort/worker-relay/dist/esm/worker.mjs", import.meta.url) : new WorkerVite(),
);
export const Relay: CacheRelay = cacheRelay
? new ConnectionCacheRelay(new Connection(cacheRelay, { read: true, write: true }))
: workerRelay;
async function tryUseCacheRelay(url: string) {
try {
const conn = new Connection(url, { read: true, write: true });
await conn.connect(true);
localStorage.setItem("cache-relay", url);
return conn;
} catch (e) {
console.warn(e);
}
}
export async function tryUseLocalRelay() {
let conn = await tryUseCacheRelay("ws://localhost:4869");
if (!conn) {
conn = await tryUseCacheRelay("ws://umbrel:4848");
}
return conn;
}
export async function initRelayWorker() {
try {
if (Relay instanceof ConnectionCacheRelay) {
await Relay.connection.connect(true);
return;
}
} catch (e) {
localStorage.removeItem("cache-relay");
console.error(e);
if (cacheRelay) {
window.location.reload();
}
}
try {
await workerRelay.debug("*");
await workerRelay.init({
databasePath: "relay.db",
insertBatchSize: 100,
});
} catch (e) {
console.error(e);
}
}
export const SystemDb = new SnortSystemDb(); export const SystemDb = new SnortSystemDb();
export const UserCache = new UserProfileCache(SystemDb.users);
export const UserRelays = new UserRelaysCache(SystemDb.userRelays); export const UserRelays = new UserRelaysCache(SystemDb.userRelays);
export const RelayMetrics = new RelayMetricCache(SystemDb.relayMetrics); export const RelayMetrics = new RelayMetricCache(SystemDb.relayMetrics);
export const UserFollows = new UserFollowsWorker(Relay); export const Chats = new ChatCache();
export const UserCache = new ProfileCacheRelayWorker(Relay); export const PaymentsCache = new Payments();
export const EventsCache = new EventCacheWorker(Relay); export const InteractionCache = new EventInteractionCache();
export const GiftsCache = new GiftWrapCache(); export const GiftsCache = new GiftWrapCache();
export const Notifications = new NotificationsCache();
export const FollowsFeed = new FollowsFeedCache();
export async function preload(follows?: Array<string>) { export async function preload(follows?: Array<string>) {
const preloads = [ const preloads = [
UserCache.preload(), UserCache.preload(follows),
Chats.preload(),
InteractionCache.preload(),
UserRelays.preload(follows),
RelayMetrics.preload(), RelayMetrics.preload(),
GiftsCache.preload(), GiftsCache.preload(),
UserRelays.preload(follows), Notifications.preload(),
EventsCache.preload(), FollowsFeed.preload(),
UserFollows.preload(),
]; ];
await Promise.all(preloads); await Promise.all(preloads);
} }

View File

@ -1,28 +0,0 @@
.spinner-wrapper {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.spinner-button > span {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.light .spinner-button {
border: 1px solid var(--border-color);
color: var(--font-secondary);
box-shadow: rgba(0, 0, 0, 0.08) 0 1px 1px;
}
.light .spinner-button:not(.primary):hover {
box-shadow: rgba(0, 0, 0, 0.2) 0 1px 3px;
}
.light .spinner-button:not(.primary) > span {
color: black;
}

View File

@ -1,36 +0,0 @@
import "./AsyncButton.css";
import classNames from "classnames";
import React, { ForwardedRef } from "react";
import Spinner from "@/Components/Icons/Spinner";
import useLoading from "@/Hooks/useLoading";
export interface AsyncButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
onClick?: (e: React.MouseEvent) => Promise<void> | void;
}
const AsyncButton = React.forwardRef<HTMLButtonElement, AsyncButtonProps>((props, ref) => {
const { handle, loading } = useLoading(props.onClick, props.disabled);
return (
<button
ref={ref as ForwardedRef<HTMLButtonElement>}
type="button"
disabled={loading || props.disabled}
{...props}
className={classNames("spinner-button", props.className)}
onClick={handle}>
<span style={{ visibility: loading ? "hidden" : "visible" }}>{props.children}</span>
{loading && (
<span className="spinner-wrapper">
<Spinner />
</span>
)}
</button>
);
});
AsyncButton.displayName = "AsyncButton";
export default AsyncButton;

View File

@ -1,24 +0,0 @@
import Icon from "@/Components/Icons/Icon";
import Spinner from "@/Components/Icons/Spinner";
import useLoading from "@/Hooks/useLoading";
export type AsyncIconProps = React.HTMLProps<HTMLDivElement> & {
iconName: string;
iconSize?: number;
onClick?: (e: React.MouseEvent) => Promise<void> | void;
};
export function AsyncIcon(props: AsyncIconProps) {
const { loading, handle } = useLoading(props.onClick, props.disabled);
const mergedProps = { ...props } as Record<string, unknown>;
delete mergedProps["iconName"];
delete mergedProps["iconSize"];
delete mergedProps["loading"];
return (
<div {...mergedProps} onClick={handle} className={props.className}>
{loading ? <Spinner /> : <Icon name={props.iconName} size={props.iconSize} />}
{props.children}
</div>
);
}

View File

@ -1,16 +0,0 @@
import classNames from "classnames";
import Icon from "@/Components/Icons/Icon";
export default function CloseButton({ onClick, className }: { onClick?: () => void; className?: string }) {
return (
<div
onClick={onClick}
className={classNames(
"self-center circle flex flex-shrink-0 flex-grow-0 items-center justify-center hover:opacity-80 bg-dark p-2 cursor-pointer",
className,
)}>
<Icon name="close" size={12} />
</div>
);
}

View File

@ -1,27 +0,0 @@
import classNames from "classnames";
import type { ReactNode } from "react";
import Icon, { IconProps } from "@/Components/Icons/Icon";
interface IconButtonProps {
onClick?: () => void;
icon: IconProps;
className?: string;
children?: ReactNode;
}
const IconButton = ({ onClick, icon, children, className }: IconButtonProps) => {
return (
<button
className={classNames(
"flex items-center justify-center aspect-square w-10 h-10 !p-0 !m-0 bg-gray-dark text-white",
className,
)}
onClick={onClick}>
<Icon {...icon} />
{children}
</button>
);
};
export default IconButton;

View File

@ -1,20 +0,0 @@
import { NavLink as RouterNavLink, NavLinkProps, useLocation } from "react-router-dom";
export default function NavLink(props: NavLinkProps) {
const { to, onClick, ...rest } = props;
const location = useLocation();
const isActive = location.pathname === to.toString();
const handleClick = event => {
if (onClick) {
onClick(event);
}
if (isActive) {
window.scrollTo({ top: 0, behavior: "instant" });
}
};
return <RouterNavLink to={to} onClick={handleClick} {...rest} />;
}

View File

@ -1,54 +0,0 @@
export default function AwardIcon({ size }: { size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 62 62" fill="none" className="award">
<defs>
<linearGradient
id="paint0_linear_2660_40043"
x1="31"
y1="3.57143"
x2="31"
y2="58.4286"
gradientUnits="userSpaceOnUse">
<stop stopColor="#5B2CB3" />
<stop offset="1" stopColor="#811EFF" />
</linearGradient>
<linearGradient
id="paint1_linear_2660_40043"
x1="15.5594"
y1="24.305"
x2="46.433"
y2="24.305"
gradientUnits="userSpaceOnUse">
<stop stopColor="#AC88FF" />
<stop offset="1" stopColor="#7234FF" />
</linearGradient>
</defs>
<g id="award-02">
<rect x="1.85713" y="1.85714" width="58.2857" height="58.2857" rx="29.1429" fill="#AC88FF" fillOpacity="0.2" />
<rect
x="1.85713"
y="1.85714"
width="58.2857"
height="58.2857"
rx="29.1429"
stroke="url(#paint0_linear_2660_40043)"
strokeWidth="3.42857"
/>
<path
id="Solid"
d="M23.2006 52.4983L22.5639 50.9066L23.2006 52.4983L30.9963 49.38L38.7919 52.4983C39.8813 52.934 41.116 52.801 42.0876 52.1432C43.0592 51.4854 43.6412 50.3885 43.6412 49.2151V38.1015C46.467 35.038 48.1957 30.9408 48.1957 26.4427C48.1957 16.9437 40.4952 9.24329 30.9963 9.24329C21.4973 9.24329 13.7968 16.9437 13.7968 26.4427C13.7968 30.9408 15.5255 35.038 18.3513 38.1015V49.2151C18.3513 50.3885 18.9333 51.4854 19.9049 52.1432C20.8765 52.801 22.1112 52.934 23.2006 52.4983ZM27.2967 43.2429L25.4234 43.9922V42.7187C26.0332 42.9275 26.6584 43.1029 27.2967 43.2429ZM34.6958 43.2429C35.3341 43.1029 35.9593 42.9275 36.5691 42.7187V43.9922L34.6958 43.2429Z"
fill="url(#paint1_linear_2660_40043)"
stroke="#251250"
strokeWidth="3.42857"
strokeLinecap="round"
/>
<path
id="Ellipse 1595"
d="M24.2557 14.6002C17.7766 18.3409 15.5567 26.6257 19.2974 33.1049L42.7604 19.5585C39.0196 13.0794 30.7348 10.8595 24.2557 14.6002Z"
fill="white"
fillOpacity="0.1"
/>
</g>
</svg>
);
}

View File

@ -1,49 +0,0 @@
import { useState } from "react";
import { FormattedMessage } from "react-intl";
import { Link } from "react-router-dom";
import CloseButton from "../Button/CloseButton";
import Modal from "../Modal/Modal";
import AwardIcon from "./Award";
export function LeaderBadge() {
const [showModal, setShowModal] = useState(false);
return (
<>
<div
className="flex gap-1 p-1 pr-2 items-center border border-[#5B2CB3] rounded-full"
onClick={e => {
e.preventDefault();
e.stopPropagation();
setShowModal(true);
}}>
<AwardIcon size={16} />
<div className="text-xs font-medium text-[#AC88FF]">
<FormattedMessage defaultMessage="Community Leader" />
</div>
</div>
{showModal && (
<Modal onClose={() => setShowModal(false)} id="leaders">
<div className="flex flex-col gap-4 items-center relative">
<CloseButton className="absolute right-2 top-2" onClick={() => setShowModal(false)} />
<AwardIcon size={80} />
<div className="text-3xl font-semibold">
<FormattedMessage defaultMessage="Community Leader" />
</div>
<p className="text-secondary">
<FormattedMessage
defaultMessage="Community leaders are individuals who grow the nostr ecosystem by being active in their local communities and helping onboard new users. Anyone can become a community leader, but few hold the current honorary title."
id="f1OxTe"
/>
</p>
<Link to="/settings/invite">
<button className="primary">
<FormattedMessage defaultMessage="Become a leader" />
</button>
</Link>
</div>
</Modal>
)}
</>
);
}

View File

@ -1,37 +0,0 @@
import "./Copy.css";
import classNames from "classnames";
import Icon from "@/Components/Icons/Icon";
import { useCopy } from "@/Hooks/useCopy";
export interface CopyProps {
text: string;
maxSize?: number;
className?: string;
showText?: boolean;
mask?: string;
}
export default function Copy({ text, maxSize = 32, className, showText, mask }: CopyProps) {
const { copy, copied } = useCopy();
const sliceLength = maxSize / 2;
const displayText = mask ? mask.repeat(text.length) : text;
const trimmed =
displayText.length > maxSize
? `${displayText.slice(0, sliceLength)}...${displayText.slice(-sliceLength)}`
: displayText;
return (
<div
className={classNames("copy flex pointer g8 items-center", className)}
onClick={e => {
e.stopPropagation();
copy(text);
}}>
{(showText ?? true) && <span className="copy-body">{trimmed}</span>}
<span className="icon" style={{ color: copied ? "var(--success)" : "var(--highlight)" }}>
{copied ? <Icon name="check" size={14} /> : <Icon name="copy-solid" size={14} />}
</span>
</div>
);
}

View File

@ -1,3 +0,0 @@
.cashu {
background: var(--cashu-gradient);
}

View File

@ -1,34 +0,0 @@
import { useState } from "react";
import Icon from "../Icons/Icon";
import { ProxyImg } from "../ProxyImg";
export default function GenericPlayer({ url, poster }: { url: string; poster: string }) {
const [play, setPlay] = useState(false);
if (!play) {
return (
<div
className="relative aspect-video"
onClick={e => {
e.preventDefault();
e.stopPropagation();
setPlay(true);
}}>
<ProxyImg className="absolute" src={poster} />
<div className="absolute w-full h-full opacity-0 hover:opacity-100 hover:bg-black/30 flex items-center justify-center transition">
<Icon name="play-square-outline" size={50} />
</div>
</div>
);
}
return (
<iframe
className="aspect-video w-full"
src={url}
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen={true}
/>
);
}

View File

@ -1,76 +0,0 @@
import { Bech32Regex } from "@snort/shared";
import { ReactNode } from "react";
import AppleMusicEmbed from "@/Components/Embed/AppleMusicEmbed";
import LinkPreview from "@/Components/Embed/LinkPreview";
import MagnetLink from "@/Components/Embed/MagnetLink";
import MixCloudEmbed from "@/Components/Embed/MixCloudEmbed";
import NostrLink from "@/Components/Embed/NostrLink";
import SoundCloudEmbed from "@/Components/Embed/SoundCloudEmded";
import SpotifyEmbed from "@/Components/Embed/SpotifyEmbed";
import TidalEmbed from "@/Components/Embed/TidalEmbed";
import TwitchEmbed from "@/Components/Embed/TwitchEmbed";
import WavlakeEmbed from "@/Components/Embed/WavlakeEmbed";
import YoutubeEmbed from "@/Components/Embed/YoutubeEmbed";
import { magnetURIDecode } from "@/Utils";
import {
AppleMusicRegex,
MixCloudRegex,
SoundCloudRegex,
SpotifyRegex,
TidalRegex,
TwitchRegex,
WavlakeRegex,
YoutubeUrlRegex,
} from "@/Utils/Const";
interface HypeTextProps {
link: string;
children?: ReactNode | Array<ReactNode> | null;
depth?: number;
showLinkPreview?: boolean;
}
export default function HyperText({ link, depth, showLinkPreview, children }: HypeTextProps) {
const a = link;
try {
const url = new URL(a);
let m = null;
if (a.match(YoutubeUrlRegex)) {
return <YoutubeEmbed link={a} />;
} else if (a.match(TidalRegex)) {
return <TidalEmbed link={a} />;
} else if (a.match(SoundCloudRegex)) {
return <SoundCloudEmbed link={a} />;
} else if (a.match(MixCloudRegex)) {
return <MixCloudEmbed link={a} />;
} else if (a.match(SpotifyRegex)) {
return <SpotifyEmbed link={a} />;
} else if (a.match(TwitchRegex)) {
return <TwitchEmbed link={a} />;
} else if (a.match(AppleMusicRegex)) {
return <AppleMusicEmbed link={a} />;
} else if (a.match(WavlakeRegex)) {
return <WavlakeEmbed link={a} />;
} else if (url.protocol === "nostr:" || url.protocol === "web+nostr:") {
return <NostrLink link={a} depth={depth} />;
} else if (url.protocol === "magnet:") {
const parsed = magnetURIDecode(a);
if (parsed) {
return <MagnetLink magnet={parsed} />;
}
} else if ((m = a.match(Bech32Regex)) != null) {
return <NostrLink link={`nostr:${m[1]}`} depth={depth} />;
} else if (showLinkPreview ?? true) {
return <LinkPreview url={a} />;
}
} catch {
// Ignore the error.
}
return (
<a href={a} onClick={e => e.stopPropagation()} target="_blank" rel="noreferrer" className="ext">
{children ?? a}
</a>
);
}

View File

@ -1,128 +0,0 @@
import { IMeta } from "@snort/system";
import classNames from "classnames";
import React, { CSSProperties, useEffect, useMemo, useRef } from "react";
import { useInView } from "react-intersection-observer";
import { ProxyImg } from "@/Components/ProxyImg";
import useImgProxy from "@/Hooks/useImgProxy";
interface MediaElementProps {
mime: string;
url: string;
meta?: IMeta;
onMediaClick?: (e: React.MouseEvent<HTMLImageElement>) => void;
size?: number;
}
interface AudioElementProps {
url: string;
}
interface VideoElementProps {
url: string;
meta?: IMeta;
}
interface ImageElementProps {
url: string;
meta?: IMeta;
size?: number;
onMediaClick?: (e: React.MouseEvent<HTMLImageElement>) => void;
}
const AudioElement = ({ url }: AudioElementProps) => {
return <audio key={url} src={url} controls />;
};
const ImageElement = ({ url, meta, onMediaClick, size }: ImageElementProps) => {
const imageRef = useRef<HTMLImageElement | null>(null);
const style = useMemo(() => {
const style = {} as CSSProperties;
if (meta?.height && meta.width && imageRef.current) {
const scale = imageRef.current.offsetWidth / meta.width;
style.height = `${Math.min(document.body.clientHeight * 0.8, meta.height * scale)}px`;
}
return style;
}, [imageRef?.current, meta]);
return (
<div
className={classNames("flex items-center -mx-4 md:mx-0 my-2", {
"md:h-[510px]": !meta && !CONFIG.media.preferLargeMedia,
"cursor-pointer": onMediaClick,
})}>
<ProxyImg
key={url}
src={url}
size={size}
sha256={meta?.sha256}
onClick={onMediaClick}
className={classNames("max-h-[80vh] w-full h-full object-contain object-center", {
"md:max-h-[510px]": !meta && !CONFIG.media.preferLargeMedia,
})}
style={style}
ref={imageRef}
/>
</div>
);
};
const VideoElement = ({ url }: VideoElementProps) => {
const { proxy } = useImgProxy();
const videoRef = useRef<HTMLVideoElement | null>(null);
const { ref: videoContainerRef, inView } = useInView({ threshold: 0.33 });
const isMobile = window.innerWidth < 768;
useEffect(() => {
if (isMobile || !videoRef.current) {
return;
}
if (inView) {
videoRef.current.play();
} else {
videoRef.current.pause();
}
}, [inView]);
return (
<div
ref={videoContainerRef}
className={classNames("flex justify-center items-center -mx-4 md:mx-0 my-2", {
"md:h-[510px]": !CONFIG.media.preferLargeMedia,
})}>
<video
crossOrigin="anonymous"
ref={videoRef}
loop={true}
muted={!isMobile}
src={url}
controls
poster={proxy(url)}
className={classNames("max-h-[80vh]", { "md:max-h-[510px]": !CONFIG.media.preferLargeMedia })}
onClick={e => e.stopPropagation()}
/>
</div>
);
};
export function MediaElement(props: MediaElementProps) {
if (props.mime.startsWith("image/")) {
return <ImageElement url={props.url} meta={props.meta} onMediaClick={props.onMediaClick} size={props.size} />;
} else if (props.mime.startsWith("audio/")) {
return <AudioElement url={props.url} />;
} else if (props.mime.startsWith("video/")) {
return <VideoElement url={props.url} />;
} else {
return (
<a
key={props.url}
href={props.url}
onClick={e => e.stopPropagation()}
target="_blank"
rel="noreferrer"
className="ext">
{props.url}
</a>
);
}
}

View File

@ -1,35 +0,0 @@
import { NostrLink, NostrPrefix } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import { useCallback, useRef, useState } from "react";
import DisplayName from "@/Components/User/DisplayName";
import { ProfileCard } from "@/Components/User/ProfileCard";
import { ProfileLink } from "@/Components/User/ProfileLink";
export default function Mention({ link }: { link: NostrLink }) {
const profile = useUserProfile(link.id);
const [isHovering, setIsHovering] = useState(false);
const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const handleMouseEnter = useCallback(() => {
hoverTimeoutRef.current && clearTimeout(hoverTimeoutRef.current);
hoverTimeoutRef.current = setTimeout(() => setIsHovering(true), 100); // Adjust timeout as needed
}, []);
const handleMouseLeave = useCallback(() => {
hoverTimeoutRef.current && clearTimeout(hoverTimeoutRef.current);
hoverTimeoutRef.current = setTimeout(() => setIsHovering(false), 300); // Adjust timeout as needed
}, []);
if (link.type !== NostrPrefix.Profile && link.type !== NostrPrefix.PublicKey) return;
return (
<span className="highlight" onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
<ProfileLink pubkey={link.id} link={link} user={profile} onClick={e => e.stopPropagation()}>
@<DisplayName user={profile} pubkey={link.id} />
</ProfileLink>
{isHovering && <ProfileCard pubkey={link.id} user={profile} show={true} />}
</span>
);
}

View File

@ -1,23 +0,0 @@
import usePreferences from "@/Hooks/usePreferences";
import { MixCloudRegex } from "@/Utils/Const";
const MixCloudEmbed = ({ link }: { link: string }) => {
const match = link.match(MixCloudRegex);
if (!match) return;
const feedPath = match[1] + "%2F" + match[2];
const theme = usePreferences(s => s.theme);
const lightParams = theme === "light" ? "light=1" : "light=0";
return (
<iframe
title="SoundCloud player"
width="100%"
height="120"
frameBorder="0"
src={`https://www.mixcloud.com/widget/iframe/?hide_cover=1&${lightParams}&feed=%2F${feedPath}%2F`}
loading="lazy"
/>
);
};
export default MixCloudEmbed;

View File

@ -1,17 +0,0 @@
import { YoutubeUrlRegex } from "@/Utils/Const";
export default function YoutubeEmbed({ link }: { link: string }) {
const m = link.match(YoutubeUrlRegex);
if (!m) return;
return (
<iframe
className="-mx-4 md:mx-0 w-max my-2"
src={`https://www.youtube.com/embed/${m[1]}${m[3] ? `?list=${m[3].slice(6)}` : ""}`}
title="YouTube video player"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen={true}
/>
);
}

View File

@ -1,44 +0,0 @@
import React from "react";
import { trackEvent } from "@/Utils";
interface ErrorBoundaryState {
hasError: boolean;
errorMessage?: string;
stack?: string;
}
interface ErrorBoundaryProps {
children: React.ReactNode;
}
export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, errorMessage: error.message, stack: error.stack };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error("Caught an error:", error, errorInfo);
trackEvent("error", { error: error.message, errorInfo: JSON.stringify(errorInfo) });
}
render() {
if (this.state.hasError) {
// Render any custom fallback UI with the error message
return (
<div className="p-2">
<h1>Something went wrong.</h1>
<p>Error: {this.state.errorMessage}</p>
<pre className="text-xs overflow-auto mt-8">{this.state.stack}</pre>
</div>
);
}
return this.props.children;
}
}

View File

@ -1,27 +0,0 @@
import { OfflineError } from "@snort/shared";
import classNames from "classnames";
import Icon from "@/Components/Icons/Icon";
import { Offline } from "./Offline";
export function ErrorOrOffline({
error,
onRetry,
className,
}: {
error: Error;
onRetry?: () => void | Promise<void>;
className?: string;
}) {
if (error instanceof OfflineError) {
return <Offline onRetry={onRetry} className={className} />;
} else {
return (
<div className={classNames("flex flex-row items-center px-4 py-3 gap-2", className)}>
<Icon name="alert-circle" size={24} />
<b>{error.message}</b>
</div>
);
}
}

View File

@ -1,751 +0,0 @@
/* eslint-disable max-lines */
import { fetchNip05Pubkey, unixNow } from "@snort/shared";
import { EventBuilder, EventKind, NostrLink, NostrPrefix, TaggedNostrEvent, tryParseNostrLink } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import { ZapTarget } from "@snort/wallet";
import { Menu, MenuItem } from "@szhsin/react-menu";
import classNames from "classnames";
import { ClipboardEventHandler, DragEvent, useEffect } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import AsyncButton from "@/Components/Button/AsyncButton";
import { AsyncIcon } from "@/Components/Button/AsyncIcon";
import CloseButton from "@/Components/Button/CloseButton";
import IconButton from "@/Components/Button/IconButton";
import { sendEventToRelays } from "@/Components/Event/Create/util";
import Note from "@/Components/Event/EventComponent";
import Flyout from "@/Components/flyout";
import Icon from "@/Components/Icons/Icon";
import { ToggleSwitch } from "@/Components/Icons/Toggle";
import Modal from "@/Components/Modal/Modal";
import Textarea from "@/Components/Textarea/Textarea";
import { Toastore } from "@/Components/Toaster/Toaster";
import { MediaServerFileList } from "@/Components/Upload/file-picker";
import Avatar from "@/Components/User/Avatar";
import useEventPublisher from "@/Hooks/useEventPublisher";
import useLogin from "@/Hooks/useLogin";
import usePreferences from "@/Hooks/usePreferences";
import useRelays from "@/Hooks/useRelays";
import { useNoteCreator } from "@/State/NoteCreator";
import { openFile, trackEvent } from "@/Utils";
import useFileUpload, { addExtensionToNip94Url, nip94TagsToIMeta, readNip94Tags } from "@/Utils/Upload";
import { GetPowWorker } from "@/Utils/wasm";
import { OkResponseRow } from "./OkResponseRow";
const previewNoteOptions = {
showContextMenu: false,
showFooter: false,
canClick: false,
showTime: false,
};
const replyToNoteOptions = {
showFooter: false,
showContextMenu: false,
showProfileCard: false,
showTime: false,
canClick: false,
longFormPreview: true,
};
const quoteNoteOptions = {
showFooter: false,
showContextMenu: false,
showTime: false,
canClick: false,
longFormPreview: true,
};
export function NoteCreator() {
const { formatMessage } = useIntl();
const uploader = useFileUpload();
const publicKey = useLogin(s => s.publicKey);
const profile = useUserProfile(publicKey);
const pow = usePreferences(s => s.pow);
const relays = useRelays();
const { system, publisher: pub } = useEventPublisher();
const publisher = pow ? pub?.pow(pow, GetPowWorker()) : pub;
const note = useNoteCreator();
useEffect(() => {
const draft = localStorage.getItem("msgDraft");
if (draft) {
note.update(n => (n.note = draft));
}
}, []);
async function buildNote() {
try {
note.update(v => (v.error = ""));
if (note && publisher) {
let extraTags: Array<Array<string>> | undefined;
if (note.zapSplits) {
const parsedSplits = [] as Array<ZapTarget>;
for (const s of note.zapSplits) {
if (s.value.startsWith(NostrPrefix.PublicKey) || s.value.startsWith(NostrPrefix.Profile)) {
const link = tryParseNostrLink(s.value);
if (link) {
parsedSplits.push({ ...s, value: link.id });
} else {
throw new Error(
formatMessage(
{
defaultMessage: "Failed to parse zap split: {input}",
id: "sZQzjQ",
},
{
input: s.value,
},
),
);
}
} else if (s.value.includes("@")) {
const [name, domain] = s.value.split("@");
const pubkey = await fetchNip05Pubkey(name, domain);
if (pubkey) {
parsedSplits.push({ ...s, value: pubkey });
} else {
throw new Error(
formatMessage(
{
defaultMessage: "Failed to parse zap split: {input}",
id: "sZQzjQ",
},
{
input: s.value,
},
),
);
}
} else {
throw new Error(
formatMessage(
{
defaultMessage: "Invalid zap split: {input}",
id: "8Y6bZQ",
},
{
input: s.value,
},
),
);
}
}
extraTags = parsedSplits.map(v => ["zap", v.value, "", String(v.weight)]);
}
if (note.sensitive) {
extraTags ??= [];
extraTags.push(["content-warning", note.sensitive]);
}
const kind = note.pollOptions ? EventKind.Polls : EventKind.TextNote;
if (note.pollOptions) {
extraTags ??= [];
extraTags.push(...note.pollOptions.map((a, i) => ["poll_option", i.toString(), a]));
}
if (note.hashTags.length > 0) {
extraTags ??= [];
extraTags.push(...note.hashTags.map(a => ["t", a.toLowerCase()]));
}
for (const ex of note.otherEvents ?? []) {
const meta = readNip94Tags(ex.tags);
if (!meta.url) continue;
if (!note.note.endsWith("\n")) {
note.note += "\n";
}
note.note += addExtensionToNip94Url(meta);
extraTags ??= [];
extraTags.push(nip94TagsToIMeta(meta));
}
// add quote repost
if (note.quote) {
if (!note.note.endsWith("\n")) {
note.note += "\n";
}
const link = NostrLink.fromEvent(note.quote);
note.note += `nostr:${link.encode(CONFIG.eventLinkPrefix)}`;
const quoteTag = link.toEventTag();
if (quoteTag) {
extraTags ??= [];
if (quoteTag[0] === "e") {
quoteTag[0] = "q"; // how to 'q' tag replacable events?
}
extraTags.push(quoteTag);
}
}
const hk = (eb: EventBuilder) => {
extraTags?.forEach(t => eb.tag(t));
note.extraTags?.forEach(t => eb.tag(t));
eb.kind(kind);
return eb;
};
const ev = note.replyTo
? await publisher.reply(note.replyTo, note.note, hk)
: await publisher.note(note.note, hk);
return ev;
}
} catch (e) {
note.update(v => {
if (e instanceof Error) {
v.error = e.message;
} else {
v.error = e as string;
}
});
}
}
async function sendNote() {
const ev = await buildNote();
if (ev) {
let props: Record<string, boolean> | undefined = undefined;
if (ev.tags.find(a => a[0] === "content-warning")) {
props ??= {};
props["content-warning"] = true;
}
if (ev.tags.find(a => a[0] === "poll_option")) {
props ??= {};
props["poll"] = true;
}
if (ev.tags.find(a => a[0] === "zap")) {
props ??= {};
props["zap-split"] = true;
}
if (note.hashTags.length > 0) {
props ??= {};
props["hashtags"] = true;
}
if (props) {
props["content-warning"] ??= false;
props["poll"] ??= false;
props["zap-split"] ??= false;
props["hashtags"] ??= false;
}
trackEvent("PostNote", props);
sendEventToRelays(system, ev, note.selectedCustomRelays, r => {
if (CONFIG.noteCreatorToast) {
r.forEach(rr => {
Toastore.push({
element: c => <OkResponseRow rsp={rr} close={c} />,
expire: unixNow() + (rr.ok ? 5 : 55555),
});
});
}
});
note.update(n => n.reset());
localStorage.removeItem("msgDraft");
}
}
async function attachFile() {
try {
const file = await openFile();
if (file) {
uploadFile(file);
}
} catch (e) {
note.update(v => {
if (e instanceof Error) {
v.error = e.message;
} else {
v.error = e as string;
}
});
}
}
async function uploadFile(file: File) {
try {
if (file && uploader) {
const rx = await uploader.upload(file, file.name);
note.update(v => {
if (rx.header) {
v.otherEvents ??= [];
v.otherEvents.push(rx.header);
} else if (rx.url) {
v.note = `${v.note ? `${v.note}\n` : ""}${rx.url}`;
if (rx.metadata) {
v.extraTags ??= [];
const imeta = nip94TagsToIMeta(rx.metadata);
v.extraTags.push(imeta);
}
} else if (rx?.error) {
v.error = rx.error;
}
});
}
} catch (e) {
note.update(v => {
if (e instanceof Error) {
v.error = e.message;
} else {
v.error = e as string;
}
});
}
}
function onChange(ev: React.ChangeEvent<HTMLTextAreaElement>) {
const { value } = ev.target;
note.update(n => (n.note = value));
localStorage.setItem("msgDraft", value);
}
function cancel() {
note.update(v => {
v.show = false;
v.reset();
});
}
async function onSubmit(ev: React.MouseEvent) {
ev.stopPropagation();
await sendNote();
}
async function loadPreview() {
if (note.preview) {
note.update(v => (v.preview = undefined));
} else if (publisher) {
const tmpNote = await buildNote();
trackEvent("PostNotePreview");
note.update(v => (v.preview = tmpNote));
}
}
function getPreviewNote() {
if (note.preview) {
return (
<Note className="hover:bg-transparent" data={note.preview as TaggedNostrEvent} options={previewNoteOptions} />
);
}
}
function renderPollOptions() {
if (note.pollOptions) {
return (
<>
<h4>
<FormattedMessage defaultMessage="Poll Options" />
</h4>
{note.pollOptions?.map((a, i) => (
<div className="form-group w-max" key={`po-${i}`}>
<div>
<FormattedMessage defaultMessage="Option: {n}" values={{ n: i + 1 }} />
</div>
<div>
<input type="text" value={a} onChange={e => changePollOption(i, e.target.value)} />
{i > 1 && <CloseButton className="ml5" onClick={() => removePollOption(i)} />}
</div>
</div>
))}
<button onClick={() => note.update(v => (v.pollOptions = [...(note.pollOptions ?? []), ""]))}>
<Icon name="plus" size={14} />
</button>
</>
);
}
}
function changePollOption(i: number, v: string) {
if (note.pollOptions) {
const copy = [...note.pollOptions];
copy[i] = v;
note.update(v => (v.pollOptions = copy));
}
}
function removePollOption(i: number) {
if (note.pollOptions) {
const copy = [...note.pollOptions];
copy.splice(i, 1);
note.update(v => (v.pollOptions = copy));
}
}
function renderRelayCustomisation() {
return (
<div className="flex flex-col gap-2">
{Object.entries(relays)
.filter(el => el[1].write)
.map(a => a[0])
.map((r, i, a) => (
<div className="p flex items-center justify-between bg-gray br" key={r}>
<div>{r}</div>
<div>
<input
type="checkbox"
checked={!note.selectedCustomRelays || note.selectedCustomRelays.includes(r)}
onChange={e => {
note.update(
v =>
(v.selectedCustomRelays =
// set false if all relays selected
e.target.checked &&
note.selectedCustomRelays &&
note.selectedCustomRelays.length == a.length - 1
? undefined
: // otherwise return selectedCustomRelays with target relay added / removed
a.filter(el =>
el === r
? e.target.checked
: !note.selectedCustomRelays || note.selectedCustomRelays.includes(el),
)),
);
}}
/>
</div>
</div>
))}
</div>
);
}
/*function listAccounts() {
return LoginStore.getSessions().map(a => (
<MenuItem
onClick={ev => {
ev.stopPropagation = true;
LoginStore.switchAccount(a);
}}>
<ProfileImage pubkey={a} link={""} />
</MenuItem>
));
}*/
function noteCreatorAdvanced() {
return (
<>
<div>
<h4>
<FormattedMessage defaultMessage="Custom Relays" />
</h4>
<p>
<FormattedMessage defaultMessage="Send note to a subset of your write relays" />
</p>
{renderRelayCustomisation()}
</div>
<div className="flex flex-col g8">
<h4>
<FormattedMessage defaultMessage="Zap Splits" />
</h4>
<FormattedMessage defaultMessage="Zaps on this note will be split to the following users." />
<div className="flex flex-col g8">
{[...(note.zapSplits ?? [])].map((v: ZapTarget, i, arr) => (
<div className="flex items-center g8" key={`${v.name}-${v.value}`}>
<div className="flex flex-col flex-4 g4">
<h4>
<FormattedMessage defaultMessage="Recipient" />
</h4>
<input
type="text"
value={v.value}
onChange={e =>
note.update(
v => (v.zapSplits = arr.map((vv, ii) => (ii === i ? { ...vv, value: e.target.value } : vv))),
)
}
placeholder={formatMessage({ defaultMessage: "npub / nprofile / nostr address", id: "WvGmZT" })}
/>
</div>
<div className="flex flex-col flex-1 g4">
<h4>
<FormattedMessage defaultMessage="Weight" />
</h4>
<input
type="number"
min={0}
value={v.weight}
onChange={e =>
note.update(
v =>
(v.zapSplits = arr.map((vv, ii) =>
ii === i ? { ...vv, weight: Number(e.target.value) } : vv,
)),
)
}
/>
</div>
<div className="flex flex-col g4">
<div>&nbsp;</div>
<Icon
name="close"
onClick={() => note.update(v => (v.zapSplits = (v.zapSplits ?? []).filter((_v, ii) => ii !== i)))}
/>
</div>
</div>
))}
<button
type="button"
onClick={() =>
note.update(v => (v.zapSplits = [...(v.zapSplits ?? []), { type: "pubkey", value: "", weight: 1 }]))
}>
<FormattedMessage defaultMessage="Add" />
</button>
</div>
<span className="warning">
<FormattedMessage defaultMessage="Not all clients support this, you may still receive some zaps as if zap splits was not configured" />
</span>
</div>
<div className="flex flex-col g8">
<h4>
<FormattedMessage defaultMessage="Sensitive Content" />
</h4>
<FormattedMessage defaultMessage="Users must accept the content warning to show the content of your note." />
<input
className="w-max"
type="text"
value={note.sensitive}
onChange={e => note.update(v => (v.sensitive = e.target.value))}
maxLength={50}
minLength={1}
placeholder={formatMessage({
defaultMessage: "Reason",
id: "AkCxS/",
})}
/>
<span className="warning">
<FormattedMessage defaultMessage="Not all clients support this yet" />
</span>
</div>
</>
);
}
function noteCreatorFooter() {
return (
<div className="flex justify-between">
<div className="flex items-center gap-4 text-gray-light cursor-pointer">
<Avatar pubkey={publicKey ?? ""} user={profile} size={28} showTitle={true} />
<Menu
menuButton={
<AsyncIcon iconName="attachment" iconSize={24} className="hover:text-gray-superlight transition" />
}
menuClassName="ctx-menu no-icons">
<div className="close-menu-container">
{/* This menu item serves as a "close menu" button;
it allows the user to click anywhere nearby the menu to close it. */}
<MenuItem>
<div className="close-menu" />
</MenuItem>
</div>
<MenuItem onClick={() => note.update(s => (s.filePicker = "compact"))}>
<FormattedMessage defaultMessage="From Server" />
</MenuItem>
<MenuItem onClick={() => attachFile()}>
<FormattedMessage defaultMessage="From File" />
</MenuItem>
</Menu>
{note.pollOptions === undefined && !note.replyTo && (
<AsyncIcon
iconName="bar-chart"
iconSize={24}
onClick={() => note.update(v => (v.pollOptions = ["A", "B"]))}
className={classNames("hover:text-gray-superlight transition", {
"text-white": note.pollOptions !== undefined,
})}
/>
)}
<AsyncIcon
iconName="settings-outline"
iconSize={24}
onClick={() => note.update(v => (v.advanced = !v.advanced))}
className={classNames("hover:text-gray-superlight transition", { "text-white": note.advanced })}
/>
<span className="sm:inline hidden">
<FormattedMessage defaultMessage="Preview" />
</span>
<ToggleSwitch
onClick={() => loadPreview()}
size={40}
className={classNames({ active: Boolean(note.preview) })}
/>
</div>
<AsyncButton onClick={onSubmit} className="primary">
{note.replyTo ? <FormattedMessage defaultMessage="Reply" /> : <FormattedMessage defaultMessage="Send" />}
</AsyncButton>
</div>
);
}
const handlePaste: ClipboardEventHandler<HTMLDivElement> = evt => {
if (evt.clipboardData) {
const clipboardItems = evt.clipboardData.items;
const items: DataTransferItem[] = Array.from(clipboardItems).filter(function (item: DataTransferItem) {
// Filter the image items only
return /^image\//.test(item.type);
});
if (items.length === 0) {
return;
}
const item = items[0];
const blob = item.getAsFile();
if (blob) {
uploadFile(blob);
}
}
};
const handleDragOver = (event: DragEvent<HTMLTextAreaElement>) => {
event.preventDefault();
};
const handleDragLeave = (event: DragEvent<HTMLTextAreaElement>) => {
event.preventDefault();
};
const handleDrop = (event: DragEvent<HTMLTextAreaElement>) => {
event.preventDefault();
const droppedFiles = Array.from(event.dataTransfer.files);
droppedFiles.forEach(async file => {
await uploadFile(file);
});
};
function noteCreatorForm() {
return (
<>
{note.replyTo && (
<>
<h4>
<FormattedMessage defaultMessage="Reply To" />
</h4>
<div className="max-h-64 overflow-y-auto">
<Note className="hover:bg-transparent" data={note.replyTo} options={replyToNoteOptions} />
</div>
<hr className="border-border-color border-1 -mx-6" />
</>
)}
{note.quote && (
<>
<h4>
<FormattedMessage defaultMessage="Quote Repost" />
</h4>
<div className="max-h-64 overflow-y-auto">
<Note className="hover:bg-transparent" data={note.quote} options={quoteNoteOptions} />
</div>
<hr className="border-border-color border-1 -mx-6" />
</>
)}
{note.preview && getPreviewNote()}
{!note.preview && (
<div className="flex flex-col gap-4">
<div className="font-medium flex justify-between items-center">
<FormattedMessage defaultMessage="Compose a note" />
<AsyncIcon
iconName="x"
className="bg-gray rounded-full items-center justify-center flex p-1 cursor-pointer"
onClick={cancel}
/>
</div>
<div onPaste={handlePaste} className={classNames({ poll: Boolean(note.pollOptions) })}>
<Textarea
className="!border-none !resize-none !p-0 !rounded-none !text-sm"
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
autoFocus={true}
onChange={c => onChange(c)}
value={note.note}
onFocus={() => note.update(v => (v.active = true))}
onKeyDown={e => {
if (e.key === "Enter" && e.metaKey) {
sendNote().catch(console.warn);
}
}}
/>
{renderPollOptions()}
</div>
</div>
)}
{(note.otherEvents?.length ?? 0) > 0 && !note.preview && (
<div className="flex gap-2 flex-wrap">
{note.otherEvents
?.map(a => ({
event: a,
tags: readNip94Tags(a.tags),
}))
.filter(a => a.tags.url)
.map(a => (
<div key={a.tags.url} className="relative">
<img
className="object-cover w-[80px] h-[80px] !mt-0 rounded-lg"
src={addExtensionToNip94Url(a.tags)}
/>
<Icon
name="x"
className="absolute -top-[0.25rem] -right-[0.25rem] bg-gray rounded-full cursor-pointer"
onClick={() =>
note.update(
n => (n.otherEvents = n.otherEvents?.filter(b => readNip94Tags(b.tags).url !== a.tags.url)),
)
}
/>
</div>
))}
</div>
)}
{noteCreatorFooter()}
{note.error && <span className="error">{note.error}</span>}
{note.advanced && noteCreatorAdvanced()}
<Flyout
show={note.filePicker !== "hidden"}
width={note.filePicker !== "compact" ? "70vw" : undefined}
onClose={() => note.update(v => (v.filePicker = "hidden"))}
side="right"
title={
<div className="text-xl font-medium">
<FormattedMessage defaultMessage="Attach Media" />
</div>
}
actions={
<>
<IconButton
className="max-lg:!hidden"
icon={{
name: "expand",
}}
onClick={() => note.update(n => (n.filePicker = n.filePicker === "wide" ? "compact" : "wide"))}
/>
</>
}>
<div className="overflow-y-auto h-[calc(100%-2rem)]">
{note.filePicker !== "hidden" && (
<MediaServerFileList
onPicked={files => {
note.update(n => {
n.otherEvents ??= [];
n.otherEvents?.push(...files);
n.filePicker = "hidden";
});
}}
cols={note.filePicker === "compact" ? 2 : 6}
/>
)}
</div>
</Flyout>
</>
);
}
function reset() {
note.update(v => {
v.show = false;
});
}
if (!note.show) return null;
return (
<Modal id="note-creator" bodyClassName="modal-body gap-4" onClose={reset}>
{noteCreatorForm()}
</Modal>
);
}

View File

@ -1,87 +0,0 @@
import classNames from "classnames";
import { useMemo, useRef } from "react";
import { FormattedMessage } from "react-intl";
import { useLocation } from "react-router-dom";
import Icon from "@/Components/Icons/Icon";
import useKeyboardShortcut from "@/Hooks/useKeyboardShortcut";
import useLogin from "@/Hooks/useLogin";
import { useNoteCreator } from "@/State/NoteCreator";
import { isFormElement } from "@/Utils";
import { NoteCreator } from "./NoteCreator";
export const NoteCreatorButton = ({
className,
alwaysShow,
showText,
withModal,
}: {
className?: string;
alwaysShow?: boolean;
showText?: boolean;
withModal: boolean;
}) => {
const buttonRef = useRef<HTMLButtonElement>(null);
const location = useLocation();
const { readonly } = useLogin(s => ({ readonly: s.readonly }));
const { show, replyTo, update } = useNoteCreator(v => ({ show: v.show, replyTo: v.replyTo, update: v.update }));
useKeyboardShortcut("n", event => {
// if event happened in a form element, do nothing, otherwise focus on search input
if (event.target && !isFormElement(event.target as HTMLElement)) {
event.preventDefault();
if (buttonRef.current) {
buttonRef.current.click();
}
}
});
const shouldHideNoteCreator = useMemo(() => {
if (alwaysShow) {
return false;
}
const isReply = replyTo && show;
const hideOn = [
"/settings",
"/messages",
"/new",
"/login",
"/donate",
"/e",
"/nevent",
"/note1",
"/naddr",
"/subscribe",
];
return (readonly || hideOn.some(a => location.pathname.startsWith(a))) && !isReply;
}, [location, readonly]);
return (
<>
{!shouldHideNoteCreator && (
<button
ref={buttonRef}
className={classNames(
"aspect-square flex flex-row items-center primary rounded-full",
{ "xl:aspect-auto": showText },
className,
)}
onClick={() =>
update(v => {
v.replyTo = undefined;
v.show = true;
})
}>
<Icon name="plus" size={16} />
{showText && (
<span className="ml-2 hidden xl:inline">
<FormattedMessage defaultMessage="New Note" />
</span>
)}
</button>
)}
{withModal && <NoteCreator key="global-note-creator" />}
</>
);
};

View File

@ -1,60 +0,0 @@
import { sanitizeRelayUrl, unwrap } from "@snort/shared";
import { OkResponse } from "@snort/system";
import { useState } from "react";
import { useIntl } from "react-intl";
import AsyncButton from "@/Components/Button/AsyncButton";
import IconButton from "@/Components/Button/IconButton";
import Icon from "@/Components/Icons/Icon";
import useEventPublisher from "@/Hooks/useEventPublisher";
import useLogin from "@/Hooks/useLogin";
import { getRelayName } from "@/Utils";
export function OkResponseRow({ rsp, close }: { rsp: OkResponse; close: () => void }) {
const [r, setResult] = useState(rsp);
const { formatMessage } = useIntl();
const { system } = useEventPublisher();
const login = useLogin();
async function removeRelayFromResult(r: OkResponse) {
await login.state.removeRelay(unwrap(sanitizeRelayUrl(r.relay)), true);
close();
}
async function retryPublish(r: OkResponse) {
const rsp = await system.WriteOnceToRelay(unwrap(sanitizeRelayUrl(r.relay)), r.event);
setResult(rsp);
}
return (
<div className="flex items-center g16">
<div className="flex flex-col grow g4">
<b>{getRelayName(r.relay)}</b>
{r.message && <small>{r.message}</small>}
</div>
{!r.ok && (
<div className="flex g8">
<AsyncButton
onClick={() => retryPublish(r)}
className="p4 br-compact flex items-center secondary"
title={formatMessage({
defaultMessage: "Retry publishing",
id: "9kSari",
})}>
<Icon name="refresh-ccw-01" />
</AsyncButton>
<AsyncButton
onClick={() => removeRelayFromResult(r)}
className="p4 br-compact flex items-center secondary"
title={formatMessage({
defaultMessage: "Remove from my relays",
id: "UJTWqI",
})}>
<Icon name="trash-01" className="trash-icon" />
</AsyncButton>
</div>
)}
<IconButton icon={{ name: "x" }} onClick={close} />
</div>
);
}

View File

@ -1,28 +0,0 @@
import { removeUndefined } from "@snort/shared";
import { NostrEvent, OkResponse, SystemInterface } from "@snort/system";
export async function sendEventToRelays(
system: SystemInterface,
ev: NostrEvent,
customRelays?: Array<string>,
setResults?: (x: Array<OkResponse>) => void,
) {
if (customRelays) {
system.HandleEvent("*", { ...ev, relays: [] });
return removeUndefined(
await Promise.all(
customRelays.map(async r => {
try {
return await system.WriteOnceToRelay(r, ev);
} catch (e) {
console.error(e);
}
}),
),
);
} else {
const responses: OkResponse[] = await system.BroadcastEvent(ev);
setResults?.(responses);
return responses;
}
}

View File

@ -1,93 +0,0 @@
import "./EventComponent.css";
import { EventKind, NostrEvent, TaggedNostrEvent } from "@snort/system";
import { memo, ReactNode } from "react";
import PubkeyList from "@/Components/Embed/PubkeyList";
import ZapstrEmbed from "@/Components/Embed/ZapstrEmbed";
import ErrorBoundary from "@/Components/ErrorBoundary";
import { NostrFileElement } from "@/Components/Event/NostrFileHeader";
import NoteReaction from "@/Components/Event/NoteReaction";
import { ZapGoal } from "@/Components/Event/ZapGoal";
import { LiveEvent } from "@/Components/LiveStream/LiveEvent";
import ProfilePreview from "@/Components/User/ProfilePreview";
import { LongFormText } from "./LongFormText";
import { Note } from "./Note/Note";
export interface NotePropsOptions {
isRoot?: boolean;
showHeader?: boolean;
showContextMenu?: boolean;
showProfileCard?: boolean;
showTime?: boolean;
showPinned?: boolean;
showBookmarked?: boolean;
showFooter?: boolean;
showReactionsLink?: boolean;
showMedia?: boolean;
canUnpin?: boolean;
canUnbookmark?: boolean;
canClick?: boolean;
showMediaSpotlight?: boolean;
longFormPreview?: boolean;
truncate?: boolean;
}
export interface NoteProps {
data: TaggedNostrEvent;
className?: string;
highlight?: boolean;
ignoreModeration?: boolean;
onClick?: (e: TaggedNostrEvent) => void;
depth?: number;
highlightText?: string;
threadChains?: Map<string, Array<NostrEvent>>;
context?: ReactNode;
options?: NotePropsOptions;
waitUntilInView?: boolean;
}
export default memo(function EventComponent(props: NoteProps) {
const { data: ev, className } = props;
let content;
switch (ev.kind) {
case EventKind.Repost:
content = <NoteReaction data={ev} key={ev.id} root={undefined} depth={(props.depth ?? 0) + 1} />;
break;
case EventKind.FileHeader:
content = <NostrFileElement ev={ev} />;
break;
case EventKind.ZapstrTrack:
content = <ZapstrEmbed ev={ev} />;
break;
case EventKind.FollowSet:
case EventKind.ContactList:
content = <PubkeyList ev={ev} className={className} />;
break;
case EventKind.LiveEvent:
content = <LiveEvent ev={ev} />;
break;
case EventKind.SetMetadata:
content = <ProfilePreview actions={<></>} pubkey={ev.pubkey} />;
break;
case 9041: // Assuming 9041 is a valid EventKind
content = <ZapGoal ev={ev} />;
break;
case EventKind.LongFormTextNote:
content = (
<LongFormText
ev={ev}
isPreview={props.options?.longFormPreview ?? false}
onClick={() => props.onClick?.(ev)}
truncate={props.options?.truncate}
/>
);
break;
default:
content = <Note {...props} />;
}
return <ErrorBoundary>{content}</ErrorBoundary>;
});

View File

@ -1,15 +0,0 @@
import Progress from "@/Components/Progress/Progress";
import { UploadProgress } from "@/Utils/Upload";
export default function FileUploadProgress({ progress }: { progress: Array<UploadProgress> }) {
return (
<div className="flex flex-col g8">
{progress.map(p => (
<div key={p.id} className="flex flex-col g2" id={p.id}>
{p.file.name}
<Progress value={p.progress} status={p.stage} />
</div>
))}
</div>
);
}

Some files were not shown because too many files have changed in this diff Show More