tooot/src/components/ParseContent.tsx

220 lines
5.8 KiB
TypeScript
Raw Normal View History

2020-12-02 00:16:27 +01:00
import React, { useCallback, useState } from 'react'
import { Pressable, Text, View } from 'react-native'
2020-12-01 00:44:28 +01:00
import HTMLView from 'react-native-htmlview'
2020-10-28 00:02:37 +01:00
import { useNavigation } from '@react-navigation/native'
2020-12-13 14:04:25 +01:00
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
import { useTheme } from '@utils/styles/ThemeManager'
2020-12-01 00:44:28 +01:00
import { Feather } from '@expo/vector-icons'
2020-12-13 14:04:25 +01:00
import { StyleConstants } from '@utils/styles/constants'
2020-12-02 00:16:27 +01:00
import { LinearGradient } from 'expo-linear-gradient'
2020-12-20 18:41:28 +01:00
import openLink from '@root/utils/openLink'
2020-11-23 00:07:32 +01:00
2020-11-05 21:47:50 +01:00
// Prevent going to the same hashtag multiple times
2020-10-31 21:04:46 +01:00
const renderNode = ({
2020-11-23 00:07:32 +01:00
theme,
2020-10-31 21:04:46 +01:00
node,
index,
2020-12-02 00:16:27 +01:00
size,
2020-10-31 21:04:46 +01:00
navigation,
mentions,
showFullLink
}: {
2020-11-23 00:07:32 +01:00
theme: any
node: any
2020-10-31 21:04:46 +01:00
index: number
2020-12-12 22:19:18 +01:00
size: 'M' | 'L'
navigation: any
mentions?: Mastodon.Mention[]
2020-10-31 21:04:46 +01:00
showFullLink: boolean
}) => {
2020-10-28 00:02:37 +01:00
if (node.name == 'a') {
const classes = node.attribs.class
const href = node.attribs.href
if (classes) {
if (classes.includes('hashtag')) {
return (
<Text
key={index}
2020-12-12 22:19:18 +01:00
style={{
2020-12-26 00:40:27 +01:00
color: theme.blue,
2020-12-12 22:19:18 +01:00
fontSize: StyleConstants.Font.Size[size]
}}
2020-10-28 00:02:37 +01:00
onPress={() => {
const tag = href.split(new RegExp(/\/tag\/(.*)|\/tags\/(.*)/))
2020-11-23 00:07:32 +01:00
navigation.push('Screen-Shared-Hashtag', {
2020-10-28 00:02:37 +01:00
hashtag: tag[1] || tag[2]
})
}}
>
{node.children[0].data}
{node.children[1]?.children[0].data}
</Text>
)
2020-11-05 21:47:50 +01:00
} else if (classes.includes('mention') && mentions) {
2020-10-28 00:02:37 +01:00
return (
<Text
key={index}
2020-12-12 22:19:18 +01:00
style={{
2020-12-26 00:40:27 +01:00
color: theme.blue,
2020-12-12 22:19:18 +01:00
fontSize: StyleConstants.Font.Size[size]
}}
2020-10-28 00:02:37 +01:00
onPress={() => {
const username = href.split(new RegExp(/@(.*)/))
const usernameIndex = mentions.findIndex(
m => m.username === username[1]
)
2020-11-23 00:07:32 +01:00
navigation.push('Screen-Shared-Account', {
2020-12-21 21:47:15 +01:00
account: mentions[usernameIndex]
2020-10-28 00:02:37 +01:00
})
}}
>
{node.children[0].data}
{node.children[1]?.children[0].data}
</Text>
)
}
} else {
2020-12-18 00:00:45 +01:00
const domain = href.split(new RegExp(/:\/\/(.[^\/]+)/))
2020-10-28 00:02:37 +01:00
return (
<Text
key={index}
2020-12-12 22:19:18 +01:00
style={{
2020-12-26 00:40:27 +01:00
color: theme.blue,
2020-12-12 22:19:18 +01:00
fontSize: StyleConstants.Font.Size[size]
}}
2020-12-20 18:41:28 +01:00
onPress={async () => await openLink(href)}
2020-10-28 00:02:37 +01:00
>
2020-12-12 22:19:18 +01:00
<Feather
name='external-link'
size={StyleConstants.Font.Size[size]}
2020-12-26 00:40:27 +01:00
color={theme.blue}
2020-12-12 22:19:18 +01:00
/>{' '}
2020-10-28 00:02:37 +01:00
{showFullLink ? href : domain[1]}
</Text>
)
}
} else {
if (node.name === 'p') {
if (!node.children.length) {
return <View key={index}></View> // bug when the tag is empty
}
}
2020-10-28 00:02:37 +01:00
}
}
2020-10-31 21:04:46 +01:00
export interface Props {
content: string
2020-12-12 22:19:18 +01:00
size: 'M' | 'L'
emojis?: Mastodon.Emoji[]
mentions?: Mastodon.Mention[]
2020-10-31 21:04:46 +01:00
showFullLink?: boolean
2020-12-01 00:44:28 +01:00
numberOfLines?: number
2020-10-31 21:04:46 +01:00
}
const ParseContent: React.FC<Props> = ({
2020-10-28 00:02:37 +01:00
content,
2020-11-23 00:07:32 +01:00
size,
2020-10-28 00:02:37 +01:00
emojis,
mentions,
2020-10-29 14:52:28 +01:00
showFullLink = false,
2020-12-01 00:44:28 +01:00
numberOfLines = 10
2020-10-31 21:04:46 +01:00
}) => {
2020-10-28 00:02:37 +01:00
const navigation = useNavigation()
2020-11-23 00:07:32 +01:00
const { theme } = useTheme()
2020-10-28 00:02:37 +01:00
2020-11-28 17:07:30 +01:00
const renderNodeCallback = useCallback(
(node, index) =>
2020-12-02 00:16:27 +01:00
renderNode({
theme,
node,
index,
size,
navigation,
mentions,
showFullLink
}),
2020-11-28 17:07:30 +01:00
[]
)
const textComponent = useCallback(({ children }) => {
return emojis && children ? (
<Emojis
content={children.toString()}
emojis={emojis}
size={StyleConstants.Font.Size[size]}
/>
) : (
<Text>{children}</Text>
)
}, [])
2020-11-28 17:07:30 +01:00
const rootComponent = useCallback(({ children }) => {
2020-12-02 00:16:27 +01:00
const { theme } = useTheme()
const [textLoaded, setTextLoaded] = useState(false)
const [totalLines, setTotalLines] = useState<number | undefined>()
const [lineHeight, setLineHeight] = useState<number | undefined>()
const [shownLines, setShownLines] = useState(numberOfLines)
2020-12-26 00:40:27 +01:00
2020-12-01 00:44:28 +01:00
return (
2020-12-02 00:16:27 +01:00
<>
<Text
numberOfLines={
totalLines && totalLines > numberOfLines ? shownLines : totalLines
}
2020-12-12 22:19:18 +01:00
style={{ lineHeight: StyleConstants.Font.LineHeight[size] }}
2020-12-02 00:16:27 +01:00
onTextLayout={({ nativeEvent }) => {
if (!textLoaded) {
setTextLoaded(true)
setTotalLines(nativeEvent.lines.length)
setLineHeight(nativeEvent.lines[0].height)
}
}}
>
{children}
</Text>
{totalLines && lineHeight && totalLines > shownLines && (
<Pressable
onPress={() => {
setShownLines(totalLines)
}}
style={{
marginTop: -lineHeight
}}
>
<LinearGradient
colors={[
theme.backgroundGradientStart,
theme.backgroundGradientEnd
]}
locations={[0, lineHeight / (StyleConstants.Font.Size.S * 5)]}
style={{
paddingTop: StyleConstants.Font.Size.S * 2,
paddingBottom: StyleConstants.Font.Size.S
}}
>
<Text
style={{
textAlign: 'center',
fontSize: StyleConstants.Font.Size.S,
color: theme.primary
}}
>
</Text>
</LinearGradient>
</Pressable>
)}
</>
2020-12-01 00:44:28 +01:00
)
2020-11-28 17:07:30 +01:00
}, [])
2020-10-28 00:02:37 +01:00
return (
<HTMLView
value={content}
2020-11-28 17:07:30 +01:00
TextComponent={textComponent}
RootComponent={rootComponent}
renderNode={renderNodeCallback}
2020-10-28 00:02:37 +01:00
/>
)
}
2020-10-31 21:04:46 +01:00
export default ParseContent