fix reading time for blog posts (#38600)

* fix reading time for blog posts

* fix prettier

* ran `pnpm run format`
This commit is contained in:
Raúl Barroso
2025-09-10 16:43:31 +02:00
committed by GitHub
parent 7676cf4468
commit 4590b36a03
9 changed files with 213 additions and 179 deletions

View File

@@ -74,7 +74,7 @@ describe('FilterBar', () => {
const handleFilterChange = vi.fn((filters) => {
currentFilters = filters
})
const { rerender } = render(
<FilterBar
filterProperties={mockFilterProperties}
@@ -100,7 +100,7 @@ describe('FilterBar', () => {
await waitFor(() => {
expect(handleFilterChange).toHaveBeenCalled()
})
// Re-render with updated filters
rerender(
<FilterBar
@@ -124,7 +124,7 @@ describe('FilterBar', () => {
const handleFilterChange = vi.fn((filters) => {
currentFilters = filters
})
const { rerender } = render(
<FilterBar
filterProperties={mockFilterProperties}
@@ -142,7 +142,7 @@ describe('FilterBar', () => {
await waitFor(() => {
expect(handleFilterChange).toHaveBeenCalled()
})
rerender(
<FilterBar
filterProperties={mockFilterProperties}
@@ -153,10 +153,9 @@ describe('FilterBar', () => {
/>
)
const valueInput = await waitFor(
() => screen.getByLabelText('Value for Status'),
{ timeout: 3000 }
)
const valueInput = await waitFor(() => screen.getByLabelText('Value for Status'), {
timeout: 3000,
})
valueInput.focus()
// Popover should show value options
@@ -169,7 +168,7 @@ describe('FilterBar', () => {
await waitFor(() => {
expect(handleFilterChange).toHaveBeenCalledTimes(2) // Once for property, once for value
})
rerender(
<FilterBar
filterProperties={mockFilterProperties}
@@ -215,7 +214,7 @@ describe('FilterBar', () => {
const handleFilterChange = vi.fn((filters) => {
currentFilters = filters
})
const { rerender } = render(
<FilterBar
filterProperties={customProps}
@@ -233,7 +232,7 @@ describe('FilterBar', () => {
await waitFor(() => {
expect(handleFilterChange).toHaveBeenCalled()
})
rerender(
<FilterBar
filterProperties={customProps}
@@ -245,10 +244,9 @@ describe('FilterBar', () => {
)
// Wait for FilterCondition to be created
const valueInput = await waitFor(
() => screen.getByLabelText('Value for Tag'),
{ timeout: 3000 }
)
const valueInput = await waitFor(() => screen.getByLabelText('Value for Tag'), {
timeout: 3000,
})
// Focus the value input to show the popover
await user.click(valueInput)
@@ -261,7 +259,7 @@ describe('FilterBar', () => {
await waitFor(() => {
expect(handleFilterChange).toHaveBeenCalledTimes(2) // Once for property, once for value
})
rerender(
<FilterBar
filterProperties={customProps}

View File

@@ -90,4 +90,4 @@ describe('FilterBar Hooks', () => {
expect(result.current.loadingOptions).toEqual({})
})
})
})
})

View File

@@ -96,9 +96,7 @@ function processConditions(conditions: any[], filterProperties: FilterProperty[]
conditions: processConditions(condition.conditions, filterProperties),
}
} else {
const matchedProperty = filterProperties.find(
(prop) => prop.name === condition.propertyName
)
const matchedProperty = filterProperties.find((prop) => prop.name === condition.propertyName)
if (!matchedProperty) {
throw new Error(`Invalid property: ${condition.propertyName}`)
}
@@ -109,4 +107,4 @@ function processConditions(conditions: any[], filterProperties: FilterProperty[]
}
}
})
}
}

View File

@@ -43,7 +43,7 @@ export function useCommandMenu({
if (activeInput?.type === 'group') {
items.push(...getPropertyItems(filterProperties, inputValue))
if (supportsOperators) {
items.push({
value: 'group',
@@ -59,7 +59,16 @@ export function useCommandMenu({
})
}
} else if (activeInput?.type === 'value') {
items.push(...getValueItems(activeInput, activeFilters, filterProperties, propertyOptionsCache, loadingOptions, inputValue))
items.push(
...getValueItems(
activeInput,
activeFilters,
filterProperties,
propertyOptionsCache,
loadingOptions,
inputValue
)
)
}
return items
@@ -86,13 +95,17 @@ function getOperatorItems(
const property = filterProperties.find((p) => p.name === condition?.propertyName)
const operatorValue = condition?.operator?.toUpperCase() || ''
const availableOperators = property?.operators || ['=']
return availableOperators
.filter((op) => op.toUpperCase().includes(operatorValue))
.map((op) => ({ value: op, label: op }))
}
function getInputValue(activeInput: ActiveInput, freeformText: string, activeFilters: FilterGroup): string {
function getInputValue(
activeInput: ActiveInput,
freeformText: string,
activeFilters: FilterGroup
): string {
return activeInput?.type === 'group'
? freeformText
: activeInput?.type === 'value'
@@ -147,7 +160,7 @@ function getValueItems(
function getArrayOptionItems(options: any[], inputValue: string): CommandItem[] {
const items: CommandItem[] = []
for (const option of options) {
if (typeof option === 'string') {
if (option.toLowerCase().includes(inputValue.toLowerCase())) {
@@ -174,7 +187,7 @@ function getArrayOptionItems(options: any[], inputValue: string): CommandItem[]
}
}
}
return items
}
@@ -191,4 +204,4 @@ function getCachedOptionItems(options: any[]): CommandItem[] {
label: option.label,
}
})
}
}

View File

@@ -90,135 +90,159 @@ export function useKeyboardNavigation({
[activeInput, activeFilters, removeFilterByPath, removeGroupByPath, setActiveInput]
)
const findPreviousCondition = useCallback((currentPath: number[]): number[] | null => {
const [groupPath, conditionIndex] = [currentPath.slice(0, -1), currentPath[currentPath.length - 1]]
// Try previous condition in same group
if (conditionIndex > 0) {
const prevPath = [...groupPath, conditionIndex - 1]
const findPreviousCondition = useCallback(
(currentPath: number[]): number[] | null => {
const [groupPath, conditionIndex] = [
currentPath.slice(0, -1),
currentPath[currentPath.length - 1],
]
// Try previous condition in same group
if (conditionIndex > 0) {
const prevPath = [...groupPath, conditionIndex - 1]
const group = findGroupByPath(activeFilters, groupPath)
const prevCondition = group?.conditions[conditionIndex - 1]
// If previous is a condition (not a group), return its path
if (prevCondition && !('logicalOperator' in prevCondition)) {
return prevPath
}
// If previous is a group, find its last condition recursively
if (prevCondition && 'logicalOperator' in prevCondition) {
return findLastConditionInGroup(prevPath)
}
}
// No previous condition in this group, go up to parent
if (groupPath.length > 0) {
return findPreviousCondition(groupPath)
}
return null
},
[activeFilters]
)
const findNextCondition = useCallback(
(currentPath: number[]): number[] | null => {
const [groupPath, conditionIndex] = [
currentPath.slice(0, -1),
currentPath[currentPath.length - 1],
]
const group = findGroupByPath(activeFilters, groupPath)
const prevCondition = group?.conditions[conditionIndex - 1]
// If previous is a condition (not a group), return its path
if (prevCondition && !('logicalOperator' in prevCondition)) {
return prevPath
// Try next condition in same group
if (group && conditionIndex < group.conditions.length - 1) {
const nextPath = [...groupPath, conditionIndex + 1]
const nextCondition = group.conditions[conditionIndex + 1]
// If next is a condition, return its path
if (!('logicalOperator' in nextCondition)) {
return nextPath
}
// If next is a group, find its first condition recursively
return findFirstConditionInGroup(nextPath)
}
// If previous is a group, find its last condition recursively
if (prevCondition && 'logicalOperator' in prevCondition) {
return findLastConditionInGroup(prevPath)
// No next condition in this group, go up to parent and find next
if (groupPath.length > 0) {
return findNextCondition(groupPath)
}
}
// No previous condition in this group, go up to parent
if (groupPath.length > 0) {
return findPreviousCondition(groupPath)
}
return null
}, [activeFilters])
const findNextCondition = useCallback((currentPath: number[]): number[] | null => {
const [groupPath, conditionIndex] = [currentPath.slice(0, -1), currentPath[currentPath.length - 1]]
const group = findGroupByPath(activeFilters, groupPath)
// Try next condition in same group
if (group && conditionIndex < group.conditions.length - 1) {
const nextPath = [...groupPath, conditionIndex + 1]
const nextCondition = group.conditions[conditionIndex + 1]
// If next is a condition, return its path
if (!('logicalOperator' in nextCondition)) {
return nextPath
return null
},
[activeFilters]
)
const findFirstConditionInGroup = useCallback(
(groupPath: number[]): number[] | null => {
const group = findGroupByPath(activeFilters, groupPath)
if (!group || group.conditions.length === 0) return null
const firstCondition = group.conditions[0]
if (!('logicalOperator' in firstCondition)) {
return [...groupPath, 0]
}
// If next is a group, find its first condition recursively
return findFirstConditionInGroup(nextPath)
}
// No next condition in this group, go up to parent and find next
if (groupPath.length > 0) {
return findNextCondition(groupPath)
}
return null
}, [activeFilters])
// First item is a group, recurse
return findFirstConditionInGroup([...groupPath, 0])
},
[activeFilters]
)
const findFirstConditionInGroup = useCallback((groupPath: number[]): number[] | null => {
const group = findGroupByPath(activeFilters, groupPath)
if (!group || group.conditions.length === 0) return null
const firstCondition = group.conditions[0]
if (!('logicalOperator' in firstCondition)) {
return [...groupPath, 0]
}
// First item is a group, recurse
return findFirstConditionInGroup([...groupPath, 0])
}, [activeFilters])
const findLastConditionInGroup = useCallback(
(groupPath: number[]): number[] | null => {
const group = findGroupByPath(activeFilters, groupPath)
if (!group || group.conditions.length === 0) return null
const findLastConditionInGroup = useCallback((groupPath: number[]): number[] | null => {
const group = findGroupByPath(activeFilters, groupPath)
if (!group || group.conditions.length === 0) return null
const lastCondition = group.conditions[group.conditions.length - 1]
const lastIndex = group.conditions.length - 1
if (!('logicalOperator' in lastCondition)) {
return [...groupPath, lastIndex]
}
// Last item is a group, recurse
return findLastConditionInGroup([...groupPath, lastIndex])
}, [activeFilters])
const lastCondition = group.conditions[group.conditions.length - 1]
const lastIndex = group.conditions.length - 1
if (!('logicalOperator' in lastCondition)) {
return [...groupPath, lastIndex]
}
// Last item is a group, recurse
return findLastConditionInGroup([...groupPath, lastIndex])
},
[activeFilters]
)
const findPreviousConditionFromGroup = useCallback((groupPath: number[]): number[] | null => {
// If this group has conditions, find the last one
const group = findGroupByPath(activeFilters, groupPath)
if (group && group.conditions.length > 0) {
return findLastConditionInGroup(groupPath)
}
// No conditions in this group, find previous sibling or parent
if (groupPath.length > 0) {
const parentPath = groupPath.slice(0, -1)
const groupIndex = groupPath[groupPath.length - 1]
if (groupIndex > 0) {
// Find last condition in previous sibling
const prevSiblingPath = [...parentPath, groupIndex - 1]
const parentGroup = findGroupByPath(activeFilters, parentPath)
const prevSibling = parentGroup?.conditions[groupIndex - 1]
if (prevSibling) {
if ('logicalOperator' in prevSibling) {
return findLastConditionInGroup(prevSiblingPath)
} else {
return prevSiblingPath
const findPreviousConditionFromGroup = useCallback(
(groupPath: number[]): number[] | null => {
// If this group has conditions, find the last one
const group = findGroupByPath(activeFilters, groupPath)
if (group && group.conditions.length > 0) {
return findLastConditionInGroup(groupPath)
}
// No conditions in this group, find previous sibling or parent
if (groupPath.length > 0) {
const parentPath = groupPath.slice(0, -1)
const groupIndex = groupPath[groupPath.length - 1]
if (groupIndex > 0) {
// Find last condition in previous sibling
const prevSiblingPath = [...parentPath, groupIndex - 1]
const parentGroup = findGroupByPath(activeFilters, parentPath)
const prevSibling = parentGroup?.conditions[groupIndex - 1]
if (prevSibling) {
if ('logicalOperator' in prevSibling) {
return findLastConditionInGroup(prevSiblingPath)
} else {
return prevSiblingPath
}
}
}
// Look at parent group
return findPreviousConditionFromGroup(parentPath)
}
// Look at parent group
return findPreviousConditionFromGroup(parentPath)
}
return null
}, [activeFilters, findLastConditionInGroup])
const findNextConditionFromGroup = useCallback((groupPath: number[]): number[] | null => {
// Find next sibling or dive into nested groups
if (groupPath.length > 0) {
const parentPath = groupPath.slice(0, -1)
const groupIndex = groupPath[groupPath.length - 1]
const parentGroup = findGroupByPath(activeFilters, parentPath)
if (parentGroup && groupIndex < parentGroup.conditions.length - 1) {
// Find first condition in next sibling
const nextSiblingPath = [...parentPath, groupIndex + 1]
const nextSibling = parentGroup.conditions[groupIndex + 1]
if ('logicalOperator' in nextSibling) {
return findFirstConditionInGroup(nextSiblingPath)
} else {
return nextSiblingPath
return null
},
[activeFilters, findLastConditionInGroup]
)
const findNextConditionFromGroup = useCallback(
(groupPath: number[]): number[] | null => {
// Find next sibling or dive into nested groups
if (groupPath.length > 0) {
const parentPath = groupPath.slice(0, -1)
const groupIndex = groupPath[groupPath.length - 1]
const parentGroup = findGroupByPath(activeFilters, parentPath)
if (parentGroup && groupIndex < parentGroup.conditions.length - 1) {
// Find first condition in next sibling
const nextSiblingPath = [...parentPath, groupIndex + 1]
const nextSibling = parentGroup.conditions[groupIndex + 1]
if ('logicalOperator' in nextSibling) {
return findFirstConditionInGroup(nextSiblingPath)
} else {
return nextSiblingPath
}
}
// Look at parent group
return findNextConditionFromGroup(parentPath)
}
// Look at parent group
return findNextConditionFromGroup(parentPath)
}
return null
}, [activeFilters, findFirstConditionInGroup])
return null
},
[activeFilters, findFirstConditionInGroup]
)
const handleArrowLeft = useCallback(
(e: KeyboardEvent<HTMLInputElement>) => {
@@ -252,7 +276,7 @@ export function useKeyboardNavigation({
const groupPath = activeInput.path.slice(0, -1)
const conditionIndex = activeInput.path[activeInput.path.length - 1]
const group = findGroupByPath(activeFilters, groupPath)
if (group && conditionIndex < group.conditions.length - 1) {
// There's a next condition, navigate to it
const nextCondition = group.conditions[conditionIndex + 1]
@@ -279,10 +303,17 @@ export function useKeyboardNavigation({
}
}
},
[activeInput, activeFilters, findGroupByPath, findFirstConditionInGroup, findNextConditionFromGroup, setActiveInput]
[
activeInput,
activeFilters,
findGroupByPath,
findFirstConditionInGroup,
findNextConditionFromGroup,
setActiveInput,
]
)
return {
handleKeyDown,
}
}
}

View File

@@ -201,7 +201,9 @@ describe('FilterBar Utils', () => {
const filterOption = { value: 'test', label: 'Test' }
expect(isFilterOptionObject(filterOption)).toBe(true)
expect(isFilterOptionObject('string')).toBe(false)
expect(isFilterOptionObject({ component: () => React.createElement('div', {}, 'test') })).toBe(false)
expect(
isFilterOptionObject({ component: () => React.createElement('div', {}, 'test') })
).toBe(false)
})
it('identifies async functions', () => {
@@ -224,4 +226,4 @@ describe('FilterBar Utils', () => {
expect(isSyncOptionsFunction(array)).toBe(false)
})
})
})
})

View File

@@ -1,12 +1,12 @@
import {
FilterGroup,
FilterCondition,
import {
FilterGroup,
FilterCondition,
FilterProperty,
CustomOptionObject,
FilterOptionObject,
AsyncOptionsFunction,
SyncOptionsFunction,
isGroup
isGroup,
} from './types'
export function findGroupByPath(group: FilterGroup, path: number[]): FilterGroup | null {
@@ -60,12 +60,16 @@ export function isAsyncOptionsFunction(
if (typeof options !== 'function') return false
// More reliable async function detection
const fnString = options.toString()
return options.constructor.name === 'AsyncFunction' ||
fnString.startsWith('async ') ||
fnString.includes('async function')
return (
options.constructor.name === 'AsyncFunction' ||
fnString.startsWith('async ') ||
fnString.includes('async function')
)
}
export function isSyncOptionsFunction(options: FilterProperty['options']): options is SyncOptionsFunction {
export function isSyncOptionsFunction(
options: FilterProperty['options']
): options is SyncOptionsFunction {
if (!options || Array.isArray(options) || isCustomOptionObject(options)) return false
return typeof options === 'function'
}
@@ -113,8 +117,8 @@ export function removeFromGroup(group: FilterGroup, path: number[]): FilterGroup
}
export function addFilterToGroup(
group: FilterGroup,
path: number[],
group: FilterGroup,
path: number[],
property: FilterProperty
): FilterGroup {
if (path.length === 0) {
@@ -207,10 +211,7 @@ export function updateNestedOperator(
}
}
export function updateNestedLogicalOperator(
group: FilterGroup,
path: number[]
): FilterGroup {
export function updateNestedLogicalOperator(group: FilterGroup, path: number[]): FilterGroup {
if (path.length === 0) {
return {
...group,
@@ -244,9 +245,7 @@ export function updateGroupAtPath(
return {
...group,
conditions: group.conditions.map((condition, index) =>
index === current
? updateGroupAtPath(condition as FilterGroup, rest, newGroup)
: condition
index === current ? updateGroupAtPath(condition as FilterGroup, rest, newGroup) : condition
),
}
}
}