From 4d60a742dc15e45b826c5dfd575fb887eab25148 Mon Sep 17 00:00:00 2001 From: yyh Date: Wed, 21 Jan 2026 16:16:52 +0800 Subject: [PATCH] update --- .../page-selector/index.tsx | 88 +++++++++---------- .../online-documents/page-selector/index.tsx | 60 +++++++------ .../online-documents/page-selector/item.tsx | 26 +++--- 3 files changed, 86 insertions(+), 88 deletions(-) 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 b53a66270f..9b424b99f5 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,7 +1,7 @@ import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common' import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' import { useVirtualizer } from '@tanstack/react-virtual' -import { memo, useMemo, useRef, useState } from 'react' +import { memo, useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { cn } from '@/utils/classnames' import Checkbox from '../../checkbox' @@ -32,16 +32,15 @@ type NotionPageItem = { } & DataSourceNotionPage type ItemProps = { - index: number virtualStart: number virtualSize: number current: NotionPageItem - handleToggle: (index: number) => void + onToggle: (pageId: string) => void checkedIds: Set disabledCheckedIds: Set - handleCheck: (index: number) => void + onCheck: (pageId: string) => void canPreview?: boolean - handlePreview: (index: number) => void + onPreview: (pageId: string) => void listMapWithChildrenAndDescendants: NotionPageTreeMap searchValue: string previewPageId: string @@ -86,16 +85,15 @@ const recursivePushInParentDescendants = ( } const ItemComponent = ({ - index, virtualStart, virtualSize, current, - handleToggle, + onToggle, checkedIds, disabledCheckedIds, - handleCheck, + onCheck, canPreview, - handlePreview, + onPreview, listMapWithChildrenAndDescendants, searchValue, previewPageId, @@ -114,7 +112,7 @@ const ItemComponent = ({
handleToggle(index)} + onClick={() => onToggle(current.page_id)} > { current.expand @@ -151,9 +149,7 @@ const ItemComponent = ({ className="mr-2 shrink-0" checked={checkedIds.has(current.page_id)} disabled={disabled} - onCheck={() => { - handleCheck(index) - }} + onCheck={() => onCheck(current.page_id)} /> {!searchValue && renderArrow()} handlePreview(index)} + onClick={() => onPreview(current.page_id)} > {t('dataSource.notion.selector.preview', { ns: 'common' })}
@@ -221,14 +217,25 @@ const PageSelector = ({ }, {}) }, [list, pagesMap]) + // Pre-build children index for O(1) lookup instead of O(n) filter + const childrenByParent = useMemo(() => { + const map = new Map() + for (const item of list) { + const isRoot = item.parent_id === 'root' || !pagesMap[item.parent_id] + const parentKey = isRoot ? null : item.parent_id + const children = map.get(parentKey) || [] + children.push(item) + map.set(parentKey, children) + } + return map + }, [list, pagesMap]) + // Compute visible data list based on expanded state const dataList = useMemo(() => { const result: NotionPageItem[] = [] const buildVisibleList = (parentId: string | null, depth: number) => { - const items = parentId === null - ? list.filter(item => item.parent_id === 'root' || !pagesMap[item.parent_id]) - : list.filter(item => item.parent_id === parentId) + const items = childrenByParent.get(parentId) || [] for (const item of items) { const isExpanded = expandedIds.has(item.page_id) @@ -245,7 +252,7 @@ const PageSelector = ({ buildVisibleList(null, 0) return result - }, [list, pagesMap, expandedIds]) + }, [childrenByParent, expandedIds]) const searchDataList = useMemo(() => list.filter((item) => { return item.page_name.includes(searchValue) @@ -268,39 +275,37 @@ const PageSelector = ({ getItemKey: index => currentDataList[index].page_id, }) - const handleToggle = (index: number) => { - const current = dataList[index] - const pageId = current.page_id - const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[pageId] - + // Stable callback - no dependencies on dataList + const handleToggle = useCallback((pageId: string) => { setExpandedIds((prev) => { const next = new Set(prev) if (prev.has(pageId)) { // Collapse: remove current and all descendants next.delete(pageId) - for (const descendantId of currentWithChildrenAndDescendants.descendants) - next.delete(descendantId) + // Note: We access listMapWithChildrenAndDescendants via closure, but it's stable (memoized) + const descendants = listMapWithChildrenAndDescendants[pageId]?.descendants + if (descendants) { + for (const descendantId of descendants) + next.delete(descendantId) + } } else { - // Expand: add current next.add(pageId) } return next }) - } + }, [listMapWithChildrenAndDescendants]) - const copyValue = new Set(value) - const handleCheck = (index: number) => { - const current = currentDataList[index] - const pageId = current.page_id + // Stable callback - uses pageId parameter instead of index + const handleCheck = useCallback((pageId: string) => { const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[pageId] + const copyValue = new Set(value) if (copyValue.has(pageId)) { if (!searchValue) { for (const item of currentWithChildrenAndDescendants.descendants) copyValue.delete(item) } - copyValue.delete(pageId) } else { @@ -308,22 +313,18 @@ const PageSelector = ({ 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 + }, [listMapWithChildrenAndDescendants, onSelect, searchValue, value]) + // Stable callback + const handlePreview = useCallback((pageId: string) => { setLocalPreviewPageId(pageId) - if (onPreview) onPreview(pageId) - } + }, [onPreview]) if (!currentDataList.length) { return ( @@ -351,16 +352,15 @@ const PageSelector = ({ return ( { + const map = new Map() + for (const item of list) { + const isRoot = item.parent_id === 'root' || !pagesMap[item.parent_id] + const parentKey = isRoot ? null : item.parent_id + const children = map.get(parentKey) || [] + children.push(item) + map.set(parentKey, children) + } + return map + }, [list, pagesMap]) + // Compute visible data list based on expanded state const dataList = useMemo(() => { const result: NotionPageItem[] = [] const buildVisibleList = (parentId: string | null, depth: number) => { - const items = parentId === null - ? list.filter(item => item.parent_id === 'root' || !pagesMap[item.parent_id]) - : list.filter(item => item.parent_id === parentId) + const items = childrenByParent.get(parentId) || [] for (const item of items) { const isExpanded = expandedIds.has(item.page_id) @@ -91,7 +102,7 @@ const PageSelector = ({ buildVisibleList(null, 0) return result - }, [list, pagesMap, expandedIds]) + }, [childrenByParent, expandedIds]) const searchDataList = useMemo(() => list.filter((item) => { return item.page_name.includes(searchValue) @@ -113,31 +124,29 @@ const PageSelector = ({ getItemKey: index => currentDataList[index].page_id, }) - const handleToggle = useCallback((index: number) => { - const current = dataList[index] - const pageId = current.page_id - const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[pageId] - + // Stable callback - no dependencies on dataList + const handleToggle = useCallback((pageId: string) => { setExpandedIds((prev) => { const next = new Set(prev) if (prev.has(pageId)) { // Collapse: remove current and all descendants next.delete(pageId) - for (const descendantId of currentWithChildrenAndDescendants.descendants) - next.delete(descendantId) + const descendants = listMapWithChildrenAndDescendants[pageId]?.descendants + if (descendants) { + for (const descendantId of descendants) + next.delete(descendantId) + } } else { - // Expand: add current next.add(pageId) } return next }) - }, [dataList, listMapWithChildrenAndDescendants]) + }, [listMapWithChildrenAndDescendants]) - const handleCheck = useCallback((index: number) => { + // Stable callback - uses pageId parameter instead of index + const handleCheck = useCallback((pageId: string) => { const copyValue = new Set(checkedIds) - const current = currentDataList[index] - const pageId = current.page_id const currentWithChildrenAndDescendants = listMapWithChildrenAndDescendants[pageId] if (copyValue.has(pageId)) { @@ -145,7 +154,6 @@ const PageSelector = ({ for (const item of currentWithChildrenAndDescendants.descendants) copyValue.delete(item) } - copyValue.delete(pageId) } else { @@ -164,17 +172,14 @@ const PageSelector = ({ } onSelect(new Set(copyValue)) - }, [currentDataList, isMultipleChoice, listMapWithChildrenAndDescendants, onSelect, searchValue, checkedIds]) - - const handlePreview = useCallback((index: number) => { - const current = currentDataList[index] - const pageId = current.page_id + }, [checkedIds, isMultipleChoice, listMapWithChildrenAndDescendants, onSelect, searchValue]) + // Stable callback + const handlePreview = useCallback((pageId: string) => { setCurrentPreviewPageId(pageId) - if (onPreview) onPreview(pageId) - }, [currentDataList, onPreview]) + }, [onPreview]) if (!currentDataList.length) { return ( @@ -202,16 +207,15 @@ const PageSelector = ({ return ( void + onToggle: (pageId: string) => void checkedIds: Set disabledCheckedIds: Set - handleCheck: (index: number) => void + onCheck: (pageId: string) => void canPreview?: boolean - handlePreview: (index: number) => void + onPreview: (pageId: string) => void listMapWithChildrenAndDescendants: NotionPageTreeMap searchValue: string previewPageId: string @@ -40,16 +39,15 @@ type ItemProps = { } const Item = ({ - index, virtualStart, virtualSize, current, - handleToggle, + onToggle, checkedIds, disabledCheckedIds, - handleCheck, + onCheck, canPreview, - handlePreview, + onPreview, listMapWithChildrenAndDescendants, searchValue, previewPageId, @@ -69,7 +67,7 @@ const Item = ({
handleToggle(index)} + onClick={() => onToggle(current.page_id)} > { current.expand @@ -108,9 +106,7 @@ const Item = ({ className="mr-2 shrink-0" checked={checkedIds.has(current.page_id)} disabled={disabled} - onCheck={() => { - handleCheck(index) - }} + onCheck={() => onCheck(current.page_id)} /> ) : ( @@ -118,9 +114,7 @@ const Item = ({ className="mr-2 shrink-0" isChecked={checkedIds.has(current.page_id)} disabled={disabled} - onCheck={() => { - handleCheck(index) - }} + onCheck={() => onCheck(current.page_id)} /> )} {!searchValue && renderArrow()} @@ -141,7 +135,7 @@ const Item = ({ className="ml-1 hidden h-6 shrink-0 cursor-pointer items-center rounded-md border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-2 text-xs font-medium leading-4 text-components-button-secondary-text shadow-xs shadow-shadow-shadow-3 backdrop-blur-[10px] hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover group-hover:flex" - onClick={() => handlePreview(index)} + onClick={() => onPreview(current.page_id)} > {t('dataSource.notion.selector.preview', { ns: 'common' })}