Skip to content

Commit 78f3746

Browse files
committed
Updates to search page
1 parent d550b7c commit 78f3746

19 files changed

Lines changed: 682 additions & 100 deletions

File tree

‎_TODO.md‎

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -217,25 +217,4 @@ All code samples in this article are licensed under the MIT License. Feel free t
217217

218218
## Search
219219

220-
- How do we handle the keywords in the long form / pdf files from a search perspective? Can we return the result in the search results if the short form content is not returned in the search results, and highlight it somehow in the search results to show that it is gated content? And clicking on its link takes the user to the Download page for that item?
221-
222-
- Search box in header should have blue outline, not highlight
223-
224-
- going to the search page with a query appended gives a 404 error
225-
226-
- Search box on the search page is not working
227-
228-
```json
229-
[{
230-
"id": "unique-id-from-crawler",
231-
"score": 0.985,
232-
"content": {
233-
"text": "The full body content extracted from the crawled article..."
234-
},
235-
"metadata": {
236-
"url": "https://example.com",
237-
"title": "Introduction to Vector Search",
238-
"description": "Learn the basics of how vector databases work."
239-
}
240-
}]
241-
```
220+
- The modal on the search page should be different than returning results - maybe the page results should update as you type

‎src/components/Header/index.css‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@
9898
background-color: var(--color-page-offset);
9999
content: '';
100100
display: block;
101-
inset: 0;
101+
height: 100%;
102+
left: 50%;
102103
position: absolute;
104+
top: 0;
105+
transform: translateX(-50%);
106+
width: 100vw;
103107
z-index: var(--z-underlay);
104108
}

‎src/components/Icon/index.astro‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export type Props = {
66
icon: string
77
/**
88
* The name of a Tailwind theme color CSS variable without the `--theme-color-` prefix.
9-
* Example: 'primary', 'text-content', 'green-500'. Defaults to "inherit". Icon files
9+
* Example: 'primary', 'content', 'green-500'. Defaults to "inherit". Icon files
1010
* apply the value to their stroke and fill attributes as appropriate for the image.
1111
*/
1212
color?: string

‎src/components/Search/SearchBar/client/__tests__/index.spec.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ describe('SearchBar web component', () => {
178178
expect(titleRow?.className).toContain('text-page-inverse')
179179

180180
const arrow = firstLink?.querySelector('svg') as SVGElement | null
181-
expect(arrow?.className.baseVal).toContain('text-gray-400')
181+
expect(arrow?.getAttribute('class')).toContain('text-gray-400')
182182
})
183183

184184
vi.useRealTimers()

‎src/components/Search/SearchBar/client/__tests__/results.spec.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,14 @@ describe('SearchBar result helpers', () => {
4545
it('returns plain text when there is no highlightable query term', () => {
4646
expect(highlightSearchText('Introduction to Vector Search', 'v')).toBe('Introduction to Vector Search')
4747
})
48+
49+
it('highlights terms containing regex metacharacters without using regex semantics', () => {
50+
const highlighted = highlightSearchText('C++ patterns for teams', 'c++')
51+
52+
expect(Array.isArray(highlighted)).toBe(true)
53+
54+
const highlightedParts = highlighted as Array<string | { values?: unknown[] }>
55+
expect(highlightedParts.some(part => typeof part === 'string' && part.includes(' patterns for teams'))).toBe(true)
56+
expect(highlightedParts.some(part => typeof part !== 'string')).toBe(true)
57+
})
4858
})

‎src/components/Search/SearchBar/client/results.ts‎

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,34 @@ interface HighlightSearchTextOptions {
99
highlightClassName?: string
1010
}
1111

12-
const escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
13-
1412
const getHighlightTerms = (query: string): string[] => {
1513
return [...new Set(query.trim().split(/\s+/).filter(term => term.length >= MIN_HIGHLIGHT_TERM_LENGTH))].sort(
1614
(left, right) => right.length - left.length
1715
)
1816
}
1917

18+
const findNextHighlightMatch = (
19+
normalizedText: string,
20+
normalizedTerms: string[],
21+
startIndex: number
22+
): { index: number; term: string } | null => {
23+
let nextMatch: { index: number; term: string } | null = null
24+
25+
for (const term of normalizedTerms) {
26+
const index = normalizedText.indexOf(term, startIndex)
27+
28+
if (index < 0) {
29+
continue
30+
}
31+
32+
if (!nextMatch || index < nextMatch.index || (index === nextMatch.index && term.length > nextMatch.term.length)) {
33+
nextMatch = { index, term }
34+
}
35+
}
36+
37+
return nextMatch
38+
}
39+
2040
export const getSearchResultDisplayPath = (url: string): string => {
2141
if (!url) {
2242
return SEARCH_RESULT_FALLBACK_PATH
@@ -46,24 +66,26 @@ export const highlightSearchText = (
4666
return text
4767
}
4868

49-
const pattern = new RegExp(`(${highlightTerms.map(term => escapeRegExp(term)).join('|')})`, 'gi')
5069
const parts: HighlightChunk[] = []
70+
const normalizedText = text.toLocaleLowerCase()
71+
const normalizedTerms = highlightTerms.map(term => term.toLocaleLowerCase())
5172
let cursor = 0
5273

53-
for (const match of text.matchAll(pattern)) {
54-
const matchText = match[0]
55-
const matchIndex = match.index ?? -1
74+
while (cursor < text.length) {
75+
const nextMatch = findNextHighlightMatch(normalizedText, normalizedTerms, cursor)
5676

57-
if (!matchText || matchIndex < 0) {
58-
continue
77+
if (!nextMatch) {
78+
break
5979
}
6080

61-
if (matchIndex > cursor) {
62-
parts.push(text.slice(cursor, matchIndex))
81+
if (nextMatch.index > cursor) {
82+
parts.push(text.slice(cursor, nextMatch.index))
6383
}
6484

85+
const matchText = text.slice(nextMatch.index, nextMatch.index + nextMatch.term.length)
86+
6587
parts.push(html`<mark class=${highlightClassName}>${matchText}</mark>`)
66-
cursor = matchIndex + matchText.length
88+
cursor = nextMatch.index + matchText.length
6789
}
6890

6991
if (parts.length === 0) {
194 KB
Loading
349 KB
Loading
304 KB
Loading
422 KB
Loading

0 commit comments

Comments
 (0)