diff --git a/.eslintrc.js b/.eslintrc.js index f4b59595a..193d390bd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -254,18 +254,12 @@ module.exports = { }, overrides: [ { - files: ['**/*.tsx'], + files: ['**/*.ts', '**/*.tsx'], rules: { + 'no-undef': 'off', // https://stackoverflow.com/a/69155899 'react/prop-types': 'off', }, - }, - // Disable no-undef in TypeScript - // https://stackoverflow.com/a/69155899 - { - files: ['*.ts', '*.tsx'], - rules: { - 'no-undef': 'off', - }, + parser: '@typescript-eslint/parser', }, ], }; diff --git a/.gitignore b/.gitignore index d2c20b8ef..1d36a4816 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,7 @@ yarn-error.log* !/custom/**/.gitkeep # surge.sh -CNAME -AUTH -CORS -ROUTER +/CNAME +/AUTH +/CORS +/ROUTER diff --git a/README.md b/README.md index a3a4e8ddc..6068126d3 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,14 @@ ![Soapbox FE Screenshot](soapbox-screenshot.png) -**Soapbox FE** is a frontend for Pleroma with a focus on custom branding and ease of use. +**Soapbox FE** is a frontend for Mastodon and Pleroma with a focus on custom branding and ease of use. It's part of the [Soapbox](https://soapbox.pub) project. -# :rocket: Deploy on Pleroma +## Try it out + +Visit https://fe.soapbox.pub/ and point it to your favorite instance. + +## :rocket: Deploy on Pleroma Installing Soapbox FE on an existing Pleroma server is extremely easy. Just ssh into the server and download a .zip of the latest build: diff --git a/app/images/avatar-missing.png b/app/images/avatar-missing.png index b3e6b5709..6de33a5a4 100644 Binary files a/app/images/avatar-missing.png and b/app/images/avatar-missing.png differ diff --git a/app/images/avatar-missing.svg b/app/images/avatar-missing.svg index 7eb156089..d5833c90c 100644 --- a/app/images/avatar-missing.svg +++ b/app/images/avatar-missing.svg @@ -1,116 +1,30 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - + xmlns:dc="http://purl.org/dc/elements/1.1/">image/svg+xml diff --git a/app/images/header-missing.png b/app/images/header-missing.png new file mode 100644 index 000000000..26b59e75a Binary files /dev/null and b/app/images/header-missing.png differ diff --git a/app/soapbox/__fixtures__/mastodon-account.json b/app/soapbox/__fixtures__/mastodon-account.json new file mode 100644 index 000000000..7a00340bf --- /dev/null +++ b/app/soapbox/__fixtures__/mastodon-account.json @@ -0,0 +1,23 @@ +{ + "id": "106801667066418367", + "username": "benis911", + "acct": "benis911", + "display_name": "", + "locked": false, + "bot": false, + "discoverable": null, + "group": false, + "created_at": "2021-08-22T00:00:00.000Z", + "note": "", + "url": "https://mastodon.social/@benis911", + "avatar": "https://mastodon.social/avatars/original/missing.png", + "avatar_static": "https://mastodon.social/avatars/original/missing.png", + "header": "https://mastodon.social/headers/original/missing.png", + "header_static": "https://mastodon.social/headers/original/missing.png", + "followers_count": 1, + "following_count": 0, + "statuses_count": 5, + "last_status_at": "2022-02-23", + "emojis": [], + "fields": [] +} diff --git a/app/soapbox/__fixtures__/mastodon-instance-rc.json b/app/soapbox/__fixtures__/mastodon-instance-rc.json new file mode 100644 index 000000000..277839d14 --- /dev/null +++ b/app/soapbox/__fixtures__/mastodon-instance-rc.json @@ -0,0 +1,123 @@ +{ + "uri": "mastodon.social", + "title": "Mastodon", + "short_description": "Server run by the main developers of the project \"🐘\" It is not focused on any particular niche interest - everyone is welcome as long as you follow our code of conduct!", + "description": "Server run by the main developers of the project \"🐘\" It is not focused on any particular niche interest - everyone is welcome as long as you follow our code of conduct!", + "email": "staff@mastodon.social", + "version": "3.5.0rc1", + "urls": { + "streaming_api": "wss://mastodon.social" + }, + "stats": { + "user_count": 635078, + "status_count": 34700866, + "domain_count": 21989 + }, + "thumbnail": "https://files.mastodon.social/site_uploads/files/000/000/001/original/vlcsnap-2018-08-27-16h43m11s127.png", + "languages": [ + "en" + ], + "registrations": true, + "approval_required": false, + "invites_enabled": true, + "configuration": { + "statuses": { + "max_characters": 500, + "max_media_attachments": 4, + "characters_reserved_per_url": 23 + }, + "media_attachments": { + "supported_mime_types": [ + "image/jpeg", + "image/png", + "image/gif", + "video/webm", + "video/mp4", + "video/quicktime", + "video/ogg", + "audio/wave", + "audio/wav", + "audio/x-wav", + "audio/x-pn-wave", + "audio/ogg", + "audio/vorbis", + "audio/mpeg", + "audio/mp3", + "audio/webm", + "audio/flac", + "audio/aac", + "audio/m4a", + "audio/x-m4a", + "audio/mp4", + "audio/3gpp", + "video/x-ms-asf" + ], + "image_size_limit": 10485760, + "image_matrix_limit": 16777216, + "video_size_limit": 41943040, + "video_frame_rate_limit": 60, + "video_matrix_limit": 2304000 + }, + "polls": { + "max_options": 4, + "max_characters_per_option": 50, + "min_expiration": 300, + "max_expiration": 2629746 + } + }, + "contact_account": { + "id": "1", + "username": "Gargron", + "acct": "Gargron", + "display_name": "Eugen", + "locked": false, + "bot": false, + "discoverable": true, + "group": false, + "created_at": "2016-03-16T00:00:00.000Z", + "note": "

Founder, CEO and lead developer @Mastodon, Germany.

", + "url": "https://mastodon.social/@Gargron", + "avatar": "https://files.mastodon.social/accounts/avatars/000/000/001/original/ccb05a778962e171.png", + "avatar_static": "https://files.mastodon.social/accounts/avatars/000/000/001/original/ccb05a778962e171.png", + "header": "https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg", + "header_static": "https://files.mastodon.social/accounts/headers/000/000/001/original/3b91c9965d00888b.jpeg", + "followers_count": 99760, + "following_count": 274, + "statuses_count": 71657, + "last_status_at": "2022-03-17", + "emojis": [], + "fields": [ + { + "name": "Patreon", + "value": "https://www.patreon.com/mastodon", + "verified_at": null + } + ] + }, + "rules": [ + { + "id": "1", + "text": "Sexually explicit or violent media must be marked as sensitive when posting" + }, + { + "id": "2", + "text": "No racism, sexism, homophobia, transphobia, xenophobia, or casteism" + }, + { + "id": "3", + "text": "No incitement of violence or promotion of violent ideologies" + }, + { + "id": "4", + "text": "No harassment, dogpiling or doxxing of other users" + }, + { + "id": "5", + "text": "No content illegal in Germany" + }, + { + "id": "7", + "text": "Do not share intentionally false or misleading information" + } + ] +} diff --git a/app/soapbox/__fixtures__/pleroma-notification-move.json b/app/soapbox/__fixtures__/pleroma-notification-move.json new file mode 100644 index 000000000..d7a763457 --- /dev/null +++ b/app/soapbox/__fixtures__/pleroma-notification-move.json @@ -0,0 +1,119 @@ +{ + "account": { + "acct": "alex@fedibird.com", + "avatar": "https://gleasonator.com/images/avi.png", + "avatar_static": "https://gleasonator.com/images/avi.png", + "bot": false, + "created_at": "2022-01-24T21:25:37.000Z", + "display_name": "alex@fedibird.com", + "emojis": [], + "fields": [], + "followers_count": 2, + "following_count": 1, + "fqn": "alex@fedibird.com", + "header": "https://gleasonator.com/images/banner.png", + "header_static": "https://gleasonator.com/images/banner.png", + "id": "AFmHQ18XZ7Lco68MW8", + "last_status_at": "2022-03-16T22:07:53", + "locked": false, + "note": "

", + "pleroma": { + "accepts_chat_messages": null, + "also_known_as": [], + "ap_id": "https://fedibird.com/users/alex", + "background_image": null, + "birthday": "1993-07-03", + "deactivated": false, + "favicon": "https://gleasonator.com/proxy/HzfsidHss3CuA7aM2zxXN-tAjF8/aHR0cHM6Ly9mZWRpYmlyZC5jb20vZmF2aWNvbi5pY28/favicon.ico", + "hide_favorites": true, + "hide_followers": false, + "hide_followers_count": false, + "hide_follows": false, + "hide_follows_count": false, + "is_admin": false, + "is_confirmed": true, + "is_moderator": false, + "is_suggested": false, + "location": "Texas, USA", + "relationship": {}, + "skip_thread_containment": false, + "tags": [] + }, + "source": { + "fields": [], + "note": "", + "pleroma": { + "actor_type": "Person", + "discoverable": false + }, + "sensitive": false + }, + "statuses_count": 5, + "url": "https://fedibird.com/@alex", + "username": "alex" + }, + "created_at": "2022-03-17T00:08:48.000Z", + "id": "406814", + "pleroma": { + "is_muted": false, + "is_seen": true + }, + "target": { + "acct": "benis911", + "avatar": "https://gleasonator.com/images/avi.png", + "avatar_static": "https://gleasonator.com/images/avi.png", + "bot": false, + "created_at": "2021-03-26T20:42:11.000Z", + "display_name": "benis911", + "emojis": [], + "fields": [], + "followers_count": 0, + "following_count": 0, + "fqn": "benis911@gleasonator.com", + "header": "https://media.gleasonator.com/fc595bbbcf5aabefecd1c2adfe5b7f5457db59847992881668653a0338ba25bd.jpg", + "header_static": "https://media.gleasonator.com/fc595bbbcf5aabefecd1c2adfe5b7f5457db59847992881668653a0338ba25bd.jpg", + "id": "A5c5LK7EJTFR0u26Pg", + "last_status_at": "2022-03-16T22:01:57", + "locked": false, + "note": "hello world 2", + "pleroma": { + "accepts_chat_messages": true, + "also_known_as": [ + "https://gleasonator.com/users/alex", + "https://poa.st/users/alex", + "https://fedibird.com/users/alex" + ], + "ap_id": "https://gleasonator.com/users/benis911", + "background_image": null, + "birthday": "2000-01-25", + "deactivated": false, + "favicon": "https://gleasonator.com/favicon.png", + "hide_favorites": true, + "hide_followers": true, + "hide_followers_count": true, + "hide_follows": true, + "hide_follows_count": true, + "is_admin": false, + "is_confirmed": true, + "is_moderator": false, + "is_suggested": false, + "location": null, + "relationship": {}, + "skip_thread_containment": false, + "tags": [] + }, + "source": { + "fields": [], + "note": "hello world 2", + "pleroma": { + "actor_type": "Person", + "discoverable": false + }, + "sensitive": false + }, + "statuses_count": 172, + "url": "https://gleasonator.com/users/benis911", + "username": "benis911" + }, + "type": "move" +} diff --git a/app/soapbox/__fixtures__/pleroma-status-deleted.json b/app/soapbox/__fixtures__/pleroma-status-deleted.json new file mode 100644 index 000000000..2d37af257 --- /dev/null +++ b/app/soapbox/__fixtures__/pleroma-status-deleted.json @@ -0,0 +1,229 @@ +{ + "account": { + "acct": "alex", + "avatar": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png", + "avatar_static": "https://media.gleasonator.com/6d64aecb17348b23aaff78db4687b9476cb0da1c07cc6a819c2e6ec7144c18b1.png", + "bot": false, + "created_at": "2020-01-08T01:25:43.000Z", + "display_name": "Alex Gleason", + "emojis": [], + "fields": [ + { + "name": "Website", + "value": "https://alexgleason.me" + }, + { + "name": "Soapbox", + "value": "https://soapbox.pub" + }, + { + "name": "Email", + "value": "alex@alexgleason.me" + }, + { + "name": "Gender identity", + "value": "Soyboy" + }, + { + "name": "Donate (PayPal)", + "value": "https://paypal.me/gleasonator" + }, + { + "name": "$BTC", + "value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n" + }, + { + "name": "$ETH", + "value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717" + }, + { + "name": "$DOGE", + "value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D" + }, + { + "name": "$XMR", + "value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK" + } + ], + "follow_requests_count": 0, + "followers_count": 2489, + "following_count": 1586, + "fqn": "alex@gleasonator.com", + "header": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png", + "header_static": "https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png", + "id": "9v5bmRalQvjOy0ECcC", + "last_status_at": "2022-03-16T21:57:17", + "locked": false, + "note": "I create Fediverse software that empowers people online.

I'm vegan btw

Note: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.", + "pleroma": { + "accepts_chat_messages": true, + "accepts_email_list": true, + "allow_following_move": true, + "also_known_as": [ + "https://mitra.social/users/alex" + ], + "ap_id": "https://gleasonator.com/users/alex", + "background_image": null, + "birthday": "1993-07-03", + "deactivated": false, + "email": "alex@alexgleason.me", + "favicon": "https://gleasonator.com/favicon.png", + "hide_favorites": true, + "hide_followers": false, + "hide_followers_count": false, + "hide_follows": false, + "hide_follows_count": false, + "is_admin": true, + "is_confirmed": true, + "is_moderator": false, + "is_suggested": true, + "location": null, + "notification_settings": { + "block_from_strangers": false, + "hide_notification_contents": false + }, + "relationship": {}, + "skip_thread_containment": false, + "tags": [], + "unread_conversation_count": 392, + "unread_notifications_count": 2 + }, + "source": { + "fields": [ + { + "name": "Website", + "value": "https://alexgleason.me" + }, + { + "name": "Soapbox", + "value": "https://soapbox.pub" + }, + { + "name": "Email", + "value": "alex@alexgleason.me" + }, + { + "name": "Gender identity", + "value": "Soyboy" + }, + { + "name": "Donate (PayPal)", + "value": "https://paypal.me/gleasonator" + }, + { + "name": "$BTC", + "value": "bc1q9cx35adpm73aq2fw40ye6ts8hfxqzjr5unwg0n" + }, + { + "name": "$ETH", + "value": "0xAc9aB5Fc04Dc1cB1789Af75b523Bd23C70B2D717" + }, + { + "name": "$DOGE", + "value": "D5zVZs6jrRakaPVGiErkQiHt9sayzm6V5D" + }, + { + "name": "$XMR", + "value": "45JDCLrjJ4bgVUSbbs2yjy9m5Mf4VLPW8fG7jw9sq5u69rXZZopQogZNeyYkMBnXpkaip4p4QwaaJNhdTotPa9g44DBCzdK" + } + ], + "note": "I create Fediverse software that empowers people online.\r\n\r\nI'm vegan btw\r\n\r\nNote: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.", + "pleroma": { + "actor_type": "Person", + "discoverable": false, + "no_rich_text": false, + "show_birthday": true, + "show_role": true + }, + "privacy": "public", + "sensitive": false + }, + "statuses_count": 23695, + "url": "https://gleasonator.com/users/alex", + "username": "alex" + }, + "application": { + "name": "Soapbox FE", + "website": "https://soapbox.pub/" + }, + "bookmarked": false, + "card": null, + "content": "

I am going to delete this post for testing purposes

", + "created_at": "2022-03-16T21:57:16.000Z", + "emojis": [], + "favourited": false, + "favourites_count": 3, + "id": "AHU2RrX0wdcwzCYjFQ", + "in_reply_to_account_id": null, + "in_reply_to_id": null, + "language": null, + "media_attachments": [ + { + "blurhash": "eWGlL@?b~q%MRj4nt7IUof%M%MIURjRjIUM{IUM{Rjayxut7j[j[xu", + "description": "", + "id": "508107650", + "meta": { + "original": { + "aspect": 1, + "height": 1024, + "width": 1024 + } + }, + "pleroma": { + "mime_type": "image/png" + }, + "preview_url": "https://media.gleasonator.com/2b9ddcd8b27cad786fd34bc2cfe02c1b63aa1b8e7b8d72379b5c9375fb61f199.png", + "remote_url": "https://media.gleasonator.com/2b9ddcd8b27cad786fd34bc2cfe02c1b63aa1b8e7b8d72379b5c9375fb61f199.png", + "text_url": "https://media.gleasonator.com/2b9ddcd8b27cad786fd34bc2cfe02c1b63aa1b8e7b8d72379b5c9375fb61f199.png", + "type": "image", + "url": "https://media.gleasonator.com/2b9ddcd8b27cad786fd34bc2cfe02c1b63aa1b8e7b8d72379b5c9375fb61f199.png" + } + ], + "mentions": [], + "muted": false, + "pinned": false, + "pleroma": { + "content": { + "text/plain": "I am going to delete this post for testing purposes" + }, + "content_type": "text/markdown", + "conversation_id": "AHU2RrUB7BMIqPESpM", + "direct_conversation_id": null, + "emoji_reactions": [ + { + "count": 1, + "me": false, + "name": "😭" + }, + { + "count": 1, + "me": false, + "name": "❔" + } + ], + "expires_at": null, + "in_reply_to_account_acct": null, + "local": true, + "parent_visible": false, + "pinned_at": null, + "quote": null, + "quote_url": null, + "quote_visible": false, + "spoiler_text": { + "text/plain": "" + }, + "thread_muted": false + }, + "poll": null, + "reblog": null, + "reblogged": false, + "reblogs_count": 1, + "replies_count": 2, + "sensitive": false, + "spoiler_text": "", + "tags": [], + "text": "I am going to delete this post for testing purposes", + "uri": "https://gleasonator.com/objects/205ec868-d28d-4668-a56a-33321f7e285e", + "url": "https://gleasonator.com/notice/AHU2RrX0wdcwzCYjFQ", + "visibility": "public" +} diff --git a/app/soapbox/__mocks__/api.js b/app/soapbox/__mocks__/api.js deleted file mode 100644 index fcb3a3253..000000000 --- a/app/soapbox/__mocks__/api.js +++ /dev/null @@ -1,26 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; - -const api = jest.requireActual('../api'); -let mocks = []; - -export const __stub = func => mocks.push(func); -export const __clear = () => mocks = []; - -const setupMock = axios => { - const mock = new MockAdapter(axios); - mocks.map(func => func(mock)); -}; - -export const staticClient = api.staticClient; - -export const baseClient = (...params) => { - const axios = api.baseClient(...params); - setupMock(axios); - return axios; -}; - -export default (...params) => { - const axios = api.default(...params); - setupMock(axios); - return axios; -}; diff --git a/app/soapbox/__mocks__/api.ts b/app/soapbox/__mocks__/api.ts new file mode 100644 index 000000000..2f3a0d7b8 --- /dev/null +++ b/app/soapbox/__mocks__/api.ts @@ -0,0 +1,28 @@ +import { jest } from '@jest/globals'; +import { AxiosInstance } from 'axios'; +import MockAdapter from 'axios-mock-adapter'; + +const api = jest.requireActual('../api') as Record; +let mocks: Array = []; + +export const __stub = (func: Function) => mocks.push(func); +export const __clear = (): Function[] => mocks = []; + +const setupMock = (axios: AxiosInstance) => { + const mock = new MockAdapter(axios); + mocks.map(func => func(mock)); +}; + +export const staticClient = api.staticClient; + +export const baseClient = (...params: any[]) => { + const axios = api.baseClient(...params); + setupMock(axios); + return axios; +}; + +export default (...params: any[]) => { + const axios = api.default(...params); + setupMock(axios); + return axios; +}; diff --git a/app/soapbox/actions/__tests__/statuses-test.js b/app/soapbox/actions/__tests__/statuses-test.js index 71a0596a4..aa1ac9f3d 100644 --- a/app/soapbox/actions/__tests__/statuses-test.js +++ b/app/soapbox/actions/__tests__/statuses-test.js @@ -1,8 +1,6 @@ -import { Map as ImmutableMap } from 'immutable'; - import { STATUSES_IMPORT } from 'soapbox/actions/importer'; import { __stub } from 'soapbox/api'; -import { mockStore } from 'soapbox/test_helpers'; +import { mockStore, rootState } from 'soapbox/test_helpers'; import { fetchContext } from '../statuses'; @@ -15,7 +13,7 @@ describe('fetchContext()', () => { .reply(200, statuses); }); - const store = mockStore(ImmutableMap()); + const store = mockStore(rootState); store.dispatch(fetchContext('017ed505-5926-392f-256a-f86d5075df70')).then(context => { const actions = store.getActions(); diff --git a/app/soapbox/actions/external_auth.js b/app/soapbox/actions/external_auth.js index f4058cba2..8bf0bba3f 100644 --- a/app/soapbox/actions/external_auth.js +++ b/app/soapbox/actions/external_auth.js @@ -6,11 +6,10 @@ * @see module:soapbox/actions/oauth */ -import { Map as ImmutableMap, fromJS } from 'immutable'; - import { createApp } from 'soapbox/actions/apps'; import { authLoggedIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth'; import { obtainOAuthToken } from 'soapbox/actions/oauth'; +import { normalizeInstance } from 'soapbox/normalizers'; import { parseBaseURL } from 'soapbox/utils/auth'; import sourceCode from 'soapbox/utils/code'; import { getWalletAndSign } from 'soapbox/utils/ethereum'; @@ -22,12 +21,12 @@ import { baseClient } from '../api'; const fetchExternalInstance = baseURL => { return baseClient(null, baseURL) .get('/api/v1/instance') - .then(({ data: instance }) => fromJS(instance)) + .then(({ data: instance }) => normalizeInstance(instance)) .catch(error => { if (error.response?.status === 401) { // Authenticated fetch is enabled. // Continue with a limited featureset. - return ImmutableMap({ version: '0.0.0' }); + return normalizeInstance({}); } else { throw error; } diff --git a/app/soapbox/actions/statuses.js b/app/soapbox/actions/statuses.js index a37a840f9..3433f47f2 100644 --- a/app/soapbox/actions/statuses.js +++ b/app/soapbox/actions/statuses.js @@ -1,5 +1,5 @@ import { isLoggedIn } from 'soapbox/utils/auth'; -import { getFeatures } from 'soapbox/utils/features'; +import { getFeatures, parseVersion } from 'soapbox/utils/features'; import { shouldHaveCard } from 'soapbox/utils/status'; import api from '../api'; @@ -99,8 +99,7 @@ export function fetchStatus(id) { export function redraft(status, raw_text, content_type) { return (dispatch, getState) => { - const state = getState(); - const instance = state.get('instance'); + const { instance } = getState(); const { explicitAddressing } = getFeatures(instance); dispatch({ @@ -109,6 +108,7 @@ export function redraft(status, raw_text, content_type) { raw_text, explicitAddressing, content_type, + v: parseVersion(instance.version), }); }; } diff --git a/app/soapbox/actions/suggestions.js b/app/soapbox/actions/suggestions.js index d896e6e07..0221b3a05 100644 --- a/app/soapbox/actions/suggestions.js +++ b/app/soapbox/actions/suggestions.js @@ -16,10 +16,10 @@ export const SUGGESTIONS_V2_FETCH_REQUEST = 'SUGGESTIONS_V2_FETCH_REQUEST'; export const SUGGESTIONS_V2_FETCH_SUCCESS = 'SUGGESTIONS_V2_FETCH_SUCCESS'; export const SUGGESTIONS_V2_FETCH_FAIL = 'SUGGESTIONS_V2_FETCH_FAIL'; -export function fetchSuggestionsV1() { +export function fetchSuggestionsV1(params = {}) { return (dispatch, getState) => { dispatch({ type: SUGGESTIONS_FETCH_REQUEST, skipLoading: true }); - return api(getState).get('/api/v1/suggestions').then(({ data: accounts }) => { + return api(getState).get('/api/v1/suggestions', { params }).then(({ data: accounts }) => { dispatch(importFetchedAccounts(accounts)); dispatch({ type: SUGGESTIONS_FETCH_SUCCESS, accounts, skipLoading: true }); return accounts; @@ -30,10 +30,10 @@ export function fetchSuggestionsV1() { }; } -export function fetchSuggestionsV2() { +export function fetchSuggestionsV2(params = {}) { return (dispatch, getState) => { dispatch({ type: SUGGESTIONS_V2_FETCH_REQUEST, skipLoading: true }); - return api(getState).get('/api/v2/suggestions').then(({ data: suggestions }) => { + return api(getState).get('/api/v2/suggestions', { params }).then(({ data: suggestions }) => { const accounts = suggestions.map(({ account }) => account); dispatch(importFetchedAccounts(accounts)); dispatch({ type: SUGGESTIONS_V2_FETCH_SUCCESS, suggestions, skipLoading: true }); @@ -45,21 +45,21 @@ export function fetchSuggestionsV2() { }; } -export function fetchSuggestions() { +export function fetchSuggestions(params = { limit: 50 }) { return (dispatch, getState) => { const state = getState(); const instance = state.get('instance'); const features = getFeatures(instance); if (features.suggestionsV2) { - dispatch(fetchSuggestionsV2()) + dispatch(fetchSuggestionsV2(params)) .then(suggestions => { const accountIds = suggestions.map(({ account }) => account.id); dispatch(fetchRelationships(accountIds)); }) .catch(() => {}); } else if (features.suggestions) { - dispatch(fetchSuggestionsV1()) + dispatch(fetchSuggestionsV1(params)) .then(accounts => { const accountIds = accounts.map(({ id }) => id); dispatch(fetchRelationships(accountIds)); diff --git a/app/soapbox/components/birthday_input.js b/app/soapbox/components/birthday_input.js index 26131370b..912ba82fb 100644 --- a/app/soapbox/components/birthday_input.js +++ b/app/soapbox/components/birthday_input.js @@ -1,12 +1,12 @@ import PropTypes from 'prop-types'; import React from 'react'; -import DatePicker from 'react-datepicker'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { defineMessages, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; -import 'react-datepicker/dist/react-datepicker.css'; import IconButton from 'soapbox/components/icon_button'; +import BundleContainer from 'soapbox/features/ui/containers/bundle_container'; +import { DatePicker } from 'soapbox/features/ui/util/async-components'; import { getFeatures } from 'soapbox/utils/features'; const messages = defineMessages({ @@ -112,16 +112,18 @@ class BirthdayInput extends ImmutablePureComponent { )}
- + + {Component => ()} +
); diff --git a/app/soapbox/features/birthdays/date_picker.js b/app/soapbox/features/birthdays/date_picker.js new file mode 100644 index 000000000..1944067d7 --- /dev/null +++ b/app/soapbox/features/birthdays/date_picker.js @@ -0,0 +1,4 @@ +import DatePicker from 'react-datepicker'; +import 'react-datepicker/dist/react-datepicker.css'; + +export default DatePicker; diff --git a/app/soapbox/features/compose/components/schedule_form.js b/app/soapbox/features/compose/components/schedule_form.js index a090abfb0..866b61429 100644 --- a/app/soapbox/features/compose/components/schedule_form.js +++ b/app/soapbox/features/compose/components/schedule_form.js @@ -3,12 +3,12 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; -import DatePicker from 'react-datepicker'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import 'react-datepicker/dist/react-datepicker.css'; import IconButton from 'soapbox/components/icon_button'; +import BundleContainer from 'soapbox/features/ui/containers/bundle_container'; +import { DatePicker } from 'soapbox/features/ui/util/async-components'; import { setSchedule, removeSchedule } from '../../../actions/compose'; @@ -104,18 +104,20 @@ class ScheduleForm extends React.Component {
- + + {Component => ()} +
diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index 9c2218f8e..1622ca575 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -465,3 +465,7 @@ export function CreateApp() { export function SettingsStore() { return import(/* webpackChunkName: "features/developers" */'../../developers/settings_store'); } + +export function DatePicker() { + return import(/* webpackChunkName: "date_picker" */'../../birthdays/date_picker'); +} diff --git a/app/soapbox/normalizers/__tests__/account-test.js b/app/soapbox/normalizers/__tests__/account-test.js index 9211a1cf8..8d12a935d 100644 --- a/app/soapbox/normalizers/__tests__/account-test.js +++ b/app/soapbox/normalizers/__tests__/account-test.js @@ -3,25 +3,27 @@ import { Record as ImmutableRecord, fromJS } from 'immutable'; import { normalizeAccount } from '../account'; const AVATAR_MISSING = require('images/avatar-missing.png'); +const HEADER_MISSING = require('images/header-missing.png'); describe('normalizeAccount()', () => { it('adds base fields', () => { - const account = fromJS({}); + const account = {}; const result = normalizeAccount(account); expect(ImmutableRecord.isRecord(result)).toBe(true); expect(result.acct).toEqual(''); expect(result.note).toEqual(''); expect(result.avatar).toEqual(AVATAR_MISSING); + expect(result.header_static).toEqual(HEADER_MISSING); }); it('normalizes a mention', () => { - const mention = fromJS({ + const mention = { acct: 'NEETzsche@iddqd.social', id: '9v5bw7hEGBPc9nrpzc', url: 'https://iddqd.social/users/NEETzsche', username: 'NEETzsche', - }); + }; const result = normalizeAccount(mention); expect(result.emojis).toEqual(fromJS([])); @@ -32,21 +34,21 @@ describe('normalizeAccount()', () => { }); it('normalizes Fedibird birthday', () => { - const account = fromJS(require('soapbox/__fixtures__/fedibird-account.json')); + const account = require('soapbox/__fixtures__/fedibird-account.json'); const result = normalizeAccount(account); expect(result.birthday).toEqual('1993-07-03'); }); it('normalizes Pleroma birthday', () => { - const account = fromJS(require('soapbox/__fixtures__/pleroma-account.json')); + const account = require('soapbox/__fixtures__/pleroma-account.json'); const result = normalizeAccount(account); expect(result.birthday).toEqual('1993-07-03'); }); it('normalizes Pleroma legacy fields', () => { - const account = fromJS(require('soapbox/__fixtures__/pleroma-2.2.2-account.json')); + const account = require('soapbox/__fixtures__/pleroma-2.2.2-account.json'); const result = normalizeAccount(account); expect(result.getIn(['pleroma', 'is_active'])).toBe(true); @@ -57,7 +59,7 @@ describe('normalizeAccount()', () => { }); it('prefers new Pleroma fields', () => { - const account = fromJS(require('soapbox/__fixtures__/pleroma-account.json')); + const account = require('soapbox/__fixtures__/pleroma-account.json'); const result = normalizeAccount(account); expect(result.getIn(['pleroma', 'is_active'])).toBe(true); @@ -66,76 +68,82 @@ describe('normalizeAccount()', () => { }); it('normalizes a verified Pleroma user', () => { - const account = fromJS(require('soapbox/__fixtures__/mk.json')); + const account = require('soapbox/__fixtures__/mk.json'); const result = normalizeAccount(account); expect(result.verified).toBe(true); }); it('normalizes an unverified Pleroma user', () => { - const account = fromJS(require('soapbox/__fixtures__/pleroma-account.json')); + const account = require('soapbox/__fixtures__/pleroma-account.json'); const result = normalizeAccount(account); expect(result.verified).toBe(false); }); it('normalizes a verified Truth Social user', () => { - const account = fromJS(require('soapbox/__fixtures__/realDonaldTrump.json')); + const account = require('soapbox/__fixtures__/realDonaldTrump.json'); const result = normalizeAccount(account); expect(result.verified).toBe(true); }); it('normalizes Fedibird location', () => { - const account = fromJS(require('soapbox/__fixtures__/fedibird-account.json')); + const account = require('soapbox/__fixtures__/fedibird-account.json'); const result = normalizeAccount(account); expect(result.location).toBe('Texas, USA'); }); it('normalizes Truth Social location', () => { - const account = fromJS(require('soapbox/__fixtures__/truthsocial-account.json')); + const account = require('soapbox/__fixtures__/truthsocial-account.json'); const result = normalizeAccount(account); expect(result.location).toBe('Texas'); }); + it('normalizes Truth Social website', () => { + const account = require('soapbox/__fixtures__/truthsocial-account.json'); + const result = normalizeAccount(account); + expect(result.website).toBe('https://soapbox.pub'); + }); + it('sets display_name from username', () => { - const account = fromJS({ username: 'alex' }); + const account = { username: 'alex' }; const result = normalizeAccount(account); expect(result.display_name).toBe('alex'); }); it('sets display_name from acct', () => { - const account = fromJS({ acct: 'alex@gleasonator.com' }); + const account = { acct: 'alex@gleasonator.com' }; const result = normalizeAccount(account); expect(result.display_name).toBe('alex'); }); it('overrides a whitespace display_name', () => { - const account = fromJS({ username: 'alex', display_name: ' ' }); + const account = { username: 'alex', display_name: ' ' }; const result = normalizeAccount(account); expect(result.display_name).toBe('alex'); }); it('emojifies display name as `display_name_html`', () => { - const account = fromJS(require('soapbox/__fixtures__/account-with-emojis.json')); + const account = require('soapbox/__fixtures__/account-with-emojis.json'); const result = normalizeAccount(account); const expected = 'Alex Gleason πŸ˜‚ :soapbox: :ablobcatrainbow:'; expect(result.display_name_html).toBe(expected); }); it('emojifies note as `note_emojified`', () => { - const account = fromJS(require('soapbox/__fixtures__/account-with-emojis.json')); + const account = require('soapbox/__fixtures__/account-with-emojis.json'); const result = normalizeAccount(account); const expected = 'I create Fediverse software that empowers people online. :soapbox:

I'm vegan btw

Note: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.'; expect(result.note_emojified).toBe(expected); }); it('unescapes HTML note as `note_plain`', () => { - const account = fromJS(require('soapbox/__fixtures__/account-with-emojis.json')); + const account = require('soapbox/__fixtures__/account-with-emojis.json'); const result = normalizeAccount(account); const expected = 'I create Fediverse software that empowers people online. :soapbox:\n\nI\'m vegan btw\n\nNote: If you have a question for me, please tag me publicly. This gives the opportunity for others to chime in, and bystanders to learn.'; expect(result.note_plain).toBe(expected); }); it('emojifies custom profile field', () => { - const account = fromJS(require('soapbox/__fixtures__/account-with-emojis.json')); + const account = require('soapbox/__fixtures__/account-with-emojis.json'); const result = normalizeAccount(account); const field = result.fields.get(1); @@ -143,4 +151,21 @@ describe('normalizeAccount()', () => { expect(field.value_emojified).toBe('https://soapbox.pub :soapbox:'); expect(field.value_plain).toBe('https://soapbox.pub :soapbox:'); }); + + it('adds default avatar and banner to GoToSocial account', () => { + const account = require('soapbox/__fixtures__/gotosocial-account.json'); + const result = normalizeAccount(account); + + expect(result.avatar).toEqual(AVATAR_MISSING); + expect(result.avatar_static).toEqual(AVATAR_MISSING); + expect(result.header).toEqual(HEADER_MISSING); + expect(result.header_static).toEqual(HEADER_MISSING); + }); + + it('adds fqn to Mastodon account', () => { + const account = require('soapbox/__fixtures__/mastodon-account.json'); + const result = normalizeAccount(account); + + expect(result.fqn).toEqual('benis911@mastodon.social'); + }); }); diff --git a/app/soapbox/normalizers/__tests__/attachment-test.js b/app/soapbox/normalizers/__tests__/attachment-test.js index ecf3813e7..9647a55fa 100644 --- a/app/soapbox/normalizers/__tests__/attachment-test.js +++ b/app/soapbox/normalizers/__tests__/attachment-test.js @@ -1,10 +1,10 @@ -import { Record as ImmutableRecord, fromJS } from 'immutable'; +import { Record as ImmutableRecord } from 'immutable'; import { normalizeAttachment } from '../attachment'; describe('normalizeAttachment()', () => { it('adds base fields', () => { - const attachment = fromJS({}); + const attachment = {}; const result = normalizeAttachment(attachment); expect(ImmutableRecord.isRecord(result)).toBe(true); @@ -13,7 +13,7 @@ describe('normalizeAttachment()', () => { }); it('infers preview_url from url', () => { - const attachment = fromJS({ url: 'https://site.fedi/123.png' }); + const attachment = { url: 'https://site.fedi/123.png' }; const result = normalizeAttachment(attachment); expect(result.preview_url).toEqual('https://site.fedi/123.png'); diff --git a/app/soapbox/normalizers/__tests__/card-test.js b/app/soapbox/normalizers/__tests__/card-test.js index e8ac120b0..fc8d06221 100644 --- a/app/soapbox/normalizers/__tests__/card-test.js +++ b/app/soapbox/normalizers/__tests__/card-test.js @@ -1,10 +1,10 @@ -import { Record as ImmutableRecord, fromJS } from 'immutable'; +import { Record as ImmutableRecord } from 'immutable'; import { normalizeCard } from '../card'; describe('normalizeCard()', () => { it('adds base fields', () => { - const card = fromJS({}); + const card = {}; const result = normalizeCard(card); expect(ImmutableRecord.isRecord(result)).toBe(true); diff --git a/app/soapbox/normalizers/__tests__/instance-test.js b/app/soapbox/normalizers/__tests__/instance-test.js index 597fbbeee..f5fd4f6af 100644 --- a/app/soapbox/normalizers/__tests__/instance-test.js +++ b/app/soapbox/normalizers/__tests__/instance-test.js @@ -59,7 +59,7 @@ describe('normalizeInstance()', () => { }); it('normalizes Pleroma instance with Mastodon configuration format', () => { - const instance = fromJS(require('soapbox/__fixtures__/pleroma-instance.json')); + const instance = require('soapbox/__fixtures__/pleroma-instance.json'); const expected = { configuration: { @@ -81,7 +81,7 @@ describe('normalizeInstance()', () => { }); it('normalizes Mastodon instance with retained configuration', () => { - const instance = fromJS(require('soapbox/__fixtures__/mastodon-instance.json')); + const instance = require('soapbox/__fixtures__/mastodon-instance.json'); const expected = { configuration: { @@ -111,7 +111,7 @@ describe('normalizeInstance()', () => { }); it('normalizes Mastodon 3.0.0 instance with default configuration', () => { - const instance = fromJS(require('soapbox/__fixtures__/mastodon-3.0.0-instance.json')); + const instance = require('soapbox/__fixtures__/mastodon-3.0.0-instance.json'); const expected = { configuration: { @@ -133,18 +133,18 @@ describe('normalizeInstance()', () => { }); it('normalizes Fedibird instance', () => { - const instance = fromJS(require('soapbox/__fixtures__/fedibird-instance.json')); + const instance = require('soapbox/__fixtures__/fedibird-instance.json'); const result = normalizeInstance(instance); // Sets description_limit expect(result.description_limit).toEqual(1500); // Preserves fedibird_capabilities - expect(result.fedibird_capabilities).toEqual(instance.get('fedibird_capabilities')); + expect(result.fedibird_capabilities).toEqual(fromJS(instance.fedibird_capabilities)); }); it('normalizes Mitra instance', () => { - const instance = fromJS(require('soapbox/__fixtures__/mitra-instance.json')); + const instance = require('soapbox/__fixtures__/mitra-instance.json'); const result = normalizeInstance(instance); // Adds configuration and description_limit @@ -153,7 +153,7 @@ describe('normalizeInstance()', () => { }); it('normalizes GoToSocial instance', () => { - const instance = fromJS(require('soapbox/__fixtures__/gotosocial-instance.json')); + const instance = require('soapbox/__fixtures__/gotosocial-instance.json'); const result = normalizeInstance(instance); // Normalizes max_toot_chars @@ -166,7 +166,7 @@ describe('normalizeInstance()', () => { }); it('normalizes Friendica instance', () => { - const instance = fromJS(require('soapbox/__fixtures__/friendica-instance.json')); + const instance = require('soapbox/__fixtures__/friendica-instance.json'); const result = normalizeInstance(instance); // Normalizes max_toot_chars @@ -177,4 +177,11 @@ describe('normalizeInstance()', () => { expect(result.get('configuration') instanceof ImmutableMap).toBe(true); expect(result.get('description_limit')).toBe(1500); }); + + it('normalizes a Mastodon RC version', () => { + const instance = require('soapbox/__fixtures__/mastodon-instance-rc.json'); + const result = normalizeInstance(instance); + + expect(result.version).toEqual('3.5.0-rc1'); + }); }); diff --git a/app/soapbox/normalizers/__tests__/mention-test.js b/app/soapbox/normalizers/__tests__/mention-test.js index e429a03b1..03d712175 100644 --- a/app/soapbox/normalizers/__tests__/mention-test.js +++ b/app/soapbox/normalizers/__tests__/mention-test.js @@ -1,10 +1,10 @@ -import { Record as ImmutableRecord, fromJS } from 'immutable'; +import { Record as ImmutableRecord } from 'immutable'; import { normalizeMention } from '../mention'; describe('normalizeMention()', () => { it('adds base fields', () => { - const account = fromJS({}); + const account = {}; const result = normalizeMention(account); expect(ImmutableRecord.isRecord(result)).toBe(true); @@ -15,7 +15,7 @@ describe('normalizeMention()', () => { }); it('infers username from acct', () => { - const account = fromJS({ acct: 'alex@gleasonator.com' }); + const account = { acct: 'alex@gleasonator.com' }; const result = normalizeMention(account); expect(result.username).toEqual('alex'); diff --git a/app/soapbox/normalizers/__tests__/notification-test.js b/app/soapbox/normalizers/__tests__/notification-test.js index c90b5451e..b72f0d9aa 100644 --- a/app/soapbox/normalizers/__tests__/notification-test.js +++ b/app/soapbox/normalizers/__tests__/notification-test.js @@ -1,10 +1,10 @@ -import { Record as ImmutableRecord, fromJS } from 'immutable'; +import { Record as ImmutableRecord } from 'immutable'; import { normalizeNotification } from '../notification'; describe('normalizeNotification()', () => { it('normalizes an empty map', () => { - const notification = fromJS({}); + const notification = {}; const result = normalizeNotification(notification); expect(ImmutableRecord.isRecord(result)).toBe(true); diff --git a/app/soapbox/normalizers/__tests__/poll-test.js b/app/soapbox/normalizers/__tests__/poll-test.js index 4cc9dbbbc..31691f484 100644 --- a/app/soapbox/normalizers/__tests__/poll-test.js +++ b/app/soapbox/normalizers/__tests__/poll-test.js @@ -1,10 +1,10 @@ -import { Record as ImmutableRecord, fromJS } from 'immutable'; +import { Record as ImmutableRecord } from 'immutable'; import { normalizePoll } from '../poll'; describe('normalizePoll()', () => { it('adds base fields', () => { - const poll = fromJS({ options: [{ title: 'Apples' }] }); + const poll = { options: [{ title: 'Apples' }] }; const result = normalizePoll(poll); const expected = { @@ -25,7 +25,7 @@ describe('normalizePoll()', () => { }); it('normalizes a Pleroma logged-out poll', () => { - const poll = fromJS(require('soapbox/__fixtures__/pleroma-status-with-poll.json')).get('poll'); + const { poll } = require('soapbox/__fixtures__/pleroma-status-with-poll.json'); const result = normalizePoll(poll); // Adds logged-in fields @@ -34,7 +34,7 @@ describe('normalizePoll()', () => { }); it('normalizes poll with emojis', () => { - const poll = fromJS(require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json')).get('poll'); + const { poll } = require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json'); const result = normalizePoll(poll); // Emojifies poll options diff --git a/app/soapbox/normalizers/__tests__/status-test.js b/app/soapbox/normalizers/__tests__/status-test.js index 2824615cc..ebfcb3eee 100644 --- a/app/soapbox/normalizers/__tests__/status-test.js +++ b/app/soapbox/normalizers/__tests__/status-test.js @@ -4,7 +4,7 @@ import { normalizeStatus } from '../status'; describe('normalizeStatus()', () => { it('adds base fields', () => { - const status = fromJS({}); + const status = {}; const result = normalizeStatus(status); expect(ImmutableRecord.isRecord(result)).toBe(true); @@ -17,7 +17,7 @@ describe('normalizeStatus()', () => { }); it('fixes the order of mentions', () => { - const status = fromJS(require('soapbox/__fixtures__/status-unordered-mentions.json')); + const status = require('soapbox/__fixtures__/status-unordered-mentions.json'); const expected = ['NEETzsche', 'alex', 'Lumeinshin', 'sneeden']; @@ -30,7 +30,7 @@ describe('normalizeStatus()', () => { }); it('adds mention to self in self-reply on Mastodon', () => { - const status = fromJS(require('soapbox/__fixtures__/mastodon-reply-to-self.json')); + const status = require('soapbox/__fixtures__/mastodon-reply-to-self.json'); const expected = { id: '106801667066418367', @@ -48,7 +48,7 @@ describe('normalizeStatus()', () => { }); it('normalizes mentions with only acct', () => { - const status = fromJS({ mentions: [{ acct: 'alex@gleasonator.com' }] }); + const status = { mentions: [{ acct: 'alex@gleasonator.com' }] }; const expected = [{ id: '', @@ -63,7 +63,7 @@ describe('normalizeStatus()', () => { }); it('normalizes Mitra attachments', () => { - const status = fromJS(require('soapbox/__fixtures__/mitra-status-with-attachments.json')); + const status = require('soapbox/__fixtures__/mitra-status-with-attachments.json'); const expected = [{ id: '017eeb0e-e5df-30a4-77a7-a929145cb836', @@ -97,7 +97,7 @@ describe('normalizeStatus()', () => { }); it('leaves Pleroma attachments alone', () => { - const status = fromJS(require('soapbox/__fixtures__/pleroma-status-with-attachments.json')); + const status = require('soapbox/__fixtures__/pleroma-status-with-attachments.json'); const result = normalizeStatus(status).media_attachments; expect(result.size).toBe(4); @@ -108,15 +108,15 @@ describe('normalizeStatus()', () => { }); it('normalizes Pleroma quote post', () => { - const status = fromJS(require('soapbox/__fixtures__/pleroma-quote-post.json')); + const status = require('soapbox/__fixtures__/pleroma-quote-post.json'); const result = normalizeStatus(status); - expect(result.quote).toEqual(status.getIn(['pleroma', 'quote'])); + expect(result.quote).toEqual(fromJS(status.pleroma.quote)); expect(result.pleroma.get('quote')).toBe(undefined); }); it('normalizes GoToSocial status', () => { - const status = fromJS(require('soapbox/__fixtures__/gotosocial-status.json')); + const status = require('soapbox/__fixtures__/gotosocial-status.json'); const result = normalizeStatus(status); // Adds missing fields @@ -132,7 +132,7 @@ describe('normalizeStatus()', () => { }); it('normalizes Friendica status', () => { - const status = fromJS(require('soapbox/__fixtures__/friendica-status.json')); + const status = require('soapbox/__fixtures__/friendica-status.json'); const result = normalizeStatus(status); // Adds missing fields @@ -145,7 +145,7 @@ describe('normalizeStatus()', () => { }); it('normalizes poll and poll options', () => { - const status = fromJS({ poll: { options: [{ title: 'Apples' }] } }); + const status = { poll: { options: [{ title: 'Apples' }] } }; const result = normalizeStatus(status); const expected = { @@ -166,7 +166,7 @@ describe('normalizeStatus()', () => { }); it('normalizes a Pleroma logged-out poll', () => { - const status = fromJS(require('soapbox/__fixtures__/pleroma-status-with-poll.json')); + const status = require('soapbox/__fixtures__/pleroma-status-with-poll.json'); const result = normalizeStatus(status); // Adds logged-in fields @@ -175,7 +175,7 @@ describe('normalizeStatus()', () => { }); it('normalizes poll with emojis', () => { - const status = fromJS(require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json')); + const status = require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json'); const result = normalizeStatus(status); // Emojifies poll options @@ -188,7 +188,7 @@ describe('normalizeStatus()', () => { }); it('normalizes a card', () => { - const status = fromJS(require('soapbox/__fixtures__/status-with-card.json')); + const status = require('soapbox/__fixtures__/status-with-card.json'); const result = normalizeStatus(status); expect(ImmutableRecord.isRecord(result.card)).toBe(true); diff --git a/app/soapbox/normalizers/account.ts b/app/soapbox/normalizers/account.ts index 7faa3282c..1c536ee4a 100644 --- a/app/soapbox/normalizers/account.ts +++ b/app/soapbox/normalizers/account.ts @@ -8,16 +8,18 @@ import { Map as ImmutableMap, List as ImmutableList, Record as ImmutableRecord, + fromJS, } from 'immutable'; import emojify from 'soapbox/features/emoji/emoji'; import { normalizeEmoji } from 'soapbox/normalizers/emoji'; import { IAccount } from 'soapbox/types'; +import { acctFull } from 'soapbox/utils/accounts'; import { unescapeHTML } from 'soapbox/utils/html'; import { mergeDefined, makeEmojiMap } from 'soapbox/utils/normalizers'; // https://docs.joinmastodon.org/entities/account/ -const AccountRecord = ImmutableRecord({ +export const AccountRecord = ImmutableRecord({ acct: '', avatar: '', avatar_static: '', @@ -44,6 +46,7 @@ const AccountRecord = ImmutableRecord({ uri: '', url: '', username: '', + website: '', verified: false, // Internal fields @@ -56,7 +59,7 @@ const AccountRecord = ImmutableRecord({ }); // https://docs.joinmastodon.org/entities/field/ -const FieldRecord = ImmutableRecord({ +export const FieldRecord = ImmutableRecord({ name: '', value: '', verified_at: null, @@ -95,6 +98,18 @@ const normalizeAvatar = (account: ImmutableMap) => { }); }; +// Add header, if missing +const normalizeHeader = (account: ImmutableMap) => { + const header = account.get('header'); + const headerStatic = account.get('header_static'); + const missing = require('images/header-missing.png'); + + return account.withMutations(account => { + account.set('header', header || headerStatic || missing); + account.set('header_static', headerStatic || header || missing); + }); +}; + // Normalize custom fields const normalizeFields = (account: ImmutableMap) => { return account.update('fields', ImmutableList(), fields => fields.map(FieldRecord)); @@ -132,11 +147,12 @@ const normalizeVerified = (account: ImmutableMap) => { }); }; -// Normalize Fedibird/Truth Social location +// Normalize Fedibird/Truth Social/Pleroma location const normalizeLocation = (account: ImmutableMap) => { return account.update('location', location => { return [ location, + account.getIn(['pleroma', 'location']), account.getIn(['other_settings', 'location']), ].find(Boolean); }); @@ -180,16 +196,22 @@ const addInternalFields = (account: ImmutableMap) => { }); }; -export const normalizeAccount = (account: ImmutableMap): IAccount => { +const normalizeFqn = (account: ImmutableMap) => { + return account.set('fqn', acctFull(account)); +}; + +export const normalizeAccount = (account: Record): IAccount => { return AccountRecord( - account.withMutations(account => { + ImmutableMap(fromJS(account)).withMutations(account => { normalizePleromaLegacyFields(account); normalizeEmojis(account); normalizeAvatar(account); + normalizeHeader(account); normalizeFields(account); normalizeVerified(account); normalizeBirthday(account); normalizeLocation(account); + normalizeFqn(account); fixUsername(account); fixDisplayName(account); addInternalFields(account); diff --git a/app/soapbox/normalizers/attachment.ts b/app/soapbox/normalizers/attachment.ts index 9599fc1ce..26e616696 100644 --- a/app/soapbox/normalizers/attachment.ts +++ b/app/soapbox/normalizers/attachment.ts @@ -6,12 +6,13 @@ import { Map as ImmutableMap, Record as ImmutableRecord, + fromJS, } from 'immutable'; import { mergeDefined } from 'soapbox/utils/normalizers'; // https://docs.joinmastodon.org/entities/attachment/ -const AttachmentRecord = ImmutableRecord({ +export const AttachmentRecord = ImmutableRecord({ blurhash: undefined, description: '', id: '', @@ -29,7 +30,7 @@ const AttachmentRecord = ImmutableRecord({ }); // Ensure attachments have required fields -export const normalizeAttachment = (attachment: ImmutableMap) => { +const normalizeUrls = (attachment: ImmutableMap) => { const url = [ attachment.get('url'), attachment.get('preview_url'), @@ -41,5 +42,11 @@ export const normalizeAttachment = (attachment: ImmutableMap) => { preview_url: url, }); - return AttachmentRecord(attachment.mergeWith(mergeDefined, base)); + return attachment.mergeWith(mergeDefined, base); +}; + +export const normalizeAttachment = (attachment: Record) => { + return AttachmentRecord( + normalizeUrls(ImmutableMap(fromJS(attachment))), + ); }; diff --git a/app/soapbox/normalizers/card.ts b/app/soapbox/normalizers/card.ts index c9ac76adb..169492647 100644 --- a/app/soapbox/normalizers/card.ts +++ b/app/soapbox/normalizers/card.ts @@ -3,10 +3,10 @@ * Converts API cards into our internal format. * @see {@link https://docs.joinmastodon.org/entities/card/} */ -import { Record as ImmutableRecord, Map as ImmutableMap } from 'immutable'; +import { Record as ImmutableRecord, Map as ImmutableMap, fromJS } from 'immutable'; // https://docs.joinmastodon.org/entities/card/ -const CardRecord = ImmutableRecord({ +export const CardRecord = ImmutableRecord({ author_name: '', author_url: '', blurhash: null, @@ -23,6 +23,8 @@ const CardRecord = ImmutableRecord({ width: 0, }); -export const normalizeCard = (card: ImmutableMap) => { - return CardRecord(card); +export const normalizeCard = (card: Record) => { + return CardRecord( + ImmutableMap(fromJS(card)), + ); }; diff --git a/app/soapbox/normalizers/emoji.ts b/app/soapbox/normalizers/emoji.ts index f450af253..8d973b175 100644 --- a/app/soapbox/normalizers/emoji.ts +++ b/app/soapbox/normalizers/emoji.ts @@ -3,10 +3,10 @@ * Converts API emojis into our internal format. * @see {@link https://docs.joinmastodon.org/entities/emoji/} */ -import { Record as ImmutableRecord, Map as ImmutableMap } from 'immutable'; +import { Record as ImmutableRecord, Map as ImmutableMap, fromJS } from 'immutable'; // https://docs.joinmastodon.org/entities/emoji/ -const EmojiRecord = ImmutableRecord({ +export const EmojiRecord = ImmutableRecord({ category: '', shortcode: '', static_url: '', @@ -14,6 +14,8 @@ const EmojiRecord = ImmutableRecord({ visible_in_picker: true, }); -export const normalizeEmoji = (emoji: ImmutableMap) => { - return EmojiRecord(emoji); +export const normalizeEmoji = (emoji: Record) => { + return EmojiRecord( + ImmutableMap(fromJS(emoji)), + ); }; diff --git a/app/soapbox/normalizers/index.ts b/app/soapbox/normalizers/index.ts new file mode 100644 index 000000000..c4a34d66c --- /dev/null +++ b/app/soapbox/normalizers/index.ts @@ -0,0 +1,9 @@ +export { AccountRecord, FieldRecord, normalizeAccount } from './account'; +export { AttachmentRecord, normalizeAttachment } from './attachment'; +export { CardRecord, normalizeCard } from './card'; +export { EmojiRecord, normalizeEmoji } from './emoji'; +export { InstanceRecord, normalizeInstance } from './instance'; +export { MentionRecord, normalizeMention } from './mention'; +export { NotificationRecord, normalizeNotification } from './notification'; +export { PollRecord, PollOptionRecord, normalizePoll } from './poll'; +export { StatusRecord, normalizeStatus } from './status'; diff --git a/app/soapbox/normalizers/instance.ts b/app/soapbox/normalizers/instance.ts index 8e0fe02a6..a33601bf9 100644 --- a/app/soapbox/normalizers/instance.ts +++ b/app/soapbox/normalizers/instance.ts @@ -7,6 +7,7 @@ import { Map as ImmutableMap, List as ImmutableList, Record as ImmutableRecord, + fromJS, } from 'immutable'; import { parseVersion, PLEROMA } from 'soapbox/utils/features'; @@ -15,7 +16,7 @@ import { isNumber } from 'soapbox/utils/numbers'; // Use Mastodon defaults // https://docs.joinmastodon.org/entities/instance/ -const InstanceRecord = ImmutableRecord({ +export const InstanceRecord = ImmutableRecord({ approval_required: false, contact_account: ImmutableMap(), configuration: ImmutableMap({ @@ -83,13 +84,25 @@ const pleromaToMastodonConfig = (instance: ImmutableMap) => { // Get the software's default attachment limit const getAttachmentLimit = (software: string) => software === PLEROMA ? Infinity : 4; -// Normalize instance (Pleroma, Mastodon, etc.) to Mastodon's format -export const normalizeInstance = (instance: ImmutableMap) => { - const { software } = parseVersion(instance.get('version')); - const mastodonConfig = pleromaToMastodonConfig(instance); +// Normalize version +const normalizeVersion = (instance: ImmutableMap) => { + return instance.update('version', '0.0.0', version => { + // Handle Mastodon release candidates + if (new RegExp(/[0-9\.]+rc[0-9]+/g).test(version)) { + return version.split('rc').join('-rc'); + } else { + return version; + } + }); +}; +// Normalize instance (Pleroma, Mastodon, etc.) to Mastodon's format +export const normalizeInstance = (instance: Record) => { return InstanceRecord( - instance.withMutations(instance => { + ImmutableMap(fromJS(instance)).withMutations((instance: ImmutableMap) => { + const { software } = parseVersion(instance.get('version')); + const mastodonConfig = pleromaToMastodonConfig(instance); + // Merge configuration instance.update('configuration', ImmutableMap(), configuration => ( configuration.mergeDeepWith(mergeDefined, mastodonConfig) @@ -100,6 +113,9 @@ export const normalizeInstance = (instance: ImmutableMap) => { return isNumber(value) ? value : getAttachmentLimit(software); }); + // Normalize version + normalizeVersion(instance); + // Merge defaults instance.mergeDeepWith(mergeDefined, InstanceRecord()); }), diff --git a/app/soapbox/normalizers/mention.ts b/app/soapbox/normalizers/mention.ts index 998202065..5ff35ce16 100644 --- a/app/soapbox/normalizers/mention.ts +++ b/app/soapbox/normalizers/mention.ts @@ -3,22 +3,19 @@ * Converts API mentions into our internal format. * @see {@link https://docs.joinmastodon.org/entities/mention/} */ -import { - Map as ImmutableMap, - Record as ImmutableRecord, -} from 'immutable'; +import { Record as ImmutableRecord } from 'immutable'; import { normalizeAccount } from 'soapbox/normalizers/account'; // https://docs.joinmastodon.org/entities/mention/ -const MentionRecord = ImmutableRecord({ +export const MentionRecord = ImmutableRecord({ id: '', acct: '', username: '', url: '', }); -export const normalizeMention = (mention: ImmutableMap) => { +export const normalizeMention = (mention: Record) => { // Simply normalize it as an account then cast it as a mention Β―\_(ツ)_/Β― return MentionRecord(normalizeAccount(mention)); }; diff --git a/app/soapbox/normalizers/notification.ts b/app/soapbox/normalizers/notification.ts index e0f466618..defd51215 100644 --- a/app/soapbox/normalizers/notification.ts +++ b/app/soapbox/normalizers/notification.ts @@ -6,10 +6,11 @@ import { Map as ImmutableMap, Record as ImmutableRecord, + fromJS, } from 'immutable'; // https://docs.joinmastodon.org/entities/notification/ -const NotificationRecord = ImmutableRecord({ +export const NotificationRecord = ImmutableRecord({ account: null, chat_message: null, // pleroma:chat_mention created_at: new Date(), @@ -20,6 +21,8 @@ const NotificationRecord = ImmutableRecord({ type: '', }); -export const normalizeNotification = (notification: ImmutableMap) => { - return NotificationRecord(notification); +export const normalizeNotification = (notification: Record) => { + return NotificationRecord( + ImmutableMap(fromJS(notification)), + ); }; diff --git a/app/soapbox/normalizers/poll.ts b/app/soapbox/normalizers/poll.ts index fa127702e..592f9aa65 100644 --- a/app/soapbox/normalizers/poll.ts +++ b/app/soapbox/normalizers/poll.ts @@ -8,6 +8,7 @@ import { Map as ImmutableMap, List as ImmutableList, Record as ImmutableRecord, + fromJS, } from 'immutable'; import emojify from 'soapbox/features/emoji/emoji'; @@ -15,7 +16,7 @@ import { normalizeEmoji } from 'soapbox/normalizers/emoji'; import { makeEmojiMap } from 'soapbox/utils/normalizers'; // https://docs.joinmastodon.org/entities/poll/ -const PollRecord = ImmutableRecord({ +export const PollRecord = ImmutableRecord({ emojis: ImmutableList(), expired: false, expires_at: new Date(), @@ -29,7 +30,7 @@ const PollRecord = ImmutableRecord({ }); // Sub-entity of Poll -const PollOptionRecord = ImmutableRecord({ +export const PollOptionRecord = ImmutableRecord({ title: '', votes_count: 0, @@ -76,9 +77,9 @@ const normalizePollVoted = (poll: ImmutableMap) => { }); }; -export const normalizePoll = (poll: ImmutableMap) => { +export const normalizePoll = (poll: Record) => { return PollRecord( - poll.withMutations((poll: ImmutableMap) => { + ImmutableMap(fromJS(poll)).withMutations((poll: ImmutableMap) => { normalizeEmojis(poll); normalizePollOptions(poll); normalizePollOwnVotes(poll); diff --git a/app/soapbox/normalizers/status.ts b/app/soapbox/normalizers/status.ts index 2a1039f45..8db44ba39 100644 --- a/app/soapbox/normalizers/status.ts +++ b/app/soapbox/normalizers/status.ts @@ -7,6 +7,7 @@ import { Map as ImmutableMap, List as ImmutableList, Record as ImmutableRecord, + fromJS, } from 'immutable'; import { normalizeAttachment } from 'soapbox/normalizers/attachment'; @@ -17,7 +18,7 @@ import { normalizePoll } from 'soapbox/normalizers/poll'; import { IStatus } from 'soapbox/types'; // https://docs.joinmastodon.org/entities/status/ -const StatusRecord = ImmutableRecord({ +export const StatusRecord = ImmutableRecord({ account: null, application: null, bookmarked: false, @@ -135,9 +136,9 @@ const fixQuote = (status: ImmutableMap) => { }); }; -export const normalizeStatus = (status: ImmutableMap): IStatus => { +export const normalizeStatus = (status: Record): IStatus => { return StatusRecord( - status.withMutations(status => { + ImmutableMap(fromJS(status)).withMutations(status => { normalizeAttachments(status); normalizeMentions(status); normalizeEmojis(status); diff --git a/app/soapbox/reducers/__tests__/compose-test.js b/app/soapbox/reducers/__tests__/compose-test.js index 348739a6b..454c787ca 100644 --- a/app/soapbox/reducers/__tests__/compose-test.js +++ b/app/soapbox/reducers/__tests__/compose-test.js @@ -1,10 +1,11 @@ -import { Map as ImmutableMap } from 'immutable'; +import { Map as ImmutableMap, fromJS } from 'immutable'; import * as actions from 'soapbox/actions/compose'; import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS } from 'soapbox/actions/me'; import { SETTING_CHANGE } from 'soapbox/actions/settings'; -//import { REDRAFT } from 'soapbox/actions/statuses'; +import { REDRAFT } from 'soapbox/actions/statuses'; import { TIMELINE_DELETE } from 'soapbox/actions/timelines'; +import { normalizeStatus } from 'soapbox/normalizers/status'; import reducer from '../compose'; @@ -38,6 +39,29 @@ describe('compose reducer', () => { expect(state.get('idempotencyKey').length === 36); }); + describe('REDRAFT', () => { + it('strips Pleroma integer attachments', () => { + const action = { + type: REDRAFT, + status: normalizeStatus(fromJS(require('soapbox/__fixtures__/pleroma-status-deleted.json'))), + v: { software: 'Pleroma' }, + }; + + const result = reducer(undefined, action); + expect(result.get('media_attachments').isEmpty()).toBe(true); + }); + + it('leaves non-Pleroma integer attachments alone', () => { + const action = { + type: REDRAFT, + status: normalizeStatus(fromJS(require('soapbox/__fixtures__/pleroma-status-deleted.json'))), + }; + + const result = reducer(undefined, action); + expect(result.getIn(['media_attachments', 0, 'id'])).toEqual('508107650'); + }); + }); + it('uses \'public\' scope as default', () => { const action = { type: actions.COMPOSE_REPLY, @@ -325,30 +349,6 @@ describe('compose reducer', () => { }); }); - // it('should handle COMPOSE_UPLOAD_UNDO', () => { - // const state = ImmutableMap({ - // media_attachments: ImmutableList([ - // description: null, - // id: '1375732379', - // pleroma: { - // mime_type: 'image/jpeg' - // }, - // preview_url: 'https://media.gleasonator.com/media_attachments/files/000/853/856/original/7035d67937053e1d.jpg', - // remote_url: 'https://media.gleasonator.com/media_attachments/files/000/853/856/original/7035d67937053e1d.jpg', - // text_url: 'https://media.gleasonator.com/media_attachments/files/000/853/856/original/7035d67937053e1d.jpg', - // type: 'image', - // url: 'https://media.gleasonator.com/media_attachments/files/000/853/856/original/7035d67937053e1d.jpg' - // ]), - // }); - // const action = { - // type: actions.COMPOSE_UPLOAD_UNDO, - // mediaId: '1375732379', - // }; - // expect(reducer(state, action)).toEqual({ - // media_attachments: [], - // }); - // }); - it('should handle COMPOSE_UPLOAD_PROGRESS', () => { const state = ImmutableMap({ progress: 0 }); const action = { @@ -361,203 +361,6 @@ describe('compose reducer', () => { }); }); - // it('should handle COMPOSE_MENTION', () => { - // const state = ImmutableMap({}); - // const account = { - // '9w1HhmenIAKBHJiUs4': { - // header_static: 'https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png', - // display_name_html: 'Alex Gleason', - // bot: false, - // display_name: 'Alex Gleason', - // created_at: '2020-06-12T21:47:28.000Z', - // locked: false, - // emojis: [], - // header: 'https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png', - // url: 'https://gleasonator.com/users/alex', - // note: 'Fediverse developer. I come in peace. #vegan #freeculture #atheist #antiporn #gendercritical. Boosts β‰  endorsements.', - // acct: 'alex@gleasonator.com', - // avatar_static: 'https://media.gleasonator.com/accounts/avatars/000/000/001/original/1a630e4c4c64c948.jpg', - // username: 'alex', - // avatar: 'https://media.gleasonator.com/accounts/avatars/000/000/001/original/1a630e4c4c64c948.jpg', - // fields: [ - // { - // name: 'Website', - // value: 'https://alexgleason.me', - // name_emojified: 'Website', - // value_emojified: 'https://alexgleason.me', - // value_plain: 'https://alexgleason.me' - // }, - // { - // name: 'Pleroma+Soapbox', - // value: 'https://soapbox.pub', - // name_emojified: 'Pleroma+Soapbox', - // value_emojified: 'https://soapbox.pub', - // value_plain: 'https://soapbox.pub' - // }, - // { - // name: 'Email', - // value: 'alex@alexgleason.me', - // name_emojified: 'Email', - // value_emojified: 'alex@alexgleason.me', - // value_plain: 'alex@alexgleason.me' - // }, - // { - // name: 'Gender identity', - // value: 'Soyboy', - // name_emojified: 'Gender identity', - // value_emojified: 'Soyboy', - // value_plain: 'Soyboy' - // } - // ], - // pleroma: { - // hide_follows: false, - // hide_followers_count: false, - // background_image: null, - // confirmation_pending: false, - // is_moderator: false, - // hide_follows_count: false, - // hide_followers: false, - // relationship: { - // showing_reblogs: true, - // followed_by: false, - // subscribing: false, - // blocked_by: false, - // requested: false, - // domain_blocking: false, - // following: false, - // endorsed: false, - // blocking: false, - // muting: false, - // id: '9w1HhmenIAKBHJiUs4', - // muting_notifications: false - // }, - // tags: [], - // hide_favorites: true, - // is_admin: false, - // skip_thread_containment: false - // }, - // source: { - // fields: [], - // note: 'Fediverse developer. I come in peace. #vegan #freeculture #atheist #antiporn #gendercritical. Boosts β‰  endorsements.', - // pleroma: { - // actor_type: 'Person', - // discoverable: false - // }, - // sensitive: false - // }, - // id: '9w1HhmenIAKBHJiUs4', - // note_emojified: 'Fediverse developer. I come in peace. #vegan #freeculture #atheist #antiporn #gendercritical. Boosts β‰  endorsements.' - // }, - // }; - // const action = { - // type: actions.COMPOSE_MENTION, - // account: account, - // }; - // expect(reducer(state, action).toJS()).toMatchObject({ - // text: '@alex@gleasonator.com', - // caretPosition: null, - // }); - // }); - - // it('should handle COMPOSE_DIRECT', () => { - // const state = ImmutableMap({}); - // const account = { - // '9w1HhmenIAKBHJiUs4': { - // header_static: 'https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png', - // display_name_html: 'Alex Gleason', - // bot: false, - // display_name: 'Alex Gleason', - // created_at: '2020-06-12T21:47:28.000Z', - // locked: false, - // emojis: [], - // header: 'https://media.gleasonator.com/accounts/headers/000/000/001/original/9d0e4dbf1c9dbc8f.png', - // url: 'https://gleasonator.com/users/alex', - // note: 'Fediverse developer. I come in peace. #vegan #freeculture #atheist #antiporn #gendercritical. Boosts β‰  endorsements.', - // acct: 'alex@gleasonator.com', - // avatar_static: 'https://media.gleasonator.com/accounts/avatars/000/000/001/original/1a630e4c4c64c948.jpg', - // username: 'alex', - // avatar: 'https://media.gleasonator.com/accounts/avatars/000/000/001/original/1a630e4c4c64c948.jpg', - // fields: [ - // { - // name: 'Website', - // value: 'https://alexgleason.me', - // name_emojified: 'Website', - // value_emojified: 'https://alexgleason.me', - // value_plain: 'https://alexgleason.me' - // }, - // { - // name: 'Pleroma+Soapbox', - // value: 'https://soapbox.pub', - // name_emojified: 'Pleroma+Soapbox', - // value_emojified: 'https://soapbox.pub', - // value_plain: 'https://soapbox.pub' - // }, - // { - // name: 'Email', - // value: 'alex@alexgleason.me', - // name_emojified: 'Email', - // value_emojified: 'alex@alexgleason.me', - // value_plain: 'alex@alexgleason.me' - // }, - // { - // name: 'Gender identity', - // value: 'Soyboy', - // name_emojified: 'Gender identity', - // value_emojified: 'Soyboy', - // value_plain: 'Soyboy' - // } - // ], - // pleroma: { - // hide_follows: false, - // hide_followers_count: false, - // background_image: null, - // confirmation_pending: false, - // is_moderator: false, - // hide_follows_count: false, - // hide_followers: false, - // relationship: { - // showing_reblogs: true, - // followed_by: false, - // subscribing: false, - // blocked_by: false, - // requested: false, - // domain_blocking: false, - // following: false, - // endorsed: false, - // blocking: false, - // muting: false, - // id: '9w1HhmenIAKBHJiUs4', - // muting_notifications: false - // }, - // tags: [], - // hide_favorites: true, - // is_admin: false, - // skip_thread_containment: false - // }, - // source: { - // fields: [], - // note: 'Fediverse developer. I come in peace. #vegan #freeculture #atheist #antiporn #gendercritical. Boosts β‰  endorsements.', - // pleroma: { - // actor_type: 'Person', - // discoverable: false - // }, - // sensitive: false - // }, - // id: '9w1HhmenIAKBHJiUs4', - // note_emojified: 'Fediverse developer. I come in peace. #vegan #freeculture #atheist #antiporn #gendercritical. Boosts β‰  endorsements.' - // } - // }; - // const action = { - // type: actions.COMPOSE_DIRECT, - // account: account, - // }; - // expect(reducer(state, action).toJS()).toMatchObject({ - // text: '@alex@gleasonator.com', - // caretPosition: null, - // privacy: 'direct', - // }); - // }); - // it('should handle COMPOSE_SUGGESTIONS_CLEAR', () => { const state = ImmutableMap({ }); const action = { @@ -570,28 +373,6 @@ describe('compose reducer', () => { }); }); - // it('should handle COMPOSE_SUGGESTIONS_READY', () => { - // const state = ImmutableMap({ default_privacy: 'public', privacy: 'public'}); - // const action = { - // type: actions.COMPOSE_SUGGESTIONS_READY, - // }; - // expect(reducer(state, action).toJS()).toMatchObject({ - // default_privacy: 'unlisted', - // privacy: 'public', - // }); - // }); - // - // it('should handle COMPOSE_SUGGESTION_SELECT', () => { - // const state = ImmutableMap({ default_privacy: 'public', privacy: 'public'}); - // const action = { - // type: actions.COMPOSE_SUGGESTION_SELECT, - // }; - // expect(reducer(state, action).toJS()).toMatchObject({ - // default_privacy: 'unlisted', - // privacy: 'public', - // }); - // }); - // it('should handle COMPOSE_SUGGESTION_TAGS_UPDATE', () => { const state = ImmutableMap({ tagHistory: [ 'hashtag' ] }); const action = { @@ -627,42 +408,6 @@ describe('compose reducer', () => { }); }); - // it('should handle COMPOSE_EMOJI_INSERT', () => { - // const state = ImmutableMap({ text: 'this is my' }); - // const action = { - // type: actions.COMPOSE_EMOJI_INSERT, - // position: 11, - // emoji: [], - // needsSpace, true, - // }; - // expect(reducer(state, action).toJS()).toMatchObject({ - // text: 'this is my :emoji:', - // caretPosition: 15, - // }); - // }); - // - // it('should handle COMPOSE_UPLOAD_CHANGE_SUCCESS', () => { - // const state = ImmutableMap({ default_privacy: 'public' }); - // const action = { - // type: actions.COMPOSE_UPLOAD_CHANGE_SUCCESS, - // }; - // expect(reducer(state, action).toJS()).toMatchObject({ - // default_privacy: 'unlisted', - // privacy: 'public', - // }); - // }); - // - // it('should handle REDRAFT', () => { - // const state = ImmutableMap({ default_privacy: 'public' }); - // const action = { - // type: REDRAFT, - // }; - // expect(reducer(state, action).toJS()).toMatchObject({ - // default_privacy: 'unlisted', - // privacy: 'public', - // }); - // }); - // it('should handle COMPOSE_POLL_ADD', () => { const state = ImmutableMap({ poll: null }); const initialPoll = Object({ @@ -691,34 +436,6 @@ describe('compose reducer', () => { }); }); - // it('should handle COMPOSE_POLL_OPTION_ADD', () => { - // const initialPoll = Object({ - // options: [ - // 'option 1', - // 'option 2', - // ], - // expires_in: 86400, - // multiple: false - // }); - // const state = ImmutableMap({ poll: initialPoll }); - // const action = { - // type: actions.COMPOSE_POLL_OPTION_ADD, - // title: 'option 3', - // }; - // const updatedPoll = Object({ - // options: [ - // 'option 1', - // 'option 2', - // 'option 3', - // ], - // expires_in: 86400, - // multiple: false, - // }); - // expect(reducer(state, action).toJS()).toMatchObject({ - // poll: updatedPoll, - // }); - // }); - it('should handle COMPOSE_POLL_OPTION_CHANGE', () => { const initialPoll = Object({ options: [ @@ -747,32 +464,6 @@ describe('compose reducer', () => { }); }); - // it('should handle COMPOSE_POLL_OPTION_REMOVE', () => { - // const initialPoll = Object({ - // options: [ - // 'option 1', - // 'option 2', - // ], - // expires_in: 86400, - // multiple: false, - // }); - // const state = ImmutableMap({ poll: initialPoll }); - // const action = { - // type: actions.COMPOSE_POLL_OPTION_REMOVE, - // index: 1, - // }; - // const updatedPoll = Object({ - // options: [ - // 'option 1', - // ], - // expires_in: 86400, - // multiple: false, - // }); - // expect(reducer(state, action).toJS()).toMatchObject({ - // poll: updatedPoll, - // }); - // }); - it('sets the post content-type', () => { const action = { type: actions.COMPOSE_TYPE_CHANGE, diff --git a/app/soapbox/reducers/__tests__/notifications-test.js b/app/soapbox/reducers/__tests__/notifications-test.js index 630aa61e3..aa5b7cd62 100644 --- a/app/soapbox/reducers/__tests__/notifications-test.js +++ b/app/soapbox/reducers/__tests__/notifications-test.js @@ -93,6 +93,22 @@ describe('notifications reducer', () => { expect(result.items.size).toEqual(1); expect(result.items.get('4').id).toEqual('4'); }); + + it('imports move notification', () => { + const action = { + type: NOTIFICATIONS_EXPAND_SUCCESS, + notifications: [ + require('soapbox/__fixtures__/pleroma-notification-move.json'), + ], + next: null, + skipLoading: true, + }; + + const result = reducer(undefined, action).items.get('406814'); + + expect(result.account).toEqual('AFmHQ18XZ7Lco68MW8'); + expect(result.target).toEqual('A5c5LK7EJTFR0u26Pg'); + }); }); describe('NOTIFICATIONS_EXPAND_REQUEST', () => { diff --git a/app/soapbox/reducers/accounts.js b/app/soapbox/reducers/accounts.js index c601ed928..de7926feb 100644 --- a/app/soapbox/reducers/accounts.js +++ b/app/soapbox/reducers/accounts.js @@ -46,7 +46,7 @@ const minifyAccount = account => { }; const fixAccount = (state, account) => { - const normalized = minifyAccount(normalizeAccount(fromJS(account))); + const normalized = minifyAccount(normalizeAccount(account)); return state.set(account.id, normalized); }; @@ -119,7 +119,7 @@ const removePermission = (state, accountIds, permissionGroup) => { }); }; -const buildAccount = adminUser => normalizeAccount(fromJS({ +const buildAccount = adminUser => normalizeAccount({ id: adminUser.get('id'), username: adminUser.get('nickname').split('@')[0], acct: adminUser.get('nickname'), @@ -142,7 +142,7 @@ const buildAccount = adminUser => normalizeAccount(fromJS({ }, }, should_refetch: true, -})); +}); const mergeAdminUser = (account, adminUser) => { return account.withMutations(account => { diff --git a/app/soapbox/reducers/compose.js b/app/soapbox/reducers/compose.js index 2690b6637..43fd23154 100644 --- a/app/soapbox/reducers/compose.js +++ b/app/soapbox/reducers/compose.js @@ -1,6 +1,8 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; import { tagHistory } from 'soapbox/settings'; +import { PLEROMA } from 'soapbox/utils/features'; +import { hasIntegerMediaIds } from 'soapbox/utils/status'; import { COMPOSE_MOUNT, @@ -431,13 +433,17 @@ export default function compose(state = initialState, action) { map.set('to', action.explicitAddressing ? getExplicitMentions(action.status.get('account', 'id'), action.status) : undefined); map.set('in_reply_to', action.status.get('in_reply_to_id')); map.set('privacy', action.status.get('visibility')); - // TODO: Actually fix this rather than just removing it - // map.set('media_attachments', action.status.get('media_attachments')); map.set('focusDate', new Date()); map.set('caretPosition', null); map.set('idempotencyKey', uuid()); map.set('content_type', action.content_type || 'text/plain'); + if (action.v?.software === PLEROMA && hasIntegerMediaIds(action.status)) { + map.set('media_attachments', ImmutableList()); + } else { + map.set('media_attachments', action.status.get('media_attachments')); + } + if (action.status.get('spoiler_text').length > 0) { map.set('spoiler', true); map.set('spoiler_text', action.status.get('spoiler_text')); diff --git a/app/soapbox/reducers/index.ts b/app/soapbox/reducers/index.ts index 3c61591e3..d63400d27 100644 --- a/app/soapbox/reducers/index.ts +++ b/app/soapbox/reducers/index.ts @@ -116,7 +116,7 @@ const reducers = { }; // Build a default state from all reducers: it has the key and `undefined` -const StateRecord = ImmutableRecord( +export const StateRecord = ImmutableRecord( Object.keys(reducers).reduce((params: Record, reducer) => { params[reducer] = undefined; return params; diff --git a/app/soapbox/reducers/notifications.js b/app/soapbox/reducers/notifications.js index 02ffa58e9..fad2563c3 100644 --- a/app/soapbox/reducers/notifications.js +++ b/app/soapbox/reducers/notifications.js @@ -56,6 +56,7 @@ const comparator = (a, b) => { const minifyNotification = notification => { return notification.mergeWith((o, n) => n || o, { account: notification.getIn(['account', 'id']), + target: notification.getIn(['target', 'id']), status: notification.getIn(['status', 'id']), }); }; diff --git a/app/soapbox/reducers/polls.js b/app/soapbox/reducers/polls.js index f1a06fdc1..f9f220edc 100644 --- a/app/soapbox/reducers/polls.js +++ b/app/soapbox/reducers/polls.js @@ -1,4 +1,4 @@ -import { Map as ImmutableMap, fromJS } from 'immutable'; +import { Map as ImmutableMap } from 'immutable'; import { POLLS_IMPORT } from 'soapbox/actions/importer'; import { normalizeStatus } from 'soapbox/normalizers/status'; @@ -6,7 +6,7 @@ import { normalizeStatus } from 'soapbox/normalizers/status'; // HOTFIX: Convert the poll into a fake status to normalize it... // TODO: get rid of POLLS_IMPORT and use STATUS_IMPORT here. const normalizePoll = poll => { - const status = fromJS({ poll }); + const status = { poll }; return normalizeStatus(status).poll; }; diff --git a/app/soapbox/test_helpers.js b/app/soapbox/test_helpers.js index c9de38dc4..90f41119e 100644 --- a/app/soapbox/test_helpers.js +++ b/app/soapbox/test_helpers.js @@ -11,6 +11,9 @@ import thunk from 'redux-thunk'; import rootReducer from 'soapbox/reducers'; +export const rootState = rootReducer(undefined, {}); +export const getState = () => rootState; + // Mock Redux // https://redux.js.org/recipes/writing-tests/ const middlewares = [thunk]; @@ -20,7 +23,7 @@ export const mockStore = configureMockStore(middlewares); export const createComponent = (children, props = {}) => { props = ImmutableMap({ locale: 'en', - store: mockStore(rootReducer(ImmutableMap(), {})), + store: mockStore(rootState), }).merge(props); return renderer.create( diff --git a/app/soapbox/utils/__tests__/numbers-test.js b/app/soapbox/utils/__tests__/numbers-test.js new file mode 100644 index 000000000..86923a781 --- /dev/null +++ b/app/soapbox/utils/__tests__/numbers-test.js @@ -0,0 +1,13 @@ +import { isIntegerId } from '../numbers'; + +test('isIntegerId()', () => { + expect(isIntegerId('0')).toBe(true); + expect(isIntegerId('1')).toBe(true); + expect(isIntegerId('508107650')).toBe(true); + expect(isIntegerId('-1764036199')).toBe(true); + expect(isIntegerId('106801667066418367')).toBe(true); + expect(isIntegerId('9v5bmRalQvjOy0ECcC')).toBe(false); + expect(isIntegerId(null)).toBe(false); + expect(isIntegerId(undefined)).toBe(false); + expect(isIntegerId()).toBe(false); +}); diff --git a/app/soapbox/utils/__tests__/status-test.js b/app/soapbox/utils/__tests__/status-test.js new file mode 100644 index 000000000..0dcb3e78a --- /dev/null +++ b/app/soapbox/utils/__tests__/status-test.js @@ -0,0 +1,12 @@ +import { fromJS } from 'immutable'; + +import { normalizeStatus } from 'soapbox/normalizers/status'; + +import { hasIntegerMediaIds } from '../status'; + +describe('hasIntegerMediaIds()', () => { + it('returns true for a Pleroma deleted status', () => { + const status = normalizeStatus(fromJS(require('soapbox/__fixtures__/pleroma-status-deleted.json'))); + expect(hasIntegerMediaIds(status)).toBe(true); + }); +}); diff --git a/app/soapbox/utils/accounts.ts b/app/soapbox/utils/accounts.ts index e93138c86..a97f76338 100644 --- a/app/soapbox/utils/accounts.ts +++ b/app/soapbox/utils/accounts.ts @@ -10,14 +10,14 @@ const getDomainFromURL = (account: ImmutableMap): string => { }; export const getDomain = (account: ImmutableMap): string => { - const domain = account.get('acct').split('@')[1]; + const domain = account.get('acct', '').split('@')[1]; return domain ? domain : getDomainFromURL(account); }; export const guessFqn = (account: ImmutableMap): string => { - const [user, domain] = account.get('acct').split('@'); + const [user, domain] = account.get('acct', '').split('@'); if (!domain) return [user, getDomainFromURL(account)].join('@'); - return account.get('acct'); + return account.get('acct', ''); }; export const getBaseURL = (account: ImmutableMap): string => { @@ -31,7 +31,7 @@ export const getBaseURL = (account: ImmutableMap): string => { // user@domain even for local users export const acctFull = (account: ImmutableMap): string => ( - account.get('fqn') || guessFqn(account) + account.get('fqn') || guessFqn(account) || '' ); export const getAcct = (account: ImmutableMap, displayFqn: boolean): string => ( diff --git a/app/soapbox/utils/numbers.js b/app/soapbox/utils/numbers.js index 8191692b3..18f4d5019 100644 --- a/app/soapbox/utils/numbers.js +++ b/app/soapbox/utils/numbers.js @@ -12,3 +12,5 @@ export const shortNumberFormat = number => { return K; } }; + +export const isIntegerId = id => new RegExp(/^-?[0-9]+$/g).test(id); diff --git a/app/soapbox/utils/status.js b/app/soapbox/utils/status.js index 48554ced9..acd69dc5e 100644 --- a/app/soapbox/utils/status.js +++ b/app/soapbox/utils/status.js @@ -1,3 +1,5 @@ +import { isIntegerId } from 'soapbox/utils/numbers'; + export const getFirstExternalLink = status => { try { // Pulled from Pleroma's media parser @@ -13,3 +15,8 @@ export const getFirstExternalLink = status => { export const shouldHaveCard = status => { return Boolean(getFirstExternalLink(status)); }; + +// https://gitlab.com/soapbox-pub/soapbox-fe/-/merge_requests/1087 +export const hasIntegerMediaIds = status => { + return status.media_attachments.some(({ id }) => isIntegerId(id)); +}; diff --git a/app/styles/chats.scss b/app/styles/chats.scss index 418d0eca8..6060e882f 100644 --- a/app/styles/chats.scss +++ b/app/styles/chats.scss @@ -487,6 +487,7 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + max-height: 19px; a { color: var(--highlight-text-color); diff --git a/package.json b/package.json index da68d8759..6d8353b28 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,6 @@ "substring-trie": "^1.0.2", "terser-webpack-plugin": "^5.2.3", "tiny-queue": "^0.2.1", - "ts-jest": "^27.0.5", "ts-loader": "^9.2.6", "tslib": "^2.3.1", "twemoji": "https://github.com/twitter/twemoji#v13.0.2", @@ -173,6 +172,9 @@ "wicg-inert": "^3.1.1" }, "devDependencies": { + "@jest/globals": "^27.5.1", + "@typescript-eslint/eslint-plugin": "^5.15.0", + "@typescript-eslint/parser": "^5.15.0", "axios-mock-adapter": "^1.18.1", "babel-eslint": "^10.1.0", "babel-jest": "^27.1.0", @@ -194,6 +196,7 @@ "stylelint": "^13.7.2", "stylelint-config-standard": "^22.0.0", "stylelint-scss": "^3.18.0", + "ts-jest": "^27.0.5", "webpack-dev-server": "^4.1.0", "yargs": "^16.0.3" } diff --git a/soapbox-screenshot.png b/soapbox-screenshot.png index 4b3f82cbf..19c19e2c4 100644 Binary files a/soapbox-screenshot.png and b/soapbox-screenshot.png differ diff --git a/tsconfig.json b/tsconfig.json index b40a14711..fa80d5507 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,5 +12,5 @@ "allowSyntheticDefaultImports": true, "typeRoots": [ "./types", "./node_modules/@types"] }, - "exclude": ["node_modules", "types"] + "exclude": ["node_modules", "types", "**/*.test.*", "**/__mocks__/*", "**/__tests__/*"] } diff --git a/yarn.lock b/yarn.lock index 01f6d2fab..e9ff6a500 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1453,6 +1453,16 @@ "@types/node" "*" jest-mock "^27.1.1" +"@jest/environment@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" + integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== + dependencies: + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + "@jest/fake-timers@^27.2.0": version "27.2.0" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.2.0.tgz#560841bc21ae7fbeff0cbff8de8f5cf43ad3561d" @@ -1465,6 +1475,18 @@ jest-mock "^27.1.1" jest-util "^27.2.0" +"@jest/fake-timers@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" + integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== + dependencies: + "@jest/types" "^27.5.1" + "@sinonjs/fake-timers" "^8.0.1" + "@types/node" "*" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-util "^27.5.1" + "@jest/globals@^27.2.0": version "27.2.0" resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.2.0.tgz#4d7085f51df5ac70c8240eb3501289676503933d" @@ -1474,6 +1496,15 @@ "@jest/types" "^27.1.1" expect "^27.2.0" +"@jest/globals@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" + integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/types" "^27.5.1" + expect "^27.5.1" + "@jest/reporters@^27.2.0": version "27.2.0" resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.2.0.tgz#629886d9a42218e504a424889a293abb27919e25" @@ -1565,6 +1596,17 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" +"@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + "@lcdp/offline-plugin@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@lcdp/offline-plugin/-/offline-plugin-5.1.0.tgz#826f3e10d618711bd002afd674edb36dc1d9a792" @@ -1703,6 +1745,13 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@sinonjs/fake-timers@^8.0.1": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@stylelint/postcss-css-in-js@^0.37.2": version "0.37.2" resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz#7e5a84ad181f4234a2480803422a47b8749af3d2" @@ -1852,6 +1901,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== +"@types/json-schema@^7.0.9": + version "7.0.10" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.10.tgz#9b05b7896166cd00e9cbd59864853abf65d9ac23" + integrity sha512-BLO9bBq59vW3fxCpD4o0N4U+DXsvwvIcl+jofw0frQo/GrBFC+/jRZj1E7kgp6dvTyNmA4y6JCV5Id/r3mNP5A== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -1962,6 +2016,86 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/eslint-plugin@^5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.15.0.tgz#c28ef7f2e688066db0b6a9d95fb74185c114fb9a" + integrity sha512-u6Db5JfF0Esn3tiAKELvoU5TpXVSkOpZ78cEGn/wXtT2RVqs2vkt4ge6N8cRCyw7YVKhmmLDbwI2pg92mlv7cA== + dependencies: + "@typescript-eslint/scope-manager" "5.15.0" + "@typescript-eslint/type-utils" "5.15.0" + "@typescript-eslint/utils" "5.15.0" + debug "^4.3.2" + functional-red-black-tree "^1.0.1" + ignore "^5.1.8" + regexpp "^3.2.0" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.15.0.tgz#95f603f8fe6eca7952a99bfeef9b85992972e728" + integrity sha512-NGAYP/+RDM2sVfmKiKOCgJYPstAO40vPAgACoWPO/+yoYKSgAXIFaBKsV8P0Cc7fwKgvj27SjRNX4L7f4/jCKQ== + dependencies: + "@typescript-eslint/scope-manager" "5.15.0" + "@typescript-eslint/types" "5.15.0" + "@typescript-eslint/typescript-estree" "5.15.0" + debug "^4.3.2" + +"@typescript-eslint/scope-manager@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.15.0.tgz#d97afab5e0abf4018d1289bd711be21676cdd0ee" + integrity sha512-EFiZcSKrHh4kWk0pZaa+YNJosvKE50EnmN4IfgjkA3bTHElPtYcd2U37QQkNTqwMCS7LXeDeZzEqnsOH8chjSg== + dependencies: + "@typescript-eslint/types" "5.15.0" + "@typescript-eslint/visitor-keys" "5.15.0" + +"@typescript-eslint/type-utils@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.15.0.tgz#d2c02eb2bdf54d0a645ba3a173ceda78346cf248" + integrity sha512-KGeDoEQ7gHieLydujGEFLyLofipe9PIzfvA/41urz4hv+xVxPEbmMQonKSynZ0Ks2xDhJQ4VYjB3DnRiywvKDA== + dependencies: + "@typescript-eslint/utils" "5.15.0" + debug "^4.3.2" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.15.0.tgz#c7bdd103843b1abae97b5518219d3e2a0d79a501" + integrity sha512-yEiTN4MDy23vvsIksrShjNwQl2vl6kJeG9YkVJXjXZnkJElzVK8nfPsWKYxcsGWG8GhurYXP4/KGj3aZAxbeOA== + +"@typescript-eslint/typescript-estree@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.15.0.tgz#81513a742a9c657587ad1ddbca88e76c6efb0aac" + integrity sha512-Hb0e3dGc35b75xLzixM3cSbG1sSbrTBQDfIScqdyvrfJZVEi4XWAT+UL/HMxEdrJNB8Yk28SKxPLtAhfCbBInA== + dependencies: + "@typescript-eslint/types" "5.15.0" + "@typescript-eslint/visitor-keys" "5.15.0" + debug "^4.3.2" + globby "^11.0.4" + is-glob "^4.0.3" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.15.0.tgz#468510a0974d3ced8342f37e6c662778c277f136" + integrity sha512-081rWu2IPKOgTOhHUk/QfxuFog8m4wxW43sXNOMSCdh578tGJ1PAaWPsj42LOa7pguh173tNlMigsbrHvh/mtA== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.15.0" + "@typescript-eslint/types" "5.15.0" + "@typescript-eslint/typescript-estree" "5.15.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/visitor-keys@5.15.0": + version "5.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.15.0.tgz#5669739fbf516df060f978be6a6dce75855a8027" + integrity sha512-+vX5FKtgvyHbmIJdxMJ2jKm9z2BIlXJiuewI8dsDYMp5LzPUcuTT78Ya5iwvQg3VqSVdmxyM8Anj1Jeq7733ZQ== + dependencies: + "@typescript-eslint/types" "5.15.0" + eslint-visitor-keys "^3.0.0" + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -2243,7 +2377,7 @@ ansi-regex@^2.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= -ansi-regex@^5.0.0: +ansi-regex@^5.0.0, ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== @@ -2866,9 +3000,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001252, caniuse-lite@^1.0.30001254: - version "1.0.30001257" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz#150aaf649a48bee531104cfeda57f92ce587f6e5" - integrity sha512-JN49KplOgHSXpIsVSF+LUyhD8PUp6xPpAXeRrrcBh4KBeP7W864jHn6RvzJgDlrReyeVjMFJL3PLpPvKIxlIHA== + version "1.0.30001317" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001317.tgz" + integrity sha512-xIZLh8gBm4dqNX0gkzrBeyI86J2eCjWzYAs40q88smG844YIrN4tVQl/RhquHvKEKImWWFIVh1Lxe5n1G/N+GQ== catharsis@^0.9.0: version "0.9.0" @@ -2989,6 +3123,11 @@ ci-info@^3.1.1: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== +ci-info@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" + integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== + cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" @@ -3556,6 +3695,13 @@ debug@^3.1.1, debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + decamelize-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" @@ -3681,6 +3827,11 @@ diff-sequences@^27.0.6: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723" integrity sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ== +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -4237,6 +4388,13 @@ eslint-utils@^2.1.0: dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" @@ -4247,6 +4405,11 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== +eslint-visitor-keys@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + eslint@^7.0.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" @@ -4405,6 +4568,16 @@ expect@^27.2.0: jest-message-util "^27.2.0" jest-regex-util "^27.0.6" +expect@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" + integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== + dependencies: + "@jest/types" "^27.5.1" + jest-get-type "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -4469,6 +4642,17 @@ fast-glob@^3.1.1, fast-glob@^3.2.5: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -4819,6 +5003,18 @@ globby@^11.0.1, globby@^11.0.3: merge2 "^1.3.0" slash "^3.0.0" +globby@^11.0.4: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + globjoin@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" @@ -4836,6 +5032,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +graceful-fs@^4.2.9: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + gzip-size@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" @@ -5162,6 +5363,11 @@ ignore@^5.1.4, ignore@^5.1.8: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -5832,6 +6038,16 @@ jest-diff@^27.2.0: jest-get-type "^27.0.6" pretty-format "^27.2.0" +jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + jest-docblock@^27.0.6: version "27.0.6" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.0.6.tgz#cc78266acf7fe693ca462cbbda0ea4e639e4e5f3" @@ -5880,6 +6096,11 @@ jest-get-type@^27.0.6: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.0.6.tgz#0eb5c7f755854279ce9b68a9f1a4122f69047cfe" integrity sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg== +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + jest-haste-map@^27.2.0: version "27.2.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.2.0.tgz#703b3a473e3f2e27d75ab07864ffd7bbaad0d75e" @@ -5942,6 +6163,16 @@ jest-matcher-utils@^27.2.0: jest-get-type "^27.0.6" pretty-format "^27.2.0" +jest-matcher-utils@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== + dependencies: + chalk "^4.0.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + jest-message-util@^27.2.0: version "27.2.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.2.0.tgz#2f65c71df55267208686b1d7514e18106c91ceaf" @@ -5957,6 +6188,21 @@ jest-message-util@^27.2.0: slash "^3.0.0" stack-utils "^2.0.3" +jest-message-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" + integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^27.5.1" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + jest-mock@^27.1.1: version "27.1.1" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.1.1.tgz#c7a2e81301fdcf3dab114931d23d89ec9d0c3a82" @@ -5965,6 +6211,14 @@ jest-mock@^27.1.1: "@jest/types" "^27.1.1" "@types/node" "*" +jest-mock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" + integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-pnp-resolver@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" @@ -6116,6 +6370,18 @@ jest-util@^27.0.0, jest-util@^27.2.0: is-ci "^3.0.0" picomatch "^2.2.3" +jest-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" + integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-validate@^27.2.0: version "27.2.0" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.2.0.tgz#b7535f12d95dd3b4382831f4047384ca098642ab" @@ -6804,7 +7070,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -7933,6 +8199,15 @@ pretty-format@^27.2.0: ansi-styles "^5.0.0" react-is "^17.0.1" +pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -8553,7 +8828,7 @@ regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.1: call-bind "^1.0.2" define-properties "^1.1.3" -regexpp@^3.1.0: +regexpp@^3.1.0, regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== @@ -9714,7 +9989,7 @@ tsconfig-paths@^3.12.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -9724,6 +9999,13 @@ tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + twemoji-parser@13.0.0: version "13.0.0" resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-13.0.0.tgz#bd9d1b98474f1651dc174696b45cabefdfa405af"