🔥 Plus de react-select pour la recherche de règles
parent
50d691b4af
commit
5dbb93eeca
|
@ -176,6 +176,7 @@
|
|||
"webpack-dev-middleware": "^3.4.0",
|
||||
"webpack-hot-middleware": "^2.24.2",
|
||||
"workbox-webpack-plugin": "^3.6.1",
|
||||
"worker-loader": "^2.0.0",
|
||||
"yaml-jest": "^1.0.5",
|
||||
"yaml-loader": "^0.5.0"
|
||||
},
|
||||
|
|
|
@ -1,50 +1,87 @@
|
|||
import withSitePaths from 'Components/utils/withSitePaths'
|
||||
import { encodeRuleName } from 'Engine/rules'
|
||||
import Fuse from 'fuse.js'
|
||||
import { compose, pick, sortBy } from 'ramda'
|
||||
import React, { useMemo, useRef, useState } from 'react'
|
||||
import { encodeRuleName, parentName } from 'Engine/rules.js'
|
||||
import { compose, pick, sortBy, take } from 'ramda'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import Highlighter from 'react-highlight-words'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Link, Redirect } from 'react-router-dom'
|
||||
import Select from 'react-select'
|
||||
import 'react-select/dist/react-select.css'
|
||||
import Worker from 'worker-loader!./SearchBar.worker.js'
|
||||
import { capitalise0 } from '../utils'
|
||||
|
||||
function SearchBar({
|
||||
const worker = new Worker()
|
||||
|
||||
let SearchBar = ({
|
||||
rules,
|
||||
showDefaultList,
|
||||
finally: finallyCallback,
|
||||
sitePaths
|
||||
}) {
|
||||
const [inputValue, setInputValue] = useState(null)
|
||||
}) => {
|
||||
const [input, setInput] = useState('')
|
||||
const [selectedOption, setSelectedOption] = useState(null)
|
||||
const inputElementRef = useRef()
|
||||
// This operation is expensive, we don't want to do it everytime we re-render, so we cache its result
|
||||
const fuse = useMemo(() => {
|
||||
const list = rules.map(
|
||||
pick(['title', 'espace', 'description', 'name', 'dottedName'])
|
||||
)
|
||||
const options = {
|
||||
keys: [
|
||||
{ name: 'name', weight: 0.3 },
|
||||
{ name: 'title', weight: 0.3 },
|
||||
{ name: 'espace', weight: 0.2 },
|
||||
{ name: 'description', weight: 0.2 }
|
||||
]
|
||||
}
|
||||
return new Fuse(list, options)
|
||||
}, [rules])
|
||||
const [results, setResults] = useState([])
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const renderOption = ({ title, dottedName }) => (
|
||||
<span>
|
||||
<Highlighter searchWords={[inputValue]} textToHighlight={title} />
|
||||
<span style={{ opacity: 0.6, fontSize: '75%', marginLeft: '.6em' }}>
|
||||
<Highlighter searchWords={[inputValue]} textToHighlight={dottedName} />
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
const filterOptions = (options, filter) => fuse.search(filter)
|
||||
useEffect(() => {
|
||||
worker.postMessage({
|
||||
rules: rules.map(
|
||||
pick(['title', 'espace', 'description', 'name', 'dottedName'])
|
||||
)
|
||||
})
|
||||
|
||||
worker.onmessage = ({ data: results }) => setResults(results)
|
||||
}, [])
|
||||
|
||||
let renderOptions = rules => {
|
||||
let options =
|
||||
(rules && sortBy(rule => rule.dottedName, rules)) || take(5)(results)
|
||||
return <ul>{options.map(option => renderOption(option))}</ul>
|
||||
}
|
||||
|
||||
let renderOption = option => {
|
||||
let { title, dottedName, name } = option
|
||||
return (
|
||||
<li
|
||||
key={dottedName}
|
||||
css={`
|
||||
padding: 0.4rem;
|
||||
border-radius: 0.3rem;
|
||||
:hover {
|
||||
background: var(--colour);
|
||||
color: var(--textColour);
|
||||
}
|
||||
:hover a {
|
||||
color: var(--textColour);
|
||||
}
|
||||
`}
|
||||
onClick={() => setSelectedOption(option)}>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: '300',
|
||||
fontSize: '85%',
|
||||
lineHeight: '.9em'
|
||||
}}>
|
||||
<Highlighter
|
||||
searchWords={[input]}
|
||||
textToHighlight={
|
||||
parentName(dottedName)
|
||||
? parentName(dottedName)
|
||||
.split(' . ')
|
||||
.map(capitalise0)
|
||||
.join(' - ')
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Link
|
||||
to={sitePaths.documentation.index + '/' + encodeRuleName(dottedName)}>
|
||||
<Highlighter
|
||||
searchWords={[input]}
|
||||
textToHighlight={title || capitalise0(name)}
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
if (selectedOption != null) {
|
||||
finallyCallback && finallyCallback()
|
||||
|
@ -61,37 +98,22 @@ function SearchBar({
|
|||
|
||||
return (
|
||||
<>
|
||||
<Select
|
||||
value={selectedOption && selectedOption.dottedName}
|
||||
onChange={setSelectedOption}
|
||||
onInputChange={setInputValue}
|
||||
valueKey="dottedName"
|
||||
labelKey="title"
|
||||
options={rules}
|
||||
filterOptions={filterOptions}
|
||||
optionRenderer={renderOption}
|
||||
<input
|
||||
type="text"
|
||||
value={input}
|
||||
placeholder={i18n.t('Entrez des mots clefs ici')}
|
||||
noResultsText={i18n.t('noresults', {
|
||||
onChange={e => {
|
||||
let input = e.target.value
|
||||
setInput(input)
|
||||
if (input.length > 2) worker.postMessage({ input })
|
||||
}}
|
||||
/>
|
||||
{input.length > 2 &&
|
||||
!results.length &&
|
||||
i18n.t('noresults', {
|
||||
defaultValue: "Nous n'avons rien trouvé…"
|
||||
})}
|
||||
ref={inputElementRef}
|
||||
/>
|
||||
{showDefaultList && !inputValue && (
|
||||
<ul>
|
||||
{sortBy(rule => rule.title, rules).map(rule => (
|
||||
<li key={rule.dottedName}>
|
||||
<Link
|
||||
to={
|
||||
sitePaths.documentation.index +
|
||||
'/' +
|
||||
encodeRuleName(rule.dottedName)
|
||||
}>
|
||||
{rule.title || capitalise0(rule.name)}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{showDefaultList && !input ? renderOptions(rules) : renderOptions()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import Fuse from 'fuse.js'
|
||||
|
||||
let searchWeights = [
|
||||
{
|
||||
name: 'name',
|
||||
weight: 0.3
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
weight: 0.3
|
||||
},
|
||||
{
|
||||
name: 'espace',
|
||||
weight: 0.2
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
weight: 0.2
|
||||
}
|
||||
]
|
||||
|
||||
let fuse = null
|
||||
onmessage = function(event) {
|
||||
if (event.data.rules)
|
||||
fuse = new Fuse(event.data.rules, {
|
||||
keys: searchWeights
|
||||
})
|
||||
|
||||
if (event.data.input) {
|
||||
let results = fuse.search(event.data.input)
|
||||
postMessage( results )
|
||||
}
|
||||
}
|
|
@ -27,7 +27,8 @@ module.exports.default = {
|
|||
publicodes: './source/sites/publi.codes/entry.js'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve('./dist/')
|
||||
path: path.resolve('./dist/'),
|
||||
globalObject: 'self'
|
||||
},
|
||||
plugins: [
|
||||
new EnvironmentPlugin({
|
||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -6736,7 +6736,7 @@ loader-runner@^2.4.0:
|
|||
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
|
||||
integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
|
||||
|
||||
loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
|
||||
loader-utils@1.2.3, loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
|
||||
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
|
||||
|
@ -11354,6 +11354,14 @@ worker-farm@^1.7.0:
|
|||
dependencies:
|
||||
errno "~0.1.7"
|
||||
|
||||
worker-loader@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-2.0.0.tgz#45fda3ef76aca815771a89107399ee4119b430ac"
|
||||
integrity sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==
|
||||
dependencies:
|
||||
loader-utils "^1.0.0"
|
||||
schema-utils "^0.4.0"
|
||||
|
||||
wrap-ansi@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
|
||||
|
|
Loading…
Reference in New Issue