diff --git a/services/web/modules/full-project-search/frontend/js/components/matched-hit-highlight.tsx b/services/web/modules/full-project-search/frontend/js/components/matched-hit-highlight.tsx index 8f7122cb22..84f22eed67 100644 --- a/services/web/modules/full-project-search/frontend/js/components/matched-hit-highlight.tsx +++ b/services/web/modules/full-project-search/frontend/js/components/matched-hit-highlight.tsx @@ -1,42 +1,13 @@ import React, { FC, useMemo } from 'react' import { Hit } from '../util/search-snapshot' +import { getMatchedHitSnippet } from '../util/matched-hit-snippet' export const MatchedHitHighlight: FC<{ text: string; hit: Hit }> = ({ text, hit, }) => { const parts = useMemo(() => { - let before = text.substring(0, hit.matchIndex).trimStart() - const match = text.substring(hit.matchIndex, hit.matchIndex + hit.length) - let after = text.substring(hit.matchIndex + hit.length).trimEnd() - - // reduce the prefix to a sensible size before trimming - if (before.length > 250) { - before = before.substring(before.length - 250) - } - - while (before.length > 10) { - const replacement = before.replace(/^\S+\s+/, '') - if (before.length === replacement.length) { - break - } - before = replacement - } - - // reduce the suffix to a sensible size before trimming - if (after.length > 250) { - after = after.substring(0, 250) - } - - while (after.length > 100) { - const replacement = after.replace(/\s+\S+$/, '') - if (after.length === replacement.length) { - break - } - after = replacement - } - - return { before, match, after } + return getMatchedHitSnippet(text, hit.matchIndex, hit.length) }, [hit, text]) return ( diff --git a/services/web/modules/full-project-search/frontend/js/util/matched-hit-snippet.ts b/services/web/modules/full-project-search/frontend/js/util/matched-hit-snippet.ts new file mode 100644 index 0000000000..4df26ac2c8 --- /dev/null +++ b/services/web/modules/full-project-search/frontend/js/util/matched-hit-snippet.ts @@ -0,0 +1,35 @@ +export const getMatchedHitSnippet = ( + text: string, + matchIndex: number, + length: number +) => { + let before = text.substring(0, matchIndex).trimStart() + const match = text.substring(matchIndex, matchIndex + length) + let after = text.substring(matchIndex + length).trimEnd() + + if (before.length > 250) { + before = before.substring(before.length - 250) + } + + while (before.length > 10) { + const replacement = before.replace(/^\S+\s+/, '') + if (before.length === replacement.length) { + break + } + before = replacement + } + + if (after.length > 250) { + after = after.substring(0, 250) + } + + while (after.length > 100) { + const replacement = after.replace(/\s+\S+$/, '') + if (after.length === replacement.length) { + break + } + after = replacement + } + + return { before, match, after } +} diff --git a/services/web/modules/full-project-search/test/frontend/util/matched-hit-snippet.test.ts b/services/web/modules/full-project-search/test/frontend/util/matched-hit-snippet.test.ts new file mode 100644 index 0000000000..56273e9af3 --- /dev/null +++ b/services/web/modules/full-project-search/test/frontend/util/matched-hit-snippet.test.ts @@ -0,0 +1,24 @@ +import { expect } from 'chai' +import { getMatchedHitSnippet } from '../../../frontend/js/util/matched-hit-snippet' + +describe('getMatchedHitSnippet', function () { + it('builds before/match/after for a simple match', function () { + const text = 'alpha beta gamma delta' + const snippet = getMatchedHitSnippet(text, 6, 4) + + expect(snippet).to.deep.equal({ + before: 'alpha ', + match: 'beta', + after: ' gamma delta', + }) + }) + + it('caps raw prefix/suffix size when surrounding text is very long', function () { + const text = `${'a'.repeat(300)}needle${'b'.repeat(300)}` + const snippet = getMatchedHitSnippet(text, 300, 6) + + expect(snippet.match).to.equal('needle') + expect(snippet.before.length).to.equal(250) + expect(snippet.after.length).to.equal(250) + }) +})