From d2cc643b9c03c4e47060e11271587ad5a808584f Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Sat, 31 Oct 2020 21:04:46 +0100 Subject: [PATCH] Transform into TypeScript --- App.jsx | 6 - App.tsx | 6 + babel.config.js | 1 + package-lock.json | 121 +++++++++ package.json | 11 +- src/@types/mastodon.d.ts | 244 ++++++++++++++++++ src/@types/store.d.ts | 47 ++++ src/{Index.jsx => Index.tsx} | 7 +- src/api/client.js.backup | 61 ----- src/api/{client.js => client.ts} | 28 +- .../{ParseContent.jsx => ParseContent.tsx} | 53 ++-- .../Toot/{Actioned.jsx => Actioned.tsx} | 27 +- .../Toot/{Actions.jsx => Actions.tsx} | 35 ++- .../Toot/{Attachment.jsx => Attachment.tsx} | 20 +- ...ttachmentImage.jsx => AttachmentImage.tsx} | 26 +- ...ttachmentVideo.jsx => AttachmentVideo.tsx} | 20 +- .../Toot/{Avatar.jsx => Avatar.tsx} | 13 +- src/components/Toot/{Card.jsx => Card.tsx} | 11 +- .../Toot/{Content.jsx => Content.tsx} | 22 +- src/components/Toot/Emojis.jsx | 49 ---- src/components/Toot/Emojis.tsx | 52 ++++ .../Toot/{Header.jsx => Header.tsx} | 38 +-- src/components/Toot/{Poll.jsx => Poll.tsx} | 17 +- ...tNotification.jsx => TootNotification.tsx} | 15 +- .../{TootTimeline.jsx => TootTimeline.tsx} | 24 +- src/components/action.js | 63 ----- src/components/action.ts | 41 +++ src/prop-types/account.js | 37 --- src/prop-types/application.js | 10 - src/prop-types/attachment.js | 19 -- src/prop-types/card.js | 23 -- src/prop-types/emoji.js | 12 - src/prop-types/mention.js | 11 - src/prop-types/notification.js | 17 -- src/prop-types/poll.js | 23 -- src/prop-types/status.js | 51 ---- src/prop-types/tag.js | 10 - src/stacks/{Local.jsx => Local.tsx} | 4 +- src/stacks/{Me.jsx => Me.tsx} | 4 +- .../{Notifications.jsx => Notifications.tsx} | 5 +- src/stacks/{Post.jsx => Post.tsx} | 7 +- src/stacks/{Public.jsx => Public.tsx} | 4 +- .../Shared/{Account.jsx => Account.tsx} | 32 ++- .../Shared/{Hashtag.jsx => Hashtag.tsx} | 14 +- src/stacks/Shared/{Toot.jsx => Toot.tsx} | 14 +- .../Shared/{Webview.jsx => Webview.tsx} | 17 +- .../{sharedScreens.jsx => sharedScreens.tsx} | 8 +- .../common/{Timeline.jsx => Timeline.tsx} | 38 ++- ...inesCombined.jsx => TimelinesCombined.tsx} | 24 +- .../{accountSlice.js => accountSlice.ts} | 18 +- ...tanceInfoSlice.js => instanceInfoSlice.ts} | 0 src/stacks/common/interfaceSlice.js | 0 src/stacks/common/{store.js => store.ts} | 10 +- .../{timelineSlice.js => timelineSlice.ts} | 75 ++++-- .../{localStorage.js => localStorage.ts} | 6 +- .../{relativeTime.js => relativeTime.ts} | 14 +- tsconfig.json | 16 ++ 57 files changed, 935 insertions(+), 646 deletions(-) delete mode 100644 App.jsx create mode 100644 App.tsx create mode 100644 src/@types/mastodon.d.ts create mode 100644 src/@types/store.d.ts rename src/{Index.jsx => Index.tsx} (92%) delete mode 100644 src/api/client.js.backup rename src/api/{client.js => client.ts} (62%) rename src/components/{ParseContent.jsx => ParseContent.tsx} (76%) rename src/components/Toot/{Actioned.jsx => Actioned.tsx} (77%) rename src/components/Toot/{Actions.jsx => Actions.tsx} (68%) rename src/components/Toot/{Attachment.jsx => Attachment.tsx} (83%) rename src/components/Toot/Attachment/{AttachmentImage.jsx => AttachmentImage.tsx} (82%) rename src/components/Toot/Attachment/{AttachmentVideo.jsx => AttachmentVideo.tsx} (81%) rename src/components/Toot/{Avatar.jsx => Avatar.tsx} (78%) rename src/components/Toot/{Card.jsx => Card.tsx} (89%) rename src/components/Toot/{Content.jsx => Content.tsx} (76%) delete mode 100644 src/components/Toot/Emojis.jsx create mode 100644 src/components/Toot/Emojis.tsx rename src/components/Toot/{Header.jsx => Header.tsx} (70%) rename src/components/Toot/{Poll.jsx => Poll.tsx} (77%) rename src/components/{TootNotification.jsx => TootNotification.tsx} (91%) rename src/components/{TootTimeline.jsx => TootTimeline.tsx} (89%) delete mode 100644 src/components/action.js create mode 100644 src/components/action.ts delete mode 100644 src/prop-types/account.js delete mode 100644 src/prop-types/application.js delete mode 100644 src/prop-types/attachment.js delete mode 100644 src/prop-types/card.js delete mode 100644 src/prop-types/emoji.js delete mode 100644 src/prop-types/mention.js delete mode 100644 src/prop-types/notification.js delete mode 100644 src/prop-types/poll.js delete mode 100644 src/prop-types/status.js delete mode 100644 src/prop-types/tag.js rename src/stacks/{Local.jsx => Local.tsx} (83%) rename src/stacks/{Me.jsx => Me.tsx} (91%) rename src/stacks/{Notifications.jsx => Notifications.tsx} (91%) rename src/stacks/{Post.jsx => Post.tsx} (81%) rename src/stacks/{Public.jsx => Public.tsx} (83%) rename src/stacks/Shared/{Account.jsx => Account.tsx} (94%) rename src/stacks/Shared/{Hashtag.jsx => Hashtag.tsx} (79%) rename src/stacks/Shared/{Toot.jsx => Toot.tsx} (80%) rename src/stacks/Shared/{Webview.jsx => Webview.tsx} (55%) rename src/stacks/Shared/{sharedScreens.jsx => sharedScreens.tsx} (90%) rename src/stacks/common/{Timeline.jsx => Timeline.tsx} (76%) rename src/stacks/common/{TimelinesCombined.jsx => TimelinesCombined.tsx} (85%) rename src/stacks/common/{accountSlice.js => accountSlice.ts} (75%) rename src/stacks/common/{instanceInfoSlice.js => instanceInfoSlice.ts} (100%) delete mode 100644 src/stacks/common/interfaceSlice.js rename src/stacks/common/{store.js => store.ts} (73%) rename src/stacks/common/{timelineSlice.js => timelineSlice.ts} (79%) rename src/utils/{localStorage.js => localStorage.ts} (82%) rename src/utils/{relativeTime.js => relativeTime.ts} (59%) create mode 100644 tsconfig.json 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 ({ >