add user page
This commit is contained in:
135
package.json
135
package.json
@@ -1,71 +1,68 @@
|
|||||||
{
|
{
|
||||||
"name": "lume",
|
"name": "lume",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"add-migrate": "cd src-tauri/ && sqlx migrate add",
|
"add-migrate": "cd src-tauri/ && sqlx migrate add",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"**/*.{js,ts,jsx,tsx}": "rome check --apply"
|
"**/*.{js,ts,jsx,tsx}": "rome check --apply"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/react": "^0.23.1",
|
"@floating-ui/react": "^0.23.1",
|
||||||
"@headlessui/react": "^1.7.15",
|
"@headlessui/react": "^1.7.15",
|
||||||
"@nostr-dev-kit/ndk": "^0.5.13",
|
"@nostr-dev-kit/ndk": "^0.5.13",
|
||||||
"@tanstack/react-query": "^4.29.18",
|
"@tanstack/react-query": "^4.29.19",
|
||||||
"@tanstack/react-query-devtools": "^4.29.18",
|
"@tanstack/react-virtual": "3.0.0-beta.54",
|
||||||
"@tanstack/react-virtual": "3.0.0-beta.54",
|
"@tauri-apps/api": "^1.4.0",
|
||||||
"@tauri-apps/api": "^1.4.0",
|
"cheerio": "1.0.0-rc.12",
|
||||||
"cheerio": "1.0.0-rc.12",
|
"dayjs": "^1.11.8",
|
||||||
"dayjs": "^1.11.8",
|
"destr": "^1.2.2",
|
||||||
"destr": "^1.2.2",
|
"framer-motion": "^10.12.17",
|
||||||
"framer-motion": "^10.12.17",
|
"get-urls": "^11.0.0",
|
||||||
"get-urls": "^11.0.0",
|
"immer": "^10.0.2",
|
||||||
"immer": "^10.0.2",
|
"light-bolt11-decoder": "^3.0.0",
|
||||||
"light-bolt11-decoder": "^3.0.0",
|
"nostr-tools": "^1.12.0",
|
||||||
"nostr-tools": "^1.12.0",
|
"react": "^18.2.0",
|
||||||
"react": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-hook-form": "^7.45.0",
|
||||||
"react-hook-form": "^7.45.0",
|
"react-hotkeys-hook": "^4.4.0",
|
||||||
"react-hotkeys-hook": "^4.4.0",
|
"react-player": "^2.12.0",
|
||||||
"react-player": "^2.12.0",
|
"react-router-dom": "^6.14.0",
|
||||||
"react-resizable-panels": "^0.0.48",
|
"react-string-replace": "^1.1.1",
|
||||||
"react-router-dom": "^6.14.0",
|
"react-virtuoso": "^4.3.11",
|
||||||
"react-string-replace": "^1.1.1",
|
"slate": "^0.94.1",
|
||||||
"react-virtuoso": "^4.3.11",
|
"slate-history": "^0.93.0",
|
||||||
"slate": "^0.94.1",
|
"slate-react": "^0.94.2",
|
||||||
"slate-history": "^0.93.0",
|
"tailwind-merge": "^1.13.2",
|
||||||
"slate-react": "^0.94.2",
|
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql",
|
||||||
"tailwind-merge": "^1.13.2",
|
"zustand": "^4.3.8"
|
||||||
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql",
|
},
|
||||||
"vidstack": "^0.4.5",
|
"devDependencies": {
|
||||||
"zustand": "^4.3.8"
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
},
|
"@tauri-apps/cli": "^1.4.0",
|
||||||
"devDependencies": {
|
"@types/node": "^18.16.18",
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@types/react": "^18.2.14",
|
||||||
"@tauri-apps/cli": "^1.4.0",
|
"@types/react-dom": "^18.2.6",
|
||||||
"@types/node": "^18.16.18",
|
"@types/youtube-player": "^5.5.7",
|
||||||
"@types/react": "^18.2.14",
|
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||||
"@types/react-dom": "^18.2.6",
|
"autoprefixer": "^10.4.14",
|
||||||
"@types/youtube-player": "^5.5.7",
|
"cross-env": "^7.0.3",
|
||||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
"csstype": "^3.1.2",
|
||||||
"autoprefixer": "^10.4.14",
|
"encoding": "^0.1.13",
|
||||||
"cross-env": "^7.0.3",
|
"husky": "^8.0.3",
|
||||||
"csstype": "^3.1.2",
|
"lint-staged": "^13.2.2",
|
||||||
"encoding": "^0.1.13",
|
"postcss": "^8.4.24",
|
||||||
"husky": "^8.0.3",
|
"prop-types": "^15.8.1",
|
||||||
"lint-staged": "^13.2.2",
|
"rome": "12.1.0",
|
||||||
"postcss": "^8.4.24",
|
"tailwindcss": "^3.3.2",
|
||||||
"prop-types": "^15.8.1",
|
"typescript": "^4.9.5",
|
||||||
"rome": "12.1.0",
|
"vite": "^4.3.9",
|
||||||
"tailwindcss": "^3.3.2",
|
"vite-plugin-top-level-await": "^1.3.1",
|
||||||
"typescript": "^4.9.5",
|
"vite-tsconfig-paths": "^4.2.0"
|
||||||
"vite": "^4.3.9",
|
}
|
||||||
"vite-plugin-top-level-await": "^1.3.1",
|
|
||||||
"vite-tsconfig-paths": "^4.2.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
169
pnpm-lock.yaml
generated
169
pnpm-lock.yaml
generated
@@ -11,11 +11,8 @@ dependencies:
|
|||||||
specifier: ^0.5.13
|
specifier: ^0.5.13
|
||||||
version: 0.5.13(typescript@4.9.5)
|
version: 0.5.13(typescript@4.9.5)
|
||||||
'@tanstack/react-query':
|
'@tanstack/react-query':
|
||||||
specifier: ^4.29.18
|
specifier: ^4.29.19
|
||||||
version: 4.29.18(react-dom@18.2.0)(react@18.2.0)
|
version: 4.29.19(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@tanstack/react-query-devtools':
|
|
||||||
specifier: ^4.29.18
|
|
||||||
version: 4.29.18(@tanstack/react-query@4.29.18)(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
'@tanstack/react-virtual':
|
'@tanstack/react-virtual':
|
||||||
specifier: 3.0.0-beta.54
|
specifier: 3.0.0-beta.54
|
||||||
version: 3.0.0-beta.54(react@18.2.0)
|
version: 3.0.0-beta.54(react@18.2.0)
|
||||||
@@ -61,9 +58,6 @@ dependencies:
|
|||||||
react-player:
|
react-player:
|
||||||
specifier: ^2.12.0
|
specifier: ^2.12.0
|
||||||
version: 2.12.0(react@18.2.0)
|
version: 2.12.0(react@18.2.0)
|
||||||
react-resizable-panels:
|
|
||||||
specifier: ^0.0.48
|
|
||||||
version: 0.0.48(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
react-router-dom:
|
react-router-dom:
|
||||||
specifier: ^6.14.0
|
specifier: ^6.14.0
|
||||||
version: 6.14.0(react-dom@18.2.0)(react@18.2.0)
|
version: 6.14.0(react-dom@18.2.0)(react@18.2.0)
|
||||||
@@ -88,9 +82,6 @@ dependencies:
|
|||||||
tauri-plugin-sql-api:
|
tauri-plugin-sql-api:
|
||||||
specifier: github:tauri-apps/tauri-plugin-sql
|
specifier: github:tauri-apps/tauri-plugin-sql
|
||||||
version: github.com/tauri-apps/tauri-plugin-sql/45b46ee428f6c13a831d237d8b602349ad6b17a6
|
version: github.com/tauri-apps/tauri-plugin-sql/45b46ee428f6c13a831d237d8b602349ad6b17a6
|
||||||
vidstack:
|
|
||||||
specifier: ^0.4.5
|
|
||||||
version: 0.4.5
|
|
||||||
zustand:
|
zustand:
|
||||||
specifier: ^4.3.8
|
specifier: ^4.3.8
|
||||||
version: 4.3.8(immer@10.0.2)(react@18.2.0)
|
version: 4.3.8(immer@10.0.2)(react@18.2.0)
|
||||||
@@ -162,6 +153,11 @@ devDependencies:
|
|||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
|
/@aashutoshrathi/word-wrap@1.2.5:
|
||||||
|
resolution: {integrity: sha512-plhoNEfSVdHMKXQyAxvH0Zyv3/4NL8r6pwgMQdmHR2vBUXn2t74PN2pBRppqKUa6RMT0yldyvOHG5Dbjwy2mBQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@alloc/quick-lru@5.2.0:
|
/@alloc/quick-lru@5.2.0:
|
||||||
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -537,10 +533,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
|
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@maverick-js/signals@5.11.2:
|
|
||||||
resolution: {integrity: sha512-jKAyNE2O7H+xrigPoqdV0Iq2AeQ6cysfBf/b2jasJ4FfCUKjGyazgtp+pIspTW6skFvpPrvq40Qft+7HuR+Tlg==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@noble/curves@1.0.0:
|
/@noble/curves@1.0.0:
|
||||||
resolution: {integrity: sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==}
|
resolution: {integrity: sha512-2upgEu0iLiDVDZkNLeFV2+ht0BAVgQnEmCk6JsOch9Rp8xfkMCbvbAZlA2pBHQc73dbl+vFOXfqkf4uemdn0bw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -831,34 +823,12 @@ packages:
|
|||||||
tailwindcss: 3.3.2
|
tailwindcss: 3.3.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@tanstack/match-sorter-utils@8.8.4:
|
/@tanstack/query-core@4.29.19:
|
||||||
resolution: {integrity: sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==}
|
resolution: {integrity: sha512-uPe1DukeIpIHpQi6UzIgBcXsjjsDaLnc7hF+zLBKnaUlh7jFE/A+P8t4cU4VzKPMFB/C970n/9SxtpO5hmIRgw==}
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
remove-accents: 0.4.2
|
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@tanstack/query-core@4.29.17:
|
/@tanstack/react-query@4.29.19(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-iDbO8yZOpm1lqgq6L8mpxGbKaoiyZSjthxEB3WGU7mNPYss9q4H3Q67+e2xXGwkemEVmtEX/WwvtFitrvVU8TA==}
|
resolution: {integrity: sha512-XiTIOHHQ5Cw1WUlHaD4fmVUMhoWjuNJlAeJGq7eM4BraI5z7y8WkZO+NR8PSuRnQGblpuVdjClQbDFtwxTtTUw==}
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@tanstack/react-query-devtools@4.29.18(@tanstack/react-query@4.29.18)(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-MH0EFL1lDzYBLbhCeD7GaNku4pOE7bPEm5HIFGw4YdRrmDjkTkkbGWqDJ15odGwp7qOvOgLbCxyTCqfof29Dag==}
|
|
||||||
peerDependencies:
|
|
||||||
'@tanstack/react-query': 4.29.18
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
dependencies:
|
|
||||||
'@tanstack/match-sorter-utils': 8.8.4
|
|
||||||
'@tanstack/react-query': 4.29.18(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
superjson: 1.12.4
|
|
||||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@tanstack/react-query@4.29.18(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-CyoxrT8U7MWLZOdl0XLCiHC+W5cnXNELafFtzcsrwrALiUulONEfWFl6TzgvENfhC50OBjVGy/6Yl3YsIhAM6g==}
|
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
@@ -869,7 +839,7 @@ packages:
|
|||||||
react-native:
|
react-native:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tanstack/query-core': 4.29.17
|
'@tanstack/query-core': 4.29.19
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||||
@@ -1458,7 +1428,7 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite: 1.0.30001508
|
caniuse-lite: 1.0.30001508
|
||||||
electron-to-chromium: 1.4.441
|
electron-to-chromium: 1.4.442
|
||||||
node-releases: 2.0.12
|
node-releases: 2.0.12
|
||||||
update-browserslist-db: 1.0.11(browserslist@4.21.9)
|
update-browserslist-db: 1.0.11(browserslist@4.21.9)
|
||||||
dev: true
|
dev: true
|
||||||
@@ -1477,7 +1447,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@npmcli/fs': 3.1.0
|
'@npmcli/fs': 3.1.0
|
||||||
fs-minipass: 3.0.2
|
fs-minipass: 3.0.2
|
||||||
glob: 10.3.0
|
glob: 10.3.1
|
||||||
lru-cache: 7.18.3
|
lru-cache: 7.18.3
|
||||||
minipass: 5.0.0
|
minipass: 5.0.0
|
||||||
minipass-collect: 1.0.2
|
minipass-collect: 1.0.2
|
||||||
@@ -1670,13 +1640,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
|
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/copy-anything@3.0.5:
|
|
||||||
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
|
|
||||||
engines: {node: '>=12.13'}
|
|
||||||
dependencies:
|
|
||||||
is-what: 4.1.15
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/create-esm-loader@0.2.3:
|
/create-esm-loader@0.2.3:
|
||||||
resolution: {integrity: sha512-cllzD6IU/mzXBs5OdQVWL3+ne5Elpu3Wdm7h5OldMbGXk76yr9XzHlQXWJ4zfs0ZAibe26rkbs4KvMAJm7fIZA==}
|
resolution: {integrity: sha512-cllzD6IU/mzXBs5OdQVWL3+ne5Elpu3Wdm7h5OldMbGXk76yr9XzHlQXWJ4zfs0ZAibe26rkbs4KvMAJm7fIZA==}
|
||||||
engines: {node: '>=14.x'}
|
engines: {node: '>=14.x'}
|
||||||
@@ -1897,8 +1860,8 @@ packages:
|
|||||||
/eastasianwidth@0.2.0:
|
/eastasianwidth@0.2.0:
|
||||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||||
|
|
||||||
/electron-to-chromium@1.4.441:
|
/electron-to-chromium@1.4.442:
|
||||||
resolution: {integrity: sha512-LlCgQ8zgYZPymf5H4aE9itwiIWH4YlCiv1HFLmmcBeFYi5E+3eaIFnjHzYtcFQbaKfAW+CqZ9pgxo33DZuoqPg==}
|
resolution: {integrity: sha512-RkrZF//Ya+0aJq2NM3OdisNh5ZodZq1rdXOS96G8DdDgpDKqKE81yTbbQ3F/4CKm1JBPsGu1Lp/akkna2xO06Q==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/emoji-regex@8.0.0:
|
/emoji-regex@8.0.0:
|
||||||
@@ -2229,7 +2192,7 @@ packages:
|
|||||||
lodash.merge: 4.6.2
|
lodash.merge: 4.6.2
|
||||||
minimatch: 3.1.2
|
minimatch: 3.1.2
|
||||||
natural-compare: 1.4.0
|
natural-compare: 1.4.0
|
||||||
optionator: 0.9.1
|
optionator: 0.9.2
|
||||||
strip-ansi: 6.0.1
|
strip-ansi: 6.0.1
|
||||||
strip-json-comments: 3.1.1
|
strip-json-comments: 3.1.1
|
||||||
text-table: 0.2.0
|
text-table: 0.2.0
|
||||||
@@ -2243,7 +2206,7 @@ packages:
|
|||||||
create-esm-loader: 0.2.3
|
create-esm-loader: 0.2.3
|
||||||
npm-run-all: 4.1.5
|
npm-run-all: 4.1.5
|
||||||
semver: 7.5.3
|
semver: 7.5.3
|
||||||
typescript: 5.1.3
|
typescript: 5.1.5
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/espree@9.5.2:
|
/espree@9.5.2:
|
||||||
@@ -2533,8 +2496,8 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
|
|
||||||
/glob@10.3.0:
|
/glob@10.3.1:
|
||||||
resolution: {integrity: sha512-AQ1/SB9HH0yCx1jXAT4vmCbTOPe5RQ+kCurjbel5xSCGhebumUv+GJZfa1rEqor3XIViqwSEmlkZCQD43RWrBg==}
|
resolution: {integrity: sha512-9BKYcEeIs7QwlCYs+Y3GBvqAMISufUS0i2ELd11zpZjxI5V9iyRj0HgzB5/cLf2NY4vcYBTYzJ7GIui7j/4DOw==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2542,7 +2505,7 @@ packages:
|
|||||||
jackspeak: 2.2.1
|
jackspeak: 2.2.1
|
||||||
minimatch: 9.0.2
|
minimatch: 9.0.2
|
||||||
minipass: 5.0.0
|
minipass: 5.0.0
|
||||||
path-scurry: 1.9.2
|
path-scurry: 1.10.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/glob@7.1.6:
|
/glob@7.1.6:
|
||||||
@@ -2969,11 +2932,6 @@ packages:
|
|||||||
call-bind: 1.0.2
|
call-bind: 1.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/is-what@4.1.15:
|
|
||||||
resolution: {integrity: sha512-uKua1wfy3Yt+YqsD6mTUEa2zSi3G1oPlqTflgaPJ7z63vUGN5pxFpnQfeSLMFnJDEsdvOtkp1rUWkYjB4YfhgA==}
|
|
||||||
engines: {node: '>=12.13'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/isexe@2.0.0:
|
/isexe@2.0.0:
|
||||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||||
|
|
||||||
@@ -3175,6 +3133,11 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
js-tokens: 4.0.0
|
js-tokens: 4.0.0
|
||||||
|
|
||||||
|
/lru-cache@10.0.0:
|
||||||
|
resolution: {integrity: sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==}
|
||||||
|
engines: {node: 14 || >=16.14}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/lru-cache@6.0.0:
|
/lru-cache@6.0.0:
|
||||||
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -3187,11 +3150,6 @@ packages:
|
|||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/lru-cache@9.1.2:
|
|
||||||
resolution: {integrity: sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==}
|
|
||||||
engines: {node: 14 || >=16.14}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/make-fetch-happen@11.1.1:
|
/make-fetch-happen@11.1.1:
|
||||||
resolution: {integrity: sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==}
|
resolution: {integrity: sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==}
|
||||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||||
@@ -3225,24 +3183,6 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/maverick.js@0.33.1:
|
|
||||||
resolution: {integrity: sha512-p8L5V62CV6TmHAngmRAopp231oJKeH77mJja5SsKOfvzrPRoThT/Jo9U0jMRB5iMykqkvyg2J5V5Agn6FPXDWQ==}
|
|
||||||
engines: {node: '>=16'}
|
|
||||||
dependencies:
|
|
||||||
'@maverick-js/signals': 5.11.2
|
|
||||||
type-fest: 3.12.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/media-captions@0.0.8:
|
|
||||||
resolution: {integrity: sha512-gX6ozU5smrAb90FwI+wd1VnqkwcAQ2NF8l72KZ67k+o3Vr0wYBAsTFRfdOePqde9IBhfBZbZsYEA5509npwtZA==}
|
|
||||||
engines: {node: '>=16'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/media-icons@0.4.2:
|
|
||||||
resolution: {integrity: sha512-jrxoQzxsZFyzt6P3CoGZWa6FVKoR/Ii1plbVjtyTSyQGpeVWkJ++0oBAwGOv/F9mWRxmGkaQ04uEIKTFouRG1w==}
|
|
||||||
engines: {node: '>=16'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/memoize-one@5.2.1:
|
/memoize-one@5.2.1:
|
||||||
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
|
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -3629,16 +3569,16 @@ packages:
|
|||||||
mimic-fn: 4.0.0
|
mimic-fn: 4.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/optionator@0.9.1:
|
/optionator@0.9.2:
|
||||||
resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}
|
resolution: {integrity: sha512-W/Q5m5jpuspnghjVKWcs+5sm9Xl9BIdDmNLWaQbwneNQEZFosaiVPC9vtwN0Dbxx4x7TLY7bxjvttW26AmcKrQ==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@aashutoshrathi/word-wrap': 1.2.5
|
||||||
deep-is: 0.1.4
|
deep-is: 0.1.4
|
||||||
fast-levenshtein: 2.0.6
|
fast-levenshtein: 2.0.6
|
||||||
levn: 0.4.1
|
levn: 0.4.1
|
||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
type-check: 0.4.0
|
type-check: 0.4.0
|
||||||
word-wrap: 1.2.3
|
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/p-limit@2.3.0:
|
/p-limit@2.3.0:
|
||||||
@@ -3744,11 +3684,11 @@ packages:
|
|||||||
/path-parse@1.0.7:
|
/path-parse@1.0.7:
|
||||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||||
|
|
||||||
/path-scurry@1.9.2:
|
/path-scurry@1.10.0:
|
||||||
resolution: {integrity: sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==}
|
resolution: {integrity: sha512-tZFEaRQbMLjwrsmidsGJ6wDMv0iazJWk6SfIKnY4Xru8auXgmJkOBa5DUbYFcFD2Rzk2+KDlIiF0GVXNCbgC7g==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache: 9.1.2
|
lru-cache: 10.0.0
|
||||||
minipass: 5.0.0
|
minipass: 5.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -3990,16 +3930,6 @@ packages:
|
|||||||
react-fast-compare: 3.2.2
|
react-fast-compare: 3.2.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-resizable-panels@0.0.48(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-fJa3itmJ3HLLmVG7y8tka80wFW63N6ai76q7MGwU8nSXeA0qkX36vnmPyXm34lvtsGjn1Cgi5IPhPQnf42SVpA==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^16.14.0 || ^17.0.0 || ^18.0.0
|
|
||||||
react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0
|
|
||||||
dependencies:
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react-router-dom@6.14.0(react-dom@18.2.0)(react@18.2.0):
|
/react-router-dom@6.14.0(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-YEwlApKwzMMMbGbhh+Q7MsloTldcwMgHxUY/1g0uA62+B1hZo2jsybCWIDCL8zvIDB1FA0pBKY9chHbZHt+2dQ==}
|
resolution: {integrity: sha512-YEwlApKwzMMMbGbhh+Q7MsloTldcwMgHxUY/1g0uA62+B1hZo2jsybCWIDCL8zvIDB1FA0pBKY9chHbZHt+2dQ==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -4113,10 +4043,6 @@ packages:
|
|||||||
functions-have-names: 1.2.3
|
functions-have-names: 1.2.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/remove-accents@0.4.2:
|
|
||||||
resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/resolve-from@4.0.0:
|
/resolve-from@4.0.0:
|
||||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -4518,13 +4444,6 @@ packages:
|
|||||||
ts-interface-checker: 0.1.13
|
ts-interface-checker: 0.1.13
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/superjson@1.12.4:
|
|
||||||
resolution: {integrity: sha512-vkpPQAxdCg9SLfPv5GPC5fnGrui/WryktoN9O5+Zif/14QIMjw+RITf/5LbBh+9QpBFb3KNvJth+puz2H8o6GQ==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
dependencies:
|
|
||||||
copy-anything: 3.0.5
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/supports-color@5.5.0:
|
/supports-color@5.5.0:
|
||||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -4740,11 +4659,6 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/type-fest@3.12.0:
|
|
||||||
resolution: {integrity: sha512-qj9wWsnFvVEMUDbESiilKeXeHL7FwwiFcogfhfyjmvT968RXSvnl23f1JOClTHYItsi7o501C/7qVllscUP3oA==}
|
|
||||||
engines: {node: '>=14.16'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/type@1.2.0:
|
/type@1.2.0:
|
||||||
resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==}
|
resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -4772,8 +4686,8 @@ packages:
|
|||||||
engines: {node: '>=4.2.0'}
|
engines: {node: '>=4.2.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
/typescript@5.1.3:
|
/typescript@5.1.5:
|
||||||
resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==}
|
resolution: {integrity: sha512-FOH+WN/DQjUvN6WgW+c4Ml3yi0PH+a/8q+kNIfRehv1wLhWONedw85iu+vQ39Wp49IzTJEsZ2lyLXpBF7mkF1g==}
|
||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: false
|
dev: false
|
||||||
@@ -4868,16 +4782,6 @@ packages:
|
|||||||
spdx-expression-parse: 3.0.1
|
spdx-expression-parse: 3.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/vidstack@0.4.5:
|
|
||||||
resolution: {integrity: sha512-6CFkWRSvDQAd91WQ6PZ/ovwbGFDI8pLuIA0swcDLLAOM6MiKWFd/gFjJ/x7+DAcCf0hqOngO34MJhG3zTZQtcQ==}
|
|
||||||
engines: {node: '>=16'}
|
|
||||||
dependencies:
|
|
||||||
maverick.js: 0.33.1
|
|
||||||
media-captions: 0.0.8
|
|
||||||
media-icons: 0.4.2
|
|
||||||
type-fest: 3.12.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/vite-plugin-top-level-await@1.3.1(vite@4.3.9):
|
/vite-plugin-top-level-await@1.3.1(vite@4.3.9):
|
||||||
resolution: {integrity: sha512-55M1h4NAwkrpxPNOJIBzKZFihqLUzIgnElLSmPNPMR2Fn9+JHKaNg3sVX1Fq+VgvuBksQYxiD3OnwQAUu7kaPQ==}
|
resolution: {integrity: sha512-55M1h4NAwkrpxPNOJIBzKZFihqLUzIgnElLSmPNPMR2Fn9+JHKaNg3sVX1Fq+VgvuBksQYxiD3OnwQAUu7kaPQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -5012,11 +4916,6 @@ packages:
|
|||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/word-wrap@1.2.3:
|
|
||||||
resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/wrap-ansi@6.2.0:
|
/wrap-ansi@6.2.0:
|
||||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function ChatsListItem({ data }: { data: any }) {
|
|||||||
<div className="inline-flex h-9 items-center gap-2.5 rounded-md px-2.5">
|
<div className="inline-flex h-9 items-center gap-2.5 rounded-md px-2.5">
|
||||||
<div className="relative h-6 w-6 shrink-0 animate-pulse rounded bg-zinc-800" />
|
<div className="relative h-6 w-6 shrink-0 animate-pulse rounded bg-zinc-800" />
|
||||||
<div>
|
<div>
|
||||||
<div className="h-2.5 w-full animate-pulse truncate rounded bg-zinc-800 text-base font-medium" />
|
<div className="h-2.5 w-2/3 animate-pulse truncate rounded bg-zinc-800 text-base font-medium" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -41,7 +41,10 @@ export function ChatsListItem({ data }: { data: any }) {
|
|||||||
<div className="w-full inline-flex items-center justify-between">
|
<div className="w-full inline-flex items-center justify-between">
|
||||||
<div className="inline-flex items-baseline gap-1">
|
<div className="inline-flex items-baseline gap-1">
|
||||||
<h5 className="max-w-[9rem] truncate font-medium text-zinc-200">
|
<h5 className="max-w-[9rem] truncate font-medium text-zinc-200">
|
||||||
{user?.nip05 || user?.displayName || shortenKey(data.sender_pubkey)}
|
{user?.nip05 ||
|
||||||
|
user?.displayName ||
|
||||||
|
user?.name ||
|
||||||
|
shortenKey(data.sender_pubkey)}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export function NewMessageModal() {
|
|||||||
/>
|
/>
|
||||||
<div className="inline-flex flex-col gap-1">
|
<div className="inline-flex flex-col gap-1">
|
||||||
<h3 className="leading-none max-w-[15rem] line-clamp-1 font-medium text-zinc-100">
|
<h3 className="leading-none max-w-[15rem] line-clamp-1 font-medium text-zinc-100">
|
||||||
{pleb.display_name || pleb.name}
|
{pleb.displayName || pleb.name}
|
||||||
</h3>
|
</h3>
|
||||||
<span className="leading-none max-w-[10rem] line-clamp-1 text-sm text-zinc-400">
|
<span className="leading-none max-w-[10rem] line-clamp-1 text-sm text-zinc-400">
|
||||||
{pleb.nip05 ||
|
{pleb.nip05 ||
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export function FollowingBlock({ block }: { block: number }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="shrink-0 relative w-[400px] border-r border-zinc-900">
|
<div className="shrink-0 relative w-[400px] border-r border-zinc-900">
|
||||||
<TitleBar title="Circle" />
|
<TitleBar title="Your Circle" />
|
||||||
{hasNewNote && (
|
{hasNewNote && (
|
||||||
<div className="z-50 absolute top-12 left-1/2 transform -translate-x-1/2">
|
<div className="z-50 absolute top-12 left-1/2 transform -translate-x-1/2">
|
||||||
<button
|
<button
|
||||||
|
|||||||
20
src/app/user/components/feed.tsx
Normal file
20
src/app/user/components/feed.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { getNotesByPubkey } from "@libs/storage";
|
||||||
|
import { Note } from "@shared/notes/note";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { LumeEvent } from "@utils/types";
|
||||||
|
|
||||||
|
export function UserFeed({ pubkey }: { pubkey: string }) {
|
||||||
|
const { status, data } = useQuery(["user-feed", pubkey], async () => {
|
||||||
|
return await getNotesByPubkey(pubkey);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-[400px] px-2">
|
||||||
|
{status === "loading" ? (
|
||||||
|
<p>Loading...</p>
|
||||||
|
) : (
|
||||||
|
data.map((note: LumeEvent) => <Note key={note.event_id} event={note} />)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
55
src/app/user/components/metadata.tsx
Normal file
55
src/app/user/components/metadata.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { compactNumber } from "@utils/number";
|
||||||
|
|
||||||
|
export function UserMetadata({ pubkey }: { pubkey: string }) {
|
||||||
|
const { status, data } = useQuery(["user-metadata", pubkey], async () => {
|
||||||
|
const res = await fetch(
|
||||||
|
`https://api.nostr.band/v0/stats/profile/${pubkey}`,
|
||||||
|
);
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error("Error");
|
||||||
|
}
|
||||||
|
return await res.json();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status === "loading") {
|
||||||
|
return <p>Loading...</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full flex items-center gap-10">
|
||||||
|
<div className="inline-flex flex-col gap-1">
|
||||||
|
<span className="leading-none font-semibold text-zinc-100">
|
||||||
|
{data.stats[pubkey].followers_pubkey_count ?? 0}
|
||||||
|
</span>
|
||||||
|
<span className="leading-none text-sm text-zinc-400">Followers</span>
|
||||||
|
</div>
|
||||||
|
<div className="inline-flex flex-col gap-1">
|
||||||
|
<span className="leading-none font-semibold text-zinc-100">
|
||||||
|
{data.stats[pubkey].pub_following_pubkey_count ?? 0}
|
||||||
|
</span>
|
||||||
|
<span className="leading-none text-sm text-zinc-400">Following</span>
|
||||||
|
</div>
|
||||||
|
<div className="inline-flex flex-col gap-1">
|
||||||
|
<span className="leading-none font-semibold text-zinc-100">
|
||||||
|
{data.stats[pubkey].zaps_received
|
||||||
|
? compactNumber.format(
|
||||||
|
data.stats[pubkey].zaps_received.msats / 1000,
|
||||||
|
)
|
||||||
|
: 0}
|
||||||
|
</span>
|
||||||
|
<span className="leading-none text-sm text-zinc-400">
|
||||||
|
Zaps received
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="inline-flex flex-col gap-1">
|
||||||
|
<span className="leading-none font-semibold text-zinc-100">
|
||||||
|
{data.stats[pubkey].zaps_sent
|
||||||
|
? compactNumber.format(data.stats[pubkey].zaps_sent.msats / 1000)
|
||||||
|
: 0}
|
||||||
|
</span>
|
||||||
|
<span className="leading-none text-sm text-zinc-400">Zaps sent</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,47 +1,25 @@
|
|||||||
import { usePublish } from "@libs/ndk";
|
import { UserFeed } from "@app/user/components/feed";
|
||||||
|
import { UserMetadata } from "@app/user/components/metadata";
|
||||||
|
import { Tab } from "@headlessui/react";
|
||||||
|
import { ThreadsIcon, ZapIcon } from "@shared/icons";
|
||||||
import { Image } from "@shared/image";
|
import { Image } from "@shared/image";
|
||||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import { useFollows } from "@utils/hooks/useFollows";
|
|
||||||
import { useProfile } from "@utils/hooks/useProfile";
|
import { useProfile } from "@utils/hooks/useProfile";
|
||||||
import { compactNumber } from "@utils/number";
|
import { useSocial } from "@utils/hooks/useSocial";
|
||||||
import { shortenKey } from "@utils/shortenKey";
|
import { shortenKey } from "@utils/shortenKey";
|
||||||
import { useEffect, useState } from "react";
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useParams } from "react-router-dom";
|
||||||
|
|
||||||
export function UserScreen() {
|
export function UserScreen() {
|
||||||
const publish = usePublish();
|
|
||||||
const [followed, setFollowed] = useState(false);
|
|
||||||
|
|
||||||
const { pubkey } = useParams();
|
const { pubkey } = useParams();
|
||||||
const { user } = useProfile(pubkey);
|
const { user } = useProfile(pubkey);
|
||||||
const { status: followsStatus, follows } = useFollows();
|
const { status, userFollows, follow, unfollow } = useSocial();
|
||||||
const {
|
|
||||||
status: userStatsStatus,
|
|
||||||
data: userStats,
|
|
||||||
error,
|
|
||||||
} = useQuery(["user", pubkey], async () => {
|
|
||||||
const res = await fetch(
|
|
||||||
`https://api.nostr.band/v0/stats/profile/${pubkey}`,
|
|
||||||
);
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error("Error");
|
|
||||||
}
|
|
||||||
return await res.json();
|
|
||||||
});
|
|
||||||
|
|
||||||
const follow = (pubkey: string) => {
|
const [followed, setFollowed] = useState(false);
|
||||||
|
|
||||||
|
const followUser = (pubkey: string) => {
|
||||||
try {
|
try {
|
||||||
const followsAsSet = new Set(follows);
|
follow(pubkey);
|
||||||
followsAsSet.add(pubkey);
|
|
||||||
|
|
||||||
const tags = [];
|
|
||||||
followsAsSet.forEach((item) => {
|
|
||||||
tags.push(["p", item]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// publish event
|
|
||||||
publish({ content: "", kind: 3, tags: tags });
|
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
setFollowed(true);
|
setFollowed(true);
|
||||||
@@ -50,18 +28,9 @@ export function UserScreen() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const unfollow = (pubkey: string) => {
|
const unfollowUser = (pubkey: string) => {
|
||||||
try {
|
try {
|
||||||
const followsAsSet = new Set(follows);
|
unfollow(pubkey);
|
||||||
followsAsSet.delete(pubkey);
|
|
||||||
|
|
||||||
const tags = [];
|
|
||||||
followsAsSet.forEach((item) => {
|
|
||||||
tags.push(["p", item]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// publish event
|
|
||||||
publish({ content: "", kind: 3, tags: tags });
|
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
setFollowed(false);
|
setFollowed(false);
|
||||||
@@ -71,15 +40,15 @@ export function UserScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (followsStatus === "success" && follows) {
|
if (status === "success" && userFollows) {
|
||||||
if (follows.includes(pubkey)) {
|
if (userFollows.includes(pubkey)) {
|
||||||
setFollowed(true);
|
setFollowed(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [followsStatus]);
|
}, [status]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full">
|
<div className="h-full w-full overflow-y-auto">
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className="h-11 w-full flex items-center px-3 border-b border-zinc-900"
|
className="h-11 w-full flex items-center px-3 border-b border-zinc-900"
|
||||||
@@ -92,107 +61,97 @@ export function UserScreen() {
|
|||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full px-5 -mt-7">
|
<div className="w-full -mt-7">
|
||||||
<div>
|
<div className="px-5">
|
||||||
<Image
|
<Image
|
||||||
src={user?.image}
|
src={user?.image}
|
||||||
fallback={DEFAULT_AVATAR}
|
fallback={DEFAULT_AVATAR}
|
||||||
alt={pubkey}
|
alt={pubkey}
|
||||||
className="w-14 h-14 rounded-md ring-2 ring-black"
|
className="w-14 h-14 rounded-md ring-2 ring-black"
|
||||||
/>
|
/>
|
||||||
<div className="flex-1 flex flex-col gap-2 mt-4">
|
<div className="flex-1 flex flex-col gap-4 mt-2">
|
||||||
<h5 className="font-semibold leading-none">
|
<div className="flex items-center gap-16">
|
||||||
{user?.displayName || user?.name || "No name"}
|
<div className="inline-flex flex-col gap-1.5">
|
||||||
</h5>
|
<h5 className="font-semibold text-lg leading-none">
|
||||||
<span className="max-w-[15rem] text-sm truncate leading-none text-zinc-500">
|
{user?.displayName || user?.name || "No name"}
|
||||||
{user?.nip05 || shortenKey(pubkey)}
|
</h5>
|
||||||
</span>
|
<span className="max-w-[15rem] text-sm truncate leading-none text-zinc-500">
|
||||||
<p className="mt-1 line-clamp-3 break-words leading-tight text-zinc-100">
|
{user?.nip05 || shortenKey(pubkey)}
|
||||||
{user?.about}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-8">
|
|
||||||
{error && <p>Failed to fetch user stats</p>}
|
|
||||||
{userStatsStatus === "loading" ? (
|
|
||||||
<p>Loading...</p>
|
|
||||||
) : (
|
|
||||||
<div className="w-full flex items-center gap-10">
|
|
||||||
<div className="inline-flex flex-col gap-1">
|
|
||||||
<span className="leading-none font-semibold text-zinc-100">
|
|
||||||
{userStats.stats[pubkey].followers_pubkey_count ?? 0}
|
|
||||||
</span>
|
|
||||||
<span className="leading-none text-sm text-zinc-400">
|
|
||||||
Followers
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="inline-flex flex-col gap-1">
|
<div className="inline-flex items-center gap-2">
|
||||||
<span className="leading-none font-semibold text-zinc-100">
|
{status === "loading" ? (
|
||||||
{userStats.stats[pubkey].pub_following_pubkey_count ?? 0}
|
<button
|
||||||
</span>
|
type="button"
|
||||||
<span className="leading-none text-sm text-zinc-400">
|
className="inline-flex w-36 h-10 items-center justify-center rounded-md bg-zinc-900 hover:bg-fuchsia-500 text-sm font-medium"
|
||||||
Following
|
>
|
||||||
</span>
|
Loading...
|
||||||
</div>
|
</button>
|
||||||
<div className="inline-flex flex-col gap-1">
|
) : followed ? (
|
||||||
<span className="leading-none font-semibold text-zinc-100">
|
<button
|
||||||
{userStats.stats[pubkey].zaps_received
|
type="button"
|
||||||
? compactNumber.format(
|
onClick={() => unfollowUser(pubkey)}
|
||||||
userStats.stats[pubkey].zaps_received.msats / 1000,
|
className="inline-flex w-36 h-10 items-center justify-center rounded-md bg-zinc-900 hover:bg-fuchsia-500 text-sm font-medium"
|
||||||
)
|
>
|
||||||
: 0}
|
Unfollow
|
||||||
</span>
|
</button>
|
||||||
<span className="leading-none text-sm text-zinc-400">
|
) : (
|
||||||
Zaps received
|
<button
|
||||||
</span>
|
type="button"
|
||||||
</div>
|
onClick={() => followUser(pubkey)}
|
||||||
<div className="inline-flex flex-col gap-1">
|
className="inline-flex w-36 h-10 items-center justify-center rounded-md bg-zinc-900 hover:bg-fuchsia-500 text-sm font-medium"
|
||||||
<span className="leading-none font-semibold text-zinc-100">
|
>
|
||||||
{userStats.stats[pubkey].zaps_sent
|
Follow
|
||||||
? compactNumber.format(
|
</button>
|
||||||
userStats.stats[pubkey].zaps_sent.msats / 1000,
|
)}
|
||||||
)
|
<Link
|
||||||
: 0}
|
to={`/app/chat/${pubkey}`}
|
||||||
</span>
|
className="inline-flex w-36 h-10 items-center justify-center rounded-md bg-zinc-900 hover:bg-fuchsia-500 text-sm font-medium"
|
||||||
<span className="leading-none text-sm text-zinc-400">
|
>
|
||||||
Zaps sent
|
Message
|
||||||
</span>
|
</Link>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="inline-flex w-10 h-10 items-center justify-center rounded-md bg-zinc-900 group hover:bg-orange-500 text-sm font-medium"
|
||||||
|
>
|
||||||
|
<ZapIcon className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="flex flex-col gap-8">
|
||||||
<div className="mt-6 flex items-center gap-2">
|
<p className="mt-2 max-w-[500px] break-words select-text text-zinc-100">
|
||||||
{followsStatus === "loading" ? (
|
{user?.about}
|
||||||
<button
|
</p>
|
||||||
type="button"
|
<UserMetadata pubkey={pubkey} />
|
||||||
className="inline-flex w-44 h-10 items-center justify-center rounded-md bg-zinc-900 hover:bg-fuchsia-500 text-sm font-medium"
|
</div>
|
||||||
>
|
|
||||||
Loading...
|
|
||||||
</button>
|
|
||||||
) : followed ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => unfollow(pubkey)}
|
|
||||||
className="inline-flex w-44 h-10 items-center justify-center rounded-md bg-zinc-900 hover:bg-fuchsia-500 text-sm font-medium"
|
|
||||||
>
|
|
||||||
Unfollow
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => follow(pubkey)}
|
|
||||||
className="inline-flex w-44 h-10 items-center justify-center rounded-md bg-zinc-900 hover:bg-fuchsia-500 text-sm font-medium"
|
|
||||||
>
|
|
||||||
Follow
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<Link
|
|
||||||
to={`/app/chat/${pubkey}`}
|
|
||||||
className="inline-flex w-44 h-10 items-center justify-center rounded-md bg-zinc-900 hover:bg-fuchsia-500 text-sm font-medium"
|
|
||||||
>
|
|
||||||
Message
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mt-8 w-full border-t border-zinc-900">
|
||||||
|
<Tab.Group>
|
||||||
|
<Tab.List className="px-5 mb-2">
|
||||||
|
<Tab as={Fragment}>
|
||||||
|
{({ selected }) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`${
|
||||||
|
selected
|
||||||
|
? "text-fuchsia-500 border-fuchsia-500"
|
||||||
|
: "text-zinc-200 border-transparent"
|
||||||
|
} font-medium inline-flex items-center gap-2 h-10 border-t`}
|
||||||
|
>
|
||||||
|
<ThreadsIcon className="w-4 h-4" />
|
||||||
|
Posts
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</Tab>
|
||||||
|
</Tab.List>
|
||||||
|
<Tab.Panels>
|
||||||
|
<Tab.Panel>
|
||||||
|
<UserFeed pubkey={pubkey} />
|
||||||
|
</Tab.Panel>
|
||||||
|
</Tab.Panels>
|
||||||
|
</Tab.Group>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -157,26 +157,13 @@ export async function getNotes(time: number, limit: number, offset: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get all notes by pubkey
|
// get all notes by pubkey
|
||||||
export async function getNotesByPubkey(
|
export async function getNotesByPubkey(pubkey: string) {
|
||||||
pubkey: string,
|
|
||||||
time: number,
|
|
||||||
limit: number,
|
|
||||||
offset: number,
|
|
||||||
) {
|
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
const totalNotes = await countTotalNotes();
|
const res: any = await db.select(
|
||||||
const nextCursor = offset + limit;
|
`SELECT * FROM notes WHERE pubkey == "${pubkey}" AND kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC;`,
|
||||||
|
|
||||||
const notes: any = { data: null, nextCursor: 0 };
|
|
||||||
const query: any = await db.select(
|
|
||||||
`SELECT * FROM notes WHERE created_at <= "${time}" AND pubkey == "${pubkey}" AND kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
notes["data"] = query;
|
return res;
|
||||||
notes["nextCursor"] =
|
|
||||||
Math.round(totalNotes / nextCursor) > 1 ? nextCursor : undefined;
|
|
||||||
|
|
||||||
return notes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all notes by authors
|
// get all notes by authors
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import App from "./app";
|
import App from "./app";
|
||||||
import { RelayProvider } from "@shared/relayProvider";
|
import { RelayProvider } from "@shared/relayProvider";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
|
|
||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
@@ -20,6 +19,5 @@ root.render(
|
|||||||
<RelayProvider>
|
<RelayProvider>
|
||||||
<App />
|
<App />
|
||||||
</RelayProvider>
|
</RelayProvider>
|
||||||
<ReactQueryDevtools initialIsOpen={false} position="top-right" />
|
|
||||||
</QueryClientProvider>,
|
</QueryClientProvider>,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -61,15 +61,16 @@ const ImagePreview = ({
|
|||||||
|
|
||||||
export function Post() {
|
export function Post() {
|
||||||
const publish = usePublish();
|
const publish = usePublish();
|
||||||
const [repost, toggle] = useComposer((state) => [
|
|
||||||
state.repost,
|
|
||||||
state.toggleModal,
|
|
||||||
]);
|
|
||||||
const editor = useMemo(
|
const editor = useMemo(
|
||||||
() => withReact(withImages(withHistory(createEditor()))),
|
() => withReact(withImages(withHistory(createEditor()))),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [repost, reply, toggle] = useComposer((state) => [
|
||||||
|
state.repost,
|
||||||
|
state.reply,
|
||||||
|
state.toggleModal,
|
||||||
|
]);
|
||||||
const [content, setContent] = useState<Node[]>([
|
const [content, setContent] = useState<Node[]>([
|
||||||
{
|
{
|
||||||
children: [
|
children: [
|
||||||
@@ -84,6 +85,18 @@ export function Post() {
|
|||||||
return nodes.map((n) => Node.string(n)).join("\n");
|
return nodes.map((n) => Node.string(n)).join("\n");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const getRef = () => {
|
||||||
|
if (repost.id) {
|
||||||
|
return repost.id;
|
||||||
|
} else if (reply.id) {
|
||||||
|
return reply.id;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refID = getRef();
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
let tags: string[][] = [];
|
let tags: string[][] = [];
|
||||||
let kind: number;
|
let kind: number;
|
||||||
@@ -94,6 +107,20 @@ export function Post() {
|
|||||||
["e", repost.id, FULL_RELAYS[0], "root"],
|
["e", repost.id, FULL_RELAYS[0], "root"],
|
||||||
["p", repost.pubkey],
|
["p", repost.pubkey],
|
||||||
];
|
];
|
||||||
|
} else if (reply.id && reply.pubkey) {
|
||||||
|
kind = 1;
|
||||||
|
if (reply.root && reply.root !== reply.id) {
|
||||||
|
tags = [
|
||||||
|
["e", reply.id, FULL_RELAYS[0], "root"],
|
||||||
|
["e", reply.root, FULL_RELAYS[0], "reply"],
|
||||||
|
["p", reply.pubkey],
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
tags = [
|
||||||
|
["e", reply.id, FULL_RELAYS[0], "root"],
|
||||||
|
["p", reply.pubkey],
|
||||||
|
];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
kind = 1;
|
kind = 1;
|
||||||
tags = [];
|
tags = [];
|
||||||
@@ -130,14 +157,14 @@ export function Post() {
|
|||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Editable
|
<Editable
|
||||||
autoFocus
|
autoFocus
|
||||||
placeholder="What's on your mind?"
|
placeholder={
|
||||||
|
refID ? "Share your thoughts on it" : "What's on your mind?"
|
||||||
|
}
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
className={`${
|
className={`${refID ? "!min-h-42" : "!min-h-[86px]"} markdown`}
|
||||||
repost.id ? "!min-h-42" : "!min-h-[86px]"
|
|
||||||
} markdown`}
|
|
||||||
renderElement={renderElement}
|
renderElement={renderElement}
|
||||||
/>
|
/>
|
||||||
{repost.id && <MentionNote id={repost.id} />}
|
{refID && <MentionNote id={refID} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex items-center justify-between">
|
<div className="mt-4 flex items-center justify-between">
|
||||||
|
|||||||
@@ -2,11 +2,22 @@ import { MentionNote } from "@shared/notes/mentions/note";
|
|||||||
import { ImagePreview } from "@shared/notes/preview/image";
|
import { ImagePreview } from "@shared/notes/preview/image";
|
||||||
import { LinkPreview } from "@shared/notes/preview/link";
|
import { LinkPreview } from "@shared/notes/preview/link";
|
||||||
import { VideoPreview } from "@shared/notes/preview/video";
|
import { VideoPreview } from "@shared/notes/preview/video";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
export function Kind1({
|
export function Kind1({
|
||||||
content,
|
content,
|
||||||
truncate = false,
|
truncate = false,
|
||||||
}: { content: any; truncate?: boolean }) {
|
}: {
|
||||||
|
content: {
|
||||||
|
original: string;
|
||||||
|
parsed: ReactNode[];
|
||||||
|
notes: string[];
|
||||||
|
images: string[];
|
||||||
|
videos: string[];
|
||||||
|
links: string[];
|
||||||
|
};
|
||||||
|
truncate?: boolean;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
@@ -16,28 +27,15 @@ export function Kind1({
|
|||||||
>
|
>
|
||||||
{content.parsed}
|
{content.parsed}
|
||||||
</div>
|
</div>
|
||||||
{Array.isArray(content.images) && content.images.length ? (
|
{content.images.length > 0 && (
|
||||||
<ImagePreview urls={content.images} truncate={truncate} />
|
<ImagePreview urls={content.images} truncate={truncate} />
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
)}
|
||||||
{Array.isArray(content.videos) && content.videos.length ? (
|
{content.videos.length > 0 && <VideoPreview urls={content.videos} />}
|
||||||
<VideoPreview urls={content.videos} />
|
{content.links.length > 0 && <LinkPreview urls={content.links} />}
|
||||||
) : (
|
{content.notes.length > 0 &&
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
{Array.isArray(content.links) && content.links.length ? (
|
|
||||||
<LinkPreview urls={content.links} />
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
{Array.isArray(content.notes) && content.notes.length ? (
|
|
||||||
content.notes.map((note: string) => (
|
content.notes.map((note: string) => (
|
||||||
<MentionNote key={note} id={note} />
|
<MentionNote key={note} id={note} />
|
||||||
))
|
))}
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,8 @@ import { useEvent } from "@utils/hooks/useEvent";
|
|||||||
import { memo } from "react";
|
import { memo } from "react";
|
||||||
|
|
||||||
export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
||||||
const { status, data } = useEvent(id);
|
|
||||||
|
|
||||||
const kind1 = data?.kind === 1 ? data.content : null;
|
|
||||||
const kind1063 = data?.kind === 1063 ? data.tags : null;
|
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const { status, data } = useEvent(id);
|
||||||
|
|
||||||
const block = useMutation({
|
const block = useMutation({
|
||||||
mutationFn: (data: any) => {
|
mutationFn: (data: any) => {
|
||||||
@@ -37,17 +33,19 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
|||||||
<div
|
<div
|
||||||
onClick={(e) => openThread(e, id)}
|
onClick={(e) => openThread(e, id)}
|
||||||
onKeyDown={(e) => openThread(e, id)}
|
onKeyDown={(e) => openThread(e, id)}
|
||||||
className="mt-3 rounded-lg bg-zinc-800 border-t border-zinc-700/50 px-3 py-3"
|
className="mt-3 rounded-lg bg-zinc-800/50 border-t border-zinc-700/50 px-3 py-3"
|
||||||
>
|
>
|
||||||
{status === "loading" ? (
|
{status === "loading" ? (
|
||||||
<NoteSkeleton />
|
<NoteSkeleton />
|
||||||
) : (
|
) : status === "success" ? (
|
||||||
<>
|
<>
|
||||||
<User pubkey={data.pubkey} time={data.created_at} size="small" />
|
<User pubkey={data.pubkey} time={data.created_at} size="small" />
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
{kind1 && <Kind1 content={kind1} truncate={true} />}
|
{data.kind === 1 && (
|
||||||
{kind1063 && <Kind1063 metadata={kind1063} />}
|
<Kind1 content={data.content} truncate={true} />
|
||||||
{!kind1 && !kind1063 && (
|
)}
|
||||||
|
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||||
|
{data.kind !== 1 && data.kind !== 1063 && (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="px-2 py-2 inline-flex flex-col gap-1 bg-zinc-800 rounded-md">
|
<div className="px-2 py-2 inline-flex flex-col gap-1 bg-zinc-800 rounded-md">
|
||||||
<span className="text-zinc-500 text-sm font-medium leading-none">
|
<span className="text-zinc-500 text-sm font-medium leading-none">
|
||||||
@@ -64,6 +62,8 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
) : (
|
||||||
|
<p>Failed to fetch event</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import { useContext } from "react";
|
|||||||
|
|
||||||
export function NoteMetadata({
|
export function NoteMetadata({
|
||||||
id,
|
id,
|
||||||
|
rootID,
|
||||||
eventPubkey,
|
eventPubkey,
|
||||||
currentBlock,
|
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
|
rootID?: string;
|
||||||
eventPubkey: string;
|
eventPubkey: string;
|
||||||
currentBlock?: number;
|
|
||||||
}) {
|
}) {
|
||||||
const ndk = useContext(RelayContext);
|
const ndk = useContext(RelayContext);
|
||||||
const { status, data } = useQuery(["note-metadata", id], async () => {
|
const { status, data } = useQuery(["note-metadata", id], async () => {
|
||||||
@@ -112,8 +112,9 @@ export function NoteMetadata({
|
|||||||
<>
|
<>
|
||||||
<NoteReply
|
<NoteReply
|
||||||
id={id}
|
id={id}
|
||||||
|
rootID={rootID}
|
||||||
|
pubkey={eventPubkey}
|
||||||
replies={data.replies}
|
replies={data.replies}
|
||||||
currentBlock={currentBlock}
|
|
||||||
/>
|
/>
|
||||||
<NoteRepost id={id} pubkey={eventPubkey} reposts={data.reposts} />
|
<NoteRepost id={id} pubkey={eventPubkey} reposts={data.reposts} />
|
||||||
<NoteZap zaps={data.zap} />
|
<NoteZap zaps={data.zap} />
|
||||||
|
|||||||
@@ -1,36 +1,19 @@
|
|||||||
import { createBlock } from "@libs/storage";
|
|
||||||
import { ReplyIcon } from "@shared/icons";
|
import { ReplyIcon } from "@shared/icons";
|
||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useComposer } from "@stores/composer";
|
||||||
import { compactNumber } from "@utils/number";
|
import { compactNumber } from "@utils/number";
|
||||||
|
|
||||||
export function NoteReply({
|
export function NoteReply({
|
||||||
id,
|
id,
|
||||||
|
rootID,
|
||||||
|
pubkey,
|
||||||
replies,
|
replies,
|
||||||
}: { id: string; replies: number; currentBlock?: number }) {
|
}: { id: string; rootID?: string; pubkey: string; replies: number }) {
|
||||||
const queryClient = useQueryClient();
|
const setReply = useComposer((state) => state.setReply);
|
||||||
|
|
||||||
const block = useMutation({
|
|
||||||
mutationFn: (data: any) => {
|
|
||||||
return createBlock(data.kind, data.title, data.content);
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["blocks"] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const openThread = (event: any, thread: string) => {
|
|
||||||
const selection = window.getSelection();
|
|
||||||
if (selection.toString().length === 0) {
|
|
||||||
block.mutate({ kind: 2, title: "Thread", content: thread });
|
|
||||||
} else {
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={(e) => openThread(e, id)}
|
onClick={() => setReply(id, rootID, pubkey)}
|
||||||
className="w-20 group inline-flex items-center gap-1.5"
|
className="w-20 group inline-flex items-center gap-1.5"
|
||||||
>
|
>
|
||||||
<ReplyIcon
|
<ReplyIcon
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export function NoteRepost({
|
|||||||
pubkey,
|
pubkey,
|
||||||
reposts,
|
reposts,
|
||||||
}: { id: string; pubkey: string; reposts: number }) {
|
}: { id: string; pubkey: string; reposts: number }) {
|
||||||
const setRepost = useComposer((state: any) => state.setRepost);
|
const setRepost = useComposer((state) => state.setRepost);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export function Note({ event, block }: Note) {
|
|||||||
|
|
||||||
const renderParent = useMemo(() => {
|
const renderParent = useMemo(() => {
|
||||||
if (!isRepost && event.parent_id && event.parent_id !== event.event_id) {
|
if (!isRepost && event.parent_id && event.parent_id !== event.event_id) {
|
||||||
return <NoteParent id={event.parent_id} currentBlock={block} />;
|
return <NoteParent id={event.parent_id} />;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ export function Note({ event, block }: Note) {
|
|||||||
|
|
||||||
const renderRepost = useMemo(() => {
|
const renderRepost = useMemo(() => {
|
||||||
if (isRepost) {
|
if (isRepost) {
|
||||||
return <Repost event={event} currentBlock={block} />;
|
return <Repost event={event} />;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -71,13 +71,13 @@ export function Note({ event, block }: Note) {
|
|||||||
time={event.created_at}
|
time={event.created_at}
|
||||||
repost={isRepost}
|
repost={isRepost}
|
||||||
/>
|
/>
|
||||||
<div className="relative -mt-6 pl-[49px]">
|
<div className="-mt-6 pl-[49px]">
|
||||||
{renderContent}
|
{renderContent}
|
||||||
{!isRepost && (
|
{!isRepost && (
|
||||||
<NoteMetadata
|
<NoteMetadata
|
||||||
id={event.event_id}
|
id={event.event_id}
|
||||||
|
rootID={event.parent_id}
|
||||||
eventPubkey={event.pubkey}
|
eventPubkey={event.pubkey}
|
||||||
currentBlock={block || 1}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,21 +5,18 @@ import { NoteSkeleton } from "@shared/notes/skeleton";
|
|||||||
import { User } from "@shared/user";
|
import { User } from "@shared/user";
|
||||||
import { useEvent } from "@utils/hooks/useEvent";
|
import { useEvent } from "@utils/hooks/useEvent";
|
||||||
|
|
||||||
export function NoteParent({
|
export function NoteParent({ id }: { id: string }) {
|
||||||
id,
|
|
||||||
currentBlock,
|
|
||||||
}: { id: string; currentBlock: number }) {
|
|
||||||
const { status, data } = useEvent(id);
|
const { status, data } = useEvent(id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative overflow-hidden flex flex-col pb-6">
|
<div className="relative flex flex-col pb-6">
|
||||||
<div className="absolute left-[18px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
<div className="absolute left-[18px] top-0 h-full w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
||||||
{status === "loading" ? (
|
{status === "loading" ? (
|
||||||
<NoteSkeleton />
|
<NoteSkeleton />
|
||||||
) : (
|
) : status === "success" ? (
|
||||||
<>
|
<>
|
||||||
<User pubkey={data.pubkey} time={data.created_at} />
|
<User pubkey={data.pubkey} time={data.created_at} />
|
||||||
<div className="z-10 relative -mt-6 pl-[49px]">
|
<div className="-mt-6 pl-[49px]">
|
||||||
{data.kind === 1 && <Kind1 content={data.content} />}
|
{data.kind === 1 && <Kind1 content={data.content} />}
|
||||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||||
{data.kind !== 1 && data.kind !== 1063 && (
|
{data.kind !== 1 && data.kind !== 1063 && (
|
||||||
@@ -40,10 +37,11 @@ export function NoteParent({
|
|||||||
<NoteMetadata
|
<NoteMetadata
|
||||||
id={data.event_id || data.id}
|
id={data.event_id || data.id}
|
||||||
eventPubkey={data.pubkey}
|
eventPubkey={data.pubkey}
|
||||||
currentBlock={currentBlock}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
) : (
|
||||||
|
<p>Failed to fetch event</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function LinkPreview({ urls }: { urls: string[] }) {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<a
|
<a
|
||||||
className="flex flex-col rounded-lg border border-zinc-800/50 hover:border-fuchsia-900"
|
className="flex flex-col rounded-lg border border-zinc-800/50"
|
||||||
href={urls[0]}
|
href={urls[0]}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
|
|||||||
@@ -7,10 +7,7 @@ import { useEvent } from "@utils/hooks/useEvent";
|
|||||||
import { getRepostID } from "@utils/transform";
|
import { getRepostID } from "@utils/transform";
|
||||||
import { LumeEvent } from "@utils/types";
|
import { LumeEvent } from "@utils/types";
|
||||||
|
|
||||||
export function Repost({
|
export function Repost({ event }: { event: LumeEvent }) {
|
||||||
event,
|
|
||||||
currentBlock,
|
|
||||||
}: { event: LumeEvent; currentBlock?: number }) {
|
|
||||||
const repostID = getRepostID(event.tags);
|
const repostID = getRepostID(event.tags);
|
||||||
const { status, data } = useEvent(repostID);
|
const { status, data } = useEvent(repostID);
|
||||||
|
|
||||||
@@ -19,10 +16,10 @@ export function Repost({
|
|||||||
<div className="absolute left-[18px] -top-10 h-[50px] w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
<div className="absolute left-[18px] -top-10 h-[50px] w-0.5 bg-gradient-to-t from-zinc-800 to-zinc-600" />
|
||||||
{status === "loading" ? (
|
{status === "loading" ? (
|
||||||
<NoteSkeleton />
|
<NoteSkeleton />
|
||||||
) : (
|
) : status === "success" ? (
|
||||||
<>
|
<>
|
||||||
<User pubkey={data.pubkey} time={data.created_at} />
|
<User pubkey={data.pubkey} time={data.created_at} />
|
||||||
<div className="z-10 relative -mt-6 pl-[49px]">
|
<div className="-mt-6 pl-[49px]">
|
||||||
{data.kind === 1 && <Kind1 content={data.content} />}
|
{data.kind === 1 && <Kind1 content={data.content} />}
|
||||||
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
{data.kind === 1063 && <Kind1063 metadata={data.tags} />}
|
||||||
{data.kind !== 1 && data.kind !== 1063 && (
|
{data.kind !== 1 && data.kind !== 1063 && (
|
||||||
@@ -43,10 +40,11 @@ export function Repost({
|
|||||||
<NoteMetadata
|
<NoteMetadata
|
||||||
id={data.event_id || data.id}
|
id={data.event_id || data.id}
|
||||||
eventPubkey={data.pubkey}
|
eventPubkey={data.pubkey}
|
||||||
currentBlock={currentBlock}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
) : (
|
||||||
|
<p>Failed to fetch event</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,12 +20,31 @@ export function User({
|
|||||||
repost?: boolean;
|
repost?: boolean;
|
||||||
isChat?: boolean;
|
isChat?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { user } = useProfile(pubkey);
|
const { status, user } = useProfile(pubkey);
|
||||||
const createdAt = formatCreatedAt(time, isChat);
|
const createdAt = formatCreatedAt(time, isChat);
|
||||||
|
|
||||||
const avatarWidth = size === "small" ? "w-6" : "w-11";
|
const avatarWidth = size === "small" ? "w-6" : "w-11";
|
||||||
const avatarHeight = size === "small" ? "h-6" : "h-11";
|
const avatarHeight = size === "small" ? "h-6" : "h-11";
|
||||||
|
|
||||||
|
if (status === "loading") {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`relative flex gap-3 ${
|
||||||
|
size === "small" ? "items-center" : "items-start"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`${avatarWidth} ${avatarHeight} ${
|
||||||
|
size === "small" ? "rounded" : "rounded-lg"
|
||||||
|
} relative z-10 bg-zinc-800 animate-pulse shrink-0 overflow-hidden`}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-wrap items-baseline gap-1">
|
||||||
|
<div className="w-36 h-3.5 rounded bg-zinc-800 animate-pulse" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
className={`relative flex gap-3 ${
|
className={`relative flex gap-3 ${
|
||||||
@@ -47,10 +66,10 @@ export function User({
|
|||||||
<div className="flex flex-wrap items-baseline gap-1">
|
<div className="flex flex-wrap items-baseline gap-1">
|
||||||
<h5
|
<h5
|
||||||
className={`text-zinc-100 font-semibold leading-none truncate ${
|
className={`text-zinc-100 font-semibold leading-none truncate ${
|
||||||
size === "small" ? "max-w-[7rem]" : "max-w-[10rem]"
|
size === "small" ? "max-w-[8rem]" : "max-w-[15rem]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{user?.nip05 || user?.name || shortenKey(pubkey)}
|
{user?.nip05 || user?.name || user?.displayName || shortenKey(pubkey)}
|
||||||
</h5>
|
</h5>
|
||||||
{repost && (
|
{repost && (
|
||||||
<span className="font-semibold leading-none text-fuchsia-500">
|
<span className="font-semibold leading-none text-fuchsia-500">
|
||||||
@@ -70,8 +89,8 @@ export function User({
|
|||||||
leaveFrom="opacity-100 translate-y-0"
|
leaveFrom="opacity-100 translate-y-0"
|
||||||
leaveTo="opacity-0 translate-y-1"
|
leaveTo="opacity-0 translate-y-1"
|
||||||
>
|
>
|
||||||
<Popover.Panel className="absolute left-0 top-10 z-50 mt-3">
|
<Popover.Panel className="absolute z-50 top-10 mt-3">
|
||||||
<div className="w-full max-w-xs overflow-hidden rounded-md border border-zinc-800/50 bg-zinc-900/90 backdrop-blur-lg">
|
<div className="w-[250px] overflow-hidden rounded-md border border-zinc-800/50 bg-zinc-900/90 backdrop-blur-lg">
|
||||||
<div className="flex gap-2.5 border-b border-zinc-800 px-3 py-3">
|
<div className="flex gap-2.5 border-b border-zinc-800 px-3 py-3">
|
||||||
<Image
|
<Image
|
||||||
src={user?.image}
|
src={user?.image}
|
||||||
@@ -81,7 +100,7 @@ export function User({
|
|||||||
/>
|
/>
|
||||||
<div className="flex-1 flex flex-col gap-2">
|
<div className="flex-1 flex flex-col gap-2">
|
||||||
<div className="inline-flex flex-col gap-1">
|
<div className="inline-flex flex-col gap-1">
|
||||||
<h5 className="font-semibold leading-none">
|
<h5 className="font-semibold text-sm leading-none">
|
||||||
{user?.displayName || user?.name || (
|
{user?.displayName || user?.name || (
|
||||||
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
|
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
|
||||||
)}
|
)}
|
||||||
@@ -91,7 +110,7 @@ export function User({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="line-clamp-3 break-words text-sm leading-tight text-zinc-100">
|
<p className="line-clamp-3 break-words leading-tight text-zinc-100">
|
||||||
{user?.about}
|
{user?.about}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,27 +2,39 @@ import { create } from "zustand";
|
|||||||
|
|
||||||
interface ComposerState {
|
interface ComposerState {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
reply: null;
|
reply: { id: string; root: string; pubkey: string };
|
||||||
repost: { id: string; pubkey: string };
|
repost: { id: string; pubkey: string };
|
||||||
toggleModal: (status: boolean) => void;
|
toggleModal: (status: boolean) => void;
|
||||||
|
setReply: (id: string, root: string, pubkey: string) => void;
|
||||||
setRepost: (id: string, pubkey: string) => void;
|
setRepost: (id: string, pubkey: string) => void;
|
||||||
|
clearReply: () => void;
|
||||||
clearRepost: () => void;
|
clearRepost: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useComposer = create<ComposerState>((set) => ({
|
export const useComposer = create<ComposerState>((set) => ({
|
||||||
open: false,
|
open: false,
|
||||||
reply: null,
|
reply: { id: null, root: null, pubkey: null },
|
||||||
repost: { id: null, pubkey: null },
|
repost: { id: null, pubkey: null },
|
||||||
toggleModal: (status: boolean) => {
|
toggleModal: (status: boolean) => {
|
||||||
set({ open: status });
|
set({ open: status });
|
||||||
if (!status) {
|
if (!status) {
|
||||||
set({ repost: { id: null, pubkey: null } });
|
set({ repost: { id: null, pubkey: null } });
|
||||||
|
set({ reply: { id: null, root: null, pubkey: null } });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setReply: (id: string, root: string, pubkey: string) => {
|
||||||
|
set({ reply: { id: id, root: root, pubkey: pubkey } });
|
||||||
|
set({ repost: { id: null, pubkey: null } });
|
||||||
|
set({ open: true });
|
||||||
|
},
|
||||||
setRepost: (id: string, pubkey: string) => {
|
setRepost: (id: string, pubkey: string) => {
|
||||||
set({ repost: { id: id, pubkey: pubkey } });
|
set({ repost: { id: id, pubkey: pubkey } });
|
||||||
|
set({ reply: { id: null, root: null, pubkey: null } });
|
||||||
set({ open: true });
|
set({ open: true });
|
||||||
},
|
},
|
||||||
|
clearReply: () => {
|
||||||
|
set({ reply: { id: null, root: null, pubkey: null } });
|
||||||
|
},
|
||||||
clearRepost: () => {
|
clearRepost: () => {
|
||||||
set({ repost: { id: null, pubkey: null } });
|
set({ repost: { id: null, pubkey: null } });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ export function useEvent(id: string) {
|
|||||||
async () => {
|
async () => {
|
||||||
const result = await getNoteByID(id);
|
const result = await getNoteByID(id);
|
||||||
if (result) {
|
if (result) {
|
||||||
result["content"] = parser(result);
|
if (result.kind === 1 || result.kind === 1063) {
|
||||||
|
result["content"] = parser(result);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
const event = await ndk.fetchEvent(id);
|
const event = await ndk.fetchEvent(id);
|
||||||
@@ -24,8 +26,10 @@ export function useEvent(id: string) {
|
|||||||
event.created_at,
|
event.created_at,
|
||||||
);
|
);
|
||||||
event["event_id"] = event.id;
|
event["event_id"] = event.id;
|
||||||
// @ts-ignore
|
if (event.kind === 1 || event.kind === 1063) {
|
||||||
event["content"] = parser(event);
|
// @ts-ignore
|
||||||
|
event["content"] = parser(event);
|
||||||
|
}
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import { useAccount } from "./useAccount";
|
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
|
||||||
import { useQuery } from "@tanstack/react-query";
|
|
||||||
import { nip02ToArray } from "@utils/transform";
|
|
||||||
import { useContext } from "react";
|
|
||||||
|
|
||||||
export function useFollows() {
|
|
||||||
const ndk = useContext(RelayContext);
|
|
||||||
const { account } = useAccount();
|
|
||||||
const { status, data: follows } = useQuery(
|
|
||||||
["follows", account.pubkey],
|
|
||||||
async () => {
|
|
||||||
const res = await ndk.fetchEvents({
|
|
||||||
kinds: [3],
|
|
||||||
authors: [account.pubkey],
|
|
||||||
});
|
|
||||||
const latest = [...res].slice(-1)[0];
|
|
||||||
const list = nip02ToArray(latest.tags);
|
|
||||||
return list;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
enabled: account ? true : false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return { status, follows };
|
|
||||||
}
|
|
||||||
@@ -23,11 +23,13 @@ export function useProfile(id: string) {
|
|||||||
const current = Math.floor(Date.now() / 1000);
|
const current = Math.floor(Date.now() / 1000);
|
||||||
const result = await getPleb(npub);
|
const result = await getPleb(npub);
|
||||||
|
|
||||||
if (result && result.created_at + 86400 > current) {
|
if (result && parseInt(result.created_at) + 86400 >= current) {
|
||||||
|
console.log("cache", result);
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
const user = ndk.getUser({ npub });
|
const user = ndk.getUser({ npub });
|
||||||
await user.fetchProfile();
|
await user.fetchProfile();
|
||||||
|
console.log("new", user);
|
||||||
await createPleb(id, user.profile);
|
await createPleb(id, user.profile);
|
||||||
|
|
||||||
return user.profile;
|
return user.profile;
|
||||||
|
|||||||
65
src/utils/hooks/useSocial.tsx
Normal file
65
src/utils/hooks/useSocial.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { useAccount } from "./useAccount";
|
||||||
|
import { usePublish } from "@libs/ndk";
|
||||||
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { nip02ToArray } from "@utils/transform";
|
||||||
|
import { useContext } from "react";
|
||||||
|
|
||||||
|
export function useSocial() {
|
||||||
|
const ndk = useContext(RelayContext);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const publish = usePublish();
|
||||||
|
|
||||||
|
const { account } = useAccount();
|
||||||
|
const { status, data: userFollows } = useQuery(
|
||||||
|
["userFollows", account.pubkey],
|
||||||
|
async () => {
|
||||||
|
const res = await ndk.fetchEvents({
|
||||||
|
kinds: [3],
|
||||||
|
authors: [account.pubkey],
|
||||||
|
});
|
||||||
|
const latest = [...res].slice(-1)[0];
|
||||||
|
const list = nip02ToArray(latest.tags);
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: account ? true : false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const unfollow = (pubkey: string) => {
|
||||||
|
const followsAsSet = new Set(userFollows);
|
||||||
|
followsAsSet.delete(pubkey);
|
||||||
|
|
||||||
|
const tags = [];
|
||||||
|
followsAsSet.forEach((item) => {
|
||||||
|
tags.push(["p", item]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// publish event
|
||||||
|
publish({ content: "", kind: 3, tags: tags });
|
||||||
|
// invalid cache
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["userFollows", account.pubkey],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const follow = (pubkey: string) => {
|
||||||
|
const followsAsSet = new Set(userFollows);
|
||||||
|
followsAsSet.add(pubkey);
|
||||||
|
|
||||||
|
const tags = [];
|
||||||
|
followsAsSet.forEach((item) => {
|
||||||
|
tags.push(["p", item]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// publish event
|
||||||
|
publish({ content: "", kind: 3, tags: tags });
|
||||||
|
// invalid cache
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["userFollows", account.pubkey],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return { status, userFollows, follow, unfollow };
|
||||||
|
}
|
||||||
@@ -2,10 +2,11 @@ import { MentionUser } from "@shared/notes/mentions/user";
|
|||||||
import destr from "destr";
|
import destr from "destr";
|
||||||
import getUrls from "get-urls";
|
import getUrls from "get-urls";
|
||||||
import { parseReferences } from "nostr-tools";
|
import { parseReferences } from "nostr-tools";
|
||||||
|
import { ReactNode } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import reactStringReplace from "react-string-replace";
|
import reactStringReplace from "react-string-replace";
|
||||||
|
|
||||||
function isJsonString(str) {
|
function isJsonString(str: string) {
|
||||||
try {
|
try {
|
||||||
JSON.parse(str);
|
JSON.parse(str);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -24,7 +25,7 @@ export function parser(event: any) {
|
|||||||
|
|
||||||
const content: {
|
const content: {
|
||||||
original: string;
|
original: string;
|
||||||
parsed: any;
|
parsed: ReactNode[];
|
||||||
notes: string[];
|
notes: string[];
|
||||||
images: string[];
|
images: string[];
|
||||||
videos: string[];
|
videos: string[];
|
||||||
@@ -39,9 +40,11 @@ export function parser(event: any) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// remove unnecessary whitespaces
|
// remove unnecessary whitespaces
|
||||||
|
// @ts-ignore
|
||||||
content.parsed = content.parsed.replace(/\s{2,}/g, " ");
|
content.parsed = content.parsed.replace(/\s{2,}/g, " ");
|
||||||
|
|
||||||
// remove unnecessary linebreak
|
// remove unnecessary linebreak
|
||||||
|
// @ts-ignore
|
||||||
content.parsed = content.parsed.replace(/(\r\n|\r|\n){2,}/g, "$1\n");
|
content.parsed = content.parsed.replace(/(\r\n|\r|\n){2,}/g, "$1\n");
|
||||||
|
|
||||||
// parse urls
|
// parse urls
|
||||||
|
|||||||
Reference in New Issue
Block a user