Skip to content
This repository was archived by the owner on Sep 9, 2024. It is now read-only.

Commit 27b6112

Browse files
committed
docs: fix search character escaping
1 parent bb0b709 commit 27b6112

File tree

1 file changed

+95
-93
lines changed

1 file changed

+95
-93
lines changed

packages/docs/src/components/layout/search/SearchModal.tsx

Lines changed: 95 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ const StyledSuggestionSection = styled('div')`
7272
gap: 4px;
7373
`;
7474

75+
function escapeRegExp(str: string) {
76+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
77+
}
78+
7579
interface SearchModalProps {
7680
open: boolean;
7781
onClose: () => void;
@@ -102,106 +106,104 @@ const SearchModal: FC<SearchModalProps> = ({ open, onClose, searchablePages }) =
102106

103107
const searchResults = useSearchScores(search, searchablePages);
104108

105-
const renderedResults = useMemo(
106-
() =>
107-
searchResults?.length > 0 ? (
108-
[...Array<unknown>(SEARCH_RESULTS_TO_SHOW)].map((_, index) => {
109-
if (searchResults.length <= index) {
110-
return;
111-
}
112-
113-
const result = searchResults[index];
114-
const { entry } = result;
115-
let summary = entry.textContent;
116-
117-
if (!result.isExactTitleMatch) {
109+
const renderedResults = useMemo(() => {
110+
const escapedSearch = escapeRegExp(search);
111+
112+
return searchResults?.length > 0 ? (
113+
[...Array<unknown>(SEARCH_RESULTS_TO_SHOW)].map((_, index) => {
114+
if (searchResults.length <= index) {
115+
return;
116+
}
117+
118+
const result = searchResults[index];
119+
const { entry } = result;
120+
let summary = entry.textContent;
121+
122+
if (!result.isExactTitleMatch) {
123+
const match = new RegExp(
124+
`(?:[\\s]+[^\\s]+){0,10}[\\s]*${escapeRegExp(
125+
escapedSearch,
126+
)}(?![^<>]*(([/"']|]]|\\b)>))[\\s]*(?:[^\\s]+\\s){0,25}`,
127+
'ig',
128+
).exec(entry.textContent);
129+
if (match && match.length >= 1) {
130+
summary = `...${match[0].trim()}...`;
131+
} else {
118132
const match = new RegExp(
119-
`(?:[\\s]+[^\\s]+){0,10}[\\s]*${search}(?![^<>]*(([/"']|]]|\\b)>))[\\s]*(?:[^\\s]+\\s){0,25}`,
133+
`(?:[\\s]+[^\\s]+){0,10}[\\s]*(${escapedSearch
134+
.split(' ')
135+
.join('|')})(?![^<>]*(([/"']|]]|\\b)>))[\\s]*(?:[^\\s]+\\s){0,25}`,
120136
'ig',
121137
).exec(entry.textContent);
122138
if (match && match.length >= 1) {
123139
summary = `...${match[0].trim()}...`;
124-
} else {
125-
const match = new RegExp(
126-
`(?:[\\s]+[^\\s]+){0,10}[\\s]*(${search
127-
.split(' ')
128-
.join('|')})(?![^<>]*(([/"']|]]|\\b)>))[\\s]*(?:[^\\s]+\\s){0,25}`,
129-
'ig',
130-
).exec(entry.textContent);
131-
if (match && match.length >= 1) {
132-
summary = `...${match[0].trim()}...`;
133-
}
134140
}
135141
}
136-
137-
summary = summary?.replace(
138-
new RegExp(`(${search.split(' ').join('|')})(?![^<>]*(([/"']|]]|\\b)>))`, 'ig'),
139-
`<strong style="color: ${theme.palette.primary.main}">$1</strong>`,
140-
);
141-
142-
return (
143-
<SearchResult
144-
key={`result-${entry.url}`}
145-
entry={entry}
146-
summary={summary}
147-
onClick={handleClose}
148-
/>
149-
);
150-
})
151-
) : isNotEmpty(search) ? (
152-
<Typography
153-
variant="h3"
154-
component="div"
155-
key="no-results"
156-
sx={{ width: '100%', textAlign: 'center', marginTop: '16px' }}
157-
>
158-
No results found
159-
</Typography>
160-
) : (
161-
<StyledSuggestions>
162-
<StyledSuggestionSection>
163-
<Typography variant="h3" sx={{ marginBottom: '4px' }}>
164-
Getting Started
165-
</Typography>
166-
<SuggestionLink href="/docs/start-with-a-template">
167-
Start With a Template
168-
</SuggestionLink>
169-
<SuggestionLink href="/docs/add-to-your-site">Add to Your Site</SuggestionLink>
170-
<SuggestionLink href="/docs/configuration-options">
171-
Configuration Options
172-
</SuggestionLink>
173-
<SuggestionLink href="/docs/collection-overview">Collections</SuggestionLink>
174-
</StyledSuggestionSection>
175-
<StyledSuggestionSection>
176-
<Typography variant="h3" sx={{ marginBottom: '4px' }}>
177-
Backends
178-
</Typography>
179-
<SuggestionLink href="/docs/github-backend">GitHub</SuggestionLink>
180-
<SuggestionLink href="/docs/bitbucket-backend">Bitbucket</SuggestionLink>
181-
<SuggestionLink href="/docs/gitlab-backend">GitLab</SuggestionLink>
182-
</StyledSuggestionSection>
183-
<StyledSuggestionSection>
184-
<Typography variant="h3" sx={{ marginBottom: '4px' }}>
185-
Customize Your CMS
186-
</Typography>
187-
<SuggestionLink href="/docs/custom-previews">Custom Previews</SuggestionLink>
188-
<SuggestionLink href="/docs/custom-widgets">Custom Widgets</SuggestionLink>
189-
<SuggestionLink href="/docs/custom-icons">Custom Icons</SuggestionLink>
190-
<SuggestionLink href="/docs/additional-links">Custom Pages / Links</SuggestionLink>
191-
</StyledSuggestionSection>
192-
<StyledSuggestionSection>
193-
<Typography variant="h3" sx={{ marginBottom: '4px' }}>
194-
Widgets
195-
</Typography>
196-
<SuggestionLink href="/docs/widget-string">String</SuggestionLink>
197-
<SuggestionLink href="/docs/widget-image">Image</SuggestionLink>
198-
<SuggestionLink href="/docs/widget-datetime">Datetime</SuggestionLink>
199-
<SuggestionLink href="/docs/widget-markdown">Markdown</SuggestionLink>
200-
</StyledSuggestionSection>
201-
</StyledSuggestions>
202-
),
203-
[handleClose, search, searchResults, theme.palette.primary.main],
204-
);
142+
}
143+
144+
summary = summary?.replace(
145+
new RegExp(`(${escapedSearch.split(' ').join('|')})(?![^<>]*(([/"']|]]|\\b)>))`, 'ig'),
146+
`<strong style="color: ${theme.palette.primary.main}">$1</strong>`,
147+
);
148+
149+
return (
150+
<SearchResult
151+
key={`result-${entry.url}`}
152+
entry={entry}
153+
summary={summary}
154+
onClick={handleClose}
155+
/>
156+
);
157+
})
158+
) : isNotEmpty(escapedSearch) ? (
159+
<Typography
160+
variant="h3"
161+
component="div"
162+
key="no-results"
163+
sx={{ width: '100%', textAlign: 'center', marginTop: '16px' }}
164+
>
165+
No results found
166+
</Typography>
167+
) : (
168+
<StyledSuggestions>
169+
<StyledSuggestionSection>
170+
<Typography variant="h3" sx={{ marginBottom: '4px' }}>
171+
Getting Started
172+
</Typography>
173+
<SuggestionLink href="/docs/start-with-a-template">Start With a Template</SuggestionLink>
174+
<SuggestionLink href="/docs/add-to-your-site">Add to Your Site</SuggestionLink>
175+
<SuggestionLink href="/docs/configuration-options">Configuration Options</SuggestionLink>
176+
<SuggestionLink href="/docs/collection-overview">Collections</SuggestionLink>
177+
</StyledSuggestionSection>
178+
<StyledSuggestionSection>
179+
<Typography variant="h3" sx={{ marginBottom: '4px' }}>
180+
Backends
181+
</Typography>
182+
<SuggestionLink href="/docs/github-backend">GitHub</SuggestionLink>
183+
<SuggestionLink href="/docs/bitbucket-backend">Bitbucket</SuggestionLink>
184+
<SuggestionLink href="/docs/gitlab-backend">GitLab</SuggestionLink>
185+
</StyledSuggestionSection>
186+
<StyledSuggestionSection>
187+
<Typography variant="h3" sx={{ marginBottom: '4px' }}>
188+
Customize Your CMS
189+
</Typography>
190+
<SuggestionLink href="/docs/custom-previews">Custom Previews</SuggestionLink>
191+
<SuggestionLink href="/docs/custom-widgets">Custom Widgets</SuggestionLink>
192+
<SuggestionLink href="/docs/custom-icons">Custom Icons</SuggestionLink>
193+
<SuggestionLink href="/docs/additional-links">Custom Pages / Links</SuggestionLink>
194+
</StyledSuggestionSection>
195+
<StyledSuggestionSection>
196+
<Typography variant="h3" sx={{ marginBottom: '4px' }}>
197+
Widgets
198+
</Typography>
199+
<SuggestionLink href="/docs/widget-string">String</SuggestionLink>
200+
<SuggestionLink href="/docs/widget-image">Image</SuggestionLink>
201+
<SuggestionLink href="/docs/widget-datetime">Datetime</SuggestionLink>
202+
<SuggestionLink href="/docs/widget-markdown">Markdown</SuggestionLink>
203+
</StyledSuggestionSection>
204+
</StyledSuggestions>
205+
);
206+
}, [handleClose, search, searchResults, theme.palette.primary.main]);
205207

206208
return (
207209
<StyledDialog open={open} onClose={handleClose} fullScreen={fullScreen} fullWidth>

0 commit comments

Comments
 (0)