Skip to content

Instantly share code, notes, and snippets.

@evenfrost
Last active March 3, 2025 11:45
Show Gist options
  • Save evenfrost/1ba123656ded32fb7a0cd4651efd4db0 to your computer and use it in GitHub Desktop.
Save evenfrost/1ba123656ded32fb7a0cd4651efd4db0 to your computer and use it in GitHub Desktop.
Fuse.js with highlight
const highlight = (fuseSearchResult: any, highlightClassName: string = 'highlight') => {
const set = (obj: object, path: string, value: any) => {
const pathValue = path.split('.');
let i;
for (i = 0; i < pathValue.length - 1; i++) {
obj = obj[pathValue[i]];
}
obj[pathValue[i]] = value;
};
const generateHighlightedText = (inputText: string, regions: number[] = []) => {
let content = '';
let nextUnhighlightedRegionStartingIndex = 0;
regions.forEach(region => {
const lastRegionNextIndex = region[1] + 1;
content += [
inputText.substring(nextUnhighlightedRegionStartingIndex, region[0]),
`<span class="${highlightClassName}">`,
inputText.substring(region[0], lastRegionNextIndex),
'</span>',
].join('');
nextUnhighlightedRegionStartingIndex = lastRegionNextIndex;
});
content += inputText.substring(nextUnhighlightedRegionStartingIndex);
return content;
};
return fuseSearchResult
.filter(({ matches }: any) => matches && matches.length)
.map(({ item, matches }: any) => {
const highlightedItem = { ...item };
matches.forEach((match: any) => {
set(highlightedItem, match.key, generateHighlightedText(match.value, match.indices));
});
return highlightedItem;
});
};
// usage:
const res = highlight(fuse.search(text)); // array of items with highlighted fields
@dieghernan
Copy link

dieghernan commented Mar 3, 2025

This is great, many thanks!

My two cents: While implementing on jekyll I saw a couple of issues:

  • Sometimes regions are unordered: (e.g. [[3,4],[1,2]]), that breaks the layout. I think this happens when useExtendedSearch: true.

  • My database has strings with html, and I saw that if the class is captured on the search (e.g, searching for myclass would hit on "<div class='myclass'>Some text</div>" the layout breaks as well (would result in something like <div class='<span class="${highlightClassName}">myclass</span>'>Some text</div>
    I solved both as follows:

const generateHighlightedText = (inputText: string, regions: number[] = []) => {
    let content = '';
    let nextUnhighlightedRegionStartingIndex = 0;

       // Sort regions to avoid breaking layout
        regions = regions.sort(function(a, b) {
         return a[0] - b[0];
      });

      regions.forEach(region => {
		if(nextUnhighlightedRegionStartingIndex > region[0]){
                   return content;
		}
      const lastRegionNextIndex = region[1] + 1;

          // Try escape html in database
            const theInput = inputText.substring(region[0]);
            const indexIniTag = theInput.indexOf("<");
            const indexCloseTag = theInput.indexOf(">");
           // Case of first '>' before first '<', we are inside of '<...>' 
           if (indexCloseTag < indexIniTag) {
                return content;
             }
           // Corner case when we're in the last html tag of the string, since indexIniTagCase not found and then -1
            if (indexIniTag == -1 && indexCloseTag > 0) {
                 return content;
             }

      content += [
        inputText.substring(nextUnhighlightedRegionStartingIndex, region[0]),
        `<span class="${highlightClassName}">`,
        inputText.substring(region[0], lastRegionNextIndex),
        '</span>',
      ].join('');

      nextUnhighlightedRegionStartingIndex = lastRegionNextIndex;
    });

Quite newby in Javascript but this did the work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment