forked from ddrilling/asb_cloud_front
Добавлена подсветка совпадения в поиске
This commit is contained in:
parent
e773943b61
commit
685191484a
@ -1,8 +1,7 @@
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { memo, ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { DefaultOptionType } from 'antd/lib/select'
|
||||
import { AutoComplete } from 'antd'
|
||||
import { BaseSelectRef } from 'rc-select'
|
||||
import { AutoComplete } from 'antd'
|
||||
import { join } from 'path'
|
||||
|
||||
import { useWell } from '@asb/context'
|
||||
@ -20,29 +19,18 @@ const transliterationTable = {
|
||||
'z': 'я', 'x': 'ч', 'c': 'с', 'v': 'м', 'b': 'и', 'n': 'т', 'm': 'ь', ',': 'б', '.': 'ю', '<': 'б', '>': 'ю',
|
||||
}
|
||||
|
||||
const transliterateToRu = (text: string): string => {
|
||||
let out = text.toLowerCase()
|
||||
Object.entries(transliterationTable).map(([en, ru]) => out = out.replaceAll(en, ru))
|
||||
return out
|
||||
type OptionType = {
|
||||
value: string
|
||||
label: ReactNode
|
||||
}
|
||||
|
||||
const transliterateToEn = (text: string): string => {
|
||||
let out = text.toLowerCase()
|
||||
Object.entries(transliterationTable).map(([en, ru]) => out = out.replaceAll(ru, en))
|
||||
return out
|
||||
}
|
||||
const transliterateToRu = (text: string) => Object.entries(transliterationTable).reduce((out, [en, ru]) => out.replaceAll(en, ru), text.toLowerCase())
|
||||
const transliterateToEn = (text: string) => Object.entries(transliterationTable).reduce((out, [en, ru]) => out.replaceAll(ru, en), text.toLowerCase())
|
||||
const applyVars = (route: string, vars?: object): string => !vars ? route :
|
||||
Object.entries(vars).reduce((out, [key, value]) => out.replaceAll(`{${key}}`, value), route)
|
||||
|
||||
const applyVars = (route: string, vars?: Record<string, any>): string => {
|
||||
if (!vars) return route
|
||||
let out = route
|
||||
Object.entries(vars).forEach(([key, value]) => {
|
||||
out = out.replaceAll(`{${key}}`, value)
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
const makeOptions = (items: PrivateWellMenuItem[], vars?: Record<string, any>): DefaultOptionType[] => {
|
||||
const out: DefaultOptionType[] = []
|
||||
const makeOptions = (items: PrivateWellMenuItem[], vars?: object): OptionType[] => {
|
||||
const out: OptionType[] = []
|
||||
items.forEach((item) => {
|
||||
if (!hasPermission(item.permissions)) return
|
||||
out.push({
|
||||
@ -60,9 +48,23 @@ const makeOptions = (items: PrivateWellMenuItem[], vars?: Record<string, any>):
|
||||
return out
|
||||
}
|
||||
|
||||
const makeFindInString = (text: string) => {
|
||||
const searchText = text.toLowerCase()
|
||||
const toRu = transliterateToRu(searchText)
|
||||
const toEn = transliterateToEn(searchText)
|
||||
|
||||
return (sourceText: string) => {
|
||||
const text = sourceText.toLowerCase()
|
||||
let idx = text.indexOf(searchText)
|
||||
if (idx < 0 && (idx = text.indexOf(toRu)) < 0 && (idx = text.indexOf(toEn)) < 0) return false
|
||||
return { from: idx, to: idx + searchText.length }
|
||||
}
|
||||
}
|
||||
|
||||
export const FastRunMenu = memo(() => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [value, setValue] = useState<string | null>()
|
||||
const [results, setResults] = useState<OptionType[]>([])
|
||||
const ref = useRef<BaseSelectRef | null>(null)
|
||||
|
||||
const [well] = useWell()
|
||||
@ -97,6 +99,32 @@ export const FastRunMenu = memo(() => {
|
||||
onClose()
|
||||
}, [onClose])
|
||||
|
||||
const onSearch = useCallback((text: string) => {
|
||||
if (text.trim() === '') {
|
||||
setResults(options)
|
||||
return
|
||||
}
|
||||
const findInString = makeFindInString(text)
|
||||
const results = options.map((option) => {
|
||||
const label = String(option.label)
|
||||
const idx = findInString(label.toLowerCase())
|
||||
if (!idx) return findInString(option.value.toLowerCase()) ? option : false
|
||||
|
||||
return {
|
||||
...option,
|
||||
label: <>
|
||||
{label.slice(0, idx.from)}
|
||||
<span className={'fast-run-menu-text-found'}>
|
||||
{label.slice(idx.from, idx.to)}
|
||||
</span>
|
||||
{label.slice(idx.to)}
|
||||
</>
|
||||
}
|
||||
}).filter(Boolean) as OptionType[]
|
||||
|
||||
setResults(results)
|
||||
}, [options])
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (event: KeyboardEvent) => {
|
||||
if (event.altKey && event.code === 'KeyA')
|
||||
@ -114,14 +142,7 @@ export const FastRunMenu = memo(() => {
|
||||
ref.current?.scrollTo(0)
|
||||
}, [isOpen])
|
||||
|
||||
const onFilter = (text: string, option: DefaultOptionType | undefined) => {
|
||||
if (!option) return false
|
||||
const search = (String(option.label).replaceAll(' > ', '') + String(option.value).replaceAll('/', '')).toLowerCase()
|
||||
if (search.includes(text.toLowerCase())) return true
|
||||
if (search.includes(transliterateToRu(text))) return true
|
||||
if (search.includes(transliterateToEn(text))) return true
|
||||
return false
|
||||
}
|
||||
useEffect(() => onSearch(''), [onSearch])
|
||||
|
||||
return isOpen ? (
|
||||
<div className={'fast-run-menu'}>
|
||||
@ -129,13 +150,13 @@ export const FastRunMenu = memo(() => {
|
||||
ref={ref}
|
||||
autoFocus
|
||||
style={{ width: '100%' }}
|
||||
options={options}
|
||||
options={results}
|
||||
onBlur={onClose}
|
||||
onChange={setValue}
|
||||
onSearch={onSearch}
|
||||
placeholder={'Введите название страницы...'}
|
||||
onSelect={onTextChanged}
|
||||
value={value}
|
||||
filterOption={onFilter}
|
||||
/>
|
||||
</div>
|
||||
) : <></>
|
||||
|
@ -7,3 +7,7 @@
|
||||
border-radius: 5px;
|
||||
width: 50vw;
|
||||
}
|
||||
|
||||
.fast-run-menu-text-found {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user