From c94951b2f8136cfd74556bc2f28ff3bdefa6be42 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Fri, 3 Apr 2026 15:03:12 +0800 Subject: [PATCH] refactor(web): migrate notion page selectors to tanstack virtual (#34508) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- package.json | 1 - pnpm-lock.yaml | 176 +--------- pnpm-workspace.yaml | 4 +- taze.config.js | 9 - web/__mocks__/@tanstack/react-virtual.ts | 36 ++ .../base/markdown-blocks/code-block.tsx | 1 - .../__tests__/base.spec.tsx | 4 +- .../base/notion-page-selector/base.tsx | 62 ++-- .../page-selector/__tests__/index.spec.tsx | 2 + .../page-selector/index.tsx | 326 ++---------------- .../page-selector/page-row.tsx | 116 +++++++ .../page-selector/types.ts | 21 ++ .../page-selector/use-page-selector-model.ts | 88 +++++ .../page-selector/utils.ts | 163 +++++++++ .../page-selector/virtual-page-list.tsx | 93 +++++ .../data-source/online-documents/index.tsx | 4 +- .../page-selector/__tests__/index.spec.tsx | 24 +- .../page-selector/__tests__/utils.spec.ts | 4 +- .../online-documents/page-selector/index.tsx | 182 ++-------- .../online-documents/page-selector/item.tsx | 152 -------- .../online-documents/page-selector/utils.ts | 39 --- web/app/styles/markdown.css | 3 - web/eslint-suppressions.json | 23 -- web/i18n/en-US/common.json | 3 + web/package.json | 3 +- 25 files changed, 639 insertions(+), 900 deletions(-) delete mode 100644 taze.config.js create mode 100644 web/__mocks__/@tanstack/react-virtual.ts create mode 100644 web/app/components/base/notion-page-selector/page-selector/page-row.tsx create mode 100644 web/app/components/base/notion-page-selector/page-selector/types.ts create mode 100644 web/app/components/base/notion-page-selector/page-selector/use-page-selector-model.ts create mode 100644 web/app/components/base/notion-page-selector/page-selector/utils.ts create mode 100644 web/app/components/base/notion-page-selector/page-selector/virtual-page-list.tsx delete mode 100644 web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/item.tsx delete mode 100644 web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/utils.ts diff --git a/package.json b/package.json index 48c3acef021..ce3180214bf 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,6 @@ "prepare": "vp config" }, "devDependencies": { - "taze": "catalog:", "vite-plus": "catalog:" }, "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f60206104da..57ca2e70bca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -171,6 +171,9 @@ catalogs: '@tanstack/react-query-devtools': specifier: 5.96.1 version: 5.96.1 + '@tanstack/react-virtual': + specifier: 3.13.23 + version: 3.13.23 '@testing-library/dom': specifier: 10.4.1 version: 10.4.1 @@ -213,9 +216,6 @@ catalogs: '@types/react-dom': specifier: 19.2.3 version: 19.2.3 - '@types/react-window': - specifier: 1.8.8 - version: 1.8.8 '@types/sortablejs': specifier: 1.15.9 version: 1.15.9 @@ -453,9 +453,6 @@ catalogs: react-textarea-autosize: specifier: 8.5.9 version: 8.5.9 - react-window: - specifier: 1.8.11 - version: 1.8.11 reactflow: specifier: 11.11.4 version: 11.11.4 @@ -495,9 +492,6 @@ catalogs: tailwindcss: specifier: 4.2.2 version: 4.2.2 - taze: - specifier: 19.11.0 - version: 19.11.0 tldts: specifier: 7.0.27 version: 7.0.27 @@ -605,9 +599,6 @@ importers: .: devDependencies: - taze: - specifier: 'catalog:' - version: 19.11.0 vite-plus: specifier: 'catalog:' version: 0.1.15(@types/node@25.5.0)(esbuild@0.27.2)(happy-dom@20.8.9)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(typescript@6.0.2)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@25.5.0)(esbuild@0.27.2)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(yaml@2.8.3) @@ -755,6 +746,9 @@ importers: '@tanstack/react-query': specifier: 'catalog:' version: 5.96.1(react@19.2.4) + '@tanstack/react-virtual': + specifier: 'catalog:' + version: 3.13.23(react-dom@19.2.4(react@19.2.4))(react@19.2.4) abcjs: specifier: 'catalog:' version: 6.6.2 @@ -920,9 +914,6 @@ importers: react-textarea-autosize: specifier: 'catalog:' version: 8.5.9(@types/react@19.2.14)(react@19.2.4) - react-window: - specifier: 'catalog:' - version: 1.8.11(react-dom@19.2.4(react@19.2.4))(react@19.2.4) reactflow: specifier: 'catalog:' version: 11.11.4(@types/react@19.2.14)(immer@11.1.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -1098,9 +1089,6 @@ importers: '@types/react-dom': specifier: 'catalog:' version: 19.2.3(@types/react@19.2.14) - '@types/react-window': - specifier: 'catalog:' - version: 1.8.8 '@types/sortablejs': specifier: 'catalog:' version: 1.15.9 @@ -1351,11 +1339,6 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - '@antfu/ni@30.0.0': - resolution: {integrity: sha512-DqBVB3XqXH4VsDpER7iLlEtayMC98iXrY7kwBzp1v6LGc/6U6+qnN3+X0bcPK63LMXJCRG2D/XDq7dvtKDGogg==} - engines: {node: '>=20.19.0'} - hasBin: true - '@antfu/utils@8.1.1': resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==} @@ -1944,9 +1927,6 @@ packages: react: ^18 || ^19 || ^19.0.0-rc react-dom: ^18 || ^19 || ^19.0.0-rc - '@henrygd/queue@1.2.0': - resolution: {integrity: sha512-jW/BLSTpcvExDhqJGxtIPgGr2O0IFF8XUNDwEbfCfhrXT8a4xztQ9Lv6U/vbYzYC0xVWn+3zv6YnLUh3bEFUKA==} - '@heroicons/react@2.2.0': resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==} peerDependencies: @@ -3049,9 +3029,6 @@ packages: '@preact/signals-core@1.14.0': resolution: {integrity: sha512-AowtCcCU/33lFlh1zRFf/u+12rfrhtNakj7UpaGEsmMwUKpKWMVvcktOGcwBBNiB4lWrZWc01LhiyyzVklJyaQ==} - '@quansync/fs@1.0.0': - resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} - '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} @@ -4327,9 +4304,6 @@ packages: peerDependencies: '@types/react': ^19.2.0 - '@types/react-window@1.8.8': - resolution: {integrity: sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==} - '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} @@ -5508,9 +5482,6 @@ packages: resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} engines: {node: '>=12'} - defu@6.1.4: - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - delaunator@5.1.0: resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} @@ -5518,9 +5489,6 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - destr@2.0.5: - resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} - detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -6161,9 +6129,6 @@ packages: functional-red-black-tree@1.0.1: resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} - fzf@0.5.2: - resolution: {integrity: sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==} - gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -6886,9 +6851,6 @@ packages: mdn-data@2.23.0: resolution: {integrity: sha512-786vq1+4079JSeu2XdcDjrhi/Ry7BWtjDl9WtGPWLiIHb2T66GvIVflZTBoSNZ5JqTtJGYEVMuFA/lbQlMOyDQ==} - memoize-one@5.2.1: - resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} - merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -7035,10 +6997,6 @@ packages: engines: {node: '>=16'} hasBin: true - mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} - mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -7160,9 +7118,6 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-fetch-native@1.6.7: - resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} - node-releases@2.0.36: resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} @@ -7207,19 +7162,12 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} - ofetch@1.5.1: - resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} - ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@7.0.0: - resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} - engines: {node: '>=18'} - oniguruma-parser@0.12.1: resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} @@ -7497,9 +7445,6 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - quansync@1.0.0: - resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -7658,13 +7603,6 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-window@1.8.11: - resolution: {integrity: sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==} - engines: {node: '>8.0.0'} - peerDependencies: - react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react@19.2.4: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} @@ -7816,10 +7754,6 @@ packages: engines: {node: '>= 0.4'} hasBin: true - restore-cursor@5.1.0: - resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} - engines: {node: '>=18'} - reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -7928,10 +7862,6 @@ packages: resolution: {integrity: sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==} engines: {node: '>=20'} - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} @@ -8158,10 +8088,6 @@ packages: resolution: {integrity: sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==} engines: {node: '>=18'} - taze@19.11.0: - resolution: {integrity: sha512-BlfH8Z6JdoIsrUptnz4P4YuEqdYsa/bSNNDOMhTlsHZ7Bbg1/0NyYh6uPkoRREjrt/kVovV+HYdi1ilHxvChfw==} - hasBin: true - terser-webpack-plugin@5.4.0: resolution: {integrity: sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==} engines: {node: '>= 10.13.0'} @@ -8356,12 +8282,6 @@ packages: resolution: {integrity: sha512-X2wH19RAPZE3+ldGicOkoj/SIA83OIxcJ6Cuaw23hf8Xc6fQpvZXY0SftE2JgS0QhYLUG4uwodSI3R53keyh7w==} engines: {node: '>=14'} - unconfig-core@7.5.0: - resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} - - unconfig@7.5.0: - resolution: {integrity: sha512-oi8Qy2JV4D3UQ0PsopR28CzdQ3S/5A1zwsUwp/rosSbfhJ5z7b90bIyTwi/F7hCLD4SGcZVjDzd4XoUQcEanvA==} - undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} @@ -9060,13 +8980,6 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.0.4 - '@antfu/ni@30.0.0': - dependencies: - fzf: 0.5.2 - package-manager-detector: 1.6.0 - tinyexec: 1.0.4 - tinyglobby: 0.2.15 - '@antfu/utils@8.1.1': {} '@babel/code-frame@7.29.0': @@ -9772,8 +9685,6 @@ snapshots: react-dom: 19.2.4(react@19.2.4) use-sync-external-store: 1.6.0(react@19.2.4) - '@henrygd/queue@1.2.0': {} - '@heroicons/react@2.2.0(react@19.2.4)': dependencies: react: 19.2.4 @@ -10662,10 +10573,6 @@ snapshots: '@preact/signals-core@1.14.0': {} - '@quansync/fs@1.0.0': - dependencies: - quansync: 1.0.0 - '@radix-ui/primitive@1.1.3': {} '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.4)': @@ -11911,10 +11818,6 @@ snapshots: dependencies: '@types/react': 19.2.14 - '@types/react-window@1.8.8': - dependencies: - '@types/react': 19.2.14 - '@types/react@19.2.14': dependencies: csstype: 3.2.3 @@ -13209,16 +13112,12 @@ snapshots: define-lazy-prop@3.0.0: {} - defu@6.1.4: {} - delaunator@5.1.0: dependencies: robust-predicates: 3.0.3 dequal@2.0.3: {} - destr@2.0.5: {} - detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -14065,8 +13964,6 @@ snapshots: functional-red-black-tree@1.0.1: {} - fzf@0.5.2: {} - gensync@1.0.0-beta.2: {} get-east-asian-width@1.5.0: {} @@ -14926,8 +14823,6 @@ snapshots: mdn-data@2.23.0: {} - memoize-one@5.2.1: {} - merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -15262,8 +15157,6 @@ snapshots: mime@4.1.0: {} - mimic-function@5.0.1: {} - mimic-response@3.1.0: optional: true @@ -15379,8 +15272,6 @@ snapshots: node-addon-api@7.1.1: optional: true - node-fetch-native@1.6.7: {} - node-releases@2.0.36: {} normalize-package-data@8.0.0: @@ -15408,22 +15299,12 @@ snapshots: obug@2.1.1: {} - ofetch@1.5.1: - dependencies: - destr: 2.0.5 - node-fetch-native: 1.6.7 - ufo: 1.6.3 - ohash@2.0.11: {} once@1.4.0: dependencies: wrappy: 1.0.2 - onetime@7.0.0: - dependencies: - mimic-function: 5.0.1 - oniguruma-parser@0.12.1: {} oniguruma-to-es@4.3.5: @@ -15811,8 +15692,6 @@ snapshots: quansync@0.2.11: {} - quansync@1.0.0: {} - queue-microtask@1.2.3: {} radash@12.1.1: {} @@ -15979,13 +15858,6 @@ snapshots: transitivePeerDependencies: - '@types/react' - react-window@1.8.11(react-dom@19.2.4(react@19.2.4))(react@19.2.4): - dependencies: - '@babel/runtime': 7.29.2 - memoize-one: 5.2.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - react@19.2.4: {} reactflow@11.11.4(@types/react@19.2.14)(immer@11.1.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): @@ -16219,11 +16091,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - restore-cursor@5.1.0: - dependencies: - onetime: 7.0.0 - signal-exit: 4.1.0 - reusify@1.1.0: {} robust-predicates@3.0.3: {} @@ -16409,8 +16276,6 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 - signal-exit@4.1.0: {} - simple-concat@1.0.1: optional: true @@ -16652,22 +16517,6 @@ snapshots: minizlib: 3.1.0 yallist: 5.0.0 - taze@19.11.0: - dependencies: - '@antfu/ni': 30.0.0 - '@henrygd/queue': 1.2.0 - cac: 7.0.0 - find-up-simple: 1.0.1 - ofetch: 1.5.1 - package-manager-detector: 1.6.0 - pathe: 2.0.3 - pnpm-workspace-yaml: 1.6.0 - restore-cursor: 5.1.0 - tinyexec: 1.0.4 - tinyglobby: 0.2.15 - unconfig: 7.5.0 - yaml: 2.8.3 - terser-webpack-plugin@5.4.0(esbuild@0.27.2)(uglify-js@3.19.3)(webpack@5.105.4(esbuild@0.27.2)(uglify-js@3.19.3)): dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -16820,19 +16669,6 @@ snapshots: unbash@2.2.0: {} - unconfig-core@7.5.0: - dependencies: - '@quansync/fs': 1.0.0 - quansync: 1.0.0 - - unconfig@7.5.0: - dependencies: - '@quansync/fs': 1.0.0 - defu: 6.1.4 - jiti: 2.6.1 - quansync: 1.0.0 - unconfig-core: 7.5.0 - undici-types@7.18.2: {} undici@7.24.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d9f6293222b..585256b4a75 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -132,6 +132,7 @@ catalog: "@tanstack/react-form-devtools": 0.2.20 "@tanstack/react-query": 5.96.1 "@tanstack/react-query-devtools": 5.96.1 + "@tanstack/react-virtual": 3.13.23 "@testing-library/dom": 10.4.1 "@testing-library/jest-dom": 6.9.1 "@testing-library/react": 16.3.2 @@ -147,7 +148,6 @@ catalog: "@types/qs": 6.15.0 "@types/react": 19.2.14 "@types/react-dom": 19.2.3 - "@types/react-window": 1.8.8 "@types/sortablejs": 1.15.9 "@typescript-eslint/eslint-plugin": 8.58.0 "@typescript-eslint/parser": 8.58.0 @@ -229,7 +229,6 @@ catalog: react-server-dom-webpack: 19.2.4 react-sortablejs: 6.1.4 react-textarea-autosize: 8.5.9 - react-window: 1.8.11 reactflow: 11.11.4 remark-breaks: 4.0.0 remark-directive: 4.0.0 @@ -243,7 +242,6 @@ catalog: string-ts: 2.3.1 tailwind-merge: 3.5.0 tailwindcss: 4.2.2 - taze: 19.11.0 tldts: 7.0.27 tsdown: 0.21.7 tsx: 4.21.0 diff --git a/taze.config.js b/taze.config.js deleted file mode 100644 index 96a69acf3e6..00000000000 --- a/taze.config.js +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig } from 'taze' - -export default defineConfig({ - exclude: [ - // We are going to replace these - 'react-window', - '@types/react-window', - ], -}) diff --git a/web/__mocks__/@tanstack/react-virtual.ts b/web/__mocks__/@tanstack/react-virtual.ts new file mode 100644 index 00000000000..59cca5e33f6 --- /dev/null +++ b/web/__mocks__/@tanstack/react-virtual.ts @@ -0,0 +1,36 @@ +import { vi } from 'vitest' + +const mockVirtualizer = ({ + count, + estimateSize, +}: { + count: number + estimateSize?: (index: number) => number +}) => { + const getSize = (index: number) => estimateSize?.(index) ?? 0 + + return { + getTotalSize: () => Array.from({ length: count }).reduce((total, _, index) => total + getSize(index), 0), + getVirtualItems: () => { + let start = 0 + + return Array.from({ length: count }).map((_, index) => { + const size = getSize(index) + const virtualItem = { + end: start + size, + index, + key: index, + size, + start, + } + + start += size + return virtualItem + }) + }, + measureElement: vi.fn(), + scrollToIndex: vi.fn(), + } +} + +export { mockVirtualizer as useVirtualizer } diff --git a/web/app/components/base/markdown-blocks/code-block.tsx b/web/app/components/base/markdown-blocks/code-block.tsx index 679b1ca0d02..67fa11da000 100644 --- a/web/app/components/base/markdown-blocks/code-block.tsx +++ b/web/app/components/base/markdown-blocks/code-block.tsx @@ -15,7 +15,6 @@ import { highlightCode } from './shiki-highlight' const Flowchart = dynamic(() => import('@/app/components/base/mermaid'), { ssr: false }) -// Available language https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD const capitalizationLanguageNameMap: Record = { sql: 'SQL', javascript: 'JavaScript', diff --git a/web/app/components/base/notion-page-selector/__tests__/base.spec.tsx b/web/app/components/base/notion-page-selector/__tests__/base.spec.tsx index 4afae28b792..54e33b8fbe1 100644 --- a/web/app/components/base/notion-page-selector/__tests__/base.spec.tsx +++ b/web/app/components/base/notion-page-selector/__tests__/base.spec.tsx @@ -9,6 +9,8 @@ import { useModalContextSelector } from '@/context/modal-context' import { useInvalidPreImportNotionPages, usePreImportNotionPages } from '@/service/knowledge/use-import' import NotionPageSelector from '../base' +vi.mock('@tanstack/react-virtual') + vi.mock('@/service/knowledge/use-import', () => ({ usePreImportNotionPages: vi.fn(), useInvalidPreImportNotionPages: vi.fn(), @@ -183,7 +185,7 @@ describe('NotionPageSelector Base', () => { const user = userEvent.setup() render() - await user.click(screen.getByRole('button', { name: 'Configure Notion' })) + await user.click(screen.getByRole('button', { name: 'common.dataSource.notion.selector.configure' })) expect(mockSetShowAccountSettingModal).toHaveBeenCalledWith({ payload: ACCOUNT_SETTING_TAB.DATA_SOURCE }) }) diff --git a/web/app/components/base/notion-page-selector/base.tsx b/web/app/components/base/notion-page-selector/base.tsx index 261e7940e1b..34926849a67 100644 --- a/web/app/components/base/notion-page-selector/base.tsx +++ b/web/app/components/base/notion-page-selector/base.tsx @@ -2,6 +2,7 @@ import type { DataSourceCredential } from '../../header/account-setting/data-sou import type { NotionCredential } from './credential-selector' import type { DataSourceNotionPageMap, DataSourceNotionWorkspace, NotionPage } from '@/models/common' import { useCallback, useEffect, useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { useModalContextSelector } from '@/context/modal-context' import { useInvalidPreImportNotionPages, usePreImportNotionPages } from '@/service/knowledge/use-import' @@ -33,6 +34,7 @@ const NotionPageSelector = ({ credentialList, onSelectCredential, }: NotionPageSelectorProps) => { + const { t } = useTranslation() const [searchValue, setSearchValue] = useState('') const setShowAccountSettingModal = useModalContextSelector(s => s.setShowAccountSettingModal) @@ -48,27 +50,34 @@ const NotionPageSelector = ({ } }) }, [credentialList]) - const [currentCredential, setCurrentCredential] = useState(notionCredentials[0]) + const [selectedCredentialId, setSelectedCredentialId] = useState(() => notionCredentials[0]?.credentialId ?? '') + const currentCredential = useMemo(() => { + return notionCredentials.find(item => item.credentialId === selectedCredentialId) ?? notionCredentials[0] ?? null + }, [notionCredentials, selectedCredentialId]) + const currentCredentialId = currentCredential?.credentialId ?? '' useEffect(() => { - const credential = notionCredentials.find(item => item.credentialId === currentCredential?.credentialId) - if (!credential) { - const firstCredential = notionCredentials[0] - invalidPreImportNotionPages({ datasetId, credentialId: firstCredential.credentialId }) - setCurrentCredential(notionCredentials[0]) - onSelect([]) // Clear selected pages when changing credential - onSelectCredential?.(firstCredential.credentialId) + onSelectCredential?.(currentCredentialId) + }, [currentCredentialId, onSelectCredential]) + + useEffect(() => { + if (!notionCredentials.length) { + onSelect([]) + return } - else { - onSelectCredential?.(credential?.credentialId || '') - } - }, [notionCredentials]) + + if (!selectedCredentialId || selectedCredentialId === currentCredentialId) + return + + invalidPreImportNotionPages({ datasetId, credentialId: currentCredentialId }) + onSelect([]) + }, [currentCredentialId, datasetId, invalidPreImportNotionPages, notionCredentials.length, onSelect, selectedCredentialId]) const { data: notionsPages, isFetching: isFetchingNotionPages, isError: isFetchingNotionPagesError, - } = usePreImportNotionPages({ datasetId, credentialId: currentCredential.credentialId || '' }) + } = usePreImportNotionPages({ datasetId, credentialId: currentCredentialId }) const pagesMapAndSelectedPagesId: [DataSourceNotionPageMap, Set, Set] = useMemo(() => { const selectedPagesId = new Set() @@ -94,28 +103,24 @@ const NotionPageSelector = ({ const defaultSelectedPagesId = useMemo(() => { return [...Array.from(pagesMapAndSelectedPagesId[1]), ...(value || [])] }, [pagesMapAndSelectedPagesId, value]) - const [selectedPagesId, setSelectedPagesId] = useState>(() => new Set(defaultSelectedPagesId)) - - useEffect(() => { - setSelectedPagesId(new Set(defaultSelectedPagesId)) - }, [defaultSelectedPagesId]) + const selectedPagesId = useMemo(() => new Set(defaultSelectedPagesId), [defaultSelectedPagesId]) const handleSearchValueChange = useCallback((value: string) => { setSearchValue(value) }, []) const handleSelectCredential = useCallback((credentialId: string) => { - const credential = notionCredentials.find(item => item.credentialId === credentialId)! - invalidPreImportNotionPages({ datasetId, credentialId: credential.credentialId }) - setCurrentCredential(credential) + if (credentialId === currentCredentialId) + return + + invalidPreImportNotionPages({ datasetId, credentialId }) + setSelectedCredentialId(credentialId) onSelect([]) // Clear selected pages when changing credential - onSelectCredential?.(credential.credentialId) - }, [datasetId, invalidPreImportNotionPages, notionCredentials, onSelect, onSelectCredential]) + }, [currentCredentialId, datasetId, invalidPreImportNotionPages, onSelect]) const handleSelectPages = useCallback((newSelectedPagesId: Set) => { const selectedPages = Array.from(newSelectedPagesId).map(pageId => pagesMapAndSelectedPagesId[0][pageId]) - setSelectedPagesId(new Set(Array.from(newSelectedPagesId))) onSelect(selectedPages) }, [pagesMapAndSelectedPagesId, onSelect]) @@ -140,16 +145,16 @@ const NotionPageSelector = ({
@@ -168,6 +173,7 @@ const NotionPageSelector = ({ ) : ( ): DataSourceNotionPage => ({ page_id: 'page-id', page_name: 'Page name', diff --git a/web/app/components/base/notion-page-selector/page-selector/index.tsx b/web/app/components/base/notion-page-selector/page-selector/index.tsx index 50ac5671930..64645398642 100644 --- a/web/app/components/base/notion-page-selector/page-selector/index.tsx +++ b/web/app/components/base/notion-page-selector/page-selector/index.tsx @@ -1,11 +1,7 @@ -import type { ListChildComponentProps } from 'react-window' import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common' -import { memo, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { areEqual, FixedSizeList as List } from 'react-window' -import { cn } from '@/utils/classnames' -import Checkbox from '../../checkbox' -import NotionIcon from '../../notion-icon' +import { usePageSelectorModel } from './use-page-selector-model' +import VirtualPageList from './virtual-page-list' type PageSelectorProps = { value: Set @@ -17,173 +13,7 @@ type PageSelectorProps = { canPreview?: boolean previewPageId?: string onPreview?: (selectedPageId: string) => void - isMultipleChoice?: boolean } -type NotionPageTreeItem = { - children: Set - descendants: Set - depth: number - ancestors: string[] -} & DataSourceNotionPage -type NotionPageTreeMap = Record -type NotionPageItem = { - expand: boolean - depth: number -} & DataSourceNotionPage - -const recursivePushInParentDescendants = ( - pagesMap: DataSourceNotionPageMap, - listTreeMap: NotionPageTreeMap, - current: NotionPageTreeItem, - leafItem: NotionPageTreeItem, -) => { - const parentId = current.parent_id - const pageId = current.page_id - - if (!parentId || !pageId) - return - - if (parentId !== 'root' && pagesMap[parentId]) { - if (!listTreeMap[parentId]) { - const children = new Set([pageId]) - const descendants = new Set([pageId, leafItem.page_id]) - listTreeMap[parentId] = { - ...pagesMap[parentId], - children, - descendants, - depth: 0, - ancestors: [], - } - } - else { - listTreeMap[parentId].children.add(pageId) - listTreeMap[parentId].descendants.add(pageId) - listTreeMap[parentId].descendants.add(leafItem.page_id) - } - leafItem.depth++ - leafItem.ancestors.unshift(listTreeMap[parentId].page_name) - - if (listTreeMap[parentId].parent_id !== 'root') - recursivePushInParentDescendants(pagesMap, listTreeMap, listTreeMap[parentId], leafItem) - } -} - -const ItemComponent = ({ index, style, data }: ListChildComponentProps<{ - dataList: NotionPageItem[] - handleToggle: (index: number) => void - checkedIds: Set - disabledCheckedIds: Set - handleCheck: (index: number) => void - canPreview?: boolean - handlePreview: (index: number) => void - listMapWithChildrenAndDescendants: NotionPageTreeMap - searchValue: string - previewPageId: string - pagesMap: DataSourceNotionPageMap -}>) => { - const { t } = useTranslation() - const { - dataList, - handleToggle, - checkedIds, - disabledCheckedIds, - handleCheck, - canPreview, - handlePreview, - listMapWithChildrenAndDescendants, - searchValue, - previewPageId, - pagesMap, - } = data - const current = dataList[index] - const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[current.page_id] - const hasChild = currentWithChildrenAndDescendants.descendants.size > 0 - const ancestors = currentWithChildrenAndDescendants.ancestors - const breadCrumbs = ancestors.length ? [...ancestors, current.page_name] : [current.page_name] - const disabled = disabledCheckedIds.has(current.page_id) - - const renderArrow = () => { - if (hasChild) { - return ( -
handleToggle(index)} - data-testid={`notion-page-toggle-${current.page_id}`} - > - { - current.expand - ?
- :
- } -
- ) - } - if (current.parent_id === 'root' || !pagesMap[current.parent_id]) { - return ( -
- ) - } - return ( -
- ) - } - - return ( -
- { - handleCheck(index) - }} - id={`notion-page-checkbox-${current.page_id}`} - /> - {!searchValue && renderArrow()} - -
- {current.page_name} -
- { - canPreview && ( -
handlePreview(index)} - data-testid={`notion-page-preview-${current.page_id}`} - > - {t('dataSource.notion.selector.preview', { ns: 'common' })} -
- ) - } - { - searchValue && ( -
- {breadCrumbs.join(' / ')} -
- ) - } -
- ) -} -const Item = memo(ItemComponent, areEqual) const PageSelector = ({ value, @@ -197,108 +27,25 @@ const PageSelector = ({ onPreview, }: PageSelectorProps) => { const { t } = useTranslation() - const [dataList, setDataList] = useState([]) - const [localPreviewPageId, setLocalPreviewPageId] = useState('') - - useEffect(() => { - setDataList(list.filter(item => item.parent_id === 'root' || !pagesMap[item.parent_id]).map((item) => { - return { - ...item, - expand: false, - depth: 0, - } - })) - }, [list]) - - const searchDataList = list.filter((item) => { - return item.page_name.includes(searchValue) - }).map((item) => { - return { - ...item, - expand: false, - depth: 0, - } + const { + currentPreviewPageId, + effectiveSearchValue, + rows, + handlePreview, + handleSelect, + handleToggle, + } = usePageSelectorModel({ + checkedIds: value, + list, + onPreview, + onSelect, + pagesMap, + previewPageId, + searchValue, + selectionMode: 'multiple', }) - const currentDataList = searchValue ? searchDataList : dataList - const currentPreviewPageId = previewPageId === undefined ? localPreviewPageId : previewPageId - const listMapWithChildrenAndDescendants = useMemo(() => { - return list.reduce((prev: NotionPageTreeMap, next: DataSourceNotionPage) => { - const pageId = next.page_id - if (!prev[pageId]) - prev[pageId] = { ...next, children: new Set(), descendants: new Set(), depth: 0, ancestors: [] } - - recursivePushInParentDescendants(pagesMap, prev, prev[pageId], prev[pageId]) - return prev - }, {}) - }, [list, pagesMap]) - - const handleToggle = (index: number) => { - const current = dataList[index] - const pageId = current.page_id - const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[pageId] - const descendantsIds = Array.from(currentWithChildrenAndDescendants.descendants) - const childrenIds = Array.from(currentWithChildrenAndDescendants.children) - let newDataList = [] - - if (current.expand) { - current.expand = false - - newDataList = dataList.filter(item => !descendantsIds.includes(item.page_id)) - } - else { - current.expand = true - - newDataList = [ - ...dataList.slice(0, index + 1), - ...childrenIds.map(item => ({ - ...pagesMap[item], - expand: false, - depth: listMapWithChildrenAndDescendants[item].depth, - })), - ...dataList.slice(index + 1), - ] - } - setDataList(newDataList) - } - - const copyValue = new Set(value) - const handleCheck = (index: number) => { - const current = currentDataList[index] - const pageId = current.page_id - const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[pageId] - - if (copyValue.has(pageId)) { - if (!searchValue) { - for (const item of currentWithChildrenAndDescendants.descendants) - copyValue.delete(item) - } - - copyValue.delete(pageId) - } - else { - if (!searchValue) { - for (const item of currentWithChildrenAndDescendants.descendants) - copyValue.add(item) - } - - copyValue.add(pageId) - } - - onSelect(new Set(copyValue)) - } - - const handlePreview = (index: number) => { - const current = currentDataList[index] - const pageId = current.page_id - - setLocalPreviewPageId(pageId) - - if (onPreview) - onPreview(pageId) - } - - if (!currentDataList.length) { + if (!rows.length) { return (
{t('dataSource.notion.selector.noSearchResult', { ns: 'common' })} @@ -307,29 +54,18 @@ const PageSelector = ({ } return ( - data.dataList[index].page_id} - itemData={{ - dataList: currentDataList, - handleToggle, - checkedIds: value, - disabledCheckedIds: disabledValue, - handleCheck, - canPreview, - handlePreview, - listMapWithChildrenAndDescendants, - searchValue, - previewPageId: currentPreviewPageId, - pagesMap, - }} - > - {Item} - + ) } diff --git a/web/app/components/base/notion-page-selector/page-selector/page-row.tsx b/web/app/components/base/notion-page-selector/page-selector/page-row.tsx new file mode 100644 index 00000000000..849c8507a84 --- /dev/null +++ b/web/app/components/base/notion-page-selector/page-selector/page-row.tsx @@ -0,0 +1,116 @@ +import type { CSSProperties } from 'react' +import type { NotionPageRow as NotionPageRowData, NotionPageSelectionMode } from './types' +import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' +import { memo } from 'react' +import { useTranslation } from 'react-i18next' +import Checkbox from '@/app/components/base/checkbox' +import NotionIcon from '@/app/components/base/notion-icon' +import Radio from '@/app/components/base/radio/ui' +import { cn } from '@/utils/classnames' + +type NotionPageRowProps = { + checked: boolean + disabled: boolean + isPreviewed: boolean + onPreview: (pageId: string) => void + onSelect: (pageId: string) => void + onToggle: (pageId: string) => void + row: NotionPageRowData + searchValue: string + selectionMode: NotionPageSelectionMode + showPreview: boolean + style: CSSProperties +} + +const NotionPageRow = ({ + checked, + disabled, + isPreviewed, + onPreview, + onSelect, + onToggle, + row, + searchValue, + selectionMode, + showPreview, + style, +}: NotionPageRowProps) => { + const { t } = useTranslation() + const pageId = row.page.page_id + const breadcrumbs = row.ancestors.length ? [...row.ancestors, row.page.page_name] : [row.page.page_name] + + return ( +
+ {selectionMode === 'multiple' + ? ( + onSelect(pageId)} + id={`notion-page-checkbox-${pageId}`} + /> + ) + : ( + onSelect(pageId)} + /> + )} + {!searchValue && row.hasChild && ( +
onToggle(pageId)} + data-testid={`notion-page-toggle-${pageId}`} + > + {row.expand + ? + : } +
+ )} + {!searchValue && !row.hasChild && row.parentExists && ( +
+ )} + +
+ {row.page.page_name} +
+ {showPreview && ( +
onPreview(pageId)} + data-testid={`notion-page-preview-${pageId}`} + > + {t('dataSource.notion.selector.preview', { ns: 'common' })} +
+ )} + {searchValue && ( +
+ {breadcrumbs.join(' / ')} +
+ )} +
+ ) +} + +export default memo(NotionPageRow) diff --git a/web/app/components/base/notion-page-selector/page-selector/types.ts b/web/app/components/base/notion-page-selector/page-selector/types.ts new file mode 100644 index 00000000000..c823ac3f7f4 --- /dev/null +++ b/web/app/components/base/notion-page-selector/page-selector/types.ts @@ -0,0 +1,21 @@ +import type { DataSourceNotionPage } from '@/models/common' + +export type NotionPageSelectionMode = 'multiple' | 'single' + +export type NotionPageTreeItem = { + children: Set + descendants: Set + depth: number + ancestors: string[] +} & DataSourceNotionPage + +export type NotionPageTreeMap = Record + +export type NotionPageRow = { + page: DataSourceNotionPage + parentExists: boolean + depth: number + expand: boolean + hasChild: boolean + ancestors: string[] +} diff --git a/web/app/components/base/notion-page-selector/page-selector/use-page-selector-model.ts b/web/app/components/base/notion-page-selector/page-selector/use-page-selector-model.ts new file mode 100644 index 00000000000..ec35ee05f26 --- /dev/null +++ b/web/app/components/base/notion-page-selector/page-selector/use-page-selector-model.ts @@ -0,0 +1,88 @@ +import type { NotionPageSelectionMode } from './types' +import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common' +import { startTransition, useCallback, useDeferredValue, useMemo, useState } from 'react' +import { buildNotionPageTree, getNextSelectedPageIds, getRootPageIds, getVisiblePageRows } from './utils' + +type UsePageSelectorModelProps = { + checkedIds: Set + searchValue: string + pagesMap: DataSourceNotionPageMap + list: DataSourceNotionPage[] + onSelect: (selectedPagesId: Set) => void + previewPageId?: string + onPreview?: (selectedPageId: string) => void + selectionMode: NotionPageSelectionMode +} + +export const usePageSelectorModel = ({ + checkedIds, + searchValue, + pagesMap, + list, + onSelect, + previewPageId, + onPreview, + selectionMode, +}: UsePageSelectorModelProps) => { + const deferredSearchValue = useDeferredValue(searchValue) + const [expandedIds, setExpandedIds] = useState>(() => new Set()) + const [localPreviewPageId, setLocalPreviewPageId] = useState('') + + const treeMap = useMemo(() => buildNotionPageTree(list, pagesMap), [list, pagesMap]) + const rootPageIds = useMemo(() => getRootPageIds(list, pagesMap), [list, pagesMap]) + + const rows = useMemo(() => { + return getVisiblePageRows({ + list, + pagesMap, + searchValue: deferredSearchValue, + treeMap, + rootPageIds, + expandedIds, + }) + }, [deferredSearchValue, expandedIds, list, pagesMap, rootPageIds, treeMap]) + + const currentPreviewPageId = previewPageId ?? localPreviewPageId + + const handleToggle = useCallback((pageId: string) => { + startTransition(() => { + setExpandedIds((currentExpandedIds) => { + const nextExpandedIds = new Set(currentExpandedIds) + + if (nextExpandedIds.has(pageId)) { + nextExpandedIds.delete(pageId) + treeMap[pageId]?.descendants.forEach(descendantId => nextExpandedIds.delete(descendantId)) + } + else { + nextExpandedIds.add(pageId) + } + + return nextExpandedIds + }) + }) + }, [treeMap]) + + const handleSelect = useCallback((pageId: string) => { + onSelect(getNextSelectedPageIds({ + checkedIds, + pageId, + searchValue: deferredSearchValue, + selectionMode, + treeMap, + })) + }, [checkedIds, deferredSearchValue, onSelect, selectionMode, treeMap]) + + const handlePreview = useCallback((pageId: string) => { + setLocalPreviewPageId(pageId) + onPreview?.(pageId) + }, [onPreview]) + + return { + currentPreviewPageId, + effectiveSearchValue: deferredSearchValue, + rows, + handlePreview, + handleSelect, + handleToggle, + } +} diff --git a/web/app/components/base/notion-page-selector/page-selector/utils.ts b/web/app/components/base/notion-page-selector/page-selector/utils.ts new file mode 100644 index 00000000000..d9327ddb0f9 --- /dev/null +++ b/web/app/components/base/notion-page-selector/page-selector/utils.ts @@ -0,0 +1,163 @@ +import type { NotionPageRow, NotionPageSelectionMode, NotionPageTreeItem, NotionPageTreeMap } from './types' +import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common' + +export const recursivePushInParentDescendants = ( + pagesMap: DataSourceNotionPageMap, + listTreeMap: NotionPageTreeMap, + current: NotionPageTreeItem, + leafItem: NotionPageTreeItem, +) => { + const parentId = current.parent_id + const pageId = current.page_id + + if (!parentId || !pageId) + return + + if (parentId !== 'root' && pagesMap[parentId]) { + if (!listTreeMap[parentId]) { + const children = new Set([pageId]) + const descendants = new Set([pageId, leafItem.page_id]) + listTreeMap[parentId] = { + ...pagesMap[parentId], + children, + descendants, + depth: 0, + ancestors: [], + } + } + else { + listTreeMap[parentId].children.add(pageId) + listTreeMap[parentId].descendants.add(pageId) + listTreeMap[parentId].descendants.add(leafItem.page_id) + } + + leafItem.depth++ + leafItem.ancestors.unshift(listTreeMap[parentId].page_name) + + if (listTreeMap[parentId].parent_id !== 'root') + recursivePushInParentDescendants(pagesMap, listTreeMap, listTreeMap[parentId], leafItem) + } +} + +export const buildNotionPageTree = ( + list: DataSourceNotionPage[], + pagesMap: DataSourceNotionPageMap, +): NotionPageTreeMap => { + return list.reduce((prev: NotionPageTreeMap, next) => { + const pageId = next.page_id + if (!prev[pageId]) + prev[pageId] = { ...next, children: new Set(), descendants: new Set(), depth: 0, ancestors: [] } + + recursivePushInParentDescendants(pagesMap, prev, prev[pageId], prev[pageId]) + return prev + }, {}) +} + +export const getRootPageIds = ( + list: DataSourceNotionPage[], + pagesMap: DataSourceNotionPageMap, +) => { + return list + .filter(item => item.parent_id === 'root' || !pagesMap[item.parent_id]) + .map(item => item.page_id) +} + +export const getVisiblePageRows = ({ + list, + pagesMap, + searchValue, + treeMap, + rootPageIds, + expandedIds, +}: { + list: DataSourceNotionPage[] + pagesMap: DataSourceNotionPageMap + searchValue: string + treeMap: NotionPageTreeMap + rootPageIds: string[] + expandedIds: Set +}): NotionPageRow[] => { + if (searchValue) { + return list + .filter(item => item.page_name.includes(searchValue)) + .map(item => ({ + page: item, + parentExists: item.parent_id !== 'root' && Boolean(pagesMap[item.parent_id]), + depth: treeMap[item.page_id]?.depth ?? 0, + expand: false, + hasChild: (treeMap[item.page_id]?.children.size ?? 0) > 0, + ancestors: treeMap[item.page_id]?.ancestors ?? [], + })) + } + + const rows: NotionPageRow[] = [] + + const visit = (pageId: string) => { + const current = treeMap[pageId] + if (!current) + return + + const expand = expandedIds.has(pageId) + rows.push({ + page: current, + parentExists: current.parent_id !== 'root' && Boolean(pagesMap[current.parent_id]), + depth: current.depth, + expand, + hasChild: current.children.size > 0, + ancestors: current.ancestors, + }) + + if (!expand) + return + + current.children.forEach(visit) + } + + rootPageIds.forEach(visit) + + return rows +} + +export const getNextSelectedPageIds = ({ + checkedIds, + pageId, + searchValue, + selectionMode, + treeMap, +}: { + checkedIds: Set + pageId: string + searchValue: string + selectionMode: NotionPageSelectionMode + treeMap: NotionPageTreeMap +}) => { + const nextCheckedIds = new Set(checkedIds) + const descendants = treeMap[pageId]?.descendants ?? new Set() + + if (selectionMode === 'single') { + if (nextCheckedIds.has(pageId)) { + nextCheckedIds.delete(pageId) + } + else { + nextCheckedIds.clear() + nextCheckedIds.add(pageId) + } + + return nextCheckedIds + } + + if (nextCheckedIds.has(pageId)) { + if (!searchValue) + descendants.forEach(item => nextCheckedIds.delete(item)) + + nextCheckedIds.delete(pageId) + return nextCheckedIds + } + + if (!searchValue) + descendants.forEach(item => nextCheckedIds.add(item)) + + nextCheckedIds.add(pageId) + + return nextCheckedIds +} diff --git a/web/app/components/base/notion-page-selector/page-selector/virtual-page-list.tsx b/web/app/components/base/notion-page-selector/page-selector/virtual-page-list.tsx new file mode 100644 index 00000000000..6fec6c0b841 --- /dev/null +++ b/web/app/components/base/notion-page-selector/page-selector/virtual-page-list.tsx @@ -0,0 +1,93 @@ +'use client' + +import type { NotionPageRow, NotionPageSelectionMode } from './types' +import { useVirtualizer } from '@tanstack/react-virtual' +import { useRef } from 'react' +import PageRow from './page-row' + +type VirtualPageListProps = { + checkedIds: Set + disabledValue: Set + onPreview: (pageId: string) => void + onSelect: (pageId: string) => void + onToggle: (pageId: string) => void + previewPageId: string + rows: NotionPageRow[] + searchValue: string + selectionMode: NotionPageSelectionMode + showPreview: boolean +} + +const rowHeight = 28 + +const VirtualPageList = ({ + checkedIds, + disabledValue, + onPreview, + onSelect, + onToggle, + previewPageId, + rows, + searchValue, + selectionMode, + showPreview, +}: VirtualPageListProps) => { + const scrollRef = useRef(null) + + const rowVirtualizer = useVirtualizer({ + count: rows.length, + estimateSize: () => rowHeight, + getScrollElement: () => scrollRef.current, + overscan: 6, + paddingEnd: 8, + paddingStart: 8, + }) + + const virtualRows = rowVirtualizer.getVirtualItems() + + return ( +
+
+ {virtualRows.map((virtualRow) => { + const row = rows[virtualRow.index] + const pageId = row.page.page_id + + return ( + + ) + })} +
+
+ ) +} + +export default VirtualPageList diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/index.tsx index 15b9ee7332c..5051d343cb5 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/index.tsx @@ -106,7 +106,7 @@ const OnlineDocuments = ({ if (!currentCredentialId) return getOnlineDocuments() - }, [currentCredentialId]) + }, [currentCredentialId, getOnlineDocuments]) const handleSearchValueChange = useCallback((value: string) => { const { setSearchValue } = dataSourceStore.getState() @@ -156,6 +156,7 @@ const OnlineDocuments = ({ {documentsData?.length ? ( ) : ( diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/__tests__/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/__tests__/index.spec.tsx index bdfa809aed8..a6d5738e2d0 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/__tests__/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/__tests__/index.spec.tsx @@ -1,26 +1,11 @@ -import type { NotionPageTreeItem, NotionPageTreeMap } from '../index' +import type { NotionPageTreeItem, NotionPageTreeMap } from '@/app/components/base/notion-page-selector/page-selector/types' import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common' import { fireEvent, render, screen } from '@testing-library/react' import * as React from 'react' +import { recursivePushInParentDescendants } from '@/app/components/base/notion-page-selector/page-selector/utils' import PageSelector from '../index' -import { recursivePushInParentDescendants } from '../utils' -// Mock react-window FixedSizeList - renders items directly for testing -vi.mock('react-window', () => ({ - FixedSizeList: ({ children: ItemComponent, itemCount, itemData, itemKey }: { children: React.ComponentType<{ index: number, style: React.CSSProperties, data: unknown }>, itemCount: number, itemData: unknown, itemKey?: (index: number, data: unknown) => string | number }) => ( -
- {Array.from({ length: itemCount }).map((_, index) => ( - - ))} -
- ), - areEqual: (prevProps: Record, nextProps: Record) => prevProps === nextProps, -})) +vi.mock('@tanstack/react-virtual') // Note: NotionIcon from @/app/components/base/ is NOT mocked - using real component per testing guidelines @@ -70,7 +55,6 @@ const createDefaultProps = (overrides?: Partial): PageSelecto canPreview: true, onPreview: vi.fn(), isMultipleChoice: true, - currentCredentialId: 'cred-1', ...overrides, } } @@ -114,7 +98,7 @@ describe('PageSelector', () => { expect(screen.queryByTestId('virtual-list')).not.toBeInTheDocument() }) - it('should render items using FixedSizeList', () => { + it('should render items using VirtualList', () => { const pages = [ createMockPage({ page_id: 'page-1', page_name: 'Page 1' }), createMockPage({ page_id: 'page-2', page_name: 'Page 2' }), diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/__tests__/utils.spec.ts b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/__tests__/utils.spec.ts index 601dc2f5bf3..2a081ef4189 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/__tests__/utils.spec.ts +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/__tests__/utils.spec.ts @@ -1,7 +1,7 @@ -import type { NotionPageTreeItem, NotionPageTreeMap } from '../index' +import type { NotionPageTreeItem, NotionPageTreeMap } from '@/app/components/base/notion-page-selector/page-selector/types' import type { DataSourceNotionPageMap } from '@/models/common' import { describe, expect, it } from 'vitest' -import { recursivePushInParentDescendants } from '../utils' +import { recursivePushInParentDescendants } from '@/app/components/base/notion-page-selector/page-selector/utils' const makePageEntry = (overrides: Partial): NotionPageTreeItem => ({ page_icon: null, diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/index.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/index.tsx index 2ea0bd3112a..5c6cb74bce8 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/index.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/index.tsx @@ -1,9 +1,7 @@ import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common' -import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { FixedSizeList as List } from 'react-window' -import Item from './item' -import { recursivePushInParentDescendants } from './utils' +import { usePageSelectorModel } from '@/app/components/base/notion-page-selector/page-selector/use-page-selector-model' +import VirtualPageList from '@/app/components/base/notion-page-selector/page-selector/virtual-page-list' type PageSelectorProps = { checkedIds: Set @@ -15,23 +13,9 @@ type PageSelectorProps = { canPreview?: boolean onPreview?: (selectedPageId: string) => void isMultipleChoice?: boolean - currentCredentialId: string + currentCredentialId?: string } -export type NotionPageTreeItem = { - children: Set - descendants: Set - depth: number - ancestors: string[] -} & DataSourceNotionPage - -export type NotionPageTreeMap = Record - -type NotionPageItem = { - expand: boolean - depth: number -} & DataSourceNotionPage - const PageSelector = ({ checkedIds, disabledValue, @@ -42,116 +26,28 @@ const PageSelector = ({ canPreview = true, onPreview, isMultipleChoice = true, - currentCredentialId, + currentCredentialId: _currentCredentialId, }: PageSelectorProps) => { const { t } = useTranslation() - const [dataList, setDataList] = useState([]) - const [currentPreviewPageId, setCurrentPreviewPageId] = useState('') - - useEffect(() => { - setDataList(list.filter(item => item.parent_id === 'root' || !pagesMap[item.parent_id]).map((item) => { - return { - ...item, - expand: false, - depth: 0, - } - })) - }, [currentCredentialId]) - - const searchDataList = list.filter((item) => { - return item.page_name.includes(searchValue) - }).map((item) => { - return { - ...item, - expand: false, - depth: 0, - } + const selectionMode = isMultipleChoice ? 'multiple' : 'single' + const { + currentPreviewPageId, + effectiveSearchValue, + rows, + handlePreview, + handleSelect, + handleToggle, + } = usePageSelectorModel({ + checkedIds, + list, + onPreview, + onSelect, + pagesMap, + searchValue, + selectionMode, }) - const currentDataList = searchValue ? searchDataList : dataList - const listMapWithChildrenAndDescendants = useMemo(() => { - return list.reduce((prev: NotionPageTreeMap, next: DataSourceNotionPage) => { - const pageId = next.page_id - if (!prev[pageId]) - prev[pageId] = { ...next, children: new Set(), descendants: new Set(), depth: 0, ancestors: [] } - - recursivePushInParentDescendants(pagesMap, prev, prev[pageId], prev[pageId]) - return prev - }, {}) - }, [list, pagesMap]) - - const handleToggle = useCallback((index: number) => { - const current = dataList[index] - const pageId = current.page_id - const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[pageId] - const descendantsIds = Array.from(currentWithChildrenAndDescendants.descendants) - const childrenIds = Array.from(currentWithChildrenAndDescendants.children) - let newDataList = [] - - if (current.expand) { - current.expand = false - - newDataList = dataList.filter(item => !descendantsIds.includes(item.page_id)) - } - else { - current.expand = true - - newDataList = [ - ...dataList.slice(0, index + 1), - ...childrenIds.map(item => ({ - ...pagesMap[item], - expand: false, - depth: listMapWithChildrenAndDescendants[item].depth, - })), - ...dataList.slice(index + 1), - ] - } - setDataList(newDataList) - }, [dataList, listMapWithChildrenAndDescendants, pagesMap]) - - const handleCheck = useCallback((index: number) => { - const copyValue = new Set(checkedIds) - const current = currentDataList[index] - const pageId = current.page_id - const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[pageId] - - if (copyValue.has(pageId)) { - if (!searchValue && isMultipleChoice) { - for (const item of currentWithChildrenAndDescendants.descendants) - copyValue.delete(item) - } - - copyValue.delete(pageId) - } - else { - if (!searchValue && isMultipleChoice) { - for (const item of currentWithChildrenAndDescendants.descendants) - copyValue.add(item) - } - // Single choice mode, clear previous selection - if (!isMultipleChoice && copyValue.size > 0) { - copyValue.clear() - copyValue.add(pageId) - } - else { - copyValue.add(pageId) - } - } - - onSelect(new Set(copyValue)) - }, [currentDataList, isMultipleChoice, listMapWithChildrenAndDescendants, onSelect, searchValue, checkedIds]) - - const handlePreview = useCallback((index: number) => { - const current = currentDataList[index] - const pageId = current.page_id - - setCurrentPreviewPageId(pageId) - - if (onPreview) - onPreview(pageId) - }, [currentDataList, onPreview]) - - if (!currentDataList.length) { + if (!rows.length) { return (
{t('dataSource.notion.selector.noSearchResult', { ns: 'common' })} @@ -160,30 +56,18 @@ const PageSelector = ({ } return ( - data.dataList[index].page_id} - itemData={{ - dataList: currentDataList, - handleToggle, - checkedIds, - disabledCheckedIds: disabledValue, - handleCheck, - canPreview, - handlePreview, - listMapWithChildrenAndDescendants, - searchValue, - previewPageId: currentPreviewPageId, - pagesMap, - isMultipleChoice, - }} - > - {Item} - + ) } diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/item.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/item.tsx deleted file mode 100644 index 29e4143e0c2..00000000000 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/item.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import type { ListChildComponentProps } from 'react-window' -import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common' -import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { areEqual } from 'react-window' -import Checkbox from '@/app/components/base/checkbox' -import NotionIcon from '@/app/components/base/notion-icon' -import Radio from '@/app/components/base/radio/ui' -import { cn } from '@/utils/classnames' - -type NotionPageTreeItem = { - children: Set - descendants: Set - depth: number - ancestors: string[] -} & DataSourceNotionPage - -type NotionPageTreeMap = Record - -type NotionPageItem = { - expand: boolean - depth: number -} & DataSourceNotionPage - -const Item = ({ index, style, data }: ListChildComponentProps<{ - dataList: NotionPageItem[] - handleToggle: (index: number) => void - checkedIds: Set - disabledCheckedIds: Set - handleCheck: (index: number) => void - canPreview?: boolean - handlePreview: (index: number) => void - listMapWithChildrenAndDescendants: NotionPageTreeMap - searchValue: string - previewPageId: string - pagesMap: DataSourceNotionPageMap - isMultipleChoice?: boolean -}>) => { - const { t } = useTranslation() - const { - dataList, - handleToggle, - checkedIds, - disabledCheckedIds, - handleCheck, - canPreview, - handlePreview, - listMapWithChildrenAndDescendants, - searchValue, - previewPageId, - pagesMap, - isMultipleChoice, - } = data - const current = dataList[index] - const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[current.page_id] - const hasChild = currentWithChildrenAndDescendants.descendants.size > 0 - const ancestors = currentWithChildrenAndDescendants.ancestors - const breadCrumbs = ancestors.length ? [...ancestors, current.page_name] : [current.page_name] - const disabled = disabledCheckedIds.has(current.page_id) - - const renderArrow = () => { - if (hasChild) { - return ( -
handleToggle(index)} - > - { - current.expand - ? - : - } -
- ) - } - if (current.parent_id === 'root' || !pagesMap[current.parent_id]) { - return ( -
- ) - } - return ( -
- ) - } - - return ( -
- {isMultipleChoice - ? ( - { - handleCheck(index) - }} - /> - ) - : ( - { - handleCheck(index) - }} - /> - )} - {!searchValue && renderArrow()} - -
- {current.page_name} -
- { - canPreview && ( -
handlePreview(index)} - > - {t('dataSource.notion.selector.preview', { ns: 'common' })} -
- ) - } - { - searchValue && ( -
- {breadCrumbs.join(' / ')} -
- ) - } -
- ) -} - -export default React.memo(Item, areEqual) diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/utils.ts b/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/utils.ts deleted file mode 100644 index 395ef49006e..00000000000 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/utils.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { NotionPageTreeItem, NotionPageTreeMap } from './index' -import type { DataSourceNotionPageMap } from '@/models/common' - -export const recursivePushInParentDescendants = ( - pagesMap: DataSourceNotionPageMap, - listTreeMap: NotionPageTreeMap, - current: NotionPageTreeItem, - leafItem: NotionPageTreeItem, -) => { - const parentId = current.parent_id - const pageId = current.page_id - - if (!parentId || !pageId) - return - - if (parentId !== 'root' && pagesMap[parentId]) { - if (!listTreeMap[parentId]) { - const children = new Set([pageId]) - const descendants = new Set([pageId, leafItem.page_id]) - listTreeMap[parentId] = { - ...pagesMap[parentId], - children, - descendants, - depth: 0, - ancestors: [], - } - } - else { - listTreeMap[parentId].children.add(pageId) - listTreeMap[parentId].descendants.add(pageId) - listTreeMap[parentId].descendants.add(leafItem.page_id) - } - leafItem.depth++ - leafItem.ancestors.unshift(listTreeMap[parentId].page_name) - - if (listTreeMap[parentId].parent_id !== 'root') - recursivePushInParentDescendants(pagesMap, listTreeMap, listTreeMap[parentId], leafItem) - } -} diff --git a/web/app/styles/markdown.css b/web/app/styles/markdown.css index 45b3d4bad1f..bd4d413b27d 100644 --- a/web/app/styles/markdown.css +++ b/web/app/styles/markdown.css @@ -1070,9 +1070,6 @@ filter: invert(50%); } -.markdown-body .react-syntax-highlighter-line-number { - color: var(--color-text-quaternary); -} .markdown-body .abcjs-inline-audio .abcjs-btn { display: flex !important; } diff --git a/web/eslint-suppressions.json b/web/eslint-suppressions.json index 1a7463b7c65..3e5e0e26031 100644 --- a/web/eslint-suppressions.json +++ b/web/eslint-suppressions.json @@ -3328,11 +3328,6 @@ "count": 2 } }, - "app/components/base/notion-page-selector/base.tsx": { - "react/set-state-in-effect": { - "count": 2 - } - }, "app/components/base/notion-page-selector/credential-selector/index.tsx": { "tailwindcss/enforce-consistent-class-order": { "count": 2 @@ -3348,14 +3343,6 @@ "count": 1 } }, - "app/components/base/notion-page-selector/page-selector/index.tsx": { - "react/set-state-in-effect": { - "count": 1 - }, - "tailwindcss/enforce-consistent-class-order": { - "count": 3 - } - }, "app/components/base/notion-page-selector/search-input/index.tsx": { "tailwindcss/enforce-consistent-class-order": { "count": 1 @@ -4831,16 +4818,6 @@ "count": 1 } }, - "app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/index.tsx": { - "react/set-state-in-effect": { - "count": 1 - } - }, - "app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/item.tsx": { - "tailwindcss/enforce-consistent-class-order": { - "count": 3 - } - }, "app/components/datasets/documents/create-from-pipeline/data-source/online-documents/title.tsx": { "tailwindcss/enforce-consistent-class-order": { "count": 1 diff --git a/web/i18n/en-US/common.json b/web/i18n/en-US/common.json index c21aa25eae2..dc8e02e66d6 100644 --- a/web/i18n/en-US/common.json +++ b/web/i18n/en-US/common.json @@ -135,6 +135,9 @@ "dataSource.notion.pagesAuthorized": "Pages authorized", "dataSource.notion.remove": "Remove", "dataSource.notion.selector.addPages": "Add pages", + "dataSource.notion.selector.configure": "Configure Notion", + "dataSource.notion.selector.docs": "Notion docs", + "dataSource.notion.selector.headerTitle": "Choose Notion pages", "dataSource.notion.selector.noSearchResult": "No search results", "dataSource.notion.selector.pageSelected": "Pages Selected", "dataSource.notion.selector.preview": "PREVIEW", diff --git a/web/package.json b/web/package.json index f822e874253..d2a9e88f4a2 100644 --- a/web/package.json +++ b/web/package.json @@ -81,6 +81,7 @@ "@tailwindcss/typography": "catalog:", "@tanstack/react-form": "catalog:", "@tanstack/react-query": "catalog:", + "@tanstack/react-virtual": "catalog:", "abcjs": "catalog:", "ahooks": "catalog:", "class-variance-authority": "catalog:", @@ -136,7 +137,6 @@ "react-pdf-highlighter": "catalog:", "react-sortablejs": "catalog:", "react-textarea-autosize": "catalog:", - "react-window": "catalog:", "reactflow": "catalog:", "remark-breaks": "catalog:", "remark-directive": "catalog:", @@ -197,7 +197,6 @@ "@types/qs": "catalog:", "@types/react": "catalog:", "@types/react-dom": "catalog:", - "@types/react-window": "catalog:", "@types/sortablejs": "catalog:", "@typescript-eslint/parser": "catalog:", "@typescript/native-preview": "catalog:",