mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Working emojis in reblog and names
This commit is contained in:
@ -3,6 +3,7 @@ module.exports = function (api) {
|
|||||||
return {
|
return {
|
||||||
presets: ['babel-preset-expo'],
|
presets: ['babel-preset-expo'],
|
||||||
plugins: [
|
plugins: [
|
||||||
|
['@babel/plugin-proposal-optional-chaining'],
|
||||||
[
|
[
|
||||||
'module-resolver',
|
'module-resolver',
|
||||||
{
|
{
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "~7.9.0",
|
"@babel/core": "~7.9.0",
|
||||||
|
"@babel/plugin-proposal-optional-chaining": "^7.12.1",
|
||||||
"babel-plugin-module-resolver": "^4.0.0"
|
"babel-plugin-module-resolver": "^4.0.0"
|
||||||
},
|
},
|
||||||
"private": true
|
"private": true
|
||||||
|
@ -1,52 +1,60 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Image, StyleSheet, Text, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import HTML from 'react-native-render-html'
|
|
||||||
|
|
||||||
import relativeTime from 'src/utils/relativeTime'
|
import Reblog from './TootTimeline/Reblog'
|
||||||
|
import Avatar from './TootTimeline/Avatar'
|
||||||
|
import Header from './TootTimeline/Header'
|
||||||
|
import Content from './TootTimeline/Content'
|
||||||
|
import Actions from './TootTimeline/Actions'
|
||||||
|
|
||||||
export default function TootTimeline ({ item, notification }) {
|
export default function TootTimeline ({ item, notification }) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.tootTimeline}>
|
<View style={styles.tootTimeline}>
|
||||||
<View style={styles.header}>
|
{item.reblog && (
|
||||||
<Image
|
<Reblog
|
||||||
source={{
|
name={item.account.display_name || item.account.username}
|
||||||
uri: item.reblog ? item.reblog.account.avatar : item.account.avatar
|
emojis={item.account.emojis}
|
||||||
}}
|
|
||||||
style={styles.avatar}
|
|
||||||
/>
|
/>
|
||||||
<View>
|
)}
|
||||||
<View style={styles.name}>
|
<View style={styles.toot}>
|
||||||
<Text>
|
<Avatar uri={item.reblog?.account.avatar || item.account.avatar} />
|
||||||
{item.reblog
|
<View style={{flexGrow: 1}}>
|
||||||
? item.reblog.account.display_name
|
<Header
|
||||||
: item.account.display_name}
|
name={
|
||||||
</Text>
|
(item.reblog?.account.display_name
|
||||||
<Text>
|
? item.reblog?.account.display_name
|
||||||
{item.reblog ? item.reblog.account.acct : item.account.acct}
|
: item.reblog?.account.username) ||
|
||||||
</Text>
|
(item.account.display_name
|
||||||
</View>
|
? item.account.display_name
|
||||||
<View>
|
: item.account.username)
|
||||||
<Text>{relativeTime(item.created_at)}</Text>
|
}
|
||||||
{item.application && item.application.name !== 'Web' && (
|
emojis={item.reblog?.account.emojis || item.account.emojis}
|
||||||
<Text onPress={() => Linking.openURL(item.application.website)}>
|
account={item.reblog?.account.acct || item.account.acct}
|
||||||
{item.application.name}
|
created_at={item.created_at}
|
||||||
</Text>
|
application={item.application || null}
|
||||||
)}
|
/>
|
||||||
</View>
|
<Content
|
||||||
|
content={notification ? item.status.content : item.content}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{notification ? (
|
<Actions />
|
||||||
<HTML html={item.status.content} />
|
|
||||||
) : item.content ? (
|
|
||||||
<HTML html={item.content} />
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
tootTimeline: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'column',
|
||||||
|
padding: 12
|
||||||
|
},
|
||||||
|
toot: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
TootTimeline.propTypes = {
|
TootTimeline.propTypes = {
|
||||||
item: PropTypes.shape({
|
item: PropTypes.shape({
|
||||||
account: PropTypes.shape({
|
account: PropTypes.shape({
|
||||||
@ -63,20 +71,3 @@ TootTimeline.propTypes = {
|
|||||||
}).isRequired,
|
}).isRequired,
|
||||||
notification: PropTypes.bool
|
notification: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
tootTimeline: {
|
|
||||||
flex: 1,
|
|
||||||
padding: 15
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
flexDirection: 'row'
|
|
||||||
},
|
|
||||||
avatar: {
|
|
||||||
width: 40,
|
|
||||||
height: 40
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
flexDirection: 'row'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
16
src/components/TootTimeline/Actions.jsx
Normal file
16
src/components/TootTimeline/Actions.jsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet } from 'react-native'
|
||||||
|
|
||||||
|
export default function Actions () {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
width: 50,
|
||||||
|
height: 50
|
||||||
|
})
|
||||||
|
|
||||||
|
// Actions.propTypes = {
|
||||||
|
// uri: PropTypes.string
|
||||||
|
// }
|
19
src/components/TootTimeline/Avatar.jsx
Normal file
19
src/components/TootTimeline/Avatar.jsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Image, StyleSheet } from 'react-native'
|
||||||
|
|
||||||
|
export default function Avatar ({ uri }) {
|
||||||
|
return <Image source={{ uri: uri }} style={styles.avatar} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
avatar: {
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
marginRight: 8
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Avatar.propTypes = {
|
||||||
|
uri: PropTypes.string.isRequired
|
||||||
|
}
|
31
src/components/TootTimeline/Content.jsx
Normal file
31
src/components/TootTimeline/Content.jsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Dimensions, StyleSheet, View } from 'react-native'
|
||||||
|
import HTML from 'react-native-render-html'
|
||||||
|
|
||||||
|
// !! Need to solve dimension issue
|
||||||
|
|
||||||
|
export default function Content ({ content }) {
|
||||||
|
const [viewWidth, setViewWidth] = useState()
|
||||||
|
return (
|
||||||
|
content && (
|
||||||
|
<View
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
onLayout={e => setViewWidth(e.nativeEvent.layout.width)}
|
||||||
|
>
|
||||||
|
{viewWidth && (
|
||||||
|
<HTML html={content} containerStyle={{ width: viewWidth }} />
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
width: 50,
|
||||||
|
height: 50
|
||||||
|
})
|
||||||
|
|
||||||
|
Content.propTypes = {
|
||||||
|
content: PropTypes.string
|
||||||
|
}
|
53
src/components/TootTimeline/Emojis.jsx
Normal file
53
src/components/TootTimeline/Emojis.jsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { Image, Text } from 'react-native'
|
||||||
|
|
||||||
|
const regexEmoji = new RegExp(/(:[a-z0-9_]+:)/g)
|
||||||
|
const regexEmojiSelect = new RegExp(/:([a-z0-9_]+):/)
|
||||||
|
|
||||||
|
export default function Emojis ({ content, emojis, dimension }) {
|
||||||
|
const hasEmojis = content.match(regexEmoji)
|
||||||
|
return hasEmojis ? (
|
||||||
|
content.split(regexEmoji).map((str, i) => {
|
||||||
|
if (str.match(regexEmoji)) {
|
||||||
|
const emojiShortcode = str.split(regexEmojiSelect)[1]
|
||||||
|
const emojiIndex = emojis.findIndex(emoji => {
|
||||||
|
return emoji.shortcode === emojiShortcode
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
key={i}
|
||||||
|
source={{ uri: emojis[emojiIndex].url }}
|
||||||
|
style={{ width: dimension, height: dimension }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
key={i}
|
||||||
|
style={{ fontSize: dimension, lineHeight: dimension + 1 }}
|
||||||
|
>
|
||||||
|
{str}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<Text style={{ fontSize: dimension, lineHeight: dimension + 1 }}>
|
||||||
|
{content}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Emojis.propTypes = {
|
||||||
|
content: PropTypes.string.isRequired,
|
||||||
|
emojis: PropTypes.arrayOf(
|
||||||
|
PropTypes.exact({
|
||||||
|
shortcode: PropTypes.string.isRequired,
|
||||||
|
url: PropTypes.string.isRequired,
|
||||||
|
static_url: PropTypes.string.isRequired,
|
||||||
|
visible_in_picker: PropTypes.bool.isRequired,
|
||||||
|
category: PropTypes.string
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
86
src/components/TootTimeline/Header.jsx
Normal file
86
src/components/TootTimeline/Header.jsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
|
|
||||||
|
import Emojis from './Emojis'
|
||||||
|
import relativeTime from 'src/utils/relativeTime'
|
||||||
|
|
||||||
|
export default function Header ({
|
||||||
|
name,
|
||||||
|
emojis,
|
||||||
|
account,
|
||||||
|
created_at,
|
||||||
|
application
|
||||||
|
}) {
|
||||||
|
const [since, setSince] = useState(relativeTime(created_at))
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setSince(relativeTime(created_at))
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<View style={styles.names}>
|
||||||
|
<View style={styles.name}>
|
||||||
|
<Emojis content={name} emojis={emojis} dimension={14} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.account}>@{account}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.meta}>
|
||||||
|
<View>
|
||||||
|
<Text style={styles.created_at}>{since}</Text>
|
||||||
|
</View>
|
||||||
|
{application && application.name !== 'Web' && (
|
||||||
|
<View>
|
||||||
|
<Text
|
||||||
|
onPress={() => Linking.openURL(application.website)}
|
||||||
|
style={styles.application}
|
||||||
|
>
|
||||||
|
{application.name}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
names: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginBottom: 8
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginRight: 8
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
fontSize: 12,
|
||||||
|
lineHeight: 14
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
fontSize: 12,
|
||||||
|
lineHeight: 12,
|
||||||
|
marginRight: 8
|
||||||
|
},
|
||||||
|
application: {
|
||||||
|
fontSize: 12,
|
||||||
|
lineHeight: 11
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Header.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
emojis: Emojis.propTypes.emojis,
|
||||||
|
account: PropTypes.string.isRequired,
|
||||||
|
created_at: PropTypes.string.isRequired,
|
||||||
|
application: PropTypes.exact({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
website: PropTypes.string
|
||||||
|
})
|
||||||
|
}
|
36
src/components/TootTimeline/Reblog.jsx
Normal file
36
src/components/TootTimeline/Reblog.jsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { StyleSheet, Text, View } from 'react-native'
|
||||||
|
import { Feather } from '@expo/vector-icons'
|
||||||
|
|
||||||
|
import Emojis from './Emojis'
|
||||||
|
|
||||||
|
export default function Reblog ({ name, emojis }) {
|
||||||
|
return (
|
||||||
|
<View style={styles.reblog}>
|
||||||
|
<Feather name='repeat' size={12} color='black' style={styles.icon} />
|
||||||
|
<View style={styles.name}>
|
||||||
|
<Emojis content={name} emojis={emojis} dimension={12} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
reblog: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginBottom: 8
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
marginLeft: 50 - 12,
|
||||||
|
marginRight: 8
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
flexDirection: 'row'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Reblog.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
emojis: Emojis.propTypes.emojis
|
||||||
|
}
|
@ -21,7 +21,6 @@ const Default = ({ dispatch, toots, status, timeline }) => {
|
|||||||
dispatch(fetch({ ...timeline, id: toots[toots.length - 1].id }))
|
dispatch(fetch({ ...timeline, id: toots[toots.length - 1].id }))
|
||||||
}
|
}
|
||||||
onEndReachedThreshold={0.5}
|
onEndReachedThreshold={0.5}
|
||||||
style={{ height: '100%', width: '100%' }}
|
|
||||||
/>
|
/>
|
||||||
{status === 'loading' && <ActivityIndicator />}
|
{status === 'loading' && <ActivityIndicator />}
|
||||||
</>
|
</>
|
||||||
@ -42,12 +41,9 @@ const Notifications = ({ dispatch, toots, status, timeline }) => {
|
|||||||
}
|
}
|
||||||
refreshing={status === 'loading'}
|
refreshing={status === 'loading'}
|
||||||
onEndReached={() =>
|
onEndReached={() =>
|
||||||
dispatch(
|
dispatch(fetch({ ...timeline, id: toots[toots.length - 1].id }))
|
||||||
fetch({ ...timeline, id: toots[toots.length - 1].id })
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
onEndReachedThreshold={0.5}
|
onEndReachedThreshold={0.5}
|
||||||
style={{ height: '100%', width: '100%' }}
|
|
||||||
/>
|
/>
|
||||||
{status === 'loading' && <ActivityIndicator />}
|
{status === 'loading' && <ActivityIndicator />}
|
||||||
</>
|
</>
|
||||||
|
@ -6,8 +6,8 @@ import timelineSlice from 'src/stacks/common/timelineSlice'
|
|||||||
// get site information from local storage and pass to reducers
|
// get site information from local storage and pass to reducers
|
||||||
const preloadedState = {
|
const preloadedState = {
|
||||||
instanceInfo: {
|
instanceInfo: {
|
||||||
current: 'm.cmx.im',
|
current: 'social.xmflsct.com',
|
||||||
currentToken: 'Cxx19XX2VNHnPy_dr_HCHMh4HvwHEvYwWrrU3r3BNzQ',
|
currentToken: 'qjzJ0IjvZ1apsn0_wBkGcdjKgX7Dao9KEPhGwggPwAo',
|
||||||
remote: 'mastodon.social'
|
remote: 'mastodon.social'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user