1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
import React from 'react'
import {Text, TextStyle, StyleProp} from 'react-native'
import {TextLink} from './Link'
import {s} from '../../lib/styles'
import {toShortUrl} from '../../../lib/strings'
type TextSlice = {start: number; end: number}
type Entity = {
index: TextSlice
type: string
value: string
}
export function RichText({
text,
entities,
style,
numberOfLines,
}: {
text: string
entities?: Entity[]
style?: StyleProp<TextStyle>
numberOfLines?: number
}) {
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(segment)
} else {
if (segment.entity.type === 'mention') {
els.push(
<TextLink
key={key}
text={segment.text}
href={`/profile/${segment.entity.value}`}
style={[style, s.blue3]}
/>,
)
} else if (segment.entity.type === 'link') {
els.push(
<TextLink
key={key}
text={toShortUrl(segment.text)}
href={segment.entity.value}
style={[style, s.blue3]}
/>,
)
}
}
key++
}
return (
<Text style={style} numberOfLines={numberOfLines}>
{els}
</Text>
)
}
function sortByIndex(a: Entity, b: Entity) {
return a.index.start - b.index.start
}
function* toSegments(text: string, entities: Entity[]) {
let cursor = 0
let i = 0
do {
let currEnt = entities[i]
if (cursor < currEnt.index.start) {
yield text.slice(cursor, currEnt.index.start)
} else if (cursor > currEnt.index.start) {
i++
continue
}
if (currEnt.index.start < currEnt.index.end) {
let subtext = text.slice(currEnt.index.start, currEnt.index.end)
if (
!subtext.trim() ||
(currEnt.type === 'mention' &&
stripUsername(subtext) !== stripUsername(currEnt.value)) ||
(currEnt.type === 'link' && !isSameLink(subtext, 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.end
i++
} while (i < entities.length)
if (cursor < text.length) {
yield text.slice(cursor, text.length)
}
}
function stripUsername(v: string): string {
return v.trim().replace('@', '')
}
function isSameLink(a: string, b: string) {
a = a.startsWith('http') ? a : `https://${a}`
b = b.startsWith('http') ? b : `https://${b}`
return a === b
}
|