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