From 5ba4275f73a13eebc7d055feeb58b82e05b65cc8 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 9 May 2022 17:39:38 -0500 Subject: [PATCH 01/26] Stop doing Tabler import hack, use our forked version from git --- package.json | 2 +- webpack/shared.js | 3 --- yarn.lock | 7 +++---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 5d19be83b..4e3608b97 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@sentry/browser": "^6.12.0", "@sentry/react": "^6.12.0", "@sentry/tracing": "^6.12.0", - "@tabler/icons": "^1.53.0", + "@tabler/icons": "https://gitlab.com/soapbox-pub/tabler-icons.git", "@tailwindcss/forms": "^0.4.0", "@tailwindcss/typography": "^0.5.1", "@testing-library/react": "^12.1.4", diff --git a/webpack/shared.js b/webpack/shared.js index 9dee79de1..fb8308ee4 100644 --- a/webpack/shared.js +++ b/webpack/shared.js @@ -145,9 +145,6 @@ module.exports = { 'node_modules', ], alias: { - // Override tabler's package.json to allow importing .svg files directly - // https://stackoverflow.com/a/35990101/8811886 - '@tabler': resolve('node_modules', '@tabler'), 'icons': resolve('app', 'icons'), 'custom': resolve('custom'), }, diff --git a/yarn.lock b/yarn.lock index 9445a64a8..cb9d3fb3f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1883,10 +1883,9 @@ remark "^13.0.0" unist-util-find-all-after "^3.0.2" -"@tabler/icons@^1.53.0": - version "1.53.0" - resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-1.53.0.tgz#51536e01b343cfaf26b701df306b2c0369769e3c" - integrity sha512-Skk1BqXEOEhiRsXJgZBYtjFa/+4dMSFA5UyzTUW20oyyUSd3iizhEWrYt0jT87iFu771gWoqVV2/OGobBcGjgQ== +"@tabler/icons@https://gitlab.com/soapbox-pub/tabler-icons.git": + version "1.68.0" + resolved "https://gitlab.com/soapbox-pub/tabler-icons.git#20e43498bdc0f38f75f176ef1a6526ca76c9172e" "@tailwindcss/forms@^0.4.0": version "0.4.0" From 660c098166ca2253a56a28f8432b1ae28a47f42f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 9 May 2022 17:57:15 -0500 Subject: [PATCH 02/26] Allow custom module imports --- custom/modules/.gitkeep | 0 webpack/rules/assets.js | 5 ++++- webpack/shared.js | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 custom/modules/.gitkeep diff --git a/custom/modules/.gitkeep b/custom/modules/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/webpack/rules/assets.js b/webpack/rules/assets.js index 1b291ae0c..e0a14c250 100644 --- a/webpack/rules/assets.js +++ b/webpack/rules/assets.js @@ -66,7 +66,10 @@ module.exports = [{ }, { test: /\.svg$/, type: 'asset/resource', - include: resolve('node_modules', '@tabler'), + include: [ + resolve('node_modules', '@tabler'), + resolve('custom', 'modules', '@tabler'), + ], generator: { filename: 'packs/icons/[name]-[contenthash:8][ext]', }, diff --git a/webpack/shared.js b/webpack/shared.js index fb8308ee4..326981b8b 100644 --- a/webpack/shared.js +++ b/webpack/shared.js @@ -141,6 +141,7 @@ module.exports = { resolve: { extensions: settings.extensions, modules: [ + resolve('custom', 'modules'), resolve(settings.source_path), 'node_modules', ], From b7e2d3e0a7d96770991a849f193ac6464aaa879c Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 11 Aug 2022 19:38:09 -0500 Subject: [PATCH 03/26] Add inert oauth consumer buttons --- .../auth_login/components/consumer-button.tsx | 46 +++++++++++++++++++ .../auth_login/components/login_form.tsx | 19 ++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 app/soapbox/features/auth_login/components/consumer-button.tsx diff --git a/app/soapbox/features/auth_login/components/consumer-button.tsx b/app/soapbox/features/auth_login/components/consumer-button.tsx new file mode 100644 index 000000000..63ca9d3df --- /dev/null +++ b/app/soapbox/features/auth_login/components/consumer-button.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { useIntl, defineMessages } from 'react-intl'; + +import { IconButton, Tooltip } from 'soapbox/components/ui'; + +const messages = defineMessages({ + tooltip: { id: 'oauth_consumer.tooltip', defaultMessage: 'Sign in with {provider}' }, +}); + +/** Map between OAuth providers and brand icons. */ +const BRAND_ICONS: Record = { + twitter: require('@tabler/icons/brand-twitter.svg'), + facebook: require('@tabler/icons/brand-facebook.svg'), + google: require('@tabler/icons/brand-google.svg'), + microsoft: require('@tabler/icons/brand-windows.svg'), + slack: require('@tabler/icons/brand-slack.svg'), + github: require('@tabler/icons/brand-github.svg'), +}; + +/** Capitalize the first letter of a string. */ +// https://stackoverflow.com/a/1026087 +function capitalize(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +interface IConsumerButton { + provider: string, +} + +/** OAuth consumer button for logging in with a third-party service. */ +const ConsumerButton: React.FC = ({ provider }) => { + const intl = useIntl(); + const icon = BRAND_ICONS[provider] || require('@tabler/icons/key.svg'); + + return ( + + + + ); +}; + +export default ConsumerButton; diff --git a/app/soapbox/features/auth_login/components/login_form.tsx b/app/soapbox/features/auth_login/components/login_form.tsx index 9e8b1dc35..44b8e6df5 100644 --- a/app/soapbox/features/auth_login/components/login_form.tsx +++ b/app/soapbox/features/auth_login/components/login_form.tsx @@ -1,8 +1,12 @@ +import { List as ImmutableList } from 'immutable'; import React from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { Link } from 'react-router-dom'; -import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui'; +import { Button, Form, FormActions, FormGroup, HStack, Input, Stack } from 'soapbox/components/ui'; +import { useAppSelector } from 'soapbox/hooks'; + +import ConsumerButton from './consumer-button'; const messages = defineMessages({ username: { @@ -22,6 +26,7 @@ interface ILoginForm { const LoginForm: React.FC = ({ isLoading, handleSubmit }) => { const intl = useIntl(); + const providers = useAppSelector(state => ImmutableList(state.instance.pleroma.get('oauth_consumer_strategies'))); return (
@@ -29,7 +34,7 @@ const LoginForm: React.FC = ({ isLoading, handleSubmit }) => {

-
+
= ({ isLoading, handleSubmit }) => { -
+ + {(providers.size > 0) && ( + + {providers.map(provider => ( + + ))} + + )} + ); }; From 304e9aa880b640bdfe9ba20dd77e459f42a3ec2d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 11 Aug 2022 19:46:56 -0500 Subject: [PATCH 04/26] Move oauth ConsumersList into its own component --- .../auth_login/components/consumers-list.tsx | 29 +++++++++++++++++++ .../auth_login/components/login_form.tsx | 15 ++-------- 2 files changed, 32 insertions(+), 12 deletions(-) create mode 100644 app/soapbox/features/auth_login/components/consumers-list.tsx diff --git a/app/soapbox/features/auth_login/components/consumers-list.tsx b/app/soapbox/features/auth_login/components/consumers-list.tsx new file mode 100644 index 000000000..368bc63f4 --- /dev/null +++ b/app/soapbox/features/auth_login/components/consumers-list.tsx @@ -0,0 +1,29 @@ +import { List as ImmutableList } from 'immutable'; +import React from 'react'; + +import { HStack } from 'soapbox/components/ui'; +import { useAppSelector } from 'soapbox/hooks'; + +import ConsumerButton from './consumer-button'; + +interface IConsumersList { +} + +/** Displays OAuth consumers to log in with. */ +const ConsumersList: React.FC = () => { + const providers = useAppSelector(state => ImmutableList(state.instance.pleroma.get('oauth_consumer_strategies'))); + + if (providers.size > 0) { + return ( + + {providers.map(provider => ( + + ))} + + ); + } else { + return null; + } +}; + +export default ConsumersList; diff --git a/app/soapbox/features/auth_login/components/login_form.tsx b/app/soapbox/features/auth_login/components/login_form.tsx index 44b8e6df5..24e4e3740 100644 --- a/app/soapbox/features/auth_login/components/login_form.tsx +++ b/app/soapbox/features/auth_login/components/login_form.tsx @@ -1,12 +1,10 @@ -import { List as ImmutableList } from 'immutable'; import React from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { Link } from 'react-router-dom'; -import { Button, Form, FormActions, FormGroup, HStack, Input, Stack } from 'soapbox/components/ui'; -import { useAppSelector } from 'soapbox/hooks'; +import { Button, Form, FormActions, FormGroup, Input, Stack } from 'soapbox/components/ui'; -import ConsumerButton from './consumer-button'; +import ConsumersList from './consumers-list'; const messages = defineMessages({ username: { @@ -26,7 +24,6 @@ interface ILoginForm { const LoginForm: React.FC = ({ isLoading, handleSubmit }) => { const intl = useIntl(); - const providers = useAppSelector(state => ImmutableList(state.instance.pleroma.get('oauth_consumer_strategies'))); return (
@@ -82,13 +79,7 @@ const LoginForm: React.FC = ({ isLoading, handleSubmit }) => { - {(providers.size > 0) && ( - - {providers.map(provider => ( - - ))} - - )} +
); From 609eb543ba991827107913b75845024fb05e5e01 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 11 Aug 2022 19:56:48 -0500 Subject: [PATCH 05/26] Style ConsumersList --- .../auth_login/components/consumers-list.tsx | 18 ++++++++++++------ .../auth_login/components/login_form.tsx | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/soapbox/features/auth_login/components/consumers-list.tsx b/app/soapbox/features/auth_login/components/consumers-list.tsx index 368bc63f4..84f65d900 100644 --- a/app/soapbox/features/auth_login/components/consumers-list.tsx +++ b/app/soapbox/features/auth_login/components/consumers-list.tsx @@ -1,7 +1,8 @@ import { List as ImmutableList } from 'immutable'; import React from 'react'; +import { FormattedMessage } from 'react-intl'; -import { HStack } from 'soapbox/components/ui'; +import { Card, HStack, Text } from 'soapbox/components/ui'; import { useAppSelector } from 'soapbox/hooks'; import ConsumerButton from './consumer-button'; @@ -15,11 +16,16 @@ const ConsumersList: React.FC = () => { if (providers.size > 0) { return ( - - {providers.map(provider => ( - - ))} - + + + + + + {providers.map(provider => ( + + ))} + + ); } else { return null; diff --git a/app/soapbox/features/auth_login/components/login_form.tsx b/app/soapbox/features/auth_login/components/login_form.tsx index 24e4e3740..5b1f6f733 100644 --- a/app/soapbox/features/auth_login/components/login_form.tsx +++ b/app/soapbox/features/auth_login/components/login_form.tsx @@ -31,7 +31,7 @@ const LoginForm: React.FC = ({ isLoading, handleSubmit }) => {

- +
Date: Thu, 11 Aug 2022 21:50:08 -0500 Subject: [PATCH 06/26] Make consumer strategy buttons work --- app/soapbox/actions/consumer-auth.ts | 55 +++++++++++++++++++ .../auth_login/components/consumer-button.tsx | 9 +++ 2 files changed, 64 insertions(+) create mode 100644 app/soapbox/actions/consumer-auth.ts diff --git a/app/soapbox/actions/consumer-auth.ts b/app/soapbox/actions/consumer-auth.ts new file mode 100644 index 000000000..b669c6393 --- /dev/null +++ b/app/soapbox/actions/consumer-auth.ts @@ -0,0 +1,55 @@ +import axios from 'axios'; + +import * as BuildConfig from 'soapbox/build_config'; +import { isURL } from 'soapbox/utils/auth'; +import sourceCode from 'soapbox/utils/code'; +import { getFeatures } from 'soapbox/utils/features'; + +import { createApp } from './apps'; + +import type { AppDispatch, RootState } from 'soapbox/store'; + +const createProviderApp = () => { + return async(dispatch: AppDispatch, getState: () => RootState) => { + const state = getState(); + const { scopes } = getFeatures(state.instance); + + const params = { + client_name: sourceCode.displayName, + redirect_uris: `${window.location.origin}/login/external`, + website: sourceCode.homepage, + scopes, + }; + + return dispatch(createApp(params)); + }; +}; + +export const prepareRequest = (provider: string) => { + return async(dispatch: AppDispatch, getState: () => RootState) => { + const baseURL = isURL(BuildConfig.BACKEND_URL) ? BuildConfig.BACKEND_URL : ''; + + const state = getState(); + const { scopes } = getFeatures(state.instance); + const app = await dispatch(createProviderApp()); + const { client_id, redirect_uri } = app; + + localStorage.setItem('soapbox:external:app', JSON.stringify(app)); + localStorage.setItem('soapbox:external:baseurl', baseURL); + localStorage.setItem('soapbox:external:scopes', scopes); + + const params = { + provider, + authorization: { + client_id, + redirect_uri, + scope: scopes, + }, + }; + + const formdata = axios.toFormData(params); + const query = new URLSearchParams(formdata as any); + + location.href = `${baseURL}/oauth/prepare_request?${query.toString()}`; + }; +}; diff --git a/app/soapbox/features/auth_login/components/consumer-button.tsx b/app/soapbox/features/auth_login/components/consumer-button.tsx index 63ca9d3df..8ce78e799 100644 --- a/app/soapbox/features/auth_login/components/consumer-button.tsx +++ b/app/soapbox/features/auth_login/components/consumer-button.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { useIntl, defineMessages } from 'react-intl'; +import { prepareRequest } from 'soapbox/actions/consumer-auth'; import { IconButton, Tooltip } from 'soapbox/components/ui'; +import { useAppDispatch } from 'soapbox/hooks'; const messages = defineMessages({ tooltip: { id: 'oauth_consumer.tooltip', defaultMessage: 'Sign in with {provider}' }, @@ -30,14 +32,21 @@ interface IConsumerButton { /** OAuth consumer button for logging in with a third-party service. */ const ConsumerButton: React.FC = ({ provider }) => { const intl = useIntl(); + const dispatch = useAppDispatch(); + const icon = BRAND_ICONS[provider] || require('@tabler/icons/key.svg'); + const handleClick = () => { + dispatch(prepareRequest(provider)); + }; + return ( ); From f2fc3698773e5e40a49ebdf22e02050c1b1538c1 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 11 Aug 2022 22:06:38 -0500 Subject: [PATCH 07/26] Allow configuring authProvider in place of registrations --- .../auth_login/components/consumer-button.tsx | 7 +--- app/soapbox/features/landing_page/index.tsx | 36 +++++++++++++++++-- .../normalizers/soapbox/soapbox_config.ts | 1 + app/soapbox/utils/strings.ts | 7 ++++ 4 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 app/soapbox/utils/strings.ts diff --git a/app/soapbox/features/auth_login/components/consumer-button.tsx b/app/soapbox/features/auth_login/components/consumer-button.tsx index 8ce78e799..0f003e98a 100644 --- a/app/soapbox/features/auth_login/components/consumer-button.tsx +++ b/app/soapbox/features/auth_login/components/consumer-button.tsx @@ -4,6 +4,7 @@ import { useIntl, defineMessages } from 'react-intl'; import { prepareRequest } from 'soapbox/actions/consumer-auth'; import { IconButton, Tooltip } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; +import { capitalize } from 'soapbox/utils/strings'; const messages = defineMessages({ tooltip: { id: 'oauth_consumer.tooltip', defaultMessage: 'Sign in with {provider}' }, @@ -19,12 +20,6 @@ const BRAND_ICONS: Record = { github: require('@tabler/icons/brand-github.svg'), }; -/** Capitalize the first letter of a string. */ -// https://stackoverflow.com/a/1026087 -function capitalize(str: string) { - return str.charAt(0).toUpperCase() + str.slice(1); -} - interface IConsumerButton { provider: string, } diff --git a/app/soapbox/features/landing_page/index.tsx b/app/soapbox/features/landing_page/index.tsx index dcb9afd36..a5b0e58e8 100644 --- a/app/soapbox/features/landing_page/index.tsx +++ b/app/soapbox/features/landing_page/index.tsx @@ -1,12 +1,15 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; +import { prepareRequest } from 'soapbox/actions/consumer-auth'; import { Button, Card, CardBody, Stack, Text } from 'soapbox/components/ui'; import VerificationBadge from 'soapbox/components/verification_badge'; import RegistrationForm from 'soapbox/features/auth_login/components/registration_form'; -import { useAppSelector, useFeatures, useSoapboxConfig } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useFeatures, useSoapboxConfig } from 'soapbox/hooks'; +import { capitalize } from 'soapbox/utils/strings'; const LandingPage = () => { + const dispatch = useAppDispatch(); const features = useFeatures(); const soapboxConfig = useSoapboxConfig(); const pepeEnabled = soapboxConfig.getIn(['extensions', 'pepe', 'enabled']) === true; @@ -40,6 +43,29 @@ const LandingPage = () => { return ; }; + /** Display login button for external provider. */ + const renderProvider = () => { + const { authProvider } = soapboxConfig; + + return ( + + + + + + + + + + ); + }; + /** Pepe API registrations are open */ const renderPepe = () => { return ( @@ -47,7 +73,9 @@ const LandingPage = () => { - Let's get started! + + + Social Media Without Discrimination @@ -58,7 +86,9 @@ const LandingPage = () => { // Render registration flow depending on features const renderBody = () => { - if (pepeEnabled && pepeOpen) { + if (soapboxConfig.authProvider) { + return renderProvider(); + } else if (pepeEnabled && pepeOpen) { return renderPepe(); } else if (features.accountCreation && instance.registrations) { return renderOpen(); diff --git a/app/soapbox/normalizers/soapbox/soapbox_config.ts b/app/soapbox/normalizers/soapbox/soapbox_config.ts index 6e9a2c745..a471401c5 100644 --- a/app/soapbox/normalizers/soapbox/soapbox_config.ts +++ b/app/soapbox/normalizers/soapbox/soapbox_config.ts @@ -71,6 +71,7 @@ export const CryptoAddressRecord = ImmutableRecord({ export const SoapboxConfigRecord = ImmutableRecord({ ads: ImmutableList(), appleAppId: null, + authProvider: '', logo: '', logoDarkMode: null, banner: '', diff --git a/app/soapbox/utils/strings.ts b/app/soapbox/utils/strings.ts new file mode 100644 index 000000000..c1c8e08bc --- /dev/null +++ b/app/soapbox/utils/strings.ts @@ -0,0 +1,7 @@ +/** Capitalize the first letter of a string. */ +// https://stackoverflow.com/a/1026087 +function capitalize(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +export { capitalize }; From 386af1ea2ce4aba0cddf05b162d098b43fc047bf Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 11 Aug 2022 22:10:16 -0500 Subject: [PATCH 08/26] i18n pepe locales --- app/soapbox/features/landing_page/index.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/soapbox/features/landing_page/index.tsx b/app/soapbox/features/landing_page/index.tsx index a5b0e58e8..1e3b01f80 100644 --- a/app/soapbox/features/landing_page/index.tsx +++ b/app/soapbox/features/landing_page/index.tsx @@ -76,10 +76,14 @@ const LandingPage = () => { - Social Media Without Discrimination + + + - + ); }; From bc72739dda9ea43461f2667730878279218db46a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 11 Aug 2022 22:31:53 -0500 Subject: [PATCH 09/26] Normalize instance in login tests --- .../auth_login/components/__tests__/login_form.test.tsx | 7 ++++--- .../auth_login/components/__tests__/login_page.test.tsx | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/soapbox/features/auth_login/components/__tests__/login_form.test.tsx b/app/soapbox/features/auth_login/components/__tests__/login_form.test.tsx index b46acf31a..8c388601f 100644 --- a/app/soapbox/features/auth_login/components/__tests__/login_form.test.tsx +++ b/app/soapbox/features/auth_login/components/__tests__/login_form.test.tsx @@ -1,6 +1,7 @@ -import { Map as ImmutableMap } from 'immutable'; import React from 'react'; +import { normalizeInstance } from 'soapbox/normalizers'; + import { fireEvent, render, screen } from '../../../../jest/test-helpers'; import LoginForm from '../login_form'; @@ -8,7 +9,7 @@ describe('', () => { it('renders for Pleroma', () => { const mockFn = jest.fn(); const store = { - instance: ImmutableMap({ + instance: normalizeInstance({ version: '2.7.2 (compatible; Pleroma 2.3.0)', }), }; @@ -21,7 +22,7 @@ describe('', () => { it('renders for Mastodon', () => { const mockFn = jest.fn(); const store = { - instance: ImmutableMap({ + instance: normalizeInstance({ version: '3.0.0', }), }; diff --git a/app/soapbox/features/auth_login/components/__tests__/login_page.test.tsx b/app/soapbox/features/auth_login/components/__tests__/login_page.test.tsx index 50f94b285..70a0f3b95 100644 --- a/app/soapbox/features/auth_login/components/__tests__/login_page.test.tsx +++ b/app/soapbox/features/auth_login/components/__tests__/login_page.test.tsx @@ -1,13 +1,14 @@ -import { Map as ImmutableMap } from 'immutable'; import React from 'react'; +import { normalizeInstance } from 'soapbox/normalizers'; + import { render, screen } from '../../../../jest/test-helpers'; import LoginPage from '../login_page'; describe('', () => { it('renders correctly on load', () => { const store = { - instance: ImmutableMap({ + instance: normalizeInstance({ version: '2.7.2 (compatible; Pleroma 2.3.0)', }), }; From d59fa058124e363d0d3c7855539e1208679002bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 12 Aug 2022 13:42:19 +0200 Subject: [PATCH 10/26] Add more context to post edit notification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/features/notifications/components/notification.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/features/notifications/components/notification.tsx b/app/soapbox/features/notifications/components/notification.tsx index 642b92a7f..cbf67c4dd 100644 --- a/app/soapbox/features/notifications/components/notification.tsx +++ b/app/soapbox/features/notifications/components/notification.tsx @@ -104,7 +104,7 @@ const messages: Record = defineMessages({ }, update: { id: 'notification.update', - defaultMessage: '{name} edited a post', + defaultMessage: '{name} edited a post you interacted with', }, }); From 66216bd5b678cd8d1107262a25bccf30be0fcf32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 12 Aug 2022 13:47:32 +0200 Subject: [PATCH 11/26] Use Array.includes instead of indexOf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/base_polyfills.ts | 2 +- app/soapbox/components/autosuggest_input.tsx | 2 +- app/soapbox/components/autosuggest_textarea.tsx | 2 +- .../features/account_gallery/components/media_item.js | 4 ++-- app/soapbox/features/emoji/emoji_mart_search_light.js | 8 ++++---- app/soapbox/features/emoji/emoji_utils.js | 6 +++--- app/soapbox/features/status/components/card.tsx | 2 +- app/soapbox/features/ui/components/modal_root.js | 2 +- app/soapbox/features/ui/components/user_panel.tsx | 2 +- app/soapbox/features/ui/index.tsx | 2 +- app/soapbox/selectors/index.ts | 2 +- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/soapbox/base_polyfills.ts b/app/soapbox/base_polyfills.ts index 53146d222..a6e92bb3c 100644 --- a/app/soapbox/base_polyfills.ts +++ b/app/soapbox/base_polyfills.ts @@ -37,7 +37,7 @@ if (!HTMLCanvasElement.prototype.toBlob) { const dataURL = this.toDataURL(type, quality); let data; - if (dataURL.indexOf(BASE64_MARKER) >= 0) { + if (dataURL.includes(BASE64_MARKER)) { const [, base64] = dataURL.split(BASE64_MARKER); data = decodeBase64(base64); } else { diff --git a/app/soapbox/components/autosuggest_input.tsx b/app/soapbox/components/autosuggest_input.tsx index e6a4af6d6..54f126a23 100644 --- a/app/soapbox/components/autosuggest_input.tsx +++ b/app/soapbox/components/autosuggest_input.tsx @@ -30,7 +30,7 @@ const textAtCursorMatchesToken = (str: string, caretPosition: number, searchToke word = str.slice(left, right + caretPosition); } - if (!word || word.trim().length < 3 || searchTokens.indexOf(word[0]) === -1) { + if (!word || word.trim().length < 3 || !searchTokens.includes(word[0])) { return [null, null]; } diff --git a/app/soapbox/components/autosuggest_textarea.tsx b/app/soapbox/components/autosuggest_textarea.tsx index b5d54b670..a475e5ce2 100644 --- a/app/soapbox/components/autosuggest_textarea.tsx +++ b/app/soapbox/components/autosuggest_textarea.tsx @@ -23,7 +23,7 @@ const textAtCursorMatchesToken = (str: string, caretPosition: number) => { word = str.slice(left, right + caretPosition); } - if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) { + if (!word || word.trim().length < 3 || !['@', ':', '#'].includes(word[0])) { return [null, null]; } diff --git a/app/soapbox/features/account_gallery/components/media_item.js b/app/soapbox/features/account_gallery/components/media_item.js index fe8c6cc85..aa15e39e2 100644 --- a/app/soapbox/features/account_gallery/components/media_item.js +++ b/app/soapbox/features/account_gallery/components/media_item.js @@ -51,7 +51,7 @@ class MediaItem extends ImmutablePureComponent { hoverToPlay = () => { const { autoPlayGif } = this.props; - return !autoPlayGif && ['gifv', 'video'].indexOf(this.props.attachment.get('type')) !== -1; + return !autoPlayGif && ['gifv', 'video'].includes(this.props.attachment.get('type')); } handleClick = e => { @@ -93,7 +93,7 @@ class MediaItem extends ImmutablePureComponent { style={{ objectPosition: `${x}% ${y}%` }} /> ); - } else if (['gifv', 'video'].indexOf(attachment.get('type')) !== -1) { + } else if (['gifv', 'video'].includes(attachment.get('type'))) { const conditionalAttributes = {}; if (isIOS()) { conditionalAttributes.playsInline = '1'; diff --git a/app/soapbox/features/emoji/emoji_mart_search_light.js b/app/soapbox/features/emoji/emoji_mart_search_light.js index 89e25785f..f16918ada 100644 --- a/app/soapbox/features/emoji/emoji_mart_search_light.js +++ b/app/soapbox/features/emoji/emoji_mart_search_light.js @@ -85,8 +85,8 @@ export function search(value, { emojisToShowFilter, maxResults, include, exclude pool = {}; data.categories.forEach(category => { - const isIncluded = include && include.length ? include.indexOf(category.name.toLowerCase()) > -1 : true; - const isExcluded = exclude && exclude.length ? exclude.indexOf(category.name.toLowerCase()) > -1 : false; + const isIncluded = include && include.length ? include.includes(category.name.toLowerCase()) : true; + const isExcluded = exclude && exclude.length ? exclude.includes(category.name.toLowerCase()) : false; if (!isIncluded || isExcluded) { return; } @@ -95,8 +95,8 @@ export function search(value, { emojisToShowFilter, maxResults, include, exclude }); if (custom.length) { - const customIsIncluded = include && include.length ? include.indexOf('custom') > -1 : true; - const customIsExcluded = exclude && exclude.length ? exclude.indexOf('custom') > -1 : false; + const customIsIncluded = include && include.length ? include.includes('custom') : true; + const customIsExcluded = exclude && exclude.length ? exclude.includes('custom') : false; if (customIsIncluded && !customIsExcluded) { addCustomToPool(custom, pool); } diff --git a/app/soapbox/features/emoji/emoji_utils.js b/app/soapbox/features/emoji/emoji_utils.js index 1f4629edf..ad0319598 100644 --- a/app/soapbox/features/emoji/emoji_utils.js +++ b/app/soapbox/features/emoji/emoji_utils.js @@ -15,7 +15,7 @@ const buildSearch = (data) => { (split ? string.split(/[-|_|\s]+/) : [string]).forEach((s) => { s = s.toLowerCase(); - if (search.indexOf(s) === -1) { + if (!search.includes(s)) { search.push(s); } }); @@ -190,7 +190,7 @@ function getData(emoji, skin, set) { function uniq(arr) { return arr.reduce((acc, item) => { - if (acc.indexOf(item) === -1) { + if (!acc.includes(item)) { acc.push(item); } return acc; @@ -201,7 +201,7 @@ function intersect(a, b) { const uniqA = uniq(a); const uniqB = uniq(b); - return uniqA.filter(item => uniqB.indexOf(item) >= 0); + return uniqA.filter(item => uniqB.includes(item)); } function deepMerge(a, b) { diff --git a/app/soapbox/features/status/components/card.tsx b/app/soapbox/features/status/components/card.tsx index fbbe0648d..d15eaadf3 100644 --- a/app/soapbox/features/status/components/card.tsx +++ b/app/soapbox/features/status/components/card.tsx @@ -26,7 +26,7 @@ const addAutoPlay = (html: string): string => { const iframe = document.querySelector('iframe'); if (iframe) { - if (iframe.src.indexOf('?') !== -1) { + if (iframe.src.includes('?')) { iframe.src += '&'; } else { iframe.src += '?'; diff --git a/app/soapbox/features/ui/components/modal_root.js b/app/soapbox/features/ui/components/modal_root.js index 62f9fa50f..f7b9b007c 100644 --- a/app/soapbox/features/ui/components/modal_root.js +++ b/app/soapbox/features/ui/components/modal_root.js @@ -92,7 +92,7 @@ export default class ModalRoot extends React.PureComponent { } renderLoading = modalId => () => { - return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? : null; + return !['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].includes(modalId) ? : null; } renderError = (props) => { diff --git a/app/soapbox/features/ui/components/user_panel.tsx b/app/soapbox/features/ui/components/user_panel.tsx index 6bf77b659..3c801fd25 100644 --- a/app/soapbox/features/ui/components/user_panel.tsx +++ b/app/soapbox/features/ui/components/user_panel.tsx @@ -28,7 +28,7 @@ const UserPanel: React.FC = ({ accountId, action, badges, domain }) if (!account) return null; const displayNameHtml = { __html: account.get('display_name_html') }; - const acct = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); + const acct = !account.get('acct').includes('@') && domain ? `${account.get('acct')}@${domain}` : account.get('acct'); const header = account.get('header'); const verified = account.get('verified'); diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index b9fdab67d..e7c0f74e2 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -352,7 +352,7 @@ const UI: React.FC = ({ children }) => { const handleDragEnter = (e: DragEvent) => { e.preventDefault(); - if (e.target && dragTargets.current.indexOf(e.target) === -1) { + if (e.target && !dragTargets.current.includes(e.target)) { dragTargets.current.push(e.target); } diff --git a/app/soapbox/selectors/index.ts b/app/soapbox/selectors/index.ts index c1ab55532..2a026afe1 100644 --- a/app/soapbox/selectors/index.ts +++ b/app/soapbox/selectors/index.ts @@ -93,7 +93,7 @@ const toServerSideType = (columnType: string): string => { case 'thread': return columnType; default: - if (columnType.indexOf('list:') > -1) { + if (columnType.includes('list:')) { return 'home'; } else { return 'public'; // community, account, hashtag From e402660bc1f1813e9bed19f40a98796ce627237f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 12 Aug 2022 10:15:21 -0500 Subject: [PATCH 12/26] StatusPage: convert to TSX --- app/soapbox/pages/status_page.js | 69 ------------------------------- app/soapbox/pages/status_page.tsx | 57 +++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 69 deletions(-) delete mode 100644 app/soapbox/pages/status_page.js create mode 100644 app/soapbox/pages/status_page.tsx diff --git a/app/soapbox/pages/status_page.js b/app/soapbox/pages/status_page.js deleted file mode 100644 index e4ff9955e..000000000 --- a/app/soapbox/pages/status_page.js +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { connect } from 'react-redux'; - -import LinkFooter from 'soapbox/features/ui/components/link_footer'; -import { - WhoToFollowPanel, - TrendsPanel, - SignUpPanel, - CtaBanner, -} from 'soapbox/features/ui/util/async-components'; -// import GroupSidebarPanel from '../features/groups/sidebar_panel'; -import { getFeatures } from 'soapbox/utils/features'; - -import { Layout } from '../components/ui'; -import BundleContainer from '../features/ui/containers/bundle_container'; - -const mapStateToProps = state => { - const me = state.get('me'); - const features = getFeatures(state.get('instance')); - - return { - me, - showTrendsPanel: features.trends, - showWhoToFollowPanel: features.suggestions, - }; -}; - -export default @connect(mapStateToProps) -class StatusPage extends ImmutablePureComponent { - - render() { - const { me, children, showTrendsPanel, showWhoToFollowPanel } = this.props; - - return ( - <> - - {children} - - {!me && ( - - {Component => } - - )} - - - - {!me && ( - - {Component => } - - )} - {showTrendsPanel && ( - - {Component => } - - )} - {showWhoToFollowPanel && ( - - {Component => } - - )} - - - - ); - } - -} diff --git a/app/soapbox/pages/status_page.tsx b/app/soapbox/pages/status_page.tsx new file mode 100644 index 000000000..2c35947ad --- /dev/null +++ b/app/soapbox/pages/status_page.tsx @@ -0,0 +1,57 @@ +import React from 'react'; + +import LinkFooter from 'soapbox/features/ui/components/link_footer'; +import { + WhoToFollowPanel, + TrendsPanel, + SignUpPanel, + CtaBanner, +} from 'soapbox/features/ui/util/async-components'; +import { useAppSelector, useFeatures } from 'soapbox/hooks'; + +import { Layout } from '../components/ui'; +import BundleContainer from '../features/ui/containers/bundle_container'; + +interface IStatusPage { + children: React.ReactNode, +} + +const StatusPage: React.FC = ({ children }) => { + const me = useAppSelector(state => state.me); + const features = useFeatures(); + + return ( + <> + + {children} + + {!me && ( + + {Component => } + + )} + + + + {!me && ( + + {Component => } + + )} + {features.trends && ( + + {Component => } + + )} + {features.suggestions && ( + + {Component => } + + )} + + + + ); +}; + +export default StatusPage; From 538613511291b1b0764439581e959ba8eff830d0 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 12 Aug 2022 10:22:30 -0500 Subject: [PATCH 13/26] WrappedRoute: fix PageProps type to include children --- app/soapbox/features/ui/util/react_router_helpers.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/soapbox/features/ui/util/react_router_helpers.tsx b/app/soapbox/features/ui/util/react_router_helpers.tsx index b7dbdb192..992a60e2b 100644 --- a/app/soapbox/features/ui/util/react_router_helpers.tsx +++ b/app/soapbox/features/ui/util/react_router_helpers.tsx @@ -13,6 +13,7 @@ import BundleContainer from '../containers/bundle_container'; type PageProps = { params?: MatchType['params'], layout?: any, + children: React.ReactNode, }; interface IWrappedRoute extends RouteProps { From 7615111eb0587f0bdcd97ca54329a605da660fa7 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 12 Aug 2022 10:42:26 -0500 Subject: [PATCH 14/26] IconButton: add themes --- app/soapbox/components/ui/icon-button/icon-button.tsx | 7 +++++-- app/soapbox/features/account/components/header.tsx | 9 ++++++--- .../features/ui/components/subscription-button.tsx | 3 ++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/soapbox/components/ui/icon-button/icon-button.tsx b/app/soapbox/components/ui/icon-button/icon-button.tsx index 352c7a92c..6b4f5414a 100644 --- a/app/soapbox/components/ui/icon-button/icon-button.tsx +++ b/app/soapbox/components/ui/icon-button/icon-button.tsx @@ -12,12 +12,14 @@ interface IIconButton extends React.ButtonHTMLAttributes { /** Text to display next ot the button. */ text?: string, /** Don't render a background behind the icon. */ - transparent?: boolean + transparent?: boolean, + /** Predefined styles to display for the button. */ + theme?: 'seamless' | 'outlined', } /** A clickable icon. */ const IconButton = React.forwardRef((props: IIconButton, ref: React.ForwardedRef): JSX.Element => { - const { src, className, iconClassName, text, transparent = false, ...filteredProps } = props; + const { src, className, iconClassName, text, transparent = false, theme = 'seamless', ...filteredProps } = props; return (