diff --git a/package.json b/package.json index 69b28e2efb65..a7924af60b08 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "react-compiler-healthcheck": "react-compiler-healthcheck --verbose", "react-compiler-healthcheck-test": "react-compiler-healthcheck --verbose &> react-compiler-output.txt", "generate-search-parser": "peggy --format es -o src/libs/SearchParser/searchParser.js src/libs/SearchParser/searchParser.peggy src/libs/SearchParser/baseRules.peggy", - "generate-autocomplete-parser": "peggy --format es -o src/libs/SearchParser/autocompleteParser.js src/libs/SearchParser/autocompleteParser.peggy src/libs/SearchParser/baseRules.peggy", + "generate-autocomplete-parser": "peggy --format es -o src/libs/SearchParser/autocompleteParser.js src/libs/SearchParser/autocompleteParser.peggy src/libs/SearchParser/baseRules.peggy && ./scripts/parser-workletization.sh src/libs/SearchParser/autocompleteParser.js", "web:prod": "http-server ./dist --cors" }, "dependencies": { diff --git a/scripts/parser-workletization.sh b/scripts/parser-workletization.sh new file mode 100755 index 000000000000..ab048e407de3 --- /dev/null +++ b/scripts/parser-workletization.sh @@ -0,0 +1,22 @@ +#!/bin/bash +### +# This script modifies the autocompleteParser.js file to be compatible with worklets. +# autocompleteParser.js is generated by PeggyJS and uses some parts of syntax not supported by worklets. +# This script runs each time the parser is generated by the `generate-autocomplete-parser` command. +### + +filePath=$1 + +if [ ! -f "$filePath" ]; then + echo "$filePath does not exist." + exit 1 +fi +# shellcheck disable=SC2016 +if awk 'BEGIN { print "\47worklet\47\n\nclass peg\$SyntaxError{}" } 1' "$filePath" | sed 's/function peg\$SyntaxError/function temporary/g' | sed 's/peg$subclass(peg$SyntaxError, Error);//g' > tmp.txt; then + mv tmp.txt "$filePath" + echo "Successfully updated $filePath" +else + echo "An error occurred while modifying the file." + rm -f tmp.txt + exit 1 +fi diff --git a/src/components/Search/SearchRouter/SearchRouterInput.tsx b/src/components/Search/SearchAutocompleteInput.tsx similarity index 75% rename from src/components/Search/SearchRouter/SearchRouterInput.tsx rename to src/components/Search/SearchAutocompleteInput.tsx index 1c4d8204f8b4..ae18442745de 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput.tsx +++ b/src/components/Search/SearchAutocompleteInput.tsx @@ -1,20 +1,24 @@ import type {ForwardedRef, ReactNode, RefObject} from 'react'; import React, {forwardRef, useState} from 'react'; -import type {StyleProp, TextInputProps, ViewStyle} from 'react-native'; import {View} from 'react-native'; +import type {StyleProp, TextInputProps, ViewStyle} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import FormHelpMessage from '@components/FormHelpMessage'; import type {SelectionListHandle} from '@components/SelectionList/types'; import TextInput from '@components/TextInput'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; +import {parseForLiveMarkdown} from '@libs/SearchAutocompleteUtils'; import handleKeyPress from '@libs/SearchInputOnKeyPress'; import shouldDelayFocus from '@libs/shouldDelayFocus'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; -type SearchRouterInputProps = { +type SearchAutocompleteInputProps = { /** Value of TextInput */ value: string; @@ -24,8 +28,8 @@ type SearchRouterInputProps = { /** Callback invoked when the user submits the input */ onSubmit?: () => void; - /** SearchRouterList ref for managing TextInput and SearchRouterList focus */ - routerListRef?: RefObject; + /** SearchAutocompleteList ref for managing TextInput and SearchAutocompleteList focus */ + autocompleteListRef?: RefObject; /** Whether the input is full width */ isFullWidth: boolean; @@ -56,14 +60,14 @@ type SearchRouterInputProps = { /** Whether the search reports API call is running */ isSearchingForReports?: boolean; -} & Pick; +} & Pick; -function SearchRouterInput( +function SearchAutocompleteInput( { value, onSearchQueryChange, onSubmit = () => {}, - routerListRef, + autocompleteListRef, isFullWidth, disabled = false, shouldShowOfflineMessage = false, @@ -76,13 +80,19 @@ function SearchRouterInput( outerWrapperStyle, rightComponent, isSearchingForReports, - }: SearchRouterInputProps, + selection, + }: SearchAutocompleteInputProps, ref: ForwardedRef, ) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [isFocused, setIsFocused] = useState(false); const {isOffline} = useNetwork(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST); + const emailList = Object.keys(loginList ?? {}); + const offlineMessage: string = isOffline && shouldShowOfflineMessage ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const inputWidth = isFullWidth ? styles.w100 : {width: variables.popoverWidth}; @@ -95,7 +105,7 @@ function SearchRouterInput( fsClass="fs-unmask" > { setIsFocused(true); - routerListRef?.current?.updateExternalTextInputFocus(true); + autocompleteListRef?.current?.updateExternalTextInputFocus(true); onFocus?.(); }} onBlur={() => { setIsFocused(false); - routerListRef?.current?.updateExternalTextInputFocus(false); + autocompleteListRef?.current?.updateExternalTextInputFocus(false); onBlur?.(); }} isLoading={!!isSearchingForReports} ref={ref} onKeyPress={handleKeyPress(onSubmit)} + isMarkdownEnabled + multiline={false} + parser={(input: string) => { + 'worklet'; + + return parseForLiveMarkdown(input, emailList, currentUserPersonalDetails.displayName ?? ''); + }} + selection={selection} /> {!!rightComponent && {rightComponent}} @@ -141,6 +159,7 @@ function SearchRouterInput( ); } -SearchRouterInput.displayName = 'SearchRouterInput'; +SearchAutocompleteInput.displayName = 'SearchAutocompleteInput'; -export default forwardRef(SearchRouterInput); +export type {SearchAutocompleteInputProps}; +export default forwardRef(SearchAutocompleteInput); diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchAutocompleteList.tsx similarity index 98% rename from src/components/Search/SearchRouter/SearchRouterList.tsx rename to src/components/Search/SearchAutocompleteList.tsx index 785708d14ec3..9daf87953b66 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -5,7 +5,6 @@ import {useOnyx} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; import {usePersonalDetails} from '@components/OnyxProvider'; import {useOptionsList} from '@components/OptionListContextProvider'; -import type {SearchFilterKey, UserFriendlyKey} from '@components/Search/types'; import SelectionList from '@components/SelectionList'; import SearchQueryListItem, {isSearchQueryItem} from '@components/SelectionList/Search/SearchQueryListItem'; import type {SearchQueryItem, SearchQueryListItemProps} from '@components/SelectionList/Search/SearchQueryListItem'; @@ -38,7 +37,8 @@ import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type PersonalDetails from '@src/types/onyx/PersonalDetails'; -import {getSubstitutionMapKey} from './getQueryWithSubstitutions'; +import {getSubstitutionMapKey} from './SearchRouter/getQueryWithSubstitutions'; +import type {SearchFilterKey, UserFriendlyKey} from './types'; type AutocompleteItemData = { filterKey: UserFriendlyKey; @@ -49,7 +49,7 @@ type AutocompleteItemData = { type GetAdditionalSectionsCallback = (options: Options) => Array> | undefined; -type SearchRouterListProps = { +type SearchAutocompleteListProps = { /** Value of TextInput */ autocompleteQueryValue: string; @@ -117,9 +117,8 @@ function SearchRouterItem(props: UserListItemProps | SearchQueryList ); } -// Todo rename to SearchAutocompleteList once it's used in both Router and SearchPage -function SearchRouterList( - {autocompleteQueryValue, searchQueryItem, getAdditionalSections, onListItemPress, setTextQuery, updateAutocompleteSubstitutions}: SearchRouterListProps, +function SearchAutocompleteList( + {autocompleteQueryValue, searchQueryItem, getAdditionalSections, onListItemPress, setTextQuery, updateAutocompleteSubstitutions}: SearchAutocompleteListProps, ref: ForwardedRef, ) { const styles = useThemeStyles(); @@ -465,7 +464,7 @@ function SearchRouterList( } const trimmedUserSearchQuery = getQueryWithoutAutocompletedPart(autocompleteQueryValue); - setTextQuery(`${trimmedUserSearchQuery}${sanitizeSearchValue(focusedItem.searchQuery)} `); + setTextQuery(`${trimmedUserSearchQuery}${sanitizeSearchValue(focusedItem.searchQuery)}\u00A0`); updateAutocompleteSubstitutions(focusedItem); }, [autocompleteQueryValue, setTextQuery, updateAutocompleteSubstitutions], @@ -495,6 +494,6 @@ function SearchRouterList( ); } -export default forwardRef(SearchRouterList); +export default forwardRef(SearchAutocompleteList); export {SearchRouterItem}; export type {GetAdditionalSectionsCallback}; diff --git a/src/components/Search/SearchInputSelectionWrapper/index.native.tsx b/src/components/Search/SearchInputSelectionWrapper/index.native.tsx new file mode 100644 index 000000000000..534e6f3824a0 --- /dev/null +++ b/src/components/Search/SearchInputSelectionWrapper/index.native.tsx @@ -0,0 +1,20 @@ +import type {ForwardedRef} from 'react'; +import React, {forwardRef} from 'react'; +import SearchAutocompleteInput from '@components/Search/SearchAutocompleteInput'; +import type {SearchAutocompleteInputProps} from '@components/Search/SearchAutocompleteInput'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; + +function SearchInputSelectionWrapper(props: SearchAutocompleteInputProps, ref: ForwardedRef) { + return ( + + ); +} + +SearchInputSelectionWrapper.displayName = 'SearchInputSelectionWrapper'; + +export default forwardRef(SearchInputSelectionWrapper); diff --git a/src/components/Search/SearchInputSelectionWrapper/index.tsx b/src/components/Search/SearchInputSelectionWrapper/index.tsx new file mode 100644 index 000000000000..0dcf3282b032 --- /dev/null +++ b/src/components/Search/SearchInputSelectionWrapper/index.tsx @@ -0,0 +1,20 @@ +import type {ForwardedRef} from 'react'; +import React, {forwardRef} from 'react'; +import SearchAutocompleteInput from '@components/Search/SearchAutocompleteInput'; +import type {SearchAutocompleteInputProps} from '@components/Search/SearchAutocompleteInput'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; + +function SearchInputSelectionWrapper({selection, ...props}: SearchAutocompleteInputProps, ref: ForwardedRef) { + return ( + + ); +} + +SearchInputSelectionWrapper.displayName = 'SearchInputSelectionWrapper'; + +export default forwardRef(SearchInputSelectionWrapper); diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx index a36368293cb2..70398194cc84 100644 --- a/src/components/Search/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader.tsx @@ -395,7 +395,7 @@ function SearchPageHeader({queryJSON}: SearchPageHeaderProps) { onTooltipPress={onFiltersButtonPress} >