Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| af822e7c63 | |||
| 4ac4f32b45 | |||
| 286a0fcadc | |||
| 013584da06 | |||
| 19df70baed | |||
| cf7384523a | |||
| 64729b9804 | |||
| 954339c3b9 |
@@ -37,6 +37,9 @@ deploy.sh
|
||||
# Build-time configuration
|
||||
ditto.json
|
||||
|
||||
# DM message sounds (copied from node_modules by postinstall)
|
||||
public/sounds/
|
||||
|
||||
# Android build outputs and sensitive files
|
||||
*.aab
|
||||
resources/
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
FROM node:22-alpine AS builder
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache git
|
||||
COPY package*.json ./
|
||||
COPY .npmrc ./
|
||||
COPY scripts/ ./scripts/
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -0,0 +1,6 @@
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "80"
|
||||
@@ -0,0 +1,31 @@
|
||||
services:
|
||||
web:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "8082:80"
|
||||
volumes:
|
||||
- ./nginx.dev.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- ./dist:/usr/share/nginx/html:ro
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- vite
|
||||
networks:
|
||||
- ditto-network
|
||||
|
||||
vite:
|
||||
image: node:22-alpine
|
||||
working_dir: /app
|
||||
# Use host node_modules (no anonymous volume) so new deps added after merge
|
||||
# are picked up after a plain "npm install" on the host and container restart.
|
||||
command: sh -c "npm install && npm run dev"
|
||||
volumes:
|
||||
- .:/app
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
networks:
|
||||
- ditto-network
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
ditto-network:
|
||||
driver: bridge
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript application/json;
|
||||
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript application/json;
|
||||
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
location /health {
|
||||
access_log off;
|
||||
return 200 "healthy\n";
|
||||
add_header Content-Type text/plain;
|
||||
}
|
||||
|
||||
location / {
|
||||
resolver 127.0.0.11 valid=10s;
|
||||
set $vite_backend http://vite:8080;
|
||||
proxy_pass $vite_backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
}
|
||||
Generated
+142
-3
@@ -90,6 +90,7 @@
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@samthomson/nostr-messaging": "^0.14.0",
|
||||
"@sentry/react": "^10.42.0",
|
||||
"@tanstack/react-query": "^5.56.2",
|
||||
"@unhead/addons": "^2.0.10",
|
||||
@@ -180,6 +181,7 @@
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -2467,6 +2469,7 @@
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "2.0.5",
|
||||
@@ -2480,6 +2483,7 @@
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
||||
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
@@ -2489,6 +2493,7 @@
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
||||
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.scandir": "2.1.5",
|
||||
@@ -5714,6 +5719,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5727,6 +5733,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5740,6 +5747,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5753,6 +5761,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5766,6 +5775,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5779,6 +5789,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5792,6 +5803,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5805,6 +5817,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5818,6 +5831,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5831,6 +5845,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5844,6 +5859,7 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5857,6 +5873,7 @@
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5870,6 +5887,7 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5883,6 +5901,7 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5896,6 +5915,7 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5909,6 +5929,7 @@
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5922,6 +5943,7 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5935,6 +5957,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5948,6 +5971,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5961,6 +5985,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5974,6 +5999,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5987,6 +6013,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -6000,6 +6027,7 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -6013,6 +6041,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -6026,12 +6055,46 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@samthomson/nostr-messaging": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@samthomson/nostr-messaging/-/nostr-messaging-0.14.0.tgz",
|
||||
"integrity": "sha512-Ykqo+XJEPBPcfwZxHG8yvVseaUsvdzvfPrU5wY996H7tKbzeiJEIi6MVTaTkhUvdojeTne7HcPTm9As+hrKK3A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"blurhash": "^2.0.5",
|
||||
"buffer": "^6.0.3",
|
||||
"fuse.js": "^7.1.0",
|
||||
"idb": "^8.0.3",
|
||||
"nostr-tools": "^2.13.0",
|
||||
"react-blurhash": "^0.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nostrify/nostrify": ">=0.47.0",
|
||||
"@nostrify/react": "^0.2.0",
|
||||
"@radix-ui/react-avatar": "^1.1.0",
|
||||
"@radix-ui/react-dialog": "^1.1.2",
|
||||
"@radix-ui/react-popover": "^1.1.0",
|
||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-separator": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.4",
|
||||
"@tanstack/react-query": "^5.56.2",
|
||||
"clsx": "^2.0.0",
|
||||
"lucide-react": "^0.462.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.0.0",
|
||||
"tailwind-merge": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@scure/base": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz",
|
||||
@@ -6586,6 +6649,7 @@
|
||||
"version": "19.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
@@ -6595,7 +6659,7 @@
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
||||
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
@@ -7283,12 +7347,14 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
@@ -7302,6 +7368,7 @@
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
@@ -7468,6 +7535,7 @@
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -7510,6 +7578,7 @@
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
@@ -7634,6 +7703,7 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
@@ -7767,6 +7837,7 @@
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
@@ -7791,6 +7862,7 @@
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
@@ -7918,6 +7990,7 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
@@ -7968,6 +8041,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"cssesc": "bin/cssesc"
|
||||
@@ -8298,6 +8372,7 @@
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
@@ -8310,6 +8385,7 @@
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dom-accessibility-api": {
|
||||
@@ -8722,6 +8798,7 @@
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
@@ -8738,6 +8815,7 @@
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
@@ -8764,6 +8842,7 @@
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
||||
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.4"
|
||||
@@ -8802,6 +8881,7 @@
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
@@ -8881,6 +8961,7 @@
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -8895,11 +8976,25 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/fuse.js": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.3.0.tgz",
|
||||
"integrity": "sha512-plz8RVjfcDedTGfVngWH1jmJvBvAwi1v2jecfDerbEnMcmOYUEEwKFTHbNoCiYyzaK2Ws8lABkTCcRSqCY1q4w==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/krisk"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
@@ -8935,6 +9030,7 @@
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.3"
|
||||
@@ -8977,6 +9073,7 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
@@ -9265,6 +9362,7 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
@@ -9277,6 +9375,7 @@
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
@@ -9318,6 +9417,7 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -9336,6 +9436,7 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
@@ -9406,6 +9507,7 @@
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
@@ -9454,6 +9556,7 @@
|
||||
"version": "1.21.7",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
@@ -9888,6 +9991,7 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -9900,6 +10004,7 @@
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
@@ -10359,6 +10464,7 @@
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
@@ -10950,6 +11056,7 @@
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
@@ -11027,6 +11134,7 @@
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"any-promise": "^1.0.0",
|
||||
@@ -11096,6 +11204,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -11166,6 +11275,7 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
@@ -11335,6 +11445,7 @@
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
@@ -11370,6 +11481,7 @@
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
@@ -11382,6 +11494,7 @@
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -11391,6 +11504,7 @@
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
||||
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
@@ -11463,6 +11577,7 @@
|
||||
"version": "15.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
||||
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.0.0",
|
||||
@@ -11480,6 +11595,7 @@
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
|
||||
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"camelcase-css": "^2.0.1"
|
||||
@@ -11499,6 +11615,7 @@
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
||||
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -11534,6 +11651,7 @@
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
||||
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -11559,6 +11677,7 @@
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
@@ -11586,6 +11705,7 @@
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/powershell-utils": {
|
||||
@@ -11920,6 +12040,7 @@
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -12223,6 +12344,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pify": "^2.3.0"
|
||||
@@ -12247,6 +12369,7 @@
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
@@ -12447,6 +12570,7 @@
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.16.0",
|
||||
@@ -12477,6 +12601,7 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
||||
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"iojs": ">=1.0.0",
|
||||
@@ -12632,7 +12757,7 @@
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
||||
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
@@ -12919,6 +13044,7 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -13251,6 +13377,7 @@
|
||||
"version": "3.35.1",
|
||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
|
||||
"integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
@@ -13286,6 +13413,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -13315,6 +13443,7 @@
|
||||
"version": "3.4.17",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
@@ -13361,6 +13490,7 @@
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
@@ -13391,6 +13521,7 @@
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"any-promise": "^1.0.0"
|
||||
@@ -13400,6 +13531,7 @@
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
||||
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"thenify": ">= 3.1.0 < 4"
|
||||
@@ -13442,6 +13574,7 @@
|
||||
"version": "0.2.15",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
@@ -13458,6 +13591,7 @@
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
@@ -13475,6 +13609,7 @@
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -13537,6 +13672,7 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
@@ -13618,6 +13754,7 @@
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
@@ -13643,7 +13780,7 @@
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -13970,6 +14107,7 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vaul": {
|
||||
@@ -15718,6 +15856,7 @@
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
|
||||
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@samthomson/nostr-messaging": "^0.14.0",
|
||||
"@sentry/react": "^10.42.0",
|
||||
"@tanstack/react-query": "^5.56.2",
|
||||
"@unhead/addons": "^2.0.10",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copy default message sounds from @samthomson/nostr-messaging package
|
||||
if [ -d "node_modules/@samthomson/nostr-messaging/assets/sounds" ]; then
|
||||
mkdir -p public/sounds
|
||||
cp node_modules/@samthomson/nostr-messaging/assets/sounds/*.mp3 public/sounds/
|
||||
echo "Copied message sounds to public/sounds/"
|
||||
fi
|
||||
+4
-1
@@ -16,7 +16,7 @@ import NostrProvider from "@/components/NostrProvider";
|
||||
import { NostrSync } from "@/components/NostrSync";
|
||||
import { PlausibleProvider } from "@/components/PlausibleProvider";
|
||||
import { SentryProvider } from "@/components/SentryProvider";
|
||||
|
||||
import { DMProviderWrapper } from "@/components/DMProviderWrapper";
|
||||
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { useNsecPasteGuard } from "@/hooks/useNsecPasteGuard";
|
||||
@@ -124,6 +124,7 @@ const hardcodedConfig: AppConfig = {
|
||||
sidebarOrder: [
|
||||
"feed",
|
||||
"notifications",
|
||||
"dms",
|
||||
"search",
|
||||
"blobbi",
|
||||
"badges",
|
||||
@@ -207,6 +208,7 @@ export function App() {
|
||||
<NativeNotifications />
|
||||
|
||||
<NWCProvider>
|
||||
<DMProviderWrapper>
|
||||
<DMProvider config={dmConfig}>
|
||||
<EmotionDevProvider>
|
||||
<TooltipProvider>
|
||||
@@ -216,6 +218,7 @@ export function App() {
|
||||
</TooltipProvider>
|
||||
</EmotionDevProvider>
|
||||
</DMProvider>
|
||||
</DMProviderWrapper>
|
||||
</NWCProvider>
|
||||
</NostrProvider>
|
||||
</NostrLoginProvider>
|
||||
|
||||
@@ -79,6 +79,8 @@ const WikipediaPage = lazy(() => import("./pages/WikipediaPage").then(m => ({ de
|
||||
const WorldPage = lazy(() => import("./pages/WorldPage").then(m => ({ default: m.WorldPage })));
|
||||
const FollowPage = lazy(() => import("./pages/FollowPage").then(m => ({ default: m.FollowPage })));
|
||||
const RemoteLoginSuccessPage = lazy(() => import("./pages/RemoteLoginSuccessPage").then(m => ({ default: m.RemoteLoginSuccessPage })));
|
||||
const MessagesPage = lazy(() => import("./pages/MessagesPage").then(m => ({ default: m.MessagesPage })));
|
||||
const MessagingSettings = lazy(() => import("./pages/MessagingSettings"));
|
||||
|
||||
const pollsDef = getExtraKindDef("polls")!;
|
||||
const colorsDef = getExtraKindDef("colors")!;
|
||||
@@ -160,6 +162,8 @@ export function AppRouter() {
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/feed" element={<Index />} />
|
||||
<Route path="/notifications" element={<NotificationsPage />} />
|
||||
<Route path="/chats" element={<MessagesPage />} />
|
||||
<Route path="/settings/messaging" element={<MessagingSettings />} />
|
||||
<Route path="/search" element={<SearchPage />} />
|
||||
<Route path="/trends" element={<TrendsPage />} />
|
||||
<Route path="/profile" element={<ProfileRedirect />} />
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import { type ReactNode, useMemo } from 'react';
|
||||
import { useNostr } from '@nostrify/react';
|
||||
import { DMProvider } from '@samthomson/nostr-messaging/core';
|
||||
import type { NostrEvent } from '@nostrify/nostrify';
|
||||
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { useAppContext } from '@/hooks/useAppContext';
|
||||
import { useNostrPublish } from '@/hooks/useNostrPublish';
|
||||
import { useUploadFile } from '@/hooks/useUploadFile';
|
||||
import { useAuthorsBatch } from '@/hooks/useAuthorsBatch';
|
||||
import { useProfileSupplementary } from '@/hooks/useProfileData';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
import { toast } from '@/hooks/useToast';
|
||||
import { getDisplayName } from '@/lib/getDisplayName';
|
||||
import { APP_NEW_MESSAGE_SOUNDS } from '@/lib/messagingSounds';
|
||||
|
||||
interface DMProviderWrapperProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function DMProviderWrapper({ children }: DMProviderWrapperProps) {
|
||||
const { nostr } = useNostr();
|
||||
const { user } = useCurrentUser();
|
||||
const { config } = useAppContext();
|
||||
const { mutateAsync: publishEvent } = useNostrPublish();
|
||||
const { mutateAsync: uploadFileMutation } = useUploadFile();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
// Get the current user's follows
|
||||
const { data: profileData } = useProfileSupplementary(user?.pubkey);
|
||||
const follows = useMemo(() => profileData?.following ?? [], [profileData]);
|
||||
|
||||
// Wrap publishEvent to match the expected signature
|
||||
const handlePublishEvent = async (event: Omit<NostrEvent, 'id' | 'pubkey' | 'sig'>): Promise<void> => {
|
||||
await publishEvent(event);
|
||||
};
|
||||
|
||||
// Wrap uploadFile to return just the URL string
|
||||
const handleUploadFile = async (file: File): Promise<string> => {
|
||||
const tags = await uploadFileMutation(file);
|
||||
return tags[0][1]; // Return the URL from the first tag
|
||||
};
|
||||
|
||||
// Wrap getDisplayName to match the expected signature
|
||||
const handleGetDisplayName = (pubkey: string, metadata?: Parameters<typeof getDisplayName>[0]) => {
|
||||
return getDisplayName(metadata, pubkey);
|
||||
};
|
||||
|
||||
// Wrap toast to match the expected signature
|
||||
const handleNotify = (options: { title?: string; description?: string; variant?: 'default' | 'destructive' }) => {
|
||||
toast({
|
||||
title: options.title,
|
||||
description: options.description,
|
||||
variant: options.variant,
|
||||
});
|
||||
};
|
||||
|
||||
const messaging = useMemo(() => config.messaging ?? {}, [config.messaging]);
|
||||
|
||||
// Discovery relays for DM inbox discovery
|
||||
const discoveryRelays = useMemo(() => {
|
||||
if (messaging.discoveryRelays?.length) {
|
||||
return messaging.discoveryRelays;
|
||||
}
|
||||
return config.relayMetadata.relays
|
||||
.filter(r => r.read)
|
||||
.map(r => r.url);
|
||||
}, [messaging.discoveryRelays, config.relayMetadata.relays]);
|
||||
|
||||
const relayMode = messaging.relayMode ?? 'hybrid';
|
||||
const messagingEnabled = messaging.enabled ?? false;
|
||||
const renderInlineMedia = messaging.renderInlineMedia ?? true;
|
||||
const soundEnabled = messaging.soundEnabled ?? false;
|
||||
const soundId = messaging.soundId ?? APP_NEW_MESSAGE_SOUNDS[0]?.id ?? '';
|
||||
const devMode = messaging.devMode ?? false;
|
||||
|
||||
return (
|
||||
<DMProvider
|
||||
nostr={nostr}
|
||||
user={user ?? null}
|
||||
messagingConfig={{
|
||||
enabled: messagingEnabled,
|
||||
discoveryRelays,
|
||||
relayMode,
|
||||
renderInlineMedia,
|
||||
devMode,
|
||||
appName: config.appName,
|
||||
appDescription: `Direct messages on ${config.appName}`,
|
||||
soundPref: {
|
||||
options: APP_NEW_MESSAGE_SOUNDS,
|
||||
value: { enabled: soundEnabled, soundId },
|
||||
onChange: () => {},
|
||||
},
|
||||
}}
|
||||
onNotify={handleNotify}
|
||||
getDisplayName={handleGetDisplayName}
|
||||
fetchAuthorsBatch={useAuthorsBatch}
|
||||
publishEvent={handlePublishEvent}
|
||||
uploadFile={handleUploadFile}
|
||||
follows={follows}
|
||||
ui={{
|
||||
showShorts: false,
|
||||
showSearch: true,
|
||||
isMobile,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</DMProvider>
|
||||
);
|
||||
}
|
||||
@@ -241,6 +241,18 @@ export interface AppConfig {
|
||||
savedFeeds: SavedFeed[];
|
||||
/** Image upload quality: "compressed" resizes/optimizes, "original" uploads as-is. Default: "compressed". */
|
||||
imageQuality: 'compressed' | 'original';
|
||||
/** Messaging configuration (custom sounds, discovery relays, etc.) */
|
||||
messaging?: {
|
||||
/** Whether direct messaging is enabled for this account/session. Default: false. */
|
||||
enabled?: boolean;
|
||||
customSoundUrl?: string;
|
||||
discoveryRelays?: string[];
|
||||
relayMode?: 'discovery' | 'hybrid' | 'strict_outbox';
|
||||
renderInlineMedia?: boolean;
|
||||
soundEnabled?: boolean;
|
||||
soundId?: string;
|
||||
devMode?: boolean;
|
||||
};
|
||||
/** Hex pubkey of the curator whose follow list defines the Ditto feed. */
|
||||
curatorPubkey?: string;
|
||||
/** Wildcard domain used for iframe sandboxing (e.g. "iframe.diy"). Default: "iframe.diy". */
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { useAuthors } from './useAuthors';
|
||||
|
||||
/**
|
||||
* Batch fetch author profiles for DM messaging integration.
|
||||
*
|
||||
* This hook wraps useAuthors to match the interface expected by
|
||||
* @samthomson/nostr-messaging's DMProvider.
|
||||
*
|
||||
* @param pubkeys - Array of pubkeys to fetch profiles for
|
||||
* @returns Query result with map of pubkey -> AuthorData
|
||||
*/
|
||||
export function useAuthorsBatch(pubkeys: string[]) {
|
||||
return useAuthors(pubkeys);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Re-exports DM hooks from the @samthomson/nostr-messaging package.
|
||||
* Separated from DMProviderWrapper to avoid Fast Refresh warnings.
|
||||
*/
|
||||
export {
|
||||
useDMContext,
|
||||
useConversationMessages,
|
||||
} from '@samthomson/nostr-messaging/core';
|
||||
@@ -0,0 +1,10 @@
|
||||
import { DEFAULT_NEW_MESSAGE_SOUNDS, type NewMessageSoundOption } from '@samthomson/nostr-messaging/core';
|
||||
|
||||
export const APP_NEW_MESSAGE_SOUNDS: NewMessageSoundOption[] = [
|
||||
...DEFAULT_NEW_MESSAGE_SOUNDS,
|
||||
{
|
||||
id: 'ditto',
|
||||
label: 'Ditto',
|
||||
url: '/custom-sounds/ditto.mp3',
|
||||
},
|
||||
];
|
||||
@@ -245,6 +245,16 @@ export const AppConfigSchema = z.object({
|
||||
})
|
||||
).optional().default([]),
|
||||
imageQuality: z.enum(['compressed', 'original']),
|
||||
messaging: z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
customSoundUrl: z.string().optional(),
|
||||
discoveryRelays: z.array(z.string().url()).optional(),
|
||||
relayMode: z.enum(['discovery', 'hybrid', 'strict_outbox']).optional(),
|
||||
renderInlineMedia: z.boolean().optional(),
|
||||
soundEnabled: z.boolean().optional(),
|
||||
soundId: z.string().optional(),
|
||||
devMode: z.boolean().optional(),
|
||||
}).optional(),
|
||||
curatorPubkey: z.string().regex(/^[0-9a-f]{64}$/i).optional(),
|
||||
sandboxDomain: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
Earth,
|
||||
Film,
|
||||
HelpCircle,
|
||||
|
||||
Mail,
|
||||
MessageSquare,
|
||||
MessageSquareMore,
|
||||
Mic,
|
||||
@@ -110,6 +110,13 @@ export const SIDEBAR_ITEMS: SidebarItemDef[] = [
|
||||
icon: Bell,
|
||||
requiresAuth: true,
|
||||
},
|
||||
{
|
||||
id: "dms",
|
||||
label: "Chats",
|
||||
path: "/chats",
|
||||
icon: Mail,
|
||||
requiresAuth: true,
|
||||
},
|
||||
{ id: "search", label: "Search", path: "/search", icon: Search },
|
||||
{ id: "trends", label: "Trends", path: "/trends", icon: TrendingUp },
|
||||
{
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { DMMessagingInterface } from '@samthomson/nostr-messaging/ui';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useLayoutOptions } from '@/contexts/LayoutContext';
|
||||
import { useAppContext } from '@/hooks/useAppContext';
|
||||
|
||||
export function MessagesPage() {
|
||||
const { config } = useAppContext();
|
||||
const messagingEnabled = config.messaging?.enabled ?? false;
|
||||
|
||||
// Hide the right sidebar and expand the main content area for messaging.
|
||||
// noOverscroll: avoid pb-overscroll on the main column so this fixed-height layout doesn't get extra scroll.
|
||||
useLayoutOptions({
|
||||
rightSidebar: null,
|
||||
noMaxWidth: true,
|
||||
noOverscroll: true,
|
||||
wrapperClassName: 'max-w-full',
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="h-dvh flex flex-col">
|
||||
{messagingEnabled ? (
|
||||
<DMMessagingInterface />
|
||||
) : (
|
||||
<div className="flex-1 flex items-center justify-center p-6">
|
||||
<div className="max-w-md text-center space-y-3">
|
||||
<h2 className="text-xl font-semibold">Chats are turned off</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Enable messaging in Settings to start using chats.
|
||||
</p>
|
||||
<Link to="/settings/messaging" className="inline-block text-sm text-primary hover:underline">
|
||||
Open Messaging Settings
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useAppContext } from '@/hooks/useAppContext';
|
||||
import { useDMContext } from '@/hooks/useDMHooks';
|
||||
import { RelayListManager } from '@/components/RelayListManager';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { RefreshCw, AlertCircle, Play } from 'lucide-react';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { getMediaCacheStats, type RelayMode } from '@samthomson/nostr-messaging/core';
|
||||
import { IntroImage } from '@/components/IntroImage';
|
||||
import { APP_NEW_MESSAGE_SOUNDS } from '@/lib/messagingSounds';
|
||||
|
||||
export default function MessagingSettings() {
|
||||
const { config, updateConfig } = useAppContext();
|
||||
const {
|
||||
subscriptions,
|
||||
messagingState,
|
||||
isLoading: dmIsLoading,
|
||||
clearCacheAndRefetch,
|
||||
} = useDMContext();
|
||||
|
||||
const messaging = config.messaging ?? {};
|
||||
|
||||
const [mediaCacheStats, setMediaCacheStats] = useState<{ count: number; size: number } | null>(null);
|
||||
|
||||
useSeoMeta({
|
||||
title: 'Messages | Settings | Ditto',
|
||||
description: 'Configure your direct messaging settings.',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
getMediaCacheStats().then(setMediaCacheStats).catch(() => {
|
||||
setMediaCacheStats({ count: 0, size: 0 });
|
||||
});
|
||||
}, []);
|
||||
|
||||
const preloadedSoundsRef = useRef<Map<string, HTMLAudioElement>>(new Map());
|
||||
useEffect(() => {
|
||||
const map = new Map<string, HTMLAudioElement>();
|
||||
APP_NEW_MESSAGE_SOUNDS.forEach((sound) => {
|
||||
const audio = new Audio(sound.url);
|
||||
audio.volume = 0.5;
|
||||
audio.preload = 'auto';
|
||||
map.set(sound.url, audio);
|
||||
});
|
||||
preloadedSoundsRef.current = map;
|
||||
return () => {
|
||||
map.clear();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const relayMode = messaging.relayMode ?? 'hybrid';
|
||||
const messagingEnabled = messaging.enabled ?? false;
|
||||
const renderInlineMedia = messaging.renderInlineMedia ?? true;
|
||||
const soundEnabled = messaging.soundEnabled ?? false;
|
||||
const soundId = messaging.soundId ?? APP_NEW_MESSAGE_SOUNDS[0]?.id ?? '';
|
||||
const devMode = messaging.devMode ?? false;
|
||||
|
||||
const handleMessagingEnabledChange = (checked: boolean) => {
|
||||
updateConfig((prev) => ({
|
||||
...prev,
|
||||
messaging: { ...messaging, enabled: checked },
|
||||
}));
|
||||
};
|
||||
|
||||
const handleRelayModeChange = (mode: string) => {
|
||||
updateConfig((prev) => ({
|
||||
...prev,
|
||||
messaging: { ...messaging, relayMode: mode as RelayMode },
|
||||
}));
|
||||
};
|
||||
|
||||
const handleRenderInlineMediaChange = (checked: boolean) => {
|
||||
updateConfig((prev) => ({
|
||||
...prev,
|
||||
messaging: { ...messaging, renderInlineMedia: checked },
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSoundIdChange = (id: string) => {
|
||||
updateConfig((prev) => ({
|
||||
...prev,
|
||||
messaging: { ...messaging, soundEnabled: true, soundId: id },
|
||||
}));
|
||||
};
|
||||
|
||||
const handleDevModeChange = (checked: boolean) => {
|
||||
updateConfig((prev) => ({
|
||||
...prev,
|
||||
messaging: { ...messaging, devMode: checked },
|
||||
}));
|
||||
};
|
||||
|
||||
const handlePlaySound = useCallback((soundUrl: string) => {
|
||||
try {
|
||||
const preloaded = preloadedSoundsRef.current.get(soundUrl);
|
||||
if (preloaded) {
|
||||
preloaded.currentTime = 0;
|
||||
preloaded.play().catch(() => {});
|
||||
} else {
|
||||
const audio = new Audio(soundUrl);
|
||||
audio.volume = 0.5;
|
||||
audio.play().catch(() => {});
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClearCache = async () => {
|
||||
if (confirm('This will clear all cached messages and re-fetch from relays. Continue?')) {
|
||||
await clearCacheAndRefetch();
|
||||
const stats = await getMediaCacheStats();
|
||||
setMediaCacheStats(stats);
|
||||
}
|
||||
};
|
||||
|
||||
const formatBytes = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
const conversationCount = messagingState ? Object.keys(messagingState.conversationMetadata).length : 0;
|
||||
const totalMessages = messagingState
|
||||
? Object.values(messagingState.conversationMessages).reduce((sum, msgs) => sum + msgs.length, 0)
|
||||
: 0;
|
||||
const lastSync = messagingState?.syncState?.lastCacheTime
|
||||
? new Date(messagingState.syncState.lastCacheTime).toLocaleString()
|
||||
: 'Never';
|
||||
|
||||
return (
|
||||
<main className="">
|
||||
<div className="px-4 pt-4 pb-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<Link to="/settings" className="p-2 -ml-2 rounded-full hover:bg-secondary transition-colors">
|
||||
<ArrowLeft className="size-5" />
|
||||
</Link>
|
||||
<div>
|
||||
<h1 className="text-xl font-bold">Messages</h1>
|
||||
<p className="text-sm text-muted-foreground mt-0.5">
|
||||
Configure direct messaging settings, relays, and cache
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4">
|
||||
<div className="flex items-center gap-4 px-3 pt-2 pb-4">
|
||||
<IntroImage src="/messaging-intro.png" />
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-sm font-semibold">Direct Messaging</h2>
|
||||
<p className="text-xs text-muted-foreground mt-1 leading-relaxed">
|
||||
Manage your encrypted messaging settings and relay connections
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<Card className="bg-card/50 backdrop-blur-sm border-border/50">
|
||||
<CardHeader>
|
||||
<CardTitle>Messaging</CardTitle>
|
||||
<CardDescription>
|
||||
Enable or disable chats in Ditto
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="messaging-enabled">Enable Messaging</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Turn chats on to use inbox, sync, and messaging features
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="messaging-enabled"
|
||||
checked={messagingEnabled}
|
||||
onCheckedChange={handleMessagingEnabledChange}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{!messagingEnabled && (
|
||||
<Card className="bg-card/50 backdrop-blur-sm border-border/50">
|
||||
<CardContent className="pt-6">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Messaging is currently off. Enable it above to reveal relay, cache, and advanced chat settings.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{messagingEnabled && (
|
||||
<>
|
||||
<Card className="bg-card/50 backdrop-blur-sm border-border/50">
|
||||
<CardHeader>
|
||||
<CardTitle>General</CardTitle>
|
||||
<CardDescription>
|
||||
Configure how messages are displayed and notified
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="render-inline-media">Render Inline Media</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Show images and videos directly in messages
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
id="render-inline-media"
|
||||
checked={renderInlineMedia}
|
||||
onCheckedChange={handleRenderInlineMediaChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border/50 pt-6">
|
||||
<div className="space-y-3">
|
||||
<Label>Sound</Label>
|
||||
<p className="text-sm text-muted-foreground mb-3">
|
||||
Play a sound when a DM arrives
|
||||
</p>
|
||||
<RadioGroup
|
||||
className="space-y-2"
|
||||
value={soundEnabled ? soundId : 'none'}
|
||||
onValueChange={(val) => {
|
||||
if (val === 'none') {
|
||||
updateConfig((prev) => ({
|
||||
...prev,
|
||||
messaging: { ...messaging, soundEnabled: false },
|
||||
}));
|
||||
} else {
|
||||
handleSoundIdChange(val);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex min-h-9 items-center justify-between space-x-3">
|
||||
<div className="flex items-center space-x-3">
|
||||
<RadioGroupItem value="none" id="sound-none" />
|
||||
<Label htmlFor="sound-none" className="font-normal cursor-pointer">
|
||||
None
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
{APP_NEW_MESSAGE_SOUNDS.map((sound) => (
|
||||
<div
|
||||
key={sound.id}
|
||||
className="group flex min-h-9 items-center justify-between space-x-3"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<RadioGroupItem value={sound.id} id={`sound-${sound.id}`} />
|
||||
<Label
|
||||
htmlFor={`sound-${sound.id}`}
|
||||
className="font-normal cursor-pointer"
|
||||
>
|
||||
{sound.label}
|
||||
</Label>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handlePlaySound(sound.url)}
|
||||
className="opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<Play className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-card/50 backdrop-blur-sm border-border/50">
|
||||
<CardHeader>
|
||||
<CardTitle>Relay Mode</CardTitle>
|
||||
<CardDescription>
|
||||
Control how relays are chosen for direct messages
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
<Label>Connection Mode</Label>
|
||||
<RadioGroup value={relayMode} onValueChange={handleRelayModeChange}>
|
||||
<div className="flex items-start space-x-3 space-y-0">
|
||||
<RadioGroupItem value="discovery" id="mode-discovery" />
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="mode-discovery" className="font-normal cursor-pointer">
|
||||
Discovery Only
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Only relays from the discovery list; fastest, may miss messages
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3 space-y-0">
|
||||
<RadioGroupItem value="hybrid" id="mode-hybrid" />
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="mode-hybrid" className="font-normal cursor-pointer">
|
||||
Hybrid <Badge variant="secondary" className="ml-2">Recommended</Badge>
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Discovery relays + user inbox relays
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3 space-y-0">
|
||||
<RadioGroupItem value="strict_outbox" id="mode-strict" />
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="mode-strict" className="font-normal cursor-pointer">
|
||||
Strict Outbox
|
||||
</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Only each user's published inbox relays (NIP-65/NIP-17). More private, but not everyone publishes relay lists yet - you may miss DMs to or from people who don't.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-card/50 backdrop-blur-sm border-border/50">
|
||||
<CardHeader>
|
||||
<CardTitle>Relays</CardTitle>
|
||||
<CardDescription>
|
||||
Discovery relays, NIP-65 inbox/outbox, and DM inbox
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<RelayListManager />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-card/50 backdrop-blur-sm border-border/50">
|
||||
<CardHeader>
|
||||
<CardTitle>Cache & Storage</CardTitle>
|
||||
<CardDescription>
|
||||
View cache status and manage stored messages
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="space-y-3">
|
||||
<Label>Connection Status</Label>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="bg-secondary/20 p-3 rounded-lg">
|
||||
<div className="text-sm font-medium mb-1">NIP-04 (Legacy)</div>
|
||||
<Badge variant={subscriptions.isNIP4Connected ? 'default' : 'secondary'}>
|
||||
{subscriptions.isNIP4Connected ? 'Connected' : 'Disconnected'}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="bg-secondary/20 p-3 rounded-lg">
|
||||
<div className="text-sm font-medium mb-1">NIP-17 (Private)</div>
|
||||
<Badge variant={subscriptions.isNIP17Connected ? 'default' : 'secondary'}>
|
||||
{subscriptions.isNIP17Connected ? 'Connected' : 'Disconnected'}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border/50 pt-6">
|
||||
<div className="space-y-3">
|
||||
<Label>Cache Statistics</Label>
|
||||
<div className="bg-secondary/20 p-4 rounded-lg space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Conversations:</span>
|
||||
<span className="font-medium">{conversationCount}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Total Messages:</span>
|
||||
<span className="font-medium">{totalMessages}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Last Sync:</span>
|
||||
<span className="font-medium">{lastSync}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Media Files Cached:</span>
|
||||
<span className="font-medium">{mediaCacheStats?.count ?? '...'}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Media Cache Size:</span>
|
||||
<span className="font-medium">
|
||||
{mediaCacheStats ? formatBytes(mediaCacheStats.size) : '...'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border/50 pt-6">
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleClearCache}
|
||||
disabled={dmIsLoading}
|
||||
className="w-full"
|
||||
>
|
||||
<RefreshCw className="h-4 w-4 mr-2" />
|
||||
Clear Cache & Refetch
|
||||
</Button>
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
This will clear all cached messages and re-fetch from relays. Use this if messages are missing or out of sync.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-card/50 backdrop-blur-sm border-border/50">
|
||||
<CardHeader>
|
||||
<CardTitle>Advanced</CardTitle>
|
||||
<CardDescription>
|
||||
Developer and debugging options
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label htmlFor="dev-mode">Developer Mode</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Show extra debug UI (seal payload, decryption details)
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<AlertCircle className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Only enable if you need to debug message encryption
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
id="dev-mode"
|
||||
checked={devMode}
|
||||
onCheckedChange={handleDevModeChange}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -57,6 +57,14 @@ const settingsSections: SettingsSection[] = [
|
||||
path: '/settings/notifications',
|
||||
requiresAuth: true,
|
||||
},
|
||||
{
|
||||
id: 'messaging',
|
||||
label: 'Messages',
|
||||
description: 'Direct messaging settings, relays, and cache',
|
||||
illustration: '/messaging-intro.png',
|
||||
path: '/settings/messaging',
|
||||
requiresAuth: true,
|
||||
},
|
||||
{
|
||||
id: 'advanced',
|
||||
label: 'Advanced',
|
||||
|
||||
@@ -8,6 +8,7 @@ export default {
|
||||
"./components/**/*.{ts,tsx}",
|
||||
"./app/**/*.{ts,tsx}",
|
||||
"./src/**/*.{ts,tsx}",
|
||||
"./node_modules/@samthomson/nostr-messaging/dist/**/*.js",
|
||||
],
|
||||
prefix: "",
|
||||
theme: {
|
||||
|
||||
Reference in New Issue
Block a user