import { LinearGradient } from 'expo-linear-gradient'
import React, { useCallback, useMemo, useState } from 'react'
import { Pressable, Text, View } from 'react-native'
import HTMLView from 'react-native-htmlview'
import { useNavigation } from '@react-navigation/native'
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
import { useTheme } from '@utils/styles/ThemeManager'
import { Feather } from '@expo/vector-icons'
import { StyleConstants } from '@utils/styles/constants'
import openLink from '@root/utils/openLink'
import layoutAnimation from '@root/utils/styles/layoutAnimation'
// Prevent going to the same hashtag multiple times
const renderNode = ({
theme,
node,
index,
size,
navigation,
mentions,
tags,
showFullLink
}: {
theme: any
node: any
index: number
size: 'M' | 'L'
navigation: any
mentions?: Mastodon.Mention[]
tags?: Mastodon.Tag[]
showFullLink: boolean
}) => {
if (node.name == 'a') {
const classes = node.attribs.class
const href = node.attribs.href
if (classes) {
if (classes.includes('hashtag')) {
return (
{
const tag = href.split(new RegExp(/\/tag\/(.*)|\/tags\/(.*)/))
navigation.push('Screen-Shared-Hashtag', {
hashtag: tag[1] || tag[2]
})
}}
>
{node.children[0].data}
{node.children[1]?.children[0].data}
)
} else if (classes.includes('mention') && mentions) {
const accountIndex = mentions.findIndex(mention => mention.url === href)
return (
{
accountIndex !== -1 &&
navigation.push('Screen-Shared-Account', {
account: mentions[accountIndex]
})
}}
>
{node.children[0].data}
{node.children[1]?.children[0].data}
)
}
} else {
const domain = href.split(new RegExp(/:\/\/(.[^\/]+)/))
// Need example here
const content = node.children && node.children[0].data
const shouldBeTag =
tags && tags.filter(tag => `#${tag.name}` === content).length > 0
return (
!shouldBeTag
? await openLink(href)
: navigation.push('Screen-Shared-Hashtag', {
hashtag: content.substring(1)
})
}
>
{!shouldBeTag ? (
) : null}
{content || (showFullLink ? href : domain[1])}
)
}
} else {
if (node.name === 'p') {
if (!node.children.length) {
return // bug when the tag is empty
}
}
}
}
export interface Props {
content: string
size?: 'M' | 'L'
emojis?: Mastodon.Emoji[]
mentions?: Mastodon.Mention[]
tags?: Mastodon.Tag[]
showFullLink?: boolean
numberOfLines?: number
expandHint?: string
}
const ParseContent: React.FC = ({
content,
size = 'M',
emojis,
mentions,
tags,
showFullLink = false,
numberOfLines = 10,
expandHint = '全文'
}) => {
const navigation = useNavigation()
const { theme } = useTheme()
const renderNodeCallback = useCallback(
(node, index) =>
renderNode({
theme,
node,
index,
size,
navigation,
mentions,
tags,
showFullLink
}),
[]
)
const textComponent = useCallback(({ children }) => {
if (children) {
return emojis ? (
) : (
{children}
)
} else {
return null
}
}, [])
const rootComponent = useCallback(
({ children }) => {
const lineHeight = StyleConstants.Font.LineHeight[size]
const [heightOriginal, setHeightOriginal] = useState()
const [heightTruncated, setHeightTruncated] = useState()
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])
return (
{
if (!heightOriginal) {
setHeightOriginal(nativeEvent.layout.height)
} else {
if (!heightTruncated) {
setHeightTruncated(nativeEvent.layout.height)
} else {
if (heightOriginal > heightTruncated) {
setAllowExpand(true)
}
}
}
}}
/>
{allowExpand && (
{
layoutAnimation()
setShowAllText(!showAllText)
}}
style={{ marginTop: showAllText ? 0 : -lineHeight * 1.25 }}
>
{`${showAllText ? '折叠' : '展开'}${expandHint}`}
)}
)
},
[theme]
)
return (
)
}
export default ParseContent