2021-01-24 02:25:43 +01:00
|
|
|
import analytics from '@components/analytics'
|
2021-01-03 02:00:26 +01:00
|
|
|
import Icon from '@components/Icon'
|
2021-01-01 16:48:16 +01:00
|
|
|
import openLink from '@components/openLink'
|
2021-01-01 17:52:14 +01:00
|
|
|
import ParseEmojis from '@components/Parse/Emojis'
|
2021-01-10 02:12:14 +01:00
|
|
|
import { useNavigation, useRoute } from '@react-navigation/native'
|
2021-01-24 02:25:43 +01:00
|
|
|
import { StackNavigationProp } from '@react-navigation/stack'
|
2021-01-01 16:48:16 +01:00
|
|
|
import { StyleConstants } from '@utils/styles/constants'
|
2021-01-12 00:12:44 +01:00
|
|
|
import layoutAnimation from '@utils/styles/layoutAnimation'
|
2021-01-01 16:48:16 +01:00
|
|
|
import { useTheme } from '@utils/styles/ThemeManager'
|
2020-12-26 12:59:16 +01:00
|
|
|
import { LinearGradient } from 'expo-linear-gradient'
|
2021-01-07 19:13:09 +01:00
|
|
|
import React, { useCallback, useState } from 'react'
|
2021-01-19 01:13:45 +01:00
|
|
|
import { useTranslation } from 'react-i18next'
|
2021-01-27 00:42:56 +01:00
|
|
|
import { Platform, Pressable, Text, View } from 'react-native'
|
2020-12-01 00:44:28 +01:00
|
|
|
import HTMLView from 'react-native-htmlview'
|
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 = ({
|
2021-01-10 02:12:14 +01:00
|
|
|
routeParams,
|
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,
|
2020-12-28 17:30:20 +01:00
|
|
|
tags,
|
2021-01-04 18:29:02 +01:00
|
|
|
showFullLink,
|
|
|
|
disableDetails
|
2020-10-31 21:04:46 +01:00
|
|
|
}: {
|
2021-01-10 02:12:14 +01:00
|
|
|
routeParams?: any
|
2020-11-23 00:07:32 +01:00
|
|
|
theme: any
|
|
|
|
node: any
|
2020-10-31 21:04:46 +01:00
|
|
|
index: number
|
2021-02-01 02:16:53 +01:00
|
|
|
size: 'S' | 'M' | 'L'
|
|
|
|
navigation: StackNavigationProp<Nav.TabLocalStackParamList>
|
2020-11-21 00:40:55 +01:00
|
|
|
mentions?: Mastodon.Mention[]
|
2020-12-28 17:30:20 +01:00
|
|
|
tags?: Mastodon.Tag[]
|
2020-10-31 21:04:46 +01:00
|
|
|
showFullLink: boolean
|
2021-01-04 18:29:02 +01:00
|
|
|
disableDetails: boolean
|
2020-10-31 21:04:46 +01:00
|
|
|
}) => {
|
2021-01-07 19:13:09 +01:00
|
|
|
switch (node.name) {
|
|
|
|
case 'a':
|
|
|
|
const classes = node.attribs.class
|
|
|
|
const href = node.attribs.href
|
|
|
|
if (classes) {
|
|
|
|
if (classes.includes('hashtag')) {
|
2021-01-10 02:12:14 +01:00
|
|
|
const tag = href.split(new RegExp(/\/tag\/(.*)|\/tags\/(.*)/))
|
|
|
|
const differentTag = routeParams?.hashtag
|
|
|
|
? routeParams.hashtag !== tag[1] && routeParams.hashtag !== tag[2]
|
|
|
|
: true
|
2021-01-07 19:13:09 +01:00
|
|
|
return (
|
2021-01-14 00:43:35 +01:00
|
|
|
<Text
|
2021-01-07 19:13:09 +01:00
|
|
|
key={index}
|
2021-01-14 00:43:35 +01:00
|
|
|
style={{
|
|
|
|
color: theme.blue,
|
|
|
|
...StyleConstants.FontStyle[size]
|
|
|
|
}}
|
2021-01-07 19:13:09 +01:00
|
|
|
onPress={() => {
|
2021-01-24 02:25:43 +01:00
|
|
|
analytics('status_hashtag_press')
|
2021-01-07 19:13:09 +01:00
|
|
|
!disableDetails &&
|
2021-01-10 02:12:14 +01:00
|
|
|
differentTag &&
|
2021-01-30 01:29:15 +01:00
|
|
|
navigation.push('Tab-Shared-Hashtag', {
|
2021-01-07 19:13:09 +01:00
|
|
|
hashtag: tag[1] || tag[2]
|
|
|
|
})
|
|
|
|
}}
|
|
|
|
>
|
2021-01-14 00:43:35 +01:00
|
|
|
{node.children[0].data}
|
|
|
|
{node.children[1]?.children[0].data}
|
|
|
|
</Text>
|
2021-01-07 19:13:09 +01:00
|
|
|
)
|
|
|
|
} else if (classes.includes('mention') && mentions) {
|
|
|
|
const accountIndex = mentions.findIndex(
|
|
|
|
mention => mention.url === href
|
|
|
|
)
|
2021-01-10 02:12:14 +01:00
|
|
|
const differentAccount = routeParams?.account
|
2021-01-17 22:37:05 +01:00
|
|
|
? routeParams.account.id !== mentions[accountIndex]?.id
|
2021-01-10 02:12:14 +01:00
|
|
|
: true
|
2021-01-07 19:13:09 +01:00
|
|
|
return (
|
2021-01-14 00:43:35 +01:00
|
|
|
<Text
|
2021-01-07 19:13:09 +01:00
|
|
|
key={index}
|
2021-01-14 00:43:35 +01:00
|
|
|
style={{
|
|
|
|
color: accountIndex !== -1 ? theme.blue : undefined,
|
|
|
|
...StyleConstants.FontStyle[size]
|
|
|
|
}}
|
2021-01-07 19:13:09 +01:00
|
|
|
onPress={() => {
|
2021-01-24 02:25:43 +01:00
|
|
|
analytics('status_mention_press')
|
2021-01-07 19:13:09 +01:00
|
|
|
accountIndex !== -1 &&
|
|
|
|
!disableDetails &&
|
2021-01-10 02:12:14 +01:00
|
|
|
differentAccount &&
|
2021-01-30 01:29:15 +01:00
|
|
|
navigation.push('Tab-Shared-Account', {
|
2021-01-07 19:13:09 +01:00
|
|
|
account: mentions[accountIndex]
|
|
|
|
})
|
|
|
|
}}
|
|
|
|
>
|
2021-01-14 00:43:35 +01:00
|
|
|
{node.children[0].data}
|
|
|
|
{node.children[1]?.children[0].data}
|
|
|
|
</Text>
|
2021-01-07 19:13:09 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
} 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
|
2020-10-28 00:02:37 +01:00
|
|
|
return (
|
2021-01-14 00:43:35 +01:00
|
|
|
<Text
|
2020-10-28 00:02:37 +01:00
|
|
|
key={index}
|
2021-01-14 00:43:35 +01:00
|
|
|
style={{
|
|
|
|
color: theme.blue,
|
2021-01-23 02:41:50 +01:00
|
|
|
...StyleConstants.FontStyle[size],
|
|
|
|
alignItems: 'center'
|
2021-01-14 00:43:35 +01:00
|
|
|
}}
|
2021-01-24 02:25:43 +01:00
|
|
|
onPress={async () => {
|
|
|
|
analytics('status_link_press')
|
2021-01-07 19:13:09 +01:00
|
|
|
!disableDetails && !shouldBeTag
|
|
|
|
? await openLink(href)
|
2021-01-30 01:29:15 +01:00
|
|
|
: navigation.push('Tab-Shared-Hashtag', {
|
2021-01-07 19:13:09 +01:00
|
|
|
hashtag: content.substring(1)
|
|
|
|
})
|
2021-01-24 02:25:43 +01:00
|
|
|
}}
|
2020-10-28 00:02:37 +01:00
|
|
|
>
|
2021-01-23 02:41:50 +01:00
|
|
|
{content || (showFullLink ? href : domain[1])}
|
2021-01-14 00:43:35 +01:00
|
|
|
{!shouldBeTag ? (
|
|
|
|
<Icon
|
|
|
|
color={theme.blue}
|
|
|
|
name='ExternalLink'
|
|
|
|
size={StyleConstants.Font.Size[size]}
|
2021-01-23 02:41:50 +01:00
|
|
|
style={{
|
|
|
|
transform: [{ translateY: size === 'L' ? -3 : -1 }]
|
|
|
|
}}
|
2021-01-14 00:43:35 +01:00
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
</Text>
|
2020-10-28 00:02:37 +01:00
|
|
|
)
|
|
|
|
}
|
2021-01-07 19:13:09 +01:00
|
|
|
break
|
|
|
|
case 'p':
|
2020-12-17 09:44:03 +01:00
|
|
|
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
|
|
|
}
|
2021-01-07 19:13:09 +01:00
|
|
|
break
|
2020-10-28 00:02:37 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-31 21:04:46 +01:00
|
|
|
export interface Props {
|
|
|
|
content: string
|
2021-02-01 02:16:53 +01:00
|
|
|
size?: 'S' | 'M' | 'L'
|
2020-11-21 00:40:55 +01:00
|
|
|
emojis?: Mastodon.Emoji[]
|
|
|
|
mentions?: Mastodon.Mention[]
|
2020-12-28 17:30:20 +01:00
|
|
|
tags?: Mastodon.Tag[]
|
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
|
2021-01-04 18:29:02 +01:00
|
|
|
disableDetails?: boolean
|
2020-10-31 21:04:46 +01:00
|
|
|
}
|
|
|
|
|
2021-01-01 16:48:16 +01:00
|
|
|
const ParseHTML: React.FC<Props> = ({
|
2020-10-28 00:02:37 +01:00
|
|
|
content,
|
2020-12-28 00:59:57 +01:00
|
|
|
size = 'M',
|
2020-10-28 00:02:37 +01:00
|
|
|
emojis,
|
|
|
|
mentions,
|
2020-12-28 17:30:20 +01:00
|
|
|
tags,
|
2020-10-29 14:52:28 +01:00
|
|
|
showFullLink = false,
|
2020-12-26 12:59:16 +01:00
|
|
|
numberOfLines = 10,
|
2021-01-22 01:34:20 +01:00
|
|
|
expandHint,
|
2021-01-04 18:29:02 +01:00
|
|
|
disableDetails = false
|
2020-10-31 21:04:46 +01:00
|
|
|
}) => {
|
2021-01-24 02:25:43 +01:00
|
|
|
const navigation = useNavigation<
|
2021-02-01 02:16:53 +01:00
|
|
|
StackNavigationProp<Nav.TabLocalStackParamList>
|
2021-01-24 02:25:43 +01:00
|
|
|
>()
|
2021-01-10 02:12:14 +01:00
|
|
|
const route = useRoute()
|
2020-11-23 00:07:32 +01:00
|
|
|
const { theme } = useTheme()
|
2021-01-22 01:34:20 +01:00
|
|
|
const { t, i18n } = useTranslation('componentParse')
|
|
|
|
if (!expandHint) {
|
|
|
|
expandHint = t('HTML.defaultHint')
|
|
|
|
}
|
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({
|
2021-01-10 02:12:14 +01:00
|
|
|
routeParams: route.params,
|
2020-12-02 00:16:27 +01:00
|
|
|
theme,
|
|
|
|
node,
|
|
|
|
index,
|
|
|
|
size,
|
|
|
|
navigation,
|
|
|
|
mentions,
|
2020-12-28 17:30:20 +01:00
|
|
|
tags,
|
2021-01-04 18:29:02 +01:00
|
|
|
showFullLink,
|
|
|
|
disableDetails
|
2020-12-02 00:16:27 +01:00
|
|
|
}),
|
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) {
|
2021-01-04 14:55:34 +01:00
|
|
|
return (
|
|
|
|
<ParseEmojis
|
|
|
|
content={children.toString()}
|
|
|
|
emojis={emojis}
|
|
|
|
size={size}
|
|
|
|
/>
|
|
|
|
)
|
2020-12-26 12:59:16 +01:00
|
|
|
} else {
|
|
|
|
return null
|
|
|
|
}
|
2020-12-17 09:44:03 +01:00
|
|
|
}, [])
|
2020-12-26 23:05:17 +01:00
|
|
|
const rootComponent = useCallback(
|
|
|
|
({ children }) => {
|
2021-01-19 01:13:45 +01:00
|
|
|
const { t } = useTranslation('componentParse')
|
2020-12-26 23:05:17 +01:00
|
|
|
const lineHeight = StyleConstants.Font.LineHeight[size]
|
2020-12-26 12:59:16 +01:00
|
|
|
|
2021-01-04 14:55:34 +01:00
|
|
|
const [expandAllow, setExpandAllow] = useState(false)
|
|
|
|
const [expanded, setExpanded] = useState(false)
|
2020-12-26 12:59:16 +01:00
|
|
|
|
2021-01-12 00:12:44 +01:00
|
|
|
const onTextLayout = useCallback(({ nativeEvent }) => {
|
2021-01-27 00:42:56 +01:00
|
|
|
switch (Platform.OS) {
|
|
|
|
case 'ios':
|
|
|
|
if (nativeEvent.lines.length === numberOfLines + 1) {
|
|
|
|
setExpandAllow(true)
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case 'android':
|
|
|
|
if (nativeEvent.lines.length > numberOfLines + 1) {
|
|
|
|
setExpandAllow(true)
|
|
|
|
}
|
|
|
|
break
|
2021-01-04 10:50:24 +01:00
|
|
|
}
|
2021-01-12 00:12:44 +01:00
|
|
|
}, [])
|
2021-01-01 16:48:16 +01:00
|
|
|
|
2020-12-26 23:05:17 +01:00
|
|
|
return (
|
2021-01-13 01:03:46 +01:00
|
|
|
<View style={{ overflow: 'hidden' }}>
|
2021-01-12 00:12:44 +01:00
|
|
|
<Text
|
|
|
|
children={children}
|
|
|
|
onTextLayout={onTextLayout}
|
|
|
|
numberOfLines={expanded ? 999 : numberOfLines + 1}
|
|
|
|
style={{
|
|
|
|
...StyleConstants.FontStyle[size],
|
|
|
|
color: theme.primary
|
|
|
|
}}
|
|
|
|
/>
|
2021-01-04 14:55:34 +01:00
|
|
|
{expandAllow ? (
|
2020-12-26 23:05:17 +01:00
|
|
|
<Pressable
|
2021-01-12 00:12:44 +01:00
|
|
|
onPress={() => {
|
2021-01-24 02:25:43 +01:00
|
|
|
analytics('status_readmore', { allow: expandAllow, expanded })
|
2021-01-12 00:12:44 +01:00
|
|
|
layoutAnimation()
|
|
|
|
setExpanded(!expanded)
|
|
|
|
}}
|
2021-01-13 01:03:46 +01:00
|
|
|
style={{
|
|
|
|
marginTop: expanded
|
|
|
|
? 0
|
|
|
|
: -lineHeight * (numberOfLines === 0 ? 1 : 2)
|
|
|
|
}}
|
2020-12-02 00:16:27 +01:00
|
|
|
>
|
2020-12-26 23:05:17 +01:00
|
|
|
<LinearGradient
|
|
|
|
colors={[
|
|
|
|
theme.backgroundGradientStart,
|
|
|
|
theme.backgroundGradientEnd
|
|
|
|
]}
|
2021-01-13 01:03:46 +01:00
|
|
|
locations={[
|
|
|
|
0,
|
|
|
|
lineHeight / (StyleConstants.Font.Size[size] * 5)
|
|
|
|
]}
|
2020-12-02 00:16:27 +01:00
|
|
|
style={{
|
2020-12-26 23:05:17 +01:00
|
|
|
paddingTop: StyleConstants.Font.Size.S * 2,
|
|
|
|
paddingBottom: StyleConstants.Font.Size.S
|
2020-12-02 00:16:27 +01:00
|
|
|
}}
|
|
|
|
>
|
2020-12-26 23:05:17 +01:00
|
|
|
<Text
|
|
|
|
style={{
|
|
|
|
textAlign: 'center',
|
2020-12-29 00:21:05 +01:00
|
|
|
...StyleConstants.FontStyle.S,
|
2020-12-26 23:05:17 +01:00
|
|
|
color: theme.primary
|
|
|
|
}}
|
|
|
|
>
|
2021-01-19 01:13:45 +01:00
|
|
|
{expanded
|
|
|
|
? t('HTML.expanded.true', { hint: expandHint })
|
|
|
|
: t('HTML.expanded.false', { hint: expandHint })}
|
2020-12-26 23:05:17 +01:00
|
|
|
</Text>
|
|
|
|
</LinearGradient>
|
|
|
|
</Pressable>
|
2021-01-01 16:48:16 +01:00
|
|
|
) : null}
|
2020-12-26 23:05:17 +01:00
|
|
|
</View>
|
|
|
|
)
|
|
|
|
},
|
2021-01-22 01:34:20 +01:00
|
|
|
[theme, i18n.language]
|
2020-12-26 23:05:17 +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
|
|
|
/>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-01-12 00:12:44 +01:00
|
|
|
// export default ParseHTML
|
|
|
|
export default React.memo(ParseHTML, () => true)
|