From 38f7f962a11744d09e255ed5182af3c345e766be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Poizat?= Date: Fri, 13 Dec 2024 15:33:27 +0100 Subject: [PATCH] feat: Delete old search components --- src/assets/icons/icon-search-empty.svg | 6 - .../search/components/AppBarSearch.spec.jsx | 17 -- .../components/BarSearchAutosuggest.jsx | 147 ------------------ .../search/components/BarSearchInputGroup.jsx | 43 ----- src/modules/search/components/SearchEmpty.jsx | 44 ------ .../search/components/SuggestionItem.jsx | 63 -------- .../components/SuggestionItemSkeleton.jsx | 44 ------ .../SuggestionItemTextHighlighted.jsx | 104 ------------- .../SuggestionItemTextSecondary.jsx | 66 -------- .../components/SuggestionListSkeleton.jsx | 17 -- src/modules/search/components/helpers.js | 114 -------------- .../search/components/helpers.spec.jsx | 118 -------------- src/modules/search/components/styles.styl | 82 ---------- src/modules/search/hooks/useSearch.jsx | 90 ----------- src/modules/views/Search/SearchView.jsx | 144 ----------------- 15 files changed, 1099 deletions(-) delete mode 100644 src/assets/icons/icon-search-empty.svg delete mode 100644 src/modules/search/components/AppBarSearch.spec.jsx delete mode 100644 src/modules/search/components/BarSearchAutosuggest.jsx delete mode 100644 src/modules/search/components/BarSearchInputGroup.jsx delete mode 100644 src/modules/search/components/SearchEmpty.jsx delete mode 100644 src/modules/search/components/SuggestionItem.jsx delete mode 100644 src/modules/search/components/SuggestionItemSkeleton.jsx delete mode 100644 src/modules/search/components/SuggestionItemTextHighlighted.jsx delete mode 100644 src/modules/search/components/SuggestionItemTextSecondary.jsx delete mode 100644 src/modules/search/components/SuggestionListSkeleton.jsx delete mode 100644 src/modules/search/components/helpers.js delete mode 100644 src/modules/search/components/helpers.spec.jsx delete mode 100644 src/modules/search/components/styles.styl delete mode 100644 src/modules/search/hooks/useSearch.jsx delete mode 100644 src/modules/views/Search/SearchView.jsx diff --git a/src/assets/icons/icon-search-empty.svg b/src/assets/icons/icon-search-empty.svg deleted file mode 100644 index 5dc84c4f71..0000000000 --- a/src/assets/icons/icon-search-empty.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/modules/search/components/AppBarSearch.spec.jsx b/src/modules/search/components/AppBarSearch.spec.jsx deleted file mode 100644 index 78401ede6e..0000000000 --- a/src/modules/search/components/AppBarSearch.spec.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import { render, screen } from '@testing-library/react' -import React from 'react' - -import CozyClient from 'cozy-client' - -import AppBarSearch from 'modules/search/components/AppBarSearch' -import AppLike from 'test/components/AppLike' - -it('should display the Searchbar', () => { - const client = new CozyClient({}) - render( - - - - ) - expect(screen.getByPlaceholderText('Search anything')).toBeInTheDocument() -}) diff --git a/src/modules/search/components/BarSearchAutosuggest.jsx b/src/modules/search/components/BarSearchAutosuggest.jsx deleted file mode 100644 index 0bd898fdee..0000000000 --- a/src/modules/search/components/BarSearchAutosuggest.jsx +++ /dev/null @@ -1,147 +0,0 @@ -import cx from 'classnames' -import React, { useState } from 'react' -import Autosuggest from 'react-autosuggest' - -import { models, useClient } from 'cozy-client' -import { isFlagshipApp } from 'cozy-device-helper' -import { useWebviewIntent } from 'cozy-intent' -import List from 'cozy-ui/transpiled/react/List' - -import { SHARED_DRIVES_DIR_ID } from 'constants/config' -import BarSearchInputGroup from 'modules/search/components/BarSearchInputGroup' -import SuggestionItem from 'modules/search/components/SuggestionItem' -import SuggestionListSkeleton from 'modules/search/components/SuggestionListSkeleton' -import useSearch from 'modules/search/hooks/useSearch' - -import styles from 'modules/search/components/styles.styl' - -const BarSearchAutosuggest = ({ t }) => { - const webviewIntent = useWebviewIntent() - const client = useClient() - - const [input, setInput] = useState('') - const [searchTerm, setSearchTerm] = useState('') - const { suggestions, hasSuggestions, isBusy, query, makeIndexes } = - useSearch(searchTerm) - const [focused, setFocused] = useState(false) - - const theme = { - container: 'u-w-100', - suggestionsContainer: - styles['bar-search-autosuggest-suggestions-container'], - suggestionsContainerOpen: - styles['bar-search-autosuggest-suggestions-container--open'], - suggestionsList: styles['bar-search-autosuggest-suggestions-list'] - } - - const onSuggestionsFetchRequested = ({ value }) => { - setSearchTerm(value) - } - const onSuggestionsClearRequested = () => { - setSearchTerm('') - } - - const cleanSearch = () => { - setInput('') - setSearchTerm('') - } - - const onSuggestionSelected = async (event, { suggestion }) => { - // Open the shared drive in a new tab - if (suggestion.parentUrl?.includes(SHARED_DRIVES_DIR_ID)) { - window.open(`/#/external/${suggestion.id}`, '_blank') - return cleanSearch() - } - - let url = `${window.location.origin}/#${suggestion.url}` - if (suggestion.openOn === 'notes') { - url = await models.note.fetchURL(client, { - id: suggestion.url.substr(3) - }) - } - - if (url) { - if (isFlagshipApp()) { - webviewIntent.call('openApp', url, { slug: suggestion.openOn }) - } else { - window.location.assign(url) - } - } else { - console.error(`openSuggestion (${suggestion.name}) could not be executed`) - } - cleanSearch() - } - - // We want the user to find folders in which he can then navigate into, so we return the path here - const getSuggestionValue = suggestion => suggestion.subtitle - - const renderSuggestion = suggestion => { - return ( - - ) - } - - const inputProps = { - placeholder: t('searchbar.placeholder'), - value: input, - onChange: (event, { newValue }) => { - setInput(newValue) - }, - onFocus: () => { - makeIndexes() - setFocused(true) - }, - onBlur: () => setFocused(false) - } - - const renderInputComponent = inputProps => ( - - - - ) - - const renderSuggestionsContainer = ({ containerProps, children }) => { - return {children} - } - - const hasNoSearchResult = searchTerm !== '' && focused && !hasSuggestions - - return ( -
- - {hasNoSearchResult && !isBusy && ( -
- {t('searchbar.empty', { query })} -
- )} - {hasNoSearchResult && isBusy && ( -
- -
- )} -
- ) -} - -export default BarSearchAutosuggest diff --git a/src/modules/search/components/BarSearchInputGroup.jsx b/src/modules/search/components/BarSearchInputGroup.jsx deleted file mode 100644 index 92dc93ea0c..0000000000 --- a/src/modules/search/components/BarSearchInputGroup.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react' - -import Icon from 'cozy-ui/transpiled/react/Icon' -import IconButton from 'cozy-ui/transpiled/react/IconButton' -import CrossCircleOutlineIcon from 'cozy-ui/transpiled/react/Icons/CrossCircleOutline' -import Magnifier from 'cozy-ui/transpiled/react/Icons/Magnifier' -import InputGroup from 'cozy-ui/transpiled/react/InputGroup' - -import styles from 'modules/search/components/styles.styl' - -const BarSearchInputGroup = ({ - children, - isMobile, - onClean, - isInputNotEmpty -}) => { - return ( - - ) -} - -export default BarSearchInputGroup diff --git a/src/modules/search/components/SearchEmpty.jsx b/src/modules/search/components/SearchEmpty.jsx deleted file mode 100644 index f7ae8c674f..0000000000 --- a/src/modules/search/components/SearchEmpty.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react' - -import Grid from 'cozy-ui/transpiled/react/Grid' -import Icon from 'cozy-ui/transpiled/react/Icon' -import Typography from 'cozy-ui/transpiled/react/Typography' -import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n' - -import searchEmptyIllustration from 'assets/icons/icon-search-empty.svg' - -const SearchEmpty = ({ query }) => { - const { t } = useI18n() - - return ( - - - - - - {t('search.empty.title', { query })} - - - - - {t('search.empty.subtitle', { query })} - - - - ) -} - -export default SearchEmpty diff --git a/src/modules/search/components/SuggestionItem.jsx b/src/modules/search/components/SuggestionItem.jsx deleted file mode 100644 index c37810da26..0000000000 --- a/src/modules/search/components/SuggestionItem.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useCallback } from 'react' - -import ListItem from 'cozy-ui/transpiled/react/ListItem' -import ListItemIcon from 'cozy-ui/transpiled/react/ListItemIcon' -import ListItemText from 'cozy-ui/transpiled/react/ListItemText' - -import { SHARED_DRIVES_DIR_ID } from 'constants/config' -import FileIconMime from 'modules/filelist/icons/FileIconMime' -import FileIconShortcut from 'modules/filelist/icons/FileIconShortcut' -import SuggestionItemTextHighlighted from 'modules/search/components/SuggestionItemTextHighlighted' -import SuggestionItemTextSecondary from 'modules/search/components/SuggestionItemTextSecondary' - -const SuggestionItem = ({ - suggestion, - query, - onClick, - onParentOpened, - isMobile = false -}) => { - const openSuggestion = useCallback(() => { - if (typeof onClick == 'function') { - onClick(suggestion) - } - }, [onClick, suggestion]) - - const file = { - class: suggestion.class, - type: suggestion.type, - mime: suggestion.mime, - name: suggestion.title.replace(/\.url$/, ''), // Not using `splitFileName()` because we don't have access to the full file here. - parentUrl: suggestion.parentUrl - } - - return ( - - - {file.class === 'shortcut' ? ( - - ) : ( - - )} - - - } - secondary={ - file.parentUrl?.includes(SHARED_DRIVES_DIR_ID) ? null : ( - - ) - } - /> - - ) -} - -export default SuggestionItem diff --git a/src/modules/search/components/SuggestionItemSkeleton.jsx b/src/modules/search/components/SuggestionItemSkeleton.jsx deleted file mode 100644 index a39fae978f..0000000000 --- a/src/modules/search/components/SuggestionItemSkeleton.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react' - -import ListItem from 'cozy-ui/transpiled/react/ListItem' -import ListItemIcon from 'cozy-ui/transpiled/react/ListItemIcon' -import ListItemText from 'cozy-ui/transpiled/react/ListItemText' -import Skeleton from 'cozy-ui/transpiled/react/Skeleton' - -const SuggestionItemSkeleton = () => { - return ( - - - - - - } - secondary={ - - } - /> - - ) -} - -export default SuggestionItemSkeleton diff --git a/src/modules/search/components/SuggestionItemTextHighlighted.jsx b/src/modules/search/components/SuggestionItemTextHighlighted.jsx deleted file mode 100644 index 1cb6ff5049..0000000000 --- a/src/modules/search/components/SuggestionItemTextHighlighted.jsx +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react' - -import { normalizeString } from 'modules/search/components/helpers' - -/** - * Add on part that equlas query into each result - * - * @param {Array} searchResult - list of results - * @param {string} query - search input - * @returns list of results with the query highlighted - */ -const highlightQueryTerms = (searchResult, query) => { - const normalizedQueryTerms = normalizeString(query) - const normalizedResultTerms = normalizeString(searchResult) - - const matchedIntervals = [] - const spacerLength = 1 - let currentIndex = 0 - - normalizedResultTerms.forEach(resultTerm => { - normalizedQueryTerms.forEach(queryTerm => { - const index = resultTerm.indexOf(queryTerm) - if (index >= 0) { - matchedIntervals.push({ - from: currentIndex + index, - to: currentIndex + index + queryTerm.length - }) - } - }) - - currentIndex += resultTerm.length + spacerLength - }) - - // matchedIntervals can overlap, so we merge them. - // - sort the intervals by starting index - // - add the first interval to the stack - // - for every interval, - // - - add it to the stack if it doesn't overlap with the stack top - // - - or extend the stack top if the start overlaps and the new interval's top is bigger - const mergedIntervals = matchedIntervals - .sort((intervalA, intervalB) => intervalA.from > intervalB.from) - .reduce((computedIntervals, newInterval) => { - if ( - computedIntervals.length === 0 || - computedIntervals[computedIntervals.length - 1].to < newInterval.from - ) { - computedIntervals.push(newInterval) - } else if ( - computedIntervals[computedIntervals.length - 1].to < newInterval.to - ) { - computedIntervals[computedIntervals.length - 1].to = newInterval.to - } - - return computedIntervals - }, []) - - // create an array containing the entire search result, with special characters, and the intervals surrounded y `` tags - const slicedOriginalResult = - mergedIntervals.length > 0 - ? [{searchResult.slice(0, mergedIntervals[0].from)}] - : searchResult - - for (let i = 0, l = mergedIntervals.length; i < l; ++i) { - slicedOriginalResult.push( - - {searchResult.slice(mergedIntervals[i].from, mergedIntervals[i].to)} - - ) - if (i + 1 < l) - slicedOriginalResult.push( - - {searchResult.slice( - mergedIntervals[i].to, - mergedIntervals[i + 1].from - )} - - ) - } - - if (mergedIntervals.length > 0) - slicedOriginalResult.push( - - {searchResult.slice( - mergedIntervals[mergedIntervals.length - 1].to, - searchResult.length - )} - - ) - - return slicedOriginalResult -} - -const SuggestionItemTextHighlighted = ({ text, query }) => { - const textHighlighted = highlightQueryTerms(text, query) - if (Array.isArray(textHighlighted)) { - return textHighlighted.map((item, idx) => ({ - ...item, - key: idx - })) - } - return textHighlighted -} - -export default SuggestionItemTextHighlighted diff --git a/src/modules/search/components/SuggestionItemTextSecondary.jsx b/src/modules/search/components/SuggestionItemTextSecondary.jsx deleted file mode 100644 index 8680a0b08d..0000000000 --- a/src/modules/search/components/SuggestionItemTextSecondary.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react' - -import { generateWebLink, useClient } from 'cozy-client' -import { isFlagshipApp } from 'cozy-device-helper' -import AppLinker, { - generateUniversalLink -} from 'cozy-ui/transpiled/react/AppLinker' - -import SuggestionItemTextHighlighted from 'modules/search/components/SuggestionItemTextHighlighted' - -import styles from 'modules/search/components/styles.styl' - -const SuggestionItemTextSecondary = ({ - text, - query, - url, - onOpened, - isMobile -}) => { - const client = useClient() - - if (isMobile) { - return - } - - const app = { - slug: 'drive' - } - - const { subdomain: subDomainType } = client.getInstanceOptions() - const generateLink = isFlagshipApp() ? generateUniversalLink : generateWebLink - - const appWebRef = - app && - generateLink({ - slug: 'drive', - cozyUrl: client.getStackClient().uri, - subDomainType, - nativePath: url, - pathname: '/', - hash: url - }) - return ( - - {({ onClick, href }) => ( - { - e.stopPropagation() - if (typeof onOpened == 'function') { - onOpened(e) - } - if (typeof onClick == 'function') { - onClick(e) - } - }} - > - - - )} - - ) -} - -export default SuggestionItemTextSecondary diff --git a/src/modules/search/components/SuggestionListSkeleton.jsx b/src/modules/search/components/SuggestionListSkeleton.jsx deleted file mode 100644 index f6011386b9..0000000000 --- a/src/modules/search/components/SuggestionListSkeleton.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' - -import List from 'cozy-ui/transpiled/react/List' - -import SuggestionItemSkeleton from 'modules/search/components/SuggestionItemSkeleton' - -const SuggestionListSkeleton = ({ count }) => ( - - {Array(count || 4) - .fill(1) - .map((_, i) => ( - - ))} - -) - -export default SuggestionListSkeleton diff --git a/src/modules/search/components/helpers.js b/src/modules/search/components/helpers.js deleted file mode 100644 index 2f4594ed46..0000000000 --- a/src/modules/search/components/helpers.js +++ /dev/null @@ -1,114 +0,0 @@ -import { models } from 'cozy-client' - -import { ROOT_DIR_ID, SHARED_DRIVES_DIR_ID } from 'constants/config' -import FuzzyPathSearch from 'lib/FuzzyPathSearch.js' -import { isEncryptedFolder } from 'lib/encryption' -import { makeOnlyOfficeFileRoute } from 'modules/views/OnlyOffice/helpers' - -export const TYPE_DIRECTORY = 'directory' - -export const normalizeString = str => - str - .toString() - .toLowerCase() - .replace(/\//g, ' ') - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '') - .split(' ') - -/** - * Normalize file for Front usage in component inside - * - * To reduce API call, the fetching of Note URL has been delayed - * inside an onSelect function called only if provided to - * see https://github.com/cozy/cozy-drive/pull/2663#discussion_r938671963 - * - * @param {CozyClient} client - cozy client instance - * @param {[IOCozyFile]} folders - all the folders returned by API - * @param {IOCozyFile} file - file to normalize - * @returns file with normalized field to be used in AutoSuggestion - */ -export const makeNormalizedFile = (client, folders, file) => { - const isDir = file.type === TYPE_DIRECTORY - const dirId = isDir ? file._id : file.dir_id - const urlToFolder = `/folder/${dirId}` - - let path, url, parentUrl - let openOn = 'drive' - if (isDir) { - path = file.path - url = urlToFolder - parentUrl = urlToFolder - } else { - const parentDir = folders.find(folder => folder._id === file.dir_id) - path = parentDir && parentDir.path ? parentDir.path : '' - parentUrl = parentDir && parentDir._id ? `/folder/${parentDir._id}` : '' - if (models.file.isNote(file)) { - url = `/n/${file.id}` - openOn = 'notes' - } else if (models.file.shouldBeOpenedByOnlyOffice(file)) { - url = makeOnlyOfficeFileRoute(file.id, { fromPathname: urlToFolder }) - } else { - url = `${urlToFolder}/file/${file._id}` - } - } - - return { - id: file._id, - type: file.type, - name: file.name, - mime: file.mime, - class: file.class, - path, - url, - parentUrl, - openOn, - isEncrypted: isEncryptedFolder(file) - } -} - -/** - * Fetches all files without trashed and preloads FuzzyPathSearch - * - * Using _all_docs route - * - * Also, this method: - * - removing trashed data directly - * - removes orphan file - * - normalize file to match expectation - * - preloads FuzzyPathSearch - * - * @returns {Promise} nothing - */ -export const indexFiles = async client => { - const resp = await client - .getStackClient() - .fetchJSON( - 'GET', - '/data/io.cozy.files/_all_docs?Fields=_id,trashed,dir_id,name,path,type,mime,class,metadata.title,metadata.version&DesignDocs=false&include_docs=true' - ) - const files = resp.rows.map(row => ({ id: row.id, ...row.doc })) - const folders = files.filter(file => file.type === TYPE_DIRECTORY) - - const notInTrash = file => !file.trashed && !/^\/\.cozy_trash/.test(file.path) - const notOrphans = file => - folders.find(folder => folder._id === file.dir_id) !== undefined - const notRoot = file => file._id !== ROOT_DIR_ID - // Shared drives folder to be hidden in search. - // The files inside it though must appear. Thus only the file with the folder ID is filtered out. - const notSharedDrivesDir = file => file._id !== SHARED_DRIVES_DIR_ID - - const normalizedFilesPrevious = files.filter( - file => - notInTrash(file) && - notOrphans(file) && - notRoot(file) && - notSharedDrivesDir(file) - ) - - const normalizedFiles = normalizedFilesPrevious.map(file => - makeNormalizedFile(client, folders, file) - ) - - return new FuzzyPathSearch(normalizedFiles) -} diff --git a/src/modules/search/components/helpers.spec.jsx b/src/modules/search/components/helpers.spec.jsx deleted file mode 100644 index e263019295..0000000000 --- a/src/modules/search/components/helpers.spec.jsx +++ /dev/null @@ -1,118 +0,0 @@ -import { createMockClient, models } from 'cozy-client' - -import { makeNormalizedFile, TYPE_DIRECTORY } from './helpers' - -models.note.fetchURL = jest.fn(() => 'noteUrl') - -const client = createMockClient({}) - -const noteFileProps = { - name: 'note.cozy-note', - metadata: { - content: '', - schema: '', - title: '', - version: '' - } -} - -describe('makeNormalizedFile', () => { - it('should return correct values for a directory', () => { - const folders = [] - const file = { - _id: 'fileId', - type: TYPE_DIRECTORY, - path: 'filePath', - name: 'fileName' - } - - const normalizedFile = makeNormalizedFile(client, folders, file) - - expect(normalizedFile).toEqual({ - id: 'fileId', - name: 'fileName', - path: 'filePath', - url: '/folder/fileId', - parentUrl: '/folder/fileId', - openOn: 'drive', - isEncrypted: false, - mime: undefined, - type: 'directory' - }) - }) - - it('should return correct values for a file', () => { - const folders = [{ _id: 'folderId', path: 'folderPath' }] - const file = { - _id: 'fileId', - dir_id: 'folderId', - type: 'file', - name: 'fileName' - } - - const normalizedFile = makeNormalizedFile(client, folders, file) - - expect(normalizedFile).toEqual({ - id: 'fileId', - name: 'fileName', - path: 'folderPath', - url: '/folder/folderId/file/fileId', - parentUrl: '/folder/folderId', - openOn: 'drive', - isEncrypted: false, - mime: undefined, - type: 'file' - }) - }) - - it('should return correct values for a note with on Select function - better for performance', () => { - const folders = [{ _id: 'folderId', path: 'folderPath' }] - const file = { - _id: 'fileId', - id: 'noteId', - dir_id: 'folderId', - type: 'file', - name: 'fileName', - ...noteFileProps - } - - const normalizedFile = makeNormalizedFile(client, folders, file) - - expect(normalizedFile).toEqual({ - id: 'fileId', - name: 'note.cozy-note', - path: 'folderPath', - url: '/n/noteId', - parentUrl: '/folder/folderId', - openOn: 'notes', - isEncrypted: false, - mime: undefined, - type: 'file' - }) - }) - - it('should not return filled onSelect for a note without metadata', () => { - const folders = [{ _id: 'folderId', path: 'folderPath' }] - const file = { - _id: 'fileId', - id: 'noteId', - dir_id: 'folderId', - type: 'file', - name: 'note.cozy-note' - } - - const normalizedFile = makeNormalizedFile(client, folders, file) - - expect(normalizedFile).toEqual({ - id: 'fileId', - name: 'note.cozy-note', - path: 'folderPath', - url: '/folder/folderId/file/fileId', - parentUrl: '/folder/folderId', - openOn: 'drive', - isEncrypted: false, - mime: undefined, - type: 'file' - }) - }) -}) diff --git a/src/modules/search/components/styles.styl b/src/modules/search/components/styles.styl deleted file mode 100644 index db89498f03..0000000000 --- a/src/modules/search/components/styles.styl +++ /dev/null @@ -1,82 +0,0 @@ -[role=banner] - .bar-search-autosuggest-suggestions-container - position absolute - top 100% - width 100% - max-height em(170px) - overflow auto - border-radius .5em - color var(--primaryTextColor) - background var(--paperBackgroundColor) - box-shadow var(--shadow7) - display none - box-sizing border-box - - .bar-search-autosuggest-suggestions-container--open - display block - - .bar-search-autosuggest-status-container - position absolute - display flex - align-items center - top 100% - left 0 - right 0 - min-height 48px - max-height em(170px) - overflow auto - border-radius .5em - background var(--paperBackgroundColor) - box-shadow var(--shadow7) - box-sizing border-box - - &.--empty - padding .75em 1em - - .bar-search-autosuggest-suggestions-list - margin 0 - padding 0 - list-style none - - .bar-search-container - position relative - display flex - align-items center - flex-grow 1 - margin-left 2em - margin-right 2em - padding-top .25em - padding-bottom .25em - - &.mobile - margin-left 0 - margin-right -.5em - - .bar-search-input-group - border 0 - max-height 40px - padding-left .5em - border-radius 1.25em - background-color var(--defaultBackgroundColor) - transition all .2s ease-out - overflow hidden - - &:hover - background linear-gradient(0deg, var(--actionColorHover), var(--actionColorHover)), var(--defaultBackgroundColor) - - .bar-search-input-group-append - padding-left .5em - color var(--secondaryTextColor) - - input - padding-left .5em - background-color transparent - max-width 100% - height 100% - -.suggestion-item-parent-link - color var(--secondaryTextColor) - text-decoration none - - &:hover - text-decoration underline diff --git a/src/modules/search/hooks/useSearch.jsx b/src/modules/search/hooks/useSearch.jsx deleted file mode 100644 index 95134f835b..0000000000 --- a/src/modules/search/hooks/useSearch.jsx +++ /dev/null @@ -1,90 +0,0 @@ -import { useState, useEffect, useMemo } from 'react' - -import { useClient } from 'cozy-client' - -import useDebounce from 'hooks/useDebounce' -import { indexFiles } from 'modules/search/components/helpers' - -const useSearch = (searchTerm, { limit = 10 } = {}) => { - const client = useClient() - const [allSuggestions, setAllSuggestions] = useState([]) - const [suggestions, setSuggestions] = useState([]) - const [fuzzy, setFuzzy] = useState(null) - const [isBusy, setBusy] = useState(true) - const [query, setQuery] = useState('') - - const debouncedSearchTerm = useDebounce(searchTerm, { - delay: 500, - ignore: searchTerm === '' - }) - - const makeIndexes = async () => { - if (fuzzy == null) { - setFuzzy(await indexFiles(client)) - } - } - - useEffect(() => { - const fetchSuggestions = async value => { - setBusy(true) - let currentFuzzy = fuzzy - if (currentFuzzy == null) { - currentFuzzy = await indexFiles(client) - setFuzzy(currentFuzzy) - } - const suggestions = currentFuzzy.search(value).map(result => ({ - id: result.id, - title: result.name, - subtitle: result.path, - url: result.url, - parentUrl: result.parentUrl, - openOn: result.openOn, - type: result.type, - mime: result.mime, - isEncrypted: result.isEncrypted, - class: result.class - })) - - setBusy(value === '') // To prevent empty state to appear at the first search - setQuery(value) - setAllSuggestions(suggestions) - setSuggestions(suggestions.slice(0, limit)) - } - - if (debouncedSearchTerm !== '') { - fetchSuggestions(debouncedSearchTerm) - } else { - clearSuggestions() - } - }, [client, debouncedSearchTerm, fuzzy, limit]) - - const hasSuggestions = useMemo(() => suggestions.length > 0, [suggestions]) - - const hasMore = useMemo( - () => suggestions.length < allSuggestions.length, - [suggestions, allSuggestions] - ) - - const fetchMore = async () => { - setSuggestions(allSuggestions.slice(0, suggestions.length + limit)) - } - - const clearSuggestions = () => { - setBusy(true) - setQuery('') - setAllSuggestions([]) - setSuggestions([]) - } - - return { - suggestions, - hasSuggestions, - hasMore, - isBusy, - query, - makeIndexes, - fetchMore - } -} - -export default useSearch diff --git a/src/modules/views/Search/SearchView.jsx b/src/modules/views/Search/SearchView.jsx deleted file mode 100644 index f5ee3c58d8..0000000000 --- a/src/modules/views/Search/SearchView.jsx +++ /dev/null @@ -1,144 +0,0 @@ -import cx from 'classnames' -import React, { useState, useCallback } from 'react' -import { useEffect } from 'react' -import { useLocation, useNavigate } from 'react-router-dom' - -import { BarLeft, BarSearch } from 'cozy-bar' -import { models, useClient } from 'cozy-client' -import { isFlagshipApp } from 'cozy-device-helper' -import { useWebviewIntent } from 'cozy-intent' -import Input from 'cozy-ui/transpiled/react/Input' -import { Main } from 'cozy-ui/transpiled/react/Layout' -import List from 'cozy-ui/transpiled/react/List' -import LoadMore from 'cozy-ui/transpiled/react/LoadMore' -import useBreakpoints from 'cozy-ui/transpiled/react/providers/Breakpoints' -import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n' - -import BackButton from 'components/Button/BackButton' -import BarSearchInputGroup from 'modules/search/components/BarSearchInputGroup' -import SearchEmpty from 'modules/search/components/SearchEmpty' -import SuggestionItem from 'modules/search/components/SuggestionItem' -import SuggestionListSkeleton from 'modules/search/components/SuggestionListSkeleton' -import useSearch from 'modules/search/hooks/useSearch' - -import styles from 'modules/search/components/styles.styl' - -const SearchView = () => { - const webviewIntent = useWebviewIntent() - const { search } = useLocation() - const navigate = useNavigate() - const { isMobile } = useBreakpoints() - const client = useClient() - - const [searchTerm, setSearchTerm] = useState('') - const { t } = useI18n() - const { - isBusy, - suggestions, - hasSuggestions, - query, - makeIndexes, - hasMore, - fetchMore - } = useSearch(searchTerm, { limit: 25 }) - - useEffect(() => { - makeIndexes() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - const onInputChanged = event => { - setSearchTerm(event.target.value) - } - - const navigateBack = useCallback(() => { - const params = new URLSearchParams(search) - const returnPath = params.get('returnPath') - navigate(returnPath ? returnPath : '/') - }, [navigate, search]) - - const openSuggestion = useCallback( - async suggestion => { - if (suggestion.openOn === 'drive') { - navigate(suggestion.url) - } else if (suggestion.openOn === 'notes') { - const url = await models.note.fetchURL(client, { - id: suggestion.url.substr(3) - }) - if (isFlagshipApp()) { - webviewIntent.call('openApp', url, { - slug: 'notes' - }) - } else { - window.location.assign(url) - } - } else { - console.error( - `openSuggestion (${suggestion.name}) could not be executed` - ) - } - }, - [navigate, webviewIntent, client] - ) - - const handleCleanInput = () => { - setSearchTerm('') - } - - const hasNoSearchResult = searchTerm !== '' && !hasSuggestions - - return ( -
- {isMobile && ( - - - - )} - -
- - - -
-
- {hasSuggestions && ( - - {suggestions.map(suggestion => ( - - ))} - - )} - {hasMore && ( -
- -
- )} - {hasNoSearchResult && !isBusy && } - {hasNoSearchResult && isBusy && } -
- ) -} - -export default SearchView