import analytics from '@components/analytics' import Icon from '@components/Icon' import openLink from '@components/openLink' import ParseEmojis from '@components/Parse/Emojis' import { useNavigation, useRoute } from '@react-navigation/native' import { StackNavigationProp } from '@react-navigation/stack' import { StyleConstants } from '@utils/styles/constants' import layoutAnimation from '@utils/styles/layoutAnimation' import { useTheme } from '@utils/styles/ThemeManager' import { LinearGradient } from 'expo-linear-gradient' import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { Pressable, Text, View } from 'react-native' import HTMLView from 'react-native-htmlview' // Prevent going to the same hashtag multiple times const renderNode = ({ routeParams, theme, node, index, size, navigation, mentions, tags, showFullLink, disableDetails }: { routeParams?: any theme: any node: any index: number size: 'M' | 'L' navigation: StackNavigationProp mentions?: Mastodon.Mention[] tags?: Mastodon.Tag[] showFullLink: boolean disableDetails: boolean }) => { switch (node.name) { case 'a': const classes = node.attribs.class const href = node.attribs.href if (classes) { if (classes.includes('hashtag')) { const tag = href.split(new RegExp(/\/tag\/(.*)|\/tags\/(.*)/)) const differentTag = routeParams?.hashtag ? routeParams.hashtag !== tag[1] && routeParams.hashtag !== tag[2] : true return ( { analytics('status_hashtag_press') !disableDetails && differentTag && 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 ) const differentAccount = routeParams?.account ? routeParams.account.id !== mentions[accountIndex]?.id : true return ( { analytics('status_mention_press') accountIndex !== -1 && !disableDetails && differentAccount && 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] && node.children[0].data const shouldBeTag = tags && tags.filter(tag => `#${tag.name}` === content).length > 0 return ( { analytics('status_link_press') !disableDetails && !shouldBeTag ? await openLink(href) : navigation.push('Screen-Shared-Hashtag', { hashtag: content.substring(1) }) }} > {content || (showFullLink ? href : domain[1])} {!shouldBeTag ? ( ) : null} ) } break case 'p': if (!node.children.length) { return // bug when the tag is empty } break } } export interface Props { content: string size?: 'M' | 'L' emojis?: Mastodon.Emoji[] mentions?: Mastodon.Mention[] tags?: Mastodon.Tag[] showFullLink?: boolean numberOfLines?: number expandHint?: string disableDetails?: boolean } const ParseHTML: React.FC = ({ content, size = 'M', emojis, mentions, tags, showFullLink = false, numberOfLines = 10, expandHint, disableDetails = false }) => { const navigation = useNavigation< StackNavigationProp >() const route = useRoute() const { theme } = useTheme() const { t, i18n } = useTranslation('componentParse') if (!expandHint) { expandHint = t('HTML.defaultHint') } const renderNodeCallback = useCallback( (node, index) => renderNode({ routeParams: route.params, theme, node, index, size, navigation, mentions, tags, showFullLink, disableDetails }), [] ) const textComponent = useCallback(({ children }) => { if (children) { return ( ) } else { return null } }, []) const rootComponent = useCallback( ({ children }) => { const { t } = useTranslation('componentParse') const lineHeight = StyleConstants.Font.LineHeight[size] const [expandAllow, setExpandAllow] = useState(false) const [expanded, setExpanded] = useState(false) const onTextLayout = useCallback(({ nativeEvent }) => { if ( nativeEvent.lines && nativeEvent.lines.length === numberOfLines + 1 ) { setExpandAllow(true) } }, []) return ( {expandAllow ? ( { analytics('status_readmore', { allow: expandAllow, expanded }) layoutAnimation() setExpanded(!expanded) }} style={{ marginTop: expanded ? 0 : -lineHeight * (numberOfLines === 0 ? 1 : 2) }} > {expanded ? t('HTML.expanded.true', { hint: expandHint }) : t('HTML.expanded.false', { hint: expandHint })} ) : null} ) }, [theme, i18n.language] ) return ( ) } // export default ParseHTML export default React.memo(ParseHTML, () => true)