-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Components for displaying shortened text with highlights
- Loading branch information
Showing
4 changed files
with
184 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Use adaptive method when trimming texts with highlights |
66 changes: 66 additions & 0 deletions
66
src/app/components/HighlightedText/AdaptiveHighlightedText.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { FC, ReactNode } from 'react' | ||
import InfoIcon from '@mui/icons-material/Info' | ||
import { HighlightedText, HighlightOptions } from './index' | ||
import { AdaptiveDynamicTrimmer } from '../AdaptiveTrimmer/AdaptiveDynamicTrimmer' | ||
import { HighlightedTrimmedText } from './HighlightedTrimmedText' | ||
|
||
type AdaptiveHighlightedTextProps = { | ||
/** | ||
* The text to display | ||
*/ | ||
text: string | undefined | ||
|
||
/** | ||
* The pattern to search for (and highlight) | ||
*/ | ||
pattern: string | undefined | ||
|
||
/** | ||
* Options for highlighting (case sensitivity, styling, etc.) | ||
* | ||
* (This is optional, sensible defaults are provided.) | ||
*/ | ||
options?: HighlightOptions | ||
|
||
/** | ||
* Extra content to put into the tooltip | ||
*/ | ||
extraTooltip?: ReactNode | ||
} | ||
|
||
/** | ||
* Display a text with a part highlighted, adaptively trimmed to the maximum length around the highlight | ||
*/ | ||
export const AdaptiveHighlightedText: FC<AdaptiveHighlightedTextProps> = ({ | ||
text, | ||
pattern, | ||
options, | ||
extraTooltip, | ||
}) => { | ||
const fullContent = <HighlightedText text={text} pattern={pattern} options={options} /> | ||
|
||
return text ? ( | ||
<AdaptiveDynamicTrimmer | ||
getFullContent={() => ({ | ||
content: fullContent, | ||
length: text.length, | ||
})} | ||
getShortenedContent={wantedLength => ( | ||
<HighlightedTrimmedText | ||
fragmentLength={wantedLength} | ||
text={text} | ||
pattern={pattern} | ||
options={options} | ||
/> | ||
)} | ||
extraTooltip={ | ||
extraTooltip ? ( | ||
<> | ||
<InfoIcon /> | ||
{extraTooltip} | ||
</> | ||
) : undefined | ||
} | ||
/> | ||
) : undefined | ||
} |
43 changes: 43 additions & 0 deletions
43
src/app/components/HighlightedText/HighlightedTrimmedText.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { FC } from 'react' | ||
|
||
import { HighlightedText, HighlightOptions } from './index' | ||
import { cutAroundMatch } from './text-cutting' | ||
|
||
type HighlightedTrimmedTextProps = { | ||
/** | ||
* The text to display | ||
*/ | ||
text: string | undefined | ||
|
||
/** | ||
* The pattern to search for (and highlight) | ||
*/ | ||
pattern: string | undefined | ||
|
||
/** | ||
* Options for highlighting (case sensitivity, styling, etc.) | ||
* | ||
* (This is optional, sensible defaults are provided.) | ||
*/ | ||
options?: HighlightOptions | ||
|
||
/** | ||
* What should be the length of the fragment delivered, which | ||
* has the pattern inside it? | ||
*/ | ||
fragmentLength: number | ||
} | ||
|
||
/** | ||
* Display a text with a part highlighted, trimmed to a specific length around the highlight | ||
*/ | ||
export const HighlightedTrimmedText: FC<HighlightedTrimmedTextProps> = props => { | ||
const { text, pattern, fragmentLength, options } = props | ||
return ( | ||
<HighlightedText | ||
text={cutAroundMatch(text, pattern, { fragmentLength }).part} | ||
pattern={pattern} | ||
options={options} | ||
/> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { findTextMatch, NormalizerOptions } from './text-matching' | ||
|
||
export interface CutAroundOptions extends NormalizerOptions { | ||
/** | ||
* What should be the length of the fragment delivered, which | ||
* has the pattern inside it? | ||
* | ||
* The default value is 80. | ||
*/ | ||
fragmentLength?: number | ||
} | ||
|
||
/** | ||
* Return a part of the corpus that contains the match to the pattern, if any | ||
* | ||
* If the corpus is undefined or empty, undefined is returned. | ||
* | ||
* If either the pattern is undefined or empty, or there is no match, | ||
* an adequately sized part from the beginning of the corpus is returned. | ||
* | ||
* If there is a match, but the corpus is at most as long as the desired fragment length, | ||
* the whole corpus is returned. | ||
* | ||
* If there is a match, and the corpus is longer than the desired fragment length, | ||
* then a part of a corpus is returned, so that the match is within the returned part, | ||
* around the middle. | ||
*/ | ||
export function cutAroundMatch( | ||
corpus: string | undefined, | ||
pattern: string | undefined, | ||
options: CutAroundOptions = {}, | ||
): { | ||
hasMatch: boolean | ||
part: string | undefined | ||
} { | ||
const { fragmentLength = 80, ...matchOptions } = options | ||
|
||
if (!corpus) { | ||
// there is nothing to see here | ||
return { | ||
hasMatch: false, | ||
part: undefined, | ||
} | ||
} | ||
|
||
// do we have a match? | ||
const match = pattern ? findTextMatch(corpus, [pattern], matchOptions) : undefined | ||
|
||
if (corpus.length <= fragmentLength) { | ||
// the whole corpus fits into the max size, no need to cut. | ||
return { | ||
hasMatch: !!match, | ||
part: corpus, | ||
} | ||
} | ||
|
||
// how much extra space do we have? | ||
const buffer = fragmentLength - (pattern || '').length | ||
|
||
const matchStart = match?.startPos ?? 0 | ||
|
||
// We will start before the start of the match, by buffer / 2 chars | ||
const startPos = Math.max(Math.min(matchStart - Math.floor(buffer / 2), corpus.length - fragmentLength), 0) | ||
const endPos = Math.min(startPos + fragmentLength, corpus.length) | ||
|
||
// compile the result | ||
const part = | ||
(startPos ? '…' : '') + corpus.substring(startPos, endPos) + (endPos < corpus.length - 1 ? '…' : '') | ||
|
||
return { | ||
hasMatch: true, | ||
part, | ||
} | ||
} |