perf improve
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
"**/*.{ts, tsx, css, md, html, json}": "prettier --cache --write"
|
"**/*.{ts, tsx, css, md, html, json}": "prettier --cache --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dnd-kit/core": "^6.0.8",
|
||||||
"@getalby/sdk": "^2.4.0",
|
"@getalby/sdk": "^2.4.0",
|
||||||
"@nostr-dev-kit/ndk": "^1.3.0",
|
"@nostr-dev-kit/ndk": "^1.3.0",
|
||||||
"@nostr-fetch/adapter-ndk": "^0.12.2",
|
"@nostr-fetch/adapter-ndk": "^0.12.2",
|
||||||
@@ -29,7 +30,6 @@
|
|||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
"@tanstack/react-query": "^4.35.3",
|
"@tanstack/react-query": "^4.35.3",
|
||||||
"@tanstack/react-virtual": "3.0.0-beta.54",
|
|
||||||
"@tauri-apps/api": "^1.4.0",
|
"@tauri-apps/api": "^1.4.0",
|
||||||
"@tiptap/extension-image": "^2.1.11",
|
"@tiptap/extension-image": "^2.1.11",
|
||||||
"@tiptap/extension-mention": "^2.1.11",
|
"@tiptap/extension-mention": "^2.1.11",
|
||||||
@@ -56,7 +56,6 @@
|
|||||||
"react-player": "^2.13.0",
|
"react-player": "^2.13.0",
|
||||||
"react-router-dom": "^6.16.0",
|
"react-router-dom": "^6.16.0",
|
||||||
"react-textarea-autosize": "^8.5.3",
|
"react-textarea-autosize": "^8.5.3",
|
||||||
"react-virtuoso": "^4.6.0",
|
|
||||||
"reactflow": "^11.8.3",
|
"reactflow": "^11.8.3",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1",
|
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1",
|
||||||
@@ -64,6 +63,7 @@
|
|||||||
"tauri-plugin-stronghold-api": "github:tauri-apps/tauri-plugin-stronghold#v1",
|
"tauri-plugin-stronghold-api": "github:tauri-apps/tauri-plugin-stronghold#v1",
|
||||||
"tauri-plugin-upload-api": "github:tauri-apps/tauri-plugin-upload#v1",
|
"tauri-plugin-upload-api": "github:tauri-apps/tauri-plugin-upload#v1",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
|
"virtua": "^0.9.1",
|
||||||
"zustand": "^4.4.1"
|
"zustand": "^4.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
101
pnpm-lock.yaml
generated
101
pnpm-lock.yaml
generated
@@ -5,6 +5,9 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@dnd-kit/core':
|
||||||
|
specifier: ^6.0.8
|
||||||
|
version: 6.0.8(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@getalby/sdk':
|
'@getalby/sdk':
|
||||||
specifier: ^2.4.0
|
specifier: ^2.4.0
|
||||||
version: 2.4.0
|
version: 2.4.0
|
||||||
@@ -38,9 +41,6 @@ dependencies:
|
|||||||
'@tanstack/react-query':
|
'@tanstack/react-query':
|
||||||
specifier: ^4.35.3
|
specifier: ^4.35.3
|
||||||
version: 4.35.3(react-dom@18.2.0)(react@18.2.0)
|
version: 4.35.3(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@tanstack/react-virtual':
|
|
||||||
specifier: 3.0.0-beta.54
|
|
||||||
version: 3.0.0-beta.54(react@18.2.0)
|
|
||||||
'@tauri-apps/api':
|
'@tauri-apps/api':
|
||||||
specifier: ^1.4.0
|
specifier: ^1.4.0
|
||||||
version: 1.4.0
|
version: 1.4.0
|
||||||
@@ -119,9 +119,6 @@ dependencies:
|
|||||||
react-textarea-autosize:
|
react-textarea-autosize:
|
||||||
specifier: ^8.5.3
|
specifier: ^8.5.3
|
||||||
version: 8.5.3(@types/react@18.2.22)(react@18.2.0)
|
version: 8.5.3(@types/react@18.2.22)(react@18.2.0)
|
||||||
react-virtuoso:
|
|
||||||
specifier: ^4.6.0
|
|
||||||
version: 4.6.0(react-dom@18.2.0)(react@18.2.0)
|
|
||||||
reactflow:
|
reactflow:
|
||||||
specifier: ^11.8.3
|
specifier: ^11.8.3
|
||||||
version: 11.8.3(@types/react@18.2.22)(react-dom@18.2.0)(react@18.2.0)
|
version: 11.8.3(@types/react@18.2.22)(react-dom@18.2.0)(react@18.2.0)
|
||||||
@@ -130,19 +127,22 @@ dependencies:
|
|||||||
version: 3.0.1
|
version: 3.0.1
|
||||||
tauri-plugin-sql-api:
|
tauri-plugin-sql-api:
|
||||||
specifier: github:tauri-apps/tauri-plugin-sql#v1
|
specifier: github:tauri-apps/tauri-plugin-sql#v1
|
||||||
version: github.com/tauri-apps/tauri-plugin-sql/51e39b0b6ba542ffc6af1fa438933fdc1ae265a0
|
version: github.com/tauri-apps/tauri-plugin-sql/533198dd3b6cfca36d876918d22efcdaac43065a
|
||||||
tauri-plugin-store-api:
|
tauri-plugin-store-api:
|
||||||
specifier: github:tauri-apps/tauri-plugin-store#v1
|
specifier: github:tauri-apps/tauri-plugin-store#v1
|
||||||
version: github.com/tauri-apps/tauri-plugin-store/a65ce9bfb168a9a3cd7ed4102b9f22770cc3abfa
|
version: github.com/tauri-apps/tauri-plugin-store/66e06b7830037fdae0b42b5499e23334eaf4e017
|
||||||
tauri-plugin-stronghold-api:
|
tauri-plugin-stronghold-api:
|
||||||
specifier: github:tauri-apps/tauri-plugin-stronghold#v1
|
specifier: github:tauri-apps/tauri-plugin-stronghold#v1
|
||||||
version: github.com/tauri-apps/tauri-plugin-stronghold/96dd2cc891915e6fdfb78868b0bef6c5648335a2
|
version: github.com/tauri-apps/tauri-plugin-stronghold/4684fed1f5e7eb01885e40114accdcecb61962ed
|
||||||
tauri-plugin-upload-api:
|
tauri-plugin-upload-api:
|
||||||
specifier: github:tauri-apps/tauri-plugin-upload#v1
|
specifier: github:tauri-apps/tauri-plugin-upload#v1
|
||||||
version: github.com/tauri-apps/tauri-plugin-upload/b53ebc6c2e716d95fd94b64d3b4b87cd57ae4feb
|
version: github.com/tauri-apps/tauri-plugin-upload/40c0bc302a9dd8304762951e450ee84d53c2037b
|
||||||
tippy.js:
|
tippy.js:
|
||||||
specifier: ^6.3.7
|
specifier: ^6.3.7
|
||||||
version: 6.3.7
|
version: 6.3.7
|
||||||
|
virtua:
|
||||||
|
specifier: ^0.9.1
|
||||||
|
version: 0.9.1(react-dom@18.2.0)(react@18.2.0)
|
||||||
zustand:
|
zustand:
|
||||||
specifier: ^4.4.1
|
specifier: ^4.4.1
|
||||||
version: 4.4.1(@types/react@18.2.22)(react@18.2.0)
|
version: 4.4.1(@types/react@18.2.22)(react@18.2.0)
|
||||||
@@ -378,6 +378,37 @@ packages:
|
|||||||
to-fast-properties: 2.0.0
|
to-fast-properties: 2.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@dnd-kit/accessibility@3.0.1(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
tslib: 2.6.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@dnd-kit/core@6.0.8(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-lYaoP8yHTQSLlZe6Rr9qogouGUz9oRUj4AHhDQGQzq/hqaJRpFo65X+JKsdHf8oUFBzx5A+SJPUvxAwTF2OabA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
react-dom: '>=16.8.0'
|
||||||
|
dependencies:
|
||||||
|
'@dnd-kit/accessibility': 3.0.1(react@18.2.0)
|
||||||
|
'@dnd-kit/utilities': 3.2.1(react@18.2.0)
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
tslib: 2.6.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@dnd-kit/utilities@3.2.1(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-OOXqISfvBw/1REtkSK2N3Fi2EQiLMlWUlqnOK/UpOISqBZPWpE6TqL+jcPtMOkE8TqYGiURvRdPSI9hltNUjEA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
tslib: 2.6.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@esbuild/android-arm64@0.18.20:
|
/@esbuild/android-arm64@0.18.20:
|
||||||
resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
|
resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -1799,19 +1830,6 @@ packages:
|
|||||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@tanstack/react-virtual@3.0.0-beta.54(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-D1mDMf4UPbrtHRZZriCly5bXTBMhylslm4dhcHqTtDJ6brQcgGmk8YD9JdWBGWfGSWPKoh2x1H3e7eh+hgPXtQ==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
|
||||||
dependencies:
|
|
||||||
'@tanstack/virtual-core': 3.0.0-beta.54
|
|
||||||
react: 18.2.0
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@tanstack/virtual-core@3.0.0-beta.54:
|
|
||||||
resolution: {integrity: sha512-jtkwqdP2rY2iCCDVAFuaNBH3fiEi29aTn2RhtIoky8DTTiCdc48plpHHreLwmv1PICJ4AJUUESaq3xa8fZH8+g==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@tauri-apps/api@1.4.0:
|
/@tauri-apps/api@1.4.0:
|
||||||
resolution: {integrity: sha512-Jd6HPoTM1PZSFIzq7FB8VmMu3qSSyo/3lSwLpoapW+lQ41CL5Dow2KryLg+gyazA/58DRWI9vu/XpEeHK4uMdw==}
|
resolution: {integrity: sha512-Jd6HPoTM1PZSFIzq7FB8VmMu3qSSyo/3lSwLpoapW+lQ41CL5Dow2KryLg+gyazA/58DRWI9vu/XpEeHK4uMdw==}
|
||||||
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
|
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
|
||||||
@@ -5671,17 +5689,6 @@ packages:
|
|||||||
- '@types/react'
|
- '@types/react'
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/react-virtuoso@4.6.0(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-paQbkLA8U6dRe9srltWgPeoFCtNKqUYIcOpUR01JyznzaXWVzgZQE0M9KGi9vMoq8vHvHkGzuxJ6jDCS6uzePg==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
peerDependencies:
|
|
||||||
react: '>=16 || >=17 || >= 18'
|
|
||||||
react-dom: '>=16 || >=17 || >= 18'
|
|
||||||
dependencies:
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/react@18.2.0:
|
/react@18.2.0:
|
||||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -6467,6 +6474,16 @@ packages:
|
|||||||
vfile-message: 3.1.4
|
vfile-message: 3.1.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/virtua@0.9.1(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-uaFvh+5zCDEenQDgxfIs67kpci7d/3XjdnWP/TdDYLcoXdWKr5ddwiP1g+wybHpXmLqbfJ0X0njmlAvP7GwMdw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.14.0'
|
||||||
|
react-dom: '>=16.14.0'
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/vite-tsconfig-paths@4.2.1(typescript@5.2.2)(vite@4.4.9):
|
/vite-tsconfig-paths@4.2.1(typescript@5.2.2)(vite@4.4.9):
|
||||||
resolution: {integrity: sha512-GNUI6ZgPqT3oervkvzU+qtys83+75N/OuDaQl7HmOqFTb0pjZsuARrRipsyJhJ3enqV8beI1xhGbToR4o78nSQ==}
|
resolution: {integrity: sha512-GNUI6ZgPqT3oervkvzU+qtys83+75N/OuDaQl7HmOqFTb0pjZsuARrRipsyJhJ3enqV8beI1xhGbToR4o78nSQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -6669,32 +6686,32 @@ packages:
|
|||||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
github.com/tauri-apps/tauri-plugin-sql/51e39b0b6ba542ffc6af1fa438933fdc1ae265a0:
|
github.com/tauri-apps/tauri-plugin-sql/533198dd3b6cfca36d876918d22efcdaac43065a:
|
||||||
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-sql/tar.gz/51e39b0b6ba542ffc6af1fa438933fdc1ae265a0}
|
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-sql/tar.gz/533198dd3b6cfca36d876918d22efcdaac43065a}
|
||||||
name: tauri-plugin-sql-api
|
name: tauri-plugin-sql-api
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 1.4.0
|
'@tauri-apps/api': 1.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
github.com/tauri-apps/tauri-plugin-store/a65ce9bfb168a9a3cd7ed4102b9f22770cc3abfa:
|
github.com/tauri-apps/tauri-plugin-store/66e06b7830037fdae0b42b5499e23334eaf4e017:
|
||||||
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-store/tar.gz/a65ce9bfb168a9a3cd7ed4102b9f22770cc3abfa}
|
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-store/tar.gz/66e06b7830037fdae0b42b5499e23334eaf4e017}
|
||||||
name: tauri-plugin-store-api
|
name: tauri-plugin-store-api
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 1.4.0
|
'@tauri-apps/api': 1.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
github.com/tauri-apps/tauri-plugin-stronghold/96dd2cc891915e6fdfb78868b0bef6c5648335a2:
|
github.com/tauri-apps/tauri-plugin-stronghold/4684fed1f5e7eb01885e40114accdcecb61962ed:
|
||||||
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-stronghold/tar.gz/96dd2cc891915e6fdfb78868b0bef6c5648335a2}
|
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-stronghold/tar.gz/4684fed1f5e7eb01885e40114accdcecb61962ed}
|
||||||
name: tauri-plugin-stronghold-api
|
name: tauri-plugin-stronghold-api
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 1.4.0
|
'@tauri-apps/api': 1.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
github.com/tauri-apps/tauri-plugin-upload/b53ebc6c2e716d95fd94b64d3b4b87cd57ae4feb:
|
github.com/tauri-apps/tauri-plugin-upload/40c0bc302a9dd8304762951e450ee84d53c2037b:
|
||||||
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-upload/tar.gz/b53ebc6c2e716d95fd94b64d3b4b87cd57ae4feb}
|
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-upload/tar.gz/40c0bc302a9dd8304762951e450ee84d53c2037b}
|
||||||
name: tauri-plugin-upload-api
|
name: tauri-plugin-upload-api
|
||||||
version: 0.0.0
|
version: 0.0.0
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function UserLatestPosts({ pubkey }: { pubkey: string }) {
|
|||||||
case NDKKind.Text:
|
case NDKKind.Text:
|
||||||
return (
|
return (
|
||||||
<NoteWrapper key={event.id} event={event}>
|
<NoteWrapper key={event.id} event={event}>
|
||||||
<TextNote content={event.content} />
|
<TextNote />
|
||||||
</NoteWrapper>
|
</NoteWrapper>
|
||||||
);
|
);
|
||||||
case NDKKind.Repost:
|
case NDKKind.Repost:
|
||||||
@@ -34,19 +34,19 @@ export function UserLatestPosts({ pubkey }: { pubkey: string }) {
|
|||||||
case 1063:
|
case 1063:
|
||||||
return (
|
return (
|
||||||
<NoteWrapper key={event.id} event={event}>
|
<NoteWrapper key={event.id} event={event}>
|
||||||
<FileNote event={event} />
|
<FileNote />
|
||||||
</NoteWrapper>
|
</NoteWrapper>
|
||||||
);
|
);
|
||||||
case NDKKind.Article:
|
case NDKKind.Article:
|
||||||
return (
|
return (
|
||||||
<NoteWrapper key={event.id} event={event}>
|
<NoteWrapper key={event.id} event={event}>
|
||||||
<ArticleNote event={event} />
|
<ArticleNote />
|
||||||
</NoteWrapper>
|
</NoteWrapper>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<NoteWrapper key={event.id} event={event}>
|
<NoteWrapper key={event.id} event={event}>
|
||||||
<UnknownNote event={event} />
|
<UnknownNote />
|
||||||
</NoteWrapper>
|
</NoteWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const UserWithDrawer = memo(function UserWithDrawer({
|
|||||||
</button>
|
</button>
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
<Dialog.Content className="fixed right-0 top-0 z-50 flex h-full w-[400px] items-center justify-center px-4 pb-4 pt-16">
|
<Dialog.Content className="fixed right-0 top-0 z-50 flex h-full w-[400px] animate-slideRightAndFade items-center justify-center px-4 pb-4 pt-16 transition-all">
|
||||||
<div className="h-full w-full overflow-y-auto rounded-lg border-t border-white/10 bg-white/20 py-3 backdrop-blur-3xl">
|
<div className="h-full w-full overflow-y-auto rounded-lg border-t border-white/10 bg-white/20 py-3 backdrop-blur-3xl">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function BrowseScreen() {
|
|||||||
to="/browse/"
|
to="/browse/"
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
twMerge(
|
twMerge(
|
||||||
'inline-flex h-8 w-20 items-center justify-center rounded-full text-sm font-semibold',
|
'inline-flex h-7 w-20 items-center justify-center rounded-full text-sm font-semibold',
|
||||||
isActive ? 'bg-white/10 hover:bg-white/20' : ' hover:bg-white/5'
|
isActive ? 'bg-white/10 hover:bg-white/20' : ' hover:bg-white/5'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ export function BrowseScreen() {
|
|||||||
to="/browse/relays"
|
to="/browse/relays"
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
twMerge(
|
twMerge(
|
||||||
'inline-flex h-8 w-20 items-center justify-center rounded-full text-sm font-semibold',
|
'inline-flex h-7 w-20 items-center justify-center rounded-full text-sm font-semibold',
|
||||||
isActive ? 'bg-white/10 hover:bg-white/20' : ' hover:bg-white/5'
|
isActive ? 'bg-white/10 hover:bg-white/20' : ' hover:bg-white/5'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
export function BrowseRelaysScreen() {
|
export function BrowseRelaysScreen() {
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full">
|
<div className="grid h-full w-full grid-cols-3">
|
||||||
<p>TODO</p>
|
<div className="col-span-2 border-r border-white/5 pt-16">
|
||||||
|
<p>Content</p>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-1 px-3 pt-6">
|
||||||
|
<h3 className="font-semibold text-white">Your relays</h3>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { UnknownsModal } from '@app/chats/components/unknowns';
|
|||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
import { useNostr } from '@utils/hooks/useNostr';
|
||||||
|
|
||||||
export function ChatsList() {
|
export function ChatsList() {
|
||||||
@@ -33,8 +35,10 @@ export function ChatsList() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="inline-flex h-10 items-center gap-2.5 border-l-2 border-transparent pl-4">
|
<div className="inline-flex h-10 items-center gap-2.5 border-l-2 border-transparent pl-4">
|
||||||
<div className="relative h-7 w-7 shrink-0 animate-pulse rounded bg-white/10 backdrop-blur-xl" />
|
<div className="relative inline-flex h-7 w-7 shrink-0 items-center justify-center">
|
||||||
<div className="h-4 w-full animate-pulse rounded bg-white/10 backdrop-blur-xl" />
|
<LoaderIcon className="h-4 w-4 animate-spin text-white" />
|
||||||
|
</div>
|
||||||
|
<h5 className="text-white/50">Loading messages...</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { NDKEvent } from '@nostr-dev-kit/ndk';
|
|||||||
|
|
||||||
import { useDecryptMessage } from '@app/chats/hooks/useDecryptMessage';
|
import { useDecryptMessage } from '@app/chats/hooks/useDecryptMessage';
|
||||||
|
|
||||||
|
import { TextNote } from '@shared/notes';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
export function ChatMessageItem({
|
export function ChatMessageItem({
|
||||||
@@ -20,13 +21,12 @@ export function ChatMessageItem({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-min min-h-min w-full select-text flex-col px-5 py-3 backdrop-blur-xl hover:bg-white/10">
|
<div className="flex h-min min-h-min w-full select-text flex-col px-5 py-3 hover:bg-white/10">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<User pubkey={message.pubkey} time={message.created_at} variant="chat" />
|
<User pubkey={message.pubkey} time={message.created_at} variant="chat" />
|
||||||
<div className="-mt-[20px] pl-[49px]">
|
<div className="-mt-5 flex items-start gap-3">
|
||||||
<p className="select-text whitespace-pre-line break-words text-base text-white">
|
<div className="w-10 shrink-0" />
|
||||||
{message.content}
|
<TextNote content={message.content} />
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { NDKSubscription } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { Virtuoso } from 'react-virtuoso';
|
import { VList, VListHandle } from 'virtua';
|
||||||
|
|
||||||
import { ChatMessageForm } from '@app/chats/components/messages/form';
|
import { ChatMessageForm } from '@app/chats/components/messages/form';
|
||||||
import { ChatMessageItem } from '@app/chats/components/messages/item';
|
import { ChatMessageItem } from '@app/chats/components/messages/item';
|
||||||
@@ -18,7 +18,7 @@ import { useStronghold } from '@stores/stronghold';
|
|||||||
import { useNostr } from '@utils/hooks/useNostr';
|
import { useNostr } from '@utils/hooks/useNostr';
|
||||||
|
|
||||||
export function ChatScreen() {
|
export function ChatScreen() {
|
||||||
const virtuosoRef = useRef(null);
|
const listRef = useRef<VListHandle>(null);
|
||||||
const userPrivkey = useStronghold((state) => state.privkey);
|
const userPrivkey = useStronghold((state) => state.privkey);
|
||||||
|
|
||||||
const { db } = useStorage();
|
const { db } = useStorage();
|
||||||
@@ -29,10 +29,8 @@ export function ChatScreen() {
|
|||||||
return await fetchNIP04Messages(pubkey);
|
return await fetchNIP04Messages(pubkey);
|
||||||
});
|
});
|
||||||
|
|
||||||
const itemContent = useCallback(
|
const renderItem = useCallback(
|
||||||
(index: string | number) => {
|
(message: NDKEvent) => {
|
||||||
const message = data[index];
|
|
||||||
if (!message) return;
|
|
||||||
return (
|
return (
|
||||||
<ChatMessageItem
|
<ChatMessageItem
|
||||||
message={message}
|
message={message}
|
||||||
@@ -44,12 +42,9 @@ export function ChatScreen() {
|
|||||||
[data]
|
[data]
|
||||||
);
|
);
|
||||||
|
|
||||||
const computeItemKey = useCallback(
|
useEffect(() => {
|
||||||
(index: string | number) => {
|
if (data.length > 0) listRef.current?.scrollToIndex(data.length);
|
||||||
return data[index].id;
|
}, [data]);
|
||||||
},
|
|
||||||
[data]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sub: NDKSubscription = ndk.subscribe(
|
const sub: NDKSubscription = ndk.subscribe(
|
||||||
@@ -86,22 +81,17 @@ export function ChatScreen() {
|
|||||||
<p className="text-sm font-medium text-white/50">Loading messages</p>
|
<p className="text-sm font-medium text-white/50">Loading messages</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : data.length === 0 ? (
|
||||||
|
<div className="absolute left-1/2 top-1/2 flex w-full -translate-x-1/2 -translate-y-1/2 transform flex-col gap-1 text-center">
|
||||||
|
<h3 className="mb-2 text-4xl">🙌</h3>
|
||||||
|
<p className="leading-none text-white/50">
|
||||||
|
You two didn't talk yet, let's send first message
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Virtuoso
|
<VList ref={listRef} className="scrollbar-hide h-full" mode="reverse">
|
||||||
ref={virtuosoRef}
|
{data.map((message) => renderItem(message))}
|
||||||
data={data}
|
</VList>
|
||||||
itemContent={itemContent}
|
|
||||||
computeItemKey={computeItemKey}
|
|
||||||
initialTopMostItemIndex={data.length - 1}
|
|
||||||
alignToBottom={true}
|
|
||||||
followOutput={true}
|
|
||||||
overscan={50}
|
|
||||||
increaseViewportBy={{ top: 200, bottom: 200 }}
|
|
||||||
className="scrollbar-hide relative overflow-y-auto"
|
|
||||||
components={{
|
|
||||||
EmptyPlaceholder: () => Empty,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="z-50 shrink-0 rounded-b-xl border-t border-white/5 bg-white/10 p-3 px-5 backdrop-blur-xl">
|
<div className="z-50 shrink-0 rounded-b-xl border-t border-white/5 bg-white/10 p-3 px-5 backdrop-blur-xl">
|
||||||
@@ -120,12 +110,3 @@ export function ChatScreen() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Empty = (
|
|
||||||
<div className="absolute left-1/2 top-1/2 flex w-full -translate-x-1/2 -translate-y-1/2 transform flex-col gap-1 text-center">
|
|
||||||
<h3 className="mb-2 text-4xl">🙌</h3>
|
|
||||||
<p className="leading-none text-white/50">
|
|
||||||
You two didn't talk yet, let's send first message
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useCallback } from 'react';
|
||||||
import { useCallback, useRef } from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
import { UserProfile } from '@app/users/components/profile';
|
import { UserProfile } from '@app/users/components/profile';
|
||||||
|
|
||||||
@@ -32,79 +32,35 @@ export function UserScreen() {
|
|||||||
return [...events] as unknown as NDKEvent[];
|
return [...events] as unknown as NDKEvent[];
|
||||||
});
|
});
|
||||||
|
|
||||||
const parentRef = useRef();
|
|
||||||
const virtualizer = useVirtualizer({
|
|
||||||
count: data ? data.length : 0,
|
|
||||||
getScrollElement: () => parentRef.current,
|
|
||||||
estimateSize: () => 650,
|
|
||||||
overscan: 4,
|
|
||||||
});
|
|
||||||
const items = virtualizer.getVirtualItems();
|
|
||||||
|
|
||||||
// render event match event kind
|
// render event match event kind
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(index: string | number) => {
|
(event: NDKEvent) => {
|
||||||
const event: NDKEvent = data[index];
|
|
||||||
if (!event) return;
|
|
||||||
|
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case NDKKind.Text:
|
case NDKKind.Text:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={event.id} event={event}>
|
||||||
key={event.id + index}
|
<TextNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<TextNote content={event.content} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case NDKKind.Repost:
|
case NDKKind.Repost:
|
||||||
return (
|
return <Repost key={event.id} event={event} />;
|
||||||
<div
|
|
||||||
key={event.id + index}
|
|
||||||
data-index={index}
|
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<Repost key={event.id} event={event} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case 1063:
|
case 1063:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={event.id} event={event}>
|
||||||
key={event.id + index}
|
<FileNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<FileNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case NDKKind.Article:
|
case NDKKind.Article:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={event.id} event={event}>
|
||||||
key={event.id + index}
|
<ArticleNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<ArticleNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={event.id} event={event}>
|
||||||
key={event.id + index}
|
<UnknownNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<UnknownNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -112,10 +68,7 @@ export function UserScreen() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="scrollbar-hide relative h-full w-full overflow-y-auto bg-white/10 backdrop-blur-xl">
|
||||||
ref={parentRef}
|
|
||||||
className="scrollbar-hide relative h-full w-full overflow-y-auto bg-white/10 backdrop-blur-xl"
|
|
||||||
>
|
|
||||||
<div data-tauri-drag-region className="absolute left-0 top-0 h-11 w-full" />
|
<div data-tauri-drag-region className="absolute left-0 top-0 h-11 w-full" />
|
||||||
<UserProfile pubkey={pubkey} />
|
<UserProfile pubkey={pubkey} />
|
||||||
<div className="mt-6 h-full w-full border-t border-white/5 px-1.5">
|
<div className="mt-6 h-full w-full border-t border-white/5 px-1.5">
|
||||||
@@ -129,7 +82,7 @@ export function UserScreen() {
|
|||||||
<NoteSkeleton />
|
<NoteSkeleton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : items.length === 0 ? (
|
) : data.length === 0 ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-6 backdrop-blur-xl">
|
<div className="rounded-xl bg-white/10 px-3 py-6 backdrop-blur-xl">
|
||||||
<div className="flex flex-col items-center gap-4">
|
<div className="flex flex-col items-center gap-4">
|
||||||
@@ -140,22 +93,10 @@ export function UserScreen() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<VList className="scrollbar-hide h-full">
|
||||||
style={{
|
{data.map((item) => renderItem(item))}
|
||||||
position: 'relative',
|
<div className="h-16" />
|
||||||
width: '100%',
|
</VList>
|
||||||
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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export const NDKInstance = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// TODO: fully support NIP-11
|
// TODO: fully support NIP-11
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
async function getExplicitRelays() {
|
async function getExplicitRelays() {
|
||||||
try {
|
try {
|
||||||
// get relays
|
// get relays
|
||||||
@@ -62,7 +63,7 @@ export const NDKInstance = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function initNDK() {
|
async function initNDK() {
|
||||||
const explicitRelayUrls = await getExplicitRelays();
|
const explicitRelayUrls = await db.getExplicitRelayUrls();
|
||||||
const instance = new NDK({
|
const instance = new NDK({
|
||||||
explicitRelayUrls,
|
explicitRelayUrls,
|
||||||
cacheAdapter,
|
cacheAdapter,
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ export function Navigation() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Frame className="relative flex h-full w-[232px] flex-col" lighter>
|
<Frame
|
||||||
|
className="relative flex h-full w-[232px] flex-col border-r border-white/5"
|
||||||
|
lighter
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className="inline-flex h-16 w-full items-center justify-end px-3"
|
className="inline-flex h-16 w-full items-center justify-end px-3"
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ import { Link } from 'react-router-dom';
|
|||||||
|
|
||||||
import { Image } from '@shared/image';
|
import { Image } from '@shared/image';
|
||||||
|
|
||||||
export function ArticleNote({ event }: { event: NDKEvent }) {
|
export function ArticleNote(props: { event?: NDKEvent }) {
|
||||||
const metadata = useMemo(() => {
|
const metadata = useMemo(() => {
|
||||||
const title = event.tags.find((tag) => tag[0] === 'title')?.[1];
|
const title = props.event.tags.find((tag) => tag[0] === 'title')?.[1];
|
||||||
const image = event.tags.find((tag) => tag[0] === 'image')?.[1];
|
const image = props.event.tags.find((tag) => tag[0] === 'image')?.[1];
|
||||||
const summary = event.tags.find((tag) => tag[0] === 'summary')?.[1];
|
const summary = props.event.tags.find((tag) => tag[0] === 'summary')?.[1];
|
||||||
|
|
||||||
let publishedAt: Date | string | number = event.tags.find(
|
let publishedAt: Date | string | number = props.event.tags.find(
|
||||||
(tag) => tag[0] === 'published_at'
|
(tag) => tag[0] === 'published_at'
|
||||||
)?.[1];
|
)?.[1];
|
||||||
if (publishedAt) {
|
if (publishedAt) {
|
||||||
publishedAt = new Date(parseInt(publishedAt)).toLocaleDateString('en-US');
|
publishedAt = new Date(parseInt(publishedAt)).toLocaleDateString('en-US');
|
||||||
} else {
|
} else {
|
||||||
publishedAt = new Date(event.created_at * 1000).toLocaleDateString('en-US');
|
publishedAt = new Date(props.event.created_at * 1000).toLocaleDateString('en-US');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -25,11 +25,11 @@ export function ArticleNote({ event }: { event: NDKEvent }) {
|
|||||||
publishedAt,
|
publishedAt,
|
||||||
summary,
|
summary,
|
||||||
};
|
};
|
||||||
}, [event.id]);
|
}, [props.event.id]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
to={`/notes/article/${event.id}`}
|
to={`/notes/article/${props.event.id}`}
|
||||||
preventScrollReset={true}
|
preventScrollReset={true}
|
||||||
className="mb-2 mt-3 rounded-lg"
|
className="mb-2 mt-3 rounded-lg"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { Image } from '@shared/image';
|
|||||||
|
|
||||||
import { fileType } from '@utils/nip94';
|
import { fileType } from '@utils/nip94';
|
||||||
|
|
||||||
export function FileNote({ event }: { event: NDKEvent }) {
|
export function FileNote(props: { event?: NDKEvent }) {
|
||||||
const url = event.tags.find((el) => el[0] === 'url')[1];
|
const url = props.event.tags.find((el) => el[0] === 'url')[1];
|
||||||
const type = fileType(url);
|
const type = fileType(url);
|
||||||
|
|
||||||
if (type === 'image') {
|
if (type === 'image') {
|
||||||
@@ -15,7 +15,7 @@ export function FileNote({ event }: { event: NDKEvent }) {
|
|||||||
<div className="mb-2 mt-3">
|
<div className="mb-2 mt-3">
|
||||||
<Image
|
<Image
|
||||||
src={url}
|
src={url}
|
||||||
alt={event.content}
|
alt={props.event.content}
|
||||||
className="h-auto w-full rounded-lg object-cover"
|
className="h-auto w-full rounded-lg object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import {
|
|||||||
|
|
||||||
import { parser } from '@utils/parser';
|
import { parser } from '@utils/parser';
|
||||||
|
|
||||||
export function TextNote({ content }: { content: string }) {
|
export function TextNote(props: { content?: string }) {
|
||||||
const richContent = parser(content) ?? null;
|
const richContent = parser(props.content) ?? null;
|
||||||
|
|
||||||
if (!richContent) {
|
if (!richContent) {
|
||||||
return (
|
return (
|
||||||
@@ -26,7 +26,7 @@ export function TextNote({ content }: { content: string }) {
|
|||||||
unwrapDisallowed={true}
|
unwrapDisallowed={true}
|
||||||
linkTarget={'_blank'}
|
linkTarget={'_blank'}
|
||||||
>
|
>
|
||||||
{content}
|
{props.content}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||||
|
|
||||||
export function UnknownNote({ event }: { event: NDKEvent }) {
|
export function UnknownNote(props: { event?: NDKEvent }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col gap-2">
|
<div className="flex w-full flex-col gap-2">
|
||||||
<div className="inline-flex flex-col gap-1 rounded-md bg-white/10 px-2 py-2 backdrop-blur-xl">
|
<div className="inline-flex flex-col gap-1 rounded-md bg-white/10 px-2 py-2 backdrop-blur-xl">
|
||||||
<span className="text-sm font-medium leading-none text-white/50">
|
<span className="text-sm font-medium leading-none text-white/50">
|
||||||
Unknown kind: {event.kind}
|
Unknown kind: {props.event.kind}
|
||||||
</span>
|
</span>
|
||||||
<p className="text-sm leading-none text-white">
|
<p className="text-sm leading-none text-white">
|
||||||
Lume isn't fully support this kind
|
Lume isn't fully support this kind
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="select-text whitespace-pre-line break-all text-white">
|
<div className="select-text whitespace-pre-line break-all text-white">
|
||||||
<p>{event.content.toString()}</p>
|
<p>{props.event.content.toString()}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export function SubReply({ event }: { event: NDKEvent }) {
|
|||||||
<div className="-mt-6 flex items-start gap-3">
|
<div className="-mt-6 flex items-start gap-3">
|
||||||
<div className="w-11 shrink-0" />
|
<div className="w-11 shrink-0" />
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<TextNote content={event.content} />
|
<TextNote />
|
||||||
<NoteActions id={event.id} pubkey={event.pubkey} />
|
<NoteActions id={event.id} pubkey={event.pubkey} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||||
import { ReactNode } from 'react';
|
import { ReactElement, cloneElement } from 'react';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
import { ChildNote, NoteActions } from '@shared/notes';
|
import { ChildNote, NoteActions } from '@shared/notes';
|
||||||
@@ -13,7 +13,7 @@ export function NoteWrapper({
|
|||||||
lighter = false,
|
lighter = false,
|
||||||
}: {
|
}: {
|
||||||
event: NDKEvent;
|
event: NDKEvent;
|
||||||
children: ReactNode;
|
children: ReactElement;
|
||||||
repost?: boolean;
|
repost?: boolean;
|
||||||
root?: string;
|
root?: string;
|
||||||
reply?: string;
|
reply?: string;
|
||||||
@@ -34,7 +34,10 @@ export function NoteWrapper({
|
|||||||
<div className="-mt-5 flex items-start gap-3">
|
<div className="-mt-5 flex items-start gap-3">
|
||||||
<div className="w-10 shrink-0" />
|
<div className="w-10 shrink-0" />
|
||||||
<div className="relative z-20 flex-1">
|
<div className="relative z-20 flex-1">
|
||||||
{children}
|
{cloneElement(
|
||||||
|
children,
|
||||||
|
event.kind === 1 ? { content: event.content } : { event: event }
|
||||||
|
)}
|
||||||
<NoteActions id={event.id} pubkey={event.pubkey} />
|
<NoteActions id={event.id} pubkey={event.pubkey} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useCallback } from 'react';
|
||||||
import { useCallback, useRef } from 'react';
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
import { useNDK } from '@libs/ndk/provider';
|
||||||
|
|
||||||
@@ -25,27 +25,13 @@ export function GlobalArticlesWidget({ params }: { params: Widget }) {
|
|||||||
{ refetchOnWindowFocus: false }
|
{ refetchOnWindowFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
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
|
// render event match event kind
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(index: string | number) => {
|
(event: NDKEvent) => {
|
||||||
const event: NDKEvent = data[index];
|
|
||||||
if (!event) return;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={event.id} data-index={index} ref={virtualizer.measureElement}>
|
<NoteWrapper key={event.id} event={event}>
|
||||||
<NoteWrapper event={event}>
|
<ArticleNote />
|
||||||
<ArticleNote event={event} />
|
</NoteWrapper>
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[data]
|
[data]
|
||||||
@@ -54,40 +40,32 @@ export function GlobalArticlesWidget({ params }: { params: Widget }) {
|
|||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={params.id} title={params.title} />
|
||||||
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
|
<div className="h-full">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
<NoteSkeleton />
|
<NoteSkeleton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : items.length === 0 ? (
|
) : data.length === 0 ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="flex h-full w-full flex-col items-center justify-center px-3">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-6 backdrop-blur-xl">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<div className="flex flex-col items-center gap-4">
|
<img src="/ghost.png" alt="empty feeds" className="h-16 w-16" />
|
||||||
<p className="text-center text-sm font-medium text-white">
|
<div className="text-center">
|
||||||
There have been no new articles in the last 24 hours.
|
<h3 className="text-xl font-semibold leading-tight">
|
||||||
|
Your newsfeed is empty
|
||||||
|
</h3>
|
||||||
|
<p className="text-center text-white/50">
|
||||||
|
Connect more people to explore more content
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<VList className="scrollbar-hide h-full">
|
||||||
style={{
|
{data.map((item) => renderItem(item))}
|
||||||
position: 'relative',
|
<div className="h-16" />
|
||||||
width: '100%',
|
</VList>
|
||||||
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>
|
||||||
</WidgetWrapper>
|
</WidgetWrapper>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useCallback } from 'react';
|
||||||
import { useCallback, useRef } from 'react';
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
import { useNDK } from '@libs/ndk/provider';
|
||||||
|
|
||||||
@@ -26,27 +26,13 @@ export function GlobalFilesWidget({ params }: { params: Widget }) {
|
|||||||
{ refetchOnWindowFocus: false }
|
{ refetchOnWindowFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
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
|
// render event match event kind
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(index: string | number) => {
|
(event: NDKEvent) => {
|
||||||
const event: NDKEvent = data[index];
|
|
||||||
if (!event) return;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={event.id} data-index={index} ref={virtualizer.measureElement}>
|
<NoteWrapper key={event.id} event={event}>
|
||||||
<NoteWrapper event={event}>
|
<FileNote />
|
||||||
<FileNote event={event} />
|
</NoteWrapper>
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[data]
|
[data]
|
||||||
@@ -55,40 +41,32 @@ export function GlobalFilesWidget({ params }: { params: Widget }) {
|
|||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={params.id} title={params.title} />
|
||||||
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
|
<div className="h-full">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
<NoteSkeleton />
|
<NoteSkeleton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : items.length === 0 ? (
|
) : data.length === 0 ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="flex h-full w-full flex-col items-center justify-center px-3">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-6 backdrop-blur-xl">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<div className="flex flex-col items-center gap-4">
|
<img src="/ghost.png" alt="empty feeds" className="h-16 w-16" />
|
||||||
<p className="text-center text-sm font-medium text-white">
|
<div className="text-center">
|
||||||
There have been no new files in the last 24 hours.
|
<h3 className="text-xl font-semibold leading-tight">
|
||||||
|
Your newsfeed is empty
|
||||||
|
</h3>
|
||||||
|
<p className="text-center text-white/50">
|
||||||
|
Connect more people to explore more content
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<VList className="scrollbar-hide h-full">
|
||||||
style={{
|
{data.map((item) => renderItem(item))}
|
||||||
position: 'relative',
|
<div className="h-16" />
|
||||||
width: '100%',
|
</VList>
|
||||||
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>
|
||||||
</WidgetWrapper>
|
</WidgetWrapper>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useCallback } from 'react';
|
||||||
import { useCallback, useRef } from 'react';
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
import { useNDK } from '@libs/ndk/provider';
|
||||||
|
|
||||||
@@ -35,79 +35,35 @@ export function GlobalHashtagWidget({ params }: { params: Widget }) {
|
|||||||
{ refetchOnWindowFocus: false }
|
{ refetchOnWindowFocus: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
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
|
// render event match event kind
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(index: string | number) => {
|
(event: NDKEvent) => {
|
||||||
const event: NDKEvent = data[index];
|
|
||||||
if (!event) return;
|
|
||||||
|
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case NDKKind.Text:
|
case NDKKind.Text:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={event.id} event={event}>
|
||||||
key={event.id + index}
|
<TextNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<TextNote content={event.content} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case NDKKind.Repost:
|
case NDKKind.Repost:
|
||||||
return (
|
return <Repost key={event.id} event={event} />;
|
||||||
<div
|
|
||||||
key={event.id + index}
|
|
||||||
data-index={index}
|
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<Repost key={event.id} event={event} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case 1063:
|
case 1063:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={event.id} event={event}>
|
||||||
key={event.id + index}
|
<FileNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<FileNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case NDKKind.Article:
|
case NDKKind.Article:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={event.id} event={event}>
|
||||||
key={event.id + index}
|
<ArticleNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<ArticleNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={event.id} event={event}>
|
||||||
key={event.id + index}
|
<UnknownNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<UnknownNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -117,40 +73,33 @@ export function GlobalHashtagWidget({ params }: { params: Widget }) {
|
|||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title={params.title + ' in 24 hours ago'} />
|
<TitleBar id={params.id} title={params.title + ' in 24 hours ago'} />
|
||||||
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
|
<div className="h-full">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
<NoteSkeleton />
|
<NoteSkeleton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : items.length === 0 ? (
|
) : data.length === 0 ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="flex h-full w-full flex-col items-center justify-center px-3">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-6 backdrop-blur-xl">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<div className="flex flex-col items-center gap-4">
|
<img src="/ghost.png" alt="empty feeds" className="h-16 w-16" />
|
||||||
<p className="text-center text-sm font-medium text-white">
|
<div className="text-center">
|
||||||
There have been no new posts with this hashtag in the last 24 hours.
|
<h3 className="text-xl font-semibold leading-tight">
|
||||||
|
Your newsfeed is empty
|
||||||
|
</h3>
|
||||||
|
<p className="text-center text-white/50">
|
||||||
|
Connect more people to explore more content
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<VList className="scrollbar-hide h-full">
|
||||||
style={{
|
{data.map((item) => renderItem(item))}
|
||||||
position: 'relative',
|
|
||||||
width: '100%',
|
<div className="h-16" />
|
||||||
height: `${virtualizer.getTotalSize()}px`,
|
</VList>
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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>
|
||||||
</WidgetWrapper>
|
</WidgetWrapper>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useCallback, useMemo, useRef } from 'react';
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
@@ -27,27 +27,15 @@ export function LocalArticlesWidget({ params }: { params: Widget }) {
|
|||||||
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
||||||
[data]
|
[data]
|
||||||
);
|
);
|
||||||
const parentRef = useRef<HTMLDivElement>();
|
|
||||||
const virtualizer = useVirtualizer({
|
|
||||||
count: hasNextPage ? dbEvents.length : dbEvents.length,
|
|
||||||
getScrollElement: () => parentRef.current,
|
|
||||||
estimateSize: () => 650,
|
|
||||||
overscan: 4,
|
|
||||||
});
|
|
||||||
const items = virtualizer.getVirtualItems();
|
|
||||||
|
|
||||||
// render event match event kind
|
// render event match event kind
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(index: string | number) => {
|
(dbEvent: DBEvent) => {
|
||||||
const event: NDKEvent = data[index];
|
const event: NDKEvent = JSON.parse(dbEvent.event as string);
|
||||||
if (!event) return;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={event.id} data-index={index} ref={virtualizer.measureElement}>
|
<NoteWrapper key={event.id} event={event}>
|
||||||
<NoteWrapper event={event}>
|
<FileNote />
|
||||||
<FileNote event={event} />
|
</NoteWrapper>
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[data]
|
[data]
|
||||||
@@ -56,75 +44,59 @@ export function LocalArticlesWidget({ params }: { params: Widget }) {
|
|||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={params.id} title={params.title} />
|
||||||
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
|
<div className="h-full">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
<NoteSkeleton />
|
<NoteSkeleton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : items.length === 0 ? (
|
) : dbEvents.length === 0 ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="flex h-full w-full flex-col items-center justify-center px-3">
|
||||||
<div className="bbg-white/10 rounded-xl px-3 py-6 backdrop-blur-xl">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<div className="flex flex-col items-center gap-4">
|
<img src="/ghost.png" alt="empty feeds" className="h-16 w-16" />
|
||||||
<p className="text-center text-sm text-white">
|
<div className="text-center">
|
||||||
There have been no new posts.
|
<h3 className="text-xl font-semibold leading-tight">
|
||||||
|
Your newsfeed is empty
|
||||||
|
</h3>
|
||||||
|
<p className="text-center text-white/50">
|
||||||
|
Connect more people to explore more content
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<VList className="scrollbar-hide h-full">
|
||||||
style={{
|
{dbEvents.map((item) => renderItem(item))}
|
||||||
position: 'relative',
|
<div className="flex items-center justify-center px-3 py-1.5">
|
||||||
width: '100%',
|
{dbEvents.length > 0 ? (
|
||||||
height: `${virtualizer.getTotalSize()}px`,
|
<button
|
||||||
}}
|
onClick={() => fetchNextPage()}
|
||||||
>
|
disabled={!hasNextPage || isFetchingNextPage}
|
||||||
<div
|
className="inline-flex h-10 w-max items-center justify-center gap-2 rounded-full bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none"
|
||||||
className="absolute left-0 top-0 w-full"
|
>
|
||||||
style={{
|
{isFetchingNextPage ? (
|
||||||
transform: `translateY(${items[0].start}px)`,
|
<>
|
||||||
}}
|
<span>Loading...</span>
|
||||||
>
|
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||||
{items.map((item) => renderItem(item.index))}
|
</>
|
||||||
|
) : hasNextPage ? (
|
||||||
|
<>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5 text-white" />
|
||||||
|
<span>Load more</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5 text-white" />
|
||||||
|
<span>Nothing more to load</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="h-16" />
|
||||||
|
</VList>
|
||||||
)}
|
)}
|
||||||
{isFetchingNextPage && (
|
|
||||||
<div className="px-3 py-1.5">
|
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
|
||||||
<NoteSkeleton />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="px-3 py-1.5">
|
|
||||||
<button
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
{isFetchingNextPage ? (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Loading...</span>
|
|
||||||
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
|
||||||
</>
|
|
||||||
) : hasNextPage ? (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Load more</span>
|
|
||||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Nothing more to load</span>
|
|
||||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</WidgetWrapper>
|
</WidgetWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useCallback, useMemo, useRef } from 'react';
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
@@ -36,80 +36,42 @@ export function LocalFeedsWidget({ params }: { params: Widget }) {
|
|||||||
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
||||||
[data]
|
[data]
|
||||||
);
|
);
|
||||||
const parentRef = useRef<HTMLDivElement>();
|
|
||||||
const virtualizer = useVirtualizer({
|
|
||||||
count: hasNextPage ? dbEvents.length : dbEvents.length,
|
|
||||||
getScrollElement: () => parentRef.current,
|
|
||||||
estimateSize: () => 650,
|
|
||||||
overscan: 4,
|
|
||||||
});
|
|
||||||
const items = virtualizer.getVirtualItems();
|
|
||||||
|
|
||||||
// render event match event kind
|
// render event match event kind
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(index: string | number) => {
|
(dbEvent: DBEvent) => {
|
||||||
const dbEvent: DBEvent = dbEvents[index];
|
|
||||||
if (!dbEvent) return;
|
|
||||||
|
|
||||||
const event: NDKEvent = JSON.parse(dbEvent.event as string);
|
const event: NDKEvent = JSON.parse(dbEvent.event as string);
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case NDKKind.Text:
|
case NDKKind.Text:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper
|
||||||
key={dbEvent.id + index}
|
key={dbEvent.id + dbEvent.root_id + dbEvent.reply_id}
|
||||||
data-index={index}
|
event={event}
|
||||||
ref={virtualizer.measureElement}
|
root={dbEvent.root_id}
|
||||||
|
reply={dbEvent.reply_id}
|
||||||
>
|
>
|
||||||
<NoteWrapper event={event} root={dbEvent.root_id} reply={dbEvent.reply_id}>
|
<TextNote />
|
||||||
<TextNote content={event.content} />
|
</NoteWrapper>
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case NDKKind.Repost:
|
case NDKKind.Repost:
|
||||||
return (
|
return <Repost key={dbEvent.id} event={event} />;
|
||||||
<div
|
|
||||||
key={dbEvent.id + index}
|
|
||||||
data-index={index}
|
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<Repost key={dbEvent.id} event={event} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case 1063:
|
case 1063:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={dbEvent.id} event={event}>
|
||||||
key={dbEvent.id + index}
|
<FileNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<FileNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case NDKKind.Article:
|
case NDKKind.Article:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={dbEvent.id} event={event}>
|
||||||
key={dbEvent.id + index}
|
<ArticleNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<ArticleNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={dbEvent.id} event={event}>
|
||||||
key={dbEvent.id + index}
|
<UnknownNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<UnknownNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -119,75 +81,59 @@ export function LocalFeedsWidget({ params }: { params: Widget }) {
|
|||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={params.id} title={params.title} />
|
||||||
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
|
<div className="h-full">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
<NoteSkeleton />
|
<NoteSkeleton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : items.length === 0 ? (
|
) : dbEvents.length === 0 ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="flex h-full w-full flex-col items-center justify-center px-3">
|
||||||
<div className="bbg-white/10 rounded-xl px-3 py-6 backdrop-blur-xl">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<div className="flex flex-col items-center gap-4">
|
<img src="/ghost.png" alt="empty feeds" className="h-16 w-16" />
|
||||||
<p className="text-center text-sm text-white">
|
<div className="text-center">
|
||||||
There have been no new posts.
|
<h3 className="text-xl font-semibold leading-tight">
|
||||||
|
Your newsfeed is empty
|
||||||
|
</h3>
|
||||||
|
<p className="text-center text-white/50">
|
||||||
|
Connect more people to explore more content
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<VList className="scrollbar-hide h-full">
|
||||||
style={{
|
{dbEvents.map((item) => renderItem(item))}
|
||||||
position: 'relative',
|
<div className="flex items-center justify-center px-3 py-1.5">
|
||||||
width: '100%',
|
{dbEvents.length > 0 ? (
|
||||||
height: `${virtualizer.getTotalSize()}px`,
|
<button
|
||||||
}}
|
onClick={() => fetchNextPage()}
|
||||||
>
|
disabled={!hasNextPage || isFetchingNextPage}
|
||||||
<div
|
className="inline-flex h-10 w-max items-center justify-center gap-2 rounded-full bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none"
|
||||||
className="absolute left-0 top-0 w-full"
|
>
|
||||||
style={{
|
{isFetchingNextPage ? (
|
||||||
transform: `translateY(${items[0].start}px)`,
|
<>
|
||||||
}}
|
<span>Loading...</span>
|
||||||
>
|
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||||
{items.map((item) => renderItem(item.index))}
|
</>
|
||||||
|
) : hasNextPage ? (
|
||||||
|
<>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5 text-white" />
|
||||||
|
<span>Load more</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5 text-white" />
|
||||||
|
<span>Nothing more to load</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="h-16" />
|
||||||
|
</VList>
|
||||||
)}
|
)}
|
||||||
{isFetchingNextPage && (
|
|
||||||
<div className="px-3 py-1.5">
|
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
|
||||||
<NoteSkeleton />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="px-3 py-1.5">
|
|
||||||
<button
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
{isFetchingNextPage ? (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Loading...</span>
|
|
||||||
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
|
||||||
</>
|
|
||||||
) : hasNextPage ? (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Load more</span>
|
|
||||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Nothing more to load</span>
|
|
||||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</WidgetWrapper>
|
</WidgetWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useCallback, useMemo, useRef } from 'react';
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
@@ -27,27 +27,15 @@ export function LocalFilesWidget({ params }: { params: Widget }) {
|
|||||||
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
||||||
[data]
|
[data]
|
||||||
);
|
);
|
||||||
const parentRef = useRef<HTMLDivElement>();
|
|
||||||
const virtualizer = useVirtualizer({
|
|
||||||
count: hasNextPage ? dbEvents.length : dbEvents.length,
|
|
||||||
getScrollElement: () => parentRef.current,
|
|
||||||
estimateSize: () => 650,
|
|
||||||
overscan: 4,
|
|
||||||
});
|
|
||||||
const items = virtualizer.getVirtualItems();
|
|
||||||
|
|
||||||
// render event match event kind
|
// render event match event kind
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(index: string | number) => {
|
(dbEvent: DBEvent) => {
|
||||||
const event: NDKEvent = data[index];
|
const event: NDKEvent = JSON.parse(dbEvent.event as string);
|
||||||
if (!event) return;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={event.id} data-index={index} ref={virtualizer.measureElement}>
|
<NoteWrapper key={event.id} event={event}>
|
||||||
<NoteWrapper event={event}>
|
<FileNote />
|
||||||
<FileNote event={event} />
|
</NoteWrapper>
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[data]
|
[data]
|
||||||
@@ -56,75 +44,59 @@ export function LocalFilesWidget({ params }: { params: Widget }) {
|
|||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={params.id} title={params.title} />
|
||||||
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
|
<div className="h-full">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
<NoteSkeleton />
|
<NoteSkeleton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : items.length === 0 ? (
|
) : dbEvents.length === 0 ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="flex h-full w-full flex-col items-center justify-center px-3">
|
||||||
<div className="bbg-white/10 rounded-xl px-3 py-6 backdrop-blur-xl">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<div className="flex flex-col items-center gap-4">
|
<img src="/ghost.png" alt="empty feeds" className="h-16 w-16" />
|
||||||
<p className="text-center text-sm text-white">
|
<div className="text-center">
|
||||||
There have been no new posts.
|
<h3 className="text-xl font-semibold leading-tight">
|
||||||
|
Your newsfeed is empty
|
||||||
|
</h3>
|
||||||
|
<p className="text-center text-white/50">
|
||||||
|
Connect more people to explore more content
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<VList className="scrollbar-hide h-full">
|
||||||
style={{
|
{dbEvents.map((item) => renderItem(item))}
|
||||||
position: 'relative',
|
<div className="flex items-center justify-center px-3 py-1.5">
|
||||||
width: '100%',
|
{dbEvents.length > 0 ? (
|
||||||
height: `${virtualizer.getTotalSize()}px`,
|
<button
|
||||||
}}
|
onClick={() => fetchNextPage()}
|
||||||
>
|
disabled={!hasNextPage || isFetchingNextPage}
|
||||||
<div
|
className="inline-flex h-10 w-max items-center justify-center gap-2 rounded-full bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none"
|
||||||
className="absolute left-0 top-0 w-full"
|
>
|
||||||
style={{
|
{isFetchingNextPage ? (
|
||||||
transform: `translateY(${items[0].start}px)`,
|
<>
|
||||||
}}
|
<span>Loading...</span>
|
||||||
>
|
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||||
{items.map((item) => renderItem(item.index))}
|
</>
|
||||||
|
) : hasNextPage ? (
|
||||||
|
<>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5 text-white" />
|
||||||
|
<span>Load more</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5 text-white" />
|
||||||
|
<span>Nothing more to load</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="h-16" />
|
||||||
|
</VList>
|
||||||
)}
|
)}
|
||||||
{isFetchingNextPage && (
|
|
||||||
<div className="px-3 py-1.5">
|
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
|
||||||
<NoteSkeleton />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="px-3 py-1.5">
|
|
||||||
<button
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
{isFetchingNextPage ? (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Loading...</span>
|
|
||||||
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
|
||||||
</>
|
|
||||||
) : hasNextPage ? (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Load more</span>
|
|
||||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Nothing more to load</span>
|
|
||||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</WidgetWrapper>
|
</WidgetWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useCallback, useMemo, useRef } from 'react';
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
@@ -35,80 +35,42 @@ export function LocalFollowsWidget({ params }: { params: Widget }) {
|
|||||||
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
||||||
[data]
|
[data]
|
||||||
);
|
);
|
||||||
const parentRef = useRef<HTMLDivElement>();
|
|
||||||
const virtualizer = useVirtualizer({
|
|
||||||
count: hasNextPage ? dbEvents.length : dbEvents.length,
|
|
||||||
getScrollElement: () => parentRef.current,
|
|
||||||
estimateSize: () => 650,
|
|
||||||
overscan: 4,
|
|
||||||
});
|
|
||||||
const items = virtualizer.getVirtualItems();
|
|
||||||
|
|
||||||
// render event match event kind
|
// render event match event kind
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(index: string | number) => {
|
(dbEvent: DBEvent) => {
|
||||||
const dbEvent: DBEvent = dbEvents[index];
|
|
||||||
if (!dbEvent) return;
|
|
||||||
|
|
||||||
const event: NDKEvent = JSON.parse(dbEvent.event as string);
|
const event: NDKEvent = JSON.parse(dbEvent.event as string);
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case NDKKind.Text:
|
case NDKKind.Text:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper
|
||||||
key={dbEvent.id + index}
|
key={dbEvent.id + dbEvent.root_id + dbEvent.reply_id}
|
||||||
data-index={index}
|
event={event}
|
||||||
ref={virtualizer.measureElement}
|
root={dbEvent.root_id}
|
||||||
|
reply={dbEvent.reply_id}
|
||||||
>
|
>
|
||||||
<NoteWrapper event={event} root={dbEvent.root_id} reply={dbEvent.reply_id}>
|
<TextNote />
|
||||||
<TextNote content={event.content} />
|
</NoteWrapper>
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case NDKKind.Repost:
|
case NDKKind.Repost:
|
||||||
return (
|
return <Repost key={dbEvent.id} event={event} />;
|
||||||
<div
|
|
||||||
key={dbEvent.id + index}
|
|
||||||
data-index={index}
|
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<Repost key={dbEvent.id} event={event} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case 1063:
|
case 1063:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={dbEvent.id} event={event}>
|
||||||
key={dbEvent.id + index}
|
<FileNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<FileNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case NDKKind.Article:
|
case NDKKind.Article:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={dbEvent.id} event={event}>
|
||||||
key={dbEvent.id + index}
|
<ArticleNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<ArticleNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={dbEvent.id} event={event}>
|
||||||
key={dbEvent.id + index}
|
<UnknownNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<UnknownNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -118,7 +80,7 @@ export function LocalFollowsWidget({ params }: { params: Widget }) {
|
|||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title="Follows" />
|
<TitleBar id={params.id} title="Follows" />
|
||||||
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
|
<div className="h-full">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
@@ -140,59 +102,37 @@ export function LocalFollowsWidget({ params }: { params: Widget }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<VList className="scrollbar-hide h-full">
|
||||||
style={{
|
{dbEvents.map((item) => renderItem(item))}
|
||||||
position: 'relative',
|
<div className="flex items-center justify-center px-3 py-1.5">
|
||||||
width: '100%',
|
{dbEvents.length > 0 ? (
|
||||||
height: `${virtualizer.getTotalSize()}px`,
|
<button
|
||||||
}}
|
onClick={() => fetchNextPage()}
|
||||||
>
|
disabled={!hasNextPage || isFetchingNextPage}
|
||||||
<div
|
className="inline-flex h-10 w-max items-center justify-center gap-2 rounded-full bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none"
|
||||||
className="absolute left-0 top-0 w-full"
|
>
|
||||||
style={{
|
{isFetchingNextPage ? (
|
||||||
transform: `translateY(${items[0].start}px)`,
|
<>
|
||||||
}}
|
<span>Loading...</span>
|
||||||
>
|
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||||
{items.map((item) => renderItem(item.index))}
|
</>
|
||||||
|
) : hasNextPage ? (
|
||||||
|
<>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5 text-white" />
|
||||||
|
<span>Load more</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5 text-white" />
|
||||||
|
<span>Nothing more to load</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="h-16" />
|
||||||
|
</VList>
|
||||||
)}
|
)}
|
||||||
{isFetchingNextPage && (
|
|
||||||
<div className="mb-20 px-3">
|
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
|
||||||
<NoteSkeleton />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="px-3 py-1.5">
|
|
||||||
{dbEvents.length > 0 ? (
|
|
||||||
<button
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
{isFetchingNextPage ? (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Loading...</span>
|
|
||||||
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
|
||||||
</>
|
|
||||||
) : hasNextPage ? (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Load more</span>
|
|
||||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Nothing more to load</span>
|
|
||||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</WidgetWrapper>
|
</WidgetWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NDKEvent, NDKFilter, NDKKind } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKFilter, NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
@@ -38,80 +38,42 @@ export function LocalNetworkWidget() {
|
|||||||
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
() => (data ? data.pages.flatMap((d: { data: DBEvent[] }) => d.data) : []),
|
||||||
[data]
|
[data]
|
||||||
);
|
);
|
||||||
const parentRef = useRef<HTMLDivElement>();
|
|
||||||
const virtualizer = useVirtualizer({
|
|
||||||
count: hasNextPage ? dbEvents.length : dbEvents.length,
|
|
||||||
getScrollElement: () => parentRef.current,
|
|
||||||
estimateSize: () => 650,
|
|
||||||
overscan: 4,
|
|
||||||
});
|
|
||||||
const items = virtualizer.getVirtualItems();
|
|
||||||
|
|
||||||
// render event match event kind
|
// render event match event kind
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(index: string | number) => {
|
(dbEvent: DBEvent) => {
|
||||||
const dbEvent: DBEvent = dbEvents[index];
|
|
||||||
if (!dbEvent) return;
|
|
||||||
|
|
||||||
const event: NDKEvent = JSON.parse(dbEvent.event as string);
|
const event: NDKEvent = JSON.parse(dbEvent.event as string);
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case NDKKind.Text:
|
case NDKKind.Text:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper
|
||||||
key={dbEvent.id + dbEvent.root_id + dbEvent.reply_id + index}
|
key={dbEvent.id + dbEvent.root_id + dbEvent.reply_id}
|
||||||
data-index={index}
|
event={event}
|
||||||
ref={virtualizer.measureElement}
|
root={dbEvent.root_id}
|
||||||
|
reply={dbEvent.reply_id}
|
||||||
>
|
>
|
||||||
<NoteWrapper event={event} root={dbEvent.root_id} reply={dbEvent.reply_id}>
|
<TextNote />
|
||||||
<TextNote content={event.content} />
|
</NoteWrapper>
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case NDKKind.Repost:
|
case NDKKind.Repost:
|
||||||
return (
|
return <Repost key={dbEvent.id} event={event} />;
|
||||||
<div
|
|
||||||
key={dbEvent.id + index}
|
|
||||||
data-index={index}
|
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<Repost key={dbEvent.id} event={event} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case 1063:
|
case 1063:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={dbEvent.id} event={event}>
|
||||||
key={dbEvent.id + index}
|
<FileNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<FileNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case NDKKind.Article:
|
case NDKKind.Article:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={dbEvent.id} event={event}>
|
||||||
key={dbEvent.id + index}
|
<ArticleNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<ArticleNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={dbEvent.id} event={event}>
|
||||||
key={dbEvent.id + index}
|
<UnknownNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<UnknownNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -125,7 +87,7 @@ export function LocalNetworkWidget() {
|
|||||||
const filter: NDKFilter = {
|
const filter: NDKFilter = {
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||||
authors: db.account.network,
|
authors: db.account.network,
|
||||||
since: db.account.last_login_at ?? Math.floor(Date.now() / 1000),
|
since: Math.floor(Date.now() / 1000),
|
||||||
};
|
};
|
||||||
|
|
||||||
sub(filter, async (event) => {
|
sub(filter, async (event) => {
|
||||||
@@ -138,7 +100,7 @@ export function LocalNetworkWidget() {
|
|||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar title="👋 Network" />
|
<TitleBar title="👋 Network" />
|
||||||
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
|
<div className="h-full">
|
||||||
{status === 'loading' ? (
|
{status === 'loading' ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
@@ -160,59 +122,37 @@ export function LocalNetworkWidget() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<VList className="scrollbar-hide h-full">
|
||||||
style={{
|
{dbEvents.map((item) => renderItem(item))}
|
||||||
position: 'relative',
|
<div className="flex items-center justify-center px-3 py-1.5">
|
||||||
width: '100%',
|
{dbEvents.length > 0 ? (
|
||||||
height: `${virtualizer.getTotalSize()}px`,
|
<button
|
||||||
}}
|
onClick={() => fetchNextPage()}
|
||||||
>
|
disabled={!hasNextPage || isFetchingNextPage}
|
||||||
<div
|
className="inline-flex h-10 w-max items-center justify-center gap-2 rounded-full bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none"
|
||||||
className="absolute left-0 top-0 w-full"
|
>
|
||||||
style={{
|
{isFetchingNextPage ? (
|
||||||
transform: `translateY(${items[0].start}px)`,
|
<>
|
||||||
}}
|
<span>Loading...</span>
|
||||||
>
|
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||||
{items.map((item) => renderItem(item.index))}
|
</>
|
||||||
|
) : hasNextPage ? (
|
||||||
|
<>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5 text-white" />
|
||||||
|
<span>Load more</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ArrowRightCircleIcon className="h-5 w-5 text-white" />
|
||||||
|
<span>Nothing more to load</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="h-16" />
|
||||||
|
</VList>
|
||||||
)}
|
)}
|
||||||
{isFetchingNextPage && (
|
|
||||||
<div className="mb-20 px-3">
|
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
|
||||||
<NoteSkeleton />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="px-3 py-1.5">
|
|
||||||
{dbEvents.length > 0 ? (
|
|
||||||
<button
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
disabled={!hasNextPage || isFetchingNextPage}
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
{isFetchingNextPage ? (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Loading...</span>
|
|
||||||
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
|
||||||
</>
|
|
||||||
) : hasNextPage ? (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Load more</span>
|
|
||||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span className="w-5" />
|
|
||||||
<span>Nothing more to load</span>
|
|
||||||
<ArrowRightCircleIcon className="h-5 w-5" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</WidgetWrapper>
|
</WidgetWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useCallback } from 'react';
|
||||||
import { useCallback, useRef } from 'react';
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
import { useNDK } from '@libs/ndk/provider';
|
||||||
|
|
||||||
@@ -41,79 +41,35 @@ export function LocalUserWidget({ params }: { params: Widget }) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
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
|
// render event match event kind
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(index: string | number) => {
|
(event: NDKEvent) => {
|
||||||
const event: NDKEvent = data[index];
|
|
||||||
if (!event) return;
|
|
||||||
|
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case NDKKind.Text:
|
case NDKKind.Text:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={event.id} event={event}>
|
||||||
key={event.id + index}
|
<TextNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<TextNote content={event.content} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case NDKKind.Repost:
|
case NDKKind.Repost:
|
||||||
return (
|
return <Repost key={event.id} event={event} />;
|
||||||
<div
|
|
||||||
key={event.id + index}
|
|
||||||
data-index={index}
|
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<Repost key={event.id} event={event} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case 1063:
|
case 1063:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={event.id} event={event}>
|
||||||
key={event.id + index}
|
<FileNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<FileNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
case NDKKind.Article:
|
case NDKKind.Article:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={event.id} event={event}>
|
||||||
key={event.id + index}
|
<ArticleNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<ArticleNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<div
|
<NoteWrapper key={event.id} event={event}>
|
||||||
key={event.id + index}
|
<UnknownNote />
|
||||||
data-index={index}
|
</NoteWrapper>
|
||||||
ref={virtualizer.measureElement}
|
|
||||||
>
|
|
||||||
<NoteWrapper event={event}>
|
|
||||||
<UnknownNote event={event} />
|
|
||||||
</NoteWrapper>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -123,7 +79,7 @@ export function LocalUserWidget({ params }: { params: Widget }) {
|
|||||||
return (
|
return (
|
||||||
<WidgetWrapper>
|
<WidgetWrapper>
|
||||||
<TitleBar id={params.id} title={params.title} />
|
<TitleBar id={params.id} title={params.title} />
|
||||||
<div ref={parentRef} className="scrollbar-hide h-full overflow-y-auto pb-20">
|
<div className="scrollbar-hide h-full overflow-y-auto pb-20">
|
||||||
<div className="px-3 pt-1.5">
|
<div className="px-3 pt-1.5">
|
||||||
<UserProfile pubkey={params.content} />
|
<UserProfile pubkey={params.content} />
|
||||||
</div>
|
</div>
|
||||||
@@ -136,7 +92,7 @@ export function LocalUserWidget({ params }: { params: Widget }) {
|
|||||||
<NoteSkeleton />
|
<NoteSkeleton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : items.length === 0 ? (
|
) : data.length === 0 ? (
|
||||||
<div className="px-3 py-1.5">
|
<div className="px-3 py-1.5">
|
||||||
<div className="rounded-xl bg-white/10 px-3 py-6 backdrop-blur-xl">
|
<div className="rounded-xl bg-white/10 px-3 py-6 backdrop-blur-xl">
|
||||||
<div className="flex flex-col items-center gap-4">
|
<div className="flex flex-col items-center gap-4">
|
||||||
@@ -147,22 +103,10 @@ export function LocalUserWidget({ params }: { params: Widget }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<VList className="scrollbar-hide h-full">
|
||||||
style={{
|
{data.map((item) => renderItem(item))}
|
||||||
position: 'relative',
|
<div className="h-16" />
|
||||||
width: '100%',
|
</VList>
|
||||||
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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
import { message, open } from '@tauri-apps/api/dialog';
|
import { message, open } from '@tauri-apps/api/dialog';
|
||||||
import { Body, fetch } from '@tauri-apps/api/http';
|
import { Body, fetch } from '@tauri-apps/api/http';
|
||||||
import { LRUCache } from 'lru-cache';
|
import { LRUCache } from 'lru-cache';
|
||||||
|
import { NostrEventExt } from 'nostr-fetch';
|
||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
@@ -220,15 +221,19 @@ export function useNostr() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fetchNIP04Messages = async (sender: string) => {
|
const fetchNIP04Messages = async (sender: string) => {
|
||||||
const senderMessages = await fetcher.fetchAllEvents(
|
let senderMessages: NostrEventExt<false>[] = [];
|
||||||
relayUrls,
|
|
||||||
{
|
if (sender !== db.account.pubkey) {
|
||||||
kinds: [NDKKind.EncryptedDirectMessage],
|
senderMessages = await fetcher.fetchAllEvents(
|
||||||
authors: [sender],
|
relayUrls,
|
||||||
'#p': [db.account.pubkey],
|
{
|
||||||
},
|
kinds: [NDKKind.EncryptedDirectMessage],
|
||||||
{ since: 0 }
|
authors: [sender],
|
||||||
);
|
'#p': [db.account.pubkey],
|
||||||
|
},
|
||||||
|
{ since: 0 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const userMessages = await fetcher.fetchAllEvents(
|
const userMessages = await fetcher.fetchAllEvents(
|
||||||
relayUrls,
|
relayUrls,
|
||||||
|
|||||||
Reference in New Issue
Block a user