mirror of
https://github.com/langgenius/dify.git
synced 2026-02-01 01:17:00 +08:00
Compare commits
3 Commits
refactor/m
...
fix/input-
| Author | SHA1 | Date | |
|---|---|---|---|
| 987809396d | |||
| 7518606932 | |||
| 3997749867 |
@ -145,10 +145,7 @@ export default function MailAndPasswordAuth({ isEmailSetup }: MailAndPasswordAut
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
id="password"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter')
|
||||
handleEmailPasswordLogin()
|
||||
}}
|
||||
onPressEnter={() => handleEmailPasswordLogin()}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
autoComplete="current-password"
|
||||
placeholder={t('passwordPlaceholder', { ns: 'login' }) || ''}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
import type { ChangeEventHandler, CSSProperties, FocusEventHandler } from 'react'
|
||||
import type { ChangeEventHandler, CSSProperties, FocusEventHandler, KeyboardEventHandler } from 'react'
|
||||
import { RiCloseCircleFill, RiErrorWarningLine, RiSearchLine } from '@remixicon/react'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import { noop } from 'es-toolkit/function'
|
||||
@ -33,6 +33,7 @@ export type InputProps = {
|
||||
wrapperClassName?: string
|
||||
styleCss?: CSSProperties
|
||||
unit?: string
|
||||
onPressEnter?: KeyboardEventHandler<HTMLInputElement>
|
||||
} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> & VariantProps<typeof inputVariants>
|
||||
|
||||
const removeLeadingZeros = (value: string) => value.replace(/^(-?)0+(?=\d)/, '$1')
|
||||
@ -52,10 +53,30 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({
|
||||
placeholder,
|
||||
onChange = noop,
|
||||
onBlur = noop,
|
||||
onKeyDown,
|
||||
onCompositionStart,
|
||||
onCompositionEnd,
|
||||
onPressEnter,
|
||||
unit,
|
||||
...props
|
||||
}, ref) => {
|
||||
const { t } = useTranslation()
|
||||
const isComposingRef = React.useRef(false)
|
||||
const handleCompositionStart: React.CompositionEventHandler<HTMLInputElement> = (e) => {
|
||||
isComposingRef.current = true
|
||||
onCompositionStart?.(e)
|
||||
}
|
||||
const handleCompositionEnd: React.CompositionEventHandler<HTMLInputElement> = (e) => {
|
||||
setTimeout(() => {
|
||||
isComposingRef.current = false
|
||||
}, 50)
|
||||
onCompositionEnd?.(e)
|
||||
}
|
||||
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (e) => {
|
||||
if (onPressEnter && e.key === 'Enter' && !e.nativeEvent.isComposing && !isComposingRef.current)
|
||||
onPressEnter(e)
|
||||
onKeyDown?.(e)
|
||||
}
|
||||
const handleNumberChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
if (value === 0) {
|
||||
// remove leading zeros
|
||||
@ -108,6 +129,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({
|
||||
onBlur={props.type === 'number' ? handleNumberBlur : onBlur}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
onKeyDown={handleKeyDown}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
/>
|
||||
{!!(showClearIcon && value && !disabled && !destructive) && (
|
||||
<div
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import type { ListChildComponentProps } from 'react-window'
|
||||
import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common'
|
||||
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
|
||||
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||
import { memo, useCallback, useMemo, useRef, useState } from 'react'
|
||||
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'
|
||||
@ -31,22 +32,6 @@ type NotionPageItem = {
|
||||
depth: number
|
||||
} & DataSourceNotionPage
|
||||
|
||||
type ItemProps = {
|
||||
virtualStart: number
|
||||
virtualSize: number
|
||||
current: NotionPageItem
|
||||
onToggle: (pageId: string) => void
|
||||
checkedIds: Set<string>
|
||||
disabledCheckedIds: Set<string>
|
||||
onCheck: (pageId: string) => void
|
||||
canPreview?: boolean
|
||||
onPreview: (pageId: string) => void
|
||||
listMapWithChildrenAndDescendants: NotionPageTreeMap
|
||||
searchValue: string
|
||||
previewPageId: string
|
||||
pagesMap: DataSourceNotionPageMap
|
||||
}
|
||||
|
||||
const recursivePushInParentDescendants = (
|
||||
pagesMap: DataSourceNotionPageMap,
|
||||
listTreeMap: NotionPageTreeMap,
|
||||
@ -84,22 +69,34 @@ const recursivePushInParentDescendants = (
|
||||
}
|
||||
}
|
||||
|
||||
const ItemComponent = ({
|
||||
virtualStart,
|
||||
virtualSize,
|
||||
current,
|
||||
onToggle,
|
||||
checkedIds,
|
||||
disabledCheckedIds,
|
||||
onCheck,
|
||||
canPreview,
|
||||
onPreview,
|
||||
listMapWithChildrenAndDescendants,
|
||||
searchValue,
|
||||
previewPageId,
|
||||
pagesMap,
|
||||
}: ItemProps) => {
|
||||
const ItemComponent = ({ index, style, data }: ListChildComponentProps<{
|
||||
dataList: NotionPageItem[]
|
||||
handleToggle: (index: number) => void
|
||||
checkedIds: Set<string>
|
||||
disabledCheckedIds: Set<string>
|
||||
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
|
||||
@ -112,7 +109,7 @@ const ItemComponent = ({
|
||||
<div
|
||||
className="mr-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-md hover:bg-components-button-ghost-bg-hover"
|
||||
style={{ marginLeft: current.depth * 8 }}
|
||||
onClick={() => onToggle(current.page_id)}
|
||||
onClick={() => handleToggle(index)}
|
||||
>
|
||||
{
|
||||
current.expand
|
||||
@ -135,21 +132,15 @@ const ItemComponent = ({
|
||||
return (
|
||||
<div
|
||||
className={cn('group flex cursor-pointer items-center rounded-md pl-2 pr-[2px] hover:bg-state-base-hover', previewPageId === current.page_id && 'bg-state-base-hover')}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 8,
|
||||
right: 8,
|
||||
width: 'calc(100% - 16px)',
|
||||
height: virtualSize,
|
||||
transform: `translateY(${virtualStart + 8}px)`,
|
||||
}}
|
||||
style={{ ...style, top: style.top as number + 8, left: 8, right: 8, width: 'calc(100% - 16px)' }}
|
||||
>
|
||||
<Checkbox
|
||||
className="mr-2 shrink-0"
|
||||
checked={checkedIds.has(current.page_id)}
|
||||
disabled={disabled}
|
||||
onCheck={() => onCheck(current.page_id)}
|
||||
onCheck={() => {
|
||||
handleCheck(index)
|
||||
}}
|
||||
/>
|
||||
{!searchValue && renderArrow()}
|
||||
<NotionIcon
|
||||
@ -169,7 +160,7 @@ const ItemComponent = ({
|
||||
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={() => onPreview(current.page_id)}
|
||||
onClick={() => handlePreview(index)}
|
||||
>
|
||||
{t('dataSource.notion.selector.preview', { ns: 'common' })}
|
||||
</div>
|
||||
@ -188,7 +179,7 @@ const ItemComponent = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const Item = memo(ItemComponent)
|
||||
const Item = memo(ItemComponent, areEqual)
|
||||
|
||||
const PageSelector = ({
|
||||
value,
|
||||
@ -202,10 +193,31 @@ const PageSelector = ({
|
||||
onPreview,
|
||||
}: PageSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const parentRef = useRef<HTMLDivElement>(null)
|
||||
const [expandedIds, setExpandedIds] = useState<Set<string>>(() => new Set())
|
||||
const [dataList, setDataList] = useState<NotionPageItem[]>([])
|
||||
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 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
|
||||
@ -217,89 +229,47 @@ const PageSelector = ({
|
||||
}, {})
|
||||
}, [list, pagesMap])
|
||||
|
||||
const childrenByParent = useMemo(() => {
|
||||
const map = new Map<string | null, DataSourceNotionPage[]>()
|
||||
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])
|
||||
|
||||
const dataList = useMemo(() => {
|
||||
const result: NotionPageItem[] = []
|
||||
|
||||
const buildVisibleList = (parentId: string | null, depth: number) => {
|
||||
const items = childrenByParent.get(parentId) || []
|
||||
|
||||
for (const item of items) {
|
||||
const isExpanded = expandedIds.has(item.page_id)
|
||||
result.push({
|
||||
...item,
|
||||
expand: isExpanded,
|
||||
depth,
|
||||
})
|
||||
if (isExpanded) {
|
||||
buildVisibleList(item.page_id, depth + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildVisibleList(null, 0)
|
||||
return result
|
||||
}, [childrenByParent, expandedIds])
|
||||
|
||||
const searchDataList = useMemo(() => list.filter((item) => {
|
||||
return item.page_name.includes(searchValue)
|
||||
}).map((item) => {
|
||||
return {
|
||||
...item,
|
||||
expand: false,
|
||||
depth: 0,
|
||||
}
|
||||
}), [list, searchValue])
|
||||
|
||||
const currentDataList = searchValue ? searchDataList : dataList
|
||||
const currentPreviewPageId = previewPageId === undefined ? localPreviewPageId : previewPageId
|
||||
|
||||
const virtualizer = useVirtualizer({
|
||||
count: currentDataList.length,
|
||||
getScrollElement: () => parentRef.current,
|
||||
estimateSize: () => 28,
|
||||
overscan: 5,
|
||||
getItemKey: index => currentDataList[index].page_id,
|
||||
})
|
||||
|
||||
const handleToggle = useCallback((pageId: string) => {
|
||||
setExpandedIds((prev) => {
|
||||
const next = new Set(prev)
|
||||
if (prev.has(pageId)) {
|
||||
next.delete(pageId)
|
||||
const descendants = listMapWithChildrenAndDescendants[pageId]?.descendants
|
||||
if (descendants) {
|
||||
for (const descendantId of descendants)
|
||||
next.delete(descendantId)
|
||||
}
|
||||
}
|
||||
else {
|
||||
next.add(pageId)
|
||||
}
|
||||
return next
|
||||
})
|
||||
}, [listMapWithChildrenAndDescendants])
|
||||
|
||||
const handleCheck = useCallback((pageId: string) => {
|
||||
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]
|
||||
const copyValue = new Set(value)
|
||||
|
||||
if (copyValue.has(pageId)) {
|
||||
if (!searchValue) {
|
||||
for (const item of currentWithChildrenAndDescendants.descendants)
|
||||
copyValue.delete(item)
|
||||
}
|
||||
|
||||
copyValue.delete(pageId)
|
||||
}
|
||||
else {
|
||||
@ -307,17 +277,22 @@ const PageSelector = ({
|
||||
for (const item of currentWithChildrenAndDescendants.descendants)
|
||||
copyValue.add(item)
|
||||
}
|
||||
|
||||
copyValue.add(pageId)
|
||||
}
|
||||
|
||||
onSelect(copyValue)
|
||||
}, [listMapWithChildrenAndDescendants, onSelect, searchValue, value])
|
||||
onSelect(new Set(copyValue))
|
||||
}
|
||||
|
||||
const handlePreview = (index: number) => {
|
||||
const current = currentDataList[index]
|
||||
const pageId = current.page_id
|
||||
|
||||
const handlePreview = useCallback((pageId: string) => {
|
||||
setLocalPreviewPageId(pageId)
|
||||
|
||||
if (onPreview)
|
||||
onPreview(pageId)
|
||||
}, [onPreview])
|
||||
}
|
||||
|
||||
if (!currentDataList.length) {
|
||||
return (
|
||||
@ -328,41 +303,29 @@ const PageSelector = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={parentRef}
|
||||
<List
|
||||
className="py-2"
|
||||
style={{ height: 296, width: '100%', overflow: 'auto' }}
|
||||
height={296}
|
||||
itemCount={currentDataList.length}
|
||||
itemSize={28}
|
||||
width="100%"
|
||||
itemKey={(index, data) => data.dataList[index].page_id}
|
||||
itemData={{
|
||||
dataList: currentDataList,
|
||||
handleToggle,
|
||||
checkedIds: value,
|
||||
disabledCheckedIds: disabledValue,
|
||||
handleCheck,
|
||||
canPreview,
|
||||
handlePreview,
|
||||
listMapWithChildrenAndDescendants,
|
||||
searchValue,
|
||||
previewPageId: currentPreviewPageId,
|
||||
pagesMap,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: virtualizer.getTotalSize(),
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{virtualizer.getVirtualItems().map((virtualRow) => {
|
||||
const current = currentDataList[virtualRow.index]
|
||||
return (
|
||||
<Item
|
||||
key={virtualRow.key}
|
||||
virtualStart={virtualRow.start}
|
||||
virtualSize={virtualRow.size}
|
||||
current={current}
|
||||
onToggle={handleToggle}
|
||||
checkedIds={value}
|
||||
disabledCheckedIds={disabledValue}
|
||||
onCheck={handleCheck}
|
||||
canPreview={canPreview}
|
||||
onPreview={handlePreview}
|
||||
listMapWithChildrenAndDescendants={listMapWithChildrenAndDescendants}
|
||||
searchValue={searchValue}
|
||||
previewPageId={currentPreviewPageId}
|
||||
pagesMap={pagesMap}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{Item}
|
||||
</List>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -71,12 +71,12 @@ const CustomizedPagination: FC<Props> = ({
|
||||
setShowInput(false)
|
||||
}
|
||||
|
||||
const handleInputPressEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
e.preventDefault()
|
||||
handleInputConfirm()
|
||||
}
|
||||
const handleInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
handleInputConfirm()
|
||||
}
|
||||
else if (e.key === 'Escape') {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
setInputValue(current + 1)
|
||||
setShowInput(false)
|
||||
@ -132,6 +132,7 @@ const CustomizedPagination: FC<Props> = ({
|
||||
autoFocus
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onPressEnter={handleInputPressEnter}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
onBlur={handleInputBlur}
|
||||
/>
|
||||
|
||||
@ -11,18 +11,21 @@ import { recursivePushInParentDescendants } from './utils'
|
||||
|
||||
// Note: react-i18next uses global mock from web/vitest.setup.ts
|
||||
|
||||
// Mock @tanstack/react-virtual useVirtualizer hook - renders items directly for testing
|
||||
vi.mock('@tanstack/react-virtual', () => ({
|
||||
useVirtualizer: ({ count, getItemKey }: { count: number, getItemKey?: (index: number) => string }) => ({
|
||||
getVirtualItems: () =>
|
||||
Array.from({ length: count }).map((_, index) => ({
|
||||
index,
|
||||
key: getItemKey ? getItemKey(index) : index,
|
||||
start: index * 28,
|
||||
size: 28,
|
||||
})),
|
||||
getTotalSize: () => count * 28,
|
||||
}),
|
||||
// Mock react-window FixedSizeList - renders items directly for testing
|
||||
vi.mock('react-window', () => ({
|
||||
FixedSizeList: ({ children: ItemComponent, itemCount, itemData, itemKey }: any) => (
|
||||
<div data-testid="virtual-list">
|
||||
{Array.from({ length: itemCount }).map((_, index) => (
|
||||
<ItemComponent
|
||||
key={itemKey?.(index, itemData) || index}
|
||||
index={index}
|
||||
style={{ top: index * 28, left: 0, right: 0, width: '100%', position: 'absolute' }}
|
||||
data={itemData}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
areEqual: (prevProps: any, nextProps: any) => prevProps === nextProps,
|
||||
}))
|
||||
|
||||
// Note: NotionIcon from @/app/components/base/ is NOT mocked - using real component per testing guidelines
|
||||
@ -116,7 +119,7 @@ describe('PageSelector', () => {
|
||||
render(<PageSelector {...props} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('Test Page')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('virtual-list')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render empty state when list is empty', () => {
|
||||
@ -131,7 +134,7 @@ describe('PageSelector', () => {
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('common.dataSource.notion.selector.noSearchResult')).toBeInTheDocument()
|
||||
expect(screen.queryByText('Test Page')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('virtual-list')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render items using FixedSizeList', () => {
|
||||
@ -1163,7 +1166,7 @@ describe('PageSelector', () => {
|
||||
render(<PageSelector {...props} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('Test Page')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('virtual-list')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle special characters in page name', () => {
|
||||
@ -1337,7 +1340,7 @@ describe('PageSelector', () => {
|
||||
render(<PageSelector {...props} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('Test Page')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('virtual-list')).toBeInTheDocument()
|
||||
if (propVariation.canPreview)
|
||||
expect(screen.getByText('common.dataSource.notion.selector.preview')).toBeInTheDocument()
|
||||
else
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common'
|
||||
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||
import { useCallback, useMemo, useRef, useState } from 'react'
|
||||
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'
|
||||
|
||||
@ -45,16 +45,29 @@ const PageSelector = ({
|
||||
currentCredentialId,
|
||||
}: PageSelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const parentRef = useRef<HTMLDivElement>(null)
|
||||
const [expandedIds, setExpandedIds] = useState<Set<string>>(() => new Set())
|
||||
const [dataList, setDataList] = useState<NotionPageItem[]>([])
|
||||
const [currentPreviewPageId, setCurrentPreviewPageId] = useState('')
|
||||
const prevCredentialIdRef = useRef(currentCredentialId)
|
||||
|
||||
// Reset expanded state when credential changes (render-time detection)
|
||||
if (prevCredentialIdRef.current !== currentCredentialId) {
|
||||
prevCredentialIdRef.current = currentCredentialId
|
||||
setExpandedIds(new Set())
|
||||
}
|
||||
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 currentDataList = searchValue ? searchDataList : dataList
|
||||
|
||||
const listMapWithChildrenAndDescendants = useMemo(() => {
|
||||
return list.reduce((prev: NotionPageTreeMap, next: DataSourceNotionPage) => {
|
||||
@ -67,86 +80,39 @@ const PageSelector = ({
|
||||
}, {})
|
||||
}, [list, pagesMap])
|
||||
|
||||
// Pre-build children index for O(1) lookup instead of O(n) filter
|
||||
const childrenByParent = useMemo(() => {
|
||||
const map = new Map<string | null, DataSourceNotionPage[]>()
|
||||
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)
|
||||
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))
|
||||
}
|
||||
return map
|
||||
}, [list, pagesMap])
|
||||
else {
|
||||
current.expand = true
|
||||
|
||||
// Compute visible data list based on expanded state
|
||||
const dataList = useMemo(() => {
|
||||
const result: NotionPageItem[] = []
|
||||
|
||||
const buildVisibleList = (parentId: string | null, depth: number) => {
|
||||
const items = childrenByParent.get(parentId) || []
|
||||
|
||||
for (const item of items) {
|
||||
const isExpanded = expandedIds.has(item.page_id)
|
||||
result.push({
|
||||
...item,
|
||||
expand: isExpanded,
|
||||
depth,
|
||||
})
|
||||
if (isExpanded) {
|
||||
buildVisibleList(item.page_id, depth + 1)
|
||||
}
|
||||
}
|
||||
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])
|
||||
|
||||
buildVisibleList(null, 0)
|
||||
return result
|
||||
}, [childrenByParent, expandedIds])
|
||||
|
||||
const searchDataList = useMemo(() => list.filter((item) => {
|
||||
return item.page_name.includes(searchValue)
|
||||
}).map((item) => {
|
||||
return {
|
||||
...item,
|
||||
expand: false,
|
||||
depth: 0,
|
||||
}
|
||||
}), [list, searchValue])
|
||||
|
||||
const currentDataList = searchValue ? searchDataList : dataList
|
||||
|
||||
const virtualizer = useVirtualizer({
|
||||
count: currentDataList.length,
|
||||
getScrollElement: () => parentRef.current,
|
||||
estimateSize: () => 28,
|
||||
overscan: 5,
|
||||
getItemKey: index => currentDataList[index].page_id,
|
||||
})
|
||||
|
||||
// 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)
|
||||
const descendants = listMapWithChildrenAndDescendants[pageId]?.descendants
|
||||
if (descendants) {
|
||||
for (const descendantId of descendants)
|
||||
next.delete(descendantId)
|
||||
}
|
||||
}
|
||||
else {
|
||||
next.add(pageId)
|
||||
}
|
||||
return next
|
||||
})
|
||||
}, [listMapWithChildrenAndDescendants])
|
||||
|
||||
// Stable callback - uses pageId parameter instead of index
|
||||
const handleCheck = useCallback((pageId: string) => {
|
||||
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)) {
|
||||
@ -154,6 +120,7 @@ const PageSelector = ({
|
||||
for (const item of currentWithChildrenAndDescendants.descendants)
|
||||
copyValue.delete(item)
|
||||
}
|
||||
|
||||
copyValue.delete(pageId)
|
||||
}
|
||||
else {
|
||||
@ -171,15 +138,18 @@ const PageSelector = ({
|
||||
}
|
||||
}
|
||||
|
||||
onSelect(copyValue)
|
||||
}, [checkedIds, isMultipleChoice, listMapWithChildrenAndDescendants, onSelect, searchValue])
|
||||
onSelect(new Set(copyValue))
|
||||
}, [currentDataList, isMultipleChoice, listMapWithChildrenAndDescendants, onSelect, searchValue, checkedIds])
|
||||
|
||||
const handlePreview = useCallback((index: number) => {
|
||||
const current = currentDataList[index]
|
||||
const pageId = current.page_id
|
||||
|
||||
// Stable callback
|
||||
const handlePreview = useCallback((pageId: string) => {
|
||||
setCurrentPreviewPageId(pageId)
|
||||
|
||||
if (onPreview)
|
||||
onPreview(pageId)
|
||||
}, [onPreview])
|
||||
}, [currentDataList, onPreview])
|
||||
|
||||
if (!currentDataList.length) {
|
||||
return (
|
||||
@ -190,42 +160,30 @@ const PageSelector = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={parentRef}
|
||||
<List
|
||||
className="py-2"
|
||||
style={{ height: 296, width: '100%', overflow: 'auto' }}
|
||||
height={296}
|
||||
itemCount={currentDataList.length}
|
||||
itemSize={28}
|
||||
width="100%"
|
||||
itemKey={(index, data) => data.dataList[index].page_id}
|
||||
itemData={{
|
||||
dataList: currentDataList,
|
||||
handleToggle,
|
||||
checkedIds,
|
||||
disabledCheckedIds: disabledValue,
|
||||
handleCheck,
|
||||
canPreview,
|
||||
handlePreview,
|
||||
listMapWithChildrenAndDescendants,
|
||||
searchValue,
|
||||
previewPageId: currentPreviewPageId,
|
||||
pagesMap,
|
||||
isMultipleChoice,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: virtualizer.getTotalSize(),
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{virtualizer.getVirtualItems().map((virtualRow) => {
|
||||
const current = currentDataList[virtualRow.index]
|
||||
return (
|
||||
<Item
|
||||
key={virtualRow.key}
|
||||
virtualStart={virtualRow.start}
|
||||
virtualSize={virtualRow.size}
|
||||
current={current}
|
||||
onToggle={handleToggle}
|
||||
checkedIds={checkedIds}
|
||||
disabledCheckedIds={disabledValue}
|
||||
onCheck={handleCheck}
|
||||
canPreview={canPreview}
|
||||
onPreview={handlePreview}
|
||||
listMapWithChildrenAndDescendants={listMapWithChildrenAndDescendants}
|
||||
searchValue={searchValue}
|
||||
previewPageId={currentPreviewPageId}
|
||||
pagesMap={pagesMap}
|
||||
isMultipleChoice={isMultipleChoice}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{Item}
|
||||
</List>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import type { ListChildComponentProps } from 'react-window'
|
||||
import type { DataSourceNotionPage, DataSourceNotionPageMap } from '@/models/common'
|
||||
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
|
||||
import { memo } from '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'
|
||||
@ -21,40 +23,36 @@ type NotionPageItem = {
|
||||
depth: number
|
||||
} & DataSourceNotionPage
|
||||
|
||||
type ItemProps = {
|
||||
virtualStart: number
|
||||
virtualSize: number
|
||||
current: NotionPageItem
|
||||
onToggle: (pageId: string) => void
|
||||
const Item = ({ index, style, data }: ListChildComponentProps<{
|
||||
dataList: NotionPageItem[]
|
||||
handleToggle: (index: number) => void
|
||||
checkedIds: Set<string>
|
||||
disabledCheckedIds: Set<string>
|
||||
onCheck: (pageId: string) => void
|
||||
handleCheck: (index: number) => void
|
||||
canPreview?: boolean
|
||||
onPreview: (pageId: string) => void
|
||||
handlePreview: (index: number) => void
|
||||
listMapWithChildrenAndDescendants: NotionPageTreeMap
|
||||
searchValue: string
|
||||
previewPageId: string
|
||||
pagesMap: DataSourceNotionPageMap
|
||||
isMultipleChoice?: boolean
|
||||
}
|
||||
|
||||
const Item = ({
|
||||
virtualStart,
|
||||
virtualSize,
|
||||
current,
|
||||
onToggle,
|
||||
checkedIds,
|
||||
disabledCheckedIds,
|
||||
onCheck,
|
||||
canPreview,
|
||||
onPreview,
|
||||
listMapWithChildrenAndDescendants,
|
||||
searchValue,
|
||||
previewPageId,
|
||||
pagesMap,
|
||||
isMultipleChoice,
|
||||
}: ItemProps) => {
|
||||
}>) => {
|
||||
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
|
||||
@ -67,7 +65,7 @@ const Item = ({
|
||||
<div
|
||||
className="mr-1 flex h-5 w-5 shrink-0 items-center justify-center rounded-md hover:bg-components-button-ghost-bg-hover"
|
||||
style={{ marginLeft: current.depth * 8 }}
|
||||
onClick={() => onToggle(current.page_id)}
|
||||
onClick={() => handleToggle(index)}
|
||||
>
|
||||
{
|
||||
current.expand
|
||||
@ -90,15 +88,7 @@ const Item = ({
|
||||
return (
|
||||
<div
|
||||
className={cn('group flex cursor-pointer items-center rounded-md pl-2 pr-[2px] hover:bg-state-base-hover', previewPageId === current.page_id && 'bg-state-base-hover')}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 8,
|
||||
right: 8,
|
||||
width: 'calc(100% - 16px)',
|
||||
height: virtualSize,
|
||||
transform: `translateY(${virtualStart + 8}px)`,
|
||||
}}
|
||||
style={{ ...style, top: style.top as number + 8, left: 8, right: 8, width: 'calc(100% - 16px)' }}
|
||||
>
|
||||
{isMultipleChoice
|
||||
? (
|
||||
@ -106,7 +96,9 @@ const Item = ({
|
||||
className="mr-2 shrink-0"
|
||||
checked={checkedIds.has(current.page_id)}
|
||||
disabled={disabled}
|
||||
onCheck={() => onCheck(current.page_id)}
|
||||
onCheck={() => {
|
||||
handleCheck(index)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
@ -114,7 +106,9 @@ const Item = ({
|
||||
className="mr-2 shrink-0"
|
||||
isChecked={checkedIds.has(current.page_id)}
|
||||
disabled={disabled}
|
||||
onCheck={() => onCheck(current.page_id)}
|
||||
onCheck={() => {
|
||||
handleCheck(index)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!searchValue && renderArrow()}
|
||||
@ -135,7 +129,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={() => onPreview(current.page_id)}
|
||||
onClick={() => handlePreview(index)}
|
||||
>
|
||||
{t('dataSource.notion.selector.preview', { ns: 'common' })}
|
||||
</div>
|
||||
@ -155,4 +149,4 @@ const Item = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(Item)
|
||||
export default React.memo(Item, areEqual)
|
||||
|
||||
@ -319,22 +319,17 @@ const GotoAnything: FC<Props> = ({
|
||||
if (!e.target.value.startsWith('@') && !e.target.value.startsWith('/'))
|
||||
clearSelection()
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
const query = searchQuery.trim()
|
||||
// Check if it's a complete slash command
|
||||
if (query.startsWith('/')) {
|
||||
const commandName = query.substring(1).split(' ')[0]
|
||||
const handler = slashCommandRegistry.findCommand(commandName)
|
||||
|
||||
// If it's a direct mode command, execute immediately
|
||||
const isAvailable = handler?.isAvailable?.() ?? true
|
||||
if (handler?.mode === 'direct' && handler.execute && isAvailable) {
|
||||
e.preventDefault()
|
||||
handler.execute()
|
||||
setShow(false)
|
||||
setSearchQuery('')
|
||||
}
|
||||
onPressEnter={(e) => {
|
||||
const query = searchQuery.trim()
|
||||
if (query.startsWith('/')) {
|
||||
const commandName = query.substring(1).split(' ')[0]
|
||||
const handler = slashCommandRegistry.findCommand(commandName)
|
||||
const isAvailable = handler?.isAvailable?.() ?? true
|
||||
if (handler?.mode === 'direct' && handler.execute && isAvailable) {
|
||||
e.preventDefault()
|
||||
handler.execute()
|
||||
setShow(false)
|
||||
setSearchQuery('')
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@ -139,10 +139,7 @@ export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegis
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter')
|
||||
handleEmailPasswordLogin()
|
||||
}}
|
||||
onPressEnter={() => handleEmailPasswordLogin()}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
autoComplete="current-password"
|
||||
placeholder={t('passwordPlaceholder', { ns: 'login' }) || ''}
|
||||
|
||||
@ -103,12 +103,10 @@ export default function InviteSettingsPage() {
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
placeholder={t('namePlaceholder', { ns: 'login' }) || ''}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handleActivate()
|
||||
}
|
||||
onPressEnter={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handleActivate()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -182,11 +182,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/annotation/add-annotation-modal/edit-item/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/annotation/batch-add-annotation-modal/csv-downloader.spec.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@ -196,9 +191,6 @@
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
@ -206,9 +198,6 @@
|
||||
"app/components/app/annotation/edit-annotation-modal/edit-item/index.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/annotation/edit-annotation-modal/index.spec.tsx": {
|
||||
@ -265,11 +254,6 @@
|
||||
"count": 6
|
||||
}
|
||||
},
|
||||
"app/components/app/configuration/base/var-highlight/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/configuration/config-prompt/advanced-prompt-input.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@ -440,11 +424,6 @@
|
||||
"count": 6
|
||||
}
|
||||
},
|
||||
"app/components/app/configuration/debug/debug-with-multiple-model/context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/configuration/debug/debug-with-multiple-model/index.spec.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 5
|
||||
@ -527,11 +506,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/create-app-dialog/app-list/sidebar.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/create-app-modal/index.spec.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 7
|
||||
@ -548,14 +522,6 @@
|
||||
"app/components/app/create-from-dsl-modal/index.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 2
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/log/filter.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/log/index.tsx": {
|
||||
@ -624,9 +590,6 @@
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 3
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
}
|
||||
@ -636,11 +599,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/app/workflow-log/filter.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/app/workflow-log/list.spec.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@ -692,11 +650,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/action-button/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/agent-log-modal/detail.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@ -725,11 +678,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/amplitude/AmplitudeProvider.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/amplitude/utils.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@ -778,9 +726,6 @@
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"react/no-nested-component-definitions": {
|
||||
"count": 1
|
||||
}
|
||||
@ -790,21 +735,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/button/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/button/sync-button.stories.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/carousel/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat-with-history/chat-wrapper.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 7
|
||||
@ -892,11 +827,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat/context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/chat/chat/hooks.ts": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 2
|
||||
@ -1000,18 +930,10 @@
|
||||
}
|
||||
},
|
||||
"app/components/base/error-boundary/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/features/context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/features/new-feature-panel/annotation-reply/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
@ -1077,11 +999,6 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/base/file-uploader/store.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"app/components/base/file-uploader/utils.spec.ts": {
|
||||
"test/no-identical-title": {
|
||||
"count": 1
|
||||
@ -1178,11 +1095,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/ga/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/icons/utils.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
@ -1234,16 +1146,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/input/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/logo/dify-logo.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/markdown-blocks/audio-block.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 5
|
||||
@ -1394,11 +1296,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/node-status/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/notion-connector/index.stories.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
@ -1409,6 +1306,11 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/notion-page-selector/page-selector/index.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/pagination/index.tsx": {
|
||||
"unicorn/prefer-number-properties": {
|
||||
"count": 1
|
||||
@ -1425,9 +1327,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/base/portal-to-follow-elem/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@ -1609,16 +1508,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/textarea/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/base/toast/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/base/video-gallery/VideoPlayer.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
@ -1662,16 +1551,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/billing/pricing/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/billing/pricing/plan-switcher/plan-range-switcher.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/billing/pricing/plans/cloud-plan-item/index.spec.tsx": {
|
||||
"test/prefer-hooks-in-order": {
|
||||
"count": 1
|
||||
@ -1722,11 +1601,6 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/datasets/common/image-uploader/store.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"app/components/datasets/common/image-uploader/utils.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@ -1737,16 +1611,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/common/retrieval-method-info/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/create/file-preview/index.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
@ -1777,11 +1641,6 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/datasets/create/step-two/preview-item/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/create/stop-embedding-modal/index.spec.tsx": {
|
||||
"test/prefer-hooks-in-order": {
|
||||
"count": 1
|
||||
@ -1839,9 +1698,6 @@
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 2
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
@ -1868,7 +1724,12 @@
|
||||
},
|
||||
"app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/index.spec.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
"count": 5
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/create-from-pipeline/data-source/online-documents/page-selector/index.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/create-from-pipeline/data-source/online-drive/connect/index.spec.tsx": {
|
||||
@ -1901,11 +1762,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/create-from-pipeline/data-source/store/provider.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/create-from-pipeline/data-source/store/slices/online-drive.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
@ -1951,11 +1807,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/detail/completed/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/detail/completed/new-child-segment.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@ -1979,11 +1830,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/detail/segment-add/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 6
|
||||
@ -2124,11 +1970,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/explore/try-app/tab.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/goto-anything/actions/commands/command-bus.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@ -2140,9 +1981,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/goto-anything/actions/commands/slash.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@ -2160,9 +1998,6 @@
|
||||
"app/components/goto-anything/context.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 4
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/goto-anything/index.spec.tsx": {
|
||||
@ -2359,11 +2194,6 @@
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"app/components/plugins/install-plugin/install-bundle/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/plugins/install-plugin/install-bundle/item/github-item.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
@ -2440,11 +2270,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-auth/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-auth/plugin-auth-in-agent.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@ -2529,9 +2354,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@ -2572,9 +2394,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/plugins/plugin-page/context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@ -2939,11 +2758,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/block-selector/constants.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/block-selector/featured-tools.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 2
|
||||
@ -2965,11 +2779,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/block-selector/index-bar.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/block-selector/market-place-plugin/action.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
@ -3008,26 +2817,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/block-selector/view-type-select.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/candidate-node-main.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/workflow/context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/datasets-detail-store/provider.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/header/run-mode.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
@ -3036,21 +2830,11 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/header/test-run-menu.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/header/view-workflow-history.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/hooks-store/provider.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/hooks-store/store.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 6
|
||||
@ -3171,18 +2955,10 @@
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 6
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/entry-node-container.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/error-handle/default-value.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@ -3208,16 +2984,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/layout/index.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 7
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/mcp-tool-availability.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/memory-config.tsx": {
|
||||
"unicorn/prefer-number-properties": {
|
||||
"count": 1
|
||||
@ -3301,9 +3067,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/_base/components/workflow-panel/tab.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@ -3357,9 +3120,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/agent/panel.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
@ -3535,9 +3295,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/human-input/components/variable-in-markdown.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 8
|
||||
}
|
||||
@ -3667,11 +3424,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/auto-width-input.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 1
|
||||
@ -3969,11 +3721,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/note-node/note-editor/toolbar/color-picker.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"app/components/workflow/note-node/note-editor/utils.ts": {
|
||||
"regexp/no-useless-quantifier": {
|
||||
"count": 1
|
||||
@ -4010,9 +3757,6 @@
|
||||
}
|
||||
},
|
||||
"app/components/workflow/panel/chat-variable-panel/components/object-value-item.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 5
|
||||
},
|
||||
@ -4299,11 +4043,6 @@
|
||||
"count": 8
|
||||
}
|
||||
},
|
||||
"app/components/workflow/workflow-history-store.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"app/components/workflow/workflow-preview/components/nodes/constants.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@ -4365,79 +4104,30 @@
|
||||
}
|
||||
},
|
||||
"context/app-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"context/datasets-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"context/event-emitter.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"context/external-api-panel-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"context/external-knowledge-api-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"context/global-public-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"context/hooks/use-trigger-events-limit-modal.ts": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"context/mitt-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"context/modal-context.test.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"context/modal-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 2
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 5
|
||||
}
|
||||
},
|
||||
"context/provider-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"context/web-app-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"context/workspace-context.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"hooks/use-async-window-open.spec.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 6
|
||||
@ -4474,9 +4164,6 @@
|
||||
"hooks/use-pay.tsx": {
|
||||
"react-hooks-extra/no-direct-set-state-in-use-effect": {
|
||||
"count": 4
|
||||
},
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"i18n-config/README.md": {
|
||||
|
||||
@ -84,7 +84,6 @@
|
||||
"@tailwindcss/typography": "0.5.19",
|
||||
"@tanstack/react-form": "1.23.7",
|
||||
"@tanstack/react-query": "5.90.5",
|
||||
"@tanstack/react-virtual": "3.13.18",
|
||||
"abcjs": "6.5.2",
|
||||
"ahooks": "3.9.5",
|
||||
"class-variance-authority": "0.7.1",
|
||||
@ -143,6 +142,7 @@
|
||||
"react-sortablejs": "6.1.4",
|
||||
"react-syntax-highlighter": "15.6.6",
|
||||
"react-textarea-autosize": "8.5.9",
|
||||
"react-window": "1.8.11",
|
||||
"reactflow": "11.11.4",
|
||||
"rehype-katex": "7.0.1",
|
||||
"rehype-raw": "7.0.0",
|
||||
@ -199,6 +199,7 @@
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/react-slider": "1.3.6",
|
||||
"@types/react-syntax-highlighter": "15.5.13",
|
||||
"@types/react-window": "1.8.8",
|
||||
"@types/semver": "7.7.1",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@types/uuid": "10.0.0",
|
||||
|
||||
1905
web/pnpm-lock.yaml
generated
1905
web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user