2020-12-26 12:59:16 +01:00
|
|
|
import { LinearGradient } from 'expo-linear-gradient'
|
|
|
|
import React, { useCallback, useMemo, useState } from 'react'
|
2020-12-17 09:44:03 +01:00
|
|
|
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-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'
|
2020-11-04 22:26:38 +01:00
|
|
|
navigation: any
|
2020-11-21 00:40:55 +01:00
|
|
|
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>
|
|
|
|
)
|
|
|
|
}
|
2020-12-17 09:44:03 +01:00
|
|
|
} else {
|
|
|
|
if (node.name === 'p') {
|
|
|
|
if (!node.children.length) {
|
2020-12-26 12:59:16 +01:00
|
|
|
return <View key={index} /> // bug when the tag is empty
|
2020-12-17 09:44:03 +01:00
|
|
|
}
|
|
|
|
}
|
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'
|
2020-11-21 00:40:55 +01:00
|
|
|
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-12-26 12:59:16 +01:00
|
|
|
expandHint?: string
|
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-26 12:59:16 +01:00
|
|
|
numberOfLines = 10,
|
|
|
|
expandHint = '全文'
|
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
|
|
|
[]
|
|
|
|
)
|
2020-12-17 09:44:03 +01:00
|
|
|
const textComponent = useCallback(({ children }) => {
|
2020-12-26 12:59:16 +01:00
|
|
|
if (children) {
|
|
|
|
return emojis ? (
|
|
|
|
<Emojis
|
|
|
|
content={children.toString()}
|
|
|
|
emojis={emojis}
|
|
|
|
size={StyleConstants.Font.Size[size]}
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<Text>{children}</Text>
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
return null
|
|
|
|
}
|
2020-12-17 09:44:03 +01:00
|
|
|
}, [])
|
2020-11-28 17:07:30 +01:00
|
|
|
const rootComponent = useCallback(({ children }) => {
|
2020-12-26 12:59:16 +01:00
|
|
|
const lineHeight = StyleConstants.Font.LineHeight[size]
|
|
|
|
|
|
|
|
const [heightOriginal, setHeightOriginal] = useState<number>()
|
|
|
|
const [heightTruncated, setHeightTruncated] = useState<number>()
|
|
|
|
const [allowExpand, setAllowExpand] = useState(false)
|
|
|
|
const [showAllText, setShowAllText] = useState(false)
|
|
|
|
|
|
|
|
const calNumberOfLines = useMemo(() => {
|
|
|
|
if (heightOriginal) {
|
|
|
|
if (!heightTruncated) {
|
|
|
|
return numberOfLines
|
|
|
|
} else {
|
|
|
|
if (allowExpand && !showAllText) {
|
|
|
|
return numberOfLines
|
|
|
|
} else {
|
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
}, [heightOriginal, heightTruncated, allowExpand, showAllText])
|
2020-12-26 00:40:27 +01:00
|
|
|
|
2020-12-01 00:44:28 +01:00
|
|
|
return (
|
2020-12-26 12:59:16 +01:00
|
|
|
<View>
|
2020-12-02 00:16:27 +01:00
|
|
|
<Text
|
2020-12-26 12:59:16 +01:00
|
|
|
style={{ lineHeight }}
|
|
|
|
children={children}
|
|
|
|
numberOfLines={calNumberOfLines}
|
|
|
|
onLayout={({ nativeEvent }) => {
|
|
|
|
if (!heightOriginal) {
|
|
|
|
setHeightOriginal(nativeEvent.layout.height)
|
|
|
|
} else {
|
|
|
|
if (!heightTruncated) {
|
|
|
|
setHeightTruncated(nativeEvent.layout.height)
|
|
|
|
} else {
|
|
|
|
if (heightOriginal > heightTruncated) {
|
|
|
|
setAllowExpand(true)
|
|
|
|
}
|
|
|
|
}
|
2020-12-02 00:16:27 +01:00
|
|
|
}
|
|
|
|
}}
|
2020-12-26 12:59:16 +01:00
|
|
|
/>
|
|
|
|
{allowExpand && (
|
2020-12-02 00:16:27 +01:00
|
|
|
<Pressable
|
2020-12-26 14:40:10 +01:00
|
|
|
onPress={() => {
|
|
|
|
setShowAllText(!showAllText)
|
|
|
|
}}
|
2020-12-26 12:59:16 +01:00
|
|
|
style={{ marginTop: showAllText ? 0 : -lineHeight * 1.25 }}
|
2020-12-02 00:16:27 +01:00
|
|
|
>
|
|
|
|
<LinearGradient
|
|
|
|
colors={[
|
|
|
|
theme.backgroundGradientStart,
|
|
|
|
theme.backgroundGradientEnd
|
|
|
|
]}
|
2020-12-26 12:59:16 +01:00
|
|
|
locations={[0, lineHeight / (StyleConstants.Font.Size.S * 4)]}
|
2020-12-02 00:16:27 +01:00
|
|
|
style={{
|
|
|
|
paddingTop: StyleConstants.Font.Size.S * 2,
|
|
|
|
paddingBottom: StyleConstants.Font.Size.S
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Text
|
|
|
|
style={{
|
|
|
|
textAlign: 'center',
|
|
|
|
fontSize: StyleConstants.Font.Size.S,
|
|
|
|
color: theme.primary
|
|
|
|
}}
|
|
|
|
>
|
2020-12-26 12:59:16 +01:00
|
|
|
{`${showAllText ? '折叠' : '展开'}${expandHint}`}
|
2020-12-02 00:16:27 +01:00
|
|
|
</Text>
|
|
|
|
</LinearGradient>
|
|
|
|
</Pressable>
|
|
|
|
)}
|
2020-12-26 12:59:16 +01:00
|
|
|
</View>
|
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
|