From 18be78475c74b3f3593fc9123b609877a5a85f08 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Mon, 24 Jul 2023 11:11:44 -0400 Subject: [PATCH 01/62] Fix bug trying to switch accounts In the profile dropdown, you couldn't click the avatar or the user's name because we had the `to` attribute on the LinkEl. This fixes that by only adding the link props when we render a Link. --- app/soapbox/components/account.tsx | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/app/soapbox/components/account.tsx b/app/soapbox/components/account.tsx index 861b8d475..18b4de814 100644 --- a/app/soapbox/components/account.tsx +++ b/app/soapbox/components/account.tsx @@ -179,6 +179,11 @@ const Account = ({ if (withDate) timestamp = account.created_at; const LinkEl: any = withLinkToProfile ? Link : 'div'; + const linkProps = withLinkToProfile ? { + to: `/@${account.acct}`, + title: account.acct, + onClick: (event: React.MouseEvent) => event.stopPropagation(), + } : {}; return (
@@ -188,11 +193,7 @@ const Account = ({ condition={showProfileHoverCard} wrapper={(children) => {children}} > - event.stopPropagation()} - > + {emoji && ( {children}} > - event.stopPropagation()} - > + Date: Sat, 16 Sep 2023 18:07:25 +0000 Subject: [PATCH 02/62] fix(deps): update dependency clsx to v2 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 043a10d6f..5ef9f2b9b 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "bootstrap-icons": "^1.5.0", "bowser": "^2.11.0", "browserslist": "^4.16.6", - "clsx": "^1.2.1", + "clsx": "^2.0.0", "core-js": "^3.27.2", "cryptocurrency-icons": "^0.18.1", "cssnano": "^5.1.10", diff --git a/yarn.lock b/yarn.lock index acfaaf99b..d401460cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3615,10 +3615,10 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" -clsx@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" - integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== +clsx@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" + integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== color-convert@^1.9.0: version "1.9.3" From f5a3a26dae9827c8b2a27ade788f1245c68808c0 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Sep 2023 14:36:53 -0500 Subject: [PATCH 03/62] tsconfig: fix third-party types --- tsconfig.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 9d426c391..14215616d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,9 +12,14 @@ "moduleResolution": "node", "resolveJsonModule": true, "esModuleInterop": true, - "typeRoots": [ "./types", "./node_modules/@types"], + "typeRoots": [ + "./types", + "./node_modules/@types", + "./node_modules" + ], "types": [ "vite/client", + "vitest/globals", "vite-plugin-compile-time/client" ] }, From 79b837cb89d388dfed774b61be109df0d6d46ec0 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Sep 2023 14:40:10 -0500 Subject: [PATCH 04/62] Upgrade @testing-library deps, move to devDependencies --- package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 5ef9f2b9b..2c6a3cb77 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "@tailwindcss/forms": "^0.5.3", "@tailwindcss/typography": "^0.5.9", "@tanstack/react-query": "^4.0.10", - "@testing-library/react": "^14.0.0", "@types/escape-html": "^1.0.1", "@types/http-link-header": "^1.0.3", "@types/leaflet": "^1.8.0", @@ -179,8 +178,9 @@ "devDependencies": { "@gitbeaker/node": "^35.8.0", "@jedmao/redux-mock-store": "^3.0.5", + "@testing-library/react": "^14.0.0", "@testing-library/react-hooks": "^8.0.1", - "@testing-library/user-event": "^14.4.3", + "@testing-library/user-event": "^14.5.1", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "babel-plugin-transform-require-context": "^0.1.1", diff --git a/yarn.lock b/yarn.lock index d401460cd..8348e65da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2222,10 +2222,10 @@ "@testing-library/dom" "^9.0.0" "@types/react-dom" "^18.0.0" -"@testing-library/user-event@^14.4.3": - version "14.4.3" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.4.3.tgz#af975e367743fa91989cd666666aec31a8f50591" - integrity sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q== +"@testing-library/user-event@^14.5.1": + version "14.5.1" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.1.tgz#27337d72046d5236b32fd977edee3f74c71d332f" + integrity sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg== "@tootallnate/once@2": version "2.0.0" From afaac6eed2788f918697399f19f4e581a69ac9ad Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Sep 2023 15:16:35 -0500 Subject: [PATCH 05/62] vitest: add setup file, use @testing-library/jest-dom --- app/soapbox/jest/test-setup.ts | 22 +++++++------- package.json | 1 + vite.config.ts | 1 + yarn.lock | 54 +++++++++++++++++++++++++++++++++- 4 files changed, 66 insertions(+), 12 deletions(-) diff --git a/app/soapbox/jest/test-setup.ts b/app/soapbox/jest/test-setup.ts index 419c9ea37..732a36ccf 100644 --- a/app/soapbox/jest/test-setup.ts +++ b/app/soapbox/jest/test-setup.ts @@ -1,18 +1,18 @@ -'use strict'; - import { act } from '@testing-library/react'; import { toast } from 'react-hot-toast'; +// eslint-disable-next-line import/no-extraneous-dependencies +import '@testing-library/jest-dom/vitest'; import { __clear as clearApiMocks } from '../api/__mocks__'; // API mocking -jest.mock('soapbox/api'); +vi.mock('soapbox/api'); afterEach(() => { clearApiMocks(); }); // Query mocking -jest.mock('soapbox/queries/client'); +vi.mock('soapbox/queries/client'); // Mock IndexedDB // https://dev.to/andyhaskell/testing-your-indexeddb-code-with-jest-2o17 @@ -26,18 +26,18 @@ afterEach(() => { }); const intersectionObserverMock = () => ({ observe: () => null, disconnect: () => null }); -window.IntersectionObserver = jest.fn().mockImplementation(intersectionObserverMock); +window.IntersectionObserver = vi.fn().mockImplementation(intersectionObserverMock); Object.defineProperty(window, 'matchMedia', { writable: true, - value: jest.fn().mockImplementation(query => ({ + value: vi.fn().mockImplementation(query => ({ matches: false, media: query, onchange: null, - addListener: jest.fn(), // Deprecated - removeListener: jest.fn(), // Deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), + addListener: vi.fn(), // Deprecated + removeListener: vi.fn(), // Deprecated + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), })), }); diff --git a/package.json b/package.json index 2c6a3cb77..ccf8f6ac3 100644 --- a/package.json +++ b/package.json @@ -178,6 +178,7 @@ "devDependencies": { "@gitbeaker/node": "^35.8.0", "@jedmao/redux-mock-store": "^3.0.5", + "@testing-library/jest-dom": "^6.1.3", "@testing-library/react": "^14.0.0", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.5.1", diff --git a/vite.config.ts b/vite.config.ts index 8917dc0bc..5c127b4f3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -90,5 +90,6 @@ export default defineConfig({ cache: { dir: '../node_modules/.vitest', }, + setupFiles: 'soapbox/jest/test-setup.ts', }, }); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 8348e65da..60fdbffa7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,11 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== +"@adobe/css-tools@^4.3.0": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.1.tgz#abfccb8ca78075a2b6187345c26243c1a0842f28" + integrity sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg== + "@akryum/flexsearch-es@^0.7.32": version "0.7.32" resolved "https://registry.yarnpkg.com/@akryum/flexsearch-es/-/flexsearch-es-0.7.32.tgz#9d00e6bdf2418ae686323a4a9224b7a1568075e9" @@ -2205,6 +2210,20 @@ lz-string "^1.5.0" pretty-format "^27.0.2" +"@testing-library/jest-dom@^6.1.3": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.1.3.tgz#443118c9e4043f96396f120de2c7122504a079c5" + integrity sha512-YzpjRHoCBWPzpPNtg6gnhasqtE/5O4qz8WCwDEaxtfnPO6gkaLrnuXusrGSPyhIGPezr1HM7ZH0CFaUTY9PJEQ== + dependencies: + "@adobe/css-tools" "^4.3.0" + "@babel/runtime" "^7.9.2" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.5.6" + lodash "^4.17.15" + redent "^3.0.0" + "@testing-library/react-hooks@^8.0.1": version "8.0.1" resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12" @@ -3529,6 +3548,14 @@ chalk@^2.3.0, chalk@^2.3.2, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -3841,6 +3868,11 @@ css-what@^6.0.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" @@ -4160,6 +4192,11 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-accessibility-api@^0.5.6: + version "0.5.16" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + dom-accessibility-api@^0.5.9: version "0.5.13" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.13.tgz#102ee5f25eacce09bdf1cfa5a298f86da473be4b" @@ -6563,7 +6600,7 @@ mimic-response@^3.1.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -min-indent@^1.0.1: +min-indent@^1.0.0, min-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== @@ -7963,6 +8000,14 @@ realistic-structured-clone@^3.0.0: typeson "^6.1.0" typeson-registry "^1.0.0-alpha.20" +redent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" + integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== + dependencies: + indent-string "^4.0.0" + strip-indent "^3.0.0" + redent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-4.0.0.tgz#0c0ba7caabb24257ab3bb7a4fd95dd1d5c5681f9" @@ -8604,6 +8649,13 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + strip-indent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-4.0.0.tgz#b41379433dd06f5eae805e21d631e07ee670d853" From 5ab87616c190c34ff430ba16be64b4301e31a691 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 16 Sep 2023 15:16:59 -0500 Subject: [PATCH 06/62] vitest: jest --> vi --- app/soapbox/__tests__/toast.test.tsx | 2 +- app/soapbox/actions/__tests__/compose.test.ts | 4 ++-- app/soapbox/actions/__tests__/me.test.ts | 4 ++-- app/soapbox/actions/__tests__/onboarding.test.ts | 12 ++++++------ .../ui/button/__tests__/button.test.tsx | 4 ++-- .../ui/datepicker/__tests__/datepicker.test.tsx | 8 ++++---- .../components/ui/form/__tests__/form.test.tsx | 4 ++-- .../components/ui/modal/__tests__/modal.test.tsx | 12 ++++++------ .../components/__tests__/login-form.test.tsx | 6 +++--- .../components/__tests__/chat-list-item.test.tsx | 10 +++++----- .../__tests__/chat-message-reaction.test.tsx | 12 ++++++------ .../__tests__/chat-pane-header.test.tsx | 16 ++++++++-------- .../polls/__tests__/duration-selector.test.tsx | 10 +++++----- .../__tests__/feed-carousel.test.tsx | 4 ++-- .../features/groups/__tests__/discover.test.tsx | 2 +- .../discover/__tests__/layout-buttons.test.tsx | 4 ++-- .../search/__tests__/recent-searches.test.tsx | 8 ++++---- .../discover/search/__tests__/results.test.tsx | 2 +- .../discover/search/__tests__/search.test.tsx | 6 +++--- .../modals/__tests__/landing-page-modal.test.tsx | 8 ++++---- .../modals/__tests__/unauthorized-modal.test.tsx | 8 ++++---- .../report-modal/__tests__/report-modal.test.tsx | 6 +++--- app/soapbox/hooks/__mocks__/resize-observer.ts | 2 +- 23 files changed, 77 insertions(+), 77 deletions(-) diff --git a/app/soapbox/__tests__/toast.test.tsx b/app/soapbox/__tests__/toast.test.tsx index 795c11493..bdeff2249 100644 --- a/app/soapbox/__tests__/toast.test.tsx +++ b/app/soapbox/__tests__/toast.test.tsx @@ -20,7 +20,7 @@ function renderApp() { } beforeAll(() => { - jest.spyOn(console, 'error').mockImplementation(() => {}); + vi.spyOn(console, 'error').mockImplementation(() => {}); }); afterEach(() => { diff --git a/app/soapbox/actions/__tests__/compose.test.ts b/app/soapbox/actions/__tests__/compose.test.ts index 58f83e537..f6c64c929 100644 --- a/app/soapbox/actions/__tests__/compose.test.ts +++ b/app/soapbox/actions/__tests__/compose.test.ts @@ -41,7 +41,7 @@ describe('uploadCompose()', () => { it('creates an alert if exceeds max size', async() => { const mockIntl = { - formatMessage: jest.fn().mockReturnValue('Image exceeds the current file size limit (10 Bytes)'), + formatMessage: vi.fn().mockReturnValue('Image exceeds the current file size limit (10 Bytes)'), } as unknown as IntlShape; const expectedActions = [ @@ -87,7 +87,7 @@ describe('uploadCompose()', () => { it('creates an alert if exceeds max size', async() => { const mockIntl = { - formatMessage: jest.fn().mockReturnValue('Video exceeds the current file size limit (10 Bytes)'), + formatMessage: vi.fn().mockReturnValue('Video exceeds the current file size limit (10 Bytes)'), } as unknown as IntlShape; const expectedActions = [ diff --git a/app/soapbox/actions/__tests__/me.test.ts b/app/soapbox/actions/__tests__/me.test.ts index 91fba12fa..0187ba093 100644 --- a/app/soapbox/actions/__tests__/me.test.ts +++ b/app/soapbox/actions/__tests__/me.test.ts @@ -7,10 +7,10 @@ import { AuthUserRecord, ReducerRecord } from 'soapbox/reducers/auth'; import { fetchMe, patchMe } from '../me'; -jest.mock('../../storage/kv-store', () => ({ +vi.mock('../../storage/kv-store', () => ({ __esModule: true, default: { - getItemOrError: jest.fn().mockReturnValue(Promise.resolve({})), + getItemOrError: vi.fn().mockReturnValue(Promise.resolve({})), }, })); diff --git a/app/soapbox/actions/__tests__/onboarding.test.ts b/app/soapbox/actions/__tests__/onboarding.test.ts index f786c7f90..15a10f93a 100644 --- a/app/soapbox/actions/__tests__/onboarding.test.ts +++ b/app/soapbox/actions/__tests__/onboarding.test.ts @@ -10,11 +10,11 @@ describe('checkOnboarding()', () => { }); beforeEach(() => { - mockGetItem = jest.fn().mockReturnValue(null); + mockGetItem = vi.fn().mockReturnValue(null); }); it('does nothing if localStorage item is not set', async() => { - mockGetItem = jest.fn().mockReturnValue(null); + mockGetItem = vi.fn().mockReturnValue(null); const state = rootState.setIn(['onboarding', 'needsOnboarding'], false); const store = mockStore(state); @@ -27,7 +27,7 @@ describe('checkOnboarding()', () => { }); it('does nothing if localStorage item is invalid', async() => { - mockGetItem = jest.fn().mockReturnValue('invalid'); + mockGetItem = vi.fn().mockReturnValue('invalid'); const state = rootState.setIn(['onboarding', 'needsOnboarding'], false); const store = mockStore(state); @@ -40,7 +40,7 @@ describe('checkOnboarding()', () => { }); it('dispatches the correct action', async() => { - mockGetItem = jest.fn().mockReturnValue('1'); + mockGetItem = vi.fn().mockReturnValue('1'); const state = rootState.setIn(['onboarding', 'needsOnboarding'], false); const store = mockStore(state); @@ -61,7 +61,7 @@ describe('startOnboarding()', () => { }); beforeEach(() => { - mockSetItem = jest.fn(); + mockSetItem = vi.fn(); }); it('dispatches the correct action', async() => { @@ -84,7 +84,7 @@ describe('endOnboarding()', () => { }); beforeEach(() => { - mockRemoveItem = jest.fn(); + mockRemoveItem = vi.fn(); }); it('dispatches the correct action', async() => { diff --git a/app/soapbox/components/ui/button/__tests__/button.test.tsx b/app/soapbox/components/ui/button/__tests__/button.test.tsx index 8d1f65ec9..f9c59c68a 100644 --- a/app/soapbox/components/ui/button/__tests__/button.test.tsx +++ b/app/soapbox/components/ui/button/__tests__/button.test.tsx @@ -27,7 +27,7 @@ describe(' diff --git a/src/features/edit-password/index.tsx b/src/features/edit-password/index.tsx index 026515d53..7761f77a2 100644 --- a/src/features/edit-password/index.tsx +++ b/src/features/edit-password/index.tsx @@ -3,11 +3,9 @@ import { defineMessages, useIntl } from 'react-intl'; import { changePassword } from 'soapbox/actions/security'; import { Button, Column, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui'; -import { useAppDispatch, useFeatures } from 'soapbox/hooks'; +import { useAppDispatch } from 'soapbox/hooks'; import toast from 'soapbox/toast'; -import PasswordIndicator from '../verification/components/password-indicator'; - const messages = defineMessages({ updatePasswordSuccess: { id: 'security.update_password.success', defaultMessage: 'Password successfully updated.' }, updatePasswordFail: { id: 'security.update_password.fail', defaultMessage: 'Update password failed.' }, @@ -24,11 +22,9 @@ const initialState = { currentPassword: '', newPassword: '', newPasswordConfirma const EditPassword = () => { const intl = useIntl(); const dispatch = useAppDispatch(); - const { passwordRequirements } = useFeatures(); const [state, setState] = React.useState(initialState); const [isLoading, setLoading] = React.useState(false); - const [hasValidPassword, setHasValidPassword] = React.useState(passwordRequirements ? false : true); const { currentPassword, newPassword, newPasswordConfirmation } = state; @@ -73,10 +69,6 @@ const EditPassword = () => { onChange={handleInputChange} value={newPassword} /> - - {passwordRequirements && ( - - )} @@ -93,7 +85,7 @@ const EditPassword = () => { {intl.formatMessage(messages.cancel)} - diff --git a/src/features/landing-page/__tests__/landing-page.test.tsx b/src/features/landing-page/__tests__/landing-page.test.tsx index 6e96672ba..bbc96deeb 100644 --- a/src/features/landing-page/__tests__/landing-page.test.tsx +++ b/src/features/landing-page/__tests__/landing-page.test.tsx @@ -1,10 +1,9 @@ import React from 'react'; +import { rememberInstance } from 'soapbox/actions/instance'; +import { render, screen, rootReducer } from 'soapbox/jest/test-helpers'; + import LandingPage from '..'; -import { rememberInstance } from '../../../actions/instance'; -import { SOAPBOX_CONFIG_REMEMBER_SUCCESS } from '../../../actions/soapbox'; -import { PEPE_FETCH_INSTANCE_SUCCESS } from '../../../actions/verification'; -import { render, screen, rootReducer, applyActions } from '../../../jest/test-helpers'; describe('', () => { it('renders a RegistrationForm for an open Pleroma instance', () => { @@ -21,7 +20,6 @@ describe('', () => { expect(screen.queryByTestId('registrations-open')).toBeInTheDocument(); expect(screen.queryByTestId('registrations-closed')).not.toBeInTheDocument(); - expect(screen.queryByTestId('registrations-pepe')).not.toBeInTheDocument(); }); it('renders "closed" message for a closed Pleroma instance', () => { @@ -38,53 +36,5 @@ describe('', () => { expect(screen.queryByTestId('registrations-closed')).toBeInTheDocument(); expect(screen.queryByTestId('registrations-open')).not.toBeInTheDocument(); - expect(screen.queryByTestId('registrations-pepe')).not.toBeInTheDocument(); - }); - - it('renders Pepe flow if Pepe extension is enabled', () => { - - const state = applyActions(undefined, [{ - type: SOAPBOX_CONFIG_REMEMBER_SUCCESS, - soapboxConfig: { - extensions: { - pepe: { - enabled: true, - }, - }, - }, - }, { - type: PEPE_FETCH_INSTANCE_SUCCESS, - instance: { - registrations: true, - }, - }], rootReducer); - - render(, undefined, state); - - expect(screen.queryByTestId('registrations-pepe')).toBeInTheDocument(); - expect(screen.queryByTestId('registrations-open')).not.toBeInTheDocument(); - expect(screen.queryByTestId('registrations-closed')).not.toBeInTheDocument(); - }); - - it('renders "closed" message for a Truth Social instance with Pepe closed', () => { - - const state = applyActions(undefined, [{ - type: rememberInstance.fulfilled.type, - payload: { - version: '3.4.1 (compatible; TruthSocial 1.0.0)', - registrations: false, - }, - }, { - type: PEPE_FETCH_INSTANCE_SUCCESS, - instance: { - registrations: false, - }, - }], rootReducer); - - render(, undefined, state); - - expect(screen.queryByTestId('registrations-closed')).toBeInTheDocument(); - expect(screen.queryByTestId('registrations-pepe')).not.toBeInTheDocument(); - expect(screen.queryByTestId('registrations-open')).not.toBeInTheDocument(); }); }); diff --git a/src/features/landing-page/index.tsx b/src/features/landing-page/index.tsx index ce1180063..a31d7d313 100644 --- a/src/features/landing-page/index.tsx +++ b/src/features/landing-page/index.tsx @@ -4,16 +4,14 @@ import { FormattedMessage } from 'react-intl'; import { prepareRequest } from 'soapbox/actions/consumer-auth'; import Markup from 'soapbox/components/markup'; 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 { useAppDispatch, useFeatures, useInstance, useRegistrationStatus, useSoapboxConfig } from 'soapbox/hooks'; +import { useAppDispatch, useFeatures, useInstance, useSoapboxConfig } from 'soapbox/hooks'; import { capitalize } from 'soapbox/utils/strings'; const LandingPage = () => { const dispatch = useAppDispatch(); const features = useFeatures(); const soapboxConfig = useSoapboxConfig(); - const { pepeEnabled, pepeOpen } = useRegistrationStatus(); const instance = useInstance(); /** Registrations are closed */ @@ -65,34 +63,10 @@ const LandingPage = () => { ); }; - /** Pepe API registrations are open */ - const renderPepe = () => { - return ( - - - - - - - - - - - - - - - ); - }; - // Render registration flow depending on features const renderBody = () => { if (soapboxConfig.authProvider) { return renderProvider(); - } else if (pepeEnabled && pepeOpen) { - return renderPepe(); } else if (features.accountCreation && instance.registrations) { return renderOpen(); } else { diff --git a/src/features/ui/components/modal-root.tsx b/src/features/ui/components/modal-root.tsx index 86d1b55cc..f43e2ce03 100644 --- a/src/features/ui/components/modal-root.tsx +++ b/src/features/ui/components/modal-root.tsx @@ -36,7 +36,6 @@ import { ReplyMentionsModal, ReportModal, UnauthorizedModal, - VerifySmsModal, VideoModal, } from 'soapbox/features/ui/util/async-components'; @@ -82,7 +81,6 @@ const MODAL_COMPONENTS = { 'REPLY_MENTIONS': ReplyMentionsModal, 'REPORT': ReportModal, 'UNAUTHORIZED': UnauthorizedModal, - 'VERIFY_SMS': VerifySmsModal, 'VIDEO': VideoModal, }; diff --git a/src/features/ui/components/modals/verify-sms-modal.tsx b/src/features/ui/components/modals/verify-sms-modal.tsx deleted file mode 100644 index b092763a4..000000000 --- a/src/features/ui/components/modals/verify-sms-modal.tsx +++ /dev/null @@ -1,228 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import OtpInput from 'react-otp-input'; - -import { verifyCredentials } from 'soapbox/actions/auth'; -import { closeModal } from 'soapbox/actions/modals'; -import { reConfirmPhoneVerification, reRequestPhoneVerification } from 'soapbox/actions/verification'; -import { FormGroup, PhoneInput, Modal, Stack, Text } from 'soapbox/components/ui'; -import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks'; -import toast from 'soapbox/toast'; -import { getAccessToken } from 'soapbox/utils/auth'; - -const messages = defineMessages({ - verificationInvalid: { - id: 'sms_verification.invalid', - defaultMessage: 'Please enter a valid phone number.', - }, - verificationSuccess: { - id: 'sms_verification.success', - defaultMessage: 'A verification code has been sent to your phone number.', - }, - verificationFail: { - id: 'sms_verification.fail', - defaultMessage: 'Failed to send SMS message to your phone number.', - }, - verificationExpired: { - id: 'sms_verification.expired', - defaultMessage: 'Your SMS token has expired.', - }, - verifySms: { - id: 'sms_verification.modal.verify_sms', - defaultMessage: 'Verify SMS', - }, - verifyNumber: { - id: 'sms_verification.modal.verify_number', - defaultMessage: 'Verify phone number', - }, - verifyCode: { - id: 'sms_verification.modal.verify_code', - defaultMessage: 'Verify code', - }, -}); - -interface IVerifySmsModal { - onClose: (type: string) => void -} - -enum Statuses { - IDLE = 'IDLE', - READY = 'READY', - REQUESTED = 'REQUESTED', - FAIL = 'FAIL', - SUCCESS = 'SUCCESS', -} - -const VerifySmsModal: React.FC = ({ onClose }) => { - const dispatch = useAppDispatch(); - const intl = useIntl(); - const instance = useInstance(); - const accessToken = useAppSelector((state) => getAccessToken(state)); - const isLoading = useAppSelector((state) => state.verification.isLoading); - - const [status, setStatus] = useState(Statuses.IDLE); - const [phone, setPhone] = useState(); - const [verificationCode, setVerificationCode] = useState(''); - const [requestedAnother, setAlreadyRequestedAnother] = useState(false); - - const isValid = !!phone; - - const onChange = useCallback((phone?: string) => { - setPhone(phone); - }, []); - - const handleSubmit = (event: React.MouseEvent) => { - event.preventDefault(); - - if (!isValid) { - setStatus(Statuses.IDLE); - toast.error(intl.formatMessage(messages.verificationInvalid)); - return; - } - - dispatch(reRequestPhoneVerification(phone!)).then(() => { - toast.success( - intl.formatMessage(messages.verificationSuccess), - ); - }) - .finally(() => setStatus(Statuses.REQUESTED)) - .catch(() => { - toast.error(intl.formatMessage(messages.verificationFail)); - }); - }; - - const resendVerificationCode = (event?: React.MouseEvent) => { - setAlreadyRequestedAnother(true); - handleSubmit(event as React.MouseEvent); - }; - - const onConfirmationClick = (event: any) => { - switch (status) { - case Statuses.IDLE: - setStatus(Statuses.READY); - break; - case Statuses.READY: - handleSubmit(event); - break; - case Statuses.REQUESTED: - submitVerification(); - break; - default: break; - } - }; - - const confirmationText = useMemo(() => { - switch (status) { - case Statuses.IDLE: - return intl.formatMessage(messages.verifySms); - case Statuses.READY: - return intl.formatMessage(messages.verifyNumber); - case Statuses.REQUESTED: - return intl.formatMessage(messages.verifyCode); - default: - return null; - } - }, [status]); - - const renderModalBody = () => { - switch (status) { - case Statuses.IDLE: - return ( - - - - ); - case Statuses.READY: - return ( - }> - - - ); - case Statuses.REQUESTED: - return ( - <> - - - - - - - ); - default: - return null; - } - }; - - const submitVerification = () => { - if (!accessToken) return; - // TODO: handle proper validation from Pepe -- expired vs invalid - dispatch(reConfirmPhoneVerification(verificationCode)) - .then(() => { - setStatus(Statuses.SUCCESS); - // eslint-disable-next-line promise/catch-or-return - dispatch(verifyCredentials(accessToken)) - .then(() => dispatch(closeModal('VERIFY_SMS'))); - - }) - .catch(() => toast.error(intl.formatMessage(messages.verificationExpired))); - }; - - useEffect(() => { - if (verificationCode.length === 6) { - submitVerification(); - } - }, [verificationCode]); - - return ( - - } - onClose={() => onClose('VERIFY_SMS')} - cancelAction={status === Statuses.IDLE ? () => onClose('VERIFY_SMS') : undefined} - cancelText='Skip for now' - confirmationAction={onConfirmationClick} - confirmationText={confirmationText} - secondaryAction={status === Statuses.REQUESTED ? resendVerificationCode : undefined} - secondaryText={status === Statuses.REQUESTED ? ( - - ) : undefined} - secondaryDisabled={requestedAnother} - > - - {renderModalBody()} - - - ); -}; - -export default VerifySmsModal; diff --git a/src/features/ui/util/async-components.ts b/src/features/ui/util/async-components.ts index a364bc33a..19a7c3b36 100644 --- a/src/features/ui/util/async-components.ts +++ b/src/features/ui/util/async-components.ts @@ -219,11 +219,11 @@ export function ListEditor() { } export function ListAdder() { - return import(/*webpackChunkName: "features/list_adder" */'../../list-adder'); + return import('../../list-adder'); } export function Search() { - return import(/*webpackChunkName: "features/search" */'../../search'); + return import('../../search'); } export function LoginPage() { @@ -478,24 +478,16 @@ export function OnboardingWizard() { return import('../../onboarding/onboarding-wizard'); } -export function WaitlistPage() { - return import('../../verification/waitlist-page'); -} - export function CompareHistoryModal() { - return import(/*webpackChunkName: "modals/compare_history_modal" */'../components/modals/compare-history-modal'); + return import('../components/modals/compare-history-modal'); } export function AuthTokenList() { return import('../../auth-token-list'); } -export function VerifySmsModal() { - return import('../components/modals/verify-sms-modal'); -} - export function FamiliarFollowersModal() { - return import(/*webpackChunkName: "modals/familiar_followers_modal" */'../components/modals/familiar-followers-modal'); + return import('../components/modals/familiar-followers-modal'); } export function AnnouncementsPanel() { @@ -503,7 +495,7 @@ export function AnnouncementsPanel() { } export function Quotes() { - return import(/*webpackChunkName: "features/quotes" */'../../quotes'); + return import('../../quotes'); } export function ComposeEventModal() { diff --git a/src/features/verification/__tests__/index.test.tsx b/src/features/verification/__tests__/index.test.tsx deleted file mode 100644 index e24aee442..000000000 --- a/src/features/verification/__tests__/index.test.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { Map as ImmutableMap, Record as ImmutableRecord } from 'immutable'; -import React from 'react'; -import { Route, Switch } from 'react-router-dom'; - -import { __stub } from 'soapbox/api'; - -import { render, screen } from '../../../jest/test-helpers'; -import Verification from '../index'; - -const TestableComponent = () => ( - - - Homepage - -); - -const renderComponent = (store: any) => render( - , - {}, - store, - { initialEntries: ['/verify'] }, -); - -describe('', () => { - let store: any; - - beforeEach(() => { - store = { - verification: ImmutableRecord({ - instance: ImmutableMap({ - isReady: true, - registrations: true, - }), - ageMinimum: null, - currentChallenge: null, - isLoading: false, - isComplete: false, - token: null, - })(), - }; - - __stub(mock => { - mock.onGet('/api/v1/pepe/instance') - .reply(200, { - age_minimum: 18, - approval_required: true, - challenges: ['age', 'email', 'sms'], - }); - - mock.onPost('/api/v1/pepe/registrations') - .reply(200, { - access_token: 'N-dZmNqNSmTutJLsGjZ5AnJL4sLw_y-N3pn2acSqJY8', - }); - }); - }); - - describe('When registration is closed', () => { - it('successfully redirects to the homepage', () => { - const verification = store.verification.setIn(['instance', 'registrations'], false); - store.verification = verification; - - renderComponent(store); - expect(screen.getByTestId('home')).toHaveTextContent('Homepage'); - }); - }); - - describe('When verification is complete', () => { - it('successfully renders the Registration component', () => { - const verification = store.verification.set('isComplete', true); - store.verification = verification; - - renderComponent(store); - expect(screen.getByRole('heading')).toHaveTextContent('Register your account'); - }); - }); - - describe('Switching verification steps', () => { - it('successfully renders the Birthday step', () => { - const verification = store.verification.set('currentChallenge', 'age'); - store.verification = verification; - - renderComponent(store); - - expect(screen.getByRole('heading')).toHaveTextContent('Enter your birth date'); - }); - - it('successfully renders the Email step', () => { - const verification = store.verification.set('currentChallenge', 'email'); - store.verification = verification; - - renderComponent(store); - - expect(screen.getByRole('heading')).toHaveTextContent('Enter your email address'); - }); - - it('successfully renders the SMS step', () => { - const verification = store.verification.set('currentChallenge', 'sms'); - store.verification = verification; - - renderComponent(store); - - expect(screen.getByRole('heading')).toHaveTextContent('Enter your phone number'); - }); - }); -}); diff --git a/src/features/verification/__tests__/registration.test.tsx b/src/features/verification/__tests__/registration.test.tsx deleted file mode 100644 index 8dfe3083f..000000000 --- a/src/features/verification/__tests__/registration.test.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React from 'react'; - -import { __stub } from 'soapbox/api'; - -import { fireEvent, render, screen, waitFor } from '../../../jest/test-helpers'; -import Registration from '../registration'; - -describe('', () => { - it('renders', () => { - render(); - - expect(screen.getByRole('heading')).toHaveTextContent(/register your account/i); - }); - - describe('with valid data', () => { - beforeEach(() => { - __stub(mock => { - mock.onPost('/api/v1/pepe/accounts').reply(200, {}); - mock.onPost('/api/v1/apps').reply(200, {}); - mock.onPost('/oauth/token').reply(200, {}); - mock.onGet('/api/v1/accounts/verify_credentials').reply(200, { id: '123' }); - mock.onGet('/api/v1/instance').reply(200, {}); - }); - }); - - it('handles successful submission', async() => { - render(); - - await waitFor(() => { - fireEvent.submit(screen.getByTestId('button'), { preventDefault: () => {} }); - }); - - await waitFor(() => { - expect(screen.getByTestId('toast')).toHaveTextContent(/welcome to/i); - }); - - expect(screen.queryAllByRole('heading')).toHaveLength(0); - }); - }); - - describe('with invalid data', () => { - it('handles 422 errors', async() => { - __stub(mock => { - mock.onPost('/api/v1/pepe/accounts').reply( - 422, { - error: 'user_taken', - }, - ); - }); - - render(); - - await waitFor(() => { - fireEvent.submit(screen.getByTestId('button'), { preventDefault: () => {} }); - }); - - await waitFor(() => { - expect(screen.getByTestId('toast')).toHaveTextContent(/this username has already been taken/i); - }); - }); - - it('handles 422 errors with messages', async() => { - __stub(mock => { - mock.onPost('/api/v1/pepe/accounts').reply( - 422, { - error: 'user_vip', - message: 'This username is unavailable.', - }, - ); - }); - - render(); - - await waitFor(() => { - fireEvent.submit(screen.getByTestId('button'), { preventDefault: () => {} }); - }); - - await waitFor(() => { - expect(screen.getByTestId('toast')).toHaveTextContent(/this username is unavailable/i); - }); - - }); - - it('handles generic errors', async() => { - __stub(mock => { - mock.onPost('/api/v1/pepe/accounts').reply(500, {}); - }); - - render(); - - await waitFor(() => { - fireEvent.submit(screen.getByTestId('button'), { preventDefault: () => {} }); - }); - - await waitFor(() => { - expect(screen.getByTestId('toast')).toHaveTextContent(/failed to register your account/i); - }); - }); - }); - - describe('validations', () => { - it('should undisable button with valid password', async() => { - render(); - - expect(screen.getByTestId('button')).toBeDisabled(); - fireEvent.change(screen.getByTestId('password-input'), { target: { value: 'Password' } }); - expect(screen.getByTestId('button')).not.toBeDisabled(); - }); - - it('should disable button with invalid password', async() => { - render(); - - fireEvent.change(screen.getByTestId('password-input'), { target: { value: 'Passwor' } }); - expect(screen.getByTestId('button')).toBeDisabled(); - }); - }); -}); diff --git a/src/features/verification/components/password-indicator.tsx b/src/features/verification/components/password-indicator.tsx deleted file mode 100644 index 7b804d3d6..000000000 --- a/src/features/verification/components/password-indicator.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React, { useEffect, useMemo } from 'react'; -import { defineMessages, useIntl } from 'react-intl'; - -import { Stack } from 'soapbox/components/ui'; -import ValidationCheckmark from 'soapbox/components/validation-checkmark'; - -const messages = defineMessages({ - minimumCharacters: { - id: 'registration.validation.minimum_characters', - defaultMessage: '8 characters', - }, - capitalLetter: { - id: 'registration.validation.capital_letter', - defaultMessage: '1 capital letter', - }, - lowercaseLetter: { - id: 'registration.validation.lowercase_letter', - defaultMessage: '1 lowercase letter', - }, -}); - -const hasUppercaseCharacter = (string: string) => { - for (let i = 0; i < string.length; i++) { - if (string.charAt(i) === string.charAt(i).toUpperCase() && string.charAt(i).match(/[a-z]/i)) { - return true; - } - } - return false; -}; - -const hasLowercaseCharacter = (string: string) => { - return string.toUpperCase() !== string; -}; - -interface IPasswordIndicator { - onChange(isValid: boolean): void - password: string -} - -const PasswordIndicator = ({ onChange, password }: IPasswordIndicator) => { - const intl = useIntl(); - - const meetsLengthRequirements = useMemo(() => password.length >= 8, [password]); - const meetsCapitalLetterRequirements = useMemo(() => hasUppercaseCharacter(password), [password]); - const meetsLowercaseLetterRequirements = useMemo(() => hasLowercaseCharacter(password), [password]); - const hasValidPassword = meetsLengthRequirements && meetsCapitalLetterRequirements && meetsLowercaseLetterRequirements; - - useEffect(() => { - onChange(hasValidPassword); - }, [hasValidPassword]); - - return ( - - - - - - - - ); -}; - -export default PasswordIndicator; diff --git a/src/features/verification/email-passthru.tsx b/src/features/verification/email-passthru.tsx deleted file mode 100644 index 86152f12d..000000000 --- a/src/features/verification/email-passthru.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React from 'react'; -import { defineMessages, useIntl } from 'react-intl'; -import { useHistory, useParams } from 'react-router-dom'; - -import { confirmEmailVerification } from 'soapbox/actions/verification'; -import { Icon, Spinner, Stack, Text } from 'soapbox/components/ui'; -import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import toast from 'soapbox/toast'; - -import { ChallengeTypes } from './index'; - -import type { AxiosError } from 'axios'; - -const Statuses = { - IDLE: 'IDLE', - SUCCESS: 'SUCCESS', - GENERIC_FAIL: 'GENERIC_FAIL', - TOKEN_NOT_FOUND: 'TOKEN_NOT_FOUND', - TOKEN_EXPIRED: 'TOKEN_EXPIRED', -}; - -const messages = defineMessages({ - emailConfirmedHeading: { id: 'email_passthru.confirmed.heading', defaultMessage: 'Email Confirmed!' }, - emailConfirmedBody: { id: 'email_passthru.confirmed.body', defaultMessage: 'Close this tab and continue the registration process on the {bold} from which you sent this email confirmation.' }, - genericFailHeading: { id: 'email_passthru.generic_fail.heading', defaultMessage: 'Something Went Wrong' }, - genericFailBody: { id: 'email_passthru.generic_fail.body', defaultMessage: 'Please request a new email confirmation.' }, - tokenNotFoundHeading: { id: 'email_passthru.token_not_found.heading', defaultMessage: 'Invalid Token' }, - tokenNotFoundBody: { id: 'email_passthru.token_not_found.body', defaultMessage: 'Your email token was not found. Please request a new email confirmation from the {bold} from which you sent this email confirmation.' }, - tokenExpiredHeading: { id: 'email_passthru.token_expired.heading', defaultMessage: 'Token Expired' }, - tokenExpiredBody: { id: 'email_passthru.token_expired.body', defaultMessage: 'Your email token has expired. Please request a new email confirmation from the {bold} from which you sent this email confirmation.' }, - emailConfirmed: { id: 'email_passthru.success', defaultMessage: 'Your email has been verified!' }, - genericFail: { id: 'email_passthru.fail.generic', defaultMessage: 'Unable to confirm your email' }, - tokenExpired: { id: 'email_passthru.fail.expired', defaultMessage: 'Your email token has expired' }, - tokenNotFound: { id: 'email_passthru.fail.not_found', defaultMessage: 'Your email token is invalid.' }, - invalidToken: { id: 'email_passthru.fail.invalid_token', defaultMessage: 'Your token is invalid' }, -}); - -const Success = () => { - const intl = useIntl(); - const history = useHistory(); - const currentChallenge = useAppSelector((state) => state.verification.currentChallenge as ChallengeTypes); - - React.useEffect(() => { - // Bypass the user straight to the next step. - if (currentChallenge === ChallengeTypes.SMS) { - history.push('/verify'); - } - }, [currentChallenge]); - - return ( - - - - {intl.formatMessage(messages.emailConfirmedHeading)} - - - {intl.formatMessage(messages.emailConfirmedBody, { bold: same device })} - - - ); -}; - -const GenericFail = () => { - const intl = useIntl(); - - return ( - - - - {intl.formatMessage(messages.genericFailHeading)} - - - {intl.formatMessage(messages.genericFailBody)} - - - ); -}; - -const TokenNotFound = () => { - const intl = useIntl(); - - return ( - - - - {intl.formatMessage(messages.tokenNotFoundHeading)} - - - {intl.formatMessage(messages.tokenNotFoundBody, { bold: same device })} - - - - ); -}; - -const TokenExpired = () => { - const intl = useIntl(); - - return ( - - - - {intl.formatMessage(messages.tokenExpiredHeading)} - - - {intl.formatMessage(messages.tokenExpiredBody, { bold: same device })} - - - ); -}; - -const EmailPassThru = () => { - const { token } = useParams<{ token: string }>(); - - const dispatch = useAppDispatch(); - const intl = useIntl(); - - const [status, setStatus] = React.useState(Statuses.IDLE); - - React.useEffect(() => { - if (token) { - dispatch(confirmEmailVerification(token)) - .then(() => { - setStatus(Statuses.SUCCESS); - toast.success(intl.formatMessage(messages.emailConfirmed)); - }) - .catch((error: AxiosError) => { - const errorKey = error?.response?.data?.error; - let message = intl.formatMessage(messages.genericFail); - - if (errorKey) { - switch (errorKey) { - case 'token_expired': - message = intl.formatMessage(messages.tokenExpired); - setStatus(Statuses.TOKEN_EXPIRED); - break; - case 'token_not_found': - message = intl.formatMessage(messages.tokenNotFound); - message = intl.formatMessage(messages.invalidToken); - setStatus(Statuses.TOKEN_NOT_FOUND); - break; - default: - setStatus(Statuses.GENERIC_FAIL); - break; - } - } - - toast.error(message); - }); - } - }, [token]); - - switch (status) { - case Statuses.SUCCESS: - return ; - case Statuses.TOKEN_EXPIRED: - return ; - case Statuses.TOKEN_NOT_FOUND: - return ; - case Statuses.GENERIC_FAIL: - return ; - default: - return ; - } -}; - -export default EmailPassThru; diff --git a/src/features/verification/index.tsx b/src/features/verification/index.tsx deleted file mode 100644 index 88c9dc435..000000000 --- a/src/features/verification/index.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React from 'react'; -import { Redirect } from 'react-router-dom'; - -import { fetchVerificationConfig } from 'soapbox/actions/verification'; -import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; - -import Registration from './registration'; -import AgeVerification from './steps/age-verification'; -import EmailVerification from './steps/email-verification'; -import SmsVerification from './steps/sms-verification'; - -export enum ChallengeTypes { - EMAIL = 'email', - SMS = 'sms', - AGE = 'age', -} - -const verificationSteps = { - email: EmailVerification, - sms: SmsVerification, - age: AgeVerification, -}; - -const Verification = () => { - const dispatch = useAppDispatch(); - - const isInstanceReady = useAppSelector((state) => state.verification.instance.get('isReady') === true); - const isRegistrationOpen = useAppSelector(state => state.verification.instance.get('registrations') === true); - const currentChallenge = useAppSelector((state) => state.verification.currentChallenge as ChallengeTypes); - const isVerificationComplete = useAppSelector((state) => state.verification.isComplete); - const StepToRender = verificationSteps[currentChallenge]; - - React.useEffect(() => { - dispatch(fetchVerificationConfig()); - }, []); - - if (isInstanceReady && !isRegistrationOpen) { - return ; - } - - if (isVerificationComplete) { - return ( - - ); - } - - if (!currentChallenge) { - return null; - } - - return ( - - ); -}; - -export default Verification; diff --git a/src/features/verification/registration.tsx b/src/features/verification/registration.tsx deleted file mode 100644 index bee939ec4..000000000 --- a/src/features/verification/registration.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import React from 'react'; -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { Redirect } from 'react-router-dom'; - -import { logIn, verifyCredentials } from 'soapbox/actions/auth'; -import { fetchInstance } from 'soapbox/actions/instance'; -import { startOnboarding } from 'soapbox/actions/onboarding'; -import { createAccount, removeStoredVerification } from 'soapbox/actions/verification'; -import { Button, Form, FormGroup, Input, Text } from 'soapbox/components/ui'; -import { useAppDispatch, useAppSelector, useInstance, useSoapboxConfig } from 'soapbox/hooks'; -import toast from 'soapbox/toast'; -import { getRedirectUrl } from 'soapbox/utils/redirect'; - -import PasswordIndicator from './components/password-indicator'; - -import type { AxiosError } from 'axios'; - -const messages = defineMessages({ - success: { id: 'registrations.success', defaultMessage: 'Welcome to {siteTitle}!' }, - usernameLabel: { id: 'registrations.username.label', defaultMessage: 'Your username' }, - usernameHint: { id: 'registrations.username.hint', defaultMessage: 'May only contain A-Z, 0-9, and underscores' }, - usernameTaken: { id: 'registrations.unprocessable_entity', defaultMessage: 'This username has already been taken.' }, - passwordLabel: { id: 'registrations.password.label', defaultMessage: 'Password' }, - error: { id: 'registrations.error', defaultMessage: 'Failed to register your account.' }, -}); - -const initialState = { - username: '', - password: '', -}; - -const Registration = () => { - const dispatch = useAppDispatch(); - const intl = useIntl(); - const instance = useInstance(); - const soapboxConfig = useSoapboxConfig(); - const { links } = soapboxConfig; - - const isLoading = useAppSelector((state) => state.verification.isLoading as boolean); - - const [state, setState] = React.useState(initialState); - const [shouldRedirect, setShouldRedirect] = React.useState(false); - const [hasValidPassword, setHasValidPassword] = React.useState(false); - const { username, password } = state; - - const handleSubmit: React.FormEventHandler = React.useCallback((event) => { - event.preventDefault(); - - dispatch(createAccount(username, password)) - .then(() => dispatch(logIn(username, password))) - .then(({ access_token }: any) => dispatch(verifyCredentials(access_token))) - .then(() => dispatch(fetchInstance())) - .then(() => { - setShouldRedirect(true); - removeStoredVerification(); - dispatch(startOnboarding()); - toast.success( - intl.formatMessage(messages.success, { siteTitle: instance.title }), - ); - }) - .catch((errorResponse: AxiosError<{ error: string, message: string }>) => { - const error = errorResponse.response?.data?.error; - - if (error) { - toast.error(errorResponse.response?.data?.message || intl.formatMessage(messages.usernameTaken)); - } else { - toast.error(intl.formatMessage(messages.error)); - } - }); - }, [username, password]); - - const handleInputChange: React.ChangeEventHandler = React.useCallback((event) => { - event.persist(); - - setState((prevState) => ({ ...prevState, [event.target.name]: event.target.value })); - }, []); - - if (shouldRedirect) { - const redirectUri = getRedirectUrl(); - return ; - } - - return ( -
-
-

- -

-
- -
-
- - - - - - - - - - -
- - - {(links.get('termsOfService') && links.get('privacyPolicy')) ? ( - - - - - ), - privacy: ( - - - - ), - }} - /> - - ) : null} -
-
-
-
- ); -}; - -export default Registration; diff --git a/src/features/verification/steps/__tests__/age-verification.test.tsx b/src/features/verification/steps/__tests__/age-verification.test.tsx deleted file mode 100644 index f310a0741..000000000 --- a/src/features/verification/steps/__tests__/age-verification.test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import userEvent from '@testing-library/user-event'; -import { Map as ImmutableMap } from 'immutable'; -import React from 'react'; - -import { __stub } from 'soapbox/api'; -import { fireEvent, render, screen } from 'soapbox/jest/test-helpers'; - -import AgeVerification from '../age-verification'; - -describe('', () => { - let store: any; - - beforeEach(() => { - store = { - verification: ImmutableMap({ - ageMinimum: 13, - }), - }; - - __stub(mock => { - mock.onPost('/api/v1/pepe/verify_age/confirm') - .reply(200, {}); - }); - }); - - it('successfully renders the Birthday step', async() => { - render( - , - {}, - store, - ); - expect(screen.getByRole('heading')).toHaveTextContent('Enter your birth date'); - }); - - it('selects a date', async() => { - render( - , - {}, - store, - ); - - await userEvent.selectOptions( - screen.getByTestId('datepicker-year'), - screen.getByRole('option', { name: '2020' }), - ); - - fireEvent.submit( - screen.getByRole('button'), { - preventDefault: () => {}, - }, - ); - }); -}); diff --git a/src/features/verification/steps/__tests__/email-verification.test.tsx b/src/features/verification/steps/__tests__/email-verification.test.tsx deleted file mode 100644 index 38e7cf8a7..000000000 --- a/src/features/verification/steps/__tests__/email-verification.test.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import userEvent from '@testing-library/user-event'; -import React from 'react'; - -import { __stub } from 'soapbox/api'; -import { fireEvent, render, screen, waitFor } from 'soapbox/jest/test-helpers'; - -import EmailVerification from '../email-verification'; - -describe('', () => { - it('successfully renders the Email step', async() => { - render(); - expect(screen.getByRole('heading')).toHaveTextContent('Enter your email address'); - }); - - describe('with valid data', () => { - beforeEach(() => { - __stub(mock => { - mock.onPost('/api/v1/pepe/verify_email/request') - .reply(200, {}); - }); - }); - - it('successfully submits', async() => { - render(); - - await userEvent.type(screen.getByLabelText('E-mail address'), 'foo@bar.com{enter}'); - - await waitFor(() => { - fireEvent.submit( - screen.getByTestId('button'), { - preventDefault: () => {}, - }, - ); - }); - - expect(screen.getByTestId('button')).toHaveTextContent('Resend verification email'); - }); - }); - - describe('with invalid data', () => { - beforeEach(() => { - __stub(mock => { - mock.onPost('/api/v1/pepe/verify_email/request') - .reply(422, { - error: 'email_taken', - }); - }); - }); - - it('renders errors', async() => { - render(); - - await userEvent.type(screen.getByLabelText('E-mail address'), 'foo@bar.com{enter}'); - - await waitFor(() => { - fireEvent.submit( - screen.getByTestId('button'), { - preventDefault: () => {}, - }, - ); - }); - - await waitFor(() => { - expect(screen.getByTestId('form-group-error')).toHaveTextContent('is taken'); - }); - }); - }); -}); diff --git a/src/features/verification/steps/__tests__/sms-verification.test.tsx b/src/features/verification/steps/__tests__/sms-verification.test.tsx deleted file mode 100644 index d837f46b7..000000000 --- a/src/features/verification/steps/__tests__/sms-verification.test.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import { act } from 'react-dom/test-utils'; -import { toast } from 'react-hot-toast'; - -import { __stub } from 'soapbox/api'; -import { fireEvent, render, screen, waitFor } from 'soapbox/jest/test-helpers'; - -import SmsVerification from '../sms-verification'; - -describe('', () => { - it('successfully renders the SMS step', async() => { - render(); - expect(screen.getByRole('heading')).toHaveTextContent('Enter your phone number'); - }); - - describe('with valid data', () => { - beforeEach(() => { - __stub(mock => { - mock.onPost('/api/v1/pepe/verify_sms/request').reply(200, {}); - }); - }); - - it('successfully submits', async() => { - __stub(mock => { - mock.onPost('/api/v1/pepe/verify_sms/confirm').reply(200, {}); - }); - - render(); - - await userEvent.type(screen.getByLabelText('Phone number'), '+1 (555) 555-5555'); - await waitFor(() => { - fireEvent.submit( - screen.getByRole('button', { name: 'Next' }), { - preventDefault: () => {}, - }, - ); - }); - - await waitFor(() => { - expect(screen.getByRole('heading')).toHaveTextContent('Verification code'); - expect(screen.getByTestId('toast')).toHaveTextContent('A verification code has been sent to your phone number.'); - }); - - act(() => { - toast.remove(); - }); - - await userEvent.type(screen.getByLabelText('Please enter verification code. Digit 1'), '1'); - await userEvent.type(screen.getByLabelText('Digit 2'), '2'); - await userEvent.type(screen.getByLabelText('Digit 3'), '3'); - await userEvent.type(screen.getByLabelText('Digit 4'), '4'); - await userEvent.type(screen.getByLabelText('Digit 5'), '5'); - await userEvent.type(screen.getByLabelText('Digit 6'), '6'); - }); - - it('handle expired tokens', async() => { - __stub(mock => { - mock.onPost('/api/v1/pepe/verify_sms/confirm').reply(422, {}); - }); - - render(); - - await userEvent.type(screen.getByLabelText('Phone number'), '+1 (555) 555-5555'); - await waitFor(() => { - fireEvent.submit( - screen.getByRole('button', { name: 'Next' }), { - preventDefault: () => {}, - }, - ); - }); - - await waitFor(() => { - expect(screen.getByRole('heading')).toHaveTextContent('Verification code'); - expect(screen.getByTestId('toast')).toHaveTextContent('A verification code has been sent to your phone number.'); - }); - - act(() => { - toast.remove(); - }); - - await userEvent.type(screen.getByLabelText('Please enter verification code. Digit 1'), '1'); - await userEvent.type(screen.getByLabelText('Digit 2'), '2'); - await userEvent.type(screen.getByLabelText('Digit 3'), '3'); - await userEvent.type(screen.getByLabelText('Digit 4'), '4'); - await userEvent.type(screen.getByLabelText('Digit 5'), '5'); - await userEvent.type(screen.getByLabelText('Digit 6'), '6'); - - await waitFor(() => { - expect(screen.getByTestId('toast')).toHaveTextContent('Your SMS token has expired.'); - }); - }); - }); - - describe('with invalid data', () => { - beforeEach(() => { - __stub(mock => { - mock.onPost('/api/v1/pepe/verify_sms/request') - .reply(422, {}); - }); - }); - - it('renders errors', async() => { - render(); - - await userEvent.type(screen.getByLabelText('Phone number'), '+1 (555) 555-5555'); - await waitFor(() => { - fireEvent.submit( - screen.getByRole('button', { name: 'Next' }), { - preventDefault: () => {}, - }, - ); - }); - - await waitFor(() => { - expect(screen.getByTestId('toast')).toHaveTextContent('Failed to send SMS message to your phone number.'); - }); - }); - }); -}); diff --git a/src/features/verification/steps/age-verification.tsx b/src/features/verification/steps/age-verification.tsx deleted file mode 100644 index 5129f5464..000000000 --- a/src/features/verification/steps/age-verification.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React from 'react'; -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; - -import { verifyAge } from 'soapbox/actions/verification'; -import { Button, Datepicker, Form, Text } from 'soapbox/components/ui'; -import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks'; -import toast from 'soapbox/toast'; - -const messages = defineMessages({ - fail: { - id: 'age_verification.fail', - defaultMessage: 'You must be {ageMinimum, plural, one {# year} other {# years}} old or older.', - }, -}); - -function meetsAgeMinimum(birthday: Date, ageMinimum: number) { - const month = birthday.getUTCMonth(); - const day = birthday.getUTCDate(); - const year = birthday.getUTCFullYear(); - - return new Date(year + ageMinimum, month, day) <= new Date(); -} - -const AgeVerification = () => { - const intl = useIntl(); - const dispatch = useAppDispatch(); - const instance = useInstance(); - - const isLoading = useAppSelector((state) => state.verification.isLoading) as boolean; - const ageMinimum = useAppSelector((state) => state.verification.ageMinimum) as any; - - const [date, setDate] = React.useState(); - const isValid = typeof date === 'object'; - - const onChange = React.useCallback((date: Date) => setDate(date), []); - - const handleSubmit: React.FormEventHandler = React.useCallback((event) => { - event.preventDefault(); - - const birthday = new Date(date!); - - if (meetsAgeMinimum(birthday, ageMinimum)) { - dispatch(verifyAge(birthday)); - } else { - toast.error(intl.formatMessage(messages.fail, { ageMinimum })); - } - }, [date, ageMinimum]); - - return ( -
-
-

- -

-
- -
-
- - - - - - - -
- -
- -
-
- ); -}; - -export default AgeVerification; diff --git a/src/features/verification/steps/email-verification.tsx b/src/features/verification/steps/email-verification.tsx deleted file mode 100644 index a7c15d686..000000000 --- a/src/features/verification/steps/email-verification.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { AxiosError } from 'axios'; -import React from 'react'; -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; - -import { checkEmailVerification, postEmailVerification, requestEmailVerification } from 'soapbox/actions/verification'; -import Icon from 'soapbox/components/icon'; -import { Button, Form, FormGroup, Input, Text } from 'soapbox/components/ui'; -import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import toast from 'soapbox/toast'; - -const messages = defineMessages({ - verificationSuccess: { id: 'email_verification.success', defaultMessage: 'Verification email sent successfully.' }, - verificationFail: { id: 'email_verification.fail', defaultMessage: 'Failed to request email verification.' }, - verificationFailTakenAlert: { id: 'email_verifilcation.exists', defaultMessage: 'This email has already been taken.' }, - verificationFailTaken: { id: 'email_verification.taken', defaultMessage: 'is taken' }, - emailLabel: { id: 'email_verification.email.label', defaultMessage: 'E-mail address' }, -}); - -const Statuses = { - IDLE: 'IDLE', - REQUESTED: 'REQUESTED', - FAIL: 'FAIL', -}; - -const EMAIL_REGEX = /^[^@\s]+@[^@\s]+$/; - -interface IEmailSent { - handleSubmit: React.FormEventHandler -} - -const EmailSent: React.FC = ({ handleSubmit }) => { - const dispatch = useAppDispatch(); - - const checkEmailConfirmation = () => { - dispatch(checkEmailVerification()) - .then(() => dispatch(postEmailVerification())) - .catch(() => null); - }; - - React.useEffect(() => { - const intervalId = setInterval(() => checkEmailConfirmation(), 2500); - - return () => clearInterval(intervalId); - }, []); - - return ( -
- - -
- We sent you an email - Click on the link in the email to validate your email. -
- - -
- ); -}; - -const EmailVerification = () => { - const intl = useIntl(); - const dispatch = useAppDispatch(); - - const isLoading = useAppSelector((state) => state.verification.isLoading) as boolean; - - const [email, setEmail] = React.useState(''); - const [status, setStatus] = React.useState(Statuses.IDLE); - const [errors, setErrors] = React.useState>([]); - - const isValid = email.length > 0 && EMAIL_REGEX.test(email); - - const onChange: React.ChangeEventHandler = React.useCallback((event) => { - setEmail(event.target.value); - }, []); - - const handleSubmit: React.FormEventHandler = React.useCallback((event) => { - event.preventDefault(); - setErrors([]); - - submitEmailForVerification(); - }, [email]); - - const submitEmailForVerification = () => { - return dispatch(requestEmailVerification((email))) - .then(() => { - setStatus(Statuses.REQUESTED); - - toast.success(intl.formatMessage(messages.verificationSuccess)); - }) - .catch((error: AxiosError) => { - const errorMessage = (error.response?.data as any)?.error; - const isEmailTaken = errorMessage === 'email_taken'; - let message = intl.formatMessage(messages.verificationFail); - - if (isEmailTaken) { - message = intl.formatMessage(messages.verificationFailTakenAlert); - } else if (errorMessage) { - message = errorMessage; - } - - if (isEmailTaken) { - setErrors([intl.formatMessage(messages.verificationFailTaken)]); - } - - toast.error(message); - setStatus(Statuses.FAIL); - }); - }; - - if (status === Statuses.REQUESTED) { - return ; - } - - return ( -
-
-

- -

-
- -
-
- - - - -
- -
-
-
-
- ); -}; - -export default EmailVerification; diff --git a/src/features/verification/steps/sms-verification.tsx b/src/features/verification/steps/sms-verification.tsx deleted file mode 100644 index 0266b1581..000000000 --- a/src/features/verification/steps/sms-verification.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { AxiosError } from 'axios'; -import React from 'react'; -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import OtpInput from 'react-otp-input'; - -import { confirmPhoneVerification, requestPhoneVerification } from 'soapbox/actions/verification'; -import { Button, Form, FormGroup, PhoneInput, Text } from 'soapbox/components/ui'; -import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import toast from 'soapbox/toast'; - -const messages = defineMessages({ - verificationInvalid: { id: 'sms_verification.invalid', defaultMessage: 'Please enter a valid phone number.' }, - verificationSuccess: { id: 'sms_verification.success', defaultMessage: 'A verification code has been sent to your phone number.' }, - verificationFail: { id: 'sms_verification.fail', defaultMessage: 'Failed to send SMS message to your phone number.' }, - verificationExpired: { id: 'sms_verification.expired', defaultMessage: 'Your SMS token has expired.' }, - phoneLabel: { id: 'sms_verification.phone.label', defaultMessage: 'Phone number' }, -}); - -const Statuses = { - IDLE: 'IDLE', - REQUESTED: 'REQUESTED', - FAIL: 'FAIL', -}; - -const SmsVerification = () => { - const intl = useIntl(); - const dispatch = useAppDispatch(); - - const isLoading = useAppSelector((state) => state.verification.isLoading) as boolean; - - const [phone, setPhone] = React.useState(); - const [status, setStatus] = React.useState(Statuses.IDLE); - const [verificationCode, setVerificationCode] = React.useState(''); - const [requestedAnother, setAlreadyRequestedAnother] = React.useState(false); - - const isValid = !!phone; - - const onChange = React.useCallback((phone?: string) => { - setPhone(phone); - }, []); - - const handleSubmit: React.FormEventHandler = React.useCallback((event) => { - event.preventDefault(); - - if (!isValid) { - setStatus(Statuses.IDLE); - toast.error(intl.formatMessage(messages.verificationInvalid)); - return; - } - - dispatch(requestPhoneVerification(phone!)).then(() => { - toast.success(intl.formatMessage(messages.verificationSuccess)); - setStatus(Statuses.REQUESTED); - }).catch((error: AxiosError) => { - const message = (error.response?.data as any)?.message || intl.formatMessage(messages.verificationFail); - - toast.error(message); - setStatus(Statuses.FAIL); - }); - }, [phone, isValid]); - - const resendVerificationCode: React.MouseEventHandler = React.useCallback((event) => { - setAlreadyRequestedAnother(true); - handleSubmit(event); - }, [isValid]); - - const submitVerification = () => { - // TODO: handle proper validation from Pepe -- expired vs invalid - dispatch(confirmPhoneVerification(verificationCode)) - .catch(() => { - toast.error(intl.formatMessage(messages.verificationExpired)); - }); - }; - - React.useEffect(() => { - if (verificationCode.length === 6) { - submitVerification(); - } - }, [verificationCode]); - - if (status === Statuses.REQUESTED) { - return ( -
-
-

- -

-
- -
- - - - - - -
- -
-
-
- ); - } - - return ( -
-
-

- -

-
- -
-
- - - - -
- -
-
-
-
- ); -}; - -export { SmsVerification as default }; diff --git a/src/features/verification/waitlist-page.tsx b/src/features/verification/waitlist-page.tsx deleted file mode 100644 index 431c76698..000000000 --- a/src/features/verification/waitlist-page.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React, { useEffect } from 'react'; -import { FormattedMessage } from 'react-intl'; -import { Link } from 'react-router-dom'; - -import { logOut } from 'soapbox/actions/auth'; -import { openModal } from 'soapbox/actions/modals'; -import LandingGradient from 'soapbox/components/landing-gradient'; -import SiteLogo from 'soapbox/components/site-logo'; -import { Button, Stack, Text } from 'soapbox/components/ui'; -import { useAppDispatch, useInstance, useOwnAccount } from 'soapbox/hooks'; - -const WaitlistPage = () => { - const dispatch = useAppDispatch(); - const instance = useInstance(); - - const { account: me } = useOwnAccount(); - const isSmsVerified = me?.source?.sms_verified ?? true; - - const onClickLogOut: React.MouseEventHandler = (event) => { - event.preventDefault(); - dispatch(logOut()); - }; - - const openVerifySmsModal = () => dispatch(openModal('VERIFY_SMS')); - - useEffect(() => { - if (!isSmsVerified) { - openVerifySmsModal(); - } - }, []); - - return ( -
- - -
-
-
- - - - -
- -
-
-
- -
-
- - Waitlisted - - - - - - -
- -
-
-
-
-
-
-
- ); -}; - -export default WaitlistPage; diff --git a/src/hooks/__tests__/useRegistrationStatus.test.ts b/src/hooks/__tests__/useRegistrationStatus.test.ts deleted file mode 100644 index 465ca3992..000000000 --- a/src/hooks/__tests__/useRegistrationStatus.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { storeClosed, storeOpen, storePepeClosed, storePepeOpen } from 'soapbox/jest/mock-stores'; -import { renderHook } from 'soapbox/jest/test-helpers'; - -import { useRegistrationStatus } from '../useRegistrationStatus'; - -describe('useRegistrationStatus()', () => { - test('Registrations open', () => { - const { result } = renderHook(useRegistrationStatus, undefined, storeOpen); - - expect(result.current).toMatchObject({ - isOpen: true, - pepeEnabled: false, - pepeOpen: false, - }); - }); - - test('Registrations closed', () => { - const { result } = renderHook(useRegistrationStatus, undefined, storeClosed); - - expect(result.current).toMatchObject({ - isOpen: false, - pepeEnabled: false, - pepeOpen: false, - }); - }); - - test('Registrations closed, Pepe enabled & open', () => { - const { result } = renderHook(useRegistrationStatus, undefined, storePepeOpen); - - expect(result.current).toMatchObject({ - isOpen: true, - pepeEnabled: true, - pepeOpen: true, - }); - }); - - test('Registrations closed, Pepe enabled & closed', () => { - const { result } = renderHook(useRegistrationStatus, undefined, storePepeClosed); - - expect(result.current).toMatchObject({ - isOpen: false, - pepeEnabled: true, - pepeOpen: false, - }); - }); -}); diff --git a/src/hooks/useRegistrationStatus.ts b/src/hooks/useRegistrationStatus.ts index 6ede86941..0f1242d1e 100644 --- a/src/hooks/useRegistrationStatus.ts +++ b/src/hooks/useRegistrationStatus.ts @@ -1,22 +1,12 @@ -import { useAppSelector } from './useAppSelector'; import { useFeatures } from './useFeatures'; import { useInstance } from './useInstance'; -import { useSoapboxConfig } from './useSoapboxConfig'; export const useRegistrationStatus = () => { const instance = useInstance(); const features = useFeatures(); - const soapboxConfig = useSoapboxConfig(); - - const pepeOpen = useAppSelector(state => state.verification.instance.get('registrations') === true); - const pepeEnabled = soapboxConfig.getIn(['extensions', 'pepe', 'enabled']) === true; return { /** Registrations are open, either through Pepe or traditional account creation. */ - isOpen: (features.accountCreation && instance.registrations) || (pepeEnabled && pepeOpen), - /** Whether Pepe is open. */ - pepeOpen, - /** Whether Pepe is enabled. */ - pepeEnabled, + isOpen: features.accountCreation && instance.registrations, }; }; \ No newline at end of file diff --git a/src/jest/mock-stores.tsx b/src/jest/mock-stores.tsx index e8969780b..0dc4bae79 100644 --- a/src/jest/mock-stores.tsx +++ b/src/jest/mock-stores.tsx @@ -1,5 +1,3 @@ -import { fromJS } from 'immutable'; - import alexJson from 'soapbox/__fixtures__/pleroma-account.json'; import { normalizeInstance } from 'soapbox/normalizers'; @@ -11,20 +9,6 @@ const storeOpen = { instance: normalizeInstance({ registrations: true }) }; /** Store with registrations closed. */ const storeClosed = { instance: normalizeInstance({ registrations: false }) }; -/** Store with registrations closed, and Pepe enabled & open. */ -const storePepeOpen = { - instance: normalizeInstance({ registrations: false }), - soapbox: fromJS({ extensions: { pepe: { enabled: true } } }), - verification: { instance: fromJS({ registrations: true }) }, -}; - -/** Store with registrations closed, and Pepe enabled & closed. */ -const storePepeClosed = { - instance: normalizeInstance({ registrations: false }), - soapbox: fromJS({ extensions: { pepe: { enabled: true } } }), - verification: { instance: fromJS({ registrations: false }) }, -}; - /** Store with a logged-in user. */ const storeLoggedIn = { me: alexJson.id, @@ -36,7 +20,5 @@ const storeLoggedIn = { export { storeOpen, storeClosed, - storePepeOpen, - storePepeClosed, storeLoggedIn, }; \ No newline at end of file diff --git a/src/reducers/index.ts b/src/reducers/index.ts index 0c567df5b..a30369561 100644 --- a/src/reducers/index.ts +++ b/src/reducers/index.ts @@ -64,7 +64,6 @@ import timelines from './timelines'; import trending_statuses from './trending-statuses'; import trends from './trends'; import user_lists from './user-lists'; -import verification from './verification'; const reducers = { accounts_meta, @@ -127,7 +126,6 @@ const reducers = { trending_statuses, trends, user_lists, - verification, }; // Build a default state from all reducers: it has the key and `undefined` diff --git a/src/reducers/verification.ts b/src/reducers/verification.ts deleted file mode 100644 index 94abdeeb1..000000000 --- a/src/reducers/verification.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Map as ImmutableMap, Record as ImmutableRecord, fromJS } from 'immutable'; - -import { - PEPE_FETCH_INSTANCE_SUCCESS, - FETCH_CHALLENGES_SUCCESS, - FETCH_TOKEN_SUCCESS, - SET_CHALLENGES_COMPLETE, - SET_LOADING, - SET_NEXT_CHALLENGE, - Challenge, -} from '../actions/verification'; - -import type { AnyAction } from 'redux'; - -const ReducerRecord = ImmutableRecord({ - ageMinimum: null as string | null, - currentChallenge: null as Challenge | null, - isLoading: false, - isComplete: false as boolean | null, - token: null as string | null, - instance: ImmutableMap(), -}); - -export default function verification(state = ReducerRecord(), action: AnyAction) { - switch (action.type) { - case PEPE_FETCH_INSTANCE_SUCCESS: - return state.set('instance', ImmutableMap(fromJS(action.instance))); - case FETCH_CHALLENGES_SUCCESS: - return state - .set('ageMinimum', action.ageMinimum) - .set('currentChallenge', action.currentChallenge) - .set('isLoading', false) - .set('isComplete', action.isComplete); - case FETCH_TOKEN_SUCCESS: - return state - .set('isLoading', false) - .set('token', action.value); - case SET_CHALLENGES_COMPLETE: - return state - .set('isLoading', false) - .set('isComplete', true); - case SET_NEXT_CHALLENGE: - return state - .set('currentChallenge', action.challenge) - .set('isLoading', false); - case SET_LOADING: - return state.set('isLoading', typeof action.value === 'boolean' ? action.value : true); - default: - return state; - } -} diff --git a/src/utils/features.ts b/src/utils/features.ts index 9fb04b394..dc1a049c1 100644 --- a/src/utils/features.ts +++ b/src/utils/features.ts @@ -734,14 +734,6 @@ const getInstanceFeatures = (instance: Instance) => { */ paginatedContext: v.software === TRUTHSOCIAL, - /** - * Require minimum password requirements. - * - 8 characters - * - 1 uppercase - * - 1 lowercase - */ - passwordRequirements: v.software === TRUTHSOCIAL, - /** * Displays a form to follow a user when logged out. * @see POST /main/ostatus diff --git a/yarn.lock b/yarn.lock index d9ca934f2..24dd23663 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5905,11 +5905,6 @@ li@^1.3.0: resolved "https://registry.yarnpkg.com/li/-/li-1.3.0.tgz#22c59bcaefaa9a8ef359cf759784e4bf106aea1b" integrity sha512-z34TU6GlMram52Tss5mt1m//ifRIpKH5Dqm7yUVOdHI+BQCs9qGPHFaCUTIzsWX7edN30aa2WrPwR7IO10FHaw== -libphonenumber-js@^1.10.8: - version "1.10.8" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.8.tgz#21925db0f16d4f1553dff2bbc62afdaeb03f21f0" - integrity sha512-MGgHrKRGE7sg7y0DikHybRDgTXcYv4HL+WwhDm5UAiChCNb5tcy5OEaU8XTTt5bDBwhZGCJNxoGMVBpZ4RfhIg== - lie@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" @@ -7330,11 +7325,6 @@ react-onclickoutside@^6.12.0: resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.12.1.tgz#92dddd28f55e483a1838c5c2930e051168c1e96b" integrity sha512-a5Q7CkWznBRUWPmocCvE8b6lEYw1s6+opp/60dCunhO+G6E4tDTO2Sd2jKE+leEnnrLAE2Wj5DlDHNqj5wPv1Q== -react-otp-input@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/react-otp-input/-/react-otp-input-2.4.0.tgz#0f0a3de1d8c8d564e2e4fbe5d6b7b56e29e3a6e6" - integrity sha512-AIgl7u4sS9BTNCxX1xlaS5fPWay/Zml8Ho5LszXZKXrH1C/TiFsTQGmtl13UecQYO3mSF3HUzG2rrDf0sjEFmg== - react-overlays@^0.9.0: version "0.9.3" resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-0.9.3.tgz#5bac8c1e9e7e057a125181dee2d784864dd62902" From 98cc8b9d6ff1f0536aeb69f6df47e21f4c5f5ad4 Mon Sep 17 00:00:00 2001 From: Soapbox Bot Date: Tue, 19 Sep 2023 02:08:46 +0000 Subject: [PATCH 37/62] fix(deps): update dependency react-inlinesvg to v4 --- package.json | 2 +- yarn.lock | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 9317e7408..2c1980a30 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,7 @@ "react-hot-toast": "^2.4.0", "react-hotkeys": "^1.1.4", "react-immutable-pure-component": "^2.2.2", - "react-inlinesvg": "^3.0.0", + "react-inlinesvg": "^4.0.0", "react-intl": "^5.0.0", "react-motion": "^0.5.2", "react-otp-input": "^2.4.0", diff --git a/yarn.lock b/yarn.lock index d9ca934f2..950e3d7c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4614,11 +4614,6 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -exenv@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" - integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= - exifr@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/exifr/-/exifr-7.1.3.tgz#f6218012c36dbb7d843222011b27f065fddbab6f" @@ -7262,12 +7257,11 @@ react-immutable-pure-component@^2.2.2: resolved "https://registry.yarnpkg.com/react-immutable-pure-component/-/react-immutable-pure-component-2.2.2.tgz#3014d3e20cd5a7a4db73b81f1f1464f4d351684b" integrity sha512-vkgoMJUDqHZfXXnjVlG3keCxSO/U6WeDQ5/Sl0GK2cH8TOxEzQ5jXqDXHEL/jqk6fsNxV05oH5kD7VNMUE2k+A== -react-inlinesvg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/react-inlinesvg/-/react-inlinesvg-3.0.0.tgz#801c6bac1535334586708cd346f4b1125dcfa8f9" - integrity sha512-zUt0DW3cKBk+vYZJJCzJqA9STRb+ZFmKLQFWurTvM4UR6vyHT8kHZSzyZZseX9BUNbTFJAfirtwpt97BWDJoSg== +react-inlinesvg@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/react-inlinesvg/-/react-inlinesvg-4.0.3.tgz#69aa4d9c01b037abb800bfa103cb5591c6f3fe76" + integrity sha512-qPSqksbgDc6uVX6w256XY6JmdkLpzA4RiajvHi8u2qszXrhjDl6JwhW8x3VMkO4BxL9ll+/IeKR9ZxgM8wLcKQ== dependencies: - exenv "^1.2.2" react-from-dom "^0.6.2" react-intl-translations-manager@^5.0.3: From 73b966361d86aab6c83bd31c0cfd337c43f95d30 Mon Sep 17 00:00:00 2001 From: Soapbox Bot Date: Tue, 19 Sep 2023 03:05:59 +0000 Subject: [PATCH 38/62] fix(deps): update dependency type-fest to v4 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9317e7408..e655c8658 100644 --- a/package.json +++ b/package.json @@ -163,7 +163,7 @@ "ts-node": "^10.9.1", "tslib": "^2.3.1", "twemoji": "https://github.com/twitter/twemoji#v14.0.2", - "type-fest": "^3.12.0", + "type-fest": "^4.0.0", "typescript": "^5.1.3", "util": "^0.12.4", "uuid": "^9.0.0", diff --git a/yarn.lock b/yarn.lock index d9ca934f2..22e364c14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8711,10 +8711,10 @@ type-fest@^1.0.1, type-fest@^1.2.1, type-fest@^1.2.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== -type-fest@^3.12.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.12.0.tgz#4ce26edc1ccc59fc171e495887ef391fe1f5280e" - integrity sha512-qj9wWsnFvVEMUDbESiilKeXeHL7FwwiFcogfhfyjmvT968RXSvnl23f1JOClTHYItsi7o501C/7qVllscUP3oA== +type-fest@^4.0.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.3.1.tgz#5cb58cdab5120f7ab0b40cfdc35073fb9adb651d" + integrity sha512-pphNW/msgOUSkJbH58x8sqpq8uQj6b0ZKGxEsLKMUnGorRcDjrUaLS+39+/ub41JNTwrrMyJcUB8+YZs3mbwqw== typed-array-buffer@^1.0.0: version "1.0.0" From 19fbaa9dff6e10772090fdbcef77e67e0b1a8c0e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 18 Sep 2023 22:16:46 -0500 Subject: [PATCH 39/62] Delete verification test --- src/reducers/__tests__/verification.test.ts | 177 -------------------- 1 file changed, 177 deletions(-) delete mode 100644 src/reducers/__tests__/verification.test.ts diff --git a/src/reducers/__tests__/verification.test.ts b/src/reducers/__tests__/verification.test.ts deleted file mode 100644 index f503de443..000000000 --- a/src/reducers/__tests__/verification.test.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { Map as ImmutableMap, Record as ImmutableRecord } from 'immutable'; - -import { - Challenge, - FETCH_CHALLENGES_SUCCESS, - FETCH_TOKEN_SUCCESS, - SET_CHALLENGES_COMPLETE, - SET_LOADING, - SET_NEXT_CHALLENGE, -} from 'soapbox/actions/verification'; - -import reducer from '../verification'; - -describe('verfication reducer', () => { - it('returns the initial state', () => { - expect(reducer(undefined, {} as any)).toMatchObject({ - ageMinimum: null, - currentChallenge: null, - isLoading: false, - isComplete: false, - token: null, - instance: ImmutableMap(), - }); - }); - - describe('FETCH_CHALLENGES_SUCCESS', () => { - it('sets the state', () => { - const state = ImmutableRecord({ - ageMinimum: null, - currentChallenge: null, - isLoading: true, - isComplete: null, - token: null, - instance: ImmutableMap(), - })(); - const action = { - type: FETCH_CHALLENGES_SUCCESS, - ageMinimum: 13, - currentChallenge: 'email', - isComplete: false, - }; - const expected = { - ageMinimum: 13, - currentChallenge: 'email', - isLoading: false, - isComplete: false, - token: null, - instance: ImmutableMap(), - }; - - expect(reducer(state, action)).toMatchObject(expected); - }); - }); - - describe('FETCH_TOKEN_SUCCESS', () => { - it('sets the state', () => { - const state = ImmutableRecord({ - ageMinimum: null, - currentChallenge: 'email' as Challenge, - isLoading: true, - isComplete: false, - token: null, - instance: ImmutableMap(), - })(); - const action = { type: FETCH_TOKEN_SUCCESS, value: '123' }; - const expected = { - ageMinimum: null, - currentChallenge: 'email', - isLoading: false, - isComplete: false, - token: '123', - instance: ImmutableMap(), - }; - - expect(reducer(state, action)).toMatchObject(expected); - }); - }); - - describe('SET_CHALLENGES_COMPLETE', () => { - it('sets the state', () => { - const state = ImmutableRecord({ - ageMinimum: null, - currentChallenge: null, - isLoading: true, - isComplete: false, - token: null, - instance: ImmutableMap(), - })(); - const action = { type: SET_CHALLENGES_COMPLETE }; - const expected = { - ageMinimum: null, - currentChallenge: null, - isLoading: false, - isComplete: true, - token: null, - instance: ImmutableMap(), - }; - - expect(reducer(state, action)).toMatchObject(expected); - }); - }); - - describe('SET_NEXT_CHALLENGE', () => { - it('sets the state', () => { - const state = ImmutableRecord({ - ageMinimum: null, - currentChallenge: null, - isLoading: true, - isComplete: false, - token: null, - instance: ImmutableMap(), - })(); - const action = { - type: SET_NEXT_CHALLENGE, - challenge: 'sms', - }; - const expected = { - ageMinimum: null, - currentChallenge: 'sms', - isLoading: false, - isComplete: false, - token: null, - instance: ImmutableMap(), - }; - - expect(reducer(state, action)).toMatchObject(expected); - }); - }); - - describe('SET_LOADING with no value', () => { - it('sets the state', () => { - const state = ImmutableRecord({ - ageMinimum: null, - currentChallenge: null, - isLoading: false, - isComplete: false, - token: null, - instance: ImmutableMap(), - })(); - const action = { type: SET_LOADING }; - const expected = { - ageMinimum: null, - currentChallenge: null, - isLoading: true, - isComplete: false, - token: null, - instance: ImmutableMap(), - }; - - expect(reducer(state, action)).toMatchObject(expected); - }); - }); - - describe('SET_LOADING with a value', () => { - it('sets the state', () => { - const state = ImmutableRecord({ - ageMinimum: null, - currentChallenge: null, - isLoading: true, - isComplete: false, - token: null, - instance: ImmutableMap(), - })(); - const action = { type: SET_LOADING, value: false }; - const expected = { - ageMinimum: null, - currentChallenge: null, - isLoading: false, - isComplete: false, - token: null, - instance: ImmutableMap(), - }; - - expect(reducer(state, action)).toMatchObject(expected); - }); - }); -}); From 757d8ba5451d944f30208b811a5f8e51386e859b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 19 Sep 2023 11:17:12 -0500 Subject: [PATCH 40/62] Remove Truth Social Ads --- package.json | 1 - src/components/status-list.tsx | 26 ---- src/features/ads/components/ad.tsx | 142 ------------------ src/features/ads/providers/index.ts | 42 ------ src/features/ads/providers/soapbox-config.ts | 14 -- src/features/ads/providers/truth.ts | 40 ----- .../__tests__/abovefold.test.ts | 18 --- .../__tests__/linear.test.ts | 19 --- src/features/timeline-insertion/abovefold.ts | 52 ------- src/features/timeline-insertion/index.ts | 11 -- src/features/timeline-insertion/linear.ts | 28 ---- src/features/timeline-insertion/types.ts | 15 -- src/normalizers/soapbox/soapbox-config.ts | 26 ---- src/queries/ads.ts | 42 ------ src/schemas/index.ts | 3 - src/schemas/soapbox/ad.ts | 14 -- src/utils/__tests__/ads.test.ts | 23 --- src/utils/ads.ts | 16 -- yarn.lock | 5 - 19 files changed, 537 deletions(-) delete mode 100644 src/features/ads/components/ad.tsx delete mode 100644 src/features/ads/providers/index.ts delete mode 100644 src/features/ads/providers/soapbox-config.ts delete mode 100644 src/features/ads/providers/truth.ts delete mode 100644 src/features/timeline-insertion/__tests__/abovefold.test.ts delete mode 100644 src/features/timeline-insertion/__tests__/linear.test.ts delete mode 100644 src/features/timeline-insertion/abovefold.ts delete mode 100644 src/features/timeline-insertion/index.ts delete mode 100644 src/features/timeline-insertion/linear.ts delete mode 100644 src/features/timeline-insertion/types.ts delete mode 100644 src/queries/ads.ts delete mode 100644 src/schemas/soapbox/ad.ts delete mode 100644 src/utils/__tests__/ads.test.ts delete mode 100644 src/utils/ads.ts diff --git a/package.json b/package.json index 005a39bbc..a3e0dd47d 100644 --- a/package.json +++ b/package.json @@ -153,7 +153,6 @@ "reselect": "^4.0.0", "resize-observer-polyfill": "^1.5.1", "sass": "^1.66.1", - "seedrandom": "^3.0.5", "semver": "^7.3.8", "stringz": "^2.0.0", "substring-trie": "^1.0.2", diff --git a/src/components/status-list.tsx b/src/components/status-list.tsx index 1d4165d22..a2507df6a 100644 --- a/src/components/status-list.tsx +++ b/src/components/status-list.tsx @@ -1,25 +1,19 @@ import clsx from 'clsx'; -import { Map as ImmutableMap } from 'immutable'; import debounce from 'lodash/debounce'; import React, { useRef, useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; -import { v4 as uuidv4 } from 'uuid'; import LoadGap from 'soapbox/components/load-gap'; import ScrollableList from 'soapbox/components/scrollable-list'; import StatusContainer from 'soapbox/containers/status-container'; -import Ad from 'soapbox/features/ads/components/ad'; import FeedSuggestions from 'soapbox/features/feed-suggestions/feed-suggestions'; import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status'; -import { ALGORITHMS } from 'soapbox/features/timeline-insertion'; import PendingStatus from 'soapbox/features/ui/components/pending-status'; import { useSoapboxConfig } from 'soapbox/hooks'; -import useAds from 'soapbox/queries/ads'; import type { OrderedSet as ImmutableOrderedSet } from 'immutable'; import type { VirtuosoHandle } from 'react-virtuoso'; import type { IScrollableList } from 'soapbox/components/scrollable-list'; -import type { Ad as AdEntity } from 'soapbox/types/soapbox'; interface IStatusList extends Omit { /** Unique key to preserve the scroll position when navigating back. */ @@ -64,14 +58,8 @@ const StatusList: React.FC = ({ showGroup = true, ...other }) => { - const { data: ads } = useAds(); const soapboxConfig = useSoapboxConfig(); - - const adsAlgorithm = String(soapboxConfig.extensions.getIn(['ads', 'algorithm', 0])); - const adsOpts = (soapboxConfig.extensions.getIn(['ads', 'algorithm', 1], ImmutableMap()) as ImmutableMap).toJS(); - const node = useRef(null); - const seed = useRef(uuidv4()); const getFeaturedStatusCount = () => { return featuredStatusIds?.size || 0; @@ -144,12 +132,6 @@ const StatusList: React.FC = ({ ); }; - const renderAd = (ad: AdEntity, index: number) => { - return ( - - ); - }; - const renderPendingStatus = (statusId: string) => { const idempotencyKey = statusId.replace(/^末pending-/, ''); @@ -192,14 +174,6 @@ const StatusList: React.FC = ({ const renderStatuses = (): React.ReactNode[] => { if (isLoading || statusIds.size > 0) { return statusIds.toList().reduce((acc, statusId, index) => { - if (showAds && ads) { - const ad = ALGORITHMS[adsAlgorithm]?.(ads, index, { ...adsOpts, seed: seed.current }); - - if (ad) { - acc.push(renderAd(ad, index)); - } - } - if (statusId === null) { const gap = renderLoadGap(index); // one does not simply push a null item to Virtuoso: https://github.com/petyosi/react-virtuoso/issues/206#issuecomment-747363793 diff --git a/src/features/ads/components/ad.tsx b/src/features/ads/components/ad.tsx deleted file mode 100644 index 4636795bc..000000000 --- a/src/features/ads/components/ad.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { useQuery, useQueryClient } from '@tanstack/react-query'; -import axios from 'axios'; -import React, { useState, useEffect, useRef } from 'react'; -import { FormattedMessage } from 'react-intl'; - -import { Avatar, Card, HStack, Icon, IconButton, Stack, Text } from 'soapbox/components/ui'; -import StatusCard from 'soapbox/features/status/components/card'; -import { useInstance } from 'soapbox/hooks'; -import { AdKeys } from 'soapbox/queries/ads'; - -import type { Ad as AdEntity } from 'soapbox/types/soapbox'; - -interface IAd { - ad: AdEntity -} - -/** Displays an ad in sponsored post format. */ -const Ad: React.FC = ({ ad }) => { - const queryClient = useQueryClient(); - const instance = useInstance(); - - const timer = useRef(undefined); - const infobox = useRef(null); - const [showInfo, setShowInfo] = useState(false); - - // Fetch the impression URL (if any) upon displaying the ad. - // Don't fetch it more than once. - useQuery(['ads', 'impression', ad.impression], async () => { - if (ad.impression) { - return await axios.get(ad.impression); - } - }, { cacheTime: Infinity, staleTime: Infinity }); - - /** Invalidate query cache for ads. */ - const bustCache = (): void => { - queryClient.invalidateQueries(AdKeys.ads); - }; - - /** Toggle the info box on click. */ - const handleInfoButtonClick: React.MouseEventHandler = () => { - setShowInfo(!showInfo); - }; - - /** Hide the info box when clicked outside. */ - const handleClickOutside = (event: MouseEvent) => { - if (event.target && infobox.current && !infobox.current.contains(event.target as any)) { - setShowInfo(false); - } - }; - - // Hide the info box when clicked outside. - // https://stackoverflow.com/a/42234988 - useEffect(() => { - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [infobox]); - - // Wait until the ad expires, then invalidate cache. - useEffect(() => { - if (ad.expires_at) { - const delta = new Date(ad.expires_at).getTime() - (new Date()).getTime(); - timer.current = setTimeout(bustCache, delta); - } - - return () => { - if (timer.current) { - clearTimeout(timer.current); - } - }; - }, [ad.expires_at]); - - return ( -
- - - - - - - - - {instance.title} - - - - - - - - - - - - - - - - - - - - { }} horizontal /> - - - - {showInfo && ( -
- - - - - - - - {ad.reason ? ( - ad.reason - ) : ( - - )} - - - -
- )} -
- ); -}; - -export default Ad; diff --git a/src/features/ads/providers/index.ts b/src/features/ads/providers/index.ts deleted file mode 100644 index cd7ba44c7..000000000 --- a/src/features/ads/providers/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { getSoapboxConfig } from 'soapbox/actions/soapbox'; - -import type { RootState } from 'soapbox/store'; -import type { Card } from 'soapbox/types/entities'; - -/** Map of available provider modules. */ -const PROVIDERS: Record Promise> = { - soapbox: async() => (await import('./soapbox-config')).default, - truth: async() => (await import('./truth')).default, -}; - -/** Ad server implementation. */ -interface AdProvider { - getAds(getState: () => RootState): Promise -} - -/** Entity representing an advertisement. */ -interface Ad { - /** Ad data in Card (OEmbed-ish) format. */ - card: Card - /** Impression URL to fetch when displaying the ad. */ - impression?: string - /** Time when the ad expires and should no longer be displayed. */ - expires_at?: string - /** Reason the ad is displayed. */ - reason?: string -} - -/** Gets the current provider based on config. */ -const getProvider = async(getState: () => RootState): Promise => { - const state = getState(); - const soapboxConfig = getSoapboxConfig(state); - const isEnabled = soapboxConfig.extensions.getIn(['ads', 'enabled'], false) === true; - const providerName = soapboxConfig.extensions.getIn(['ads', 'provider'], 'soapbox') as string; - - if (isEnabled && PROVIDERS[providerName]) { - return PROVIDERS[providerName](); - } -}; - -export { getProvider }; -export type { Ad, AdProvider }; diff --git a/src/features/ads/providers/soapbox-config.ts b/src/features/ads/providers/soapbox-config.ts deleted file mode 100644 index 21163729c..000000000 --- a/src/features/ads/providers/soapbox-config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { getSoapboxConfig } from 'soapbox/actions/soapbox'; - -import type { AdProvider } from '.'; - -/** Provides ads from Soapbox Config. */ -const SoapboxConfigAdProvider: AdProvider = { - getAds: async(getState) => { - const state = getState(); - const soapboxConfig = getSoapboxConfig(state); - return soapboxConfig.ads.toArray(); - }, -}; - -export default SoapboxConfigAdProvider; diff --git a/src/features/ads/providers/truth.ts b/src/features/ads/providers/truth.ts deleted file mode 100644 index 5582bd3cf..000000000 --- a/src/features/ads/providers/truth.ts +++ /dev/null @@ -1,40 +0,0 @@ -import axios from 'axios'; -import { z } from 'zod'; - -import { getSettings } from 'soapbox/actions/settings'; -import { cardSchema } from 'soapbox/schemas/card'; -import { filteredArray } from 'soapbox/schemas/utils'; - -import type { AdProvider } from '.'; - -/** TruthSocial ad API entity. */ -const truthAdSchema = z.object({ - impression: z.string(), - card: cardSchema, - expires_at: z.string(), - reason: z.string().catch(''), -}); - -/** Provides ads from the TruthSocial API. */ -const TruthAdProvider: AdProvider = { - getAds: async(getState) => { - const state = getState(); - const settings = getSettings(state); - - try { - const { data } = await axios.get('/api/v2/truth/ads?device=desktop', { - headers: { - 'Accept-Language': z.string().catch('*').parse(settings.get('locale')), - }, - }); - - return filteredArray(truthAdSchema).parse(data); - } catch (e) { - // do nothing - } - - return []; - }, -}; - -export default TruthAdProvider; diff --git a/src/features/timeline-insertion/__tests__/abovefold.test.ts b/src/features/timeline-insertion/__tests__/abovefold.test.ts deleted file mode 100644 index 81de8c1a4..000000000 --- a/src/features/timeline-insertion/__tests__/abovefold.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { abovefoldAlgorithm } from '../abovefold'; - -const DATA = Object.freeze(['a', 'b', 'c', 'd']); - -test('abovefoldAlgorithm', () => { - const result = Array(50).fill('').map((_, i) => { - return abovefoldAlgorithm(DATA, i, { seed: '!', range: [2, 6], pageSize: 20 }); - }); - - // console.log(result); - expect(result[0]).toBe(undefined); - expect(result[4]).toBe('a'); - expect(result[5]).toBe(undefined); - expect(result[24]).toBe('b'); - expect(result[30]).toBe(undefined); - expect(result[42]).toBe('c'); - expect(result[43]).toBe(undefined); -}); \ No newline at end of file diff --git a/src/features/timeline-insertion/__tests__/linear.test.ts b/src/features/timeline-insertion/__tests__/linear.test.ts deleted file mode 100644 index 09d484f12..000000000 --- a/src/features/timeline-insertion/__tests__/linear.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { linearAlgorithm } from '../linear'; - -const DATA = Object.freeze(['a', 'b', 'c', 'd']); - -test('linearAlgorithm', () => { - const result = Array(50).fill('').map((_, i) => { - return linearAlgorithm(DATA, i, { interval: 5 }); - }); - - // console.log(result); - expect(result[0]).toBe(undefined); - expect(result[4]).toBe('a'); - expect(result[8]).toBe(undefined); - expect(result[9]).toBe('b'); - expect(result[10]).toBe(undefined); - expect(result[14]).toBe('c'); - expect(result[15]).toBe(undefined); - expect(result[19]).toBe('d'); -}); \ No newline at end of file diff --git a/src/features/timeline-insertion/abovefold.ts b/src/features/timeline-insertion/abovefold.ts deleted file mode 100644 index 5298f029c..000000000 --- a/src/features/timeline-insertion/abovefold.ts +++ /dev/null @@ -1,52 +0,0 @@ -import seedrandom from 'seedrandom'; - -import type { PickAlgorithm } from './types'; - -type Opts = { - /** Randomization seed. */ - seed: string - /** - * Start/end index of the slot by which one item will be randomly picked per page. - * - * Eg. `[2, 6]` will cause one item to be picked among the third through seventh indexes. - * - * `end` must be larger than `start`. - */ - range: [start: number, end: number] - /** Number of items in the page. */ - pageSize: number -}; - -/** - * Algorithm to display items per-page. - * One item is randomly inserted into each page within the index range. - */ -const abovefoldAlgorithm: PickAlgorithm = (items, iteration, rawOpts) => { - const opts = normalizeOpts(rawOpts); - /** Current page of the index. */ - const page = Math.floor(iteration / opts.pageSize); - /** Current index within the page. */ - const pageIndex = (iteration % opts.pageSize); - /** RNG for the page. */ - const rng = seedrandom(`${opts.seed}-page-${page}`); - /** Index to insert the item. */ - const insertIndex = Math.floor(rng() * (opts.range[1] - opts.range[0])) + opts.range[0]; - - if (pageIndex === insertIndex) { - return items[page % items.length]; - } -}; - -const normalizeOpts = (opts: unknown): Opts => { - const { seed, range, pageSize } = (opts && typeof opts === 'object' ? opts : {}) as Record; - - return { - seed: typeof seed === 'string' ? seed : '', - range: Array.isArray(range) ? [Number(range[0]), Number(range[1])] : [2, 6], - pageSize: typeof pageSize === 'number' ? pageSize : 20, - }; -}; - -export { - abovefoldAlgorithm, -}; diff --git a/src/features/timeline-insertion/index.ts b/src/features/timeline-insertion/index.ts deleted file mode 100644 index f4e00ed29..000000000 --- a/src/features/timeline-insertion/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { abovefoldAlgorithm } from './abovefold'; -import { linearAlgorithm } from './linear'; - -import type { PickAlgorithm } from './types'; - -const ALGORITHMS: Record = { - 'linear': linearAlgorithm, - 'abovefold': abovefoldAlgorithm, -}; - -export { ALGORITHMS }; \ No newline at end of file diff --git a/src/features/timeline-insertion/linear.ts b/src/features/timeline-insertion/linear.ts deleted file mode 100644 index cae923944..000000000 --- a/src/features/timeline-insertion/linear.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { PickAlgorithm } from './types'; - -type Opts = { - /** Number of iterations until the next item is picked. */ - interval: number -}; - -/** Picks the next item every iteration. */ -const linearAlgorithm: PickAlgorithm = (items, iteration, rawOpts) => { - const opts = normalizeOpts(rawOpts); - const itemIndex = items ? Math.floor(iteration / opts.interval) % items.length : 0; - const item = items ? items[itemIndex] : undefined; - const showItem = (iteration + 1) % opts.interval === 0; - - return showItem ? item : undefined; -}; - -const normalizeOpts = (opts: unknown): Opts => { - const { interval } = (opts && typeof opts === 'object' ? opts : {}) as Record; - - return { - interval: typeof interval === 'number' ? interval : 20, - }; -}; - -export { - linearAlgorithm, -}; \ No newline at end of file diff --git a/src/features/timeline-insertion/types.ts b/src/features/timeline-insertion/types.ts deleted file mode 100644 index b874754d0..000000000 --- a/src/features/timeline-insertion/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Returns an item to insert at the index, or `undefined` if an item shouldn't be inserted. - */ -type PickAlgorithm = ( - /** Elligible candidates to pick. */ - items: readonly D[], - /** Current iteration by which an item may be chosen. */ - iteration: number, - /** Implementation-specific opts. */ - opts: Record -) => D | undefined; - -export { - PickAlgorithm, -}; \ No newline at end of file diff --git a/src/normalizers/soapbox/soapbox-config.ts b/src/normalizers/soapbox/soapbox-config.ts index d003f75e0..4f25992ef 100644 --- a/src/normalizers/soapbox/soapbox-config.ts +++ b/src/normalizers/soapbox/soapbox-config.ts @@ -6,8 +6,6 @@ import { } from 'immutable'; import trimStart from 'lodash/trimStart'; -import { adSchema } from 'soapbox/schemas'; -import { filteredArray } from 'soapbox/schemas/utils'; import { normalizeUsername } from 'soapbox/utils/input'; import { toTailwind } from 'soapbox/utils/tailwind'; import { generateAccent } from 'soapbox/utils/theme'; @@ -124,15 +122,6 @@ export const SoapboxConfigRecord = ImmutableRecord({ type SoapboxConfigMap = ImmutableMap; -const normalizeAds = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMap => { - if (soapboxConfig.has('ads')) { - const ads = filteredArray(adSchema).parse(ImmutableList(soapboxConfig.get('ads')).toJS()); - return soapboxConfig.set('ads', ads); - } else { - return soapboxConfig; - } -}; - const normalizeCryptoAddress = (address: unknown): CryptoAddress => { return CryptoAddressRecord(ImmutableMap(fromJS(address))).update('ticker', ticker => { return trimStart(ticker, '$').toLowerCase(); @@ -188,19 +177,6 @@ const normalizeFooterLinks = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMap return soapboxConfig.setIn(path, items); }; -/** Migrate legacy ads config. */ -const normalizeAdsAlgorithm = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMap => { - const interval = soapboxConfig.getIn(['extensions', 'ads', 'interval']); - const algorithm = soapboxConfig.getIn(['extensions', 'ads', 'algorithm']); - - if (typeof interval === 'number' && !algorithm) { - const result = fromJS(['linear', { interval }]); - return soapboxConfig.setIn(['extensions', 'ads', 'algorithm'], result); - } else { - return soapboxConfig; - } -}; - /** Single user mode is now managed by `redirectRootNoLogin`. */ const upgradeSingleUserMode = (soapboxConfig: SoapboxConfigMap): SoapboxConfigMap => { const singleUserMode = soapboxConfig.get('singleUserMode') as boolean | undefined; @@ -250,8 +226,6 @@ export const normalizeSoapboxConfig = (soapboxConfig: Record) => { normalizeFooterLinks(soapboxConfig); maybeAddMissingColors(soapboxConfig); normalizeCryptoAddresses(soapboxConfig); - normalizeAds(soapboxConfig); - normalizeAdsAlgorithm(soapboxConfig); upgradeSingleUserMode(soapboxConfig); normalizeRedirectRootNoLogin(soapboxConfig); }), diff --git a/src/queries/ads.ts b/src/queries/ads.ts deleted file mode 100644 index c45bf8b33..000000000 --- a/src/queries/ads.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; - -import { Ad, getProvider } from 'soapbox/features/ads/providers'; -import { useAppDispatch } from 'soapbox/hooks'; -import { adSchema } from 'soapbox/schemas'; -import { filteredArray } from 'soapbox/schemas/utils'; -import { isExpired } from 'soapbox/utils/ads'; - -const AdKeys = { - ads: ['ads'] as const, -}; - -function useAds() { - const dispatch = useAppDispatch(); - - const getAds = async () => { - return dispatch(async (_, getState) => { - const provider = await getProvider(getState); - if (provider) { - return provider.getAds(getState); - } else { - return []; - } - }); - }; - - const result = useQuery(AdKeys.ads, getAds, { - placeholderData: [], - }); - - // Filter out expired ads. - const data = filteredArray(adSchema) - .parse(result.data) - .filter(ad => !isExpired(ad)); - - return { - ...result, - data, - }; -} - -export { useAds as default, AdKeys }; diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 2c99ef8b8..81b507ce5 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -16,6 +16,3 @@ export { relationshipSchema, type Relationship } from './relationship'; export { statusSchema, type Status } from './status'; export { tagSchema, type Tag } from './tag'; export { tombstoneSchema, type Tombstone } from './tombstone'; - -// Soapbox -export { adSchema, type Ad } from './soapbox/ad'; \ No newline at end of file diff --git a/src/schemas/soapbox/ad.ts b/src/schemas/soapbox/ad.ts deleted file mode 100644 index 40dc05fb3..000000000 --- a/src/schemas/soapbox/ad.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { z } from 'zod'; - -import { cardSchema } from '../card'; - -const adSchema = z.object({ - card: cardSchema, - impression: z.string().optional().catch(undefined), - expires_at: z.string().datetime().optional().catch(undefined), - reason: z.string().optional().catch(undefined), -}); - -type Ad = z.infer; - -export { adSchema, type Ad }; \ No newline at end of file diff --git a/src/utils/__tests__/ads.test.ts b/src/utils/__tests__/ads.test.ts deleted file mode 100644 index 5ceb9d45b..000000000 --- a/src/utils/__tests__/ads.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { buildAd } from 'soapbox/jest/factory'; - -import { isExpired } from '../ads'; - -/** 3 minutes in milliseconds. */ -const threeMins = 3 * 60 * 1000; - -/** 5 minutes in milliseconds. */ -const fiveMins = 5 * 60 * 1000; - -test('isExpired()', () => { - const now = new Date(); - const iso = now.toISOString(); - const epoch = now.getTime(); - - // Sanity tests. - expect(isExpired(buildAd({ expires_at: iso }))).toBe(true); - expect(isExpired(buildAd({ expires_at: new Date(epoch + 999999).toISOString() }))).toBe(false); - - // Testing the 5-minute mark. - expect(isExpired(buildAd({ expires_at: new Date(epoch + threeMins).toISOString() }), fiveMins)).toBe(true); - expect(isExpired(buildAd({ expires_at: new Date(epoch + fiveMins + 1000).toISOString() }), fiveMins)).toBe(false); -}); diff --git a/src/utils/ads.ts b/src/utils/ads.ts deleted file mode 100644 index ed2bf0cad..000000000 --- a/src/utils/ads.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Ad } from 'soapbox/schemas'; - -/** Time (ms) window to not display an ad if it's about to expire. */ -const AD_EXPIRY_THRESHOLD = 5 * 60 * 1000; - -/** Whether the ad is expired or about to expire. */ -const isExpired = (ad: Pick, threshold = AD_EXPIRY_THRESHOLD): boolean => { - if (ad.expires_at) { - const now = new Date(); - return now.getTime() > (new Date(ad.expires_at).getTime() - threshold); - } else { - return false; - } -}; - -export { isExpired }; diff --git a/yarn.lock b/yarn.lock index b74ead8a1..76f2a23e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7902,11 +7902,6 @@ scroll-behavior@^0.9.1: dom-helpers "^3.4.0" invariant "^2.2.4" -seedrandom@^3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" - integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== - semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" From d229e3a73636102708b87cb699023d0608c5b0bc Mon Sep 17 00:00:00 2001 From: Tassoman Date: Tue, 19 Sep 2023 17:48:47 +0000 Subject: [PATCH 41/62] Translated using Weblate (Italian) Currently translated at 100.0% (1592 of 1592 strings) Translation: Soapbox/Soapbox Translate-URL: https://hosted.weblate.org/projects/soapbox-pub/soapbox/it/ --- src/locales/it.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/locales/it.json b/src/locales/it.json index 96afe28e9..f68b44040 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -80,6 +80,7 @@ "account_moderation_modal.roles.moderator": "Moderatore", "account_moderation_modal.roles.user": "Utente", "account_moderation_modal.title": "Gestisci @{acct}", + "account_note.header": "Annotazione", "account_note.placeholder": "Nessun commento", "account_search.placeholder": "Cerca un profilo", "actualStatus.edited": "Modificato: {date}", @@ -765,6 +766,7 @@ "gdpr.learn_more": "Maggiori informazioni", "gdpr.message": "{siteTitle} usa i cookie tecnici, quelli essenziali al funzionamento.", "gdpr.title": "{siteTitle} usa i cookie", + "generic.saved": "Salvata", "getting_started.open_source_notice": "{code_name} è un software open source. Puoi contribuire o segnalare errori su GitLab all'indirizzo {code_link} (v{code_version}).", "group.banned.message": "Hai ricevuto il ban da {group}", "group.cancel_request": "Cancella richiesta", From 1e71923f6d09d11ddc49afdc6bce8a8757ea989f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 19 Sep 2023 13:10:57 -0500 Subject: [PATCH 42/62] Remove support for Rumble video embeds --- ...ruthsocial-status-with-external-video.json | 95 ------------------- src/components/status-media.tsx | 68 ++++--------- src/features/preferences/index.tsx | 4 - src/features/test-timeline/index.tsx | 1 - src/normalizers/__tests__/status.test.ts | 6 -- src/normalizers/attachment.ts | 1 - src/schemas/attachment.ts | 1 - 7 files changed, 17 insertions(+), 159 deletions(-) delete mode 100644 src/__fixtures__/truthsocial-status-with-external-video.json diff --git a/src/__fixtures__/truthsocial-status-with-external-video.json b/src/__fixtures__/truthsocial-status-with-external-video.json deleted file mode 100644 index eb6150192..000000000 --- a/src/__fixtures__/truthsocial-status-with-external-video.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "id": "108046244464677537", - "created_at": "2022-03-30T15:40:53.287Z", - "in_reply_to_id": null, - "in_reply_to_account_id": null, - "sensitive": false, - "spoiler_text": "", - "visibility": "public", - "language": null, - "uri": "https://truthsocial.com/users/alex/statuses/108046244464677537", - "url": "https://truthsocial.com/@alex/108046244464677537", - "replies_count": 0, - "reblogs_count": 0, - "favourites_count": 0, - "favourited": false, - "reblogged": false, - "muted": false, - "bookmarked": false, - "pinned": false, - "content": "", - "reblog": null, - "application": { - "name": "Soapbox FE", - "website": "https://soapbox.pub/" - }, - "account": { - "id": "107759994408336377", - "username": "alex", - "acct": "alex", - "display_name": "Alex Gleason", - "locked": false, - "bot": false, - "discoverable": null, - "group": false, - "created_at": "2022-02-08T00:00:00.000Z", - "note": "

Launching Truth Social

", - "url": "https://truthsocial.com/@alex", - "avatar": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/avatars/107/759/994/408/336/377/original/119cb0dd1fa615b7.png", - "avatar_static": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/avatars/107/759/994/408/336/377/original/119cb0dd1fa615b7.png", - "header": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/headers/107/759/994/408/336/377/original/31f62b0453ccf554.png", - "header_static": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/headers/107/759/994/408/336/377/original/31f62b0453ccf554.png", - "followers_count": 4713, - "following_count": 43, - "statuses_count": 7, - "last_status_at": "2022-03-30", - "verified": true, - "location": "Texas", - "website": "https://soapbox.pub/", - "emojis": [], - "fields": [] - }, - "media_attachments": [ - { - "id": "108046243948255335", - "type": "video", - "url": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/media_attachments/files/108/046/243/948/255/335/original/3b17ce701c0d6f08.mp4", - "preview_url": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/cache/preview_cards/images/000/543/912/original/e1fcf6ace01d9ded.jpg", - "external_video_id": "vwfnq9", - "remote_url": null, - "preview_remote_url": null, - "text_url": "https://truthsocial.com/media/SpbYvqKIT2VubC9FFn0", - "meta": { - "original": { - "width": 988, - "height": 556, - "frame_rate": "60/1", - "duration": 1.949025, - "bitrate": 402396 - } - }, - "description": null, - "blurhash": null - } - ], - "mentions": [], - "tags": [], - "emojis": [], - "card": { - "url": "https://rumble.com/vz1trd-video-upload-for-108046244464677537.html?mref=ummtf&mc=3nvg0", - "title": "Video upload for 108046244464677537", - "description": "", - "type": "video", - "author_name": "hostid1", - "author_url": "https://rumble.com/user/hostid1", - "provider_name": "Rumble.com", - "provider_url": "https://rumble.com/", - "html": "", - "width": 988, - "height": 556, - "image": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/cache/preview_cards/images/000/543/912/original/e1fcf6ace01d9ded.jpg", - "embed_url": "", - "blurhash": "UQH1;m~8sks,%M~9?DRk-mRnR+xs9cWVj[bH" - }, - "poll": null -} diff --git a/src/components/status-media.tsx b/src/components/status-media.tsx index 192a4c169..99329f297 100644 --- a/src/components/status-media.tsx +++ b/src/components/status-media.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { openModal } from 'soapbox/actions/modals'; import AttachmentThumbs from 'soapbox/components/attachment-thumbs'; @@ -7,8 +7,7 @@ import PlaceholderCard from 'soapbox/features/placeholder/components/placeholder import Card from 'soapbox/features/status/components/card'; import Bundle from 'soapbox/features/ui/components/bundle'; import { MediaGallery, Video, Audio } from 'soapbox/features/ui/util/async-components'; -import { useAppDispatch, useSettings } from 'soapbox/hooks'; -import { addAutoPlay } from 'soapbox/utils/media'; +import { useAppDispatch } from 'soapbox/hooks'; import type { List as ImmutableList } from 'immutable'; import type VideoType from 'soapbox/features/video'; @@ -36,22 +35,12 @@ const StatusMedia: React.FC = ({ onToggleVisibility = () => { }, }) => { const dispatch = useAppDispatch(); - const settings = useSettings(); - const shouldAutoPlayVideo = settings.get('autoPlayVideo'); - - const [mediaWrapperWidth, setMediaWrapperWidth] = useState(undefined); const size = status.media_attachments.size; const firstAttachment = status.media_attachments.first(); let media: JSX.Element | null = null; - const setRef = (c: HTMLDivElement): void => { - if (c) { - setMediaWrapperWidth(c.offsetWidth); - } - }; - const renderLoadingMediaGallery = (): JSX.Element => { return
; }; @@ -80,45 +69,22 @@ const StatusMedia: React.FC = ({ } else if (size === 1 && firstAttachment.type === 'video') { const video = firstAttachment; - if (video.external_video_id && status.card) { - const getHeight = (): number => { - const width = Number(video.meta.getIn(['original', 'width'])); - const height = Number(video.meta.getIn(['original', 'height'])); - return Number(mediaWrapperWidth) / (width / height); - }; - - const height = getHeight(); - - media = ( -
-
+ {(Component: typeof VideoType) => ( + -
- ); - } else { - media = ( - - {(Component: typeof VideoType) => ( - - )} - - ); - } + )} + + ); } else if (size === 1 && firstAttachment.type === 'audio') { const attachment = firstAttachment; diff --git a/src/features/preferences/index.tsx b/src/features/preferences/index.tsx index cd10fd45a..84f84ba43 100644 --- a/src/features/preferences/index.tsx +++ b/src/features/preferences/index.tsx @@ -196,10 +196,6 @@ const Preferences = () => { - }> - - - {features.spoilers && }> } diff --git a/src/features/test-timeline/index.tsx b/src/features/test-timeline/index.tsx index 136d0d324..1601dc165 100644 --- a/src/features/test-timeline/index.tsx +++ b/src/features/test-timeline/index.tsx @@ -22,7 +22,6 @@ const MOCK_STATUSES: any[] = [ require('soapbox/__fixtures__/pleroma-status-vertical-video-without-metadata.json'), require('soapbox/__fixtures__/pleroma-status-with-poll-with-emojis.json'), require('soapbox/__fixtures__/pleroma-quote-of-quote-post.json'), - require('soapbox/__fixtures__/truthsocial-status-with-external-video.json'), require('soapbox/__fixtures__/truthsocial-status-in-moderation.json'), ]; diff --git a/src/normalizers/__tests__/status.test.ts b/src/normalizers/__tests__/status.test.ts index 0024a212c..05dd29e6a 100644 --- a/src/normalizers/__tests__/status.test.ts +++ b/src/normalizers/__tests__/status.test.ts @@ -198,10 +198,4 @@ describe('normalizeStatus()', () => { expect(card.type).toEqual('link'); expect(card.provider_url).toEqual('https://soapbox.pub'); }); - - it('preserves Truth Social external_video_id', () => { - const status = require('soapbox/__fixtures__/truthsocial-status-with-external-video.json'); - const result = normalizeStatus(status); - expect(result.media_attachments.get(0)?.external_video_id).toBe('vwfnq9'); - }); }); diff --git a/src/normalizers/attachment.ts b/src/normalizers/attachment.ts index f5e00135e..b63337adc 100644 --- a/src/normalizers/attachment.ts +++ b/src/normalizers/attachment.ts @@ -15,7 +15,6 @@ import { mergeDefined } from 'soapbox/utils/normalizers'; export const AttachmentRecord = ImmutableRecord({ blurhash: undefined, description: '', - external_video_id: null as string | null, // TruthSocial id: '', meta: ImmutableMap(), pleroma: ImmutableMap(), diff --git a/src/schemas/attachment.ts b/src/schemas/attachment.ts index 3df39d542..502de94e1 100644 --- a/src/schemas/attachment.ts +++ b/src/schemas/attachment.ts @@ -15,7 +15,6 @@ const blurhashSchema = z.string().superRefine((value, ctx) => { const baseAttachmentSchema = z.object({ blurhash: blurhashSchema.nullable().catch(null), description: z.string().catch(''), - external_video_id: z.string().optional().catch(undefined), // TruthSocial id: z.string(), pleroma: z.object({ mime_type: z.string().regex(/^\w+\/[-+.\w]+$/), From 804761fd093be9f39ae584a3360512d0f0af7b5f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 19 Sep 2023 17:29:24 -0500 Subject: [PATCH 43/62] Move About pages to within the UI --- src/actions/about.ts | 7 ++++--- src/containers/soapbox.tsx | 1 - src/features/about/index.tsx | 12 +++++++----- src/features/public-layout/index.tsx | 2 -- src/features/ui/index.tsx | 3 +++ src/features/ui/util/async-components.ts | 4 ++++ src/instance/about.example/index.html | 4 ++-- 7 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/actions/about.ts b/src/actions/about.ts index 07a486fd2..c8410ded8 100644 --- a/src/actions/about.ts +++ b/src/actions/about.ts @@ -1,16 +1,17 @@ -import { staticClient } from '../api'; +import api from '../api'; import type { AnyAction } from 'redux'; +import type { RootState } from 'soapbox/store'; const FETCH_ABOUT_PAGE_REQUEST = 'FETCH_ABOUT_PAGE_REQUEST'; const FETCH_ABOUT_PAGE_SUCCESS = 'FETCH_ABOUT_PAGE_SUCCESS'; const FETCH_ABOUT_PAGE_FAIL = 'FETCH_ABOUT_PAGE_FAIL'; -const fetchAboutPage = (slug = 'index', locale?: string) => (dispatch: React.Dispatch) => { +const fetchAboutPage = (slug = 'index', locale?: string) => (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: FETCH_ABOUT_PAGE_REQUEST, slug, locale }); const filename = `${slug}${locale ? `.${locale}` : ''}.html`; - return staticClient.get(`/instance/about/${filename}`) + return api(getState).get(`/instance/about/${filename}`) .then(({ data: html }) => { dispatch({ type: FETCH_ABOUT_PAGE_SUCCESS, slug, locale, html }); return html; diff --git a/src/containers/soapbox.tsx b/src/containers/soapbox.tsx index 92911789d..811784cbd 100644 --- a/src/containers/soapbox.tsx +++ b/src/containers/soapbox.tsx @@ -110,7 +110,6 @@ const SoapboxMount = () => { )} - {(features.accountCreation && instance.registrations) && ( diff --git a/src/features/about/index.tsx b/src/features/about/index.tsx index 8d1be5c2e..0112c786a 100644 --- a/src/features/about/index.tsx +++ b/src/features/about/index.tsx @@ -3,6 +3,7 @@ import { FormattedMessage } from 'react-intl'; import { useParams } from 'react-router-dom'; import { fetchAboutPage } from 'soapbox/actions/about'; +import { Card } from 'soapbox/components/ui'; import { useSoapboxConfig, useSettings, useAppDispatch } from 'soapbox/hooks'; import { languages } from '../preferences'; @@ -60,11 +61,12 @@ const AboutPage: React.FC = () => { ); return ( -
-
- - {alsoAvailable} -
+ +
+
+ {alsoAvailable} +
+ ); }; diff --git a/src/features/public-layout/index.tsx b/src/features/public-layout/index.tsx index 4b5801eb0..e39bb7508 100644 --- a/src/features/public-layout/index.tsx +++ b/src/features/public-layout/index.tsx @@ -5,7 +5,6 @@ import LandingGradient from 'soapbox/components/landing-gradient'; import { useAppSelector } from 'soapbox/hooks'; import { isStandalone } from 'soapbox/utils/state'; -import AboutPage from '../about'; import LandingPage from '../landing-page'; import Footer from './components/footer'; @@ -29,7 +28,6 @@ const PublicLayout = () => {
-
diff --git a/src/features/ui/index.tsx b/src/features/ui/index.tsx index cb2da25d1..68b65e19f 100644 --- a/src/features/ui/index.tsx +++ b/src/features/ui/index.tsx @@ -134,6 +134,7 @@ import { Announcements, EditGroup, FollowedTags, + AboutPage, } from './util/async-components'; import GlobalHotkeys from './util/global-hotkeys'; import { WrappedRoute } from './util/react-router-helpers'; @@ -350,6 +351,8 @@ const SwitchingColumnsArea: React.FC = ({ children }) => + + ); diff --git a/src/features/ui/util/async-components.ts b/src/features/ui/util/async-components.ts index 19a7c3b36..499883f90 100644 --- a/src/features/ui/util/async-components.ts +++ b/src/features/ui/util/async-components.ts @@ -1,3 +1,7 @@ +export function AboutPage() { + return import('../../about'); +} + export function EmojiPicker() { return import('../../emoji/components/emoji-picker'); } diff --git a/src/instance/about.example/index.html b/src/instance/about.example/index.html index 6af826f85..6f1a5dc39 100644 --- a/src/instance/about.example/index.html +++ b/src/instance/about.example/index.html @@ -2,7 +2,7 @@

Your_Instance description

Your_Instance is a way to join the Fediverse, to be part of a community, and to reclaim your freedom of speech in social media.

-

Site rules

+

Site rules

Please refrain from:

  1. Posting anything illegal.
  2. @@ -22,6 +22,6 @@
  3. A bot where all posts are unlisted.
-

Open Source Software

+

Open Source Software

Soapbox is free and open source (FOSS) software.

The Soapbox repository can be found at Soapbox

From 22bb44910278a4399938c53f47185507892b8912 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 19 Sep 2023 17:35:54 -0500 Subject: [PATCH 44/62] Improve about templates --- src/instance/about.example/dmca.html | 2 +- src/instance/about.example/index.html | 5 ++--- src/instance/about.example/privacy.html | 12 +++--------- src/instance/about.example/tos.html | 2 +- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/instance/about.example/dmca.html b/src/instance/about.example/dmca.html index 516748de8..7e8a236e6 100644 --- a/src/instance/about.example/dmca.html +++ b/src/instance/about.example/dmca.html @@ -1,4 +1,4 @@ -

COPYRIGHT POLICY

+

Copyright Policy

Reporting Claims of Copyright Infringement

We take claims of copyright infringement seriously. We will respond to notices of alleged copyright infringement that comply with applicable law. If you believe any materials accessible on or from this site (the "Website") infringe your copyright, you may request removal of those materials (or access to them) from the Website by submitting written notification to our copyright agent designated below. In accordance with the Online Copyright Infringement Liability Limitation Act of the Digital Millennium Copyright Act (17 U.S.C. § 512) ("DMCA"), the written notice (the "DMCA Notice") must include substantially the following:

diff --git a/src/instance/about.example/index.html b/src/instance/about.example/index.html index 6f1a5dc39..7974e5374 100644 --- a/src/instance/about.example/index.html +++ b/src/instance/about.example/index.html @@ -1,6 +1,5 @@ -

About Your_Instance

-

Your_Instance description

-

Your_Instance is a way to join the Fediverse, to be part of a community, and to reclaim your freedom of speech in social media.

+

About Us

+

Join the Fediverse, be part of a community, and to reclaim your freedom online.

Site rules

Please refrain from:

diff --git a/src/instance/about.example/privacy.html b/src/instance/about.example/privacy.html index 2b9869ba2..cf353cdae 100644 --- a/src/instance/about.example/privacy.html +++ b/src/instance/about.example/privacy.html @@ -1,12 +1,6 @@ -

PRIVACY POLICY

-

Last Updated: Your_Update_Date

- -

Your_Entity_Name ("Company" or "We") respect your privacy and are committed to protecting it through our compliance with this policy.

-

- This policy describes the types of information we may collect from you or that you may provide when you visit the website - Your_Instance_Name - and our practices for collecting, using, maintaining, protecting, and disclosing that information. -

+

Privacy Policy

+

We respect your privacy and are committed to protecting it through our compliance with this policy.

+

This policy describes the types of information we may collect from you or that you may provide when you visit our website, and our practices for collecting, using, maintaining, protecting, and disclosing that information.

This policy applies to information we collect:

On the Website.

In email, text, and other electronic messages between you and this Website.

diff --git a/src/instance/about.example/tos.html b/src/instance/about.example/tos.html index 85464c282..6569b6cc1 100644 --- a/src/instance/about.example/tos.html +++ b/src/instance/about.example/tos.html @@ -1,9 +1,9 @@ +

Terms of Service

By using this web site, you agree to these Terms of Use, to our Copyright Policy, and to our Privacy Policy.

-

Our Terms of Use are simple:

  • From f602be29051d291459eb2097452b58a988a5df15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 20 Sep 2023 15:01:19 +0200 Subject: [PATCH 45/62] Remove types for seedrandom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- package.json | 1 - src/assets/icons/COPYING.md | 2 -- yarn.lock | 5 ----- 3 files changed, 8 deletions(-) diff --git a/package.json b/package.json index a3e0dd47d..ab1de58a7 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,6 @@ "@types/react-sparklines": "^1.7.2", "@types/react-swipeable-views": "^0.13.1", "@types/redux-mock-store": "^1.0.3", - "@types/seedrandom": "^3.0.2", "@types/semver": "^7.3.9", "@types/uuid": "^9.0.0", "@vitejs/plugin-react": "^4.0.4", diff --git a/src/assets/icons/COPYING.md b/src/assets/icons/COPYING.md index a5dbe7d98..811c33b12 100644 --- a/src/assets/icons/COPYING.md +++ b/src/assets/icons/COPYING.md @@ -1,5 +1,3 @@ # Custom icons - verified.svg - Created by Alex Gleason. CC0 - -Fediverse logo: https://en.wikipedia.org/wiki/Fediverse#/media/File:Fediverse_logo_proposal.svg diff --git a/yarn.lock b/yarn.lock index 76f2a23e1..80867c74d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2511,11 +2511,6 @@ dependencies: schema-utils "*" -"@types/seedrandom@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-3.0.2.tgz#7f30db28221067a90b02e73ffd46b6685b18df1a" - integrity sha512-YPLqEOo0/X8JU3rdiq+RgUKtQhQtrppE766y7vMTu8dGML7TVtZNiiiaC/hhU9Zqw9UYopXxhuWWENclMVBwKQ== - "@types/semver@^7.3.9", "@types/semver@^7.5.0": version "7.5.2" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.2.tgz#31f6eec1ed7ec23f4f05608d3a2d381df041f564" From 25f7771c9303ef69e9c04efdb741dd16f9a9a52d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 Sep 2023 11:21:08 -0500 Subject: [PATCH 46/62] About: add Navlinks to about pages footer --- src/components/navlinks.tsx | 44 ++++++++++++++++++++++++++++++++++++ src/features/about/index.tsx | 17 +++++++++----- 2 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 src/components/navlinks.tsx diff --git a/src/components/navlinks.tsx b/src/components/navlinks.tsx new file mode 100644 index 000000000..4e0804326 --- /dev/null +++ b/src/components/navlinks.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +import { Text } from 'soapbox/components/ui'; +import { useSettings, useSoapboxConfig } from 'soapbox/hooks'; + +interface INavlinks { + type: string +} + +const Navlinks: React.FC = ({ type }) => { + const settings = useSettings(); + const { copyright, navlinks } = useSoapboxConfig(); + const locale = settings.get('locale') as string; + + return ( +
    +
    + {navlinks.get(type)?.map((link, idx) => { + const url = link.url; + const isExternal = url.startsWith('http'); + const Comp = (isExternal ? 'a' : Link) as 'a'; + const compProps = isExternal ? { href: url, target: '_blank' } : { to: url }; + + return ( +
    + + + {(link.getIn(['titleLocales', locale]) || link.get('title')) as string} + + +
    + ); + })} +
    + +
    + {copyright} +
    +
    + ); +}; + +export { Navlinks }; diff --git a/src/features/about/index.tsx b/src/features/about/index.tsx index 0112c786a..d2aa40aaf 100644 --- a/src/features/about/index.tsx +++ b/src/features/about/index.tsx @@ -3,6 +3,7 @@ import { FormattedMessage } from 'react-intl'; import { useParams } from 'react-router-dom'; import { fetchAboutPage } from 'soapbox/actions/about'; +import { Navlinks } from 'soapbox/components/navlinks'; import { Card } from 'soapbox/components/ui'; import { useSoapboxConfig, useSettings, useAppDispatch } from 'soapbox/hooks'; @@ -61,12 +62,16 @@ const AboutPage: React.FC = () => { ); return ( - -
    -
    - {alsoAvailable} -
    - +
    + +
    +
    + {alsoAvailable} +
    + + + +
    ); }; From e9b245b7d43162074ff9907418cb13d89c6373e6 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 Sep 2023 11:55:04 -0500 Subject: [PATCH 47/62] Remove 'use strict' expressions --- src/api/index.ts | 1 - src/containers/soapbox.tsx | 2 -- src/features/account/components/header.tsx | 2 -- src/features/compose/components/schedule-form.tsx | 2 -- .../components/instance-restrictions.tsx | 2 -- src/features/remote-timeline/components/pinned-hosts-picker.tsx | 2 -- src/features/ui/components/instance-info-panel.tsx | 2 -- src/features/ui/components/instance-moderation-panel.tsx | 2 -- src/features/ui/components/profile-info-panel.tsx | 2 -- src/features/ui/index.tsx | 2 -- src/is-mobile.ts | 2 -- src/middleware/sounds.ts | 2 -- src/ready.ts | 2 -- src/reducers/meta.ts | 2 -- src/rtl.ts | 2 -- src/settings.ts | 2 -- src/stream.ts | 2 -- src/utils/comparators.ts | 2 -- 18 files changed, 35 deletions(-) diff --git a/src/api/index.ts b/src/api/index.ts index 09587e443..f71676145 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -3,7 +3,6 @@ * @see {@link https://github.com/axios/axios} * @module soapbox/api */ -'use strict'; import axios, { AxiosInstance, AxiosResponse } from 'axios'; import LinkHeader from 'http-link-header'; diff --git a/src/containers/soapbox.tsx b/src/containers/soapbox.tsx index 811784cbd..2d26d78ad 100644 --- a/src/containers/soapbox.tsx +++ b/src/containers/soapbox.tsx @@ -1,5 +1,3 @@ -'use strict'; - import { QueryClientProvider } from '@tanstack/react-query'; import clsx from 'clsx'; import React, { useState, useEffect } from 'react'; diff --git a/src/features/account/components/header.tsx b/src/features/account/components/header.tsx index bbf494f34..733c10410 100644 --- a/src/features/account/components/header.tsx +++ b/src/features/account/components/header.tsx @@ -1,5 +1,3 @@ -'use strict'; - import { useMutation } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import { List as ImmutableList } from 'immutable'; diff --git a/src/features/compose/components/schedule-form.tsx b/src/features/compose/components/schedule-form.tsx index 769dc6a97..add85a428 100644 --- a/src/features/compose/components/schedule-form.tsx +++ b/src/features/compose/components/schedule-form.tsx @@ -1,5 +1,3 @@ -'use strict'; - import clsx from 'clsx'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; diff --git a/src/features/federation-restrictions/components/instance-restrictions.tsx b/src/features/federation-restrictions/components/instance-restrictions.tsx index 153cba1a7..f4ae397cd 100644 --- a/src/features/federation-restrictions/components/instance-restrictions.tsx +++ b/src/features/federation-restrictions/components/instance-restrictions.tsx @@ -1,5 +1,3 @@ -'use strict'; - import React from 'react'; import { FormattedMessage } from 'react-intl'; diff --git a/src/features/remote-timeline/components/pinned-hosts-picker.tsx b/src/features/remote-timeline/components/pinned-hosts-picker.tsx index c787764b2..39dfe119b 100644 --- a/src/features/remote-timeline/components/pinned-hosts-picker.tsx +++ b/src/features/remote-timeline/components/pinned-hosts-picker.tsx @@ -1,5 +1,3 @@ -'use strict'; - import React from 'react'; import { Button, HStack } from 'soapbox/components/ui'; diff --git a/src/features/ui/components/instance-info-panel.tsx b/src/features/ui/components/instance-info-panel.tsx index 4e2d07730..dd06665c5 100644 --- a/src/features/ui/components/instance-info-panel.tsx +++ b/src/features/ui/components/instance-info-panel.tsx @@ -1,5 +1,3 @@ -'use strict'; - import React from 'react'; import { useIntl, defineMessages } from 'react-intl'; diff --git a/src/features/ui/components/instance-moderation-panel.tsx b/src/features/ui/components/instance-moderation-panel.tsx index 4143f14d4..45d31993a 100644 --- a/src/features/ui/components/instance-moderation-panel.tsx +++ b/src/features/ui/components/instance-moderation-panel.tsx @@ -1,5 +1,3 @@ -'use strict'; - import React from 'react'; import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; diff --git a/src/features/ui/components/profile-info-panel.tsx b/src/features/ui/components/profile-info-panel.tsx index d7cbb8775..9fe73129c 100644 --- a/src/features/ui/components/profile-info-panel.tsx +++ b/src/features/ui/components/profile-info-panel.tsx @@ -1,5 +1,3 @@ -'use strict'; - import React from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; diff --git a/src/features/ui/index.tsx b/src/features/ui/index.tsx index 68b65e19f..10ef6bc1e 100644 --- a/src/features/ui/index.tsx +++ b/src/features/ui/index.tsx @@ -1,5 +1,3 @@ -'use strict'; - import clsx from 'clsx'; import React, { useEffect, useRef } from 'react'; import { Switch, useHistory, useLocation, Redirect } from 'react-router-dom'; diff --git a/src/is-mobile.ts b/src/is-mobile.ts index fe90f9a97..bbe38a123 100644 --- a/src/is-mobile.ts +++ b/src/is-mobile.ts @@ -1,5 +1,3 @@ -'use strict'; - import { supportsPassiveEvents } from 'detect-passive-events'; /** Breakpoint at which the application is considered "mobile". */ diff --git a/src/middleware/sounds.ts b/src/middleware/sounds.ts index a1055701c..4f9167135 100644 --- a/src/middleware/sounds.ts +++ b/src/middleware/sounds.ts @@ -1,5 +1,3 @@ -'use strict'; - import { AnyAction } from 'redux'; import { play, soundCache } from 'soapbox/utils/sounds'; diff --git a/src/ready.ts b/src/ready.ts index f74b03694..4ba42b1f4 100644 --- a/src/ready.ts +++ b/src/ready.ts @@ -1,5 +1,3 @@ -'use strict'; - export default function ready(loaded: () => void): void { if (['interactive', 'complete'].includes(document.readyState)) { loaded(); diff --git a/src/reducers/meta.ts b/src/reducers/meta.ts index fdce05e3e..923a89c1b 100644 --- a/src/reducers/meta.ts +++ b/src/reducers/meta.ts @@ -1,5 +1,3 @@ -'use strict'; - import { Record as ImmutableRecord } from 'immutable'; import { fetchInstance } from 'soapbox/actions/instance'; diff --git a/src/rtl.ts b/src/rtl.ts index 16a6100ef..29faab06b 100644 --- a/src/rtl.ts +++ b/src/rtl.ts @@ -1,5 +1,3 @@ -'use strict'; - // U+0590 to U+05FF - Hebrew // U+0600 to U+06FF - Arabic // U+0700 to U+074F - Syriac diff --git a/src/settings.ts b/src/settings.ts index e0ee05852..ce713f17e 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,5 +1,3 @@ -'use strict'; - export default class Settings { keyBase: string | null = null; diff --git a/src/stream.ts b/src/stream.ts index a15ce02e8..a8bb88b63 100644 --- a/src/stream.ts +++ b/src/stream.ts @@ -1,5 +1,3 @@ -'use strict'; - import WebSocketClient from '@gamestdio/websocket'; import { getAccessToken } from 'soapbox/utils/auth'; diff --git a/src/utils/comparators.ts b/src/utils/comparators.ts index ed91510e8..b664cdb6f 100644 --- a/src/utils/comparators.ts +++ b/src/utils/comparators.ts @@ -1,5 +1,3 @@ -'use strict'; - /** * Compare numerical primary keys represented as strings. * For example, '10' (as a string) is considered less than '9' From 079469f6652f6daac7039df9cce1a73195683511 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 Sep 2023 12:10:49 -0500 Subject: [PATCH 48/62] Remove useless escape characters from regexes --- src/features/compose/util/counter.ts | 2 +- src/features/compose/util/url-regex.ts | 11 ++++++----- src/normalizers/instance.ts | 2 +- src/rtl.ts | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/features/compose/util/counter.ts b/src/features/compose/util/counter.ts index 379da63b9..6d999a547 100644 --- a/src/features/compose/util/counter.ts +++ b/src/features/compose/util/counter.ts @@ -5,5 +5,5 @@ const urlPlaceholder = 'xxxxxxxxxxxxxxxxxxxxxxx'; export function countableText(inputText: string) { return inputText .replace(urlRegex, urlPlaceholder) - .replace(/(^|[^\/\w])@(([a-z0-9_]+)@[a-z0-9\.\-]+[a-z0-9]+)/ig, '$1@$3'); + .replace(/(^|[^/\w])@(([a-z0-9_]+)@[a-z0-9.-]+[a-z0-9]+)/ig, '$1@$3'); } diff --git a/src/features/compose/util/url-regex.ts b/src/features/compose/util/url-regex.ts index 5b015240e..d5171ac87 100644 --- a/src/features/compose/util/url-regex.ts +++ b/src/features/compose/util/url-regex.ts @@ -32,7 +32,7 @@ const stringSupplant = function(str: string, values: { [x: string]: any }) { export const urlRegex = (function() { regexen.spaces_group = /\x09-\x0D\x20\x85\xA0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000/; // eslint-disable-line no-control-regex regexen.invalid_chars_group = /\uFFFE\uFEFF\uFFFF\u202A-\u202E/; - regexen.punct = /\!'#%&'\(\)*\+,\\\-\.\/:;<=>\?@\[\]\^_{|}~\$/; + regexen.punct = /!'#%&'\(\)*\+,\\-\.\/:;<=>\?@\[\]\^_{|}~\$/; regexen.validUrlPrecedingChars = regexSupplant(/(?:[^A-Za-z0-9@@$###{invalid_chars_group}]|^)/); regexen.invalidDomainChars = stringSupplant('#{punct}#{spaces_group}#{invalid_chars_group}', regexen); regexen.validDomainChars = regexSupplant(/[^#{invalidDomainChars}]/); @@ -148,7 +148,7 @@ export const urlRegex = (function() { regexen.validDomain = regexSupplant(/(?:#{validSubdomain}*#{validDomainName}(?:#{validGTLD}|#{validCCTLD}|#{validPunycode}))/); regexen.validPortNumber = /[0-9]+/; regexen.pd = /\u002d\u058a\u05be\u1400\u1806\u2010-\u2015\u2e17\u2e1a\u2e3a\u2e40\u301c\u3030\u30a0\ufe31\ufe58\ufe63\uff0d/; - regexen.validGeneralUrlPathChars = regexSupplant(/[^#{spaces_group}\(\)\?]/i); + regexen.validGeneralUrlPathChars = regexSupplant(/[^#{spaces_group}()?]/i); // Allow URL paths to contain up to two nested levels of balanced parens // 1. Used in Wikipedia URLs like /Primer_(film) // 2. Used in IIS sessions like /S(dfd346)/ @@ -171,17 +171,18 @@ export const urlRegex = (function() { 'i'); // Valid end-of-path characters (so /foo. does not gobble the period). // 1. Allow =&# for empty URL parameters and other URL-join artifacts - regexen.validUrlPathEndingChars = regexSupplant(/[^#{spaces_group}\(\)\?!\*';:=\,\.\$%\[\]#{pd}~&\|@]|(?:#{validUrlBalancedParens})/i); + regexen.validUrlPathEndingChars = regexSupplant(/[^#{spaces_group}()?!*';:=,.$%[\]#{pd}~&|@]|(?:#{validUrlBalancedParens})/i); // Allow @ in a url, but only in the middle. Catch things like http://example.com/@user/ regexen.validUrlPath = regexSupplant('(?:' + '(?:' + '#{validGeneralUrlPathChars}*' + '(?:#{validUrlBalancedParens}#{validGeneralUrlPathChars}*)*' + '#{validUrlPathEndingChars}' + + // eslint-disable-next-line no-useless-escape ')|(?:@#{validGeneralUrlPathChars}+\/)' + ')', 'i'); - regexen.validUrlQueryChars = /[a-z0-9!?\*'@\(\);:&=\+\$\/%#\[\]\-_\.,~|]/i; - regexen.validUrlQueryEndingChars = /[a-z0-9_&=#\/]/i; + regexen.validUrlQueryChars = /[a-z0-9!?*'@();:&=+$/%#[\]\-_.,~|]/i; + regexen.validUrlQueryEndingChars = /[a-z0-9_&=#/]/i; regexen.validUrl = regexSupplant( '(' + // $1 URL '(https?:\\/\\/)' + // $2 Protocol diff --git a/src/normalizers/instance.ts b/src/normalizers/instance.ts index b198c7b6a..ea3327fb6 100644 --- a/src/normalizers/instance.ts +++ b/src/normalizers/instance.ts @@ -102,7 +102,7 @@ const getAttachmentLimit = (software: string | null) => software === PLEROMA ? I 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)) { + if (new RegExp(/[0-9.]+rc[0-9]+/g).test(version)) { return version.split('rc').join('-rc'); } else { return version; diff --git a/src/rtl.ts b/src/rtl.ts index 16a6100ef..2f165bda4 100644 --- a/src/rtl.ts +++ b/src/rtl.ts @@ -25,8 +25,8 @@ export function isRtl(text: string): boolean { text = text.replace(/(mailto:)([^\s@]+@[^\s@]+\.[^\s@]+)/g, ''); // Remove Phone numbe links text = text.replace(/(tel:)([+\d\s()-]+)/g, ''); - text = text.replace(/(?:^|[^\/\w])@([a-z0-9_]+(@[a-z0-9\.\-]+)?)/ig, ''); - text = text.replace(/(?:^|[^\/\w])#([\S]+)/ig, ''); + text = text.replace(/(?:^|[^/\w])@([a-z0-9_]+(@[a-z0-9.-]+)?)/ig, ''); + text = text.replace(/(?:^|[^/\w])#([\S]+)/ig, ''); text = text.replace(/\s+/g, ''); const matches = text.match(rtlChars); From eccf23eae376d3a5faff743c70c3e5b73afa405b Mon Sep 17 00:00:00 2001 From: Tassoman Date: Wed, 20 Sep 2023 17:34:33 +0000 Subject: [PATCH 49/62] adding tailwind styling for rich text markdown headings --- src/components/markup.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/components/markup.css b/src/components/markup.css index 48e292bcc..dd6d6eddc 100644 --- a/src/components/markup.css +++ b/src/components/markup.css @@ -2,6 +2,18 @@ @apply whitespace-pre-wrap; } +[data-markup] h1 { + @apply text-3xl font-semibold; +} + +[data-markup] h2 { + @apply text-2xl font-semibold; +} + +[data-markup] h3 { + @apply text-xl font-black; +} + [data-markup] p { @apply mb-4 whitespace-pre-wrap; } From e9caa6f69e4a315f471831629864d3768b4f0393 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 Sep 2023 12:59:55 -0500 Subject: [PATCH 50/62] Move signup page into the UI --- src/containers/soapbox.tsx | 9 --------- src/features/auth-layout/index.tsx | 2 -- .../components/registration-page.tsx | 19 +++++++++++++++++++ src/features/ui/index.tsx | 8 +++++++- src/features/ui/util/async-components.ts | 4 ++++ 5 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 src/features/auth-login/components/registration-page.tsx diff --git a/src/containers/soapbox.tsx b/src/containers/soapbox.tsx index 2d26d78ad..deebda4ee 100644 --- a/src/containers/soapbox.tsx +++ b/src/containers/soapbox.tsx @@ -30,12 +30,10 @@ import { useAppSelector, useAppDispatch, useOwnAccount, - useFeatures, useSoapboxConfig, useSettings, useTheme, useLocale, - useInstance, } from 'soapbox/hooks'; import MESSAGES from 'soapbox/messages'; import { normalizeSoapboxConfig } from 'soapbox/normalizers'; @@ -76,10 +74,8 @@ const SoapboxMount = () => { useCachedLocationHandler(); const me = useAppSelector(state => state.me); - const instance = useInstance(); const { account } = useOwnAccount(); const soapboxConfig = useSoapboxConfig(); - const features = useFeatures(); const needsOnboarding = useAppSelector(state => state.onboarding.needsOnboarding); const showOnboarding = account && needsOnboarding; @@ -109,11 +105,6 @@ const SoapboxMount = () => { )} - - {(features.accountCreation && instance.registrations) && ( - - )} - diff --git a/src/features/auth-layout/index.tsx b/src/features/auth-layout/index.tsx index 608e8fe83..df1d0dd4b 100644 --- a/src/features/auth-layout/index.tsx +++ b/src/features/auth-layout/index.tsx @@ -10,7 +10,6 @@ import { Button, Card, CardBody } from '../../components/ui'; import LoginPage from '../auth-login/components/login-page'; import PasswordReset from '../auth-login/components/password-reset'; import PasswordResetConfirm from '../auth-login/components/password-reset-confirm'; -import RegistrationForm from '../auth-login/components/registration-form'; import ExternalLoginForm from '../external-login/components/external-login-form'; import Footer from '../public-layout/components/footer'; import RegisterInvite from '../register-invite'; @@ -66,7 +65,6 @@ const AuthLayout = () => { - diff --git a/src/features/auth-login/components/registration-page.tsx b/src/features/auth-login/components/registration-page.tsx new file mode 100644 index 000000000..c58633fa6 --- /dev/null +++ b/src/features/auth-login/components/registration-page.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { Card, CardTitle, Stack } from 'soapbox/components/ui'; + +import RegistrationForm from './registration-form'; + +const RegistrationPage: React.FC = () => { + return ( + + + } /> + + + + ); +}; + +export default RegistrationPage; \ No newline at end of file diff --git a/src/features/ui/index.tsx b/src/features/ui/index.tsx index 10ef6bc1e..77520d0a5 100644 --- a/src/features/ui/index.tsx +++ b/src/features/ui/index.tsx @@ -21,7 +21,7 @@ import withHoc from 'soapbox/components/hoc/with-hoc'; import SidebarNavigation from 'soapbox/components/sidebar-navigation'; import ThumbNavigation from 'soapbox/components/thumb-navigation'; import { Layout } from 'soapbox/components/ui'; -import { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures, useDraggedFiles } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures, useDraggedFiles, useInstance } from 'soapbox/hooks'; import AdminPage from 'soapbox/pages/admin-page'; import ChatsPage from 'soapbox/pages/chats-page'; import DefaultPage from 'soapbox/pages/default-page'; @@ -133,6 +133,7 @@ import { EditGroup, FollowedTags, AboutPage, + RegistrationPage, } from './util/async-components'; import GlobalHotkeys from './util/global-hotkeys'; import { WrappedRoute } from './util/react-router-helpers'; @@ -158,6 +159,7 @@ interface ISwitchingColumnsArea { } const SwitchingColumnsArea: React.FC = ({ children }) => { + const instance = useInstance(); const features = useFeatures(); const { search } = useLocation(); @@ -351,6 +353,10 @@ const SwitchingColumnsArea: React.FC = ({ children }) => + {(features.accountCreation && instance.registrations) && ( + + )} + ); diff --git a/src/features/ui/util/async-components.ts b/src/features/ui/util/async-components.ts index 499883f90..bef8fcf30 100644 --- a/src/features/ui/util/async-components.ts +++ b/src/features/ui/util/async-components.ts @@ -242,6 +242,10 @@ export function LogoutPage() { return import('../../auth-login/components/logout'); } +export function RegistrationPage() { + return import('../../auth-login/components/registration-page'); +} + export function Settings() { return import('../../settings'); } From de68ca487bec9df244f53b80bbc4390b55effd3c Mon Sep 17 00:00:00 2001 From: Soapbox Bot Date: Wed, 20 Sep 2023 18:08:10 +0000 Subject: [PATCH 51/62] Update dependency cssnano to v6 --- package.json | 2 +- yarn.lock | 459 +++++++++++++++++++++++++++------------------------ 2 files changed, 247 insertions(+), 214 deletions(-) diff --git a/package.json b/package.json index ab1de58a7..69ce380dd 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "clsx": "^2.0.0", "core-js": "^3.27.2", "cryptocurrency-icons": "^0.18.1", - "cssnano": "^5.1.10", + "cssnano": "^6.0.0", "detect-passive-events": "^2.0.0", "dotenv": "^16.0.0", "emoji-datasource": "14.0.0", diff --git a/yarn.lock b/yarn.lock index 80867c74d..5289f8f55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3283,7 +3283,7 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" -browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.20.3, browserslist@^4.21.10, browserslist@^4.21.9: +browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.21.9: version "4.21.10" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== @@ -3689,17 +3689,17 @@ cryptocurrency-icons@^0.18.1: resolved "https://registry.yarnpkg.com/cryptocurrency-icons/-/cryptocurrency-icons-0.18.1.tgz#b1bdbfb4b78996ec003e63dbd7ec0b830d23b89c" integrity sha512-dvR5O8JOmav3559Yb0Igpkia+3vpt/aeNvMu5ZIVUG2Bzpq9wNcOJRIQas49XJrPjtZ98GAEn3aDQO+w7uhS2w== -css-declaration-sorter@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz#bfd2f6f50002d6a3ae779a87d3a0c5d5b10e0f02" - integrity sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg== +css-declaration-sorter@^6.3.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz#28beac7c20bad7f1775be3a7129d7eae409a3a71" + integrity sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g== css-functions-list@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.2.0.tgz#8290b7d064bf483f48d6559c10e98dc4d1ad19ee" integrity sha512-d/jBMPyYybkkLVypgtGv12R+pIFw4/f/IHtCTxWpZc8ofTYOPigIgmA6vu5rMHartZC+WuXhBUHfnyNUIQSYrg== -css-select@^4.1.3, css-select@^4.2.1: +css-select@^4.2.1: version "4.3.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== @@ -3710,15 +3710,18 @@ css-select@^4.1.3, css-select@^4.2.1: domutils "^2.8.0" nth-check "^2.0.1" -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" -css-tree@^2.3.1: +css-tree@^2.2.1, css-tree@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== @@ -3726,7 +3729,15 @@ css-tree@^2.3.1: mdn-data "2.0.30" source-map-js "^1.0.1" -css-what@^6.0.1: +css-tree@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" + integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== + dependencies: + mdn-data "2.0.28" + source-map-js "^1.0.1" + +css-what@^6.0.1, css-what@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== @@ -3741,61 +3752,60 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-default@^5.2.10: - version "5.2.10" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.10.tgz#6dfffe6cc3b13f3bb356a42c49a334a98700ef45" - integrity sha512-H8TJRhTjBKVOPltp9vr9El9I+IfYsOMhmXdK0LwdvwJcxYX9oWkY7ctacWusgPWAgQq1vt/WO8v+uqpfLnM7QA== - dependencies: - css-declaration-sorter "^6.2.2" - cssnano-utils "^3.1.0" - postcss-calc "^8.2.3" - postcss-colormin "^5.3.0" - postcss-convert-values "^5.1.2" - postcss-discard-comments "^5.1.2" - postcss-discard-duplicates "^5.1.0" - postcss-discard-empty "^5.1.1" - postcss-discard-overridden "^5.1.0" - postcss-merge-longhand "^5.1.5" - postcss-merge-rules "^5.1.2" - postcss-minify-font-values "^5.1.0" - postcss-minify-gradients "^5.1.1" - postcss-minify-params "^5.1.3" - postcss-minify-selectors "^5.2.1" - postcss-normalize-charset "^5.1.0" - postcss-normalize-display-values "^5.1.0" - postcss-normalize-positions "^5.1.0" - postcss-normalize-repeat-style "^5.1.0" - postcss-normalize-string "^5.1.0" - postcss-normalize-timing-functions "^5.1.0" - postcss-normalize-unicode "^5.1.0" - postcss-normalize-url "^5.1.0" - postcss-normalize-whitespace "^5.1.1" - postcss-ordered-values "^5.1.1" - postcss-reduce-initial "^5.1.0" - postcss-reduce-transforms "^5.1.0" - postcss-svgo "^5.1.0" - postcss-unique-selectors "^5.1.1" - -cssnano-utils@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" - integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== +cssnano-preset-default@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-6.0.1.tgz#2a93247140d214ddb9f46bc6a3562fa9177fe301" + integrity sha512-7VzyFZ5zEB1+l1nToKyrRkuaJIx0zi/1npjvZfbBwbtNTzhLtlvYraK/7/uqmX2Wb2aQtd983uuGw79jAjLSuQ== + dependencies: + css-declaration-sorter "^6.3.1" + cssnano-utils "^4.0.0" + postcss-calc "^9.0.0" + postcss-colormin "^6.0.0" + postcss-convert-values "^6.0.0" + postcss-discard-comments "^6.0.0" + postcss-discard-duplicates "^6.0.0" + postcss-discard-empty "^6.0.0" + postcss-discard-overridden "^6.0.0" + postcss-merge-longhand "^6.0.0" + postcss-merge-rules "^6.0.1" + postcss-minify-font-values "^6.0.0" + postcss-minify-gradients "^6.0.0" + postcss-minify-params "^6.0.0" + postcss-minify-selectors "^6.0.0" + postcss-normalize-charset "^6.0.0" + postcss-normalize-display-values "^6.0.0" + postcss-normalize-positions "^6.0.0" + postcss-normalize-repeat-style "^6.0.0" + postcss-normalize-string "^6.0.0" + postcss-normalize-timing-functions "^6.0.0" + postcss-normalize-unicode "^6.0.0" + postcss-normalize-url "^6.0.0" + postcss-normalize-whitespace "^6.0.0" + postcss-ordered-values "^6.0.0" + postcss-reduce-initial "^6.0.0" + postcss-reduce-transforms "^6.0.0" + postcss-svgo "^6.0.0" + postcss-unique-selectors "^6.0.0" + +cssnano-utils@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-4.0.0.tgz#d1da885ec04003ab19505ff0e62e029708d36b08" + integrity sha512-Z39TLP+1E0KUcd7LGyF4qMfu8ZufI0rDzhdyAMsa/8UyNUU8wpS0fhdBxbQbv32r64ea00h4878gommRVg2BHw== -cssnano@^5.1.10: - version "5.1.10" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.10.tgz#fc6ddd9a4d7d238f320634326ed814cf0abf6e1c" - integrity sha512-ACpnRgDg4m6CZD/+8SgnLcGCgy6DDGdkMbOawwdvVxNietTNLe/MtWcenp6qT0PRt5wzhGl6/cjMWCdhKXC9QA== +cssnano@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-6.0.1.tgz#87c38c4cd47049c735ab756d7e77ac3ca855c008" + integrity sha512-fVO1JdJ0LSdIGJq68eIxOqFpIJrZqXUsBt8fkrBcztCQqAjQD51OhZp7tc0ImcbwXD4k7ny84QTV90nZhmqbkg== dependencies: - cssnano-preset-default "^5.2.10" - lilconfig "^2.0.3" - yaml "^1.10.2" + cssnano-preset-default "^6.0.1" + lilconfig "^2.1.0" -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== +csso@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6" + integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== dependencies: - css-tree "^1.1.2" + css-tree "~2.2.0" cssstyle@^3.0.0: version "3.0.0" @@ -4021,11 +4031,25 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + domelementtype@^2.0.1, domelementtype@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + domexception@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" @@ -4047,6 +4071,13 @@ domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" @@ -4056,6 +4087,15 @@ domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -4145,7 +4185,7 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== -entities@^4.4.0: +entities@^4.2.0, entities@^4.4.0: version "4.5.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== @@ -5902,7 +5942,7 @@ lie@3.1.1: dependencies: immediate "~3.0.5" -lilconfig@^2.0.3, lilconfig@^2.0.5, lilconfig@^2.1.0: +lilconfig@^2.0.5, lilconfig@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== @@ -6163,10 +6203,10 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== +mdn-data@2.0.28: + version "2.0.28" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" + integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== mdn-data@2.0.30: version "2.0.30" @@ -6725,51 +6765,51 @@ please-upgrade-node@^3.2.0: dependencies: semver-compare "^1.0.0" -postcss-calc@^8.2.3: - version "8.2.4" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" - integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== +postcss-calc@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.1.tgz#a744fd592438a93d6de0f1434c572670361eb6c6" + integrity sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ== dependencies: - postcss-selector-parser "^6.0.9" + postcss-selector-parser "^6.0.11" postcss-value-parser "^4.2.0" -postcss-colormin@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a" - integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg== +postcss-colormin@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-6.0.0.tgz#d4250652e952e1c0aca70c66942da93d3cdeaafe" + integrity sha512-EuO+bAUmutWoZYgHn2T1dG1pPqHU6L4TjzPlu4t1wZGXQ/fxV16xg2EJmYi0z+6r+MGV1yvpx1BHkUaRrPa2bw== dependencies: - browserslist "^4.16.6" + browserslist "^4.21.4" caniuse-api "^3.0.0" colord "^2.9.1" postcss-value-parser "^4.2.0" -postcss-convert-values@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz#31586df4e184c2e8890e8b34a0b9355313f503ab" - integrity sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g== +postcss-convert-values@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-6.0.0.tgz#ec94a954957e5c3f78f0e8f65dfcda95280b8996" + integrity sha512-U5D8QhVwqT++ecmy8rnTb+RL9n/B806UVaS3m60lqle4YDFcpbS3ae5bTQIh3wOGUSDHSEtMYLs/38dNG7EYFw== dependencies: - browserslist "^4.20.3" + browserslist "^4.21.4" postcss-value-parser "^4.2.0" -postcss-discard-comments@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" - integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== +postcss-discard-comments@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-6.0.0.tgz#9ca335e8b68919f301b24ba47dde226a42e535fe" + integrity sha512-p2skSGqzPMZkEQvJsgnkBhCn8gI7NzRH2683EEjrIkoMiwRELx68yoUJ3q3DGSGuQ8Ug9Gsn+OuDr46yfO+eFw== -postcss-discard-duplicates@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" - integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== +postcss-discard-duplicates@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.0.tgz#c26177a6c33070922e67e9a92c0fd23d443d1355" + integrity sha512-bU1SXIizMLtDW4oSsi5C/xHKbhLlhek/0/yCnoMQany9k3nPBq+Ctsv/9oMmyqbR96HYHxZcHyK2HR5P/mqoGA== -postcss-discard-empty@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" - integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== +postcss-discard-empty@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-6.0.0.tgz#06c1c4fce09e22d2a99e667c8550eb8a3a1b9aee" + integrity sha512-b+h1S1VT6dNhpcg+LpyiUrdnEZfICF0my7HAKgJixJLW7BnNmpRH34+uw/etf5AhOlIhIAuXApSzzDzMI9K/gQ== -postcss-discard-overridden@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" - integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== +postcss-discard-overridden@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-6.0.0.tgz#49c5262db14e975e349692d9024442de7cd8e234" + integrity sha512-4VELwssYXDFigPYAZ8vL4yX4mUepF/oCBeeIT4OXsJPYOtvJumyz9WflmJWTfDwCUcpDR+z0zvCWBXgTx35SVw== postcss-import@^15.1.0: version "15.1.0" @@ -6800,53 +6840,53 @@ postcss-media-query-parser@^0.2.3: resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" integrity sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ= -postcss-merge-longhand@^5.1.5: - version "5.1.5" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.5.tgz#b0e03bee3b964336f5f33c4fc8eacae608e91c05" - integrity sha512-NOG1grw9wIO+60arKa2YYsrbgvP6tp+jqc7+ZD5/MalIw234ooH2C6KlR6FEn4yle7GqZoBxSK1mLBE9KPur6w== +postcss-merge-longhand@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-6.0.0.tgz#6f627b27db939bce316eaa97e22400267e798d69" + integrity sha512-4VSfd1lvGkLTLYcxFuISDtWUfFS4zXe0FpF149AyziftPFQIWxjvFSKhA4MIxMe4XM3yTDgQMbSNgzIVxChbIg== dependencies: postcss-value-parser "^4.2.0" - stylehacks "^5.1.0" + stylehacks "^6.0.0" -postcss-merge-rules@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz#7049a14d4211045412116d79b751def4484473a5" - integrity sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ== +postcss-merge-rules@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-6.0.1.tgz#39f165746404e646c0f5c510222ccde4824a86aa" + integrity sha512-a4tlmJIQo9SCjcfiCcCMg/ZCEe0XTkl/xK0XHBs955GWg9xDX3NwP9pwZ78QUOWB8/0XCjZeJn98Dae0zg6AAw== dependencies: - browserslist "^4.16.6" + browserslist "^4.21.4" caniuse-api "^3.0.0" - cssnano-utils "^3.1.0" + cssnano-utils "^4.0.0" postcss-selector-parser "^6.0.5" -postcss-minify-font-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" - integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== +postcss-minify-font-values@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-6.0.0.tgz#68d4a028f9fa5f61701974724b2cc9445d8e6070" + integrity sha512-zNRAVtyh5E8ndZEYXA4WS8ZYsAp798HiIQ1V2UF/C/munLp2r1UGHwf1+6JFu7hdEhJFN+W1WJQKBrtjhFgEnA== dependencies: postcss-value-parser "^4.2.0" -postcss-minify-gradients@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" - integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== +postcss-minify-gradients@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-6.0.0.tgz#22b5c88cc63091dadbad34e31ff958404d51d679" + integrity sha512-wO0F6YfVAR+K1xVxF53ueZJza3L+R3E6cp0VwuXJQejnNUH0DjcAFe3JEBeTY1dLwGa0NlDWueCA1VlEfiKgAA== dependencies: colord "^2.9.1" - cssnano-utils "^3.1.0" + cssnano-utils "^4.0.0" postcss-value-parser "^4.2.0" -postcss-minify-params@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz#ac41a6465be2db735099bbd1798d85079a6dc1f9" - integrity sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg== +postcss-minify-params@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-6.0.0.tgz#2b3a85a9e3b990d7a16866f430f5fd1d5961b539" + integrity sha512-Fz/wMQDveiS0n5JPcvsMeyNXOIMrwF88n7196puSuQSWSa+/Ofc1gDOSY2xi8+A4PqB5dlYCKk/WfqKqsI+ReQ== dependencies: - browserslist "^4.16.6" - cssnano-utils "^3.1.0" + browserslist "^4.21.4" + cssnano-utils "^4.0.0" postcss-value-parser "^4.2.0" -postcss-minify-selectors@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" - integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg== +postcss-minify-selectors@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-6.0.0.tgz#5046c5e8680a586e5a0cad52cc9aa36d6be5bda2" + integrity sha512-ec/q9JNCOC2CRDNnypipGfOhbYPuUkewGwLnbv6omue/PSASbHSU7s6uSQ0tcFRVv731oMIx8k0SP4ZX6be/0g== dependencies: postcss-selector-parser "^6.0.5" @@ -6857,89 +6897,88 @@ postcss-nested@^6.0.1: dependencies: postcss-selector-parser "^6.0.11" -postcss-normalize-charset@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" - integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== +postcss-normalize-charset@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-6.0.0.tgz#36cc12457259064969fb96f84df491652a4b0975" + integrity sha512-cqundwChbu8yO/gSWkuFDmKrCZ2vJzDAocheT2JTd0sFNA4HMGoKMfbk2B+J0OmO0t5GUkiAkSM5yF2rSLUjgQ== -postcss-normalize-display-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" - integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== +postcss-normalize-display-values@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.0.tgz#8d2961415078644d8c6bbbdaf9a2fdd60f546cd4" + integrity sha512-Qyt5kMrvy7dJRO3OjF7zkotGfuYALETZE+4lk66sziWSPzlBEt7FrUshV6VLECkI4EN8Z863O6Nci4NXQGNzYw== dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-positions@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.0.tgz#902a7cb97cf0b9e8b1b654d4a43d451e48966458" - integrity sha512-8gmItgA4H5xiUxgN/3TVvXRoJxkAWLW6f/KKhdsH03atg0cB8ilXnrB5PpSshwVu/dD2ZsRFQcR1OEmSBDAgcQ== +postcss-normalize-positions@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-6.0.0.tgz#25b96df99a69f8925f730eaee0be74416865e301" + integrity sha512-mPCzhSV8+30FZyWhxi6UoVRYd3ZBJgTRly4hOkaSifo0H+pjDYcii/aVT4YE6QpOil15a5uiv6ftnY3rm0igPg== dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-repeat-style@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.0.tgz#f6d6fd5a54f51a741cc84a37f7459e60ef7a6398" - integrity sha512-IR3uBjc+7mcWGL6CtniKNQ4Rr5fTxwkaDHwMBDGGs1x9IVRkYIT/M4NelZWkAOBdV6v3Z9S46zqaKGlyzHSchw== +postcss-normalize-repeat-style@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.0.tgz#ddf30ad8762feb5b1eb97f39f251acd7b8353299" + integrity sha512-50W5JWEBiOOAez2AKBh4kRFm2uhrT3O1Uwdxz7k24aKtbD83vqmcVG7zoIwo6xI2FZ/HDlbrCopXhLeTpQib1A== dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-string@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" - integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== +postcss-normalize-string@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-6.0.0.tgz#948282647a51e409d69dde7910f0ac2ff97cb5d8" + integrity sha512-KWkIB7TrPOiqb8ZZz6homet2KWKJwIlysF5ICPZrXAylGe2hzX/HSf4NTX2rRPJMAtlRsj/yfkrWGavFuB+c0w== dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-timing-functions@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" - integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== +postcss-normalize-timing-functions@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.0.tgz#5f13e650b8c43351989fc5de694525cc2539841c" + integrity sha512-tpIXWciXBp5CiFs8sem90IWlw76FV4oi6QEWfQwyeREVwUy39VSeSqjAT7X0Qw650yAimYW5gkl2Gd871N5SQg== dependencies: postcss-value-parser "^4.2.0" -postcss-normalize-unicode@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz#3d23aede35e160089a285e27bf715de11dc9db75" - integrity sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ== +postcss-normalize-unicode@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.0.tgz#741b3310f874616bdcf07764f5503695d3604730" + integrity sha512-ui5crYkb5ubEUDugDc786L/Me+DXp2dLg3fVJbqyAl0VPkAeALyAijF2zOsnZyaS1HyfPuMH0DwyY18VMFVNkg== dependencies: - browserslist "^4.16.6" + browserslist "^4.21.4" postcss-value-parser "^4.2.0" -postcss-normalize-url@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" - integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== +postcss-normalize-url@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-6.0.0.tgz#d0a31e962a16401fb7deb7754b397a323fb650b4" + integrity sha512-98mvh2QzIPbb02YDIrYvAg4OUzGH7s1ZgHlD3fIdTHLgPLRpv1ZTKJDnSAKr4Rt21ZQFzwhGMXxpXlfrUBKFHw== dependencies: - normalize-url "^6.0.1" postcss-value-parser "^4.2.0" -postcss-normalize-whitespace@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" - integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== +postcss-normalize-whitespace@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.0.tgz#accb961caa42e25ca4179b60855b79b1f7129d4d" + integrity sha512-7cfE1AyLiK0+ZBG6FmLziJzqQCpTQY+8XjMhMAz8WSBSCsCNNUKujgIgjCAmDT3cJ+3zjTXFkoD15ZPsckArVw== dependencies: postcss-value-parser "^4.2.0" -postcss-ordered-values@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.1.tgz#0b41b610ba02906a3341e92cab01ff8ebc598adb" - integrity sha512-7lxgXF0NaoMIgyihL/2boNAEZKiW0+HkMhdKMTD93CjW8TdCy2hSdj8lsAo+uwm7EDG16Da2Jdmtqpedl0cMfw== +postcss-ordered-values@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-6.0.0.tgz#374704cdff25560d44061d17ba3c6308837a3218" + integrity sha512-K36XzUDpvfG/nWkjs6d1hRBydeIxGpKS2+n+ywlKPzx1nMYDYpoGbcjhj5AwVYJK1qV2/SDoDEnHzlPD6s3nMg== dependencies: - cssnano-utils "^3.1.0" + cssnano-utils "^4.0.0" postcss-value-parser "^4.2.0" -postcss-reduce-initial@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz#fc31659ea6e85c492fb2a7b545370c215822c5d6" - integrity sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw== +postcss-reduce-initial@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz#7d16e83e60e27e2fa42f56ec0b426f1da332eca7" + integrity sha512-s2UOnidpVuXu6JiiI5U+fV2jamAw5YNA9Fdi/GRK0zLDLCfXmSGqQtzpUPtfN66RtCbb9fFHoyZdQaxOB3WxVA== dependencies: - browserslist "^4.16.6" + browserslist "^4.21.4" caniuse-api "^3.0.0" -postcss-reduce-transforms@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" - integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== +postcss-reduce-transforms@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.0.tgz#28ff2601a6d9b96a2f039b3501526e1f4d584a46" + integrity sha512-FQ9f6xM1homnuy1wLe9lP1wujzxnwt1EwiigtWwuyf8FsqqXUDUp2Ulxf9A5yjlUOTdCJO6lonYjg1mgqIIi2w== dependencies: postcss-value-parser "^4.2.0" @@ -6966,7 +7005,7 @@ postcss-selector-parser@6.0.10: cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: +postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5: version "6.0.13" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== @@ -6974,18 +7013,18 @@ postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.13, postcss-select cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss-svgo@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" - integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== +postcss-svgo@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-6.0.0.tgz#7b18742d38d4505a0455bbe70d52b49f00eaf69d" + integrity sha512-r9zvj/wGAoAIodn84dR/kFqwhINp5YsJkLoujybWG59grR/IHx+uQ2Zo+IcOwM0jskfYX3R0mo+1Kip1VSNcvw== dependencies: postcss-value-parser "^4.2.0" - svgo "^2.7.0" + svgo "^3.0.2" -postcss-unique-selectors@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" - integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== +postcss-unique-selectors@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-6.0.0.tgz#c94e9b0f7bffb1203894e42294b5a1b3fb34fbe1" + integrity sha512-EPQzpZNxOxP7777t73RQpZE5e9TrnCrkvp7AH7a0l89JmZiPnS82y216JowHXwpBCQitfyxrof9TK3rYbi7/Yw== dependencies: postcss-selector-parser "^6.0.5" @@ -8067,11 +8106,6 @@ split-on-first@^1.0.0: resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - stackback@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" @@ -8219,12 +8253,12 @@ style-search@^0.1.0: resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI= -stylehacks@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.0.tgz#a40066490ca0caca04e96c6b02153ddc39913520" - integrity sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q== +stylehacks@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-6.0.0.tgz#9fdd7c217660dae0f62e14d51c89f6c01b3cb738" + integrity sha512-+UT589qhHPwz6mTlCLSt/vMNTJx8dopeJlZAlBMJPWA3ORqu6wmQY7FBXf+qD+FsqoBJODyqNxOUP3jdntFRdw== dependencies: - browserslist "^4.16.6" + browserslist "^4.21.4" postcss-selector-parser "^6.0.4" stylelint-config-recommended-scss@^13.0.0: @@ -8370,18 +8404,17 @@ svg-tags@^1.0.0: resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q= -svgo@^2.7.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== +svgo@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.0.2.tgz#5e99eeea42c68ee0dc46aa16da093838c262fe0a" + integrity sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ== dependencies: "@trysound/sax" "0.2.0" commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" + css-select "^5.1.0" + css-tree "^2.2.1" + csso "^5.0.5" picocolors "^1.0.0" - stable "^0.1.8" symbol-tree@^3.2.4: version "3.2.4" @@ -9496,7 +9529,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.0, yaml@^1.10.2: +yaml@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== From f5d17fd55d30088283f780d36ba778acaf689ade Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 Sep 2023 13:36:20 -0500 Subject: [PATCH 52/62] Fix regex crash --- src/features/compose/util/url-regex.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/features/compose/util/url-regex.ts b/src/features/compose/util/url-regex.ts index d5171ac87..fccabb1e5 100644 --- a/src/features/compose/util/url-regex.ts +++ b/src/features/compose/util/url-regex.ts @@ -32,7 +32,8 @@ const stringSupplant = function(str: string, values: { [x: string]: any }) { export const urlRegex = (function() { regexen.spaces_group = /\x09-\x0D\x20\x85\xA0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000/; // eslint-disable-line no-control-regex regexen.invalid_chars_group = /\uFFFE\uFEFF\uFFFF\u202A-\u202E/; - regexen.punct = /!'#%&'\(\)*\+,\\-\.\/:;<=>\?@\[\]\^_{|}~\$/; + // eslint-disable-next-line no-useless-escape + regexen.punct = /!'#%&@,:;<=>_~{}\$\?\^\*\+\-\.\(\)\[\]\|\/\\/; regexen.validUrlPrecedingChars = regexSupplant(/(?:[^A-Za-z0-9@@$###{invalid_chars_group}]|^)/); regexen.invalidDomainChars = stringSupplant('#{punct}#{spaces_group}#{invalid_chars_group}', regexen); regexen.validDomainChars = regexSupplant(/[^#{invalidDomainChars}]/); From f3503a67f1bedf0d6b7d2ebfb4ffbbadc037274e Mon Sep 17 00:00:00 2001 From: Tassoman Date: Wed, 20 Sep 2023 18:47:47 +0000 Subject: [PATCH 53/62] added table styling to rich text markdown content statuses --- src/components/markup.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/components/markup.css b/src/components/markup.css index dd6d6eddc..185493d72 100644 --- a/src/components/markup.css +++ b/src/components/markup.css @@ -51,6 +51,18 @@ @apply py-1 pl-4 mb-4 border-l-4 border-solid border-gray-400 text-gray-500 dark:text-gray-400; } +[data-markup] table { + @apply table-auto w-full bg-gray-200 dark:bg-gray-900 my-4 rounded-md; +} + +[data-markup] table th, table td { + @apply text-center px-2; +} + +[data-markup] table th { + @apply border-b-2 border-gray-600; +} + [data-markup] code, [data-markup] pre { @apply cursor-text font-mono; From ffae3f89df6e26243c493f849719bf317fab735b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 Sep 2023 13:57:33 -0500 Subject: [PATCH 54/62] Move Login page to UI --- src/components/big-card.tsx | 32 ++++++ src/containers/soapbox.tsx | 1 - src/features/auth-layout/index.tsx | 1 - .../auth-login/components/login-form.tsx | 100 ++++++++---------- .../auth-login/components/login-page.tsx | 10 +- src/features/ui/index.tsx | 3 + 6 files changed, 88 insertions(+), 59 deletions(-) create mode 100644 src/components/big-card.tsx diff --git a/src/components/big-card.tsx b/src/components/big-card.tsx new file mode 100644 index 000000000..2fcb7078a --- /dev/null +++ b/src/components/big-card.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +import { Card, CardBody, Stack, Text } from 'soapbox/components/ui'; + +interface IBigCard { + title: React.ReactNode + subtitle?: React.ReactNode + children: React.ReactNode +} + +const BigCard: React.FC = ({ title, subtitle, children }) => { + return ( + + +
    + + {title} + {subtitle && {subtitle}} + +
    + + +
    + {children} +
    +
    +
    +
    + ); +}; + +export { BigCard }; \ No newline at end of file diff --git a/src/containers/soapbox.tsx b/src/containers/soapbox.tsx index deebda4ee..8f0015b87 100644 --- a/src/containers/soapbox.tsx +++ b/src/containers/soapbox.tsx @@ -104,7 +104,6 @@ const SoapboxMount = () => { )} - diff --git a/src/features/auth-layout/index.tsx b/src/features/auth-layout/index.tsx index df1d0dd4b..42cc1ba50 100644 --- a/src/features/auth-layout/index.tsx +++ b/src/features/auth-layout/index.tsx @@ -64,7 +64,6 @@ const AuthLayout = () => { - diff --git a/src/features/auth-login/components/login-form.tsx b/src/features/auth-login/components/login-form.tsx index d7871fc75..19f9e76bb 100644 --- a/src/features/auth-login/components/login-form.tsx +++ b/src/features/auth-login/components/login-form.tsx @@ -2,11 +2,9 @@ import React from 'react'; import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { Link } from 'react-router-dom'; -import { Button, Form, FormActions, FormGroup, Input, Stack } from 'soapbox/components/ui'; +import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui'; import { useFeatures } from 'soapbox/hooks'; -import ConsumersList from './consumers-list'; - const messages = defineMessages({ username: { id: 'login.fields.username_label', @@ -35,62 +33,52 @@ const LoginForm: React.FC = ({ isLoading, handleSubmit }) => { const passwordLabel = intl.formatMessage(messages.password); return ( -
    -
    -

    -
    - - -
    - - - + + + + - - - - } - > - + - - - - - -
    + + } + > + + - -
    -
    + + + + ); }; diff --git a/src/features/auth-login/components/login-page.tsx b/src/features/auth-login/components/login-page.tsx index 609af73a4..8817b6d39 100644 --- a/src/features/auth-login/components/login-page.tsx +++ b/src/features/auth-login/components/login-page.tsx @@ -1,13 +1,16 @@ import React, { useState } from 'react'; +import { FormattedMessage } from 'react-intl'; import { Redirect } from 'react-router-dom'; import { logIn, verifyCredentials, switchAccount } from 'soapbox/actions/auth'; import { fetchInstance } from 'soapbox/actions/instance'; import { closeModal } from 'soapbox/actions/modals'; +import { BigCard } from 'soapbox/components/big-card'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { getRedirectUrl } from 'soapbox/utils/redirect'; import { isStandalone } from 'soapbox/utils/state'; +import ConsumersList from './consumers-list'; import LoginForm from './login-form'; import OtpAuthForm from './otp-auth-form'; @@ -68,7 +71,12 @@ const LoginPage = () => { if (mfaAuthNeeded) return ; - return ; + return ( + }> + + + + ); }; export default LoginPage; diff --git a/src/features/ui/index.tsx b/src/features/ui/index.tsx index 77520d0a5..e8672be8d 100644 --- a/src/features/ui/index.tsx +++ b/src/features/ui/index.tsx @@ -134,6 +134,7 @@ import { FollowedTags, AboutPage, RegistrationPage, + LoginPage, } from './util/async-components'; import GlobalHotkeys from './util/global-hotkeys'; import { WrappedRoute } from './util/react-router-helpers'; @@ -357,6 +358,8 @@ const SwitchingColumnsArea: React.FC = ({ children }) => )} + + ); From 59a32b1a0f9954e3ba492b1a6f3e3d0fdec13674 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 Sep 2023 14:10:54 -0500 Subject: [PATCH 55/62] Refactor a bunch of components to use BigCard --- .../auth-login/components/otp-auth-form.tsx | 59 ++++--- .../components/password-reset-confirm.tsx | 47 +++--- .../auth-login/components/password-reset.tsx | 45 +++--- .../components/registration-page.tsx | 11 +- .../steps/avatar-selection-step.tsx | 112 ++++++-------- src/features/onboarding/steps/bio-step.tsx | 92 +++++------ .../steps/cover-photo-selection-step.tsx | 144 ++++++++---------- .../onboarding/steps/display-name-step.tsx | 98 +++++------- .../steps/suggested-accounts-step.tsx | 58 +++---- 9 files changed, 287 insertions(+), 379 deletions(-) diff --git a/src/features/auth-login/components/otp-auth-form.tsx b/src/features/auth-login/components/otp-auth-form.tsx index 06423e5e6..a7b55d21d 100644 --- a/src/features/auth-login/components/otp-auth-form.tsx +++ b/src/features/auth-login/components/otp-auth-form.tsx @@ -3,6 +3,7 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; import { Redirect } from 'react-router-dom'; import { otpVerify, verifyCredentials, switchAccount } from 'soapbox/actions/auth'; +import { BigCard } from 'soapbox/components/big-card'; import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; @@ -47,41 +48,33 @@ const OtpAuthForm: React.FC = ({ mfa_token }) => { if (shouldRedirect) return ; return ( -
    -
    -

    - -

    -
    + }> +
    + + + -
    - - + - - -
    -
    + + + + + ); }; diff --git a/src/features/auth-login/components/password-reset-confirm.tsx b/src/features/auth-login/components/password-reset-confirm.tsx index 78ef26fd7..c079b418d 100644 --- a/src/features/auth-login/components/password-reset-confirm.tsx +++ b/src/features/auth-login/components/password-reset-confirm.tsx @@ -3,6 +3,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { Redirect } from 'react-router-dom'; import { resetPasswordConfirm } from 'soapbox/actions/security'; +import { BigCard } from 'soapbox/components/big-card'; import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; @@ -55,33 +56,25 @@ const PasswordResetConfirm = () => { } return ( -
    -
    -

    - -

    -
    - -
    -
    - } errors={renderErrors()}> - - - - - - -
    -
    -
    + }> +
    + } errors={renderErrors()}> + + + + + + +
    +
    ); }; diff --git a/src/features/auth-login/components/password-reset.tsx b/src/features/auth-login/components/password-reset.tsx index 6cc9109d3..11d1f42f0 100644 --- a/src/features/auth-login/components/password-reset.tsx +++ b/src/features/auth-login/components/password-reset.tsx @@ -3,6 +3,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { Redirect } from 'react-router-dom'; import { resetPassword } from 'soapbox/actions/security'; +import { BigCard } from 'soapbox/components/big-card'; import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui'; import { useAppDispatch, useFeatures } from 'soapbox/hooks'; import toast from 'soapbox/toast'; @@ -36,32 +37,24 @@ const PasswordReset = () => { if (success) return ; return ( -
    -
    -

    - -

    -
    - -
    -
    - - - - - - - -
    -
    -
    + }> +
    + + + + + + + +
    +
    ); }; diff --git a/src/features/auth-login/components/registration-page.tsx b/src/features/auth-login/components/registration-page.tsx index c58633fa6..7f1e81ce5 100644 --- a/src/features/auth-login/components/registration-page.tsx +++ b/src/features/auth-login/components/registration-page.tsx @@ -1,18 +1,15 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; -import { Card, CardTitle, Stack } from 'soapbox/components/ui'; +import { BigCard } from 'soapbox/components/big-card'; import RegistrationForm from './registration-form'; const RegistrationPage: React.FC = () => { return ( - - - } /> - - - + }> + + ); }; diff --git a/src/features/onboarding/steps/avatar-selection-step.tsx b/src/features/onboarding/steps/avatar-selection-step.tsx index 55a54c514..149d5e3e3 100644 --- a/src/features/onboarding/steps/avatar-selection-step.tsx +++ b/src/features/onboarding/steps/avatar-selection-step.tsx @@ -3,7 +3,8 @@ import React from 'react'; import { defineMessages, FormattedMessage } from 'react-intl'; import { patchMe } from 'soapbox/actions/me'; -import { Avatar, Button, Card, CardBody, Icon, Spinner, Stack, Text } from 'soapbox/components/ui'; +import { BigCard } from 'soapbox/components/big-card'; +import { Avatar, Button, Icon, Spinner, Stack } from 'soapbox/components/ui'; import { useAppDispatch, useOwnAccount } from 'soapbox/hooks'; import toast from 'soapbox/toast'; import { isDefaultAvatar } from 'soapbox/utils/accounts'; @@ -64,69 +65,54 @@ const AvatarSelectionStep = ({ onNext }: { onNext: () => void }) => { }; return ( - - -
    -
    - - - - - - - - - -
    - -
    - -
    - {account && ( - - )} - - {isSubmitting && ( -
    - -
    - )} - - - - -
    - - - - - {isDisabled && ( - - )} - -
    -
    + } + subtitle={} + > + +
    + {account && ( + + )} + + {isSubmitting && ( +
    + +
    + )} + + + +
    - - + + + + + {isDisabled && ( + + )} + +
    +
    ); }; diff --git a/src/features/onboarding/steps/bio-step.tsx b/src/features/onboarding/steps/bio-step.tsx index c0a029d80..6b2862bbb 100644 --- a/src/features/onboarding/steps/bio-step.tsx +++ b/src/features/onboarding/steps/bio-step.tsx @@ -2,7 +2,8 @@ import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { patchMe } from 'soapbox/actions/me'; -import { Button, Card, CardBody, FormGroup, Stack, Text, Textarea } from 'soapbox/components/ui'; +import { BigCard } from 'soapbox/components/big-card'; +import { Button, FormGroup, Stack, Textarea } from 'soapbox/components/ui'; import { useAppDispatch, useOwnAccount } from 'soapbox/hooks'; import toast from 'soapbox/toast'; @@ -43,62 +44,49 @@ const BioStep = ({ onNext }: { onNext: () => void }) => { }; return ( - - + } + subtitle={} + > +
    -
    - - - - - - - - - -
    - - -
    - } - labelText={} - errors={errors} - > -