[codex] try vue virtual scroller for messages (#1103)
* try vue virtual scroller for messages * hide inactive virtual scroller rows
This commit is contained in:
Generated
+12
-112
@@ -16,7 +16,8 @@
|
|||||||
"node-edge-tts": "^1.2.10",
|
"node-edge-tts": "^1.2.10",
|
||||||
"node-pty": "^1.1.0",
|
"node-pty": "^1.1.0",
|
||||||
"socket.io": "^4.8.3",
|
"socket.io": "^4.8.3",
|
||||||
"socket.io-client": "^4.8.3"
|
"socket.io-client": "^4.8.3",
|
||||||
|
"vue-virtual-scroller": "^3.0.4"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"hermes-web-ui": "bin/hermes-web-ui.mjs"
|
"hermes-web-ui": "bin/hermes-web-ui.mjs"
|
||||||
@@ -157,7 +158,6 @@
|
|||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
|
||||||
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -167,7 +167,6 @@
|
|||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
||||||
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -177,7 +176,6 @@
|
|||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
|
||||||
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.29.7"
|
"@babel/types": "^7.29.7"
|
||||||
@@ -193,7 +191,6 @@
|
|||||||
"version": "7.29.7",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
|
||||||
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.29.7",
|
"@babel/helper-string-parser": "^7.29.7",
|
||||||
@@ -1401,7 +1398,6 @@
|
|||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
@@ -1659,9 +1655,6 @@
|
|||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1683,9 +1676,6 @@
|
|||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1707,9 +1697,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1731,9 +1718,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1755,9 +1739,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -1779,9 +1760,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2012,9 +1990,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2032,9 +2007,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2052,9 +2024,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2072,9 +2041,6 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2092,9 +2058,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2112,9 +2075,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2293,9 +2253,6 @@
|
|||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2310,9 +2267,6 @@
|
|||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2327,9 +2281,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2344,9 +2295,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2361,9 +2309,6 @@
|
|||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2378,9 +2323,6 @@
|
|||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2395,9 +2337,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2412,9 +2351,6 @@
|
|||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2429,9 +2365,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2446,9 +2379,6 @@
|
|||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2463,9 +2393,6 @@
|
|||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2480,9 +2407,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -2497,9 +2421,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -3562,7 +3483,6 @@
|
|||||||
"version": "3.5.35",
|
"version": "3.5.35",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.35.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.35.tgz",
|
||||||
"integrity": "sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==",
|
"integrity": "sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.29.3",
|
"@babel/parser": "^7.29.3",
|
||||||
@@ -3576,7 +3496,6 @@
|
|||||||
"version": "3.5.35",
|
"version": "3.5.35",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.35.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.35.tgz",
|
||||||
"integrity": "sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==",
|
"integrity": "sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-core": "3.5.35",
|
"@vue/compiler-core": "3.5.35",
|
||||||
@@ -3587,7 +3506,6 @@
|
|||||||
"version": "3.5.35",
|
"version": "3.5.35",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.35.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.35.tgz",
|
||||||
"integrity": "sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==",
|
"integrity": "sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.29.3",
|
"@babel/parser": "^7.29.3",
|
||||||
@@ -3605,7 +3523,6 @@
|
|||||||
"version": "3.5.35",
|
"version": "3.5.35",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.35.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.35.tgz",
|
||||||
"integrity": "sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==",
|
"integrity": "sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.5.35",
|
"@vue/compiler-dom": "3.5.35",
|
||||||
@@ -3681,7 +3598,6 @@
|
|||||||
"version": "3.5.35",
|
"version": "3.5.35",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.35.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.35.tgz",
|
||||||
"integrity": "sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==",
|
"integrity": "sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "3.5.35"
|
"@vue/shared": "3.5.35"
|
||||||
@@ -3691,7 +3607,6 @@
|
|||||||
"version": "3.5.35",
|
"version": "3.5.35",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.35.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.35.tgz",
|
||||||
"integrity": "sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==",
|
"integrity": "sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/reactivity": "3.5.35",
|
"@vue/reactivity": "3.5.35",
|
||||||
@@ -3702,7 +3617,6 @@
|
|||||||
"version": "3.5.35",
|
"version": "3.5.35",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.35.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.35.tgz",
|
||||||
"integrity": "sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==",
|
"integrity": "sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/reactivity": "3.5.35",
|
"@vue/reactivity": "3.5.35",
|
||||||
@@ -3715,7 +3629,6 @@
|
|||||||
"version": "3.5.35",
|
"version": "3.5.35",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.35.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.35.tgz",
|
||||||
"integrity": "sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==",
|
"integrity": "sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-ssr": "3.5.35",
|
"@vue/compiler-ssr": "3.5.35",
|
||||||
@@ -3729,7 +3642,6 @@
|
|||||||
"version": "3.5.35",
|
"version": "3.5.35",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.35.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.35.tgz",
|
||||||
"integrity": "sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==",
|
"integrity": "sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@vue/test-utils": {
|
"node_modules/@vue/test-utils": {
|
||||||
@@ -4687,7 +4599,6 @@
|
|||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/cytoscape": {
|
"node_modules/cytoscape": {
|
||||||
@@ -5613,7 +5524,6 @@
|
|||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
||||||
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12"
|
"node": ">=0.12"
|
||||||
@@ -5751,7 +5661,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/etag": {
|
"node_modules/etag": {
|
||||||
@@ -7265,9 +7174,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7289,9 +7195,6 @@
|
|||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7313,9 +7216,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7337,9 +7237,6 @@
|
|||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -7470,7 +7367,6 @@
|
|||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
@@ -7879,7 +7775,6 @@
|
|||||||
"version": "3.3.12",
|
"version": "3.3.12",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
||||||
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -8343,7 +8238,6 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
@@ -8525,7 +8419,6 @@
|
|||||||
"version": "8.5.15",
|
"version": "8.5.15",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
|
||||||
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -9659,7 +9552,6 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -10363,7 +10255,7 @@
|
|||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
|
||||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@@ -10957,7 +10849,6 @@
|
|||||||
"version": "3.5.35",
|
"version": "3.5.35",
|
||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.35.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.35.tgz",
|
||||||
"integrity": "sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==",
|
"integrity": "sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.5.35",
|
"@vue/compiler-dom": "3.5.35",
|
||||||
@@ -11051,6 +10942,15 @@
|
|||||||
"typescript": ">=5.0.0"
|
"typescript": ">=5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-virtual-scroller": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-virtual-scroller/-/vue-virtual-scroller-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-3qh3c9VUVysuXynaa4fVZ3ncx3VgD7EPRiQcj+jUVZl5u/TTkD3c27XvSEu3JGJfsJt/vVTVziZ3djiiHtW4cQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vueuc": {
|
"node_modules/vueuc": {
|
||||||
"version": "0.4.65",
|
"version": "0.4.65",
|
||||||
"resolved": "https://registry.npmjs.org/vueuc/-/vueuc-0.4.65.tgz",
|
"resolved": "https://registry.npmjs.org/vueuc/-/vueuc-0.4.65.tgz",
|
||||||
|
|||||||
+2
-1
@@ -63,7 +63,8 @@
|
|||||||
"node-edge-tts": "^1.2.10",
|
"node-edge-tts": "^1.2.10",
|
||||||
"node-pty": "^1.1.0",
|
"node-pty": "^1.1.0",
|
||||||
"socket.io": "^4.8.3",
|
"socket.io": "^4.8.3",
|
||||||
"socket.io-client": "^4.8.3"
|
"socket.io-client": "^4.8.3",
|
||||||
|
"vue-virtual-scroller": "^3.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@koa/bodyparser": "^5.0.0",
|
"@koa/bodyparser": "^5.0.0",
|
||||||
|
|||||||
@@ -1,10 +1,26 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch, type ComponentPublicInstance } from "vue";
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||||
|
import {
|
||||||
|
DynamicScroller,
|
||||||
|
DynamicScrollerItem,
|
||||||
|
type DynamicScrollerExposed,
|
||||||
|
type ScrollToOptions,
|
||||||
|
} from "vue-virtual-scroller";
|
||||||
|
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
|
||||||
|
|
||||||
type VirtualItem = {
|
type VirtualItem = {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AnchorAlign = "start" | "center";
|
||||||
|
type AnchorTarget = {
|
||||||
|
token: number;
|
||||||
|
index: number;
|
||||||
|
messageId: string;
|
||||||
|
anchorId: string;
|
||||||
|
align: AnchorAlign;
|
||||||
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
messages: VirtualItem[];
|
messages: VirtualItem[];
|
||||||
estimatedItemHeight?: number;
|
estimatedItemHeight?: number;
|
||||||
@@ -32,127 +48,29 @@ defineSlots<{
|
|||||||
after?: () => any;
|
after?: () => any;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const scrollerRef = ref<HTMLElement | null>(null);
|
const hostRef = ref<HTMLElement | null>(null);
|
||||||
|
const scrollerRef = ref<DynamicScrollerExposed<VirtualItem> | null>(null);
|
||||||
const scrollTop = ref(0);
|
const scrollTop = ref(0);
|
||||||
const viewportHeight = ref(0);
|
const viewportHeight = ref(0);
|
||||||
const heightVersion = ref(0);
|
|
||||||
const measuredHeights = new Map<string, number>();
|
|
||||||
const observedElements = new Map<string, HTMLElement>();
|
|
||||||
const observers = new Map<string, ResizeObserver>();
|
|
||||||
let keepBottomUntil = 0;
|
let keepBottomUntil = 0;
|
||||||
let bottomFrame: number | null = null;
|
let bottomFrame: number | null = null;
|
||||||
|
let anchorFrame: number | null = null;
|
||||||
|
let anchorToken = 0;
|
||||||
|
let activeAnchorTarget: AnchorTarget | null = null;
|
||||||
|
|
||||||
const messageKeys = computed(() => props.messages.map(messageKey));
|
const messageKeys = computed(() => props.messages.map(messageKey));
|
||||||
|
const bufferPx = computed(() => Math.max(props.estimatedItemHeight, props.estimatedItemHeight * props.overscan));
|
||||||
|
|
||||||
function messageKey(message: VirtualItem): string {
|
function messageKey(message: VirtualItem): string {
|
||||||
return String(message.id);
|
return String(message.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function itemHeight(key: string): number {
|
function getScrollerElement(): HTMLElement | null {
|
||||||
return measuredHeights.get(key) || props.estimatedItemHeight;
|
return hostRef.value?.querySelector<HTMLElement>(".virtual-message-list") ?? null;
|
||||||
}
|
|
||||||
|
|
||||||
function measuredRowHeight(el: HTMLElement): number {
|
|
||||||
return Math.ceil(el.getBoundingClientRect().height || props.estimatedItemHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMeasuredHeight(key: string, height: number) {
|
|
||||||
const oldHeight = itemHeight(key);
|
|
||||||
if (oldHeight === height) return;
|
|
||||||
|
|
||||||
const el = scrollerRef.value;
|
|
||||||
const shouldKeepBottom = !!el && (Date.now() < keepBottomUntil || isNearBottom(64));
|
|
||||||
const index = messageKeys.value.indexOf(key);
|
|
||||||
if (index >= 0 && !shouldKeepBottom) {
|
|
||||||
const rowTop = layout.value.offsets[index] || 0;
|
|
||||||
const delta = height - oldHeight;
|
|
||||||
if (el && rowTop < scrollTop.value && delta !== 0) {
|
|
||||||
el.scrollTop = Math.max(0, el.scrollTop + delta);
|
|
||||||
syncViewport();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
measuredHeights.set(key, height);
|
|
||||||
heightVersion.value += 1;
|
|
||||||
if (shouldKeepBottom) scheduleScrollToBottom(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
const layout = computed(() => {
|
|
||||||
heightVersion.value;
|
|
||||||
const offsets: number[] = [];
|
|
||||||
let total = 0;
|
|
||||||
for (const key of messageKeys.value) {
|
|
||||||
offsets.push(total);
|
|
||||||
total += itemHeight(key);
|
|
||||||
}
|
|
||||||
return { offsets, total };
|
|
||||||
});
|
|
||||||
|
|
||||||
const visibleRange = computed(() => {
|
|
||||||
const count = props.messages.length;
|
|
||||||
if (count === 0) return { start: 0, end: -1 };
|
|
||||||
|
|
||||||
const overscanPx = props.estimatedItemHeight * props.overscan;
|
|
||||||
const startPx = Math.max(0, scrollTop.value - overscanPx);
|
|
||||||
const endPx = scrollTop.value + viewportHeight.value + overscanPx;
|
|
||||||
const { offsets } = layout.value;
|
|
||||||
|
|
||||||
let start = 0;
|
|
||||||
let low = 0;
|
|
||||||
let high = count - 1;
|
|
||||||
while (low <= high) {
|
|
||||||
const mid = Math.floor((low + high) / 2);
|
|
||||||
const bottom = offsets[mid] + itemHeight(messageKeys.value[mid]);
|
|
||||||
if (bottom < startPx) low = mid + 1;
|
|
||||||
else {
|
|
||||||
start = mid;
|
|
||||||
high = mid - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let end = start;
|
|
||||||
while (end < count - 1 && offsets[end] < endPx) end += 1;
|
|
||||||
return { start, end };
|
|
||||||
});
|
|
||||||
|
|
||||||
const visibleMessages = computed(() => {
|
|
||||||
const { start, end } = visibleRange.value;
|
|
||||||
return end >= start ? props.messages.slice(start, end + 1) : [];
|
|
||||||
});
|
|
||||||
|
|
||||||
const topSpacerHeight = computed(() => layout.value.offsets[visibleRange.value.start] || 0);
|
|
||||||
const bottomSpacerHeight = computed(() => {
|
|
||||||
const { end } = visibleRange.value;
|
|
||||||
if (end < 0) return 0;
|
|
||||||
const nextOffset = end + 1 < props.messages.length
|
|
||||||
? layout.value.offsets[end + 1]
|
|
||||||
: layout.value.total;
|
|
||||||
return Math.max(0, layout.value.total - nextOffset);
|
|
||||||
});
|
|
||||||
|
|
||||||
function setItemRef(key: string, el: Element | ComponentPublicInstance | null) {
|
|
||||||
const existing = observedElements.get(key);
|
|
||||||
if (existing === el) return;
|
|
||||||
|
|
||||||
observers.get(key)?.disconnect();
|
|
||||||
observers.delete(key);
|
|
||||||
observedElements.delete(key);
|
|
||||||
|
|
||||||
if (!(el instanceof HTMLElement)) return;
|
|
||||||
|
|
||||||
observedElements.set(key, el);
|
|
||||||
if (typeof ResizeObserver === "undefined") {
|
|
||||||
setMeasuredHeight(key, measuredRowHeight(el));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const observer = new ResizeObserver(() => setMeasuredHeight(key, measuredRowHeight(el)));
|
|
||||||
observer.observe(el);
|
|
||||||
observers.set(key, observer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncViewport() {
|
function syncViewport() {
|
||||||
const el = scrollerRef.value;
|
const el = getScrollerElement();
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
scrollTop.value = el.scrollTop;
|
scrollTop.value = el.scrollTop;
|
||||||
viewportHeight.value = el.clientHeight;
|
viewportHeight.value = el.clientHeight;
|
||||||
@@ -164,8 +82,14 @@ function handleScroll() {
|
|||||||
if (scrollTop.value <= props.topThreshold) emit("topReach");
|
if (scrollTop.value <= props.topThreshold) emit("topReach");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleResize() {
|
||||||
|
syncViewport();
|
||||||
|
if (Date.now() < keepBottomUntil || isNearBottom(64)) scheduleScrollToBottom(2);
|
||||||
|
if (activeAnchorTarget) scheduleAnchorAlignment(activeAnchorTarget.token, 4);
|
||||||
|
}
|
||||||
|
|
||||||
function isNearBottom(threshold = 200): boolean {
|
function isNearBottom(threshold = 200): boolean {
|
||||||
const el = scrollerRef.value;
|
const el = getScrollerElement();
|
||||||
if (!el) return true;
|
if (!el) return true;
|
||||||
return el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
|
return el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
|
||||||
}
|
}
|
||||||
@@ -178,9 +102,11 @@ function scrollToBottom() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setScrollToBottomNow() {
|
function setScrollToBottomNow() {
|
||||||
const el = scrollerRef.value;
|
const el = getScrollerElement();
|
||||||
if (!el) return;
|
scrollerRef.value?.scrollToBottom();
|
||||||
|
if (el) {
|
||||||
el.scrollTop = Math.max(0, el.scrollHeight - el.clientHeight);
|
el.scrollTop = Math.max(0, el.scrollHeight - el.clientHeight);
|
||||||
|
}
|
||||||
syncViewport();
|
syncViewport();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,18 +125,97 @@ function scheduleScrollToBottom(frames = 1) {
|
|||||||
bottomFrame = requestAnimationFrame(() => step(frames));
|
bottomFrame = requestAnimationFrame(() => step(frames));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findTargetElement(messageId: string, anchorId: string): HTMLElement | null {
|
||||||
|
const el = getScrollerElement();
|
||||||
|
if (!el) return null;
|
||||||
|
|
||||||
|
const anchor = document.getElementById(anchorId);
|
||||||
|
if (anchor instanceof HTMLElement && el.contains(anchor)) return anchor;
|
||||||
|
|
||||||
|
const message = document.getElementById(`message-${messageId}`);
|
||||||
|
if (message instanceof HTMLElement && el.contains(message)) return message;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function alignElement(targetEl: HTMLElement, align: AnchorAlign) {
|
||||||
|
const el = getScrollerElement();
|
||||||
|
if (!el) return;
|
||||||
|
|
||||||
|
const scrollerRect = el.getBoundingClientRect();
|
||||||
|
const targetRect = targetEl.getBoundingClientRect();
|
||||||
|
const delta = align === "center"
|
||||||
|
? targetRect.top + targetRect.height / 2 - (scrollerRect.top + scrollerRect.height / 2)
|
||||||
|
: targetRect.top - scrollerRect.top - 24;
|
||||||
|
|
||||||
|
if (Math.abs(delta) > 1) {
|
||||||
|
el.scrollTop = Math.max(0, el.scrollTop + delta);
|
||||||
|
}
|
||||||
|
syncViewport();
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollToItem(index: number, options?: ScrollToOptions) {
|
||||||
|
scrollerRef.value?.scrollToItem(index, options);
|
||||||
|
syncViewport();
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleAnchorAlignment(token: number, frames = 1) {
|
||||||
|
if (anchorFrame != null) cancelAnimationFrame(anchorFrame);
|
||||||
|
|
||||||
|
const step = (remaining: number) => {
|
||||||
|
const target = activeAnchorTarget;
|
||||||
|
if (!target || target.token !== token) {
|
||||||
|
anchorFrame = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetEl = findTargetElement(target.messageId, target.anchorId);
|
||||||
|
if (targetEl) {
|
||||||
|
alignElement(targetEl, target.align);
|
||||||
|
} else {
|
||||||
|
scrollToItem(target.index, {
|
||||||
|
align: target.align,
|
||||||
|
offset: target.align === "start" ? -24 : 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remaining <= 1) {
|
||||||
|
anchorFrame = null;
|
||||||
|
activeAnchorTarget = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
anchorFrame = requestAnimationFrame(() => step(remaining - 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
anchorFrame = requestAnimationFrame(() => step(frames));
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelAnchorAlignment() {
|
||||||
|
anchorToken += 1;
|
||||||
|
activeAnchorTarget = null;
|
||||||
|
if (anchorFrame != null) {
|
||||||
|
cancelAnimationFrame(anchorFrame);
|
||||||
|
anchorFrame = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function scrollToMessage(messageId: string) {
|
function scrollToMessage(messageId: string) {
|
||||||
const index = props.messages.findIndex(message => String(message.id) === messageId);
|
const index = props.messages.findIndex(message => String(message.id) === messageId);
|
||||||
if (index < 0) return;
|
if (index < 0) return;
|
||||||
|
|
||||||
|
cancelAnchorAlignment();
|
||||||
|
const token = anchorToken;
|
||||||
|
activeAnchorTarget = {
|
||||||
|
token,
|
||||||
|
index,
|
||||||
|
messageId,
|
||||||
|
anchorId: `message-${messageId}`,
|
||||||
|
align: "center",
|
||||||
|
};
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const el = scrollerRef.value;
|
scrollToItem(index, { align: "center" });
|
||||||
if (!el) return;
|
scheduleAnchorAlignment(token, 8);
|
||||||
el.scrollTop = Math.max(0, (layout.value.offsets[index] || 0) - el.clientHeight / 2);
|
|
||||||
syncViewport();
|
|
||||||
nextTick(() => {
|
|
||||||
document.getElementById(`message-${messageId}`)?.scrollIntoView({ block: "center" });
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,21 +223,24 @@ function scrollToAnchor(messageId: string, anchorId: string) {
|
|||||||
const index = props.messages.findIndex(message => String(message.id) === messageId);
|
const index = props.messages.findIndex(message => String(message.id) === messageId);
|
||||||
if (index < 0) return;
|
if (index < 0) return;
|
||||||
|
|
||||||
|
cancelAnchorAlignment();
|
||||||
|
const token = anchorToken;
|
||||||
|
activeAnchorTarget = {
|
||||||
|
token,
|
||||||
|
index,
|
||||||
|
messageId,
|
||||||
|
anchorId,
|
||||||
|
align: "start",
|
||||||
|
};
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const el = scrollerRef.value;
|
scrollToItem(index, { align: "start", offset: -24 });
|
||||||
if (!el) return;
|
scheduleAnchorAlignment(token, 10);
|
||||||
el.scrollTop = Math.max(0, (layout.value.offsets[index] || 0) - 24);
|
|
||||||
syncViewport();
|
|
||||||
nextTick(() => {
|
|
||||||
const target = document.getElementById(anchorId) || document.getElementById(`message-${messageId}`);
|
|
||||||
target?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
||||||
syncViewport();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function captureScrollPosition() {
|
function captureScrollPosition() {
|
||||||
const el = scrollerRef.value;
|
const el = getScrollerElement();
|
||||||
if (!el) return null;
|
if (!el) return null;
|
||||||
return {
|
return {
|
||||||
scrollTop: el.scrollTop,
|
scrollTop: el.scrollTop,
|
||||||
@@ -243,9 +251,11 @@ function captureScrollPosition() {
|
|||||||
function restoreScrollPosition(snapshot: { scrollTop: number; scrollHeight: number } | null) {
|
function restoreScrollPosition(snapshot: { scrollTop: number; scrollHeight: number } | null) {
|
||||||
if (!snapshot) return;
|
if (!snapshot) return;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const el = scrollerRef.value;
|
const el = getScrollerElement();
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
el.scrollTop = Math.max(0, el.scrollHeight - snapshot.scrollHeight + snapshot.scrollTop);
|
const nextScrollTop = Math.max(0, el.scrollHeight - snapshot.scrollHeight + snapshot.scrollTop);
|
||||||
|
scrollerRef.value?.scrollToPosition(nextScrollTop);
|
||||||
|
el.scrollTop = nextScrollTop;
|
||||||
syncViewport();
|
syncViewport();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -253,36 +263,24 @@ function restoreScrollPosition(snapshot: { scrollTop: number; scrollHeight: numb
|
|||||||
let resizeObserver: ResizeObserver | null = null;
|
let resizeObserver: ResizeObserver | null = null;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
syncViewport();
|
syncViewport();
|
||||||
if (scrollerRef.value && typeof ResizeObserver !== "undefined") {
|
const el = getScrollerElement();
|
||||||
resizeObserver = new ResizeObserver(syncViewport);
|
if (el && typeof ResizeObserver !== "undefined") {
|
||||||
resizeObserver.observe(scrollerRef.value);
|
resizeObserver = new ResizeObserver(handleResize);
|
||||||
|
resizeObserver.observe(el);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (bottomFrame != null) cancelAnimationFrame(bottomFrame);
|
if (bottomFrame != null) cancelAnimationFrame(bottomFrame);
|
||||||
|
if (anchorFrame != null) cancelAnimationFrame(anchorFrame);
|
||||||
resizeObserver?.disconnect();
|
resizeObserver?.disconnect();
|
||||||
for (const observer of observers.values()) observer.disconnect();
|
|
||||||
observers.clear();
|
|
||||||
observedElements.clear();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(messageKeys, keys => {
|
watch(messageKeys, () => {
|
||||||
const activeKeys = new Set(keys);
|
cancelAnchorAlignment();
|
||||||
for (const key of [...observedElements.keys()]) {
|
|
||||||
if (activeKeys.has(key)) continue;
|
|
||||||
observers.get(key)?.disconnect();
|
|
||||||
observers.delete(key);
|
|
||||||
observedElements.delete(key);
|
|
||||||
}
|
|
||||||
let droppedHeights = false;
|
|
||||||
for (const key of [...measuredHeights.keys()]) {
|
|
||||||
if (activeKeys.has(key)) continue;
|
|
||||||
measuredHeights.delete(key);
|
|
||||||
droppedHeights = true;
|
|
||||||
}
|
|
||||||
if (droppedHeights) heightVersion.value += 1;
|
|
||||||
nextTick(syncViewport);
|
nextTick(syncViewport);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -298,51 +296,69 @@ defineExpose({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
ref="hostRef"
|
||||||
|
class="virtual-message-list-host"
|
||||||
|
:style="{ '--virtual-row-gap': `${rowGap}px`, '--virtual-list-padding': padding }"
|
||||||
|
>
|
||||||
|
<DynamicScroller
|
||||||
ref="scrollerRef"
|
ref="scrollerRef"
|
||||||
class="virtual-message-list"
|
class="virtual-message-list"
|
||||||
:style="{ '--virtual-row-gap': `${rowGap}px`, '--virtual-list-padding': padding }"
|
:items="messages"
|
||||||
@scroll="handleScroll"
|
key-field="id"
|
||||||
|
:min-item-size="estimatedItemHeight"
|
||||||
|
:buffer="bufferPx"
|
||||||
|
:flow-mode="true"
|
||||||
|
:prerender="overscan"
|
||||||
|
@scroll.passive="handleScroll"
|
||||||
|
@resize="handleResize"
|
||||||
|
@visible="syncViewport"
|
||||||
>
|
>
|
||||||
<slot v-if="messages.length === 0" name="empty" />
|
<template #empty>
|
||||||
<template v-else>
|
<slot name="empty" />
|
||||||
<slot name="before" />
|
</template>
|
||||||
<div class="virtual-spacer" :style="{ height: `${topSpacerHeight}px` }" />
|
<template #before>
|
||||||
<div
|
<slot v-if="messages.length > 0" name="before" />
|
||||||
v-for="msg in visibleMessages"
|
</template>
|
||||||
:key="msg.id"
|
<template #default="{ item, index, active }">
|
||||||
:ref="(el) => setItemRef(messageKey(msg), el)"
|
<DynamicScrollerItem
|
||||||
|
:item="item"
|
||||||
|
:index="index"
|
||||||
|
:active="active"
|
||||||
class="virtual-row"
|
class="virtual-row"
|
||||||
>
|
>
|
||||||
<slot name="item" :message="msg" />
|
<slot v-if="active" name="item" :message="item" />
|
||||||
</div>
|
</DynamicScrollerItem>
|
||||||
<div class="virtual-spacer" :style="{ height: `${bottomSpacerHeight}px` }" />
|
|
||||||
</template>
|
</template>
|
||||||
<slot name="after" />
|
<template #after>
|
||||||
|
<slot v-if="messages.length > 0" name="after" />
|
||||||
|
</template>
|
||||||
|
</DynamicScroller>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/styles/variables" as *;
|
@use "@/styles/variables" as *;
|
||||||
|
|
||||||
|
.virtual-message-list-host {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.virtual-message-list {
|
.virtual-message-list {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
min-height: 0;
|
||||||
padding: var(--virtual-list-padding);
|
padding: var(--virtual-list-padding);
|
||||||
display: flex;
|
box-sizing: border-box;
|
||||||
flex-direction: column;
|
|
||||||
background-color: $bg-card;
|
background-color: $bg-card;
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.dark & {
|
.dark & {
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtual-spacer {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.virtual-row {
|
.virtual-row {
|
||||||
|
box-sizing: border-box;
|
||||||
padding-bottom: var(--virtual-row-gap);
|
padding-bottom: var(--virtual-row-gap);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user