diff options
Diffstat (limited to 'src/view/com/util/RichText.tsx')
-rw-r--r-- | src/view/com/util/RichText.tsx | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/src/view/com/util/RichText.tsx b/src/view/com/util/RichText.tsx new file mode 100644 index 000000000..25e031556 --- /dev/null +++ b/src/view/com/util/RichText.tsx @@ -0,0 +1,95 @@ +import React from 'react' +import {Text, TextStyle, StyleProp} from 'react-native' +import {Link} from './Link' +import {s} from '../../lib/styles' + +type TextSlice = [number, number] +type Entity = { + index: TextSlice + type: string + value: string +} + +export function RichText({ + text, + entities, + style, +}: { + text: string + entities?: Entity[] + style?: StyleProp<TextStyle> +}) { + if (!entities?.length) { + return <Text style={style}>{text}</Text> + } + if (!style) style = [] + else if (!Array.isArray(style)) style = [style] + entities.sort(sortByIndex) + const segments = Array.from(toSegments(text, entities)) + const els = [] + let key = 0 + for (const segment of segments) { + if (typeof segment === 'string') { + els.push( + <Text key={key} style={style}> + {segment} + </Text>, + ) + } else { + els.push( + <Link + key={key} + title={segment.text} + href={`/profile/${segment.entity.value}`}> + <Text key={key} style={[style, s.blue3]}> + {segment.text} + </Text> + </Link>, + ) + } + key++ + } + return <>{els}</> +} + +function sortByIndex(a: Entity, b: Entity) { + return a.index[0] - b.index[0] +} + +function* toSegments(text: string, entities: Entity[]) { + let cursor = 0 + let i = 0 + do { + let currEnt = entities[i] + if (cursor < currEnt.index[0]) { + yield text.slice(cursor, currEnt.index[0]) + } else { + i++ + continue + } + if (currEnt.index[0] < currEnt.index[1]) { + let subtext = text.slice(currEnt.index[0], currEnt.index[1]) + if ( + !subtext.trim() || + stripUsername(subtext) !== stripUsername(currEnt.value) + ) { + // dont yield links to empty strings or strings that don't match the entity value + yield subtext + } else { + yield { + entity: currEnt, + text: subtext, + } + } + } + cursor = currEnt.index[1] + i++ + } while (i < entities.length) + if (cursor < text.length) { + yield text.slice(cursor, text.length) + } +} + +function stripUsername(v: string): string { + return v.trim().replace('@', '') +} |