mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-19 11:43:30 +00:00
update space screen
This commit is contained in:
parent
917e49b25d
commit
4830f0b236
@ -19,11 +19,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ctrl/magnet-link": "^3.1.2",
|
"@ctrl/magnet-link": "^3.1.2",
|
||||||
"@headlessui/react": "^1.7.17",
|
"@headlessui/react": "^1.7.17",
|
||||||
"@nostr-dev-kit/ndk": "^0.8.18",
|
"@nostr-dev-kit/ndk": "^0.8.19",
|
||||||
"@nostr-fetch/adapter-ndk": "^0.12.2",
|
"@nostr-fetch/adapter-ndk": "^0.12.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.0.4",
|
"@radix-ui/react-alert-dialog": "^1.0.4",
|
||||||
"@radix-ui/react-collapsible": "^1.0.3",
|
"@radix-ui/react-collapsible": "^1.0.3",
|
||||||
"@radix-ui/react-dialog": "^1.0.4",
|
"@radix-ui/react-dialog": "^1.0.4",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||||
"@radix-ui/react-popover": "^1.0.6",
|
"@radix-ui/react-popover": "^1.0.6",
|
||||||
"@radix-ui/react-tooltip": "^1.0.6",
|
"@radix-ui/react-tooltip": "^1.0.6",
|
||||||
"@tanstack/react-query": "^4.33.0",
|
"@tanstack/react-query": "^4.33.0",
|
||||||
|
155
pnpm-lock.yaml
155
pnpm-lock.yaml
@ -8,11 +8,11 @@ dependencies:
|
|||||||
specifier: ^1.7.17
|
specifier: ^1.7.17
|
||||||
version: 1.7.17(react-dom@18.2.0)(react@18.2.0)
|
version: 1.7.17(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@nostr-dev-kit/ndk':
|
'@nostr-dev-kit/ndk':
|
||||||
specifier: ^0.8.18
|
specifier: ^0.8.19
|
||||||
version: 0.8.18(typescript@5.1.6)
|
version: 0.8.19(typescript@5.1.6)
|
||||||
'@nostr-fetch/adapter-ndk':
|
'@nostr-fetch/adapter-ndk':
|
||||||
specifier: ^0.12.2
|
specifier: ^0.12.2
|
||||||
version: 0.12.2(@nostr-dev-kit/ndk@0.8.18)(nostr-fetch@0.12.2)
|
version: 0.12.2(@nostr-dev-kit/ndk@0.8.19)(nostr-fetch@0.12.2)
|
||||||
'@radix-ui/react-alert-dialog':
|
'@radix-ui/react-alert-dialog':
|
||||||
specifier: ^1.0.4
|
specifier: ^1.0.4
|
||||||
version: 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
version: 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
@ -22,6 +22,9 @@ dependencies:
|
|||||||
'@radix-ui/react-dialog':
|
'@radix-ui/react-dialog':
|
||||||
specifier: ^1.0.4
|
specifier: ^1.0.4
|
||||||
version: 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
version: 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-dropdown-menu':
|
||||||
|
specifier: ^2.0.5
|
||||||
|
version: 2.0.5(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@radix-ui/react-popover':
|
'@radix-ui/react-popover':
|
||||||
specifier: ^1.0.6
|
specifier: ^1.0.6
|
||||||
version: 1.0.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
version: 1.0.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
@ -965,8 +968,8 @@ packages:
|
|||||||
'@nodelib/fs.scandir': 2.1.5
|
'@nodelib/fs.scandir': 2.1.5
|
||||||
fastq: 1.15.0
|
fastq: 1.15.0
|
||||||
|
|
||||||
/@nostr-dev-kit/ndk@0.8.18(typescript@5.1.6):
|
/@nostr-dev-kit/ndk@0.8.19(typescript@5.1.6):
|
||||||
resolution: {integrity: sha512-LbEVEjJgFqLmKD0Y76bPxaa7YLlRuz3cPM+kg3XNqHqdN9cDnklYYxAcels58OQqgtFsZBxoqyT64JoYREdr+g==}
|
resolution: {integrity: sha512-CyqZZiwhsWCCkfoT9JUAyDoQnfyFUVvb7vFhmsxg51eICqLV8SXNxKEyNaMFN1UJdqqi0GamFwisitC65c12jw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@noble/hashes': 1.3.1
|
'@noble/hashes': 1.3.1
|
||||||
'@noble/secp256k1': 2.0.0
|
'@noble/secp256k1': 2.0.0
|
||||||
@ -994,13 +997,13 @@ packages:
|
|||||||
- typescript
|
- typescript
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@nostr-fetch/adapter-ndk@0.12.2(@nostr-dev-kit/ndk@0.8.18)(nostr-fetch@0.12.2):
|
/@nostr-fetch/adapter-ndk@0.12.2(@nostr-dev-kit/ndk@0.8.19)(nostr-fetch@0.12.2):
|
||||||
resolution: {integrity: sha512-+7EVuxS5DDZvNo6qbfFp7xRHwIyjyi36hYkiQFDjbQ4gX5LKo9RIPB1P+1XGkOSDFshypTbovZCaFunscJ/zhQ==}
|
resolution: {integrity: sha512-+7EVuxS5DDZvNo6qbfFp7xRHwIyjyi36hYkiQFDjbQ4gX5LKo9RIPB1P+1XGkOSDFshypTbovZCaFunscJ/zhQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@nostr-dev-kit/ndk': ^0.7.5
|
'@nostr-dev-kit/ndk': ^0.7.5
|
||||||
nostr-fetch: ^0.12.2
|
nostr-fetch: ^0.12.2
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nostr-dev-kit/ndk': 0.8.18(typescript@5.1.6)
|
'@nostr-dev-kit/ndk': 0.8.19(typescript@5.1.6)
|
||||||
'@nostr-fetch/kernel': 0.12.2
|
'@nostr-fetch/kernel': 0.12.2
|
||||||
nostr-fetch: 0.12.2
|
nostr-fetch: 0.12.2
|
||||||
dev: false
|
dev: false
|
||||||
@ -1097,6 +1100,30 @@ packages:
|
|||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.22.10
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-context': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@types/react': 18.2.20
|
||||||
|
'@types/react-dom': 18.2.7
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.20)(react@18.2.0):
|
/@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.20)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
|
resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1159,6 +1186,20 @@ packages:
|
|||||||
react-remove-scroll: 2.5.5(@types/react@18.2.20)(react@18.2.0)
|
react-remove-scroll: 2.5.5(@types/react@18.2.20)(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-direction@1.0.1(@types/react@18.2.20)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.22.10
|
||||||
|
'@types/react': 18.2.20
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-dismissable-layer@1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
/@radix-ui/react-dismissable-layer@1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg==}
|
resolution: {integrity: sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1184,6 +1225,33 @@ packages:
|
|||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-dropdown-menu@2.0.5(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-xdOrZzOTocqqkCkYo8yRPCib5OkTkqN7lqNCdxwPOdE466DOaNl4N8PkUIlsXthQvW5Wwkd+aEmWpfWlBoDPEw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.22.10
|
||||||
|
'@radix-ui/primitive': 1.0.1
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-context': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-id': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-menu': 2.0.5(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@types/react': 18.2.20
|
||||||
|
'@types/react-dom': 18.2.7
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.20)(react@18.2.0):
|
/@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.20)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
|
resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1236,6 +1304,44 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-menu@2.0.5(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-Gw4f9pwdH+w5w+49k0gLjN0PfRDHvxmAgG16AbyJZ7zhwZ6PBHKtWohvnSwfusfnK3L68dpBREHpVkj8wEM7ZA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.22.10
|
||||||
|
'@radix-ui/primitive': 1.0.1
|
||||||
|
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-context': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-focus-scope': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-id': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-popper': 1.1.2(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-portal': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-slot': 1.0.2(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@types/react': 18.2.20
|
||||||
|
'@types/react-dom': 18.2.7
|
||||||
|
aria-hidden: 1.2.3
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
react-remove-scroll: 2.5.5(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-popover@1.0.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
/@radix-ui/react-popover@1.0.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-cZ4defGpkZ0qTRtlIBzJLSzL6ht7ofhhW4i1+pkemjV1IKXm0wgCRnee154qlV6r9Ttunmh2TNZhMfV2bavUyA==}
|
resolution: {integrity: sha512-cZ4defGpkZ0qTRtlIBzJLSzL6ht7ofhhW4i1+pkemjV1IKXm0wgCRnee154qlV6r9Ttunmh2TNZhMfV2bavUyA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -1365,6 +1471,35 @@ packages:
|
|||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.22.10
|
||||||
|
'@radix-ui/primitive': 1.0.1
|
||||||
|
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-context': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-id': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.20)(react@18.2.0)
|
||||||
|
'@types/react': 18.2.20
|
||||||
|
'@types/react-dom': 18.2.7
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-slot@1.0.2(@types/react@18.2.20)(react@18.2.0):
|
/@radix-ui/react-slot@1.0.2(@types/react@18.2.20)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
|
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2087,7 +2222,7 @@ packages:
|
|||||||
prosemirror-inputrules: 1.2.1
|
prosemirror-inputrules: 1.2.1
|
||||||
prosemirror-keymap: 1.2.2
|
prosemirror-keymap: 1.2.2
|
||||||
prosemirror-markdown: 1.11.2
|
prosemirror-markdown: 1.11.2
|
||||||
prosemirror-menu: 1.2.3
|
prosemirror-menu: 1.2.4
|
||||||
prosemirror-model: 1.19.3
|
prosemirror-model: 1.19.3
|
||||||
prosemirror-schema-basic: 1.2.2
|
prosemirror-schema-basic: 1.2.2
|
||||||
prosemirror-schema-list: 1.3.0
|
prosemirror-schema-list: 1.3.0
|
||||||
@ -5671,8 +5806,8 @@ packages:
|
|||||||
prosemirror-model: 1.19.3
|
prosemirror-model: 1.19.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/prosemirror-menu@1.2.3:
|
/prosemirror-menu@1.2.4:
|
||||||
resolution: {integrity: sha512-13H9+XvdJiUt2vQVMqCveFbc7YfEKR3g70pUwuQdQLwuvNfVGTzMHr1y5dwdY5vOBQbzhmjgnWUnclKzMdnlJA==}
|
resolution: {integrity: sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
crelt: 1.0.6
|
crelt: 1.0.6
|
||||||
prosemirror-commands: 1.5.2
|
prosemirror-commands: 1.5.2
|
||||||
|
10
src-tauri/Cargo.lock
generated
10
src-tauri/Cargo.lock
generated
@ -241,14 +241,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-executor"
|
name = "async-executor"
|
||||||
version = "1.5.2"
|
version = "1.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "232344f51ca91edec473432f677c0f5ddf8deaa72f165d253ee19fb196f7d6f2"
|
checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-lock",
|
"async-lock",
|
||||||
"async-task",
|
"async-task",
|
||||||
"concurrent-queue",
|
"concurrent-queue",
|
||||||
"fastrand 2.0.0",
|
"fastrand 1.9.0",
|
||||||
"futures-lite",
|
"futures-lite",
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
@ -663,9 +663,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.82"
|
version = "1.0.83"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
|
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -76,7 +76,7 @@ export function OnboardStep1Screen() {
|
|||||||
<p className="text-sm text-white/50">Choose account you want to follow</p>
|
<p className="text-sm text-white/50">Choose account you want to follow</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="scrollbar-hide flex h-[500px] w-full flex-col overflow-y-auto rounded-xl bg-white/10">
|
<div className="scrollbar-hide flex h-[500px] w-full flex-col overflow-y-auto rounded-xl bg-white/10 py-2">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<LoaderIcon className="h-4 w-4 animate-spin text-white" />
|
<LoaderIcon className="h-4 w-4 animate-spin text-white" />
|
||||||
@ -88,7 +88,7 @@ export function OnboardStep1Screen() {
|
|||||||
key={item.pubkey}
|
key={item.pubkey}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => toggleFollow(item.pubkey)}
|
onClick={() => toggleFollow(item.pubkey)}
|
||||||
className="inline-flex transform items-center justify-between bg-white/10 px-4 py-2 hover:bg-white/20"
|
className="inline-flex transform items-center justify-between px-4 py-2 hover:bg-white/20"
|
||||||
>
|
>
|
||||||
<User pubkey={item.pubkey} fallback={item.profile?.content} />
|
<User pubkey={item.pubkey} fallback={item.profile?.content} />
|
||||||
{follows.includes(item.pubkey) && (
|
{follows.includes(item.pubkey) && (
|
||||||
|
@ -5,8 +5,8 @@ import { useStorage } from '@libs/storage/provider';
|
|||||||
|
|
||||||
import { ArrowRightCircleIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons';
|
import { ArrowRightCircleIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { widgetKinds } from '@stores/constants';
|
|
||||||
import { useOnboarding } from '@stores/onboarding';
|
import { useOnboarding } from '@stores/onboarding';
|
||||||
|
import { WidgetKinds } from '@stores/widgets';
|
||||||
|
|
||||||
const data = [
|
const data = [
|
||||||
{ hashtag: '#bitcoin' },
|
{ hashtag: '#bitcoin' },
|
||||||
@ -52,7 +52,7 @@ export function OnboardStep2Screen() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
await db.createWidget(widgetKinds.hashtag, tag, tag.replace('#', ''));
|
await db.createWidget(WidgetKinds.hashtag, tag, tag.replace('#', ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
navigate('/auth/onboarding/step-3', { replace: true });
|
navigate('/auth/onboarding/step-3', { replace: true });
|
||||||
|
163
src/app/space/components/button.tsx
Normal file
163
src/app/space/components/button.tsx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||||
|
|
||||||
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FeedIcon,
|
||||||
|
FileIcon,
|
||||||
|
HashtagIcon,
|
||||||
|
PlusIcon,
|
||||||
|
ThreadsIcon,
|
||||||
|
TrendingIcon,
|
||||||
|
} from '@shared/icons';
|
||||||
|
|
||||||
|
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||||
|
|
||||||
|
export function AddWidgetButton() {
|
||||||
|
const { db } = useStorage();
|
||||||
|
const setWidget = useWidgets((state) => state.setWidget);
|
||||||
|
|
||||||
|
const setTrendingProfilesWidget = () => {
|
||||||
|
setWidget(db, {
|
||||||
|
kind: WidgetKinds.trendingProfiles,
|
||||||
|
title: 'Trending Profiles',
|
||||||
|
content: 'https://api.nostr.band/v0/trending/profiles',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setTrendingNotesWidget = () => {
|
||||||
|
setWidget(db, {
|
||||||
|
kind: WidgetKinds.trendingNotes,
|
||||||
|
title: 'Trending Notes',
|
||||||
|
content: 'https://api.nostr.band/v0/trending/notes',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setArticleWidget = () => {
|
||||||
|
setWidget(db, {
|
||||||
|
kind: WidgetKinds.article,
|
||||||
|
title: 'Blogs',
|
||||||
|
content: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setFileWidget = () => {
|
||||||
|
setWidget(db, {
|
||||||
|
kind: WidgetKinds.file,
|
||||||
|
title: 'Files',
|
||||||
|
content: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setHashtagWidget = () => {
|
||||||
|
setWidget(db, {
|
||||||
|
kind: WidgetKinds.xhashtag,
|
||||||
|
title: 'New hashtag',
|
||||||
|
content: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setGroupFeedWidget = () => {
|
||||||
|
setWidget(db, {
|
||||||
|
kind: WidgetKinds.xfeed,
|
||||||
|
title: 'New user group feed',
|
||||||
|
content: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu.Root>
|
||||||
|
<div className="flex h-full shrink-0 grow-0 basis-[400px] flex-col">
|
||||||
|
<div className="inline-flex h-full w-full flex-col items-center justify-center">
|
||||||
|
<DropdownMenu.Trigger asChild>
|
||||||
|
<button type="button" className="flex flex-col items-center gap-2">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-white/5 hover:bg-white/10">
|
||||||
|
<PlusIcon className="h-5 w-5 text-white" />
|
||||||
|
</div>
|
||||||
|
<p className="font-medium text-white/50">Add widget</p>
|
||||||
|
</button>
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DropdownMenu.Portal>
|
||||||
|
<DropdownMenu.Content
|
||||||
|
sideOffset={-20}
|
||||||
|
className="flex w-[256px] flex-col overflow-hidden rounded-md bg-white/10 p-2 backdrop-blur-3xl focus:outline-none"
|
||||||
|
>
|
||||||
|
<DropdownMenu.Item asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={setHashtagWidget}
|
||||||
|
className="inline-flex h-11 items-center gap-2 rounded-md px-2 text-sm font-medium text-white hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10">
|
||||||
|
<HashtagIcon className="h-4 w-4 text-white" />
|
||||||
|
</div>
|
||||||
|
Add hashtag feeds
|
||||||
|
</button>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={setGroupFeedWidget}
|
||||||
|
className="inline-flex h-11 items-center gap-2 rounded-md px-2 text-sm font-medium text-white hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10">
|
||||||
|
<FeedIcon className="h-4 w-4 text-white" />
|
||||||
|
</div>
|
||||||
|
Add user group feeds
|
||||||
|
</button>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={setArticleWidget}
|
||||||
|
className="inline-flex h-11 items-center gap-2 rounded-md px-2 text-sm font-medium text-white hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10">
|
||||||
|
<ThreadsIcon className="h-4 w-4 text-white" />
|
||||||
|
</div>
|
||||||
|
Add article feeds
|
||||||
|
</button>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={setFileWidget}
|
||||||
|
className="inline-flex h-11 items-center gap-2 rounded-md px-2 text-sm font-medium text-white hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10">
|
||||||
|
<FileIcon className="h-4 w-4 text-white" />
|
||||||
|
</div>
|
||||||
|
Add file feeds
|
||||||
|
</button>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={setTrendingProfilesWidget}
|
||||||
|
className="inline-flex h-11 items-center gap-2 rounded-md px-2 text-sm font-medium text-white hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10">
|
||||||
|
<TrendingIcon className="h-4 w-4 text-white" />
|
||||||
|
</div>
|
||||||
|
Add trending accounts
|
||||||
|
</button>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={setTrendingNotesWidget}
|
||||||
|
className="inline-flex h-11 items-center gap-2 rounded-md px-2 text-sm font-medium text-white hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10">
|
||||||
|
<TrendingIcon className="h-4 w-4 text-white" />
|
||||||
|
</div>
|
||||||
|
Add trending notes
|
||||||
|
</button>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Portal>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
);
|
||||||
|
}
|
89
src/app/space/components/forms/feed.tsx
Normal file
89
src/app/space/components/forms/feed.tsx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { User } from '@app/auth/components/user';
|
||||||
|
|
||||||
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { ArrowRightCircleIcon, CheckCircleIcon } from '@shared/icons';
|
||||||
|
|
||||||
|
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||||
|
|
||||||
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
|
export function FeedWidgetForm({ params }: { params: Widget }) {
|
||||||
|
const { db } = useStorage();
|
||||||
|
|
||||||
|
const [setWidget, removeWidget] = useWidgets((state) => [
|
||||||
|
state.setWidget,
|
||||||
|
state.removeWidget,
|
||||||
|
]);
|
||||||
|
const [title, setTitle] = useState<string>('');
|
||||||
|
const [groups, setGroups] = useState<Array<string>>([]);
|
||||||
|
|
||||||
|
// toggle follow state
|
||||||
|
const toggleGroup = (pubkey: string) => {
|
||||||
|
const arr = groups.includes(pubkey)
|
||||||
|
? groups.filter((i) => i !== pubkey)
|
||||||
|
: [...groups, pubkey];
|
||||||
|
setGroups(arr);
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
setWidget(db, {
|
||||||
|
kind: WidgetKinds.feed,
|
||||||
|
title: title || 'Group',
|
||||||
|
content: JSON.stringify(groups),
|
||||||
|
});
|
||||||
|
// remove temp widget
|
||||||
|
removeWidget(db, params.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full shrink-0 grow-0 basis-[400px] flex-col items-center justify-center">
|
||||||
|
<div className="w-full px-5">
|
||||||
|
<h3 className="mb-4 text-center text-lg font-semibold">
|
||||||
|
Choose account you want to add to group feeds
|
||||||
|
</h3>
|
||||||
|
<div className="mb-0 flex flex-col gap-2">
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
placeholder="Title"
|
||||||
|
className="relative h-11 w-full rounded-lg bg-white/10 px-3 py-1 text-white !outline-none placeholder:text-white/50"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="scrollbar-hide flex h-[500px] w-full flex-col overflow-y-auto rounded-lg bg-white/10 py-2">
|
||||||
|
{db.account.network.map((item: string) => (
|
||||||
|
<button
|
||||||
|
key={item}
|
||||||
|
type="button"
|
||||||
|
onClick={() => toggleGroup(item)}
|
||||||
|
className="inline-flex transform items-center justify-between px-4 py-2 hover:bg-white/20"
|
||||||
|
>
|
||||||
|
<User pubkey={item} />
|
||||||
|
{groups.includes(item) && (
|
||||||
|
<div>
|
||||||
|
<CheckCircleIcon className="h-4 w-4 text-green-400" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={groups.length < 1}
|
||||||
|
onClick={submit}
|
||||||
|
className="inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<span className="w-5" />
|
||||||
|
<span>Add {groups.length} account to group feed</span>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
92
src/app/space/components/forms/hashtag.tsx
Normal file
92
src/app/space/components/forms/hashtag.tsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { Resolver, useForm } from 'react-hook-form';
|
||||||
|
|
||||||
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { ArrowRightCircleIcon } from '@shared/icons';
|
||||||
|
|
||||||
|
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||||
|
|
||||||
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
|
type FormValues = {
|
||||||
|
hashtag: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolver: Resolver<FormValues> = async (values) => {
|
||||||
|
return {
|
||||||
|
values: values.hashtag ? values : {},
|
||||||
|
errors: !values.hashtag
|
||||||
|
? {
|
||||||
|
hashtag: {
|
||||||
|
type: 'required',
|
||||||
|
message: 'This is required.',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function HashTagWidgetForm({ params }: { params: Widget }) {
|
||||||
|
const [setWidget, removeWidget] = useWidgets((state) => [
|
||||||
|
state.setWidget,
|
||||||
|
state.removeWidget,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { db } = useStorage();
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
setError,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors, isDirty, isValid },
|
||||||
|
} = useForm<FormValues>({ resolver });
|
||||||
|
|
||||||
|
const onSubmit = async (data: FormValues) => {
|
||||||
|
try {
|
||||||
|
setWidget(db, {
|
||||||
|
kind: WidgetKinds.hashtag,
|
||||||
|
title: data.hashtag + ' in 24 hours ago',
|
||||||
|
content: data.hashtag.replace('#', ''),
|
||||||
|
});
|
||||||
|
// remove temp widget
|
||||||
|
removeWidget(db, params.id);
|
||||||
|
} catch (e) {
|
||||||
|
setError('hashtag', {
|
||||||
|
type: 'custom',
|
||||||
|
message: e,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full shrink-0 grow-0 basis-[400px] flex-col items-center justify-center">
|
||||||
|
<div className="w-full px-5">
|
||||||
|
<h3 className="mb-4 text-center text-lg font-semibold">
|
||||||
|
Enter hashtag you want to follow
|
||||||
|
</h3>
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)} className="mb-0 flex flex-col gap-2">
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<input
|
||||||
|
{...register('hashtag', { required: true, minLength: 1 })}
|
||||||
|
placeholder="#bitcoin"
|
||||||
|
className="relative h-11 w-full rounded-lg bg-white/10 px-3 py-1 text-white !outline-none placeholder:text-white/50"
|
||||||
|
/>
|
||||||
|
<span className="text-sm text-red-400">
|
||||||
|
{errors.hashtag && <p>{errors.hashtag.message}</p>}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={!isDirty || !isValid}
|
||||||
|
className="inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<span className="w-5" />
|
||||||
|
<span>Create</span>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -10,8 +10,7 @@ import { useStorage } from '@libs/storage/provider';
|
|||||||
|
|
||||||
import { CancelIcon, CheckCircleIcon, CommandIcon, LoaderIcon } from '@shared/icons';
|
import { CancelIcon, CheckCircleIcon, CommandIcon, LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { widgetKinds } from '@stores/constants';
|
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||||
import { useWidgets } from '@stores/widgets';
|
|
||||||
|
|
||||||
export function FeedModal() {
|
export function FeedModal() {
|
||||||
const setWidget = useWidgets((state) => state.setWidget);
|
const setWidget = useWidgets((state) => state.setWidget);
|
||||||
@ -40,7 +39,7 @@ export function FeedModal() {
|
|||||||
|
|
||||||
// update state
|
// update state
|
||||||
setWidget(db, {
|
setWidget(db, {
|
||||||
kind: widgetKinds.feed,
|
kind: WidgetKinds.feed,
|
||||||
title: data.title,
|
title: data.title,
|
||||||
content: JSON.stringify(selected),
|
content: JSON.stringify(selected),
|
||||||
});
|
});
|
||||||
|
@ -6,8 +6,7 @@ import { useStorage } from '@libs/storage/provider';
|
|||||||
|
|
||||||
import { CancelIcon, CommandIcon, LoaderIcon } from '@shared/icons';
|
import { CancelIcon, CommandIcon, LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { widgetKinds } from '@stores/constants';
|
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||||
import { useWidgets } from '@stores/widgets';
|
|
||||||
|
|
||||||
export function HashtagModal() {
|
export function HashtagModal() {
|
||||||
const setWidget = useWidgets((state) => state.setWidget);
|
const setWidget = useWidgets((state) => state.setWidget);
|
||||||
@ -28,7 +27,7 @@ export function HashtagModal() {
|
|||||||
|
|
||||||
// update state
|
// update state
|
||||||
setWidget(db, {
|
setWidget(db, {
|
||||||
kind: widgetKinds.hashtag,
|
kind: WidgetKinds.hashtag,
|
||||||
title: data.hashtag,
|
title: data.hashtag,
|
||||||
content: data.hashtag.replace('#', ''),
|
content: data.hashtag.replace('#', ''),
|
||||||
});
|
});
|
||||||
|
@ -1,158 +0,0 @@
|
|||||||
import * as Dialog from '@radix-ui/react-dialog';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useForm } from 'react-hook-form';
|
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
|
||||||
|
|
||||||
import { CancelIcon, CommandIcon, LoaderIcon } from '@shared/icons';
|
|
||||||
import { Image } from '@shared/image';
|
|
||||||
|
|
||||||
import { widgetKinds } from '@stores/constants';
|
|
||||||
import { useWidgets } from '@stores/widgets';
|
|
||||||
|
|
||||||
import { useImageUploader } from '@utils/hooks/useUploader';
|
|
||||||
|
|
||||||
export function ImageModal() {
|
|
||||||
const upload = useImageUploader();
|
|
||||||
const setWidget = useWidgets((state) => state.setWidget);
|
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [image, setImage] = useState('');
|
|
||||||
|
|
||||||
const { db } = useStorage();
|
|
||||||
const {
|
|
||||||
register,
|
|
||||||
handleSubmit,
|
|
||||||
reset,
|
|
||||||
setValue,
|
|
||||||
formState: { isDirty, isValid },
|
|
||||||
} = useForm();
|
|
||||||
|
|
||||||
const uploadImage = async () => {
|
|
||||||
const image = await upload(null, true);
|
|
||||||
if (image.url) {
|
|
||||||
setImage(image.url);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSubmit = async (data: { kind: number; title: string; content: string }) => {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
// mutate
|
|
||||||
setWidget(db, { kind: widgetKinds.image, title: data.title, content: data.content });
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
// reset form
|
|
||||||
reset();
|
|
||||||
// close modal
|
|
||||||
setOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setValue('content', image);
|
|
||||||
}, [setValue, image]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog.Root open={open} onOpenChange={setOpen}>
|
|
||||||
<Dialog.Trigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="inline-flex h-9 w-72 items-center justify-start gap-2.5 rounded-md px-2.5"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10">
|
|
||||||
<CommandIcon width={12} height={12} className="text-white" />
|
|
||||||
</div>
|
|
||||||
<div className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded bg-white/10">
|
|
||||||
<span className="text-sm leading-none text-white">I</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<h5 className="font-medium text-white/50">Add image widget</h5>
|
|
||||||
</button>
|
|
||||||
</Dialog.Trigger>
|
|
||||||
<Dialog.Portal className="relative z-10">
|
|
||||||
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/80 backdrop-blur-xl" />
|
|
||||||
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
|
||||||
<div className="relative h-min w-full max-w-xl rounded-xl bg-white/10">
|
|
||||||
<div className="h-min w-full shrink-0 border-b border-white/10 bg-white/5 px-5 py-5">
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Dialog.Title className="text-lg font-semibold leading-none text-white">
|
|
||||||
Create image block
|
|
||||||
</Dialog.Title>
|
|
||||||
<Dialog.Close className="inline-flex h-6 w-6 items-center justify-center rounded-md hover:bg-white/10">
|
|
||||||
<CancelIcon className="h-4 w-4 text-white/50" />
|
|
||||||
</Dialog.Close>
|
|
||||||
</div>
|
|
||||||
<Dialog.Description className="text-sm leading-tight text-white/50">
|
|
||||||
Pin your favorite image to Space then you can view every time that you
|
|
||||||
use Lume, your image will be broadcast to Nostr Relay as well
|
|
||||||
</Dialog.Description>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex h-full w-full flex-col overflow-y-auto px-5 pb-5 pt-3">
|
|
||||||
<form
|
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
|
||||||
className="mb-0 flex h-full w-full flex-col gap-3"
|
|
||||||
>
|
|
||||||
<input type={'hidden'} {...register('content')} value={image} />
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<label
|
|
||||||
htmlFor="title"
|
|
||||||
className="text-sm font-medium uppercase tracking-wider text-white/50"
|
|
||||||
>
|
|
||||||
Title *
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type={'text'}
|
|
||||||
{...register('title', {
|
|
||||||
required: true,
|
|
||||||
})}
|
|
||||||
spellCheck={false}
|
|
||||||
className="relative h-11 w-full rounded-lg bg-white/10 px-3 py-2 text-white !outline-none placeholder:text-white/50"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-1">
|
|
||||||
<label
|
|
||||||
htmlFor="picture"
|
|
||||||
className="text-sm font-medium uppercase tracking-wider text-white/50"
|
|
||||||
>
|
|
||||||
Picture
|
|
||||||
</label>
|
|
||||||
<div className="relative inline-flex h-56 w-full items-center justify-center overflow-hidden rounded-lg bg-white/10">
|
|
||||||
<Image
|
|
||||||
src={image}
|
|
||||||
alt="content"
|
|
||||||
className="relative z-10 h-auto max-h-[156px] w-[150px] rounded-md object-cover"
|
|
||||||
/>
|
|
||||||
<div className="absolute bottom-3 right-3 z-10">
|
|
||||||
<button
|
|
||||||
onClick={() => uploadImage()}
|
|
||||||
type="button"
|
|
||||||
className="inline-flex h-6 items-center justify-center rounded bg-white/10 px-3 text-sm font-medium text-white hover:bg-fuchsia-500"
|
|
||||||
>
|
|
||||||
Upload
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
disabled={!isDirty || !isValid}
|
|
||||||
className="inline-flex h-11 w-full transform items-center justify-center rounded-lg bg-fuchsia-500 font-medium text-white active:translate-y-1 disabled:cursor-not-allowed disabled:opacity-30"
|
|
||||||
>
|
|
||||||
{loading ? (
|
|
||||||
<LoaderIcon className="h-4 w-4 animate-spin text-white" />
|
|
||||||
) : (
|
|
||||||
'Confirm'
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog.Content>
|
|
||||||
</Dialog.Portal>
|
|
||||||
</Dialog.Root>
|
|
||||||
);
|
|
||||||
}
|
|
114
src/app/space/components/widgets/article.tsx
Normal file
114
src/app/space/components/widgets/article.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
|
||||||
|
import { useNDK } from '@libs/ndk/provider';
|
||||||
|
|
||||||
|
import { NoteKind_1, NoteSkeleton, Repost } from '@shared/notes';
|
||||||
|
import { NoteKindUnsupport } from '@shared/notes/kinds/unsupport';
|
||||||
|
import { TitleBar } from '@shared/titleBar';
|
||||||
|
|
||||||
|
import { nHoursAgo } from '@utils/date';
|
||||||
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
|
export function ArticleWidget({ params }: { params: Widget }) {
|
||||||
|
const { ndk } = useNDK();
|
||||||
|
const { status, data } = useQuery(['article-widget', params.content], async () => {
|
||||||
|
const events = await ndk.fetchEvents({
|
||||||
|
kinds: [30023],
|
||||||
|
'#t': [params.content],
|
||||||
|
since: nHoursAgo(48),
|
||||||
|
});
|
||||||
|
return [...events] as unknown as NDKEvent[];
|
||||||
|
});
|
||||||
|
|
||||||
|
const parentRef = useRef<HTMLDivElement>(null);
|
||||||
|
const virtualizer = useVirtualizer({
|
||||||
|
count: data ? data.length : 0,
|
||||||
|
getScrollElement: () => parentRef.current,
|
||||||
|
estimateSize: () => 650,
|
||||||
|
overscan: 4,
|
||||||
|
});
|
||||||
|
const items = virtualizer.getVirtualItems();
|
||||||
|
|
||||||
|
// render event match event kind
|
||||||
|
const renderItem = useCallback(
|
||||||
|
(index: string | number) => {
|
||||||
|
const event: NDKEvent = data[index];
|
||||||
|
if (!event) return;
|
||||||
|
|
||||||
|
switch (event.kind) {
|
||||||
|
case 1:
|
||||||
|
return (
|
||||||
|
<div key={event.id} data-index={index} ref={virtualizer.measureElement}>
|
||||||
|
<NoteKind_1 event={event} skipMetadata={false} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case 6:
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={event.id + index}
|
||||||
|
data-index={index}
|
||||||
|
ref={virtualizer.measureElement}
|
||||||
|
>
|
||||||
|
<Repost key={event.id} event={event} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={event.id + index}
|
||||||
|
data-index={index}
|
||||||
|
ref={virtualizer.measureElement}
|
||||||
|
>
|
||||||
|
<NoteKindUnsupport key={event.id} event={event} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[data]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-[400px] shrink-0 bg-white/10">
|
||||||
|
<TitleBar id={params.id} title={params.title + ' in 24 hours ago'} />
|
||||||
|
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
|
||||||
|
{status === 'loading' ? (
|
||||||
|
<div className="px-3 py-1.5">
|
||||||
|
<div className="rounded-xl bg-white/10 px-3 py-3">
|
||||||
|
<NoteSkeleton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : items.length === 0 ? (
|
||||||
|
<div className="px-3 py-1.5">
|
||||||
|
<div className="rounded-xl bg-white/10 px-3 py-6">
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
<p className="text-center text-sm font-medium text-white">
|
||||||
|
No new postrs about this hashtag in 24 hours ago
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
height: `${virtualizer.getTotalSize()}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute left-0 top-0 w-full"
|
||||||
|
style={{
|
||||||
|
transform: `translateY(${items[0].start}px)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{items.map((item) => renderItem(item.index))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -17,7 +17,7 @@ export function FeedWidget({ params }: { params: Widget }) {
|
|||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||||
useInfiniteQuery({
|
useInfiniteQuery({
|
||||||
queryKey: ['newsfeed', params.content],
|
queryKey: ['groupfeed-widget', params.content],
|
||||||
queryFn: async ({ pageParam = 0 }) => {
|
queryFn: async ({ pageParam = 0 }) => {
|
||||||
const authors = JSON.parse(params.content);
|
const authors = JSON.parse(params.content);
|
||||||
return await db.getAllEventsByAuthors(authors, 20, pageParam);
|
return await db.getAllEventsByAuthors(authors, 20, pageParam);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
|
import { destr } from 'destr';
|
||||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
@ -137,7 +138,7 @@ export function NetworkWidget() {
|
|||||||
}
|
}
|
||||||
await db.createEvent(
|
await db.createEvent(
|
||||||
event.id,
|
event.id,
|
||||||
JSON.stringify(event),
|
destr(event),
|
||||||
event.pubkey,
|
event.pubkey,
|
||||||
event.kind,
|
event.kind,
|
||||||
root,
|
root,
|
||||||
|
@ -13,7 +13,7 @@ interface Response {
|
|||||||
|
|
||||||
export function TrendingNotesWidget({ params }: { params: Widget }) {
|
export function TrendingNotesWidget({ params }: { params: Widget }) {
|
||||||
const { status, data } = useQuery(
|
const { status, data } = useQuery(
|
||||||
['trending-notes'],
|
['trending-notes-widget'],
|
||||||
async () => {
|
async () => {
|
||||||
const res = await fetch(params.content);
|
const res = await fetch(params.content);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@ -33,7 +33,7 @@ export function TrendingNotesWidget({ params }: { params: Widget }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="scrollbar-hide relative h-full w-[400px] shrink-0 overflow-y-auto bg-white/10 pb-20">
|
<div className="scrollbar-hide relative h-full w-[400px] shrink-0 overflow-y-auto bg-white/10 pb-20">
|
||||||
<TitleBar title={params.title} />
|
<TitleBar id={params.id} title={params.title} />
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
|
@ -13,7 +13,7 @@ interface Response {
|
|||||||
|
|
||||||
export function TrendingProfilesWidget({ params }: { params: Widget }) {
|
export function TrendingProfilesWidget({ params }: { params: Widget }) {
|
||||||
const { status, data } = useQuery(
|
const { status, data } = useQuery(
|
||||||
['trending-profiles'],
|
['trending-profiles-widget'],
|
||||||
async () => {
|
async () => {
|
||||||
const res = await fetch(params.content);
|
const res = await fetch(params.content);
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@ -33,7 +33,7 @@ export function TrendingProfilesWidget({ params }: { params: Widget }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="scrollbar-hide relative h-full w-[400px] shrink-0 overflow-y-auto bg-white/10 pb-20">
|
<div className="scrollbar-hide relative h-full w-[400px] shrink-0 overflow-y-auto bg-white/10 pb-20">
|
||||||
<TitleBar title={params.title} />
|
<TitleBar id={params.id} title={params.title} />
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { AddWidgetButton } from '@app/space/components/button';
|
||||||
|
import { FeedWidgetForm } from '@app/space/components/forms/feed';
|
||||||
|
import { HashTagWidgetForm } from '@app/space/components/forms/hashtag';
|
||||||
import { FeedWidget } from '@app/space/components/widgets/feed';
|
import { FeedWidget } from '@app/space/components/widgets/feed';
|
||||||
import { HashtagWidget } from '@app/space/components/widgets/hashtag';
|
import { HashtagWidget } from '@app/space/components/widgets/hashtag';
|
||||||
import { NetworkWidget } from '@app/space/components/widgets/network';
|
import { NetworkWidget } from '@app/space/components/widgets/network';
|
||||||
import { ThreadBlock } from '@app/space/components/widgets/thread';
|
import { ThreadBlock } from '@app/space/components/widgets/thread';
|
||||||
|
import { TrendingNotesWidget } from '@app/space/components/widgets/trendingNotes';
|
||||||
|
import { TrendingProfilesWidget } from '@app/space/components/widgets/trendingProfile';
|
||||||
import { UserWidget } from '@app/space/components/widgets/user';
|
import { UserWidget } from '@app/space/components/widgets/user';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
import { LoaderIcon, PlusIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { useWidgets } from '@stores/widgets';
|
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||||
|
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
@ -26,16 +31,24 @@ export function SpaceScreen() {
|
|||||||
(widget: Widget) => {
|
(widget: Widget) => {
|
||||||
if (!widget) return;
|
if (!widget) return;
|
||||||
switch (widget.kind) {
|
switch (widget.kind) {
|
||||||
case 1:
|
case WidgetKinds.feed:
|
||||||
return <FeedWidget key={widget.id} params={widget} />;
|
return <FeedWidget key={widget.id} params={widget} />;
|
||||||
case 2:
|
case WidgetKinds.thread:
|
||||||
return <ThreadBlock key={widget.id} params={widget} />;
|
return <ThreadBlock key={widget.id} params={widget} />;
|
||||||
case 3:
|
case WidgetKinds.hashtag:
|
||||||
return <HashtagWidget key={widget.id} params={widget} />;
|
return <HashtagWidget key={widget.id} params={widget} />;
|
||||||
case 5:
|
case WidgetKinds.user:
|
||||||
return <UserWidget key={widget.id} params={widget} />;
|
return <UserWidget key={widget.id} params={widget} />;
|
||||||
case 9999:
|
case WidgetKinds.trendingProfiles:
|
||||||
|
return <TrendingProfilesWidget key={widget.id} params={widget} />;
|
||||||
|
case WidgetKinds.trendingNotes:
|
||||||
|
return <TrendingNotesWidget key={widget.id} params={widget} />;
|
||||||
|
case WidgetKinds.network:
|
||||||
return <NetworkWidget key={widget.id} />;
|
return <NetworkWidget key={widget.id} />;
|
||||||
|
case WidgetKinds.xhashtag:
|
||||||
|
return <HashTagWidgetForm key={widget.id} params={widget} />;
|
||||||
|
case WidgetKinds.xfeed:
|
||||||
|
return <FeedWidgetForm key={widget.id} params={widget} />;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -58,16 +71,7 @@ export function SpaceScreen() {
|
|||||||
) : (
|
) : (
|
||||||
widgets.map((widget) => renderItem(widget))
|
widgets.map((widget) => renderItem(widget))
|
||||||
)}
|
)}
|
||||||
<div className="flex h-full shrink-0 grow-0 basis-[400px] flex-col">
|
<AddWidgetButton />
|
||||||
<div className="inline-flex h-full w-full flex-col items-center justify-center">
|
|
||||||
<button type="button" className="flex flex-col items-center gap-2">
|
|
||||||
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-white/5 hover:bg-white/10">
|
|
||||||
<PlusIcon className="h-5 w-5 text-white" />
|
|
||||||
</div>
|
|
||||||
<p className="font-medium text-white/50">Add widget</p>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -205,8 +205,8 @@ export class LumeStorage {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const query: DBEvent[] = await this.db.select(
|
const query: DBEvent[] = await this.db.select(
|
||||||
'SELECT * FROM events WHERE author IN ($1) ORDER BY created_at DESC LIMIT $2 OFFSET $3;',
|
`SELECT * FROM events WHERE author IN (${authorsArr}) ORDER BY created_at DESC LIMIT $1 OFFSET $2;`,
|
||||||
[authorsArr, limit, offset]
|
[limit, offset]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (query && query.length > 0) {
|
if (query && query.length > 0) {
|
||||||
|
22
src/shared/icons/file.tsx
Normal file
22
src/shared/icons/file.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { SVGProps } from 'react';
|
||||||
|
|
||||||
|
export function FileIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
d="M8.75 6.75h6.5m-6.5 4h6.5m-6.5 4h2.5m-5.5 6.5h12.5a1 1 0 001-1V3.75a1 1 0 00-1-1H5.75a1 1 0 00-1 1v16.5a1 1 0 001 1z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
22
src/shared/icons/hashtag.tsx
Normal file
22
src/shared/icons/hashtag.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { SVGProps } from 'react';
|
||||||
|
|
||||||
|
export function HashtagIcon(props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
d="M8.75 3.75l-2 16.5m10.5-16.5l-2 16.5M3.75 7.75h16.5m0 8.5H3.75"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
// @index('./*.tsx', f => `export * from '${f.path}'`)
|
|
||||||
export * from './arrowLeft';
|
export * from './arrowLeft';
|
||||||
export * from './arrowRight';
|
export * from './arrowRight';
|
||||||
export * from './bell';
|
export * from './bell';
|
||||||
@ -49,4 +48,5 @@ export * from './strangers';
|
|||||||
export * from './download';
|
export * from './download';
|
||||||
export * from './horizontalDots';
|
export * from './horizontalDots';
|
||||||
export * from './arrowRightCircle';
|
export * from './arrowRightCircle';
|
||||||
// @endindex
|
export * from './hashtag';
|
||||||
|
export * from './file';
|
||||||
|
@ -5,7 +5,6 @@ import { useStorage } from '@libs/storage/provider';
|
|||||||
import { ActiveAccount } from '@shared/accounts/active';
|
import { ActiveAccount } from '@shared/accounts/active';
|
||||||
import { SettingsIcon } from '@shared/icons';
|
import { SettingsIcon } from '@shared/icons';
|
||||||
import { Logout } from '@shared/logout';
|
import { Logout } from '@shared/logout';
|
||||||
import { NotificationModal } from '@shared/notification/modal';
|
|
||||||
|
|
||||||
export function LumeBar() {
|
export function LumeBar() {
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
@ -14,7 +13,6 @@ export function LumeBar() {
|
|||||||
<div className="rounded-xl bg-white/10 p-2 backdrop-blur-xl">
|
<div className="rounded-xl bg-white/10 p-2 backdrop-blur-xl">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<ActiveAccount data={db.account} />
|
<ActiveAccount data={db.account} />
|
||||||
<NotificationModal pubkey={db.account.pubkey} />
|
|
||||||
<Link
|
<Link
|
||||||
to="/settings/general"
|
to="/settings/general"
|
||||||
className="inline-flex h-9 w-9 transform items-center justify-center rounded-md bg-white/20 active:translate-y-1"
|
className="inline-flex h-9 w-9 transform items-center justify-center rounded-md bg-white/20 active:translate-y-1"
|
||||||
|
@ -9,8 +9,7 @@ import { NoteReply } from '@shared/notes/actions/reply';
|
|||||||
import { NoteRepost } from '@shared/notes/actions/repost';
|
import { NoteRepost } from '@shared/notes/actions/repost';
|
||||||
import { NoteZap } from '@shared/notes/actions/zap';
|
import { NoteZap } from '@shared/notes/actions/zap';
|
||||||
|
|
||||||
import { widgetKinds } from '@stores/constants';
|
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||||
import { useWidgets } from '@stores/widgets';
|
|
||||||
|
|
||||||
export function NoteActions({
|
export function NoteActions({
|
||||||
id,
|
id,
|
||||||
@ -44,7 +43,7 @@ export function NoteActions({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setWidget(db, {
|
setWidget(db, {
|
||||||
kind: widgetKinds.thread,
|
kind: WidgetKinds.thread,
|
||||||
title: 'Thread',
|
title: 'Thread',
|
||||||
content: id,
|
content: id,
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as Popover from '@radix-ui/react-popover';
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||||
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
@ -25,17 +25,17 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover.Root open={open} onOpenChange={setOpen}>
|
<DropdownMenu.Root open={open} onOpenChange={setOpen}>
|
||||||
<Tooltip.Root delayDuration={150}>
|
<Tooltip.Root delayDuration={150}>
|
||||||
<Tooltip.Trigger asChild>
|
<Tooltip.Trigger asChild>
|
||||||
<Popover.Trigger asChild>
|
<DropdownMenu.Trigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="group ml-auto inline-flex h-7 w-7 items-center justify-center"
|
className="group ml-auto inline-flex h-7 w-7 items-center justify-center"
|
||||||
>
|
>
|
||||||
<HorizontalDotsIcon className="h-5 w-5 text-white group-hover:text-fuchsia-400" />
|
<HorizontalDotsIcon className="h-5 w-5 text-white group-hover:text-fuchsia-400" />
|
||||||
</button>
|
</button>
|
||||||
</Popover.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
</Tooltip.Trigger>
|
</Tooltip.Trigger>
|
||||||
<Tooltip.Portal>
|
<Tooltip.Portal>
|
||||||
<Tooltip.Content className="-left-10 select-none rounded-md bg-black px-3.5 py-1.5 text-sm leading-none text-white will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
|
<Tooltip.Content className="-left-10 select-none rounded-md bg-black px-3.5 py-1.5 text-sm leading-none text-white will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
|
||||||
@ -44,15 +44,17 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
|
|||||||
</Tooltip.Content>
|
</Tooltip.Content>
|
||||||
</Tooltip.Portal>
|
</Tooltip.Portal>
|
||||||
</Tooltip.Root>
|
</Tooltip.Root>
|
||||||
<Popover.Portal>
|
<DropdownMenu.Portal>
|
||||||
<Popover.Content className="w-[200px] overflow-hidden rounded-md bg-white/10 backdrop-blur-3xl focus:outline-none">
|
<DropdownMenu.Content className="flex w-[200px] flex-col overflow-hidden rounded-md bg-white/10 p-2 backdrop-blur-3xl focus:outline-none">
|
||||||
<div className="flex flex-col p-2">
|
<DropdownMenu.Item asChild>
|
||||||
<Link
|
<Link
|
||||||
to={`/events/${id}`}
|
to={`/events/${id}`}
|
||||||
className="inline-flex h-10 items-center rounded-md px-2 text-sm font-medium text-white hover:bg-white/10"
|
className="inline-flex h-10 items-center rounded-md px-2 text-sm font-medium text-white hover:bg-white/10"
|
||||||
>
|
>
|
||||||
Open as new screen
|
Open as new screen
|
||||||
</Link>
|
</Link>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => copyLink()}
|
onClick={() => copyLink()}
|
||||||
@ -60,6 +62,8 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
|
|||||||
>
|
>
|
||||||
Copy shareable link
|
Copy shareable link
|
||||||
</button>
|
</button>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => copyID()}
|
onClick={() => copyID()}
|
||||||
@ -67,15 +71,17 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
|
|||||||
>
|
>
|
||||||
Copy ID
|
Copy ID
|
||||||
</button>
|
</button>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
<DropdownMenu.Item asChild>
|
||||||
<Link
|
<Link
|
||||||
to={`/users/${pubkey}`}
|
to={`/users/${pubkey}`}
|
||||||
className="inline-flex h-10 items-center rounded-md px-2 text-sm font-medium text-white hover:bg-white/10"
|
className="inline-flex h-10 items-center rounded-md px-2 text-sm font-medium text-white hover:bg-white/10"
|
||||||
>
|
>
|
||||||
View profile
|
View profile
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</DropdownMenu.Item>
|
||||||
</Popover.Content>
|
</DropdownMenu.Content>
|
||||||
</Popover.Portal>
|
</DropdownMenu.Portal>
|
||||||
</Popover.Root>
|
</DropdownMenu.Root>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
import { widgetKinds } from '@stores/constants';
|
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||||
import { useWidgets } from '@stores/widgets';
|
|
||||||
|
|
||||||
export function Hashtag({ tag }: { tag: string }) {
|
export function Hashtag({ tag }: { tag: string }) {
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
@ -12,7 +11,7 @@ export function Hashtag({ tag }: { tag: string }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setWidget(db, {
|
setWidget(db, {
|
||||||
kind: widgetKinds.hashtag,
|
kind: WidgetKinds.hashtag,
|
||||||
title: tag,
|
title: tag,
|
||||||
content: tag.replace('#', ''),
|
content: tag.replace('#', ''),
|
||||||
})
|
})
|
||||||
|
@ -8,28 +8,28 @@ import { Image } from '@shared/image';
|
|||||||
import { MentionUser, NoteSkeleton } from '@shared/notes';
|
import { MentionUser, NoteSkeleton } from '@shared/notes';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
import { widgetKinds } from '@stores/constants';
|
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||||
import { useWidgets } from '@stores/widgets';
|
|
||||||
|
|
||||||
import { useEvent } from '@utils/hooks/useEvent';
|
import { useEvent } from '@utils/hooks/useEvent';
|
||||||
import { isImage } from '@utils/isImage';
|
import { isImage } from '@utils/isImage';
|
||||||
|
|
||||||
export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
const { status, data } = useEvent(id);
|
const { status, data, error } = useEvent(id);
|
||||||
|
|
||||||
const setWidget = useWidgets((state) => state.setWidget);
|
const setWidget = useWidgets((state) => state.setWidget);
|
||||||
|
|
||||||
const openThread = (event, thread: string) => {
|
const openThread = (event, thread: string) => {
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
if (selection.toString().length === 0) {
|
if (selection.toString().length === 0) {
|
||||||
setWidget(db, { kind: widgetKinds.thread, title: 'Thread', content: thread });
|
setWidget(db, { kind: WidgetKinds.thread, title: 'Thread', content: thread });
|
||||||
} else {
|
} else {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderItem = useCallback(() => {
|
const renderItem = useCallback(() => {
|
||||||
|
if (!data) return;
|
||||||
switch (data.event.kind) {
|
switch (data.event.kind) {
|
||||||
case 1: {
|
case 1: {
|
||||||
return (
|
return (
|
||||||
@ -85,6 +85,14 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="mb-2 mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3">
|
||||||
|
<p>Can't get event from relay</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={(e) => openThread(e, id)}
|
onClick={(e) => openThread(e, id)}
|
||||||
@ -93,7 +101,7 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) {
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className="mb-2 mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3"
|
className="mb-2 mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3"
|
||||||
>
|
>
|
||||||
<User pubkey={data?.event?.pubkey} time={data?.event?.created_at} size="small" />
|
<User pubkey={data.event.pubkey} time={data.event.created_at} size="small" />
|
||||||
<div className="mt-2">{renderItem()}</div>
|
<div className="mt-2">{renderItem()}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
import { widgetKinds } from '@stores/constants';
|
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||||
import { useWidgets } from '@stores/widgets';
|
|
||||||
|
|
||||||
import { useProfile } from '@utils/hooks/useProfile';
|
import { useProfile } from '@utils/hooks/useProfile';
|
||||||
import { displayNpub } from '@utils/shortenKey';
|
import { displayNpub } from '@utils/shortenKey';
|
||||||
@ -17,7 +16,7 @@ export function MentionUser({ pubkey }: { pubkey: string }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setWidget(db, {
|
setWidget(db, {
|
||||||
kind: widgetKinds.user,
|
kind: WidgetKinds.user,
|
||||||
title: user?.nip05 || user?.name || user?.display_name,
|
title: user?.nip05 || user?.name || user?.display_name,
|
||||||
content: pubkey,
|
content: pubkey,
|
||||||
})
|
})
|
||||||
|
@ -8,8 +8,7 @@ import { useStorage } from '@libs/storage/provider';
|
|||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
import { MiniUser } from '@shared/notes/users/mini';
|
import { MiniUser } from '@shared/notes/users/mini';
|
||||||
|
|
||||||
import { widgetKinds } from '@stores/constants';
|
import { WidgetKinds, useWidgets } from '@stores/widgets';
|
||||||
import { useWidgets } from '@stores/widgets';
|
|
||||||
|
|
||||||
import { compactNumber } from '@utils/number';
|
import { compactNumber } from '@utils/number';
|
||||||
|
|
||||||
@ -54,7 +53,11 @@ export function NoteMetadata({ id }: { id: string }) {
|
|||||||
|
|
||||||
return { replies, users, zap };
|
return { replies, users, zap };
|
||||||
},
|
},
|
||||||
{ refetchOnWindowFocus: false, refetchOnReconnect: false, refetchOnMount: false }
|
{
|
||||||
|
enabled: !!ndk,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
refetchOnReconnect: false,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (status === 'loading') {
|
if (status === 'loading') {
|
||||||
@ -68,6 +71,12 @@ export function NoteMetadata({ id }: { id: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status === 'error') {
|
||||||
|
<div>
|
||||||
|
<div className="pb-3" />
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{data.replies > 0 ? (
|
{data.replies > 0 ? (
|
||||||
@ -86,7 +95,7 @@ export function NoteMetadata({ id }: { id: string }) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setWidget(db, {
|
setWidget(db, {
|
||||||
kind: widgetKinds.thread,
|
kind: WidgetKinds.thread,
|
||||||
title: 'Thread',
|
title: 'Thread',
|
||||||
content: id,
|
content: id,
|
||||||
})
|
})
|
||||||
|
@ -3,12 +3,3 @@ export const FULL_RELAYS = [
|
|||||||
'wss://relay.nostr.band/all',
|
'wss://relay.nostr.band/all',
|
||||||
'wss://nostr.mutinywallet.com',
|
'wss://nostr.mutinywallet.com',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const widgetKinds = {
|
|
||||||
image: 0,
|
|
||||||
feed: 1,
|
|
||||||
thread: 2,
|
|
||||||
hashtag: 3,
|
|
||||||
exchange_rate: 4,
|
|
||||||
user: 5,
|
|
||||||
};
|
|
||||||
|
@ -12,6 +12,20 @@ interface WidgetState {
|
|||||||
removeWidget: (db: LumeStorage, id: string) => void;
|
removeWidget: (db: LumeStorage, id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const WidgetKinds = {
|
||||||
|
feed: 1, // NIP-01
|
||||||
|
thread: 2, // NIP-01
|
||||||
|
hashtag: 3, // NIP-01
|
||||||
|
article: 4, // NIP-23
|
||||||
|
user: 5, // NIP-01
|
||||||
|
trendingProfiles: 6,
|
||||||
|
trendingNotes: 7,
|
||||||
|
file: 8, // NIP-94
|
||||||
|
network: 9999,
|
||||||
|
xfeed: 10000, // x is temporary state for new feed widget form
|
||||||
|
xhashtag: 10001, // x is temporary state for new hashtag widget form
|
||||||
|
};
|
||||||
|
|
||||||
export const useWidgets = create<WidgetState>()(
|
export const useWidgets = create<WidgetState>()(
|
||||||
persist(
|
persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
@ -25,7 +39,7 @@ export const useWidgets = create<WidgetState>()(
|
|||||||
id: '9999',
|
id: '9999',
|
||||||
title: 'Network',
|
title: 'Network',
|
||||||
content: '',
|
content: '',
|
||||||
kind: 9999,
|
kind: WidgetKinds.network,
|
||||||
});
|
});
|
||||||
|
|
||||||
set({ widgets: dbWidgets });
|
set({ widgets: dbWidgets });
|
||||||
|
@ -30,12 +30,17 @@ export function useEvent(id: string, embed?: string) {
|
|||||||
} else {
|
} else {
|
||||||
// get event from relay if event in db not present
|
// get event from relay if event in db not present
|
||||||
const event = await ndk.fetchEvent(id);
|
const event = await ndk.fetchEvent(id);
|
||||||
if (event.kind === 1) richContent = parser(event);
|
|
||||||
|
if (!event) throw new Error(`Event not found: ${id}`);
|
||||||
|
if (event.kind === 1) {
|
||||||
|
richContent = parser(event);
|
||||||
|
}
|
||||||
|
|
||||||
return { event: event, richContent: richContent };
|
return { event: event, richContent: richContent };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
enabled: !!ndk,
|
||||||
staleTime: Infinity,
|
staleTime: Infinity,
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
|
@ -109,7 +109,7 @@ export function useNostr() {
|
|||||||
const events = fetcher.allEventsIterator(
|
const events = fetcher.allEventsIterator(
|
||||||
relayUrls,
|
relayUrls,
|
||||||
{
|
{
|
||||||
kinds: [1, 6],
|
kinds: [NDKKind.Text, NDKKind.Repost, 1063, NDKKind.Article],
|
||||||
authors: db.account.network,
|
authors: db.account.network,
|
||||||
},
|
},
|
||||||
{ since: since }
|
{ since: since }
|
||||||
|
@ -20,7 +20,7 @@ export function useProfile(pubkey: string, embed?: string) {
|
|||||||
user.profile.display_name = user.profile.displayName;
|
user.profile.display_name = user.profile.displayName;
|
||||||
return user.profile;
|
return user.profile;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('User not found');
|
throw new Error(`User not found: ${pubkey}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const profile: NDKUserProfile = JSON.parse(embed);
|
const profile: NDKUserProfile = JSON.parse(embed);
|
||||||
|
Loading…
Reference in New Issue
Block a user