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
@gisheri
Copy link

gisheri commented Nov 2, 2022

The type on the regions is actually a bit off. And in React, any leading or trailing spaces are ignored, so you lose spaces between matches.. Here's adapted version that solved the problems for me for use in react.

export const highlightMatches = (inputText: string, regions: [number, number][] = []) => {
  const children: React.ReactNode[] = [];
  let nextUnhighlightedRegionStartingIndex = 0;

  regions.forEach((region, i) => {
    const lastRegionNextIndex = region[1] + 1;

    children.push(
      ...[
        inputText.substring(nextUnhighlightedRegionStartingIndex, region[0]).replace(' ', '\u00A0'),
        <span key={region + ' ' + i} className='fuse-highlight'>
          {inputText.substring(region[0], lastRegionNextIndex).replace(' ', '\u00A0')}
        </span>,
      ]
    );

    nextUnhighlightedRegionStartingIndex = lastRegionNextIndex;
  });

  children.push(inputText.substring(nextUnhighlightedRegionStartingIndex).replace(' ', '\u00A0'));

  return <>{children}</>;
};

@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