mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-03-17 13:21:44 +01:00
Update timelines to use applesauce
This commit is contained in:
parent
0e2054453e
commit
60b61e96b8
5
.changeset/long-suits-clap.md
Normal file
5
.changeset/long-suits-clap.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"nostrudel": minor
|
||||
---
|
||||
|
||||
Update timelines to use applesauce
|
@ -1,7 +1,7 @@
|
||||
identifier: noStrudel
|
||||
maintainers:
|
||||
- npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr
|
||||
- npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr
|
||||
relays:
|
||||
- wss://nostrue.com/
|
||||
- wss://nostr.wine/
|
||||
- wss://nos.lol/
|
||||
- wss://nostrue.com/
|
||||
- wss://nostr.wine/
|
||||
- wss://nos.lol/
|
||||
|
8631
pnpm-lock.yaml
generated
8631
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
168
server/pnpm-lock.yaml
generated
168
server/pnpm-lock.yaml
generated
@ -1,11 +1,10 @@
|
||||
lockfileVersion: '9.0'
|
||||
lockfileVersion: "9.0"
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
cors-anywhere:
|
||||
@ -16,147 +15,180 @@ importers:
|
||||
version: 7.0.2
|
||||
|
||||
packages:
|
||||
|
||||
'@tootallnate/quickjs-emscripten@0.23.0':
|
||||
resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
|
||||
"@tootallnate/quickjs-emscripten@0.23.0":
|
||||
resolution:
|
||||
{ integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA== }
|
||||
|
||||
agent-base@7.1.1:
|
||||
resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==}
|
||||
engines: {node: '>= 14'}
|
||||
resolution:
|
||||
{ integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
ast-types@0.13.4:
|
||||
resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
|
||||
engines: {node: '>=4'}
|
||||
resolution:
|
||||
{ integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== }
|
||||
engines: { node: ">=4" }
|
||||
|
||||
basic-ftp@5.0.5:
|
||||
resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
resolution:
|
||||
{ integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg== }
|
||||
engines: { node: ">=10.0.0" }
|
||||
|
||||
cors-anywhere@0.4.4:
|
||||
resolution: {integrity: sha512-8OBFwnzMgR4mNrAeAyOLB2EruS2z7u02of2bOu7i9kKYlZG+niS7CTHLPgEXKWW2NAOJWRry9RRCaL9lJRjNqg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
resolution:
|
||||
{ integrity: sha512-8OBFwnzMgR4mNrAeAyOLB2EruS2z7u02of2bOu7i9kKYlZG+niS7CTHLPgEXKWW2NAOJWRry9RRCaL9lJRjNqg== }
|
||||
engines: { node: ">=0.10.0" }
|
||||
|
||||
data-uri-to-buffer@6.0.2:
|
||||
resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
|
||||
engines: {node: '>= 14'}
|
||||
resolution:
|
||||
{ integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
debug@4.3.7:
|
||||
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
resolution:
|
||||
{ integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== }
|
||||
engines: { node: ">=6.0" }
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
supports-color: "*"
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
degenerator@5.0.1:
|
||||
resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
|
||||
engines: {node: '>= 14'}
|
||||
resolution:
|
||||
{ integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
escodegen@2.1.0:
|
||||
resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==}
|
||||
engines: {node: '>=6.0'}
|
||||
resolution:
|
||||
{ integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== }
|
||||
engines: { node: ">=6.0" }
|
||||
hasBin: true
|
||||
|
||||
esprima@4.0.1:
|
||||
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||
engines: {node: '>=4'}
|
||||
resolution:
|
||||
{ integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== }
|
||||
engines: { node: ">=4" }
|
||||
hasBin: true
|
||||
|
||||
estraverse@5.3.0:
|
||||
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
|
||||
engines: {node: '>=4.0'}
|
||||
resolution:
|
||||
{ integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== }
|
||||
engines: { node: ">=4.0" }
|
||||
|
||||
esutils@2.0.3:
|
||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
resolution:
|
||||
{ integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== }
|
||||
engines: { node: ">=0.10.0" }
|
||||
|
||||
eventemitter3@1.2.0:
|
||||
resolution: {integrity: sha512-DOFqA1MF46fmZl2xtzXR3MPCRsXqgoFqdXcrCVYM3JNnfUeHTm/fh/v/iU7gBFpwkuBmoJPAm5GuhdDfSEJMJA==}
|
||||
resolution:
|
||||
{ integrity: sha512-DOFqA1MF46fmZl2xtzXR3MPCRsXqgoFqdXcrCVYM3JNnfUeHTm/fh/v/iU7gBFpwkuBmoJPAm5GuhdDfSEJMJA== }
|
||||
|
||||
fs-extra@11.2.0:
|
||||
resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
|
||||
engines: {node: '>=14.14'}
|
||||
resolution:
|
||||
{ integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== }
|
||||
engines: { node: ">=14.14" }
|
||||
|
||||
get-uri@6.0.3:
|
||||
resolution: {integrity: sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==}
|
||||
engines: {node: '>= 14'}
|
||||
resolution:
|
||||
{ integrity: sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
resolution:
|
||||
{ integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== }
|
||||
|
||||
http-proxy-agent@7.0.2:
|
||||
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
|
||||
engines: {node: '>= 14'}
|
||||
resolution:
|
||||
{ integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
http-proxy@1.11.1:
|
||||
resolution: {integrity: sha512-qz7jZarkVG3G6GMq+4VRJPSN4NkIjL4VMTNhKGd8jc25BumeJjWWvnY3A7OkCGa8W1TTxbaK3dcE0ijFalITVA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
resolution:
|
||||
{ integrity: sha512-qz7jZarkVG3G6GMq+4VRJPSN4NkIjL4VMTNhKGd8jc25BumeJjWWvnY3A7OkCGa8W1TTxbaK3dcE0ijFalITVA== }
|
||||
engines: { node: ">=0.10.0" }
|
||||
|
||||
https-proxy-agent@7.0.5:
|
||||
resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==}
|
||||
engines: {node: '>= 14'}
|
||||
resolution:
|
||||
{ integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
ip-address@9.0.5:
|
||||
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
|
||||
engines: {node: '>= 12'}
|
||||
resolution:
|
||||
{ integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== }
|
||||
engines: { node: ">= 12" }
|
||||
|
||||
jsbn@1.1.0:
|
||||
resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==}
|
||||
resolution:
|
||||
{ integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== }
|
||||
|
||||
jsonfile@6.1.0:
|
||||
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
|
||||
resolution:
|
||||
{ integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== }
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
resolution:
|
||||
{ integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== }
|
||||
|
||||
netmask@2.0.2:
|
||||
resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
resolution:
|
||||
{ integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== }
|
||||
engines: { node: ">= 0.4.0" }
|
||||
|
||||
pac-proxy-agent@7.0.2:
|
||||
resolution: {integrity: sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==}
|
||||
engines: {node: '>= 14'}
|
||||
resolution:
|
||||
{ integrity: sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
pac-resolver@7.0.1:
|
||||
resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==}
|
||||
engines: {node: '>= 14'}
|
||||
resolution:
|
||||
{ integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
proxy-from-env@0.0.1:
|
||||
resolution: {integrity: sha512-B9Hnta3CATuMS0q6kt5hEezOPM+V3dgaRewkFtFoaRQYTVNsHqUvFXmndH06z3QO1ZdDnRELv5vfY6zAj/gG7A==}
|
||||
resolution:
|
||||
{ integrity: sha512-B9Hnta3CATuMS0q6kt5hEezOPM+V3dgaRewkFtFoaRQYTVNsHqUvFXmndH06z3QO1ZdDnRELv5vfY6zAj/gG7A== }
|
||||
|
||||
requires-port@0.0.1:
|
||||
resolution: {integrity: sha512-AzPDCliPoWDSvEVYRQmpzuPhGGEnPrQz9YiOEvn+UdB9ixBpw+4IOZWtwctmpzySLZTy7ynpn47V14H4yaowtA==}
|
||||
resolution:
|
||||
{ integrity: sha512-AzPDCliPoWDSvEVYRQmpzuPhGGEnPrQz9YiOEvn+UdB9ixBpw+4IOZWtwctmpzySLZTy7ynpn47V14H4yaowtA== }
|
||||
|
||||
smart-buffer@4.2.0:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||
resolution:
|
||||
{ integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== }
|
||||
engines: { node: ">= 6.0.0", npm: ">= 3.0.0" }
|
||||
|
||||
socks-proxy-agent@8.0.4:
|
||||
resolution: {integrity: sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==}
|
||||
engines: {node: '>= 14'}
|
||||
resolution:
|
||||
{ integrity: sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw== }
|
||||
engines: { node: ">= 14" }
|
||||
|
||||
socks@2.8.3:
|
||||
resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==}
|
||||
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
|
||||
resolution:
|
||||
{ integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== }
|
||||
engines: { node: ">= 10.0.0", npm: ">= 3.0.0" }
|
||||
|
||||
source-map@0.6.1:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
resolution:
|
||||
{ integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== }
|
||||
engines: { node: ">=0.10.0" }
|
||||
|
||||
sprintf-js@1.1.3:
|
||||
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
|
||||
resolution:
|
||||
{ integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== }
|
||||
|
||||
tslib@2.7.0:
|
||||
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
|
||||
resolution:
|
||||
{ integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== }
|
||||
|
||||
universalify@2.0.1:
|
||||
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
resolution:
|
||||
{ integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== }
|
||||
engines: { node: ">= 10.0.0" }
|
||||
|
||||
snapshots:
|
||||
|
||||
'@tootallnate/quickjs-emscripten@0.23.0': {}
|
||||
"@tootallnate/quickjs-emscripten@0.23.0": {}
|
||||
|
||||
agent-base@7.1.1:
|
||||
dependencies:
|
||||
@ -258,7 +290,7 @@ snapshots:
|
||||
|
||||
pac-proxy-agent@7.0.2:
|
||||
dependencies:
|
||||
'@tootallnate/quickjs-emscripten': 0.23.0
|
||||
"@tootallnate/quickjs-emscripten": 0.23.0
|
||||
agent-base: 7.1.1
|
||||
debug: 4.3.7
|
||||
get-uri: 6.0.3
|
||||
|
@ -1,21 +1,20 @@
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
import { AbstractRelay } from "nostr-tools/abstract-relay";
|
||||
import _throttle from "lodash.throttle";
|
||||
import { EventStore } from "applesauce-core";
|
||||
import debug, { Debugger } from "debug";
|
||||
|
||||
import EventStore from "./event-store";
|
||||
import PersistentSubscription from "./persistent-subscription";
|
||||
import Process from "./process";
|
||||
import BracketsX from "../components/icons/brackets-x";
|
||||
import processManager from "../services/process-manager";
|
||||
import createDefer, { Deferred } from "./deferred";
|
||||
import { eventStore } from "../services/event-store";
|
||||
|
||||
/** This class is used to batch requests for single events from a relay */
|
||||
export default class BatchEventLoader {
|
||||
events = new EventStore();
|
||||
relay: AbstractRelay;
|
||||
process: Process;
|
||||
store: EventStore;
|
||||
|
||||
subscription: PersistentSubscription;
|
||||
|
||||
@ -27,8 +26,9 @@ export default class BatchEventLoader {
|
||||
|
||||
log: Debugger;
|
||||
|
||||
constructor(relay: AbstractRelay, log?: Debugger) {
|
||||
constructor(store: EventStore, relay: AbstractRelay, log?: Debugger) {
|
||||
this.relay = relay;
|
||||
this.store = store;
|
||||
this.log = log || debug("BatchEventLoader");
|
||||
this.process = new Process("BatchEventLoader", this, [relay]);
|
||||
this.process.icon = BracketsX;
|
||||
@ -41,15 +41,15 @@ export default class BatchEventLoader {
|
||||
this.process.addChild(this.subscription.process);
|
||||
}
|
||||
|
||||
requestEvent(id: string): Promise<NostrEvent | null> {
|
||||
const event = this.events.getEvent(id);
|
||||
requestEvent(uid: string): Promise<NostrEvent | null> {
|
||||
const event = this.store.getEvent(uid);
|
||||
|
||||
if (!event) {
|
||||
if (this.pending.has(id)) return this.pending.get(id)!;
|
||||
if (this.next.has(id)) return this.next.get(id)!;
|
||||
if (this.pending.has(uid)) return this.pending.get(uid)!;
|
||||
if (this.next.has(uid)) return this.next.get(uid)!;
|
||||
|
||||
const defer = createDefer<NostrEvent | null>();
|
||||
this.next.set(id, defer);
|
||||
this.next.set(uid, defer);
|
||||
|
||||
// request subscription update
|
||||
this.start();
|
||||
@ -73,11 +73,10 @@ export default class BatchEventLoader {
|
||||
);
|
||||
|
||||
private handleEvent(event: NostrEvent) {
|
||||
event = eventStore.add(event, this.relay.url);
|
||||
event = this.store.add(event, this.relay.url);
|
||||
|
||||
const key = event.id;
|
||||
|
||||
this.events.addEvent(event);
|
||||
this.pending.get(key)?.resolve(event);
|
||||
this.pending.delete(key);
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ import { NostrEvent } from "nostr-tools";
|
||||
import { AbstractRelay } from "nostr-tools/abstract-relay";
|
||||
import _throttle from "lodash.throttle";
|
||||
import debug, { Debugger } from "debug";
|
||||
import { getEventUID } from "nostr-idb";
|
||||
import { EventStore } from "applesauce-core";
|
||||
import { getEventUID } from "applesauce-core/helpers";
|
||||
|
||||
import PersistentSubscription from "./persistent-subscription";
|
||||
import Process from "./process";
|
||||
@ -11,10 +12,10 @@ import createDefer, { Deferred } from "./deferred";
|
||||
import Dataflow04 from "../components/icons/dataflow-04";
|
||||
import SuperMap from "./super-map";
|
||||
import Subject from "./subject";
|
||||
import { eventStore } from "../services/event-store";
|
||||
|
||||
/** Batches requests for events with #d tags from a single relay */
|
||||
export default class BatchIdentifierLoader {
|
||||
store: EventStore;
|
||||
kinds: number[];
|
||||
relay: AbstractRelay;
|
||||
process: Process;
|
||||
@ -36,7 +37,8 @@ export default class BatchIdentifierLoader {
|
||||
|
||||
log: Debugger;
|
||||
|
||||
constructor(relay: AbstractRelay, kinds: number[], log?: Debugger) {
|
||||
constructor(store: EventStore, relay: AbstractRelay, kinds: number[], log?: Debugger) {
|
||||
this.store = store;
|
||||
this.relay = relay;
|
||||
this.kinds = kinds;
|
||||
this.log = log || debug("BatchIdentifierLoader");
|
||||
@ -82,7 +84,7 @@ export default class BatchIdentifierLoader {
|
||||
);
|
||||
|
||||
handleEvent(event: NostrEvent) {
|
||||
event = eventStore.add(event, this.relay.url);
|
||||
event = this.store.add(event, this.relay.url);
|
||||
|
||||
// add event to cache
|
||||
for (const tag of event.tags) {
|
||||
|
@ -2,15 +2,14 @@ import { Filter, NostrEvent } from "nostr-tools";
|
||||
import { AbstractRelay } from "nostr-tools/abstract-relay";
|
||||
import _throttle from "lodash.throttle";
|
||||
import debug, { Debugger } from "debug";
|
||||
import { EventStore } from "applesauce-core";
|
||||
|
||||
import EventStore from "./event-store";
|
||||
import { getEventUID } from "../helpers/nostr/event";
|
||||
import PersistentSubscription from "./persistent-subscription";
|
||||
import Process from "./process";
|
||||
import BracketsX from "../components/icons/brackets-x";
|
||||
import processManager from "../services/process-manager";
|
||||
import createDefer, { Deferred } from "./deferred";
|
||||
import { eventStore } from "../services/event-store";
|
||||
|
||||
export function createCoordinate(kind: number, pubkey: string, d?: string) {
|
||||
return `${kind}:${pubkey}${d ? ":" + d : ""}`;
|
||||
@ -18,7 +17,7 @@ export function createCoordinate(kind: number, pubkey: string, d?: string) {
|
||||
|
||||
/** This class is used to batch requests by kind and pubkey to a single relay */
|
||||
export default class BatchKindPubkeyLoader {
|
||||
events = new EventStore();
|
||||
store: EventStore;
|
||||
relay: AbstractRelay;
|
||||
process: Process;
|
||||
|
||||
@ -32,7 +31,8 @@ export default class BatchKindPubkeyLoader {
|
||||
|
||||
log: Debugger;
|
||||
|
||||
constructor(relay: AbstractRelay, log?: Debugger) {
|
||||
constructor(store: EventStore, relay: AbstractRelay, log?: Debugger) {
|
||||
this.store = store;
|
||||
this.relay = relay;
|
||||
this.log = log || debug("BatchKindPubkeyLoader");
|
||||
this.process = new Process("BatchKindPubkeyLoader", this, [relay]);
|
||||
@ -48,7 +48,7 @@ export default class BatchKindPubkeyLoader {
|
||||
|
||||
requestEvent(kind: number, pubkey: string, d?: string): Promise<NostrEvent | null> {
|
||||
const key = createCoordinate(kind, pubkey, d);
|
||||
const event = this.events.getEvent(key);
|
||||
const event = this.store.getEvent(key);
|
||||
|
||||
if (!event) {
|
||||
if (this.pending.has(key)) return this.pending.get(key)!;
|
||||
@ -79,16 +79,16 @@ export default class BatchKindPubkeyLoader {
|
||||
);
|
||||
|
||||
private handleEvent(event: NostrEvent) {
|
||||
event = eventStore.add(event, this.relay.url);
|
||||
event = this.store.add(event, this.relay.url);
|
||||
|
||||
const key = getEventUID(event);
|
||||
|
||||
const defer = this.pending.get(key);
|
||||
if (defer) this.pending.delete(key);
|
||||
|
||||
const current = this.events.getEvent(key);
|
||||
const current = this.store.getEvent(key);
|
||||
if (!current || event.created_at > current.created_at) {
|
||||
this.events.addEvent(event);
|
||||
this.store.add(event);
|
||||
|
||||
if (defer) defer.resolve(event);
|
||||
} else if (defer) defer.resolve(null);
|
||||
|
@ -19,7 +19,7 @@ import { eventStore } from "../services/event-store";
|
||||
|
||||
const DEFAULT_CHUNK_SIZE = 100;
|
||||
|
||||
export type EventFilter = (event: NostrEvent, store: EventStore) => boolean;
|
||||
export type EventFilter = (event: NostrEvent) => boolean;
|
||||
|
||||
export default class ChunkedRequest {
|
||||
id: string;
|
||||
|
@ -6,7 +6,7 @@ import ControlledObservable from "./controlled-observable";
|
||||
import SuperMap from "./super-map";
|
||||
import deleteEventService from "../services/delete-events";
|
||||
|
||||
export type EventFilter = (event: NostrEvent, store: EventStore) => boolean;
|
||||
export type EventFilter = (event: NostrEvent) => boolean;
|
||||
|
||||
/** a class used to store and sort events */
|
||||
export default class EventStore {
|
||||
@ -96,7 +96,7 @@ export default class EventStore {
|
||||
while (true) {
|
||||
const event = events.shift();
|
||||
if (!event) return;
|
||||
if (filter && !filter(event, this)) continue;
|
||||
if (filter && !filter(event)) continue;
|
||||
if (i === nth) return event;
|
||||
i++;
|
||||
}
|
||||
@ -108,7 +108,7 @@ export default class EventStore {
|
||||
while (true) {
|
||||
const event = events.pop();
|
||||
if (!event) return;
|
||||
if (filter && !filter(event, this)) continue;
|
||||
if (filter && !filter(event)) continue;
|
||||
if (i === nth) return event;
|
||||
i++;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { nanoid } from "nanoid";
|
||||
import { SimpleRelay, Subscription, SubscriptionOptions } from "nostr-idb";
|
||||
import { Filter, NostrEvent, matchFilters } from "nostr-tools";
|
||||
import { EventStore } from "applesauce-core";
|
||||
|
||||
import EventStore from "./event-store";
|
||||
import { logger } from "../helpers/debug";
|
||||
|
||||
export default class MemoryRelay implements SimpleRelay {
|
||||
@ -10,62 +10,39 @@ export default class MemoryRelay implements SimpleRelay {
|
||||
connected = true;
|
||||
url = ":memory:";
|
||||
|
||||
events = new EventStore();
|
||||
store = new EventStore();
|
||||
subscriptions = new Map<string, Subscription>();
|
||||
|
||||
constructor() {
|
||||
this.events.onEvent.subscribe((event) => {
|
||||
for (const [id, sub] of this.subscriptions) {
|
||||
if (sub.onevent && matchFilters(sub.filters, event)) sub.onevent(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async connect() {}
|
||||
close(): void {}
|
||||
|
||||
async publish(event: NostrEvent) {
|
||||
this.events.addEvent(event);
|
||||
return "accepted";
|
||||
}
|
||||
|
||||
private async executeSubscription(sub: Subscription) {
|
||||
const limit = sub.filters.reduce((v, f) => (f.limit ? Math.min(v, f.limit) : v), Infinity);
|
||||
|
||||
let count = 0;
|
||||
if (sub.onevent) {
|
||||
const events = this.events.getSortedEvents();
|
||||
for (const event of events) {
|
||||
if (matchFilters(sub.filters, event)) {
|
||||
sub.onevent(event);
|
||||
count++;
|
||||
}
|
||||
if (count === limit) break;
|
||||
}
|
||||
}
|
||||
|
||||
this.log(`Ran ${sub.id} and got ${count} events`, sub.filters);
|
||||
|
||||
if (sub.oneose) sub.oneose();
|
||||
this.store.add(event);
|
||||
return "";
|
||||
}
|
||||
|
||||
subscribe(filters: Filter[], options: SubscriptionOptions) {
|
||||
let stream: ZenObservable.Subscription | undefined = undefined;
|
||||
const sub: Subscription = {
|
||||
id: nanoid(8),
|
||||
filters,
|
||||
...options,
|
||||
fire: () => {
|
||||
this.executeSubscription(sub);
|
||||
if (stream) stream.unsubscribe();
|
||||
stream = this.store.stream(filters).subscribe((event) => sub.onevent?.(event));
|
||||
if (sub.oneose) sub.oneose();
|
||||
},
|
||||
close: () => {
|
||||
this.subscriptions.delete(sub.id);
|
||||
if (stream) stream.unsubscribe();
|
||||
},
|
||||
};
|
||||
|
||||
this.subscriptions.set(sub.id, sub);
|
||||
|
||||
setTimeout(() => {
|
||||
this.executeSubscription(sub);
|
||||
sub.fire();
|
||||
}, 0);
|
||||
|
||||
return sub;
|
||||
}
|
||||
|
||||
@ -75,10 +52,6 @@ export default class MemoryRelay implements SimpleRelay {
|
||||
id?: string | null;
|
||||
},
|
||||
) {
|
||||
let count = 0;
|
||||
for (const [id, event] of this.events.events) {
|
||||
if (matchFilters(filters, event)) count++;
|
||||
}
|
||||
return count;
|
||||
return this.store.database.getForFilters(filters).size;
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ import { Debugger } from "debug";
|
||||
import { Filter, NostrEvent } from "nostr-tools";
|
||||
import { AbstractRelay } from "nostr-tools/abstract-relay";
|
||||
import _throttle from "lodash.throttle";
|
||||
import Observable from "zen-observable";
|
||||
|
||||
import MultiSubscription from "./multi-subscription";
|
||||
import { PersistentSubject } from "./subject";
|
||||
import { logger } from "../helpers/debug";
|
||||
import EventStore from "./event-store";
|
||||
import { isReplaceable } from "../helpers/nostr/event";
|
||||
import replaceableEventsService from "../services/replaceable-events";
|
||||
import { mergeFilter, isFilterEqual } from "../helpers/nostr/filter";
|
||||
@ -18,19 +18,17 @@ import relayPoolService from "../services/relay-pool";
|
||||
import Process from "./process";
|
||||
import AlignHorizontalCentre02 from "../components/icons/align-horizontal-centre-02";
|
||||
import processManager from "../services/process-manager";
|
||||
import { eventStore } from "../services/event-store";
|
||||
import { eventStore, queryStore } from "../services/event-store";
|
||||
|
||||
const BLOCK_SIZE = 100;
|
||||
|
||||
export type EventFilter = (event: NostrEvent, store: EventStore) => boolean;
|
||||
export type EventFilter = (event: NostrEvent) => boolean;
|
||||
|
||||
export default class TimelineLoader {
|
||||
cursor = dayjs().unix();
|
||||
filters: Filter[] = [];
|
||||
relays: AbstractRelay[] = [];
|
||||
|
||||
events: EventStore;
|
||||
timeline = new PersistentSubject<NostrEvent[]>([]);
|
||||
loading = new PersistentSubject(false);
|
||||
complete = new PersistentSubject(false);
|
||||
|
||||
@ -39,6 +37,8 @@ export default class TimelineLoader {
|
||||
useCache = true;
|
||||
|
||||
name: string;
|
||||
/** @deprecated */
|
||||
timeline?: Observable<NostrEvent[]>;
|
||||
process: Process;
|
||||
private log: Debugger;
|
||||
private subscription: MultiSubscription;
|
||||
@ -53,28 +53,22 @@ export default class TimelineLoader {
|
||||
this.process.icon = AlignHorizontalCentre02;
|
||||
|
||||
this.log = logger.extend("TimelineLoader:" + name);
|
||||
this.events = new EventStore(name);
|
||||
this.events.connect(replaceableEventsService.events, false);
|
||||
|
||||
this.subscription = new MultiSubscription(name);
|
||||
this.subscription.onEvent.subscribe(this.handleEvent.bind(this));
|
||||
this.subscription.onCacheEvent.subscribe((event) => this.handleEvent(event, true));
|
||||
this.process.addChild(this.subscription.process);
|
||||
|
||||
// update the timeline when there are new events
|
||||
this.events.onEvent.subscribe(this.throttleUpdateTimeline.bind(this));
|
||||
this.events.onDelete.subscribe(this.throttleUpdateTimeline.bind(this));
|
||||
this.events.onClear.subscribe(this.throttleUpdateTimeline.bind(this));
|
||||
|
||||
processManager.registerProcess(this.process);
|
||||
}
|
||||
|
||||
private throttleUpdateTimeline = _throttle(this.updateTimeline, 10);
|
||||
private updateTimeline() {
|
||||
this.timeline = queryStore.timeline(this.filters);
|
||||
|
||||
if (this.eventFilter) {
|
||||
const filter = this.eventFilter;
|
||||
this.timeline.next(this.events.getSortedEvents().filter((e) => filter(e, this.events)));
|
||||
} else this.timeline.next(this.events.getSortedEvents());
|
||||
// add filter
|
||||
this.timeline = this.timeline.map((events) => events.filter((e) => this.eventFilter!(e)));
|
||||
}
|
||||
}
|
||||
|
||||
private seenInCache = new Set<string>();
|
||||
@ -82,9 +76,9 @@ export default class TimelineLoader {
|
||||
// if this is a replaceable event, mirror it over to the replaceable event service
|
||||
if (isReplaceable(event.kind)) replaceableEventsService.handleEvent(event);
|
||||
|
||||
eventStore.add(event);
|
||||
event = eventStore.add(event);
|
||||
|
||||
this.events.addEvent(event);
|
||||
// publish to local relay
|
||||
if (!fromCache && this.useCache && localRelay && !this.seenInCache.has(event.id)) localRelay.publish(event);
|
||||
|
||||
if (fromCache) this.seenInCache.add(event.id);
|
||||
@ -98,13 +92,12 @@ export default class TimelineLoader {
|
||||
private connectToChunkLoader(loader: ChunkedRequest) {
|
||||
this.process.addChild(loader.process);
|
||||
|
||||
this.events.connect(loader.events);
|
||||
const subs = this.chunkLoaderSubs.get(loader);
|
||||
subs.push(loader.onChunkFinish.subscribe(this.handleChunkFinished.bind(this)));
|
||||
}
|
||||
private disconnectFromChunkLoader(loader: ChunkedRequest) {
|
||||
loader.destroy();
|
||||
this.events.disconnect(loader.events);
|
||||
|
||||
const subs = this.chunkLoaderSubs.get(loader);
|
||||
for (const sub of subs) sub.unsubscribe();
|
||||
this.chunkLoaderSubs.delete(loader);
|
||||
@ -144,6 +137,9 @@ export default class TimelineLoader {
|
||||
|
||||
// update the live subscription query map and add limit
|
||||
this.subscription.setFilters(mergeFilter(filters, { limit: BLOCK_SIZE / 2 }));
|
||||
|
||||
// update timeline
|
||||
this.updateTimeline();
|
||||
}
|
||||
|
||||
setRelays(relays: Iterable<string | URL | AbstractRelay>) {
|
||||
@ -256,8 +252,7 @@ export default class TimelineLoader {
|
||||
}
|
||||
|
||||
forgetEvents() {
|
||||
this.events.clear();
|
||||
this.timeline.next([]);
|
||||
this.timeline = undefined;
|
||||
this.subscription.forgetEvents();
|
||||
}
|
||||
reset() {
|
||||
@ -280,7 +275,6 @@ export default class TimelineLoader {
|
||||
|
||||
this.subscription.destroy();
|
||||
|
||||
this.events.cleanup();
|
||||
this.process.remove();
|
||||
processManager.unregisterProcess(this.process);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -26,7 +26,6 @@ import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useSingleEvent from "../../hooks/use-single-event";
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { Kind0ParsedContent, getDisplayName, parseMetadataContent } from "../../helpers/nostr/user-metadata";
|
||||
import { MetadataAvatar } from "../user/user-avatar";
|
||||
import HoverLinkOverlay from "../hover-link-overlay";
|
||||
@ -96,15 +95,18 @@ export default function AppHandlerModal({
|
||||
const kind = event?.kind ?? getKindFromDecoded(decoded);
|
||||
const alt = event?.tags.find((t) => t[0] === "alt")?.[1];
|
||||
const address = encodeDecodeResult(decoded);
|
||||
const timeline = useTimelineLoader(
|
||||
const eventFilter = useCallback((event: NostrEvent) => {
|
||||
return event.content.length > 0;
|
||||
}, []);
|
||||
const { loader, timeline: apps } = useTimelineLoader(
|
||||
`${kind}-apps`,
|
||||
readRelays,
|
||||
kind ? { kinds: [kinds.Handlerinformation], "#k": [String(kind)] } : { kinds: [kinds.Handlerinformation] },
|
||||
{ eventFilter },
|
||||
);
|
||||
|
||||
const autofocus = useBreakpointValue({ base: false, lg: true });
|
||||
const [search, setSearch] = useState("");
|
||||
const apps = useSubject(timeline.timeline).filter((a) => a.content.length > 0);
|
||||
|
||||
const filteredApps = apps.filter((app) => {
|
||||
if (search.length > 1) {
|
||||
@ -117,7 +119,7 @@ export default function AppHandlerModal({
|
||||
return false;
|
||||
} else return true;
|
||||
});
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="xl">
|
||||
|
@ -5,7 +5,6 @@ import { Link as RouterLink } from "react-router-dom";
|
||||
import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import TimelineActionAndStatus from "../../timeline/timeline-action-and-status";
|
||||
import IntersectionObserverProvider from "../../../providers/local/intersection-observer";
|
||||
import Timestamp from "../../timestamp";
|
||||
@ -98,18 +97,17 @@ export default function GhostTimeline({ ...props }: Omit<FlexProps, "children">)
|
||||
const account = useCurrentAccount()!;
|
||||
const readRelays = useReadRelays();
|
||||
|
||||
const timeline = useTimelineLoader(`${account.pubkey}-ghost`, readRelays, { authors: [account.pubkey] });
|
||||
const events = useSubject(timeline.timeline);
|
||||
const { loader, timeline: events } = useTimelineLoader(`${account.pubkey}-ghost`, readRelays, {
|
||||
authors: [account.pubkey],
|
||||
});
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex direction="column" overflow="auto" {...props}>
|
||||
{events.map((event) => (
|
||||
<TimelineItem key={event.id} event={event} />
|
||||
))}
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
{events?.map((event) => <TimelineItem key={event.id} event={event} />)}
|
||||
<TimelineActionAndStatus timeline={loader} />
|
||||
</Flex>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
|
@ -26,7 +26,7 @@ export default function RelayListButton({ relay, ...props }: { relay: string } &
|
||||
const account = useCurrentAccount();
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
|
||||
const sets = useUserRelaySets(account?.pubkey);
|
||||
const sets = useUserRelaySets(account?.pubkey) ?? [];
|
||||
|
||||
const inSets = sets.filter((set) => set.tags.some((t) => isRTag(t) && t[1] === relay));
|
||||
|
||||
|
@ -110,7 +110,7 @@ export default function RelayManagementDrawer({ isOpen, onClose, ...props }: Omi
|
||||
|
||||
const save = useDisclosure();
|
||||
const [selected, setSelected] = useState<string>();
|
||||
const relaySets = useUserRelaySets(account?.pubkey);
|
||||
const relaySets = useUserRelaySets(account?.pubkey) ?? [];
|
||||
|
||||
const changeSet = (cord: string) => {
|
||||
setSelected(cord);
|
||||
|
@ -24,8 +24,8 @@ export function SaveRelaySetForm({
|
||||
const publish = usePublishEvent();
|
||||
const { register, formState, handleSubmit } = useForm({
|
||||
defaultValues: {
|
||||
name: relaySet ? getListName(relaySet) ?? "" : "",
|
||||
description: relaySet ? getListDescription(relaySet) ?? "" : "",
|
||||
name: relaySet ? (getListName(relaySet) ?? "") : "",
|
||||
description: relaySet ? (getListDescription(relaySet) ?? "") : "",
|
||||
},
|
||||
mode: "all",
|
||||
resetOptions: { keepDirtyValues: true },
|
||||
|
@ -4,8 +4,6 @@ import { NostrEvent } from "nostr-tools";
|
||||
import { getEventUID } from "nostr-idb";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import TimelineLoader from "../../../classes/timeline-loader";
|
||||
import useNumberCache from "../../../hooks/timeline/use-number-cache";
|
||||
import useCacheEntryHeight from "../../../hooks/timeline/use-cache-entry-height";
|
||||
import { useTimelineDates } from "../../../hooks/timeline/use-timeline-dates";
|
||||
@ -15,8 +13,7 @@ import TimelineItem from "./timeline-item";
|
||||
const INITIAL_NOTES = 10;
|
||||
const NOTE_BUFFER = 5;
|
||||
|
||||
function GenericNoteTimeline({ timeline }: { timeline: TimelineLoader }) {
|
||||
const events = useSubject(timeline.timeline);
|
||||
function GenericNoteTimeline({ timeline }: { timeline: NostrEvent[] }) {
|
||||
const [latest, setLatest] = useState(() => dayjs().unix());
|
||||
|
||||
const cacheKey = useTimelineLocationCacheKey();
|
||||
@ -28,7 +25,7 @@ function GenericNoteTimeline({ timeline }: { timeline: TimelineLoader }) {
|
||||
|
||||
const newNotes: NostrEvent[] = [];
|
||||
const notes: NostrEvent[] = [];
|
||||
for (const note of events) {
|
||||
for (const note of timeline) {
|
||||
if (note.created_at > latest) newNotes.push(note);
|
||||
else if (note.created_at >= dates.cursor) notes.push(note);
|
||||
}
|
||||
@ -38,7 +35,7 @@ function GenericNoteTimeline({ timeline }: { timeline: TimelineLoader }) {
|
||||
{newNotes.length > 0 && (
|
||||
<Box h="0" overflow="visible" w="full" zIndex={100} display="flex" position="relative">
|
||||
<Button
|
||||
onClick={() => setLatest(timeline.timeline.value[0].created_at + 10)}
|
||||
onClick={() => setLatest(newNotes[newNotes.length - 1].created_at + 10)}
|
||||
colorScheme="primary"
|
||||
size="lg"
|
||||
mx="auto"
|
||||
|
@ -29,11 +29,15 @@ export function useTimelinePageEventFilter() {
|
||||
export type TimelineViewType = "timeline" | "images" | "health";
|
||||
|
||||
export default function TimelinePage({
|
||||
loader,
|
||||
timeline,
|
||||
header,
|
||||
...props
|
||||
}: { timeline: TimelineLoader; header?: React.ReactNode } & Omit<FlexProps, "children" | "direction" | "gap">) {
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
}: { loader: TimelineLoader; timeline: NostrEvent[]; header?: React.ReactNode } & Omit<
|
||||
FlexProps,
|
||||
"children" | "direction" | "gap"
|
||||
>) {
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
const viewParam = useRouteSearchValue("view", "timeline");
|
||||
const mode = (viewParam.value as TimelineViewType) ?? "timeline";
|
||||
@ -47,7 +51,7 @@ export default function TimelinePage({
|
||||
return <MediaTimeline timeline={timeline} />;
|
||||
|
||||
case "health":
|
||||
return <TimelineHealth timeline={timeline} />;
|
||||
return <TimelineHealth loader={loader} timeline={timeline} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -57,7 +61,7 @@ export default function TimelinePage({
|
||||
<Flex direction="column" gap="2" {...props}>
|
||||
{header}
|
||||
{renderTimeline()}
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
<TimelineActionAndStatus timeline={loader} />
|
||||
</Flex>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
|
@ -2,8 +2,6 @@ import { useMemo } from "react";
|
||||
import { kinds } from "nostr-tools";
|
||||
import { Photo } from "react-photo-album";
|
||||
|
||||
import TimelineLoader from "../../../classes/timeline-loader";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { getMatchLink } from "../../../helpers/regexp";
|
||||
import { LightboxProvider } from "../../lightbox-provider";
|
||||
import { isImageURL } from "../../../helpers/url";
|
||||
@ -36,13 +34,11 @@ function ImageGallery({ images }: { images: PhotoWithEvent[] }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function MediaTimeline({ timeline }: { timeline: TimelineLoader }) {
|
||||
const events = useSubject(timeline.timeline);
|
||||
|
||||
export default function MediaTimeline({ timeline }: { timeline: NostrEvent[] }) {
|
||||
const images = useMemo(() => {
|
||||
var images: PhotoWithEvent[] = [];
|
||||
|
||||
for (const event of events) {
|
||||
for (const event of timeline) {
|
||||
if (event.kind === kinds.Repost || event.kind === kinds.GenericRepost) continue;
|
||||
const urls = event.content.matchAll(getMatchLink());
|
||||
|
||||
@ -53,7 +49,7 @@ export default function MediaTimeline({ timeline }: { timeline: TimelineLoader }
|
||||
}
|
||||
|
||||
return images;
|
||||
}, [events]);
|
||||
}, [timeline]);
|
||||
|
||||
return (
|
||||
<LightboxProvider>
|
||||
|
@ -16,7 +16,6 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
import TimelineLoader from "../../../classes/timeline-loader";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { RelayFavicon } from "../../relay-favicon";
|
||||
import { NoteLink } from "../../note/note-link";
|
||||
@ -24,6 +23,7 @@ import { BroadcastEventIcon } from "../../icons";
|
||||
import Timestamp from "../../timestamp";
|
||||
import { usePublishEvent } from "../../../providers/global/publish-provider";
|
||||
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
|
||||
import { getSeenRelays } from "applesauce-core/helpers";
|
||||
|
||||
function EventRow({
|
||||
event,
|
||||
@ -31,7 +31,6 @@ function EventRow({
|
||||
...props
|
||||
}: { event: NostrEvent; relays: string[] } & Omit<TableRowProps, "children">) {
|
||||
// const sub = useMemo(() => getEventRelays(event.id), [event.id]);
|
||||
const seenRelays = true; //useSubject(sub);
|
||||
const publish = usePublishEvent();
|
||||
|
||||
const ref = useEventIntersectionRef(event);
|
||||
@ -64,7 +63,7 @@ function EventRow({
|
||||
{broadcasting ? <Spinner size="xs" /> : <BroadcastEventIcon />}
|
||||
</Td>
|
||||
{relays.map((relay) => (
|
||||
<Td key={relay} title={relay} p="2" backgroundColor={/*seenRelays.includes(relay)*/ true ? yes : no}>
|
||||
<Td key={relay} title={relay} p="2" backgroundColor={getSeenRelays(event)?.has(relay) ? yes : no}>
|
||||
<RelayFavicon relay={relay} size="2xs" />
|
||||
</Td>
|
||||
))}
|
||||
@ -72,9 +71,8 @@ function EventRow({
|
||||
);
|
||||
}
|
||||
|
||||
export default function TimelineHealth({ timeline }: { timeline: TimelineLoader }) {
|
||||
const events = useSubject(timeline.timeline);
|
||||
const relays = Array.from(Object.keys(timeline.relays));
|
||||
export default function TimelineHealth({ timeline, loader }: { loader: TimelineLoader; timeline: NostrEvent[] }) {
|
||||
const relays = Array.from(Object.keys(loader.relays));
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -102,7 +100,7 @@ export default function TimelineHealth({ timeline }: { timeline: TimelineLoader
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{events.map((event) => (
|
||||
{timeline.map((event) => (
|
||||
<EventRow key={event.id} event={event} relays={relays} />
|
||||
))}
|
||||
</Tbody>
|
||||
|
@ -265,8 +265,8 @@ export function getSortedKinds(events: NostrEvent[]) {
|
||||
.reduce((dir, k) => ({ ...dir, [k.kind]: k.count }), {} as Record<string, number>);
|
||||
}
|
||||
|
||||
export function getTagValue(event: NostrEvent, tag: string){
|
||||
return event.tags.find(t => t[0]===tag && t.length>=2)?.[1]
|
||||
export function getTagValue(event: NostrEvent, tag: string) {
|
||||
return event.tags.find((t) => t[0] === tag && t.length >= 2)?.[1];
|
||||
}
|
||||
|
||||
export { getEventUID };
|
||||
|
@ -1,10 +1,12 @@
|
||||
import dayjs from "dayjs";
|
||||
import { kinds } from "nostr-tools";
|
||||
import { DecodeResult } from "nostr-tools/nip19";
|
||||
import { getPointerFromTag } from "applesauce-core/helpers";
|
||||
|
||||
import { NostrEvent, isRTag } from "../../types/nostr-event";
|
||||
|
||||
export const GOAL_KIND = 9041;
|
||||
/** @deprecated use kinds.ZapGoal */
|
||||
export const GOAL_KIND = kinds.ZapGoal;
|
||||
|
||||
export type ParsedGoal = {
|
||||
event: NostrEvent;
|
||||
|
@ -22,7 +22,7 @@ export function getPageForks(page: NostrEvent) {
|
||||
const addressFork = page.tags.find((t) => t[0] === "a" && t[1] && t[3] === "fork");
|
||||
const eventFork = page.tags.find((t) => t[0] === "a" && t[1] && t[3] === "fork");
|
||||
|
||||
const address = addressFork ? parseCoordinate(addressFork[1], true) ?? undefined : undefined;
|
||||
const address = addressFork ? (parseCoordinate(addressFork[1], true) ?? undefined) : undefined;
|
||||
const event: nip19.EventPointer | undefined = eventFork ? { id: eventFork[1] } : undefined;
|
||||
|
||||
return { event, address };
|
||||
@ -32,7 +32,7 @@ export function getPageDefer(page: NostrEvent) {
|
||||
const addressTag = page.tags.find((t) => t[0] === "a" && t[1] && t[3] === "defer");
|
||||
const eventTag = page.tags.find((t) => t[0] === "a" && t[1] && t[3] === "defer");
|
||||
|
||||
const address = addressTag ? parseCoordinate(addressTag[1], true) ?? undefined : undefined;
|
||||
const address = addressTag ? (parseCoordinate(addressTag[1], true) ?? undefined) : undefined;
|
||||
const event: nip19.EventPointer | undefined = eventTag ? { id: eventTag[1] } : undefined;
|
||||
|
||||
if (event || address) return { event, address };
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { useEffect } from "react";
|
||||
import TimelineLoader from "../../classes/timeline-loader";
|
||||
|
||||
import useMinNumber from "./use-min-number";
|
||||
import { NumberCache } from "./use-number-cache";
|
||||
import useTimelineViewDatesBuffer from "./use-timeline-view-dates-buffer";
|
||||
|
||||
export function useTimelineDates(
|
||||
timeline: { id: string; created_at: number }[] | TimelineLoader,
|
||||
timeline: { id: string; created_at: number }[],
|
||||
cache: NumberCache,
|
||||
buffer = 5,
|
||||
initialRender = 10,
|
||||
@ -13,7 +13,7 @@ export function useTimelineDates(
|
||||
const dates = useTimelineViewDatesBuffer(
|
||||
cache.key,
|
||||
{ min: cache.get("min"), max: cache.get("max") },
|
||||
Array.isArray(timeline) ? timeline : timeline.timeline.value,
|
||||
timeline,
|
||||
buffer,
|
||||
initialRender,
|
||||
);
|
||||
|
@ -2,13 +2,12 @@ import { kinds } from "nostr-tools";
|
||||
|
||||
import useTimelineLoader from "./use-timeline-loader";
|
||||
import { recommendedReadRelays } from "../services/client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
|
||||
export default function useNip05Providers() {
|
||||
const timeline = useTimelineLoader("nip05-providers", recommendedReadRelays, {
|
||||
const { timeline } = useTimelineLoader("nip05-providers", recommendedReadRelays, {
|
||||
kinds: [kinds.Handlerinformation],
|
||||
"#k": [String(kinds.NostrConnect)],
|
||||
});
|
||||
|
||||
return useSubject(timeline.timeline);
|
||||
return timeline;
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { kinds as eventKinds } from "nostr-tools";
|
||||
|
||||
import useSubject from "./use-subject";
|
||||
import useSingleEvent from "./use-single-event";
|
||||
import singleEventService from "../services/single-event";
|
||||
import useTimelineLoader from "./use-timeline-loader";
|
||||
@ -21,7 +20,7 @@ export default function useThreadTimelineLoader(
|
||||
|
||||
const kindArr = kinds ? (kinds.length > 0 ? kinds : undefined) : [eventKinds.ShortTextNote];
|
||||
const timelineId = `${rootPointer?.id}-thread`;
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: events } = useTimelineLoader(
|
||||
timelineId,
|
||||
readRelays,
|
||||
rootPointer
|
||||
@ -38,8 +37,6 @@ export default function useThreadTimelineLoader(
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const events = useSubject(timeline.timeline);
|
||||
|
||||
// mirror all events to single event cache
|
||||
useEffect(() => {
|
||||
for (const e of events) singleEventService.handleEvent(e);
|
||||
@ -53,5 +50,5 @@ export default function useThreadTimelineLoader(
|
||||
return arr;
|
||||
}, [events, rootEvent, focusedEvent]);
|
||||
|
||||
return { events: allEvents, rootEvent, rootPointer, timeline };
|
||||
return { events: allEvents, rootEvent, rootPointer, timeline: loader };
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { NostrEvent } from "nostr-tools";
|
||||
|
||||
import TimelineLoader from "../classes/timeline-loader";
|
||||
import { useCachedIntersectionMapCallback } from "../providers/local/intersection-observer";
|
||||
import { eventStore } from "../services/event-store";
|
||||
|
||||
export function useTimelineCurserIntersectionCallback(timeline: TimelineLoader) {
|
||||
// if the cursor is set too far ahead and the last block did not overlap with the cursor
|
||||
@ -17,7 +18,7 @@ export function useTimelineCurserIntersectionCallback(timeline: TimelineLoader)
|
||||
let oldestEvent: NostrEvent | undefined = undefined;
|
||||
for (const [id, entry] of map) {
|
||||
if (!entry.isIntersecting) continue;
|
||||
const event = timeline.events.getEvent(id);
|
||||
const event = eventStore.getEvent(id);
|
||||
if (!event) continue;
|
||||
if (!oldestEvent || event.created_at < oldestEvent.created_at) {
|
||||
oldestEvent = event;
|
||||
|
@ -1,17 +1,15 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { usePrevious, useUnmount } from "react-use";
|
||||
import { Filter, NostrEvent } from "nostr-tools";
|
||||
import { Filter } from "nostr-tools";
|
||||
|
||||
import timelineCacheService from "../services/timeline-cache";
|
||||
import { EventFilter } from "../classes/timeline-loader";
|
||||
import TimelineLoader, { EventFilter } from "../classes/timeline-loader";
|
||||
import { useStoreQuery } from "applesauce-react";
|
||||
import { Queries } from "applesauce-core";
|
||||
|
||||
type Options = {
|
||||
/** @deprecated */
|
||||
enabled?: boolean;
|
||||
eventFilter?: EventFilter;
|
||||
useCache?: boolean;
|
||||
cursor?: number;
|
||||
customSort?: (a: NostrEvent, b: NostrEvent) => number;
|
||||
};
|
||||
|
||||
export default function useTimelineLoader(
|
||||
@ -20,56 +18,46 @@ export default function useTimelineLoader(
|
||||
filters: Filter | Filter[] | undefined,
|
||||
opts?: Options,
|
||||
) {
|
||||
const timeline = useMemo(() => timelineCacheService.createTimeline(key), [key]);
|
||||
const loader = useMemo(() => timelineCacheService.createTimeline(key), [key]);
|
||||
|
||||
// set use cache
|
||||
if (opts?.useCache !== undefined) timeline.useCache = opts?.useCache;
|
||||
if (opts?.useCache !== undefined) loader.useCache = opts?.useCache;
|
||||
|
||||
// update relays
|
||||
useEffect(() => {
|
||||
timeline.setRelays(relays);
|
||||
timeline.triggerChunkLoad();
|
||||
loader.setRelays(relays);
|
||||
loader.triggerChunkLoad();
|
||||
}, [Array.from(relays).join("|")]);
|
||||
|
||||
// update filters
|
||||
useEffect(() => {
|
||||
if (filters) {
|
||||
timeline.setFilters(Array.isArray(filters) ? filters : [filters]);
|
||||
timeline.open();
|
||||
timeline.triggerChunkLoad();
|
||||
} else timeline.close();
|
||||
}, [timeline, JSON.stringify(filters)]);
|
||||
loader.setFilters(Array.isArray(filters) ? filters : [filters]);
|
||||
loader.open();
|
||||
loader.triggerChunkLoad();
|
||||
} else loader.close();
|
||||
}, [loader, JSON.stringify(filters)]);
|
||||
|
||||
// update event filter
|
||||
useEffect(() => {
|
||||
timeline.setEventFilter(opts?.eventFilter);
|
||||
}, [timeline, opts?.eventFilter]);
|
||||
|
||||
// update cursor
|
||||
// NOTE: I don't think this is used anywhere and should be removed
|
||||
useEffect(() => {
|
||||
if (opts?.cursor !== undefined) {
|
||||
timeline.setCursor(opts.cursor);
|
||||
}
|
||||
}, [timeline, opts?.cursor]);
|
||||
|
||||
// update custom sort
|
||||
useEffect(() => {
|
||||
timeline.events.customSort = opts?.customSort;
|
||||
}, [timeline, opts?.customSort]);
|
||||
loader.setEventFilter(opts?.eventFilter);
|
||||
}, [loader, opts?.eventFilter]);
|
||||
|
||||
// close the old timeline when the key changes
|
||||
const oldTimeline = usePrevious(timeline);
|
||||
const oldTimeline = usePrevious(loader);
|
||||
useEffect(() => {
|
||||
if (oldTimeline && oldTimeline !== timeline) {
|
||||
if (oldTimeline && oldTimeline !== loader) {
|
||||
oldTimeline.close();
|
||||
}
|
||||
}, [timeline, oldTimeline]);
|
||||
}, [loader, oldTimeline]);
|
||||
|
||||
// stop the loader when unmount
|
||||
useUnmount(() => {
|
||||
timeline.close();
|
||||
loader.close();
|
||||
});
|
||||
|
||||
return timeline;
|
||||
let timeline = useStoreQuery(Queries.TimelineQuery, filters && [filters]) ?? [];
|
||||
if (opts?.eventFilter) timeline = timeline.filter(opts.eventFilter);
|
||||
|
||||
return { loader, timeline };
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import { useCallback } from "react";
|
||||
|
||||
import { NOTE_LIST_KIND, PEOPLE_LIST_KIND, isJunkList } from "../helpers/nostr/lists";
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
import useTimelineLoader from "./use-timeline-loader";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { truncateId } from "../helpers/string";
|
||||
@ -12,18 +11,18 @@ export default function useUserLists(pubkey?: string, additionalRelays?: Iterabl
|
||||
const eventFilter = useCallback((event: NostrEvent) => {
|
||||
return !isJunkList(event);
|
||||
}, []);
|
||||
const timeline = useTimelineLoader(
|
||||
|
||||
const { timeline } = useTimelineLoader(
|
||||
`${truncateId(pubkey ?? "anon")}-lists`,
|
||||
readRelays,
|
||||
pubkey
|
||||
? {
|
||||
authors: pubkey ? [pubkey] : [],
|
||||
authors: [pubkey],
|
||||
kinds: [PEOPLE_LIST_KIND, NOTE_LIST_KIND],
|
||||
}
|
||||
: undefined,
|
||||
{ eventFilter },
|
||||
);
|
||||
|
||||
const lists = useSubject(timeline.timeline);
|
||||
return pubkey ? lists : [];
|
||||
return timeline;
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { useCallback } from "react";
|
||||
import { kinds } from "nostr-tools";
|
||||
import { useStoreQuery } from "applesauce-react";
|
||||
import { Queries } from "applesauce-core";
|
||||
|
||||
import { useReadRelays } from "./use-client-relays";
|
||||
import useSubject from "./use-subject";
|
||||
import useTimelineLoader from "./use-timeline-loader";
|
||||
import { NostrEvent, isRTag } from "../types/nostr-event";
|
||||
import { truncateId } from "../helpers/string";
|
||||
@ -10,18 +11,16 @@ import { truncateId } from "../helpers/string";
|
||||
export default function useUserRelaySets(pubkey?: string, additionalRelays?: Iterable<string>) {
|
||||
const readRelays = useReadRelays(additionalRelays);
|
||||
const eventFilter = useCallback((event: NostrEvent) => event.tags.some(isRTag), []);
|
||||
const timeline = useTimelineLoader(
|
||||
`${truncateId(pubkey || "anon")}-relay-sets`,
|
||||
readRelays,
|
||||
pubkey
|
||||
? {
|
||||
authors: pubkey ? [pubkey] : [],
|
||||
kinds: [kinds.Relaysets],
|
||||
}
|
||||
: undefined,
|
||||
{ eventFilter },
|
||||
);
|
||||
|
||||
const lists = useSubject(timeline.timeline);
|
||||
return pubkey ? lists : [];
|
||||
const filters = pubkey
|
||||
? {
|
||||
authors: [pubkey],
|
||||
kinds: [kinds.Relaysets],
|
||||
}
|
||||
: undefined;
|
||||
const { timeline } = useTimelineLoader(`${truncateId(pubkey || "anon")}-relay-sets`, readRelays, filters, {
|
||||
eventFilter,
|
||||
});
|
||||
|
||||
return timeline;
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ export default function DMTimelineProvider({ children }: PropsWithChildren) {
|
||||
[userMuteFilter],
|
||||
);
|
||||
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader } = useTimelineLoader(
|
||||
`${truncateId(account?.pubkey ?? "anon")}-dms`,
|
||||
inbox ?? [],
|
||||
account?.pubkey
|
||||
@ -47,7 +47,7 @@ export default function DMTimelineProvider({ children }: PropsWithChildren) {
|
||||
{ eventFilter },
|
||||
);
|
||||
|
||||
const context = useMemo(() => ({ timeline }), [timeline]);
|
||||
const context = useMemo(() => ({ timeline: loader }), [loader]);
|
||||
|
||||
return <DMTimelineContext.Provider value={context}>{children}</DMTimelineContext.Provider>;
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ export default function NotificationsProvider({ children }: PropsWithChildren) {
|
||||
[userMuteFilter],
|
||||
);
|
||||
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader } = useTimelineLoader(
|
||||
`${truncateId(account?.pubkey ?? "anon")}-notification`,
|
||||
readRelays,
|
||||
account?.pubkey
|
||||
@ -74,7 +74,7 @@ export default function NotificationsProvider({ children }: PropsWithChildren) {
|
||||
};
|
||||
}, [account?.pubkey]);
|
||||
|
||||
const context = useMemo(() => ({ timeline, notifications }), [timeline, notifications]);
|
||||
const context = useMemo(() => ({ timeline: loader, notifications }), [loader, notifications]);
|
||||
|
||||
return <NotificationTimelineContext.Provider value={context}>{children}</NotificationTimelineContext.Provider>;
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { PropsWithChildren, createContext, useCallback, useContext, useMemo } from "react";
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
import { useObservable } from "applesauce-react";
|
||||
|
||||
import TimelineLoader from "../../classes/timeline-loader";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { eventStore } from "../../services/event-store";
|
||||
|
||||
export type Thread = {
|
||||
root?: NostrEvent;
|
||||
@ -25,7 +26,7 @@ export function useThreadsContext() {
|
||||
}
|
||||
|
||||
export default function ThreadsProvider({ timeline, children }: { timeline: TimelineLoader } & PropsWithChildren) {
|
||||
const messages = useSubject(timeline.timeline);
|
||||
const messages = useObservable(timeline.timeline) ?? [];
|
||||
|
||||
const threads = useMemo(() => {
|
||||
const grouped: Record<string, Thread> = {};
|
||||
@ -36,21 +37,18 @@ export default function ThreadsProvider({ timeline, children }: { timeline: Time
|
||||
grouped[rootId] = {
|
||||
messages: [],
|
||||
rootId,
|
||||
root: timeline.events.getEvent(rootId),
|
||||
root: eventStore.getEvent(rootId),
|
||||
};
|
||||
}
|
||||
grouped[rootId].messages.push(message);
|
||||
}
|
||||
}
|
||||
return grouped;
|
||||
}, [messages.length, timeline.events]);
|
||||
}, [messages.length]);
|
||||
|
||||
const getRoot = useCallback(
|
||||
(id: string) => {
|
||||
return timeline.events.getEvent(id);
|
||||
},
|
||||
[timeline.events],
|
||||
);
|
||||
const getRoot = useCallback((id: string) => {
|
||||
return eventStore.getEvent(id);
|
||||
}, []);
|
||||
|
||||
const context = useMemo(() => ({ threads, getRoot }), [threads, getRoot]);
|
||||
|
||||
|
@ -31,7 +31,7 @@ export function TrustProvider({
|
||||
const context = useMemo(() => {
|
||||
const trust = parentTrust || isEventTrusted;
|
||||
return {
|
||||
trust: allowOverride ? override ?? trust : trust,
|
||||
trust: allowOverride ? (override ?? trust) : trust,
|
||||
setOverride: (v: boolean) => allowOverride && setOverride(v),
|
||||
};
|
||||
}, [override, parentTrust, isEventTrusted, setOverride, allowOverride]);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
import { AbstractRelay } from "nostr-tools/abstract-relay";
|
||||
import { EventStore } from "applesauce-core";
|
||||
|
||||
import { WIKI_PAGE_KIND } from "../helpers/nostr/wiki";
|
||||
import { logger } from "../helpers/debug";
|
||||
@ -11,15 +12,17 @@ import BookOpen01 from "../components/icons/book-open-01";
|
||||
import processManager from "./process-manager";
|
||||
import { localRelay } from "./local-relay";
|
||||
import relayPoolService from "./relay-pool";
|
||||
import { eventStore } from "./event-store";
|
||||
|
||||
class DictionaryService {
|
||||
log = logger.extend("DictionaryService");
|
||||
process: Process;
|
||||
store: EventStore;
|
||||
|
||||
topics = new SuperMap<string, Subject<Map<string, NostrEvent>>>(() => new Subject<Map<string, NostrEvent>>());
|
||||
|
||||
loaders = new SuperMap<AbstractRelay, BatchIdentifierLoader>((relay) => {
|
||||
const loader = new BatchIdentifierLoader(relay, [WIKI_PAGE_KIND], this.log.extend(relay.url));
|
||||
const loader = new BatchIdentifierLoader(this.store, relay, [WIKI_PAGE_KIND], this.log.extend(relay.url));
|
||||
this.process.addChild(loader.process);
|
||||
loader.onIdentifierUpdate.subscribe((identifier) => {
|
||||
this.updateSubject(identifier);
|
||||
@ -27,7 +30,8 @@ class DictionaryService {
|
||||
return loader;
|
||||
});
|
||||
|
||||
constructor() {
|
||||
constructor(store: EventStore) {
|
||||
this.store = store;
|
||||
this.process = new Process("DictionaryService", this);
|
||||
this.process.icon = BookOpen01;
|
||||
this.process.active = true;
|
||||
@ -75,12 +79,15 @@ class DictionaryService {
|
||||
}
|
||||
|
||||
handleEvent(event: NostrEvent) {
|
||||
event = this.store.add(event);
|
||||
|
||||
// pretend it came from the local relay
|
||||
// TODO: remove this once DictionaryService uses subscriptions from event store
|
||||
if (localRelay) this.loaders.get(localRelay as AbstractRelay).handleEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
const dictionaryService = new DictionaryService();
|
||||
const dictionaryService = new DictionaryService(eventStore);
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
// @ts-expect-error
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
import { AbstractRelay } from "nostr-tools/abstract-relay";
|
||||
import _throttle from "lodash.throttle";
|
||||
import { EventStore } from "applesauce-core";
|
||||
|
||||
import SuperMap from "../classes/super-map";
|
||||
import EventStore from "../classes/event-store";
|
||||
import Subject from "../classes/subject";
|
||||
import BatchKindPubkeyLoader, { createCoordinate } from "../classes/batch-kind-pubkey-loader";
|
||||
import Process from "../classes/process";
|
||||
@ -29,31 +29,34 @@ export function getHumanReadableCoordinate(kind: number, pubkey: string, d?: str
|
||||
}
|
||||
|
||||
class ReplaceableEventsService {
|
||||
store: EventStore;
|
||||
process: Process;
|
||||
|
||||
/** @deprecated */
|
||||
private subjects = new SuperMap<string, Subject<NostrEvent>>(() => new Subject<NostrEvent>());
|
||||
|
||||
cacheLoader: BatchKindPubkeyLoader | null = null;
|
||||
loaders = new SuperMap<AbstractRelay, BatchKindPubkeyLoader>((relay) => {
|
||||
const loader = new BatchKindPubkeyLoader(relay, this.log.extend(relay.url));
|
||||
loader.events.onEvent.subscribe((e) => this.handleEvent(e));
|
||||
const loader = new BatchKindPubkeyLoader(this.store, relay, this.log.extend(relay.url));
|
||||
this.process.addChild(loader.process);
|
||||
return loader;
|
||||
});
|
||||
|
||||
events = new EventStore();
|
||||
|
||||
log = logger.extend("ReplaceableEventLoader");
|
||||
|
||||
constructor() {
|
||||
constructor(store: EventStore) {
|
||||
this.store = store;
|
||||
this.process = new Process("ReplaceableEventsService", this);
|
||||
this.process.icon = UserSquare;
|
||||
this.process.active = true;
|
||||
processManager.registerProcess(this.process);
|
||||
|
||||
if (localRelay) {
|
||||
this.cacheLoader = new BatchKindPubkeyLoader(localRelay as AbstractRelay, this.log.extend("cache-relay"));
|
||||
this.cacheLoader.events.onEvent.subscribe((e) => this.handleEvent(e, true));
|
||||
this.cacheLoader = new BatchKindPubkeyLoader(
|
||||
this.store,
|
||||
localRelay as AbstractRelay,
|
||||
this.log.extend("cache-relay"),
|
||||
);
|
||||
this.process.addChild(this.cacheLoader.process);
|
||||
}
|
||||
}
|
||||
@ -61,15 +64,14 @@ class ReplaceableEventsService {
|
||||
private seenInCache = new Set<string>();
|
||||
handleEvent(event: NostrEvent, fromCache = false) {
|
||||
if (!fromCache && !alwaysVerify(event)) return;
|
||||
const cord = getEventCoordinate(event);
|
||||
event = this.store.add(event);
|
||||
|
||||
eventStore.add(event);
|
||||
const cord = getEventCoordinate(event);
|
||||
|
||||
const subject = this.subjects.get(cord);
|
||||
const current = subject.value;
|
||||
if (!current || event.created_at > current.created_at) {
|
||||
subject.next(event);
|
||||
this.events.addEvent(event);
|
||||
|
||||
if (!fromCache && localRelay && !this.seenInCache.has(event.id)) localRelay.publish(event);
|
||||
}
|
||||
@ -125,7 +127,7 @@ class ReplaceableEventsService {
|
||||
}
|
||||
}
|
||||
|
||||
const replaceableEventsService = new ReplaceableEventsService();
|
||||
const replaceableEventsService = new ReplaceableEventsService(eventStore);
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
//@ts-ignore
|
||||
|
@ -1,53 +1,40 @@
|
||||
import _throttle from "lodash.throttle";
|
||||
import { EventStore } from "applesauce-core";
|
||||
import { AbstractRelay } from "nostr-tools/abstract-relay";
|
||||
|
||||
import SuperMap from "../classes/super-map";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import { localRelay } from "./local-relay";
|
||||
import { logger } from "../helpers/debug";
|
||||
import Subject from "../classes/subject";
|
||||
import relayPoolService from "./relay-pool";
|
||||
import Process from "../classes/process";
|
||||
import processManager from "./process-manager";
|
||||
import Code02 from "../components/icons/code-02";
|
||||
import BatchEventLoader from "../classes/batch-event-loader";
|
||||
import EventStore from "../classes/event-store";
|
||||
import { eventStore } from "./event-store";
|
||||
|
||||
class SingleEventService {
|
||||
process: Process;
|
||||
store: EventStore;
|
||||
log = logger.extend("SingleEventService");
|
||||
|
||||
// events = new EventStore();
|
||||
// subjects = new SuperMap<string, Subject<NostrEvent>>(() => new Subject<NostrEvent>());
|
||||
|
||||
loaders = new SuperMap<AbstractRelay, BatchEventLoader>((relay) => {
|
||||
const loader = new BatchEventLoader(relay, this.log.extend(relay.url));
|
||||
const loader = new BatchEventLoader(this.store, relay, this.log.extend(relay.url));
|
||||
this.process.addChild(loader.process);
|
||||
// this.events.connect(loader.events);
|
||||
return loader;
|
||||
});
|
||||
|
||||
pendingRelays = new SuperMap<string, Set<AbstractRelay>>(() => new Set());
|
||||
|
||||
idsFromRelays = new SuperMap<AbstractRelay, Set<string>>(() => new Set());
|
||||
// subscriptions = new Map<AbstractRelay, PersistentSubscription>();
|
||||
constructor() {
|
||||
constructor(store: EventStore) {
|
||||
this.store = store;
|
||||
this.process = new Process("SingleEventService", this);
|
||||
this.process.icon = Code02;
|
||||
this.process.active = true;
|
||||
processManager.registerProcess(this.process);
|
||||
|
||||
// when an event is added to the store, pass it along to the subjects
|
||||
// this.events.onEvent.subscribe((event) => {
|
||||
// this.subjects.get(event.id).next(event);
|
||||
// });
|
||||
}
|
||||
|
||||
// getSubject(id: string) {
|
||||
// return this.subjects.get(id);
|
||||
// }
|
||||
|
||||
private loadEventFromRelays(id: string) {
|
||||
const relays = this.pendingRelays.get(id);
|
||||
|
||||
@ -58,9 +45,7 @@ class SingleEventService {
|
||||
|
||||
loadingFromCache = new Set<string>();
|
||||
requestEvent(id: string, urls: Iterable<string | URL | AbstractRelay>) {
|
||||
if (eventStore.hasEvent(id)) return;
|
||||
// const subject = this.subjects.get(id);
|
||||
// if (subject.value) return subject;
|
||||
if (this.store.hasEvent(id)) return;
|
||||
|
||||
const relays = relayPoolService.getRelays(urls);
|
||||
for (const relay of relays) this.pendingRelays.get(id).add(relay);
|
||||
@ -88,13 +73,13 @@ class SingleEventService {
|
||||
// this.events.addEvent(event);
|
||||
this.pendingRelays.delete(event.id);
|
||||
|
||||
eventStore.add(event);
|
||||
event = this.store.add(event);
|
||||
|
||||
if (!fromCache && localRelay) localRelay.publish(event);
|
||||
}
|
||||
}
|
||||
|
||||
const singleEventService = new SingleEventService();
|
||||
const singleEventService = new SingleEventService(eventStore);
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
//@ts-expect-error
|
||||
|
@ -1,10 +1,12 @@
|
||||
import _throttle from "lodash.throttle";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { getSearchNames } from "../helpers/nostr/user-metadata";
|
||||
import db from "./db";
|
||||
import replaceableEventsService from "./replaceable-events";
|
||||
import userMetadataService from "./user-metadata";
|
||||
import { logger } from "../helpers/debug";
|
||||
import Subject from "../classes/subject";
|
||||
import { eventStore } from "./event-store";
|
||||
|
||||
const WRITE_USER_SEARCH_BATCH_TIME = 500;
|
||||
const log = logger.extend("UsernameSearch");
|
||||
@ -32,9 +34,7 @@ const writeSearchData = _throttle(async () => {
|
||||
userSearchUpdate.next(Math.random());
|
||||
}, WRITE_USER_SEARCH_BATCH_TIME);
|
||||
|
||||
replaceableEventsService.events.onEvent.subscribe((event) => {
|
||||
if (event.kind === 0) {
|
||||
writeSearchQueue.add(event.pubkey);
|
||||
writeSearchData();
|
||||
}
|
||||
eventStore.stream([{ kinds: [kinds.Metadata] }]).subscribe((event) => {
|
||||
writeSearchQueue.add(event.pubkey);
|
||||
writeSearchData();
|
||||
});
|
||||
|
@ -1,16 +1,13 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Filter, kinds, NostrEvent } from "nostr-tools";
|
||||
import { Button, Flex, Heading, Spacer } from "@chakra-ui/react";
|
||||
import { Flex, Heading, Spacer } from "@chakra-ui/react";
|
||||
import { getEventUID } from "nostr-idb";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
|
||||
import Plus from "../../components/icons/plus";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import TimelineActionAndStatus from "../../components/timeline/timeline-action-and-status";
|
||||
@ -25,21 +22,23 @@ function ArticlesHomePage() {
|
||||
const eventFilter = useCallback(
|
||||
(event: NostrEvent) => {
|
||||
if (userMuteFilter(event)) return false;
|
||||
if (!getArticleTitle(event)) return false;
|
||||
if (!event.content) return false;
|
||||
return true;
|
||||
},
|
||||
[userMuteFilter],
|
||||
);
|
||||
|
||||
const { filter, listId } = usePeopleListContext();
|
||||
const query = useMemo<Filter[] | undefined>(() => {
|
||||
const filters = useMemo<Filter[] | undefined>(() => {
|
||||
if (!filter) return undefined;
|
||||
return [{ authors: filter.authors, kinds: [kinds.LongFormArticle] }];
|
||||
}, [filter]);
|
||||
|
||||
const timeline = useTimelineLoader(`${listId ?? "global"}-articles`, relays, query, { eventFilter });
|
||||
|
||||
const articles = useSubject(timeline.timeline).filter((article) => !!getArticleTitle(article) && !!article.content);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const { loader, timeline: articles } = useTimelineLoader(`${listId ?? "global"}-articles`, relays, filters, {
|
||||
eventFilter,
|
||||
});
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
@ -56,7 +55,7 @@ function ArticlesHomePage() {
|
||||
{articles.map((article) => (
|
||||
<ArticleCard key={getEventUID(article)} article={article} />
|
||||
))}
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
<TimelineActionAndStatus timeline={loader} />
|
||||
</IntersectionObserverProvider>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { kinds } from "nostr-tools";
|
||||
import { useObservable } from "applesauce-react";
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
@ -37,13 +38,13 @@ import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import useParamsAddressPointer from "../../hooks/use-params-address-pointer";
|
||||
|
||||
function BadgeActivityTab({ timeline }: { timeline: TimelineLoader }) {
|
||||
const awards = useSubject(timeline.timeline);
|
||||
const awards = useObservable(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="4">
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
{awards.map((award) => (
|
||||
{awards?.map((award) => (
|
||||
<ErrorBoundary key={award.id}>
|
||||
<BadgeAwardCard award={award} showImage={false} />
|
||||
</ErrorBoundary>
|
||||
@ -54,13 +55,15 @@ function BadgeActivityTab({ timeline }: { timeline: TimelineLoader }) {
|
||||
}
|
||||
|
||||
function BadgeUsersTab({ timeline }: { timeline: TimelineLoader }) {
|
||||
const awards = useSubject(timeline.timeline);
|
||||
const awards = useObservable(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
|
||||
const pubkeys = new Set<string>();
|
||||
for (const award of awards) {
|
||||
for (const { pubkey } of getBadgeAwardPubkeys(award)) {
|
||||
pubkeys.add(pubkey);
|
||||
if (awards) {
|
||||
for (const award of awards) {
|
||||
for (const { pubkey } of getBadgeAwardPubkeys(award)) {
|
||||
pubkeys.add(pubkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,7 +89,7 @@ function BadgeDetailsPage({ badge }: { badge: NostrEvent }) {
|
||||
|
||||
const readRelays = useReadRelays();
|
||||
const coordinate = getEventCoordinate(badge);
|
||||
const awardsTimeline = useTimelineLoader(`${coordinate}-awards`, readRelays, {
|
||||
const { loader } = useTimelineLoader(`${coordinate}-awards`, readRelays, {
|
||||
"#a": [coordinate],
|
||||
kinds: [kinds.BadgeAward],
|
||||
});
|
||||
@ -141,10 +144,10 @@ function BadgeDetailsPage({ badge }: { badge: NostrEvent }) {
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel px="0">
|
||||
<BadgeActivityTab timeline={awardsTimeline} />
|
||||
<BadgeActivityTab timeline={loader} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<BadgeUsersTab timeline={awardsTimeline} />
|
||||
<BadgeUsersTab timeline={loader} />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
|
@ -7,7 +7,6 @@ import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { getEventUID } from "../../helpers/nostr/event";
|
||||
import BadgeCard from "./components/badge-card";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
@ -16,14 +15,12 @@ function BadgesBrowsePage() {
|
||||
const { filter, listId } = usePeopleListContext();
|
||||
|
||||
const readRelays = useReadRelays();
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: lists } = useTimelineLoader(
|
||||
`${listId}-badges`,
|
||||
readRelays,
|
||||
filter ? { ...filter, kinds: [kinds.BadgeDefinition] } : undefined,
|
||||
);
|
||||
|
||||
const lists = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
@ -33,9 +30,7 @@ function BadgesBrowsePage() {
|
||||
</Flex>
|
||||
|
||||
<SimpleGrid columns={{ base: 1, sm: 2, md: 2, lg: 3, xl: 4 }} spacing="2">
|
||||
{lists.map((badge) => (
|
||||
<BadgeCard key={getEventUID(badge)} badge={badge} />
|
||||
))}
|
||||
{lists?.map((badge) => <BadgeCard key={getEventUID(badge)} badge={badge} />)}
|
||||
</SimpleGrid>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
|
@ -9,7 +9,6 @@ import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
|
||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import BadgeAwardCard from "./components/badge-award-card";
|
||||
@ -28,7 +27,7 @@ function BadgesPage() {
|
||||
[muteFilter],
|
||||
);
|
||||
const readRelays = useReadRelays();
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: awards } = useTimelineLoader(
|
||||
`${listId}-lists`,
|
||||
readRelays,
|
||||
{
|
||||
@ -37,9 +36,7 @@ function BadgesPage() {
|
||||
},
|
||||
{ eventFilter },
|
||||
);
|
||||
|
||||
const awards = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
@ -63,7 +60,7 @@ function BadgesPage() {
|
||||
<PeopleListSelection />
|
||||
</Flex>
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
{awards.map((award) => (
|
||||
{awards?.map((award) => (
|
||||
<ErrorBoundary key={award.id}>
|
||||
<BadgeAwardCard award={award} />
|
||||
</ErrorBoundary>
|
||||
|
@ -68,7 +68,7 @@ function ChannelPage({ channel }: { channel: NostrEvent }) {
|
||||
},
|
||||
[clientMuteFilter],
|
||||
);
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline } = useTimelineLoader(
|
||||
`${truncateId(channel.id)}-chat-messages`,
|
||||
relays,
|
||||
{
|
||||
@ -77,10 +77,10 @@ function ChannelPage({ channel }: { channel: NostrEvent }) {
|
||||
},
|
||||
{ eventFilter },
|
||||
);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<ThreadsProvider timeline={timeline}>
|
||||
<ThreadsProvider timeline={loader}>
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex h="full" overflow="hidden" direction="column" p="2" gap="2" flexGrow={1}>
|
||||
<Flex gap="2" alignItems="center">
|
||||
@ -106,8 +106,8 @@ function ChannelPage({ channel }: { channel: NostrEvent }) {
|
||||
py="4"
|
||||
px="2"
|
||||
>
|
||||
<ChannelChatLog timeline={timeline} channel={channel} />
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
<ChannelChatLog timeline={loader} channel={channel} />
|
||||
<TimelineActionAndStatus timeline={loader} />
|
||||
</Flex>
|
||||
|
||||
<ChannelMessageForm channel={channel} />
|
||||
|
@ -16,10 +16,10 @@ import {
|
||||
LinkBox,
|
||||
Text,
|
||||
} from "@chakra-ui/react";
|
||||
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import useChannelMetadata from "../../../hooks/use-channel-metadata";
|
||||
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../../providers/local/intersection-observer";
|
||||
import UserLink from "../../../components/user/user-link";
|
||||
@ -42,20 +42,16 @@ function UserCard({ pubkey }: { pubkey: string }) {
|
||||
);
|
||||
}
|
||||
function ChannelMembers({ channel, relays }: { channel: NostrEvent; relays: Iterable<string> }) {
|
||||
const timeline = useTimelineLoader(`${channel.id}-members`, relays, {
|
||||
const { loader, timeline: userLists } = useTimelineLoader(`${channel.id}-members`, relays, {
|
||||
kinds: [CHANNELS_LIST_KIND],
|
||||
"#e": [channel.id],
|
||||
});
|
||||
const userLists = useSubject(timeline.timeline);
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Flex gap="2" direction="column">
|
||||
{userLists.map((list) => (
|
||||
<UserCard key={list.pubkey} pubkey={list.pubkey} />
|
||||
))}
|
||||
{userLists?.map((list) => <UserCard key={list.pubkey} pubkey={list.pubkey} />)}
|
||||
</Flex>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
|
@ -3,7 +3,6 @@ import { kinds } from "nostr-tools";
|
||||
import { Flex, SimpleGrid } from "@chakra-ui/react";
|
||||
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
@ -27,15 +26,13 @@ function ChannelsHomePage() {
|
||||
},
|
||||
[clientMuteFilter],
|
||||
);
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: channels } = useTimelineLoader(
|
||||
`${listId}-channels`,
|
||||
relays,
|
||||
filter ? { ...filter, kinds: [kinds.ChannelCreation] } : undefined,
|
||||
{ eventFilter },
|
||||
);
|
||||
const channels = useSubject(timeline.timeline);
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
@ -44,7 +41,7 @@ function ChannelsHomePage() {
|
||||
</Flex>
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<SimpleGrid columns={{ base: 1, xl: 2 }} spacing="2">
|
||||
{channels.map((channel) => (
|
||||
{channels?.map((channel) => (
|
||||
<ErrorBoundary key={channel.id}>
|
||||
<ChannelCard channel={channel} additionalRelays={relays} />
|
||||
</ErrorBoundary>
|
||||
|
@ -38,7 +38,6 @@ import {
|
||||
import { getImageSize } from "../../helpers/image";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import useUserMuteFilter from "../../hooks/use-user-mute-filter";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import useReplaceableEvents from "../../hooks/use-replaceable-events";
|
||||
@ -88,7 +87,7 @@ function CommunitiesHomePage() {
|
||||
if (pub) navigate(`/c/${getCommunityName(pub.event)}/${pub.event.pubkey}`);
|
||||
};
|
||||
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: events } = useTimelineLoader(
|
||||
`all-communities-timeline`,
|
||||
readRelays,
|
||||
communityCoordinates.length > 0
|
||||
@ -111,7 +110,6 @@ function CommunitiesHomePage() {
|
||||
return Array.from(set);
|
||||
}, [communities]);
|
||||
|
||||
const events = useSubject(timeline.timeline);
|
||||
const approvalMap = buildApprovalMap(events, mods);
|
||||
|
||||
const approved = events
|
||||
@ -119,7 +117,7 @@ function CommunitiesHomePage() {
|
||||
.map((event) => ({ event, approvals: approvalMap.get(event.id) }))
|
||||
.filter((e) => !muteFilter(e.event));
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
const communityDrawer = useDisclosure();
|
||||
|
||||
@ -152,7 +150,7 @@ function CommunitiesHomePage() {
|
||||
<ApprovedEvent key={event.id} event={event} approvals={approvals ?? []} showCommunity />
|
||||
))}
|
||||
</IntersectionObserverProvider>
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
<TimelineActionAndStatus timeline={loader} />
|
||||
</Flex>
|
||||
<Flex gap="2" direction="column" w="md" flexShrink={0} hideBelow="xl">
|
||||
<Heading size="md">Joined Communities</Heading>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useContext } from "react";
|
||||
import { Button, ButtonGroup, Divider, Flex, Heading, Text, useDisclosure } from "@chakra-ui/react";
|
||||
import { Outlet, Link as RouterLink, useLocation } from "react-router-dom";
|
||||
import { useObservable } from "applesauce-react";
|
||||
import { kinds, nip19 } from "nostr-tools";
|
||||
|
||||
import {
|
||||
@ -30,7 +31,6 @@ import { WritingIcon } from "../../components/icons";
|
||||
import { PostModalContext } from "../../providers/route/post-modal-provider";
|
||||
import CommunityEditModal from "./components/community-edit-modal";
|
||||
import TimelineLoader from "../../classes/timeline-loader";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter";
|
||||
|
||||
function getCommunityPath(community: NostrEvent) {
|
||||
@ -51,13 +51,13 @@ export default function CommunityHomePage({ community }: { community: NostrEvent
|
||||
|
||||
const communityRelays = getCommunityRelays(community);
|
||||
const readRelays = useReadRelays(communityRelays);
|
||||
const timeline = useTimelineLoader(`${getEventUID(community)}-timeline`, readRelays, {
|
||||
const { loader } = useTimelineLoader(`${getEventUID(community)}-timeline`, readRelays, {
|
||||
kinds: [kinds.ShortTextNote, kinds.Repost, kinds.GenericRepost, COMMUNITY_APPROVAL_KIND],
|
||||
"#a": [communityCoordinate],
|
||||
});
|
||||
|
||||
// get pending notes
|
||||
const events = useSubject(timeline.timeline);
|
||||
const events = useObservable(loader.timeline) ?? [];
|
||||
const mods = getCommunityMods(community);
|
||||
const approvals = buildApprovalMap(events, mods);
|
||||
const pending = events.filter((e) => e.kind !== COMMUNITY_APPROVAL_KIND && !approvals.has(e.id) && !muteFilter(e));
|
||||
@ -142,7 +142,7 @@ export default function CommunityHomePage({ community }: { community: NostrEvent
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<Outlet context={{ community, timeline } satisfies RouterContext} />
|
||||
<Outlet context={{ community, timeline: loader } satisfies RouterContext} />
|
||||
</Flex>
|
||||
|
||||
{!verticalLayout && (
|
||||
|
@ -19,7 +19,6 @@ import { getCommunityRelays } from "../../../helpers/nostr/communities";
|
||||
import { getEventCoordinate } from "../../../helpers/nostr/event";
|
||||
import { COMMUNITIES_LIST_KIND } from "../../../helpers/nostr/lists";
|
||||
import IntersectionObserverProvider from "../../../providers/local/intersection-observer";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import TimelineActionAndStatus from "../../../components/timeline/timeline-action-and-status";
|
||||
import UserLink from "../../../components/user/user-link";
|
||||
@ -45,17 +44,17 @@ export default function CommunityMembersModal({
|
||||
}: Omit<ModalProps, "children"> & { community: NostrEvent }) {
|
||||
const communityCoordinate = getEventCoordinate(community);
|
||||
const readRelays = useReadRelays(getCommunityRelays(community));
|
||||
const timeline = useTimelineLoader(`${communityCoordinate}-members`, readRelays, [
|
||||
const { loader, timeline: lists } = useTimelineLoader(`${communityCoordinate}-members`, readRelays, [
|
||||
{ "#a": [communityCoordinate], kinds: [COMMUNITIES_LIST_KIND] },
|
||||
]);
|
||||
|
||||
const lists = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
const listsByPubkey: Record<string, NostrEvent> = {};
|
||||
for (const list of lists) {
|
||||
if (!listsByPubkey[list.pubkey] || listsByPubkey[list.pubkey].created_at < list.created_at) {
|
||||
listsByPubkey[list.pubkey] = list;
|
||||
if (lists) {
|
||||
for (const list of lists) {
|
||||
if (!listsByPubkey[list.pubkey] || listsByPubkey[list.pubkey].created_at < list.created_at) {
|
||||
listsByPubkey[list.pubkey] = list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,11 +69,9 @@ export default function CommunityMembersModal({
|
||||
<ModalCloseButton />
|
||||
<ModalBody p="4">
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing="4">
|
||||
{lists.map((list) => (
|
||||
<UserCard key={list.id} pubkey={list.pubkey} />
|
||||
))}
|
||||
{lists?.map((list) => <UserCard key={list.id} pubkey={list.pubkey} />)}
|
||||
</SimpleGrid>
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
<TimelineActionAndStatus timeline={loader} />
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter px="4" pt="0" pb="4">
|
||||
|
@ -6,7 +6,6 @@ import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import { COMMUNITY_DEFINITION_KIND, validateCommunity } from "../../helpers/nostr/communities";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
@ -27,23 +26,19 @@ export default function CommunityFindByNameView() {
|
||||
const eventFilter = useCallback((event: NostrEvent) => {
|
||||
return validateCommunity(event);
|
||||
}, []);
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: communities } = useTimelineLoader(
|
||||
`${community}-find-communities`,
|
||||
readRelays,
|
||||
community ? { kinds: [COMMUNITY_DEFINITION_KIND], "#d": [community] } : undefined,
|
||||
);
|
||||
|
||||
const communities = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<VerticalPageLayout>
|
||||
<Heading>Select Community</Heading>
|
||||
<SimpleGrid spacing="2" columns={{ base: 1, lg: 2 }}>
|
||||
{communities.map((event) => (
|
||||
<CommunityCard key={getEventUID(event)} community={event} />
|
||||
))}
|
||||
{communities?.map((event) => <CommunityCard key={getEventUID(event)} community={event} />)}
|
||||
</SimpleGrid>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
|
@ -8,13 +8,14 @@ import TimelineActionAndStatus from "../../../components/timeline/timeline-actio
|
||||
import useUserMuteFilter from "../../../hooks/use-user-mute-filter";
|
||||
import ApprovedEvent from "../components/community-approved-post";
|
||||
import { RouterContext } from "../community-home";
|
||||
import { useObservable } from "applesauce-react";
|
||||
|
||||
export default function CommunityNewestView() {
|
||||
const { community, timeline } = useOutletContext<RouterContext>();
|
||||
const muteFilter = useUserMuteFilter();
|
||||
const mods = getCommunityMods(community);
|
||||
|
||||
const events = useSubject(timeline.timeline);
|
||||
const events = useObservable(timeline.timeline) ?? [];
|
||||
const approvalMap = buildApprovalMap(events, mods);
|
||||
|
||||
const approved = events
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { Button, Flex } from "@chakra-ui/react";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import { useObservable } from "applesauce-react";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { DraftNostrEvent, NostrEvent } from "../../../types/nostr-event";
|
||||
@ -11,7 +12,6 @@ import {
|
||||
getCommunityMods,
|
||||
getCommunityRelays,
|
||||
} from "../../../helpers/nostr/communities";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import IntersectionObserverProvider from "../../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import TimelineActionAndStatus from "../../../components/timeline/timeline-action-and-status";
|
||||
@ -79,7 +79,7 @@ export default function CommunityPendingView() {
|
||||
const muteFilter = useUserMuteFilter();
|
||||
const { community, timeline } = useOutletContext<RouterContext>();
|
||||
|
||||
const events = useSubject(timeline.timeline);
|
||||
const events = useObservable(timeline.timeline) ?? [];
|
||||
|
||||
const mods = getCommunityMods(community);
|
||||
const approvals = buildApprovalMap(events, mods);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useMemo } from "react";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import { useObservable } from "applesauce-react";
|
||||
|
||||
import {
|
||||
COMMUNITY_APPROVAL_KIND,
|
||||
@ -7,7 +8,6 @@ import {
|
||||
getCommunityMods,
|
||||
getCommunityRelays,
|
||||
} from "../../../helpers/nostr/communities";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../../providers/local/intersection-observer";
|
||||
import TimelineActionAndStatus from "../../../components/timeline/timeline-action-and-status";
|
||||
@ -22,7 +22,7 @@ export default function CommunityTrendingView() {
|
||||
const muteFilter = useUserMuteFilter();
|
||||
const mods = getCommunityMods(community);
|
||||
|
||||
const events = useSubject(timeline.timeline);
|
||||
const events = useObservable(timeline.timeline) ?? [];
|
||||
const approvalMap = buildApprovalMap(events, mods);
|
||||
|
||||
const approved = events
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Divider, Flex, Heading, Spacer, Spinner, useDisclosure } from "@chakra-ui/react";
|
||||
import { Flex, Heading, Spacer, Spinner, useDisclosure } from "@chakra-ui/react";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { kinds, NostrEvent } from "nostr-tools";
|
||||
|
||||
@ -15,7 +15,6 @@ import KindSelectionProvider, { useKindSelectionContext } from "../../../provide
|
||||
import NoteFilterTypeButtons from "../../../components/note-filter-type-buttons";
|
||||
import TimelineViewTypeButtons from "../../../components/timeline-page/timeline-view-type";
|
||||
import Telescope from "../../../components/icons/telescope";
|
||||
import UserAvatar from "../../../components/user/user-avatar";
|
||||
import UserAvatarLink from "../../../components/user/user-avatar-link";
|
||||
import BackButton from "../../../components/router/back-button";
|
||||
import UserLink from "../../../components/user/user-link";
|
||||
@ -53,7 +52,7 @@ function BlindspotFeedPage({ pubkey }: { pubkey: string }) {
|
||||
);
|
||||
|
||||
const { kinds } = useKindSelectionContext();
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline } = useTimelineLoader(
|
||||
`blnidspot-${account.pubkey}-${pubkey}-${kinds.join(",")}`,
|
||||
readRelays,
|
||||
blindspot.length > 0 ? [{ authors: blindspot, kinds }] : undefined,
|
||||
@ -87,7 +86,7 @@ function BlindspotFeedPage({ pubkey }: { pubkey: string }) {
|
||||
<TimelineViewTypeButtons />
|
||||
</Flex>
|
||||
|
||||
<TimelinePage timeline={timeline} />
|
||||
<TimelinePage loader={loader} timeline={timeline} />
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ import { usePublishEvent } from "../../../../providers/global/publish-provider";
|
||||
|
||||
function NextPageButton({ chain, pointer }: { pointer: AddressPointer; chain: ChainedDVMJob[] }) {
|
||||
const publish = usePublishEvent();
|
||||
const dvmRelays = useUserMailboxes(pointer.pubkey)
|
||||
const dvmRelays = useUserMailboxes(pointer.pubkey);
|
||||
const readRelays = useReadRelays();
|
||||
|
||||
const lastJob = chain[chain.length - 1];
|
||||
|
@ -25,7 +25,6 @@ import {
|
||||
} from "../../../helpers/nostr/dvm";
|
||||
import { DraftNostrEvent } from "../../../types/nostr-event";
|
||||
import VerticalPageLayout from "../../../components/vertical-page-layout";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
@ -49,7 +48,7 @@ function DVMFeedPage({ pointer }: { pointer: AddressPointer }) {
|
||||
|
||||
const dvmRelays = useUserMailboxes(pointer.pubkey)?.outboxes;
|
||||
const readRelays = useReadRelays(dvmRelays);
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: events } = useTimelineLoader(
|
||||
`${getHumanReadableCoordinate(pointer.kind, pointer.pubkey, pointer.identifier)}-jobs`,
|
||||
readRelays,
|
||||
[
|
||||
@ -61,8 +60,6 @@ function DVMFeedPage({ pointer }: { pointer: AddressPointer }) {
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
const events = useSubject(timeline.timeline);
|
||||
const jobs = groupEventsIntoJobs(events);
|
||||
const pages = chainJobs(Array.from(Object.values(jobs)));
|
||||
const jobChains = flattenJobChain(pages);
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { useCallback } from "react";
|
||||
import { Card, Flex, Heading, Link, LinkBox, SimpleGrid, Text } from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { kinds, NostrEvent } from "nostr-tools";
|
||||
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import DVMCard from "./dvm-feed/components/dvm-card";
|
||||
import { DVM_CONTENT_DISCOVERY_JOB_KIND } from "../../helpers/nostr/dvm";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import RequireCurrentAccount from "../../providers/route/require-current-account";
|
||||
import { getEventCoordinate } from "../../helpers/nostr/event";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
@ -17,14 +18,19 @@ import { RelayIcon } from "../../components/icons";
|
||||
|
||||
function DVMFeeds() {
|
||||
const readRelays = useReadRelays();
|
||||
const timeline = useTimelineLoader("content-discovery-dvms", readRelays, {
|
||||
kinds: [31990],
|
||||
"#k": [String(DVM_CONTENT_DISCOVERY_JOB_KIND)],
|
||||
});
|
||||
|
||||
const DMVs = useSubject(timeline.timeline).filter((e) => !e.tags.some((t) => t[0] === "web"));
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const eventFilter = useCallback((event: NostrEvent) => {
|
||||
return !event.tags.some((t) => t[0] === "web");
|
||||
}, []);
|
||||
const { loader, timeline: DVMs } = useTimelineLoader(
|
||||
"content-discovery-dvms",
|
||||
readRelays,
|
||||
{
|
||||
kinds: [kinds.Handlerinformation],
|
||||
"#k": [String(DVM_CONTENT_DISCOVERY_JOB_KIND)],
|
||||
},
|
||||
{ eventFilter },
|
||||
);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -39,7 +45,7 @@ function DVMFeeds() {
|
||||
</Text>
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<SimpleGrid columns={{ base: 1, md: 1, lg: 2, xl: 3 }} spacing="2">
|
||||
{DMVs.map((appData) => (
|
||||
{DVMs.map((appData) => (
|
||||
<DVMCard key={appData.id} appData={appData} to={`/discovery/dvm/${getEventCoordinate(appData)}`} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
|
@ -6,7 +6,6 @@ import { NostrEvent, kinds } from "nostr-tools";
|
||||
import { ThreadIcon } from "../../components/icons";
|
||||
import UserAvatar from "../../components/user/user-avatar";
|
||||
import UserLink from "../../components/user/user-link";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import RequireCurrentAccount from "../../providers/route/require-current-account";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
@ -18,7 +17,6 @@ import SendMessageForm from "./components/send-message-form";
|
||||
import { groupMessages } from "../../helpers/nostr/dms";
|
||||
import ThreadDrawer from "./components/thread-drawer";
|
||||
import ThreadsProvider from "../../providers/local/thread-provider";
|
||||
import TimelineLoader from "../../classes/timeline-loader";
|
||||
import DirectMessageBlock from "./components/direct-message-block";
|
||||
import useParamsProfilePointer from "../../hooks/use-params-pubkey-pointer";
|
||||
import useUserMailboxes from "../../hooks/use-user-mailboxes";
|
||||
@ -30,8 +28,7 @@ import { BackIconButton } from "../../components/router/back-button";
|
||||
import decryptionCacheService from "../../services/decryption-cache";
|
||||
|
||||
/** This is broken out from DirectMessageChatPage for performance reasons. Don't use outside of file */
|
||||
const ChatLog = memo(({ timeline }: { timeline: TimelineLoader }) => {
|
||||
const messages = useSubject(timeline.timeline);
|
||||
const ChatLog = memo(({ messages }: { messages: NostrEvent[] }) => {
|
||||
const filteredMessages = useMemo(
|
||||
() => messages.filter((e) => !e.tags.some((t) => t[0] === "e" && t[3] === "root")),
|
||||
[messages.length],
|
||||
@ -86,7 +83,7 @@ function DirectMessageChatPage({ pubkey }: { pubkey: string }) {
|
||||
|
||||
const otherMailboxes = useUserMailboxes(pubkey);
|
||||
const mailboxes = useUserMailboxes(account.pubkey);
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: messages } = useTimelineLoader(
|
||||
`${truncateId(pubkey)}-${truncateId(account.pubkey)}-messages`,
|
||||
RelaySet.from(mailboxes?.inboxes, mailboxes?.outboxes, otherMailboxes?.inboxes, otherMailboxes?.outboxes),
|
||||
[
|
||||
@ -101,7 +98,7 @@ function DirectMessageChatPage({ pubkey }: { pubkey: string }) {
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const decryptAll = async () => {
|
||||
const promises = timeline.timeline.value
|
||||
const promises = messages
|
||||
.map((message) => {
|
||||
const container = decryptionCacheService.getOrCreateContainer(message.id, "nip04", pubkey, message.content);
|
||||
return decryptionCacheService.requestDecrypt(container);
|
||||
@ -112,10 +109,10 @@ function DirectMessageChatPage({ pubkey }: { pubkey: string }) {
|
||||
Promise.all(promises).finally(() => setLoading(false));
|
||||
};
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<ThreadsProvider timeline={timeline}>
|
||||
<ThreadsProvider timeline={loader}>
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<Card size="sm" flexShrink={0} p="2" flexDirection="row">
|
||||
<Flex gap="2" alignItems="center">
|
||||
@ -139,8 +136,8 @@ function DirectMessageChatPage({ pubkey }: { pubkey: string }) {
|
||||
</ButtonGroup>
|
||||
</Card>
|
||||
<Flex h="0" flex={1} overflowX="hidden" overflowY="scroll" direction="column-reverse" gap="2" py="4" px="2">
|
||||
<ChatLog timeline={timeline} />
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
<ChatLog messages={messages} />
|
||||
<TimelineActionAndStatus timeline={loader} />
|
||||
</Flex>
|
||||
<SendMessageForm flexShrink={0} pubkey={pubkey} />
|
||||
{location.state?.thread && (
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useMemo } from "react";
|
||||
import { Card, CardBody, Flex, LinkBox, LinkOverlay, Text } from "@chakra-ui/react";
|
||||
import { Outlet, Link as RouterLink, useLocation, useParams } from "react-router-dom";
|
||||
import { useObservable } from "applesauce-react";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
import UserAvatar from "../../components/user/user-avatar";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import RequireCurrentAccount from "../../providers/route/require-current-account";
|
||||
import Timestamp from "../../components/timestamp";
|
||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||
@ -66,7 +66,7 @@ function DirectMessagesPage() {
|
||||
const account = useCurrentAccount()!;
|
||||
const timeline = useDMTimeline();
|
||||
|
||||
const messages = useSubject(timeline.timeline);
|
||||
const messages = useObservable(timeline.timeline) ?? [];
|
||||
const conversations = useMemo(() => {
|
||||
const conversations = groupIntoConversations(messages).map((c) => identifyConversation(c, account.pubkey));
|
||||
const filtered = conversations.filter((conversation) =>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useCallback } from "react";
|
||||
import { Flex, SimpleGrid, Switch, useDisclosure } from "@chakra-ui/react";
|
||||
import { getEventUID } from "applesauce-core/helpers";
|
||||
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
|
||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||
@ -8,9 +9,7 @@ import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import EmojiPackCard from "./components/emoji-pack-card";
|
||||
import { getEventUID } from "../../helpers/nostr/event";
|
||||
import { EMOJI_PACK_KIND, getEmojisFromPack } from "../../helpers/nostr/emoji-packs";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
@ -26,15 +25,13 @@ function EmojiPacksBrowsePage() {
|
||||
[showEmpty.isOpen],
|
||||
);
|
||||
const readRelays = useReadRelays();
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: packs } = useTimelineLoader(
|
||||
`${listId}-browse-emoji-packs`,
|
||||
readRelays,
|
||||
filter ? { ...filter, kinds: [EMOJI_PACK_KIND] } : undefined,
|
||||
{ eventFilter },
|
||||
);
|
||||
|
||||
const packs = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
@ -47,9 +44,7 @@ function EmojiPacksBrowsePage() {
|
||||
</Flex>
|
||||
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing="2">
|
||||
{packs.map((event) => (
|
||||
<EmojiPackCard key={getEventUID(event)} pack={event} />
|
||||
))}
|
||||
{packs?.map((event) => <EmojiPackCard key={getEventUID(event)} pack={event} />)}
|
||||
</SimpleGrid>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Button, Divider, Flex, Heading, Link, SimpleGrid, useDisclosure } from "@chakra-ui/react";
|
||||
import { Button, Flex, Heading, Link, SimpleGrid, useDisclosure } from "@chakra-ui/react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { useObservable } from "applesauce-react";
|
||||
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import { ExternalLinkIcon } from "../../components/icons";
|
||||
@ -7,7 +8,6 @@ import { getEventCoordinate, getEventUID } from "../../helpers/nostr/event";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { EMOJI_PACK_KIND, getPackCordsFromFavorites } from "../../helpers/nostr/emoji-packs";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import EmojiPackCard from "./components/emoji-pack-card";
|
||||
import useFavoriteEmojiPacks from "../../hooks/use-favorite-emoji-packs";
|
||||
import useReplaceableEvents from "../../hooks/use-replaceable-events";
|
||||
@ -19,7 +19,7 @@ function UserEmojiPackMangerPage() {
|
||||
|
||||
const favoritePacks = useFavoriteEmojiPacks(account.pubkey);
|
||||
const readRelays = useReadRelays();
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: packs } = useTimelineLoader(
|
||||
`${account.pubkey}-emoji-packs`,
|
||||
readRelays,
|
||||
account.pubkey
|
||||
@ -31,7 +31,7 @@ function UserEmojiPackMangerPage() {
|
||||
);
|
||||
|
||||
const favorites = useReplaceableEvents(favoritePacks && getPackCordsFromFavorites(favoritePacks));
|
||||
const packs = useSubject(timeline.timeline).filter((pack) => {
|
||||
const filtered = packs.filter((pack) => {
|
||||
const cord = getEventCoordinate(pack);
|
||||
return !favorites.some((e) => getEventCoordinate(e) === cord);
|
||||
});
|
||||
@ -50,7 +50,7 @@ function UserEmojiPackMangerPage() {
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
{packs.length > 0 && (
|
||||
{filtered.length > 0 && (
|
||||
<>
|
||||
<Heading size="lg" mt="2">
|
||||
Emoji packs
|
||||
|
@ -2,7 +2,6 @@ import { useState } from "react";
|
||||
import { Flex, Image, SimpleGrid, Spacer, Text } from "@chakra-ui/react";
|
||||
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { FILE_KIND, IMAGE_TYPES, VIDEO_TYPES, getFileUrl, parseImageFile } from "../../helpers/nostr/files";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
@ -112,15 +111,12 @@ function FilesPage() {
|
||||
|
||||
const [selectedTypes, setSelectedTypes] = useState<string[]>(IMAGE_TYPES);
|
||||
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: events } = useTimelineLoader(
|
||||
`${listId}-files`,
|
||||
relays,
|
||||
{ kinds: [FILE_KIND], "#m": selectedTypes, ...filter },
|
||||
{ enabled: selectedTypes.length > 0 && !!filter },
|
||||
selectedTypes.length > 0 && !!filter ? { kinds: [FILE_KIND], "#m": selectedTypes, ...filter } : undefined,
|
||||
);
|
||||
|
||||
const events = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
@ -131,14 +127,14 @@ function FilesPage() {
|
||||
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<SimpleGrid minChildWidth="20rem" spacing="2">
|
||||
{events.map((event) => (
|
||||
{events?.map((event) => (
|
||||
<ErrorBoundary key={event.id} event={event}>
|
||||
<FileType event={event} />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</IntersectionObserverProvider>
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
<TimelineActionAndStatus timeline={loader} />
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useCallback } from "react";
|
||||
import { Flex, SimpleGrid, Switch, useDisclosure } from "@chakra-ui/react";
|
||||
import { getEventUID } from "applesauce-core/helpers";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
|
||||
@ -8,13 +9,12 @@ import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import GoalCard from "./components/goal-card";
|
||||
import { getEventUID } from "../../helpers/nostr/event";
|
||||
import { GOAL_KIND, getGoalClosedDate } from "../../helpers/nostr/goal";
|
||||
import { getGoalClosedDate } from "../../helpers/nostr/goal";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
function GoalsBrowsePage() {
|
||||
const { filter, listId } = usePeopleListContext();
|
||||
@ -29,15 +29,13 @@ function GoalsBrowsePage() {
|
||||
},
|
||||
[showClosed.isOpen],
|
||||
);
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: goals } = useTimelineLoader(
|
||||
`${listId}-browse-goals`,
|
||||
readRelays,
|
||||
filter ? { ...filter, kinds: [GOAL_KIND] } : undefined,
|
||||
filter ? { ...filter, kinds: [kinds.ZapGoal] } : undefined,
|
||||
{ eventFilter },
|
||||
);
|
||||
|
||||
const goals = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
@ -50,7 +48,7 @@ function GoalsBrowsePage() {
|
||||
</Flex>
|
||||
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing="2">
|
||||
{goals.map((event) => (
|
||||
{goals?.map((event) => (
|
||||
<ErrorBoundary key={getEventUID(event)} event={event}>
|
||||
<GoalCard goal={event} />
|
||||
</ErrorBoundary>
|
||||
|
@ -1,33 +1,30 @@
|
||||
import { Button, Center, Divider, Flex, Heading, Link, SimpleGrid, Spacer } from "@chakra-ui/react";
|
||||
import { Navigate, Link as RouterLink } from "react-router-dom";
|
||||
import { getEventUID } from "applesauce-core/helpers";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
import { ExternalLinkIcon } from "../../components/icons";
|
||||
import { getEventUID } from "../../helpers/nostr/event";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import GoalCard from "./components/goal-card";
|
||||
import { GOAL_KIND } from "../../helpers/nostr/goal";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
|
||||
function UserGoalsManagerPage() {
|
||||
const account = useCurrentAccount()!;
|
||||
|
||||
const readRelays = useReadRelays();
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: goals } = useTimelineLoader(
|
||||
`${account.pubkey}-goals`,
|
||||
readRelays,
|
||||
account.pubkey
|
||||
? {
|
||||
authors: [account.pubkey],
|
||||
kinds: [GOAL_KIND],
|
||||
kinds: [kinds.ZapGoal],
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const goals = useSubject(timeline.timeline);
|
||||
|
||||
if (goals.length === 0) {
|
||||
return (
|
||||
<Center p="10" fontSize="lg" whiteSpace="pre">
|
||||
@ -45,7 +42,7 @@ function UserGoalsManagerPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{goals.length > 0 && (
|
||||
{goals && goals.length > 0 && (
|
||||
<>
|
||||
<Heading size="md" mt="2">
|
||||
Created goals
|
||||
|
@ -67,14 +67,14 @@ function HashTagPage() {
|
||||
},
|
||||
[showReplies.isOpen, showReposts.isOpen, muteFilter, timelinePageEventFilter],
|
||||
);
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline } = useTimelineLoader(
|
||||
`${listId ?? "global"}-${hashtag}-hashtag`,
|
||||
readRelays,
|
||||
{ kinds: [1], "#t": [hashtag], ...filter },
|
||||
{ eventFilter },
|
||||
);
|
||||
|
||||
useRelaysChanged(readRelays, () => timeline.reset());
|
||||
useRelaysChanged(readRelays, () => loader.reset());
|
||||
|
||||
const header = (
|
||||
<Flex gap="2" alignItems="center" wrap="wrap">
|
||||
@ -103,7 +103,7 @@ function HashTagPage() {
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return <TimelinePage timeline={timeline} header={header} pt="2" pb="12" px="2" />;
|
||||
return <TimelinePage loader={loader} timeline={timeline} header={header} pt="2" pb="12" px="2" />;
|
||||
}
|
||||
|
||||
export default function HashTagView() {
|
||||
|
@ -42,9 +42,14 @@ function HomePage() {
|
||||
const { listId, filter } = usePeopleListContext();
|
||||
const { kinds } = useKindSelectionContext();
|
||||
|
||||
const timeline = useTimelineLoader(`${listId}-home-feed`, relays, filter ? { ...filter, kinds } : undefined, {
|
||||
eventFilter,
|
||||
});
|
||||
const { loader, timeline } = useTimelineLoader(
|
||||
`${listId}-home-feed`,
|
||||
relays,
|
||||
filter ? { ...filter, kinds } : undefined,
|
||||
{
|
||||
eventFilter,
|
||||
},
|
||||
);
|
||||
|
||||
const header = (
|
||||
<Flex gap="2" wrap="wrap" alignItems="center">
|
||||
@ -55,7 +60,7 @@ function HomePage() {
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return <TimelinePage timeline={timeline} header={header} pt="2" pb="12" px="2" />;
|
||||
return <TimelinePage loader={loader} timeline={timeline} header={header} pt="2" pb="12" px="2" />;
|
||||
}
|
||||
|
||||
export default function HomeView() {
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { Button, Card, CardBody, CardHeader, CardProps, Flex, Heading, Link, LinkBox, Text } from "@chakra-ui/react";
|
||||
import { Link as RouterLink, useNavigate } from "react-router-dom";
|
||||
import { useObservable } from "applesauce-react";
|
||||
import { nip19 } from "nostr-tools";
|
||||
|
||||
import KeyboardShortcut from "../../../components/keyboard-shortcut";
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import { useDMTimeline } from "../../../providers/global/dms-provider";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import {
|
||||
KnownConversation,
|
||||
groupIntoConversations,
|
||||
@ -54,7 +54,7 @@ export default function DMsCard({ ...props }: Omit<CardProps, "children">) {
|
||||
|
||||
const timeline = useDMTimeline();
|
||||
|
||||
const messages = useSubject(timeline.timeline);
|
||||
const messages = useObservable(timeline.timeline) ?? [];
|
||||
const conversations = useMemo(() => {
|
||||
const grouped = groupIntoConversations(messages)
|
||||
.map((c) => identifyConversation(c, account.pubkey))
|
||||
|
@ -8,7 +8,6 @@ import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||
import useClientSideMuteFilter from "../../../hooks/use-client-side-mute-filter";
|
||||
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../../providers/local/people-list-provider";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { ParsedStream, parseStreamEvent } from "../../../helpers/nostr/stream";
|
||||
import UserAvatar from "../../../components/user/user-avatar";
|
||||
import UserName from "../../../components/user/user-name";
|
||||
@ -52,9 +51,9 @@ function StreamsCardContent({ ...props }: Omit<CardProps, "children">) {
|
||||
];
|
||||
}, [filter]);
|
||||
|
||||
const timeline = useTimelineLoader(`${listId ?? "global"}-streams`, relays, query, { eventFilter });
|
||||
const { loader, timeline } = useTimelineLoader(`${listId ?? "global"}-streams`, relays, query, { eventFilter });
|
||||
|
||||
const streams = useSubject(timeline.timeline)
|
||||
const streams = timeline
|
||||
.map((event) => {
|
||||
try {
|
||||
return parseStreamEvent(event);
|
||||
@ -75,7 +74,7 @@ function StreamsCardContent({ ...props }: Omit<CardProps, "children">) {
|
||||
<KeyboardShortcut letter="l" requireMeta ml="auto" onPress={() => navigate("/streams")} />
|
||||
</CardHeader>
|
||||
<CardBody overflowX="hidden" overflowY="auto" pt="4" display="flex" gap="2" flexDirection="column" maxH="50vh">
|
||||
{streams.map((stream) => (
|
||||
{streams?.map((stream) => (
|
||||
<ErrorBoundary key={getEventUID(stream.event)} event={stream.event}>
|
||||
<LiveStream stream={stream} />
|
||||
</ErrorBoundary>
|
||||
|
@ -16,7 +16,6 @@ import { useCallback, useState } from "react";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import ListCard from "./components/list-card";
|
||||
import { getEventUID } from "../../helpers/nostr/event";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
@ -43,15 +42,13 @@ function BrowseListPage() {
|
||||
[showEmpty.isOpen, showMute.isOpen, listKind],
|
||||
);
|
||||
const readRelays = useReadRelays();
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: lists } = useTimelineLoader(
|
||||
`${listId}-lists`,
|
||||
readRelays,
|
||||
filter ? { ...filter, kinds: [PEOPLE_LIST_KIND, NOTE_LIST_KIND] } : undefined,
|
||||
{ eventFilter },
|
||||
);
|
||||
|
||||
const lists = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
@ -71,9 +68,7 @@ function BrowseListPage() {
|
||||
</Flex>
|
||||
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing="2">
|
||||
{lists.map((event) => (
|
||||
<ListCard key={getEventUID(event)} list={event} />
|
||||
))}
|
||||
{lists?.map((event) => <ListCard key={getEventUID(event)} list={event} />)}
|
||||
</SimpleGrid>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
|
@ -2,13 +2,13 @@ import { useCallback, useEffect, useState } from "react";
|
||||
import { useNavigate, useSearchParams } from "react-router-dom";
|
||||
import { Button, Flex } from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
import { useObservable } from "applesauce-react";
|
||||
import ngeohash from "ngeohash";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import L from "leaflet";
|
||||
import "leaflet.locatecontrol/dist/L.Control.Locate.min.css";
|
||||
import "leaflet.locatecontrol";
|
||||
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
|
||||
@ -56,7 +56,7 @@ export default function MapView() {
|
||||
const [cells, setCells] = useState<string[]>([]);
|
||||
|
||||
const readRelays = useReadRelays();
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline } = useTimelineLoader(
|
||||
"geo-events",
|
||||
readRelays,
|
||||
cells.length > 0 ? { "#g": cells, kinds: [kinds.ShortTextNote] } : undefined,
|
||||
@ -82,7 +82,7 @@ export default function MapView() {
|
||||
setFocused(event.id);
|
||||
}, []);
|
||||
|
||||
const events = useSubject(timeline.timeline);
|
||||
const events = useObservable(loader.timeline) ?? [];
|
||||
useEventMarkers(events, map, handleMarkerClick);
|
||||
|
||||
return (
|
||||
@ -98,8 +98,8 @@ export default function MapView() {
|
||||
</Flex>
|
||||
|
||||
<Flex overflowY="auto" overflowX="hidden" gap="2" direction="column" h="full">
|
||||
<MapTimeline timeline={timeline} focused={focused} />
|
||||
{cells.length > 0 && <TimelineActionAndStatus timeline={timeline} />}
|
||||
<MapTimeline timeline={loader} focused={focused} />
|
||||
{cells.length > 0 && <TimelineActionAndStatus timeline={loader} />}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
|
@ -2,18 +2,17 @@ import React from "react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { ErrorBoundary } from "../../components/error-boundary";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import StreamNote from "../../components/timeline-page/generic-note-timeline/stream-note";
|
||||
import { STREAM_KIND } from "../../helpers/nostr/stream";
|
||||
import TimelineLoader from "../../classes/timeline-loader";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import TimelineNote from "../../components/note/timeline-note";
|
||||
import { useObservable } from "applesauce-react";
|
||||
|
||||
const RenderEvent = React.memo(({ event, focused }: { event: NostrEvent; focused?: boolean }) => {
|
||||
switch (event.kind) {
|
||||
case kinds.ShortTextNote:
|
||||
return <TimelineNote event={event} variant={focused ? "elevated" : undefined} />;
|
||||
case STREAM_KIND:
|
||||
case kinds.LiveEvent:
|
||||
return <StreamNote event={event} />;
|
||||
default:
|
||||
return null;
|
||||
@ -21,11 +20,11 @@ const RenderEvent = React.memo(({ event, focused }: { event: NostrEvent; focused
|
||||
});
|
||||
|
||||
const MapTimeline = React.memo(({ timeline, focused }: { timeline: TimelineLoader; focused?: string }) => {
|
||||
const events = useSubject(timeline.timeline);
|
||||
const events = useObservable(timeline.timeline);
|
||||
|
||||
return (
|
||||
<>
|
||||
{events.map((event) => (
|
||||
{events?.map((event) => (
|
||||
<ErrorBoundary key={event.id} event={event}>
|
||||
<RenderEvent event={event} focused={focused === event.id} />
|
||||
</ErrorBoundary>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { MouseEventHandler, useCallback, useMemo } from "react";
|
||||
import { kinds } from "nostr-tools";
|
||||
import { kinds, NostrEvent } from "nostr-tools";
|
||||
import { useObservable } from "applesauce-react";
|
||||
import { Link as RouterLink, useNavigate } from "react-router-dom";
|
||||
|
||||
import useCurrentAccount from "../../hooks/use-current-account";
|
||||
@ -11,7 +12,6 @@ import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-
|
||||
import { useNotifications } from "../../providers/global/notifications-provider";
|
||||
import { TORRENT_COMMENT_KIND } from "../../helpers/nostr/torrents";
|
||||
import { groupByRoot } from "../../helpers/notification";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { ChevronLeftIcon } from "../../components/icons";
|
||||
import { AvatarGroup, Box, Button, ButtonGroup, Flex, LinkBox, Text, useDisclosure } from "@chakra-ui/react";
|
||||
import UserAvatarLink from "../../components/user/user-avatar-link";
|
||||
@ -67,7 +67,7 @@ function ThreadGroup({ rootId, events }: { rootId: string; events: NostrEvent[]
|
||||
const ref = useEventIntersectionRef(events[events.length - 1]);
|
||||
|
||||
return (
|
||||
<Flex>
|
||||
<Flex ref={ref}>
|
||||
<GitBranch01 boxSize={8} color="green.500" mr="2" />
|
||||
<Flex direction="column" gap="2">
|
||||
<AvatarGroup size="sm">
|
||||
@ -104,7 +104,7 @@ function ThreadsNotificationsPage() {
|
||||
|
||||
const { timeline } = useNotifications();
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const events = useSubject(timeline?.timeline);
|
||||
const events = useObservable(timeline?.timeline) ?? [];
|
||||
|
||||
const filteredEvents = useMemo(
|
||||
() =>
|
||||
|
@ -16,7 +16,7 @@ export default function SelectRelaySet({
|
||||
pubkey?: string;
|
||||
}) {
|
||||
const account = useCurrentAccount();
|
||||
const relaySets = useUserRelaySets(pubkey || account?.pubkey);
|
||||
const relaySets = useUserRelaySets(pubkey || account?.pubkey) ?? [];
|
||||
|
||||
return (
|
||||
<Select
|
||||
|
@ -7,7 +7,6 @@ import PeopleListProvider, { usePeopleListContext } from "../../providers/local/
|
||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { getListName, getRelaysFromList } from "../../helpers/nostr/lists";
|
||||
@ -36,12 +35,15 @@ function RelaySetCard({ set }: { set: NostrEvent }) {
|
||||
function BrowseRelaySetsPage() {
|
||||
const relays = useReadRelays();
|
||||
const { filter } = usePeopleListContext();
|
||||
const timeline = useTimelineLoader("relay-sets", relays, filter && { kinds: [kinds.Relaysets], ...filter }, {
|
||||
eventFilter: (e) => getRelaysFromList(e).length > 0,
|
||||
});
|
||||
|
||||
const relaySets = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const { loader, timeline: relaySets } = useTimelineLoader(
|
||||
"relay-sets",
|
||||
relays,
|
||||
filter && { kinds: [kinds.Relaysets], ...filter },
|
||||
{
|
||||
eventFilter: (e) => getRelaysFromList(e).length > 0,
|
||||
},
|
||||
);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
@ -49,11 +51,9 @@ function BrowseRelaySetsPage() {
|
||||
<PeopleListSelection />
|
||||
</Flex>
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
{relaySets.map((set) => (
|
||||
<RelaySetCard key={getEventUID(set)} set={set} />
|
||||
))}
|
||||
{relaySets?.map((set) => <RelaySetCard key={getEventUID(set)} set={set} />)}
|
||||
</IntersectionObserverProvider>
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
<TimelineActionAndStatus timeline={loader} />
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
24
src/views/relays/cache/database/memory.tsx
vendored
24
src/views/relays/cache/database/memory.tsx
vendored
@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Button, ButtonGroup, Card, Flex, Heading, Text } from "@chakra-ui/react";
|
||||
import { Button, ButtonGroup, Card, Flex, Heading, Text, useForceUpdate } from "@chakra-ui/react";
|
||||
import { NostrEvent } from "nostr-tools";
|
||||
|
||||
import { localRelay } from "../../../../services/local-relay";
|
||||
@ -17,40 +17,33 @@ async function importEvents(events: NostrEvent[]) {
|
||||
}
|
||||
async function exportEvents() {
|
||||
if (localRelay instanceof MemoryRelay) {
|
||||
return localRelay.events.getSortedEvents();
|
||||
return Array.from(localRelay.store.database.iterateTime(0, Infinity));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export default function MemoryDatabasePage() {
|
||||
const [update, setUpdate] = useState(0);
|
||||
const update = useForceUpdate();
|
||||
|
||||
useEffect(() => {
|
||||
if (localRelay instanceof MemoryRelay) {
|
||||
const sub = localRelay.events.onEvent.subscribe((e) => setUpdate((v) => v + 1));
|
||||
const sub = localRelay.store.database.inserted.subscribe(update);
|
||||
return () => sub.unsubscribe();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const count = useMemo(() => {
|
||||
if (localRelay instanceof MemoryRelay) return localRelay.events.events.size;
|
||||
if (localRelay instanceof MemoryRelay) return localRelay.store.database.events.size;
|
||||
return 0;
|
||||
}, [update]);
|
||||
|
||||
const kinds = useMemo(() => {
|
||||
if (localRelay instanceof MemoryRelay) {
|
||||
return getSortedKinds(Array.from(localRelay.events.events.values()));
|
||||
return getSortedKinds(Array.from(localRelay.store.database.iterateTime(0, Infinity)));
|
||||
}
|
||||
return {};
|
||||
}, [update]);
|
||||
|
||||
const handleClearData = async () => {
|
||||
if (localRelay instanceof MemoryRelay) {
|
||||
localRelay.events.clear();
|
||||
setUpdate(-1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text>Total events: {count ?? "Loading..."}</Text>
|
||||
@ -58,11 +51,6 @@ export default function MemoryDatabasePage() {
|
||||
<ImportEventsButton onLoad={importEvents} />
|
||||
<ExportEventsButton getEvents={exportEvents} />
|
||||
</ButtonGroup>
|
||||
<ButtonGroup flexWrap="wrap">
|
||||
<Button onClick={handleClearData} colorScheme="primary" variant="outline">
|
||||
Clear cache
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<Flex gap="2" wrap="wrap" alignItems="flex-start" w="full">
|
||||
{kinds && (
|
||||
<>
|
||||
|
@ -82,7 +82,7 @@ function NipTag({ nip, name }: { nip: number; name?: boolean }) {
|
||||
return (
|
||||
<Tooltip label={NIP_NAMES[nipStr]}>
|
||||
<Tag as="a" target="_blank" href={`https://github.com/nostr-protocol/nips/blob/master/${nipStr}.md`}>
|
||||
{name ? NIP_NAMES[nipStr] ?? nipNumber : nipNumber}
|
||||
{name ? (NIP_NAMES[nipStr] ?? nipNumber) : nipNumber}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
);
|
||||
|
@ -12,6 +12,7 @@ import PeopleListSelection from "../../../components/people-list-selection/peopl
|
||||
import { usePeopleListContext } from "../../../providers/local/people-list-provider";
|
||||
import useClientSideMuteFilter from "../../../hooks/use-client-side-mute-filter";
|
||||
import NoteFilterTypeButtons from "../../../components/note-filter-type-buttons";
|
||||
import { getSeenRelays } from "applesauce-core/helpers";
|
||||
|
||||
export default function RelayNotes({ relay }: { relay: string }) {
|
||||
useAppTitle(`${relay} - Notes`);
|
||||
@ -25,6 +26,7 @@ export default function RelayNotes({ relay }: { relay: string }) {
|
||||
const muteFilter = useClientSideMuteFilter();
|
||||
const eventFilter = useCallback(
|
||||
(event: NostrEvent) => {
|
||||
if (!getSeenRelays(event)?.has(relay)) return false;
|
||||
if (muteFilter(event)) return false;
|
||||
if (!showReplies.isOpen && isReply(event)) return false;
|
||||
if (!showReposts.isOpen && isRepost(event)) return false;
|
||||
@ -32,10 +34,15 @@ export default function RelayNotes({ relay }: { relay: string }) {
|
||||
},
|
||||
[timelineEventFilter, showReplies.isOpen, showReposts.isOpen, muteFilter],
|
||||
);
|
||||
const timeline = useTimelineLoader(`${relay}-notes`, [relay], filter ? { ...filter, kinds: k } : undefined, {
|
||||
eventFilter,
|
||||
useCache: false,
|
||||
});
|
||||
const { loader, timeline } = useTimelineLoader(
|
||||
`${relay}-notes`,
|
||||
[relay],
|
||||
filter ? { ...filter, kinds: k } : undefined,
|
||||
{
|
||||
eventFilter,
|
||||
useCache: false,
|
||||
},
|
||||
);
|
||||
|
||||
const header = (
|
||||
<Flex gap="2" wrap="wrap" px={["2", 0]}>
|
||||
@ -46,5 +53,5 @@ export default function RelayNotes({ relay }: { relay: string }) {
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return <TimelinePage timeline={timeline} header={header} />;
|
||||
return <TimelinePage loader={loader} timeline={timeline} header={header} />;
|
||||
}
|
||||
|
@ -2,18 +2,19 @@ import { Flex } from "@chakra-ui/react";
|
||||
|
||||
import { RELAY_REVIEW_LABEL } from "../../../helpers/nostr/reviews";
|
||||
import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import RelayReviewNote from "../components/relay-review-note";
|
||||
import { useAppTitle } from "../../../hooks/use-app-title";
|
||||
import { usePeopleListContext } from "../../../providers/local/people-list-provider";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../../providers/local/intersection-observer";
|
||||
|
||||
export default function RelayReviews({ relay }: { relay: string }) {
|
||||
useAppTitle(`${relay} - Reviews`);
|
||||
const readRelays = useReadRelays();
|
||||
|
||||
const { filter } = usePeopleListContext();
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: reviews } = useTimelineLoader(
|
||||
`${relay}-reviews`,
|
||||
readRelays,
|
||||
filter
|
||||
@ -25,14 +26,13 @@ export default function RelayReviews({ relay }: { relay: string }) {
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const events = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2">
|
||||
{events.map((event) => (
|
||||
<RelayReviewNote key={event.id} event={event} hideUrl />
|
||||
))}
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
{reviews?.map((event) => <RelayReviewNote key={event.id} event={event} hideUrl />)}
|
||||
</IntersectionObserverProvider>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import PeopleListSelection from "../../../components/people-list-selection/people-list-selection";
|
||||
import { usePeopleListContext } from "../../../providers/local/people-list-provider";
|
||||
import IntersectionObserverProvider from "../../../providers/local/intersection-observer";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import TimelineActionAndStatus from "../../../components/timeline/timeline-action-and-status";
|
||||
import UserAvatarLink from "../../../components/user/user-avatar-link";
|
||||
@ -36,7 +35,7 @@ function UserCard({ list, pubkey }: { list: NostrEvent; pubkey: string }) {
|
||||
export default function RelayUsersTab({ relay }: { relay: string }) {
|
||||
useAppTitle(`${relay} - Users`);
|
||||
const { filter } = usePeopleListContext();
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: lists } = useTimelineLoader(
|
||||
`${relay}-users`,
|
||||
[relay],
|
||||
filter && { ...filter, kinds: [kinds.RelayList], "#r": getRelayVariations(relay) },
|
||||
@ -44,9 +43,7 @@ export default function RelayUsersTab({ relay }: { relay: string }) {
|
||||
eventFilter: (e) => getRelaysFromList(e).includes(relay),
|
||||
},
|
||||
);
|
||||
|
||||
const lists = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2">
|
||||
@ -55,12 +52,10 @@ export default function RelayUsersTab({ relay }: { relay: string }) {
|
||||
</Flex>
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<SimpleGrid columns={[1, 1, 2, 3, 4]} spacing="2">
|
||||
{lists.map((list) => (
|
||||
<UserCard key={getEventUID(list)} pubkey={list.pubkey} list={list} />
|
||||
))}
|
||||
{lists?.map((list) => <UserCard key={getEventUID(list)} pubkey={list.pubkey} list={list} />)}
|
||||
</SimpleGrid>
|
||||
</IntersectionObserverProvider>
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
<TimelineActionAndStatus timeline={loader} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import RelayReviewNote from "./components/relay-review-note";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
@ -17,7 +16,7 @@ function RelayReviewsPage() {
|
||||
const readRelays = useReadRelays();
|
||||
|
||||
const { filter } = usePeopleListContext();
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: reviews } = useTimelineLoader(
|
||||
"relay-reviews",
|
||||
readRelays,
|
||||
filter
|
||||
@ -28,10 +27,7 @@ function RelayReviewsPage() {
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const reviews = useSubject(timeline.timeline);
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
@ -43,9 +39,7 @@ function RelayReviewsPage() {
|
||||
<PeopleListSelection />
|
||||
<Heading size="md">Relay Reviews</Heading>
|
||||
</Flex>
|
||||
{reviews.map((event) => (
|
||||
<RelayReviewNote key={event.id} event={event} />
|
||||
))}
|
||||
{reviews?.map((event) => <RelayReviewNote key={event.id} event={event} />)}
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
|
@ -8,13 +8,11 @@ import { LightningIcon } from "../../../components/icons";
|
||||
import { readablizeSats } from "../../../helpers/bolt11";
|
||||
import useStreamChatTimeline from "../stream/stream-chat/use-stream-chat-timeline";
|
||||
import { ParsedStream } from "../../../helpers/nostr/stream";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import UserAvatarLink from "../../../components/user/user-avatar-link";
|
||||
|
||||
export default function TopZappers({ stream, ...props }: FlexProps & { stream: ParsedStream }) {
|
||||
const timeline = useStreamChatTimeline(stream);
|
||||
const events = useSubject(timeline.timeline);
|
||||
const zaps = useMemo(() => parseZapEvents(events.filter((e) => e.kind === kinds.Zap)), [events]);
|
||||
const { timeline } = useStreamChatTimeline(stream);
|
||||
const zaps = useMemo(() => parseZapEvents(timeline.filter((e) => e.kind === kinds.Zap)), [timeline]);
|
||||
|
||||
const totals: Record<string, number> = {};
|
||||
for (const zap of zaps) {
|
||||
|
@ -9,10 +9,10 @@ import ChatMessageForm from "../stream/stream-chat/stream-chat-form";
|
||||
import { ParsedStream } from "../../../helpers/nostr/stream";
|
||||
|
||||
function ChatCard({ stream }: { stream: ParsedStream }) {
|
||||
const timeline = useStreamChatTimeline(stream);
|
||||
const { loader } = useStreamChatTimeline(stream);
|
||||
|
||||
const scrollBox = useRef<HTMLDivElement | null>(null);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<Flex flex={1} direction="column" overflow="hidden" p={0}>
|
||||
|
@ -7,7 +7,6 @@ import "./styles.css";
|
||||
import "react-mosaic-component/react-mosaic-component.css";
|
||||
|
||||
import useParsedStreams from "../../../hooks/use-parsed-streams";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { ParsedStream, STREAM_KIND, getATag } from "../../../helpers/nostr/stream";
|
||||
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import RequireCurrentAccount from "../../../providers/route/require-current-account";
|
||||
@ -72,7 +71,7 @@ function StreamModerationPage() {
|
||||
const account = useCurrentAccount()!;
|
||||
const readRelays = useReadRelays();
|
||||
|
||||
const timeline = useTimelineLoader(account.pubkey + "-streams", readRelays, [
|
||||
const { loader, timeline } = useTimelineLoader(account.pubkey + "-streams", readRelays, [
|
||||
{
|
||||
authors: [account.pubkey],
|
||||
kinds: [STREAM_KIND],
|
||||
@ -80,8 +79,7 @@ function StreamModerationPage() {
|
||||
{ "#p": [account.pubkey], kinds: [STREAM_KIND] },
|
||||
]);
|
||||
|
||||
const streamEvents = useSubject(timeline.timeline);
|
||||
const streams = useParsedStreams(streamEvents);
|
||||
const streams = useParsedStreams(timeline);
|
||||
|
||||
const [selected, setSelected] = useState<ParsedStream>();
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ReactNode, memo, useMemo, useState } from "react";
|
||||
import { useInterval } from "react-use";
|
||||
import { Button, ButtonGroup, Divider, Flex, Heading } from "@chakra-ui/react";
|
||||
import dayjs from "dayjs";
|
||||
import { useInterval, useObservable } from "react-use";
|
||||
|
||||
import useCurrentAccount from "../../../hooks/use-current-account";
|
||||
import useStreamChatTimeline from "../stream/stream-chat/use-stream-chat-timeline";
|
||||
@ -57,11 +57,7 @@ function UserCard({ pubkey }: { pubkey: string }) {
|
||||
|
||||
function UsersCard({ stream }: { stream: ParsedStream }) {
|
||||
const account = useCurrentAccount()!;
|
||||
const streamChatTimeline = useStreamChatTimeline(stream);
|
||||
|
||||
// refresh when a new event
|
||||
useObservable(streamChatTimeline.events.onEvent);
|
||||
const chatEvents = streamChatTimeline.events.getSortedEvents();
|
||||
const { loader, timeline: chatEvents } = useStreamChatTimeline(stream);
|
||||
|
||||
const muteList = useUserMuteList(account.pubkey);
|
||||
const pubkeysInChat = useMemo(() => {
|
||||
|
@ -1,18 +1,16 @@
|
||||
import { memo } from "react";
|
||||
import { Flex } from "@chakra-ui/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
import { useObservable } from "react-use";
|
||||
|
||||
import useStreamChatTimeline from "../stream/stream-chat/use-stream-chat-timeline";
|
||||
import ZapMessageMemo from "../stream/stream-chat/zap-message";
|
||||
import { ParsedStream } from "../../../helpers/nostr/stream";
|
||||
|
||||
function ZapsCard({ stream }: { stream: ParsedStream }) {
|
||||
const streamChatTimeline = useStreamChatTimeline(stream);
|
||||
const { timeline } = useStreamChatTimeline(stream);
|
||||
|
||||
// refresh when a new event
|
||||
useObservable(streamChatTimeline.events.onEvent);
|
||||
const zapMessages = streamChatTimeline.events.getSortedEvents().filter((event) => {
|
||||
const zapMessages = timeline.filter((event) => {
|
||||
if (stream.starts && event.created_at < stream.starts) return false;
|
||||
if (stream.ends && event.created_at > stream.ends) return false;
|
||||
if (event.kind !== kinds.Zap) return false;
|
||||
@ -21,9 +19,7 @@ function ZapsCard({ stream }: { stream: ParsedStream }) {
|
||||
|
||||
return (
|
||||
<Flex flex={1} p="2" gap="2" overflowY="auto" overflowX="hidden" flexDirection="column">
|
||||
{zapMessages.map((event) => (
|
||||
<ZapMessageMemo key={event.id} zap={event} stream={stream} />
|
||||
))}
|
||||
{zapMessages?.map((event) => <ZapMessageMemo key={event.id} zap={event} stream={stream} />)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { Flex, Heading, SimpleGrid, Switch } from "@chakra-ui/react";
|
||||
import { Filter } from "nostr-tools";
|
||||
import { Filter, kinds } from "nostr-tools";
|
||||
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import StreamCard from "./components/stream-card";
|
||||
import { STREAM_KIND } from "../../helpers/nostr/stream";
|
||||
import useRelaysChanged from "../../hooks/use-relays-changed";
|
||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
|
||||
@ -39,19 +37,17 @@ function StreamsPage() {
|
||||
const query = useMemo<Filter | Filter[] | undefined>(() => {
|
||||
if (!filter) return undefined;
|
||||
return [
|
||||
{ authors: filter.authors, kinds: [STREAM_KIND] },
|
||||
{ "#p": filter.authors, kinds: [STREAM_KIND] },
|
||||
{ authors: filter.authors, kinds: [kinds.LiveEvent] },
|
||||
{ "#p": filter.authors, kinds: [kinds.LiveEvent] },
|
||||
];
|
||||
}, [filter]);
|
||||
|
||||
const timeline = useTimelineLoader(`${listId ?? "global"}-streams`, relays, query, { eventFilter });
|
||||
const { loader, timeline } = useTimelineLoader(`${listId ?? "global"}-streams`, relays, query, { eventFilter });
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
useRelaysChanged(relays, () => timeline.reset());
|
||||
useRelaysChanged(relays, () => loader.reset());
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
|
||||
const events = useSubject(timeline.timeline);
|
||||
const streams = useParsedStreams(events);
|
||||
const streams = useParsedStreams(timeline);
|
||||
|
||||
const liveStreams = streams.filter((stream) => stream.status === "live");
|
||||
const endedStreams = streams.filter((stream) => stream.status === "ended");
|
||||
@ -85,7 +81,7 @@ function StreamsPage() {
|
||||
</SimpleGrid>
|
||||
</>
|
||||
)}
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
<TimelineActionAndStatus timeline={loader} />
|
||||
</IntersectionObserverProvider>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { forwardRef } from "react";
|
||||
import { Flex, FlexProps } from "@chakra-ui/react";
|
||||
import { css } from "@emotion/react";
|
||||
import { kinds } from "nostr-tools";
|
||||
|
||||
import { ParsedStream, STREAM_CHAT_MESSAGE_KIND } from "../../../../helpers/nostr/stream";
|
||||
import useSubject from "../../../../hooks/use-subject";
|
||||
import { ParsedStream } from "../../../../helpers/nostr/stream";
|
||||
import useStreamChatTimeline from "./use-stream-chat-timeline";
|
||||
import ChatMessage from "./chat-message";
|
||||
import ZapMessage from "./zap-message";
|
||||
@ -20,8 +20,7 @@ const StreamChatLog = forwardRef<
|
||||
HTMLDivElement,
|
||||
Omit<FlexProps, "children"> & { stream: ParsedStream; hideScrollbar?: boolean }
|
||||
>(({ stream, hideScrollbar, ...props }, ref) => {
|
||||
const timeline = useStreamChatTimeline(stream);
|
||||
const events = useSubject(timeline.timeline);
|
||||
const { timeline: events } = useStreamChatTimeline(stream);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@ -34,7 +33,7 @@ const StreamChatLog = forwardRef<
|
||||
{...props}
|
||||
>
|
||||
{events.map((event) =>
|
||||
event.kind === STREAM_CHAT_MESSAGE_KIND ? (
|
||||
event.kind === kinds.LiveChatMessage ? (
|
||||
<ChatMessage key={event.id} event={event} stream={stream} />
|
||||
) : (
|
||||
<ZapMessage key={event.id} zap={event} stream={stream} />
|
||||
|
@ -18,10 +18,10 @@ export default function StreamChat({
|
||||
displayMode,
|
||||
...props
|
||||
}: CardProps & { stream: ParsedStream; actions?: React.ReactNode; displayMode?: ChatDisplayMode }) {
|
||||
const timeline = useStreamChatTimeline(stream);
|
||||
const { loader } = useStreamChatTimeline(stream);
|
||||
|
||||
const scrollBox = useRef<HTMLDivElement | null>(null);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
const isPopup = !!displayMode;
|
||||
const isChatLog = displayMode === "log";
|
||||
|
@ -12,7 +12,6 @@ import PostRepostsTab from "./tabs/reposts";
|
||||
import PostQuotesTab from "./tabs/quotes";
|
||||
import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import { getContentTagRefs } from "../../../helpers/nostr/event";
|
||||
import { CORRECTION_EVENT_KIND } from "../../../helpers/nostr/corrections";
|
||||
import CorrectionsTab from "./tabs/corrections";
|
||||
@ -34,8 +33,9 @@ export default function DetailsTabs({ post }: { post: ThreadItem }) {
|
||||
const zaps = useEventZaps(getEventUID(post.event));
|
||||
|
||||
const readRelays = useReadRelays();
|
||||
const timeline = useTimelineLoader(`${post.event.id}-thread-refs`, readRelays, { "#e": [post.event.id] });
|
||||
const events = useSubject(timeline.timeline);
|
||||
const { loader, timeline: events } = useTimelineLoader(`${post.event.id}-thread-refs`, readRelays, {
|
||||
"#e": [post.event.id],
|
||||
});
|
||||
|
||||
const reactions = events.filter((e) => e.kind === kinds.Reaction);
|
||||
const reposts = events.filter((e) => e.kind === kinds.Repost || e.kind === kinds.GenericRepost);
|
||||
|
@ -2,24 +2,24 @@ import { Flex, Heading } from "@chakra-ui/react";
|
||||
|
||||
import VerticalPageLayout from "../../../components/vertical-page-layout";
|
||||
import { useReadRelays } from "../../../hooks/use-client-relays";
|
||||
import useSubject from "../../../hooks/use-subject";
|
||||
import useTimelineLoader from "../../../hooks/use-timeline-loader";
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../../providers/local/people-list-provider";
|
||||
import BackButton from "../../../components/router/back-button";
|
||||
import PeopleListSelection from "../../../components/people-list-selection/people-list-selection";
|
||||
import CorrectionCard from "./correction-card";
|
||||
import { CORRECTION_EVENT_KIND } from "../../../helpers/nostr/corrections";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../../providers/local/intersection-observer";
|
||||
|
||||
function CorrectionsPage() {
|
||||
const { listId, filter } = usePeopleListContext();
|
||||
const readRelays = useReadRelays();
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: corrections } = useTimelineLoader(
|
||||
`${listId}-corrections`,
|
||||
readRelays,
|
||||
filter ? [{ kinds: [CORRECTION_EVENT_KIND], ...filter }] : undefined,
|
||||
);
|
||||
|
||||
const corrections = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
@ -29,9 +29,9 @@ function CorrectionsPage() {
|
||||
<PeopleListSelection />
|
||||
</Flex>
|
||||
|
||||
{corrections.map((correction) => (
|
||||
<CorrectionCard correction={correction} key={correction.id} />
|
||||
))}
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
{corrections?.map((correction) => <CorrectionCard correction={correction} key={correction.id} />)}
|
||||
</IntersectionObserverProvider>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import EmbeddedDM from "../../components/embed-event/event-types/embedded-dm";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { ChevronLeftIcon } from "../../components/icons";
|
||||
@ -41,7 +40,7 @@ export function DMTimelinePage() {
|
||||
[clientMuteFilter],
|
||||
);
|
||||
const readRelays = useReadRelays();
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: dms } = useTimelineLoader(
|
||||
`${listId ?? "global"}-dm-feed`,
|
||||
readRelays,
|
||||
filter
|
||||
@ -55,9 +54,7 @@ export function DMTimelinePage() {
|
||||
: { kinds: [kinds.EncryptedDirectMessage] },
|
||||
{ eventFilter },
|
||||
);
|
||||
|
||||
const dms = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
@ -68,7 +65,7 @@ export function DMTimelinePage() {
|
||||
<PeopleListSelection />
|
||||
</Flex>
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
{dms.map((dm) => (
|
||||
{dms?.map((dm) => (
|
||||
<ErrorBoundary key={dm.id} event={dm}>
|
||||
<DirectMessage dm={dm} />
|
||||
</ErrorBoundary>
|
||||
|
@ -13,7 +13,6 @@ import {
|
||||
DVM_TTS_RESULT_KIND,
|
||||
groupEventsIntoJobs,
|
||||
} from "../../../../helpers/nostr/dvm";
|
||||
import useSubject from "../../../../hooks/use-subject";
|
||||
import { DraftNostrEvent, NostrEvent } from "../../../../types/nostr-event";
|
||||
import relayScoreboardService from "../../../../services/relay-scoreboard";
|
||||
import useCurrentAccount from "../../../../hooks/use-current-account";
|
||||
@ -42,7 +41,7 @@ export default function NoteTextToSpeechPage({ note }: { note: NostrEvent }) {
|
||||
await publish("Request Reading", draft);
|
||||
}, [publish, note, readRelays, lang]);
|
||||
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: events } = useTimelineLoader(
|
||||
`${getEventUID(note)}-readings`,
|
||||
readRelays,
|
||||
[
|
||||
@ -54,7 +53,6 @@ export default function NoteTextToSpeechPage({ note }: { note: NostrEvent }) {
|
||||
].filter(Boolean) as Filter[],
|
||||
);
|
||||
|
||||
const events = useSubject(timeline.timeline);
|
||||
const jobs = groupEventsIntoJobs(events);
|
||||
|
||||
return (
|
||||
|
@ -13,12 +13,12 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
import dayjs from "dayjs";
|
||||
import codes from "iso-language-codes";
|
||||
import { Filter } from "nostr-tools";
|
||||
import { getEventUID } from "applesauce-core/helpers";
|
||||
|
||||
import { DraftNostrEvent, NostrEvent } from "../../../../types/nostr-event";
|
||||
import useTimelineLoader from "../../../../hooks/use-timeline-loader";
|
||||
import { getEventUID } from "../../../../helpers/nostr/event";
|
||||
import { useReadRelays } from "../../../../hooks/use-client-relays";
|
||||
import useSubject from "../../../../hooks/use-subject";
|
||||
import relayScoreboardService from "../../../../services/relay-scoreboard";
|
||||
import {
|
||||
DVM_STATUS_KIND,
|
||||
@ -29,7 +29,6 @@ import {
|
||||
import useCurrentAccount from "../../../../hooks/use-current-account";
|
||||
import TranslationJob from "./translation-job";
|
||||
import { usePublishEvent } from "../../../../providers/global/publish-provider";
|
||||
import { Filter } from "nostr-tools";
|
||||
|
||||
export function NoteTranslationsPage({ note }: { note: NostrEvent }) {
|
||||
const account = useCurrentAccount();
|
||||
@ -54,7 +53,7 @@ export function NoteTranslationsPage({ note }: { note: NostrEvent }) {
|
||||
await publish("Request Translation", draft);
|
||||
}, [publish, note, readRelays, lang]);
|
||||
|
||||
const timeline = useTimelineLoader(
|
||||
const { loader, timeline: events } = useTimelineLoader(
|
||||
`${getEventUID(note)}-translations`,
|
||||
readRelays,
|
||||
[
|
||||
@ -66,7 +65,6 @@ export function NoteTranslationsPage({ note }: { note: NostrEvent }) {
|
||||
].filter(Boolean) as Filter[],
|
||||
);
|
||||
|
||||
const events = useSubject(timeline.timeline);
|
||||
const jobs = Object.values(groupEventsIntoJobs(events));
|
||||
|
||||
return (
|
||||
|
@ -10,12 +10,10 @@ import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { ChevronLeftIcon } from "../../components/icons";
|
||||
import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter";
|
||||
import { EmbedEvent } from "../../components/embed-event";
|
||||
import { STREAM_CHAT_MESSAGE_KIND, STREAM_KIND } from "../../helpers/nostr/stream";
|
||||
import {
|
||||
BOOKMARK_LIST_KIND,
|
||||
BOOKMARK_LIST_SET_KIND,
|
||||
@ -44,12 +42,12 @@ const commonTimelineKinds = [
|
||||
kinds.Reaction,
|
||||
kinds.BadgeAward,
|
||||
kinds.BadgeDefinition,
|
||||
STREAM_KIND,
|
||||
kinds.LiveEvent,
|
||||
kinds.Contacts,
|
||||
kinds.Metadata,
|
||||
kinds.EncryptedDirectMessage,
|
||||
MUTE_LIST_KIND,
|
||||
STREAM_CHAT_MESSAGE_KIND,
|
||||
kinds.LiveChatMessage,
|
||||
kinds.EventDeletion,
|
||||
kinds.CommunityPostApproval,
|
||||
BOOKMARK_LIST_KIND,
|
||||
@ -57,6 +55,7 @@ const commonTimelineKinds = [
|
||||
PEOPLE_LIST_KIND,
|
||||
PIN_LIST_KIND,
|
||||
COMMUNITIES_LIST_KIND,
|
||||
kinds.ZapGoal,
|
||||
];
|
||||
|
||||
export function UnknownTimelinePage() {
|
||||
@ -73,10 +72,10 @@ export function UnknownTimelinePage() {
|
||||
[clientMuteFilter],
|
||||
);
|
||||
const readRelays = useReadRelays();
|
||||
const timeline = useTimelineLoader(`${listId ?? "global"}-unknown-feed`, readRelays, filter, { eventFilter });
|
||||
|
||||
const events = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const { loader, timeline } = useTimelineLoader(`${listId ?? "global"}-unknown-feed`, readRelays, filter, {
|
||||
eventFilter,
|
||||
});
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
@ -87,8 +86,8 @@ export function UnknownTimelinePage() {
|
||||
<PeopleListSelection />
|
||||
</Flex>
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
{events.map((dm) => (
|
||||
<UnknownEvent key={dm.id} event={dm} />
|
||||
{timeline.map((event) => (
|
||||
<UnknownEvent key={event.id} event={event} />
|
||||
))}
|
||||
</IntersectionObserverProvider>
|
||||
</VerticalPageLayout>
|
||||
|
@ -9,7 +9,6 @@ import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { TORRENT_KIND, validateTorrent } from "../../helpers/nostr/torrents";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import TorrentTableRow from "./components/torrent-table-row";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
@ -82,12 +81,10 @@ function TorrentsPage() {
|
||||
if (tags.length > 0) return { ...filter, kinds: [TORRENT_KIND], "#t": tags };
|
||||
else return { ...filter, kinds: [TORRENT_KIND] };
|
||||
}, [tags.join(","), filter]);
|
||||
const timeline = useTimelineLoader(`${listId || "global"}-torrents`, relays, query, {
|
||||
const { loader, timeline: torrents } = useTimelineLoader(`${listId || "global"}-torrents`, relays, query, {
|
||||
eventFilter,
|
||||
});
|
||||
|
||||
const torrents = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
const account = useCurrentAccount();
|
||||
|
||||
@ -115,11 +112,7 @@ function TorrentsPage() {
|
||||
<Th />
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{torrents.map((torrent) => (
|
||||
<TorrentTableRow key={torrent.id} torrent={torrent} />
|
||||
))}
|
||||
</Tbody>
|
||||
<Tbody>{torrents?.map((torrent) => <TorrentTableRow key={torrent.id} torrent={torrent} />)}</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</IntersectionObserverProvider>
|
||||
|
@ -3,7 +3,6 @@ import { Flex } from "@chakra-ui/react";
|
||||
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
import { STEMSTR_RELAY, STEMSTR_TRACK_KIND } from "../../helpers/nostr/stemstr";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider";
|
||||
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
|
||||
@ -27,12 +26,15 @@ function TracksPage() {
|
||||
},
|
||||
[clientMuteFilter],
|
||||
);
|
||||
const timeline = useTimelineLoader(`${listId}-tracks`, relays, filter && { kinds: [STEMSTR_TRACK_KIND], ...filter }, {
|
||||
eventFilter,
|
||||
});
|
||||
const tracks = useSubject(timeline.timeline);
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const { loader, timeline: tracks } = useTimelineLoader(
|
||||
`${listId}-tracks`,
|
||||
relays,
|
||||
filter && { kinds: [STEMSTR_TRACK_KIND], ...filter },
|
||||
{
|
||||
eventFilter,
|
||||
},
|
||||
);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<VerticalPageLayout>
|
||||
@ -40,9 +42,7 @@ function TracksPage() {
|
||||
<PeopleListSelection />
|
||||
</Flex>
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
{tracks.map((track) => (
|
||||
<TrackCard track={track} />
|
||||
))}
|
||||
{tracks?.map((track) => <TrackCard key={track.id} track={track} />)}
|
||||
</IntersectionObserverProvider>
|
||||
</VerticalPageLayout>
|
||||
);
|
||||
|
@ -3,7 +3,6 @@ import { kinds } from "nostr-tools";
|
||||
|
||||
import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import TimelineActionAndStatus from "../../components/timeline/timeline-action-and-status";
|
||||
@ -15,23 +14,21 @@ export default function UserArticlesTab() {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const readRelays = useAdditionalRelayContext();
|
||||
|
||||
const timeline = useTimelineLoader(pubkey + "-articles", readRelays, {
|
||||
const { loader, timeline: articles } = useTimelineLoader(pubkey + "-articles", readRelays, {
|
||||
authors: [pubkey],
|
||||
kinds: [kinds.LongFormArticle],
|
||||
});
|
||||
|
||||
const articles = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<VerticalPageLayout>
|
||||
{articles.map((article) => (
|
||||
{articles?.map((article) => (
|
||||
<ErrorBoundary key={article.id} event={article}>
|
||||
<ArticleCard article={article} />
|
||||
</ErrorBoundary>
|
||||
))}
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
<TimelineActionAndStatus timeline={loader} />
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
|
@ -4,7 +4,6 @@ import { useOutletContext } from "react-router-dom";
|
||||
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import VerticalPageLayout from "../../components/vertical-page-layout";
|
||||
@ -62,24 +61,20 @@ export default function UserDMsTab() {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const readRelays = useAdditionalRelayContext();
|
||||
|
||||
const timeline = useTimelineLoader(pubkey + "-articles", readRelays, [
|
||||
const { loader, timeline: dms } = useTimelineLoader(pubkey + "-articles", readRelays, [
|
||||
{
|
||||
authors: [pubkey],
|
||||
kinds: [kinds.EncryptedDirectMessage],
|
||||
},
|
||||
{ "#p": [pubkey], kinds: [kinds.EncryptedDirectMessage] },
|
||||
]);
|
||||
|
||||
const dms = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<VerticalPageLayout>
|
||||
{dms.map((dm) => (
|
||||
<DirectMessage key={dm.id} dm={dm} pubkey={pubkey} />
|
||||
))}
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
{dms?.map((dm) => <DirectMessage key={dm.id} dm={dm} pubkey={pubkey} />)}
|
||||
<TimelineActionAndStatus timeline={loader} />
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import { Heading, SimpleGrid } from "@chakra-ui/react";
|
||||
import { useObservable } from "applesauce-react";
|
||||
|
||||
import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { getEventUID } from "../../helpers/nostr/event";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
@ -17,16 +17,15 @@ export default function UserEmojiPacksTab() {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const readRelays = useAdditionalRelayContext();
|
||||
|
||||
const timeline = useTimelineLoader(pubkey + "-emoji-packs", readRelays, {
|
||||
const { loader, timeline: packs } = useTimelineLoader(pubkey + "-emoji-packs", readRelays, {
|
||||
authors: [pubkey],
|
||||
kinds: [EMOJI_PACK_KIND],
|
||||
});
|
||||
const packs = useSubject(timeline.timeline);
|
||||
|
||||
const favoritePacks = useFavoriteEmojiPacks(pubkey);
|
||||
const favorites = useReplaceableEvents(favoritePacks && getPackCordsFromFavorites(favoritePacks));
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
|
@ -5,7 +5,6 @@ import { Event, kinds } from "nostr-tools";
|
||||
|
||||
import { useReadRelays } from "../../hooks/use-client-relays";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import TimelineActionAndStatus from "../../components/timeline/timeline-action-and-status";
|
||||
@ -28,22 +27,20 @@ export default function UserFollowersTab() {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const readRelays = useReadRelays();
|
||||
|
||||
const timeline = useTimelineLoader(`${pubkey}-followers`, readRelays, {
|
||||
const { loader, timeline: events } = useTimelineLoader(`${pubkey}-followers`, readRelays, {
|
||||
"#p": [pubkey],
|
||||
kinds: [kinds.Contacts],
|
||||
});
|
||||
|
||||
const lists = useSubject(timeline.timeline);
|
||||
const followerEvents = useSubject(timeline.timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
const followers = useMemo(() => {
|
||||
const dedupe = new Map<string, Event>();
|
||||
for (const event of followerEvents) {
|
||||
for (const event of events) {
|
||||
dedupe.set(event.pubkey, event);
|
||||
}
|
||||
return Array.from(dedupe.values());
|
||||
}, [followerEvents]);
|
||||
}, [events]);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
@ -52,7 +49,7 @@ export default function UserFollowersTab() {
|
||||
<FollowerItem key={event.pubkey} event={event} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
<TimelineActionAndStatus timeline={timeline} />
|
||||
<TimelineActionAndStatus timeline={loader} />
|
||||
</IntersectionObserverProvider>
|
||||
);
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import { Flex, SimpleGrid } from "@chakra-ui/react";
|
||||
import { SimpleGrid } from "@chakra-ui/react";
|
||||
|
||||
import { useAdditionalRelayContext } from "../../providers/local/additional-relay-context";
|
||||
import useTimelineLoader from "../../hooks/use-timeline-loader";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { getEventUID } from "../../helpers/nostr/event";
|
||||
import IntersectionObserverProvider from "../../providers/local/intersection-observer";
|
||||
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
|
||||
@ -15,21 +14,17 @@ export default function UserGoalsTab() {
|
||||
const { pubkey } = useOutletContext() as { pubkey: string };
|
||||
const readRelays = useAdditionalRelayContext();
|
||||
|
||||
const timeline = useTimelineLoader(pubkey + "-goals", readRelays, {
|
||||
const { loader, timeline: goals } = useTimelineLoader(pubkey + "-goals", readRelays, {
|
||||
authors: [pubkey],
|
||||
kinds: [GOAL_KIND],
|
||||
});
|
||||
const goals = useSubject(timeline.timeline);
|
||||
|
||||
const callback = useTimelineCurserIntersectionCallback(timeline);
|
||||
const callback = useTimelineCurserIntersectionCallback(loader);
|
||||
|
||||
return (
|
||||
<IntersectionObserverProvider callback={callback}>
|
||||
<VerticalPageLayout>
|
||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing="2">
|
||||
{goals.map((goal) => (
|
||||
<GoalCard key={getEventUID(goal)} goal={goal} />
|
||||
))}
|
||||
{goals?.map((goal) => <GoalCard key={getEventUID(goal)} goal={goal} />)}
|
||||
</SimpleGrid>
|
||||
</VerticalPageLayout>
|
||||
</IntersectionObserverProvider>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user