-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Implement search feature through Dataproxy #2198
Changes from all commits
1695a6e
5773369
118bc82
58ddb0b
c170054
b102c45
0260903
9139bfc
15057f3
2b69690
2486630
0456cb9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
import React from 'react' | ||
|
||
import flag from 'cozy-flags' | ||
import List from 'cozy-ui/transpiled/react/List' | ||
import Circle from 'cozy-ui/transpiled/react/Circle' | ||
import PaperplaneIcon from 'cozy-ui/transpiled/react/Icons/Paperplane' | ||
|
@@ -9,13 +8,15 @@ import ListItemSkeleton from 'cozy-ui/transpiled/react/Skeletons/ListItemSkeleto | |
import { useI18n } from 'cozy-ui/transpiled/react/providers/I18n' | ||
import { useBreakpoints } from 'cozy-ui/transpiled/react/providers/Breakpoints' | ||
|
||
import { useDataProxy } from 'dataproxy/DataProxyProvider' | ||
|
||
import { useSearch } from '../Search/SearchProvider' | ||
import ResultMenuItem from './ResultMenuItem' | ||
|
||
const SearchResult = () => { | ||
const { isLoading, results } = useSearch() | ||
const { isLoading, results, searchValue } = useSearch() | ||
|
||
if (isLoading) | ||
if (isLoading && !results?.length) | ||
return ( | ||
<> | ||
<ListItemSkeleton hasSecondary /> | ||
|
@@ -26,10 +27,12 @@ const SearchResult = () => { | |
|
||
return results.map((result, idx) => ( | ||
<ResultMenuItem | ||
key={idx} | ||
icon={<Icon icon={result.icon} size={32} />} | ||
key={result.id || idx} | ||
icon={result.icon} | ||
primaryText={result.primary} | ||
secondaryText={result.secondary} | ||
query={searchValue} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. j'aurai gardé There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On a eu exactement cette discussion avec @paultranvan y a quelques jours qui a opté pour query car c'est le terme généralement utilisé. Mais j'ai pas d'objection forte pour changer le nom. |
||
highlightQuery="true" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. attention on devrait probablement avoir |
||
onClick={result.onClick} | ||
/> | ||
)) | ||
|
@@ -39,6 +42,7 @@ const ResultMenuContent = ({ onClick }) => { | |
const { t } = useI18n() | ||
const { isMobile } = useBreakpoints() | ||
const { searchValue } = useSearch() | ||
const { dataProxyServicesAvailable } = useDataProxy() | ||
|
||
return ( | ||
<List> | ||
|
@@ -49,10 +53,11 @@ const ResultMenuContent = ({ onClick }) => { | |
</Circle> | ||
} | ||
primaryText={searchValue} | ||
query={searchValue} | ||
secondaryText={t('assistant.search.result')} | ||
onClick={onClick} | ||
/> | ||
{flag('cozy.assistant.withSearchResult') && <SearchResult />} | ||
{dataProxyServicesAvailable && <SearchResult />} | ||
</List> | ||
) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/** | ||
* Code copied and adapted from cozy-drive | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. est-ce la manière la plus optimisé et moderne de le faire ? De quand date le code sur cozy-drive ? Est-ce qu'il n'y a pas des outils côté Mui pour faire ça plus simplement ? Par ailleurs, comme on utilise la même chose à deux endroits, on devrait peut-être faire un compose cozy-ui enfin, pour verrouiller l'approche, on pourrait peut-être rajouter des tests... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Je suis allé au plus simple car j'imagine qu'on va bientôt remplacer la recherche de drive par la celle de dataproxy. Ok pour les tests, j'ajouterai ça dans les prochaines PR |
||
* | ||
* See source: https://github.com/cozy/cozy-drive/blob/fbe2df67199683b23a40f476ccdacb00ee027459/src/modules/search/components/SuggestionItemTextHighlighted.jsx | ||
*/ | ||
|
||
import React from 'react' | ||
|
||
const normalizeString = str => | ||
str | ||
.toString() | ||
.toLowerCase() | ||
.replace(/\//g, ' ') | ||
.normalize('NFD') | ||
.replace(/[\u0300-\u036f]/g, '') | ||
.split(' ') | ||
|
||
/** | ||
* Add <b> 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 `<b>` tags | ||
const slicedOriginalResult = | ||
mergedIntervals.length > 0 | ||
? [<span key="0">{searchResult.slice(0, mergedIntervals[0].from)}</span>] | ||
: searchResult | ||
|
||
for (let i = 0, l = mergedIntervals.length; i < l; ++i) { | ||
slicedOriginalResult.push( | ||
<span className="u-primaryColor"> | ||
{searchResult.slice(mergedIntervals[i].from, mergedIntervals[i].to)} | ||
</span> | ||
) | ||
if (i + 1 < l) | ||
slicedOriginalResult.push( | ||
<span> | ||
{searchResult.slice( | ||
mergedIntervals[i].to, | ||
mergedIntervals[i + 1].from | ||
)} | ||
</span> | ||
) | ||
} | ||
|
||
if (mergedIntervals.length > 0) | ||
slicedOriginalResult.push( | ||
<span> | ||
{searchResult.slice( | ||
mergedIntervals[mergedIntervals.length - 1].to, | ||
searchResult.length | ||
)} | ||
</span> | ||
) | ||
|
||
return slicedOriginalResult | ||
} | ||
|
||
const SuggestionItemTextHighlighted = ({ text, query }) => { | ||
if (!text) return null | ||
|
||
const textHighlighted = highlightQueryTerms(text, query) | ||
if (Array.isArray(textHighlighted)) { | ||
return textHighlighted.map((item, idx) => ({ | ||
...item, | ||
key: idx | ||
})) | ||
} | ||
return textHighlighted | ||
} | ||
|
||
export default SuggestionItemTextHighlighted |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import React from 'react' | ||
|
||
const EncryptedFolderIcon = props => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on devrait passer SVGO dessus et potentiellement avoir ça dans cozy-ui non ? |
||
return ( | ||
<svg | ||
viewBox="0 0 32 32" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
{...props} | ||
> | ||
<path | ||
opacity="0.34" | ||
fillRule="evenodd" | ||
clipRule="evenodd" | ||
d="M12.9657 1C13.5206 1 14.2876 1.3125 14.6803 1.6995L16 3H30.0059C31.1072 3 32 3.89498 32 4.997V27.003C32 28.1059 31.1107 29 29.9983 29H2.00174C0.896211 29 0 28.1001 0 27.0088V1.99653C0 1.44616 0.448999 1 1.00472 1H12.9657Z" | ||
fill="#297EF2" | ||
/> | ||
<path | ||
fillRule="evenodd" | ||
clipRule="evenodd" | ||
d="M1 1C0.447715 1 0 1.44772 0 2V6C0 6.55228 0.447716 7 1 7H13.5858C13.851 7 14.1054 6.89464 14.2929 6.70711L15.7071 5.29289C15.8946 5.10536 16.149 5 16.4142 5H32C32 3.89543 31.1046 3 30 3H16.4142C16.149 3 15.8946 2.89464 15.7071 2.70711L14.2929 1.29289C14.1054 1.10536 13.851 1 13.5858 1H1ZM10 15.9954V24.0046C10 24.5543 10.4558 25 11.0025 25H20.9975C21.5512 25 22 24.5443 22 24.0046V15.9954C22 15.4457 21.5561 15 21 15H20V13C20 10.794 18.2053 9 16 9C13.794 9 12 10.794 12 13V15H11C10.4477 15 10 15.4557 10 15.9954ZM16 11C14.8968 11 14 12.1215 14 13.5V15H18V13.5C18 12.1215 17.1028 11 16 11ZM17.5 19C17.5 18.172 16.8265 17.5 16 17.5C15.172 17.5 14.5 18.172 14.5 19C14.5 19.552 14.803 20.032 15.25 20.29V22.75C15.25 23.1625 15.586 23.5 16 23.5C16.4125 23.5 16.75 23.1625 16.75 22.75V20.29C17.1955 20.032 17.5 19.552 17.5 19Z" | ||
fill="#297EF2" | ||
/> | ||
</svg> | ||
) | ||
} | ||
|
||
export default EncryptedFolderIcon |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on peut peut-être rajouter un test ou deux sur ça ? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import mime from 'mime-types' | ||
|
||
const getMimetypeFromFilename = name => { | ||
return mime.lookup(name) || 'application/octet-stream' | ||
} | ||
|
||
const mappingMimetypeSubtype = { | ||
word: 'text', | ||
text: 'text', | ||
zip: 'zip', | ||
pdf: 'pdf', | ||
spreadsheet: 'sheet', | ||
excel: 'sheet', | ||
sheet: 'sheet', | ||
presentation: 'slide', | ||
powerpoint: 'slide' | ||
} | ||
|
||
export const getFileMimetype = | ||
collection => | ||
(mime = '', name = '') => { | ||
const mimetype = | ||
mime === 'application/octet-stream' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. on peut peut-être mettre A propos des Mime, on n'a pas des helpers cozy-client (model ?) qui peuvent servir ? |
||
? getMimetypeFromFilename(name.toLowerCase()) | ||
: mime | ||
const [type, subtype] = mimetype.split('/') | ||
if (collection[type]) { | ||
return type | ||
} | ||
if (type === 'application') { | ||
const existingType = subtype.match( | ||
Object.keys(mappingMimetypeSubtype).join('|') | ||
) | ||
return existingType ? mappingMimetypeSubtype[existingType[0]] : undefined | ||
} | ||
return undefined | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c'est dû au fait que le result peut être
[]
même pendant un loading ? Est-ce qu'on pourrait plutôt avoir plus simplement un tableau, ou null, si on est !loading ou loading ? C'est basé sur client.query donc on hérite du comportement de cozy-client ?