Skip to content

Commit f47f03b

Browse files
committed
Highlight search text
1 parent 39e7e25 commit f47f03b

3 files changed

Lines changed: 46 additions & 8 deletions

File tree

frontend/src/app/util.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,24 @@ export function toCamel(str: string) {
6565
return $1.toUpperCase().replace("-", "").replace("_", "");
6666
});
6767
}
68+
69+
/**
70+
* Highlights the query text within the target text by wrapping matches in <mark> tags
71+
* @param text The text to search in
72+
* @param query The search query to highlight
73+
* @returns HTML string with highlighted matches
74+
*/
75+
export function highlightMatch(text: string, query: string): string {
76+
if (!query || !text) {
77+
return text;
78+
}
79+
80+
// Escape special regex characters in the query
81+
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
82+
83+
// Create a case-insensitive regex to find all occurrences
84+
const regex = new RegExp(`(${escapedQuery})`, 'gi');
85+
86+
// Replace matches with highlighted version
87+
return text.replace(regex, '<mark>$1</mark>');
88+
}

frontend/src/components/SearchBox.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Fragment, useCallback, useEffect, useRef, useState } from "react";
33
import { Link, useNavigate, useSearchParams } from "react-router-dom";
44
import { get, getPaginated } from "../app/api";
55
import { theme } from "../app/mui";
6-
import { randomString, thingFromJsonProperties } from "../app/util";
6+
import { randomString, thingFromJsonProperties, highlightMatch } from "../app/util";
77
import Entity from "../model/Entity";
88
import Ontology from "../model/Ontology";
99
import { Suggest } from "../model/Suggest";
@@ -175,7 +175,11 @@ export default function SearchBox({
175175
setQuery(autocomplete.autosuggest);
176176
}}
177177
>
178-
{autocomplete.autosuggest}
178+
<span
179+
dangerouslySetInnerHTML={{
180+
__html: highlightMatch(autocomplete.autosuggest, query)
181+
}}
182+
/>
179183
</li>
180184
),
181185
};
@@ -216,9 +220,13 @@ export default function SearchBox({
216220
to={linkUrl}
217221
>
218222
<div className="flex justify-between">
219-
<div className="truncate flex-auto" title={name}>
220-
{name}
221-
</div>
223+
<div
224+
className="truncate flex-auto"
225+
title={name}
226+
dangerouslySetInnerHTML={{
227+
__html: highlightMatch(name, query)
228+
}}
229+
/>
222230
<div className="truncate flex-initial ml-2 text-right">
223231
<span
224232
className="mr-2 bg-link-default px-3 py-1 rounded-lg text-sm text-white uppercase"
@@ -277,9 +285,10 @@ export default function SearchBox({
277285
title={
278286
jumpToEntry.getName() || jumpToEntry.getOntologyId()
279287
}
280-
>
281-
{jumpToEntry.getName() || jumpToEntry.getOntologyId()}
282-
</div>
288+
dangerouslySetInnerHTML={{
289+
__html: highlightMatch(jumpToEntry.getName() || jumpToEntry.getOntologyId(), query)
290+
}}
291+
/>
283292
<div className="truncate flex-initial ml-2 text-right">
284293
<span className="bg-link-default px-3 py-1 rounded-lg text-sm text-white uppercase">
285294
{jumpToEntry.getOntologyId()}

frontend/src/index.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,3 +1361,11 @@ filter: gray;
13611361
margin-left: 0.25rem;
13621362
margin-right: 0.25rem;
13631363
}
1364+
1365+
/* Highlight matching text in autocomplete */
1366+
mark {
1367+
background-color: #fef08a; /* Tailwind yellow-200 */
1368+
color: inherit;
1369+
padding: 0;
1370+
font-weight: 600;
1371+
}

0 commit comments

Comments
 (0)