diff --git a/app/constants/files.ts b/app/constants/files.ts index 12d34ae1b..632d3f2f1 100644 --- a/app/constants/files.ts +++ b/app/constants/files.ts @@ -35,7 +35,7 @@ const Files: Record = { PATCH_TYPES: ['patch'], PDF_TYPES: ['pdf'], PRESENTATION_TYPES: ['ppt', 'pptx', 'odp'], - SPREADSHEET_TYPES: ['xls, xlsx', 'csv', 'ods'], + SPREADSHEET_TYPES: ['xls', 'xlsx', 'csv', 'ods'], TEXT_TYPES: ['txt', 'rtf'], VIDEO_TYPES: ['mp4', 'avi', 'webm', 'mkv', 'wmv', 'mpg', 'mov', 'flv', 'ogm', 'mpeg'], WORD_TYPES: ['doc', 'docx', 'odt'], diff --git a/app/screens/home/search/results/filter.tsx b/app/screens/home/search/results/filter.tsx index 3cd42a109..3204e561a 100644 --- a/app/screens/home/search/results/filter.tsx +++ b/app/screens/home/search/results/filter.tsx @@ -14,7 +14,7 @@ import {useIsTablet} from '@hooks/device'; import {t} from '@i18n'; import BottomSheetContent from '@screens/bottom_sheet/content'; import {dismissBottomSheet} from '@screens/navigation'; -import {FileFilter} from '@utils/file'; +import {FileFilter, FileFilters} from '@utils/file'; import {makeStyleSheetFromTheme} from '@utils/theme'; import {typography} from '@utils/typography'; @@ -43,35 +43,35 @@ const data: FilterItem[] = [ { id: t('screen.search.results.filter.all_file_types'), defaultMessage: 'All file types', - filterType: 'all', + filterType: FileFilters.ALL, }, { id: t('screen.search.results.filter.documents'), defaultMessage: 'Documents', - filterType: 'documents', + filterType: FileFilters.DOCUMENTS, }, { id: t('screen.search.results.filter.spreadsheets'), defaultMessage: 'Spreadsheets', - filterType: 'spreadsheets', + filterType: FileFilters.SPREADSHEETS, }, { id: t('screen.search.results.filter.presentations'), defaultMessage: 'Presentations', - filterType: 'presentations', + filterType: FileFilters.PRESENTATIONS, }, { id: t('screen.search.results.filter.code'), defaultMessage: 'Code', - filterType: 'code', + filterType: FileFilters.CODE, }, { id: t('screen.search.results.filter.images'), defaultMessage: 'Images', - filterType: 'images', + filterType: FileFilters.IMAGES, }, { id: t('screen.search.results.filter.audio'), defaultMessage: 'Audio', - filterType: 'audio', + filterType: FileFilters.AUDIO, }, { id: t('screen.search.results.filter.videos'), defaultMessage: 'Videos', - filterType: 'videos', + filterType: FileFilters.VIDEOS, separator: false, }, ]; diff --git a/app/screens/home/search/results/header.tsx b/app/screens/home/search/results/header.tsx index c771bd409..5e0ccf217 100644 --- a/app/screens/home/search/results/header.tsx +++ b/app/screens/home/search/results/header.tsx @@ -8,7 +8,7 @@ import Badge from '@components/badge'; import CompassIcon from '@components/compass_icon'; import {useTheme} from '@context/theme'; import {bottomSheet} from '@screens/navigation'; -import {FileFilter} from '@utils/file'; +import {FileFilter, FileFilters} from '@utils/file'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; import Filter from './filter'; @@ -68,7 +68,7 @@ const Header = ({ const filesText = intl.formatMessage({id: 'screen.search.header.files', defaultMessage: 'Files'}); const showFilterIcon = selectedTab === 'files'; - const hasFilters = selectedFilter !== 'all'; + const hasFilters = selectedFilter !== FileFilters.ALL; const handleMessagesPress = useCallback(() => { onTabSelect('messages'); diff --git a/app/screens/home/search/search.tsx b/app/screens/home/search/search.tsx index 930df2eb2..89b9557af 100644 --- a/app/screens/home/search/search.tsx +++ b/app/screens/home/search/search.tsx @@ -15,7 +15,7 @@ import NavigationHeader from '@components/navigation_header'; import RoundedHeaderContext from '@components/rounded_header_context'; import {useServerUrl} from '@context/server'; import {useCollapsibleHeader} from '@hooks/header'; -import {FileFilter, filterFiles} from '@utils/file'; +import {FileFilter, FileFilters, filterFileExtensions} from '@utils/file'; import Results from './results'; import Header, {SelectTab} from './results/header'; @@ -46,14 +46,23 @@ const SearchScreen = ({teamId}: Props) => { const [searchValue, setSearchValue] = useState(searchTerm); const [selectedTab, setSelectedTab] = useState('messages'); - const [filter, setFilter] = useState('all'); + const [filter, setFilter] = useState(FileFilters.ALL); const [loading, setLoading] = useState(false); const [lastSearchedValue, setLastSearchedValue] = useState(''); const [postIds, setPostIds] = useState(emptyPostResults); const [fileInfos, setFileInfos] = useState(emptyFileResults); - const [filteredFileInfos, setFilteredFileInfos] = useState(emptyFileResults); + + const getSearchParams = useCallback((filterValue?: FileFilter) => { + const terms = filterValue ? lastSearchedValue : searchValue; + const fileExtensions = filterFileExtensions(filterValue || filter); + const extensionTerms = fileExtensions ? ' ' + fileExtensions : ''; + return { + terms: terms + extensionTerms, + is_or_search: true, + }; + }, [filter, lastSearchedValue, searchValue]); const handleSearch = useCallback((debounce(async () => { // execute the search for the text in the navigation text box @@ -62,13 +71,9 @@ const SearchScreen = ({teamId}: Props) => { // - updated recent createdAt if exists?? setLoading(true); + setFilter(FileFilters.ALL); setLastSearchedValue(searchValue); - - const searchParams: PostSearchParams | FileSearchParams = { - terms: searchValue, - is_or_search: true, - }; - + const searchParams = getSearchParams(); const [postResults, fileResults] = await Promise.all([ searchPosts(serverUrl, searchParams), searchFiles(serverUrl, teamId, searchParams), @@ -85,14 +90,21 @@ const SearchScreen = ({teamId}: Props) => { scrollRef.current?.scrollToOffset({offset, animated: true}); }; + const handleFilterChange = useCallback(async (filterValue: FileFilter) => { + setLoading(true); + setFilter(filterValue); + const searchParams = getSearchParams(filterValue); + const fileResults = await searchFiles(serverUrl, teamId, searchParams); + const fileInfosResult = fileResults?.file_infos && Object.values(fileResults?.file_infos); + setFileInfos(fileInfosResult?.length ? fileInfosResult : emptyFileResults); + + setLoading(false); + }, [lastSearchedValue]); + useEffect(() => { setSearchValue(searchTerm); }, [searchTerm]); - useEffect(() => { - setFilteredFileInfos(filterFiles(fileInfos, filter)); - }, [filter, fileInfos]); - const {scrollPaddingTop, scrollRef, scrollValue, onScroll, headerHeight, hideHeader} = useCollapsibleHeader(true, onSnap); const animated = useAnimatedStyle(() => { @@ -118,15 +130,21 @@ const SearchScreen = ({teamId}: Props) => { }; }, [headerHeight, lastSearchedValue]); + const onClear = useCallback(() => { + setSearchValue(''); + setLastSearchedValue(''); + setFilter(FileFilters.ALL); + }, [filter]); + let header = null; if (lastSearchedValue) { header = (
); @@ -146,6 +164,7 @@ const SearchScreen = ({teamId}: Props) => { blurOnSubmit={true} placeholder={intl.formatMessage({id: 'screen.search.placeholder', defaultMessage: 'Search messages & files'})} defaultValue={searchValue} + onClear={onClear} /> { selectedTab={selectedTab} searchValue={lastSearchedValue} postIds={postIds} - fileInfos={filteredFileInfos} + fileInfos={fileInfos} scrollRef={scrollRef} onScroll={onScroll} scrollPaddingTop={scrollPaddingTop} diff --git a/app/utils/file/index.ts b/app/utils/file/index.ts index dbcad0855..17d36ac9f 100644 --- a/app/utils/file/index.ts +++ b/app/utils/file/index.ts @@ -15,6 +15,7 @@ import Permissions, {PERMISSIONS} from 'react-native-permissions'; import {Files} from '@constants'; import {generateId} from '@utils/general'; +import keyMirror from '@utils/key_mirror'; import {deleteEntititesFile, getIOSAppGroupDetails} from '@utils/mattermost_managed'; import {hashCode} from '@utils/security'; @@ -24,7 +25,17 @@ const EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/; const CONTENT_DISPOSITION_REGEXP = /inline;filename=".*\.([a-z]+)";/i; const DEFAULT_SERVER_MAX_FILE_SIZE = 50 * 1024 * 1024;// 50 Mb -export type FileFilter = 'all' | 'documents' | 'spreadsheets'| 'presentations' | 'code' | 'images' | 'audio' | 'videos' +export const FileFilters = keyMirror({ + ALL: null, + DOCUMENTS: null, + SPREADSHEETS: null, + PRESENTATIONS: null, + CODE: null, + IMAGES: null, + AUDIO: null, + VIDEOS: null, +}); +export type FileFilter = keyof typeof FileFilters export const GENERAL_SUPPORTED_DOCS_FORMAT = [ 'application/json', @@ -60,20 +71,36 @@ const SUPPORTED_VIDEO_FORMAT = Platform.select({ const types: Record = {}; const extensions: Record = {}; -export function filterFiles(files: T[], filter: FileFilter) { +export function filterFileExtensions(filter: FileFilter): string { + let searchTerms: string[] = []; switch (filter) { - case 'all': - return files; - case 'videos': - return files.filter((f) => isVideo(f)); - case 'documents': - return files.filter((f) => isDocument(f)); - case 'images': - return files.filter((f) => isImage(f)); + case FileFilters.ALL: + return ''; + case FileFilters.DOCUMENTS: + searchTerms = Files.DOCUMENT_TYPES; + break; + case FileFilters.SPREADSHEETS: + searchTerms = Files.SPREADSHEET_TYPES; + break; + case FileFilters.PRESENTATIONS: + searchTerms = Files.PRESENTATION_TYPES; + break; + case FileFilters.CODE: + searchTerms = Files.CODE_TYPES; + break; + case FileFilters.IMAGES: + searchTerms = Files.IMAGE_TYPES; + break; + case FileFilters.AUDIO: + searchTerms = Files.AUDIO_TYPES; + break; + case FileFilters.VIDEOS: + searchTerms = Files.VIDEO_TYPES; + break; default: - // TODO create the rest of the filters - return files.filter((f) => !isVideo(f) && !isDocument(f) && !isImage(f)); + return ''; } + return 'ext:' + searchTerms.join(' ext:'); } /**