Implement mentions rendering
This commit is contained in:
parent
2058505bf1
commit
195d2f7d2b
7 changed files with 177 additions and 19 deletions
95
src/view/com/util/RichText.tsx
Normal file
95
src/view/com/util/RichText.tsx
Normal file
|
@ -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('@', '')
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue