highlightFuseMatches.tsx 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import type {Fuse} from 'sentry/utils/fuzzySearch';
  2. type Match = Fuse.FuseResultMatch;
  3. type HighlightResult = {
  4. highlight: boolean;
  5. text: string;
  6. };
  7. type MatchResult = HighlightResult[];
  8. /**
  9. * Parses matches from fuse.js library
  10. *
  11. * Example `match` would be
  12. *
  13. * {
  14. * value: 'Authentication tokens allow you to perform actions',
  15. * indices: [[4, 6], [12, 13], [15, 16]],
  16. * }
  17. *
  18. * So:
  19. *
  20. * 00-03 -> not highlighted,
  21. * 04-06 -> highlighted,
  22. * 07-11 -> not highlighted,
  23. * 12-13 -> highlighted,
  24. * ...etc
  25. *
  26. * @param match The match object from fuse
  27. * @param match.value The entire string that has matches
  28. * @param match.indices Array of indices that represent matches
  29. */
  30. const getFuseMatches = ({value, indices}: Match): MatchResult => {
  31. if (value === undefined) {
  32. return [];
  33. }
  34. if (indices.length === 0) {
  35. return [{highlight: false, text: value}];
  36. }
  37. const strLength = value.length;
  38. const result: MatchResult = [];
  39. let prev = [0, -1];
  40. indices.forEach(([start, end]) => {
  41. // Unhighlighted string before the match
  42. const stringBeforeMatch = value.substring(prev[1] + 1, start);
  43. // Only add to result if non-empty string
  44. if (stringBeforeMatch) {
  45. result.push({
  46. highlight: false,
  47. text: stringBeforeMatch,
  48. });
  49. }
  50. // This is the matched string, which should be highlighted
  51. const matchedString = value.substring(start, end + 1);
  52. result.push({
  53. highlight: true,
  54. text: matchedString,
  55. });
  56. prev = [start, end];
  57. });
  58. // The rest of the string starting from the last match index
  59. const restOfString = value.substring(prev[1] + 1, strLength);
  60. // Only add to result if non-empty string
  61. if (restOfString) {
  62. result.push({highlight: false, text: restOfString});
  63. }
  64. return result;
  65. };
  66. /**
  67. * Given a match object from fuse.js, returns an array of components with
  68. * "highlighted" (bold) substrings.
  69. */
  70. const highlightFuseMatches = (matchObj: Match, Marker: React.ElementType = 'mark') =>
  71. getFuseMatches(matchObj).map(({highlight, text}, index) => {
  72. if (!text) {
  73. return null;
  74. }
  75. if (highlight) {
  76. return <Marker key={index}>{text}</Marker>;
  77. }
  78. return <span key={index}>{text}</span>;
  79. });
  80. export {getFuseMatches};
  81. export default highlightFuseMatches;