From e7c0f5fab389ac518660a430d2e2cb544a77294f Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 22 Nov 2025 16:47:55 +0100 Subject: [PATCH] fix(components): fix all type errors in FilterAutocomplete.ts - Add type filters for label and project arrays to remove undefined values - Add @ts-expect-error for projectId parameter in getAll call - Add type assertion for userService.getAll empty object parameter - Add default value for prefix in destructuring - Add null check for match.index - Add type assertions for union type property access (username/title) - Add null check for autocompleteContext - Fix Selection.near call with @ts-expect-error comment - Add optional chaining for array access that could be undefined --- .../input/filter/FilterAutocomplete.ts | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/input/filter/FilterAutocomplete.ts b/frontend/src/components/input/filter/FilterAutocomplete.ts index bdaf60500..38ab076c1 100644 --- a/frontend/src/components/input/filter/FilterAutocomplete.ts +++ b/frontend/src/components/input/filter/FilterAutocomplete.ts @@ -167,7 +167,7 @@ export default Extension.create({ const fetchSuggestions = async (autocompleteContext: AutocompleteContext, fieldType: AutocompleteField): Promise => { try { if (fieldType === 'labels') { - return labelStore.filterLabelsByQuery([], autocompleteContext.search) + return labelStore.filterLabelsByQuery([], autocompleteContext.search).filter((label): label is ILabel => label !== undefined) as SuggestionItem[] } if (fieldType === 'assignees') { @@ -181,9 +181,10 @@ export default Extension.create({ let assigneeSuggestions: SuggestionItem[] = [] try { if (this.options.projectId) { - assigneeSuggestions = await projectUserService.getAll({projectId: this.options.projectId}, {s: autocompleteContext.search}) + // @ts-expect-error - projectId is used for URL replacement but not part of IAbstract + assigneeSuggestions = await projectUserService.getAll({projectId: this.options.projectId}, {s: autocompleteContext.search}) as SuggestionItem[] } else { - assigneeSuggestions = await userService.getAll({}, {s: autocompleteContext.search}) + assigneeSuggestions = await userService.getAll({} as IUser, {s: autocompleteContext.search}) as SuggestionItem[] } // For assignees, show suggestions even with empty search, but limit if we have many if (autocompleteContext.search === '' && assigneeSuggestions.length > 10) { @@ -197,9 +198,9 @@ export default Extension.create({ }, 300) }) } - + if (fieldType === 'projects' && !this.options.projectId) { - return projectStore.searchProject(autocompleteContext.search) + return projectStore.searchProject(autocompleteContext.search).filter((project): project is IProject => project !== undefined) as SuggestionItem[] } } catch (error) { console.error('Error fetching suggestions:', error) @@ -257,14 +258,14 @@ export default Extension.create({ const pattern = new RegExp(`(${field}\\s*${FILTER_OPERATORS_REGEX}\\s*)(["']?)([^"'&|()]*)?$`, 'ig') const match = pattern.exec(textUpToCursor) - if (match) { - const [, prefix, , , keyword = ''] = match + if (match && match.index !== undefined) { + const [, prefix = '', , , keyword = ''] = match let search = keyword.trim() const operator = match[0].match(new RegExp(FILTER_OPERATORS_REGEX))?.[0] || '' if (operator === 'in' || operator === '?=') { const keywords = keyword.split(',') - search = keywords[keywords.length - 1].trim() + search = keywords[keywords.length - 1]?.trim() ?? '' } // Check if this expression is complete @@ -327,23 +328,28 @@ export default Extension.create({ items, command: (item: AutocompleteItem) => { // Handle selection - const newValue = item.fieldType === 'assignees' ? item.item.username : item.item.title + const newValue = item.fieldType === 'assignees' + ? (item.item as IUser).username + : (item.item as IProject | ILabel).title const {from} = view.state.selection const context = autocompleteContext + if (!context) { + return + } const operator = context.operator - + let insertValue: string = newValue ?? '' const replaceFrom = Math.max(0, from - context.search.length) const replaceTo = from - + // Handle multi-value operators if (isMultiValueOperator(operator) && context.keyword.includes(',')) { // For multi-value fields, we need to replace only the current search term const keywords = context.keyword.split(',') const currentKeywordIndex = keywords.length - 1 - + // If we're not adding the first item, add comma prefix - if (currentKeywordIndex > 0 && keywords[currentKeywordIndex].trim() === context.search.trim()) { + if (currentKeywordIndex > 0 && keywords[currentKeywordIndex]?.trim() === context.search.trim()) { // We're replacing the last incomplete keyword insertValue = newValue ?? '' } else { @@ -351,7 +357,7 @@ export default Extension.create({ insertValue = ',' + newValue } } - + const tr = view.state.tr.replaceWith( replaceFrom, replaceTo, @@ -359,6 +365,7 @@ export default Extension.create({ ) // Position cursor after the inserted text const newPos = replaceFrom + insertValue.length + // @ts-expect-error - Selection.near is a static method but TypeScript doesn't recognize it on constructor tr.setSelection(view.state.selection.constructor.near(tr.doc.resolve(newPos))) view.dispatch(tr)