From 27b6112cbafcb12b016c880753066a486c831d2d Mon Sep 17 00:00:00 2001 From: Daniel Lautzenheiser Date: Wed, 8 Nov 2023 14:42:18 -0500 Subject: [PATCH] docs: fix search character escaping --- .../components/layout/search/SearchModal.tsx | 180 +++++++++--------- 1 file changed, 91 insertions(+), 89 deletions(-) diff --git a/packages/docs/src/components/layout/search/SearchModal.tsx b/packages/docs/src/components/layout/search/SearchModal.tsx index 07e1b09e..e227731b 100644 --- a/packages/docs/src/components/layout/search/SearchModal.tsx +++ b/packages/docs/src/components/layout/search/SearchModal.tsx @@ -72,6 +72,10 @@ const StyledSuggestionSection = styled('div')` gap: 4px; `; +function escapeRegExp(str: string) { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + interface SearchModalProps { open: boolean; onClose: () => void; @@ -102,106 +106,104 @@ const SearchModal: FC = ({ open, onClose, searchablePages }) = const searchResults = useSearchScores(search, searchablePages); - const renderedResults = useMemo( - () => - searchResults?.length > 0 ? ( - [...Array(SEARCH_RESULTS_TO_SHOW)].map((_, index) => { - if (searchResults.length <= index) { - return; - } + const renderedResults = useMemo(() => { + const escapedSearch = escapeRegExp(search); - const result = searchResults[index]; - const { entry } = result; - let summary = entry.textContent; + return searchResults?.length > 0 ? ( + [...Array(SEARCH_RESULTS_TO_SHOW)].map((_, index) => { + if (searchResults.length <= index) { + return; + } - if (!result.isExactTitleMatch) { + const result = searchResults[index]; + const { entry } = result; + let summary = entry.textContent; + + if (!result.isExactTitleMatch) { + const match = new RegExp( + `(?:[\\s]+[^\\s]+){0,10}[\\s]*${escapeRegExp( + escapedSearch, + )}(?![^<>]*(([/"']|]]|\\b)>))[\\s]*(?:[^\\s]+\\s){0,25}`, + 'ig', + ).exec(entry.textContent); + if (match && match.length >= 1) { + summary = `...${match[0].trim()}...`; + } else { const match = new RegExp( - `(?:[\\s]+[^\\s]+){0,10}[\\s]*${search}(?![^<>]*(([/"']|]]|\\b)>))[\\s]*(?:[^\\s]+\\s){0,25}`, + `(?:[\\s]+[^\\s]+){0,10}[\\s]*(${escapedSearch + .split(' ') + .join('|')})(?![^<>]*(([/"']|]]|\\b)>))[\\s]*(?:[^\\s]+\\s){0,25}`, 'ig', ).exec(entry.textContent); if (match && match.length >= 1) { summary = `...${match[0].trim()}...`; - } else { - const match = new RegExp( - `(?:[\\s]+[^\\s]+){0,10}[\\s]*(${search - .split(' ') - .join('|')})(?![^<>]*(([/"']|]]|\\b)>))[\\s]*(?:[^\\s]+\\s){0,25}`, - 'ig', - ).exec(entry.textContent); - if (match && match.length >= 1) { - summary = `...${match[0].trim()}...`; - } } } + } - summary = summary?.replace( - new RegExp(`(${search.split(' ').join('|')})(?![^<>]*(([/"']|]]|\\b)>))`, 'ig'), - `$1`, - ); + summary = summary?.replace( + new RegExp(`(${escapedSearch.split(' ').join('|')})(?![^<>]*(([/"']|]]|\\b)>))`, 'ig'), + `$1`, + ); - return ( - - ); - }) - ) : isNotEmpty(search) ? ( - - No results found - - ) : ( - - - - Getting Started - - - Start With a Template - - Add to Your Site - - Configuration Options - - Collections - - - - Backends - - GitHub - Bitbucket - GitLab - - - - Customize Your CMS - - Custom Previews - Custom Widgets - Custom Icons - Custom Pages / Links - - - - Widgets - - String - Image - Datetime - Markdown - - - ), - [handleClose, search, searchResults, theme.palette.primary.main], - ); + return ( + + ); + }) + ) : isNotEmpty(escapedSearch) ? ( + + No results found + + ) : ( + + + + Getting Started + + Start With a Template + Add to Your Site + Configuration Options + Collections + + + + Backends + + GitHub + Bitbucket + GitLab + + + + Customize Your CMS + + Custom Previews + Custom Widgets + Custom Icons + Custom Pages / Links + + + + Widgets + + String + Image + Datetime + Markdown + + + ); + }, [handleClose, search, searchResults, theme.palette.primary.main]); return (