diff --git a/App.jsx b/App.jsx
deleted file mode 100644
index c502efb9..00000000
--- a/App.jsx
+++ /dev/null
@@ -1,6 +0,0 @@
-import React from 'react'
-import Index from './src/Index'
-
-const App = () =>
-
-export default App
diff --git a/App.tsx b/App.tsx
new file mode 100644
index 00000000..0b267d8a
--- /dev/null
+++ b/App.tsx
@@ -0,0 +1,6 @@
+import React from 'react'
+import { Index } from 'src/Index'
+
+const App: React.FC = () =>
+
+export default App
diff --git a/babel.config.js b/babel.config.js
index e5e6c445..fa8c550d 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -4,6 +4,7 @@ module.exports = function (api) {
presets: ['babel-preset-expo'],
plugins: [
['@babel/plugin-proposal-optional-chaining'],
+ ['babel-plugin-typescript-to-proptypes'],
[
'module-resolver',
{
diff --git a/package-lock.json b/package-lock.json
index 158ab384..4689357a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1762,6 +1762,27 @@
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.36.tgz",
"integrity": "sha512-7TUK/k2/QGpEAv/BCwSHlYu3NXZhQ9ZwBYpzr9tjlPIL2C5BeGhH3DmVavRx3ZNyELX5TLC91JTz/cen6AAtIQ=="
},
+ "@types/hoist-non-react-statics": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+ "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ },
+ "dependencies": {
+ "hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "dev": true,
+ "requires": {
+ "react-is": "^16.7.0"
+ }
+ }
+ }
+ },
"@types/istanbul-lib-coverage": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
@@ -1784,6 +1805,82 @@
"@types/istanbul-lib-report": "*"
}
},
+ "@types/prop-types": {
+ "version": "15.7.3",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
+ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
+ "dev": true
+ },
+ "@types/react": {
+ "version": "16.9.55",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.55.tgz",
+ "integrity": "sha512-6KLe6lkILeRwyyy7yG9rULKJ0sXplUsl98MGoCfpteXf9sPWFWWMknDcsvubcpaTdBuxtsLF6HDUwdApZL/xIg==",
+ "dev": true,
+ "requires": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "@types/react-dom": {
+ "version": "16.9.9",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.9.tgz",
+ "integrity": "sha512-jE16FNWO3Logq/Lf+yvEAjKzhpST/Eac8EMd1i4dgZdMczfgqC8EjpxwNgEe3SExHYLliabXDh9DEhhqnlXJhg==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@types/react-native": {
+ "version": "0.63.30",
+ "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.63.30.tgz",
+ "integrity": "sha512-8/PrOjuUaPTCfMeW12ubseZPUGdbRhxYDa/aT+0D0KWVTe60b4H/gJrcfJmBXC6EcCFcimuTzQCv8/S03slYqA==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@types/react-native-htmlview": {
+ "version": "0.12.2",
+ "resolved": "https://registry.npmjs.org/@types/react-native-htmlview/-/react-native-htmlview-0.12.2.tgz",
+ "integrity": "sha512-r5lWdZcZmcxLrfhIAAzBCEpDUuDFRiB5V9d0QvCqhTRh9vorlEjXgyZ5K8/HzbIOuvGb9/mQJPK0rItEAQk0dw==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*",
+ "@types/react-native": "*"
+ }
+ },
+ "@types/react-navigation": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/@types/react-navigation/-/react-navigation-3.4.0.tgz",
+ "integrity": "sha512-Y7F5zU8BTBK8tEOvUqgvwvPZ7+9vnc2UI1vHwJ/9ZJG98TntNv04GWa6lrn4MA4149pqw6cyNw/V49Yd2osAFQ==",
+ "dev": true,
+ "requires": {
+ "react-navigation": "*"
+ }
+ },
+ "@types/react-redux": {
+ "version": "7.1.9",
+ "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.9.tgz",
+ "integrity": "sha512-mpC0jqxhP4mhmOl3P4ipRsgTgbNofMRXJb08Ms6gekViLj61v1hOZEKWDCyWsdONr6EjEA6ZHXC446wdywDe0w==",
+ "dev": true,
+ "requires": {
+ "@types/hoist-non-react-statics": "^3.3.0",
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0",
+ "redux": "^4.0.0"
+ },
+ "dependencies": {
+ "hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "dev": true,
+ "requires": {
+ "react-is": "^16.7.0"
+ }
+ }
+ }
+ },
"@types/stack-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
@@ -2111,6 +2208,18 @@
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz",
"integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ=="
},
+ "babel-plugin-typescript-to-proptypes": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-typescript-to-proptypes/-/babel-plugin-typescript-to-proptypes-1.4.1.tgz",
+ "integrity": "sha512-CxTjlgiB/qjcC8lMTnmgt/6FsviyDbK/m08ImhnY+M45ZFnDL37zU68n5Kaxh1YWmjNex5R3HMzz0cPwthzHgQ==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.10.4",
+ "@babel/helper-plugin-utils": "^7.10.4",
+ "@babel/plugin-syntax-typescript": "^7.10.4",
+ "@babel/types": "^7.11.5"
+ }
+ },
"babel-preset-expo": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-8.3.0.tgz",
@@ -2767,6 +2876,12 @@
"isobject": "^3.0.1"
}
},
+ "csstype": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.4.tgz",
+ "integrity": "sha512-xc8DUsCLmjvCfoD7LTGE0ou2MIWLx0K9RCZwSHMOdynqRsP4MtUcLeqh1HcQ2dInwDTqn+3CE0/FZh1et+p4jA==",
+ "dev": true
+ },
"dayjs": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.9.4.tgz",
@@ -7743,6 +7858,12 @@
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
+ "typescript": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz",
+ "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==",
+ "dev": true
+ },
"ua-parser-js": {
"version": "0.7.22",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
diff --git a/package.json b/package.json
index 4fa26a0f..c3e624b5 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,6 @@
"expo-splash-screen": "~0.6.1",
"expo-status-bar": "~1.0.2",
"ky": "^0.24.0",
- "prop-types": "^15.7.2",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-native": "https://github.com/expo/react-native/archive/sdk-39.0.3.tar.gz",
@@ -44,7 +43,15 @@
"devDependencies": {
"@babel/core": "~7.9.0",
"@babel/plugin-proposal-optional-chaining": "^7.12.1",
- "babel-plugin-module-resolver": "^4.0.0"
+ "@types/react": "^16.9.55",
+ "@types/react-dom": "^16.9.9",
+ "@types/react-native": "^0.63.30",
+ "@types/react-native-htmlview": "^0.12.2",
+ "@types/react-navigation": "^3.4.0",
+ "@types/react-redux": "^7.1.9",
+ "babel-plugin-module-resolver": "^4.0.0",
+ "babel-plugin-typescript-to-proptypes": "^1.4.1",
+ "typescript": "^4.0.5"
},
"private": true
}
diff --git a/src/@types/mastodon.d.ts b/src/@types/mastodon.d.ts
new file mode 100644
index 00000000..3b7c0732
--- /dev/null
+++ b/src/@types/mastodon.d.ts
@@ -0,0 +1,244 @@
+declare namespace mastodon {
+ type Account = {
+ // Base
+ id: string
+ username: string
+ acct: string
+ url: string
+
+ // Attributes
+ display_name: string
+ note?: string
+ avatar: string
+ avatar_static: string
+ header: string
+ header_static: string
+ locked: boolean
+ emojis?: Emoji[]
+ discoverable: boolean
+
+ // Statistics
+ created_at: string
+ last_status_at: string
+ statuses_count: number
+ followers_count: number
+ following_count: number
+
+ // Others
+ moved?: Status
+ fields: Field[]
+ bot: boolean
+ source: Source
+ }
+
+ type Application = {
+ // Base
+ name: string
+ website?: string
+ vapid_key?: string
+ }
+
+ type Attachment = {
+ // Base
+ id: string
+ type: 'unknown' | 'image' | 'gifv' | 'video' | 'audio'
+ url: string
+ preview_url?: string
+
+ // Others
+ remote_url?: string
+ text_url?: string
+ meta: {
+ original: { width: number; height: number; size: string; aspect: number }
+ small: { width: number; height: number; size: string; aspect: number }
+ focus:
+ | { x: number; y: number }
+ | {
+ length: string
+ duration: number
+ fps: number
+ size: string
+ width: number
+ height: number
+ aspect: number
+ audio_encode: string
+ audio_bitrate: string
+ audio_channels: string
+ original: {
+ width: number
+ height: number
+ frame_rate: string
+ duration: number
+ bitrate: number
+ }
+ small: {
+ width: number
+ height: number
+ size: string
+ aspect: number
+ }
+ }
+ | {
+ length: string
+ duration: number
+ fps: number
+ size: string
+ width: number
+ height: number
+ aspect: number
+ original: {
+ width: number
+ height: number
+ frame_rate: string
+ duration: number
+ bitrate: number
+ }
+ small: {
+ width: number
+ height: number
+ size: string
+ aspect: number
+ }
+ }
+ | {
+ length: string
+ duration: number
+ audio_encode: string
+ audio_bitrate: string
+ audio_channels: string
+ original: {
+ duration: number
+ bitrate: number
+ }
+ }
+ }
+ description?: string
+ blurhash?: string
+ }
+
+ type Card = {
+ // Base
+ url: string
+ title: string
+ description: string
+ type: 'link' | 'photo' | 'video' | 'rich'
+
+ // Attributes
+ author_name: string
+ author_url: string
+ provider_name: string
+ provider_url: string
+ html: string
+ width: number
+ height: number
+ image: string
+ embed_url: string
+ blurhash: string
+ }
+
+ type Emoji = {
+ // Base
+ shortcode: string
+ url: string
+ static_url: string
+ visible_in_picker: boolean
+ category?: string
+ }
+
+ type Field = {
+ name: string
+ value: string
+ verified_at?: string
+ }
+
+ type Mention = {
+ // Base
+ id: string
+ username: string
+ acct: string
+ url: string
+ }
+
+ type Notification = {
+ // Base
+ id: string
+ type: 'follow' | 'mention' | 'reblog' | 'favourite' | 'poll'
+ created_at: string
+ account: Account
+
+ // Others
+ status?: Status
+ }
+
+ type Poll = {
+ // Base
+ id: string
+ expires_at: string
+ expired: boolean
+ multiple: bool
+ votes_count: number
+ voters_count: number
+ voted?: boolean
+ own_votes?: number[]
+ options: { title: string; votes_count: number }[]
+ emojis: Emoji[]
+ }
+
+ type Status = {
+ // Base
+ id: string
+ urk: string
+ created_at: string
+ account: Account
+ content: string
+ visibility: 'public' | 'unlisted' | 'private' | 'direct'
+ sensitive: boolean
+ spoiler_text?: string
+ media_attachments: Attachment[]
+ application: Application
+
+ // Attributes
+ mentions: Mention[]
+ tags: Tag[]
+ emojis: Emoji[]
+
+ // Interaction
+ reblogs_count: number
+ favourites_count: number
+ replies_count: number
+ favourited: boolean
+ reblogged: boolean
+ muted: boolean
+ bookmarked: boolean
+ pinned: boolean
+
+ // Others
+ url?: string
+ in_reply_to_id?: string
+ in_reply_to_account_id?: string
+ reblog: Status
+ poll: Poll
+ card: Card
+ language?: string
+ text?: string
+ }
+
+ type Source = {
+ // Base
+ note: string
+ fields: Field[]
+
+ // Others
+ privacy?: 'public' | 'unlisted' | 'private' | 'direct'
+ sensitive?: boolean
+ language?: string
+ follow_requests_count?: number
+ }
+
+ type Tag = {
+ // Base
+ name: string
+ url: string
+ // history: types
+ }
+}
diff --git a/src/@types/store.d.ts b/src/@types/store.d.ts
new file mode 100644
index 00000000..61bd1b17
--- /dev/null
+++ b/src/@types/store.d.ts
@@ -0,0 +1,47 @@
+declare namespace store {
+ type AsyncStatus = 'idle' | 'loading' | 'succeeded' | 'failed'
+
+ type InstanceInfoState = {
+ local: string
+ localToken: string
+ remote: string
+ }
+
+ type TimelinePage =
+ | 'Following'
+ | 'Local'
+ | 'LocalPublic'
+ | 'RemotePublic'
+ | 'Notifications'
+ | 'Hashtag'
+ | 'List'
+ | 'Toot'
+ | 'Account_Default'
+ | 'Account_All'
+ | 'Account_Media'
+
+ type TimelineState = {
+ toots: mastodon.Status[] | []
+ pointer?: string
+ status: AsyncStatus
+ }
+
+ type TimelinesState = {
+ Following: TimelineState
+ Local: TimelineState
+ LocalPublic: TimelineState
+ RemotePublic: TimelineState
+ Notifications: TimelineState
+ Hashtag: TimelineState
+ List: TimelineState
+ Toot: TimelineState
+ Account_Default: TimelineState
+ Account_All: TimelineState
+ Account_Media: TimelineState
+ }
+
+ type AccountState = {
+ account: mastodon.Account | {}
+ status: AsyncStatus
+ }
+}
diff --git a/src/Index.jsx b/src/Index.tsx
similarity index 92%
rename from src/Index.jsx
rename to src/Index.tsx
index 4594210b..f48d87d7 100644
--- a/src/Index.jsx
+++ b/src/Index.tsx
@@ -18,7 +18,7 @@ import Me from 'src/stacks/Me'
enableScreens()
const Tab = createBottomTabNavigator()
-export default function Index () {
+export const Index: React.FC = () => {
return (
@@ -26,7 +26,7 @@ export default function Index () {
({
tabBarIcon: ({ focused, color, size }) => {
- let name
+ let name: string
switch (route.name) {
case 'Local':
name = 'home'
@@ -43,6 +43,9 @@ export default function Index () {
case 'Me':
name = focused ? 'smile' : 'meh'
break
+ default:
+ name = 'alert-octagon'
+ break
}
return
}
diff --git a/src/api/client.js.backup b/src/api/client.js.backup
deleted file mode 100644
index b6e07dd2..00000000
--- a/src/api/client.js.backup
+++ /dev/null
@@ -1,61 +0,0 @@
-import store from 'src/stacks/common/store'
-
-export async function client (instance, query, { body, ...customConfig } = {}) {
- const state = store.getState().instanceInfo
-
- let url
- let authHeader
- switch (instance.type) {
- case 'local':
- url = `https://${state.local}/${instance.endpoint}`
- authHeader = {
- Authorization: `Bearer ${state.localToken}`
- }
- break
- case 'remote':
- url = `https://${state.remote}/${instance.endpoint}`
- authHeader = {}
- break
- default:
- return Promise.reject('Instance type is not defined.')
- }
-
- const headers = { 'Content-Type': 'application/json', ...authHeader }
-
- const config = {
- method: body ? 'POST' : 'GET',
- ...customConfig,
- headers: {
- ...headers,
- ...customConfig.headers
- }
- }
-
- const queryString = query
- ? `?${query.map(({ key, value }) => `${key}=${value}`).join('&')}`
- : ''
-
- if (body) {
- config.body = JSON.stringify(body)
- }
-
- let data
- try {
- const response = await fetch(`${url}${queryString}`, config)
- data = await response.json()
- if (response.ok) {
- return { headers: response.headers, body: data }
- }
- throw new Error(response.statusText)
- } catch (err) {
- return Promise.reject(err.message ? err.message : data)
- }
-}
-
-client.get = function (instance, endpoint, query, customConfig = {}) {
- return client(instance, endpoint, query, { ...customConfig, method: 'GET' })
-}
-
-client.post = function (instance, endpoint, query, body, customConfig = {}) {
- return client(instance, endpoint, query, { ...customConfig, body })
-}
diff --git a/src/api/client.js b/src/api/client.ts
similarity index 62%
rename from src/api/client.js
rename to src/api/client.ts
index 1d22de22..c490a1ad 100644
--- a/src/api/client.js
+++ b/src/api/client.ts
@@ -1,14 +1,22 @@
-import store from 'src/stacks/common/store'
+import store, { RootState } from 'src/stacks/common/store'
import ky from 'ky'
-export default async function client ({
- method, // * get / post
- instance, // * local / remote
- endpoint, // * if url is empty
- query, // object
- body // object
-}) {
- const state = store.getState().instanceInfo
+const client = async ({
+ method,
+ instance,
+ endpoint,
+ query,
+ body
+}: {
+ method: 'get' | 'post'
+ instance: 'local' | 'remote'
+ endpoint: string
+ query?: {
+ [key: string]: string | number | boolean
+ }
+ body?: object
+}): Promise => {
+ const state: RootState['instanceInfo'] = store.getState().instanceInfo
let response
try {
@@ -38,3 +46,5 @@ export default async function client ({
return Promise.reject({ body: response.error_message })
}
}
+
+export default client
diff --git a/src/components/ParseContent.jsx b/src/components/ParseContent.tsx
similarity index 76%
rename from src/components/ParseContent.jsx
rename to src/components/ParseContent.tsx
index 71e1db91..fd2df16d 100644
--- a/src/components/ParseContent.jsx
+++ b/src/components/ParseContent.tsx
@@ -1,14 +1,23 @@
import React from 'react'
-import PropTypes from 'prop-types'
-import propTypesEmoji from 'src/prop-types/emoji'
-import propTypesMention from 'src/prop-types/mention'
import { StyleSheet, Text } from 'react-native'
-import HTMLView from 'react-native-htmlview'
+import HTMLView, { HTMLViewNode } from 'react-native-htmlview'
import { useNavigation } from '@react-navigation/native'
import Emojis from 'src/components/Toot/Emojis'
-function renderNode ({ node, index, navigation, mentions, showFullLink }) {
+const renderNode = ({
+ node,
+ index,
+ navigation,
+ mentions,
+ showFullLink
+}: {
+ node: HTMLViewNode
+ index: number
+ navigation: object
+ mentions?: mastodon.Mention[]
+ showFullLink: boolean
+}) => {
if (node.name == 'a') {
const classes = node.attribs.class
const href = node.attribs.href
@@ -69,27 +78,40 @@ function renderNode ({ node, index, navigation, mentions, showFullLink }) {
}
}
-export default function ParseContent ({
+export interface Props {
+ content: string
+ emojis?: mastodon.Emoji[]
+ emojiSize?: number
+ mentions?: mastodon.Mention[]
+ showFullLink?: boolean
+ linesTruncated?: number
+}
+
+const ParseContent: React.FC = ({
content,
emojis,
emojiSize = 14,
mentions,
showFullLink = false,
linesTruncated = 10
-}) {
+}) => {
const navigation = useNavigation()
return (
renderNode({ node, index, navigation, mentions, showFullLink })
}
- TextComponent={({ children }) => (
-
- )}
+ TextComponent={({ children }) =>
+ emojis ? (
+
+ ) : (
+ {children}
+ )
+ }
RootComponent={({ children }) => {
return {children}
}}
@@ -109,11 +131,4 @@ const HTMLstyles = StyleSheet.create({
}
})
-ParseContent.propTypes = {
- content: PropTypes.string.isRequired,
- emojis: PropTypes.arrayOf(propTypesEmoji),
- emojiSize: PropTypes.number,
- mentions: PropTypes.arrayOf(propTypesMention),
- showFullLink: PropTypes.bool,
- linesTruncated: PropTypes.number
-}
+export default ParseContent
diff --git a/src/components/Toot/Actioned.jsx b/src/components/Toot/Actioned.tsx
similarity index 77%
rename from src/components/Toot/Actioned.jsx
rename to src/components/Toot/Actioned.tsx
index e0554702..61209830 100644
--- a/src/components/Toot/Actioned.jsx
+++ b/src/components/Toot/Actioned.tsx
@@ -1,17 +1,22 @@
import React from 'react'
-import PropTypes from 'prop-types'
-import propTypesEmoji from 'src/prop-types/emoji'
import { StyleSheet, Text, View } from 'react-native'
import { Feather } from '@expo/vector-icons'
import Emojis from './Emojis'
-export default function Actioned ({
+export interface Props {
+ action: 'favourite' | 'follow' | 'mention' | 'poll' | 'reblog'
+ name?: string
+ emojis?: mastodon.Emoji[]
+ notification?: boolean
+}
+
+const Actioned: React.FC = ({
action,
name,
emojis,
notification = false
-}) {
+}) => {
let icon
let content
switch (action) {
@@ -51,7 +56,11 @@ export default function Actioned ({
{icon}
{content ? (
-
+ {emojis ? (
+
+ ) : (
+ {content}
+ )}
) : (
<>>
@@ -74,10 +83,4 @@ const styles = StyleSheet.create({
}
})
-Actioned.propTypes = {
- action: PropTypes.oneOf(['favourite', 'follow', 'mention', 'poll', 'reblog'])
- .isRequired,
- name: PropTypes.string,
- emojis: PropTypes.arrayOf(propTypesEmoji),
- notification: PropTypes.bool
-}
+export default Actioned
diff --git a/src/components/Toot/Actions.jsx b/src/components/Toot/Actions.tsx
similarity index 68%
rename from src/components/Toot/Actions.jsx
rename to src/components/Toot/Actions.tsx
index c1f997ee..db7caaaf 100644
--- a/src/components/Toot/Actions.jsx
+++ b/src/components/Toot/Actions.tsx
@@ -1,18 +1,26 @@
import React from 'react'
-import PropTypes from 'prop-types'
import { Pressable, StyleSheet, Text, View } from 'react-native'
import { Feather } from '@expo/vector-icons'
import action from 'src/components/action'
-export default function Actions ({
+export interface Props {
+ id: string
+ replies_count: number
+ reblogs_count: number
+ reblogged?: boolean
+ favourites_count: number
+ favourited?: boolean
+}
+
+const Actions: React.FC = ({
id,
replies_count,
reblogs_count,
reblogged,
favourites_count,
favourited
-}) {
+}) => {
return (
@@ -23,7 +31,17 @@ export default function Actions ({
{reblogs_count}
- action('favourite', id)}>
+
+ action({
+ id,
+ type: 'favourite',
+ stateKey: 'favourited',
+ statePrev: favourited || false
+ })
+ }
+ >
{favourites_count}
@@ -49,11 +67,4 @@ const styles = StyleSheet.create({
}
})
-Actions.propTypes = {
- id: PropTypes.string.isRequired,
- replies_count: PropTypes.number.isRequired,
- reblogs_count: PropTypes.number.isRequired,
- reblogged: PropTypes.bool.isRequired,
- favourites_count: PropTypes.number.isRequired,
- favourited: PropTypes.bool.isRequired
-}
+export default Actions
diff --git a/src/components/Toot/Attachment.jsx b/src/components/Toot/Attachment.tsx
similarity index 83%
rename from src/components/Toot/Attachment.jsx
rename to src/components/Toot/Attachment.tsx
index ef44da58..b33439f9 100644
--- a/src/components/Toot/Attachment.jsx
+++ b/src/components/Toot/Attachment.tsx
@@ -1,12 +1,20 @@
import React from 'react'
-import PropTypes from 'prop-types'
-import propTypesAttachment from 'src/prop-types/attachment'
import { Text, View } from 'react-native'
import AttachmentImage from './Attachment/AttachmentImage'
import AttachmentVideo from './Attachment/AttachmentVideo'
-export default function Attachment ({ media_attachments, sensitive, width }) {
+export interface Props {
+ media_attachments: mastodon.Attachment[]
+ sensitive: boolean
+ width: number
+}
+
+const Attachment: React.FC = ({
+ media_attachments,
+ sensitive,
+ width
+}) => {
let attachment
let attachmentHeight
// if (width) {}
@@ -74,8 +82,4 @@ export default function Attachment ({ media_attachments, sensitive, width }) {
)
}
-Attachment.propTypes = {
- media_attachments: PropTypes.arrayOf(propTypesAttachment),
- sensitive: PropTypes.bool.isRequired,
- width: PropTypes.number.isRequired
-}
+export default Attachment
diff --git a/src/components/Toot/Attachment/AttachmentImage.jsx b/src/components/Toot/Attachment/AttachmentImage.tsx
similarity index 82%
rename from src/components/Toot/Attachment/AttachmentImage.jsx
rename to src/components/Toot/Attachment/AttachmentImage.tsx
index 1c2eda89..baaaba61 100644
--- a/src/components/Toot/Attachment/AttachmentImage.jsx
+++ b/src/components/Toot/Attachment/AttachmentImage.tsx
@@ -1,10 +1,18 @@
import React, { useEffect, useState } from 'react'
-import PropTypes from 'prop-types'
-import propTypesAttachment from 'src/prop-types/attachment'
import { Button, Image, Modal, StyleSheet, Pressable, View } from 'react-native'
import ImageViewer from 'react-native-image-zoom-viewer'
-export default function AttachmentImage ({ media_attachments, sensitive, width }) {
+export interface Props {
+ media_attachments: mastodon.Attachment[]
+ sensitive: boolean
+ width: number
+}
+
+const AttachmentImage: React.FC = ({
+ media_attachments,
+ sensitive,
+ width
+}) => {
const [mediaSensitive, setMediaSensitive] = useState(sensitive)
const [imageModalVisible, setImageModalVisible] = useState(false)
const [imageModalIndex, setImageModalIndex] = useState(0)
@@ -16,8 +24,8 @@ export default function AttachmentImage ({ media_attachments, sensitive, width }
}
}, [mediaSensitive])
- let images = []
- media_attachments = media_attachments.map((m, i) => {
+ let images: { url: string; width: number; height: number }[] = []
+ const imagesNode = media_attachments.map((m, i) => {
images.push({
url: m.url,
width: m.meta.original.width,
@@ -44,7 +52,7 @@ export default function AttachmentImage ({ media_attachments, sensitive, width }
return (
<>
- {media_attachments}
+ {imagesNode}
{mediaSensitive && (
= ({
media_attachments,
sensitive,
width
-}) {
+}) => {
const videoPlayer = useRef()
const [mediaSensitive, setMediaSensitive] = useState(sensitive)
const [videoPlay, setVideoPlay] = useState(false)
@@ -28,7 +32,7 @@ export default function AttachmentVideo ({
>