diff --git a/src/components/Highlight.tsx b/src/components/Highlight.tsx index 083630d..820a294 100644 --- a/src/components/Highlight.tsx +++ b/src/components/Highlight.tsx @@ -1,18 +1,23 @@ import * as React from 'react' -export function Highlight(props: { text: string; match?: RegExp }) { +export function Highlight(props: { text: string; match?: RegExp | string }) { const { text, match } = props const $match = React.useMemo(() => { - if (match instanceof RegExp) { - if (match.flags.includes('g')) return match - return new RegExp(match.source, 'g' + match.flags) + if (match) { + if (match instanceof RegExp) { + if (match.flags.includes('g')) return match + return new RegExp(match.source, 'g' + match.flags) + } + return new RegExp(match, 'g') } return null }, [match]) - if (!($match instanceof RegExp) || typeof text !== 'string') return <>{text} + if (!$match) return <>{text} - const matchedPieces = Array.from(text.matchAll($match)).map(([text]) => text) + const matchedPieces = Array.from(text.matchAll($match)).map( + ([text, highlightText = text]) => highlightText, + ) const preservedPieces = text.split($match) const content = [] diff --git a/src/components/searchModes/fuzzyMode.tsx b/src/components/searchModes/fuzzyMode.tsx index 9aa7f8d..1bcbe9a 100644 --- a/src/components/searchModes/fuzzyMode.tsx +++ b/src/components/searchModes/fuzzyMode.tsx @@ -2,7 +2,6 @@ import * as React from 'react' import { cx } from 'utils/cx' import { hasUpperCase } from 'utils/general' import { ModeShape } from '.' -import { Highlight } from '../Highlight' export const fuzzyMode: ModeShape = { getSearchParams(searchKey) { @@ -26,13 +25,13 @@ export const fuzzyMode: ModeShape = { let progress = 0 return chunks.map((chunk, index, chunks) => { const chunkIndexes = indexes.filter(i => i >= progress && i < chunk.length + progress) - const regexp = chunkIndexes.length - ? new RegExp(chunkIndexes.map(i => `(?<=^.{${i - progress}}).`).join('|')) - : undefined + + const $indexes = chunkIndexes.length ? chunkIndexes.map(i => i - progress) : undefined + progress += chunk.length + 1 return ( - + ) }) @@ -61,3 +60,19 @@ function fuzzyMatchIndexes(input: string, sample: string, shift: number = 0) { } return indexes } + +function Highlight(props: { text: string; match?: number[] }) { + const { text, match } = props + + if (!match?.length) return <>{text} + + return ( + <> + {text + .split('') + .map((char, i) => + match.includes(i) ? {char} : {char}, + )} + + ) +} diff --git a/src/components/searchModes/regexMode.tsx b/src/components/searchModes/regexMode.tsx index 6de1b3b..bfd3b39 100644 --- a/src/components/searchModes/regexMode.tsx +++ b/src/components/searchModes/regexMode.tsx @@ -1,29 +1,22 @@ import * as React from 'react' import { cx } from 'utils/cx' -import { hasUpperCase, isValidRegexpSource, searchKeyToRegexp } from 'utils/general' +import { searchKeyToRegexp } from 'utils/general' import { ModeShape } from '.' import { Highlight } from '../Highlight' export const regexMode: ModeShape = { getSearchParams(searchKey) { - if (!searchKey) return null - const regexp = searchKeyToRegexp(searchKey) if (regexp) { - const matchNode = (node: TreeNode) => regexp.test(node.name) - return { - matchNode, + matchNode: node => regexp.test(node.name), } } return null }, renderNodeLabelText(node, searchKey) { - const regex = - searchKey && isValidRegexpSource(searchKey) - ? new RegExp(searchKey, 'g' + (hasUpperCase(searchKey) ? '' : 'i')) - : undefined + const regex = searchKeyToRegexp(searchKey) || undefined const { name } = node return name.includes('/') ? ( name.split('/').map((chunk, index, chunks) => ( diff --git a/src/utils/general.ts b/src/utils/general.ts index 9bafe4e..9d31607 100644 --- a/src/utils/general.ts +++ b/src/utils/general.ts @@ -146,13 +146,7 @@ export async function JSONRequest(url: string, data: any, extra: RequestInit = { export function searchKeyToRegexp(searchKey: string) { if (!searchKey) return null - try { - const flags = hasUpperCase(searchKey) ? '' : 'i' - // case-sensitive when searchKey contains uppercase char - return new RegExp(searchKey, flags) - } catch (err) { - return null // matching nothing if failed transforming regexp - } + return safeRegexp(searchKey, hasUpperCase(searchKey) ? 'g' : 'gi') } export function hasUpperCase(input: string) { @@ -168,15 +162,18 @@ export async function renderReact(element: ReactElement) { }) } -export function isValidRegexpSource(source: string) { +export function safeRegexp(pattern: string | RegExp, flags?: string | undefined) { try { - new RegExp(source) - return true + return new RegExp(pattern, flags) } catch (err) { - return false + return null } } +export function isValidRegexpSource(source: string) { + return Boolean(safeRegexp(source)) +} + export function withEffect any>( method: Method, effect: (payload: ReturnType) => void,