From cbca95463259d7bf5586a253be902773a6487d14 Mon Sep 17 00:00:00 2001 From: fusagiko / takayamaki <24884114+takayamaki@users.noreply.github.com> Date: Wed, 10 May 2023 06:08:28 +0900 Subject: [PATCH 01/98] Rename Image component to ServerHeroImage (#24894) --- app/javascript/mastodon/components/server_banner.jsx | 4 ++-- .../mastodon/components/{image.tsx => server_hero_image.tsx} | 2 +- app/javascript/mastodon/features/about/index.jsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename app/javascript/mastodon/components/{image.tsx => server_hero_image.tsx} (93%) diff --git a/app/javascript/mastodon/components/server_banner.jsx b/app/javascript/mastodon/components/server_banner.jsx index 991907a3f0..9669decc85 100644 --- a/app/javascript/mastodon/components/server_banner.jsx +++ b/app/javascript/mastodon/components/server_banner.jsx @@ -7,7 +7,7 @@ import ShortNumber from 'mastodon/components/short_number'; import Skeleton from 'mastodon/components/skeleton'; import Account from 'mastodon/containers/account_container'; import { domain } from 'mastodon/initial_state'; -import { Image } from 'mastodon/components/image'; +import { ServerHeroImage } from 'mastodon/components/server_hero_image'; import { Link } from 'react-router-dom'; const messages = defineMessages({ @@ -41,7 +41,7 @@ class ServerBanner extends React.PureComponent { {domain}, mastodon: Mastodon }} /> - +
{isLoading ? ( diff --git a/app/javascript/mastodon/components/image.tsx b/app/javascript/mastodon/components/server_hero_image.tsx similarity index 93% rename from app/javascript/mastodon/components/image.tsx rename to app/javascript/mastodon/components/server_hero_image.tsx index 490543424a..d10b8a620b 100644 --- a/app/javascript/mastodon/components/image.tsx +++ b/app/javascript/mastodon/components/server_hero_image.tsx @@ -9,7 +9,7 @@ type Props = { className?: string; }; -export const Image: React.FC = ({ +export const ServerHeroImage: React.FC = ({ src, srcSet, blurhash, diff --git a/app/javascript/mastodon/features/about/index.jsx b/app/javascript/mastodon/features/about/index.jsx index 11aff67c67..f025a9633a 100644 --- a/app/javascript/mastodon/features/about/index.jsx +++ b/app/javascript/mastodon/features/about/index.jsx @@ -11,7 +11,7 @@ import Account from 'mastodon/containers/account_container'; import Skeleton from 'mastodon/components/skeleton'; import { Icon } from 'mastodon/components/icon'; import classNames from 'classnames'; -import { Image } from 'mastodon/components/image'; +import { ServerHeroImage } from 'mastodon/components/server_hero_image'; const messages = defineMessages({ title: { id: 'column.about', defaultMessage: 'About' }, @@ -114,7 +114,7 @@ class About extends React.PureComponent {
- `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' /> + `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' />

{isLoading ? : server.get('domain')}

Mastodon }} />

From a150bb4f2ef7078ffe26f27cf9adab2d72c0bca8 Mon Sep 17 00:00:00 2001 From: fusagiko / takayamaki <24884114+takayamaki@users.noreply.github.com> Date: Wed, 10 May 2023 06:08:54 +0900 Subject: [PATCH 02/98] Add type annotation for DisplayName component (#24752) --- .../__tests__/display_name-test.jsx | 2 +- .../mastodon/components/account.jsx | 2 +- .../mastodon/components/display_name.jsx | 79 ------------ .../mastodon/components/display_name.tsx | 115 ++++++++++++++++++ app/javascript/mastodon/components/status.jsx | 2 +- .../components/moved_note.jsx | 2 +- .../components/autosuggest_account.jsx | 2 +- .../compose/components/reply_indicator.jsx | 2 +- .../directory/components/account_card.jsx | 2 +- .../components/account_authorize.jsx | 2 +- .../list_adder/components/account.jsx | 2 +- .../list_editor/components/account.jsx | 2 +- .../components/follow_request.jsx | 2 +- .../picture_in_picture/components/header.jsx | 2 +- .../report/components/status_check_box.jsx | 2 +- .../status/components/detailed_status.jsx | 2 +- .../features/ui/components/boost_modal.jsx | 2 +- 17 files changed, 130 insertions(+), 94 deletions(-) delete mode 100644 app/javascript/mastodon/components/display_name.jsx create mode 100644 app/javascript/mastodon/components/display_name.tsx diff --git a/app/javascript/mastodon/components/__tests__/display_name-test.jsx b/app/javascript/mastodon/components/__tests__/display_name-test.jsx index 0d040c4cd8..afb6c4758a 100644 --- a/app/javascript/mastodon/components/__tests__/display_name-test.jsx +++ b/app/javascript/mastodon/components/__tests__/display_name-test.jsx @@ -1,7 +1,7 @@ import React from 'react'; import renderer from 'react-test-renderer'; import { fromJS } from 'immutable'; -import DisplayName from '../display_name'; +import { DisplayName } from '../display_name'; describe('', () => { it('renders display name + account name', () => { diff --git a/app/javascript/mastodon/components/account.jsx b/app/javascript/mastodon/components/account.jsx index b0bbea7ce4..f110bce7a8 100644 --- a/app/javascript/mastodon/components/account.jsx +++ b/app/javascript/mastodon/components/account.jsx @@ -2,7 +2,7 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { Avatar } from './avatar'; -import DisplayName from './display_name'; +import { DisplayName } from './display_name'; import { IconButton } from './icon_button'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; diff --git a/app/javascript/mastodon/components/display_name.jsx b/app/javascript/mastodon/components/display_name.jsx deleted file mode 100644 index 1dd9fb1d67..0000000000 --- a/app/javascript/mastodon/components/display_name.jsx +++ /dev/null @@ -1,79 +0,0 @@ -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import { autoPlayGif } from 'mastodon/initial_state'; -import Skeleton from 'mastodon/components/skeleton'; - -export default class DisplayName extends React.PureComponent { - - static propTypes = { - account: ImmutablePropTypes.map, - others: ImmutablePropTypes.list, - localDomain: PropTypes.string, - }; - - handleMouseEnter = ({ currentTarget }) => { - if (autoPlayGif) { - return; - } - - const emojis = currentTarget.querySelectorAll('.custom-emoji'); - - for (var i = 0; i < emojis.length; i++) { - let emoji = emojis[i]; - emoji.src = emoji.getAttribute('data-original'); - } - }; - - handleMouseLeave = ({ currentTarget }) => { - if (autoPlayGif) { - return; - } - - const emojis = currentTarget.querySelectorAll('.custom-emoji'); - - for (var i = 0; i < emojis.length; i++) { - let emoji = emojis[i]; - emoji.src = emoji.getAttribute('data-static'); - } - }; - - render () { - const { others, localDomain } = this.props; - - let displayName, suffix, account; - - if (others && others.size > 1) { - displayName = others.take(2).map(a => ).reduce((prev, cur) => [prev, ', ', cur]); - - if (others.size - 2 > 0) { - suffix = `+${others.size - 2}`; - } - } else if ((others && others.size > 0) || this.props.account) { - if (others && others.size > 0) { - account = others.first(); - } else { - account = this.props.account; - } - - let acct = account.get('acct'); - - if (acct.indexOf('@') === -1 && localDomain) { - acct = `${acct}@${localDomain}`; - } - - displayName = ; - suffix = @{acct}; - } else { - displayName = ; - suffix = ; - } - - return ( - - {displayName} {suffix} - - ); - } - -} diff --git a/app/javascript/mastodon/components/display_name.tsx b/app/javascript/mastodon/components/display_name.tsx new file mode 100644 index 0000000000..0452dba794 --- /dev/null +++ b/app/javascript/mastodon/components/display_name.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import { autoPlayGif } from '..//initial_state'; +import Skeleton from './skeleton'; +import { Account } from '../../types/resources'; +import { List } from 'immutable'; + +type Props = { + account: Account; + others: List; + localDomain: string; +}; +export class DisplayName extends React.PureComponent { + handleMouseEnter: React.ReactEventHandler = ({ + currentTarget, + }) => { + if (autoPlayGif) { + return; + } + + const emojis = + currentTarget.querySelectorAll('img.custom-emoji'); + + emojis.forEach((emoji) => { + const originalSrc = emoji.getAttribute('data-original'); + if (originalSrc != null) emoji.src = originalSrc; + }); + }; + + handleMouseLeave: React.ReactEventHandler = ({ + currentTarget, + }) => { + if (autoPlayGif) { + return; + } + + const emojis = + currentTarget.querySelectorAll('img.custom-emoji'); + + emojis.forEach((emoji) => { + const staticSrc = emoji.getAttribute('data-static'); + if (staticSrc != null) emoji.src = staticSrc; + }); + }; + + render() { + const { others, localDomain } = this.props; + + let displayName: React.ReactNode, suffix: React.ReactNode, account: Account; + + if (others && others.size > 1) { + displayName = others + .take(2) + .map((a) => ( + + + + )) + .reduce((prev, cur) => [prev, ', ', cur]); + + if (others.size - 2 > 0) { + suffix = `+${others.size - 2}`; + } + } else if ((others && others.size > 0) || this.props.account) { + if (others && others.size > 0) { + account = others.first(); + } else { + account = this.props.account; + } + + let acct = account.get('acct'); + + if (acct.indexOf('@') === -1 && localDomain) { + acct = `${acct}@${localDomain}`; + } + + displayName = ( + + + + ); + suffix = @{acct}; + } else { + displayName = ( + + + + + + ); + suffix = ( + + + + ); + } + + return ( + + {displayName} {suffix} + + ); + } +} diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 3ca840b720..8cd322edab 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import { Avatar } from './avatar'; import { AvatarOverlay } from './avatar_overlay'; import { RelativeTimestamp } from './relative_timestamp'; -import DisplayName from './display_name'; +import { DisplayName } from './display_name'; import StatusContent from './status_content'; import StatusActionBar from './status_action_bar'; import AttachmentList from './attachment_list'; diff --git a/app/javascript/mastodon/features/account_timeline/components/moved_note.jsx b/app/javascript/mastodon/features/account_timeline/components/moved_note.jsx index 273583c0ad..29861612c3 100644 --- a/app/javascript/mastodon/features/account_timeline/components/moved_note.jsx +++ b/app/javascript/mastodon/features/account_timeline/components/moved_note.jsx @@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { AvatarOverlay } from '../../../components/avatar_overlay'; -import DisplayName from '../../../components/display_name'; +import { DisplayName } from '../../../components/display_name'; import { Link } from 'react-router-dom'; export default class MovedNote extends ImmutablePureComponent { diff --git a/app/javascript/mastodon/features/compose/components/autosuggest_account.jsx b/app/javascript/mastodon/features/compose/components/autosuggest_account.jsx index 2c92016e86..a635657d9f 100644 --- a/app/javascript/mastodon/features/compose/components/autosuggest_account.jsx +++ b/app/javascript/mastodon/features/compose/components/autosuggest_account.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { Avatar } from '../../../components/avatar'; -import DisplayName from '../../../components/display_name'; +import { DisplayName } from '../../../components/display_name'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.jsx b/app/javascript/mastodon/features/compose/components/reply_indicator.jsx index 68cd7b0804..b3f1b1b482 100644 --- a/app/javascript/mastodon/features/compose/components/reply_indicator.jsx +++ b/app/javascript/mastodon/features/compose/components/reply_indicator.jsx @@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { Avatar } from '../../../components/avatar'; import { IconButton } from '../../../components/icon_button'; -import DisplayName from '../../../components/display_name'; +import { DisplayName } from '../../../components/display_name'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import AttachmentList from 'mastodon/components/attachment_list'; diff --git a/app/javascript/mastodon/features/directory/components/account_card.jsx b/app/javascript/mastodon/features/directory/components/account_card.jsx index 4951427151..1ef9d64813 100644 --- a/app/javascript/mastodon/features/directory/components/account_card.jsx +++ b/app/javascript/mastodon/features/directory/components/account_card.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { makeGetAccount } from 'mastodon/selectors'; import { Avatar } from 'mastodon/components/avatar'; -import DisplayName from 'mastodon/components/display_name'; +import { DisplayName } from 'mastodon/components/display_name'; import { Link } from 'react-router-dom'; import Button from 'mastodon/components/button'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; diff --git a/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx b/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx index b370b33ecb..5d0632b0f6 100644 --- a/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx +++ b/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { Link } from 'react-router-dom'; import { Avatar } from '../../../components/avatar'; -import DisplayName from '../../../components/display_name'; +import { DisplayName } from '../../../components/display_name'; import { IconButton } from '../../../components/icon_button'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; diff --git a/app/javascript/mastodon/features/list_adder/components/account.jsx b/app/javascript/mastodon/features/list_adder/components/account.jsx index 410f1537a5..5dc384aba6 100644 --- a/app/javascript/mastodon/features/list_adder/components/account.jsx +++ b/app/javascript/mastodon/features/list_adder/components/account.jsx @@ -4,7 +4,7 @@ import { makeGetAccount } from '../../../selectors'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { Avatar } from '../../../components/avatar'; -import DisplayName from '../../../components/display_name'; +import { DisplayName } from '../../../components/display_name'; import { injectIntl } from 'react-intl'; const makeMapStateToProps = () => { diff --git a/app/javascript/mastodon/features/list_editor/components/account.jsx b/app/javascript/mastodon/features/list_editor/components/account.jsx index b46d0504a4..fc1d2d6071 100644 --- a/app/javascript/mastodon/features/list_editor/components/account.jsx +++ b/app/javascript/mastodon/features/list_editor/components/account.jsx @@ -5,7 +5,7 @@ import { makeGetAccount } from '../../../selectors'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { Avatar } from '../../../components/avatar'; -import DisplayName from '../../../components/display_name'; +import { DisplayName } from '../../../components/display_name'; import { IconButton } from '../../../components/icon_button'; import { defineMessages, injectIntl } from 'react-intl'; import { removeFromListEditor, addToListEditor } from '../../../actions/lists'; diff --git a/app/javascript/mastodon/features/notifications/components/follow_request.jsx b/app/javascript/mastodon/features/notifications/components/follow_request.jsx index 0d930f0b1f..d8b2ca1cc9 100644 --- a/app/javascript/mastodon/features/notifications/components/follow_request.jsx +++ b/app/javascript/mastodon/features/notifications/components/follow_request.jsx @@ -2,7 +2,7 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { Avatar } from 'mastodon/components/avatar'; -import DisplayName from 'mastodon/components/display_name'; +import { DisplayName } from 'mastodon/components/display_name'; import { Link } from 'react-router-dom'; import { IconButton } from 'mastodon/components/icon_button'; import { defineMessages, injectIntl } from 'react-intl'; diff --git a/app/javascript/mastodon/features/picture_in_picture/components/header.jsx b/app/javascript/mastodon/features/picture_in_picture/components/header.jsx index c6d2a103dc..c1c04da548 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/header.jsx +++ b/app/javascript/mastodon/features/picture_in_picture/components/header.jsx @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; import { IconButton } from 'mastodon/components/icon_button'; import { Link } from 'react-router-dom'; import { Avatar } from 'mastodon/components/avatar'; -import DisplayName from 'mastodon/components/display_name'; +import { DisplayName } from 'mastodon/components/display_name'; import { defineMessages, injectIntl } from 'react-intl'; const messages = defineMessages({ diff --git a/app/javascript/mastodon/features/report/components/status_check_box.jsx b/app/javascript/mastodon/features/report/components/status_check_box.jsx index 003cdc8e3a..8d6091f778 100644 --- a/app/javascript/mastodon/features/report/components/status_check_box.jsx +++ b/app/javascript/mastodon/features/report/components/status_check_box.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import StatusContent from 'mastodon/components/status_content'; import { Avatar } from 'mastodon/components/avatar'; -import DisplayName from 'mastodon/components/display_name'; +import { DisplayName } from 'mastodon/components/display_name'; import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; import Option from './option'; import MediaAttachments from 'mastodon/components/media_attachments'; diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx index 72c9021242..5019dfdb4d 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.jsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { Avatar } from '../../../components/avatar'; -import DisplayName from '../../../components/display_name'; +import { DisplayName } from '../../../components/display_name'; import StatusContent from '../../../components/status_content'; import MediaGallery from '../../../components/media_gallery'; import { Link } from 'react-router-dom'; diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.jsx b/app/javascript/mastodon/features/ui/components/boost_modal.jsx index 1f28927d60..4eeb30be09 100644 --- a/app/javascript/mastodon/features/ui/components/boost_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/boost_modal.jsx @@ -7,7 +7,7 @@ import Button from '../../../components/button'; import StatusContent from '../../../components/status_content'; import { Avatar } from '../../../components/avatar'; import { RelativeTimestamp } from '../../../components/relative_timestamp'; -import DisplayName from '../../../components/display_name'; +import { DisplayName } from '../../../components/display_name'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { Icon } from 'mastodon/components/icon'; import AttachmentList from 'mastodon/components/attachment_list'; From c9dfc55b22fbfde79b525754854d9a0f6bc466f7 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Wed, 10 May 2023 08:38:02 +0200 Subject: [PATCH 03/98] Disable RTK safety middlewares (#24936) --- app/javascript/mastodon/store/index.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/store/index.ts b/app/javascript/mastodon/store/index.ts index 6c3e963d9e..f7e1be6b7b 100644 --- a/app/javascript/mastodon/store/index.ts +++ b/app/javascript/mastodon/store/index.ts @@ -8,7 +8,21 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; export const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => - getDefaultMiddleware() + getDefaultMiddleware({ + // In development, Redux Toolkit enables 2 default middlewares to detect + // common issues with states. Unfortunately, our use of ImmutableJS for state + // triggers both, so lets disable them until our state is fully refactored + + // https://redux-toolkit.js.org/api/serializabilityMiddleware + // This checks recursively that every values in the state are serializable in JSON + // Which is not the case, as we use ImmutableJS structures, but also File objects + serializableCheck: false, + + // https://redux-toolkit.js.org/api/immutabilityMiddleware + // This checks recursively if every value in the state is immutable (ie, a JS primitive type) + // But this is not the case, as our Root State is an ImmutableJS map, which is an object + immutableCheck: false, + }) .concat( loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'], From 2f09bbdfe06355c18d05a496ef7e0d28f0a7bb58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=84=E3=81=A1=20=E3=81=B2?= Date: Wed, 10 May 2023 15:58:21 +0900 Subject: [PATCH 04/98] Rewrite `logo.tsx` as FC (#24909) --- app/javascript/mastodon/components/{logo.jsx => logo.tsx} | 6 ++---- .../mastodon/features/ui/components/navigation_panel.jsx | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) rename app/javascript/mastodon/components/{logo.jsx => logo.tsx} (75%) diff --git a/app/javascript/mastodon/components/logo.jsx b/app/javascript/mastodon/components/logo.tsx similarity index 75% rename from app/javascript/mastodon/components/logo.jsx rename to app/javascript/mastodon/components/logo.tsx index 60e8f40b23..c3f409947a 100644 --- a/app/javascript/mastodon/components/logo.jsx +++ b/app/javascript/mastodon/components/logo.tsx @@ -1,15 +1,13 @@ import React from 'react'; import logo from 'mastodon/../images/logo.svg'; -export const WordmarkLogo = () => ( +export const WordmarkLogo: React.FC = () => ( Mastodon ); -export const SymbolLogo = () => ( +export const SymbolLogo: React.FC = () => ( Mastodon ); - -export default WordmarkLogo; diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx index ee1a83cc60..b62d216ae9 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import { Link } from 'react-router-dom'; -import Logo from 'mastodon/components/logo'; +import { WordmarkLogo } from 'mastodon/components/logo'; import { timelinePreview, showTrends } from 'mastodon/initial_state'; import ColumnLink from './column_link'; import DisabledAccountBanner from './disabled_account_banner'; @@ -46,7 +46,7 @@ class NavigationPanel extends React.Component { return (
- +
From b0dd260a7a98f0a110ade6ee636435fe7efdb3a9 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Wed, 10 May 2023 03:05:32 -0400 Subject: [PATCH 05/98] Enable ESLint react/no-deprecated (#24471) --- .eslintrc.js | 1 - app/javascript/mastodon/components/autosuggest_input.jsx | 2 +- app/javascript/mastodon/components/autosuggest_textarea.jsx | 2 +- app/javascript/mastodon/components/media_gallery.jsx | 2 +- app/javascript/mastodon/components/modal_root.jsx | 2 +- .../mastodon/features/account/components/account_note.jsx | 6 +++--- app/javascript/mastodon/features/audio/index.jsx | 2 +- app/javascript/mastodon/features/blocks/index.jsx | 2 +- .../mastodon/features/bookmarked_statuses/index.jsx | 2 +- .../features/compose/components/emoji_picker_dropdown.jsx | 2 +- .../features/compose/components/privacy_dropdown.jsx | 2 +- app/javascript/mastodon/features/domain_blocks/index.jsx | 2 +- .../mastodon/features/favourited_statuses/index.jsx | 2 +- app/javascript/mastodon/features/favourites/index.jsx | 4 ++-- app/javascript/mastodon/features/follow_requests/index.jsx | 2 +- app/javascript/mastodon/features/list_timeline/index.jsx | 2 +- app/javascript/mastodon/features/lists/index.jsx | 2 +- app/javascript/mastodon/features/mutes/index.jsx | 2 +- app/javascript/mastodon/features/notifications/index.jsx | 2 +- app/javascript/mastodon/features/pinned_statuses/index.jsx | 2 +- app/javascript/mastodon/features/reblogs/index.jsx | 4 ++-- app/javascript/mastodon/features/status/components/card.jsx | 2 +- app/javascript/mastodon/features/status/index.jsx | 4 ++-- app/javascript/mastodon/features/ui/components/bundle.jsx | 4 ++-- .../mastodon/features/ui/components/columns_area.jsx | 2 +- app/javascript/mastodon/features/ui/index.jsx | 2 +- app/javascript/mastodon/features/video/index.jsx | 2 +- 27 files changed, 32 insertions(+), 33 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 9e965791b0..cbac530d6f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -104,7 +104,6 @@ module.exports = { 'react/jsx-equals-spacing': 'error', 'react/jsx-no-bind': 'error', 'react/jsx-no-target-blank': 'off', - 'react/no-deprecated': 'off', 'react/no-unknown-property': 'off', 'react/self-closing-comp': 'error', diff --git a/app/javascript/mastodon/components/autosuggest_input.jsx b/app/javascript/mastodon/components/autosuggest_input.jsx index a68e2a01b4..218faabb79 100644 --- a/app/javascript/mastodon/components/autosuggest_input.jsx +++ b/app/javascript/mastodon/components/autosuggest_input.jsx @@ -154,7 +154,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { this.input.focus(); }; - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { this.setState({ suggestionsHidden: false }); } diff --git a/app/javascript/mastodon/components/autosuggest_textarea.jsx b/app/javascript/mastodon/components/autosuggest_textarea.jsx index a627bc1ec2..50cc24b002 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.jsx +++ b/app/javascript/mastodon/components/autosuggest_textarea.jsx @@ -153,7 +153,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { this.textarea.focus(); }; - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { this.setState({ suggestionsHidden: false }); } diff --git a/app/javascript/mastodon/components/media_gallery.jsx b/app/javascript/mastodon/components/media_gallery.jsx index 54b414de20..1eeb63fa45 100644 --- a/app/javascript/mastodon/components/media_gallery.jsx +++ b/app/javascript/mastodon/components/media_gallery.jsx @@ -231,7 +231,7 @@ class MediaGallery extends React.PureComponent { window.removeEventListener('resize', this.handleResize); } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (!is(nextProps.media, this.props.media) && nextProps.visible === undefined) { this.setState({ visible: displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all' }); } else if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) { diff --git a/app/javascript/mastodon/components/modal_root.jsx b/app/javascript/mastodon/components/modal_root.jsx index c0525c2217..7671d2725d 100644 --- a/app/javascript/mastodon/components/modal_root.jsx +++ b/app/javascript/mastodon/components/modal_root.jsx @@ -57,7 +57,7 @@ export default class ModalRoot extends React.PureComponent { this.history = this.context.router ? this.context.router.history : createBrowserHistory(); } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (!!nextProps.children && !this.props.children) { this.activeElement = document.activeElement; diff --git a/app/javascript/mastodon/features/account/components/account_note.jsx b/app/javascript/mastodon/features/account/components/account_note.jsx index 5201ebd4dc..9a81b0aee2 100644 --- a/app/javascript/mastodon/features/account/components/account_note.jsx +++ b/app/javascript/mastodon/features/account/components/account_note.jsx @@ -22,7 +22,7 @@ class InlineAlert extends React.PureComponent { static TRANSITION_DELAY = 200; - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (!this.props.show && nextProps.show) { this.setState({ mountMessage: true }); } else if (this.props.show && !nextProps.show) { @@ -58,11 +58,11 @@ class AccountNote extends ImmutablePureComponent { saved: false, }; - componentWillMount () { + UNSAFE_componentWillMount () { this._reset(); } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { const accountWillChange = !is(this.props.account, nextProps.account); const newState = {}; diff --git a/app/javascript/mastodon/features/audio/index.jsx b/app/javascript/mastodon/features/audio/index.jsx index 56e913ae3a..5ed02d9378 100644 --- a/app/javascript/mastodon/features/audio/index.jsx +++ b/app/javascript/mastodon/features/audio/index.jsx @@ -136,7 +136,7 @@ class Audio extends React.PureComponent { } } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) { this.setState({ revealed: nextProps.visible }); } diff --git a/app/javascript/mastodon/features/blocks/index.jsx b/app/javascript/mastodon/features/blocks/index.jsx index 38616ba844..da28f12d79 100644 --- a/app/javascript/mastodon/features/blocks/index.jsx +++ b/app/javascript/mastodon/features/blocks/index.jsx @@ -34,7 +34,7 @@ class Blocks extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchBlocks()); } diff --git a/app/javascript/mastodon/features/bookmarked_statuses/index.jsx b/app/javascript/mastodon/features/bookmarked_statuses/index.jsx index 46fff856e4..1ffe7e768d 100644 --- a/app/javascript/mastodon/features/bookmarked_statuses/index.jsx +++ b/app/javascript/mastodon/features/bookmarked_statuses/index.jsx @@ -34,7 +34,7 @@ class Bookmarks extends ImmutablePureComponent { isLoading: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchBookmarkedStatuses()); } diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx index 4fb131b476..095e33cf7f 100644 --- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx +++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx @@ -59,7 +59,7 @@ class ModifierPickerMenu extends React.PureComponent { this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1); }; - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (nextProps.active) { this.attachListeners(); } else { diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx index ecb7accad9..bbc4b0d064 100644 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx @@ -212,7 +212,7 @@ class PrivacyDropdown extends React.PureComponent { this.props.onChange(value); }; - componentWillMount () { + UNSAFE_componentWillMount () { const { intl: { formatMessage } } = this.props; this.options = [ diff --git a/app/javascript/mastodon/features/domain_blocks/index.jsx b/app/javascript/mastodon/features/domain_blocks/index.jsx index 0c22aa2396..6a9f6e4cf5 100644 --- a/app/javascript/mastodon/features/domain_blocks/index.jsx +++ b/app/javascript/mastodon/features/domain_blocks/index.jsx @@ -34,7 +34,7 @@ class Blocks extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchDomainBlocks()); } diff --git a/app/javascript/mastodon/features/favourited_statuses/index.jsx b/app/javascript/mastodon/features/favourited_statuses/index.jsx index 2cbf002919..161297114d 100644 --- a/app/javascript/mastodon/features/favourited_statuses/index.jsx +++ b/app/javascript/mastodon/features/favourited_statuses/index.jsx @@ -34,7 +34,7 @@ class Favourites extends ImmutablePureComponent { isLoading: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchFavouritedStatuses()); } diff --git a/app/javascript/mastodon/features/favourites/index.jsx b/app/javascript/mastodon/features/favourites/index.jsx index 4a8c1deb6f..ed210dad5b 100644 --- a/app/javascript/mastodon/features/favourites/index.jsx +++ b/app/javascript/mastodon/features/favourites/index.jsx @@ -31,13 +31,13 @@ class Favourites extends ImmutablePureComponent { intl: PropTypes.object.isRequired, }; - componentWillMount () { + UNSAFE_componentWillMount () { if (!this.props.accountIds) { this.props.dispatch(fetchFavourites(this.props.params.statusId)); } } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { this.props.dispatch(fetchFavourites(nextProps.params.statusId)); } diff --git a/app/javascript/mastodon/features/follow_requests/index.jsx b/app/javascript/mastodon/features/follow_requests/index.jsx index a8875bbd31..779bc473e4 100644 --- a/app/javascript/mastodon/features/follow_requests/index.jsx +++ b/app/javascript/mastodon/features/follow_requests/index.jsx @@ -39,7 +39,7 @@ class FollowRequests extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchFollowRequests()); } diff --git a/app/javascript/mastodon/features/list_timeline/index.jsx b/app/javascript/mastodon/features/list_timeline/index.jsx index c93305341a..e1408d8efb 100644 --- a/app/javascript/mastodon/features/list_timeline/index.jsx +++ b/app/javascript/mastodon/features/list_timeline/index.jsx @@ -76,7 +76,7 @@ class ListTimeline extends React.PureComponent { this.disconnect = dispatch(connectListStream(id)); } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { const { dispatch } = this.props; const { id } = nextProps.params; diff --git a/app/javascript/mastodon/features/lists/index.jsx b/app/javascript/mastodon/features/lists/index.jsx index afd645a308..232b0c2d5a 100644 --- a/app/javascript/mastodon/features/lists/index.jsx +++ b/app/javascript/mastodon/features/lists/index.jsx @@ -42,7 +42,7 @@ class Lists extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchLists()); } diff --git a/app/javascript/mastodon/features/mutes/index.jsx b/app/javascript/mastodon/features/mutes/index.jsx index 5c05d2f700..078d8779ec 100644 --- a/app/javascript/mastodon/features/mutes/index.jsx +++ b/app/javascript/mastodon/features/mutes/index.jsx @@ -35,7 +35,7 @@ class Mutes extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchMutes()); } diff --git a/app/javascript/mastodon/features/notifications/index.jsx b/app/javascript/mastodon/features/notifications/index.jsx index 8a31c5db6c..8b77374c40 100644 --- a/app/javascript/mastodon/features/notifications/index.jsx +++ b/app/javascript/mastodon/features/notifications/index.jsx @@ -93,7 +93,7 @@ class Notifications extends React.PureComponent { trackScroll: true, }; - componentWillMount() { + UNSAFE_componentWillMount() { this.props.dispatch(mountNotifications()); } diff --git a/app/javascript/mastodon/features/pinned_statuses/index.jsx b/app/javascript/mastodon/features/pinned_statuses/index.jsx index 24a0b6d2e3..e58ce2bb89 100644 --- a/app/javascript/mastodon/features/pinned_statuses/index.jsx +++ b/app/javascript/mastodon/features/pinned_statuses/index.jsx @@ -29,7 +29,7 @@ class PinnedStatuses extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchPinnedStatuses()); } diff --git a/app/javascript/mastodon/features/reblogs/index.jsx b/app/javascript/mastodon/features/reblogs/index.jsx index fb503f40f1..757ef0dd0e 100644 --- a/app/javascript/mastodon/features/reblogs/index.jsx +++ b/app/javascript/mastodon/features/reblogs/index.jsx @@ -31,13 +31,13 @@ class Reblogs extends ImmutablePureComponent { intl: PropTypes.object.isRequired, }; - componentWillMount () { + UNSAFE_componentWillMount () { if (!this.props.accountIds) { this.props.dispatch(fetchReblogs(this.props.params.statusId)); } } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { this.props.dispatch(fetchReblogs(nextProps.params.statusId)); } diff --git a/app/javascript/mastodon/features/status/components/card.jsx b/app/javascript/mastodon/features/status/components/card.jsx index 1d5edfc173..bc02665311 100644 --- a/app/javascript/mastodon/features/status/components/card.jsx +++ b/app/javascript/mastodon/features/status/components/card.jsx @@ -66,7 +66,7 @@ export default class Card extends React.PureComponent { revealed: !this.props.sensitive, }; - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (!Immutable.is(this.props.card, nextProps.card)) { this.setState({ embedded: false, previewLoaded: false }); } diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 0c0b557487..e77d687e8d 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -207,7 +207,7 @@ class Status extends ImmutablePureComponent { loadedStatusId: undefined, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchStatus(this.props.params.statusId)); } @@ -215,7 +215,7 @@ class Status extends ImmutablePureComponent { attachFullscreenListener(this.onFullScreenChange); } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { this._scrolledIntoView = false; this.props.dispatch(fetchStatus(nextProps.params.statusId)); diff --git a/app/javascript/mastodon/features/ui/components/bundle.jsx b/app/javascript/mastodon/features/ui/components/bundle.jsx index 1b10a218b6..c1e837b16e 100644 --- a/app/javascript/mastodon/features/ui/components/bundle.jsx +++ b/app/javascript/mastodon/features/ui/components/bundle.jsx @@ -33,11 +33,11 @@ class Bundle extends React.PureComponent { forceRender: false, }; - componentWillMount() { + UNSAFE_componentWillMount() { this.load(this.props); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.fetchComponent !== this.props.fetchComponent) { this.load(nextProps); } diff --git a/app/javascript/mastodon/features/ui/components/columns_area.jsx b/app/javascript/mastodon/features/ui/components/columns_area.jsx index 1dd6e34e88..90be614b1f 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.jsx +++ b/app/javascript/mastodon/features/ui/components/columns_area.jsx @@ -76,7 +76,7 @@ export default class ColumnsArea extends ImmutablePureComponent { this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl'); } - componentWillUpdate(nextProps) { + UNSAFE_componentWillUpdate(nextProps) { if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) { this.node.removeEventListener('wheel', this.handleWheel); } diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 6dc5177b5c..26a7777324 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -123,7 +123,7 @@ class SwitchingColumnsArea extends React.PureComponent { mobile: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { if (this.props.mobile) { document.body.classList.toggle('layout-single-column', true); document.body.classList.toggle('layout-multiple-columns', false); diff --git a/app/javascript/mastodon/features/video/index.jsx b/app/javascript/mastodon/features/video/index.jsx index 8b57cf3d10..3d9d1aec6c 100644 --- a/app/javascript/mastodon/features/video/index.jsx +++ b/app/javascript/mastodon/features/video/index.jsx @@ -370,7 +370,7 @@ class Video extends React.PureComponent { } } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) { this.setState({ revealed: nextProps.visible }); } From 6a3211876c6aab15ecca36e4d41cbf6b26788ef5 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Wed, 10 May 2023 12:59:29 +0200 Subject: [PATCH 06/98] Add stricter ESLint rules for Typescript files (#24926) --- .eslintrc.js | 72 +++++++- app/javascript/mastodon/actions/app.ts | 5 +- .../mastodon/actions/pin_statuses.js | 4 +- .../mastodon/components/animated_number.tsx | 15 +- app/javascript/mastodon/components/avatar.tsx | 8 +- .../mastodon/components/avatar_overlay.tsx | 7 +- .../mastodon/components/blurhash.tsx | 9 +- .../mastodon/components/display_name.tsx | 15 +- app/javascript/mastodon/components/domain.tsx | 9 +- app/javascript/mastodon/components/gifv.tsx | 4 +- app/javascript/mastodon/components/icon.tsx | 7 +- .../mastodon/components/icon_button.tsx | 16 +- .../mastodon/components/icon_with_badge.tsx | 5 +- app/javascript/mastodon/components/logo.tsx | 1 + .../components/not_signed_in_indicator.tsx | 1 + .../mastodon/components/radio_button.tsx | 5 +- .../components/relative_timestamp.tsx | 12 +- .../mastodon/components/server_hero_image.tsx | 8 +- .../mastodon/components/verified_badge.tsx | 5 +- .../features/account_timeline/index.jsx | 3 +- .../features/ui/components/columns_area.jsx | 2 +- .../ui/components/focal_point_modal.jsx | 3 +- .../features/ui/components/upload_area.jsx | 2 +- app/javascript/mastodon/is_mobile.ts | 1 + .../mastodon/locales/locale-data/co.js | 4 +- .../mastodon/locales/locale-data/oc.js | 4 +- .../mastodon/locales/locale-data/sa.js | 7 +- .../mastodon/polyfills/base_polyfills.ts | 9 +- app/javascript/mastodon/reducers/index.ts | 71 ++++---- app/javascript/mastodon/reducers/markers.js | 4 +- .../mastodon/reducers/missed_updates.ts | 12 +- app/javascript/mastodon/store/index.ts | 8 +- .../mastodon/store/middlewares/errors.ts | 9 +- .../mastodon/store/middlewares/loading_bar.ts | 23 ++- .../mastodon/store/middlewares/sounds.ts | 23 ++- app/javascript/mastodon/uuid.ts | 7 +- app/javascript/types/resources.ts | 8 +- package.json | 1 + yarn.lock | 172 +++++++++++++++++- 39 files changed, 432 insertions(+), 149 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index cbac530d6f..1800daa55d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -55,10 +55,7 @@ module.exports = { '\\.(css|scss|json)$', ], 'import/resolver': { - node: { - paths: ['app/javascript'], - extensions: ['.js', '.jsx', '.ts', '.tsx'], - }, + typescript: {}, }, }, @@ -167,11 +164,14 @@ module.exports = { { js: 'never', jsx: 'never', + mjs: 'never', ts: 'never', tsx: 'never', }, ], + 'import/first': 'error', 'import/newline-after-import': 'error', + 'import/no-anonymous-default-export': 'error', 'import/no-extraneous-dependencies': [ 'error', { @@ -186,6 +186,9 @@ module.exports = { 'import/no-amd': 'error', 'import/no-commonjs': 'error', 'import/no-import-module-exports': 'error', + 'import/no-relative-packages': 'error', + 'import/no-self-import': 'error', + 'import/no-useless-path-segments': 'error', 'import/no-webpack-loader-syntax': 'error', 'promise/always-return': 'off', @@ -257,6 +260,7 @@ module.exports = { extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', 'plugin:react/recommended', 'plugin:react-hooks/recommended', 'plugin:jsx-a11y/recommended', @@ -267,8 +271,66 @@ module.exports = { 'plugin:prettier/recommended', ], + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: __dirname, + }, + rules: { - '@typescript-eslint/no-explicit-any': 'off', + 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], + + 'import/order': [ + 'error', + { + alphabetize: { order: 'asc' }, + 'newlines-between': 'always', + groups: [ + 'builtin', + 'external', + 'internal', + 'parent', + ['index', 'sibling'], + 'object', + ], + pathGroups: [ + // React core packages + { + pattern: '{react,react-dom,prop-types}', + group: 'builtin', + position: 'after', + }, + // I18n + { + pattern: 'react-intl', + group: 'builtin', + position: 'after', + }, + // Common React utilities + { + pattern: '{classnames,react-helmet}', + group: 'external', + position: 'before', + }, + // Immutable / Redux / data store + { + pattern: '{immutable,react-redux,react-immutable-proptypes,react-immutable-pure-component,reselect}', + group: 'external', + position: 'before', + }, + // Internal packages + { + pattern: '{mastodon/**}', + group: 'internal', + position: 'after', + }, + ], + pathGroupsExcludedImportTypes: [], + }, + ], + + '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'], + '@typescript-eslint/consistent-type-exports': 'error', + '@typescript-eslint/consistent-type-imports': 'error', 'jsdoc/require-jsdoc': 'off', diff --git a/app/javascript/mastodon/actions/app.ts b/app/javascript/mastodon/actions/app.ts index 50fd317a65..be1a5cced2 100644 --- a/app/javascript/mastodon/actions/app.ts +++ b/app/javascript/mastodon/actions/app.ts @@ -1,11 +1,12 @@ import { createAction } from '@reduxjs/toolkit'; + import type { LayoutType } from '../is_mobile'; export const focusApp = createAction('APP_FOCUS'); export const unfocusApp = createAction('APP_UNFOCUS'); -type ChangeLayoutPayload = { +interface ChangeLayoutPayload { layout: LayoutType; -}; +} export const changeLayout = createAction('APP_LAYOUT_CHANGE'); diff --git a/app/javascript/mastodon/actions/pin_statuses.js b/app/javascript/mastodon/actions/pin_statuses.js index e2de98ca9d..1e4bd37bf0 100644 --- a/app/javascript/mastodon/actions/pin_statuses.js +++ b/app/javascript/mastodon/actions/pin_statuses.js @@ -1,12 +1,12 @@ import api from '../api'; import { importFetchedStatuses } from './importer'; +import { me } from '../initial_state'; + export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST'; export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS'; export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL'; -import { me } from '../initial_state'; - export function fetchPinnedStatuses() { return (dispatch, getState) => { dispatch(fetchPinnedStatusesRequest()); diff --git a/app/javascript/mastodon/components/animated_number.tsx b/app/javascript/mastodon/components/animated_number.tsx index f6c77d35ff..b6b073161b 100644 --- a/app/javascript/mastodon/components/animated_number.tsx +++ b/app/javascript/mastodon/components/animated_number.tsx @@ -1,8 +1,11 @@ import React, { useCallback, useState } from 'react'; -import ShortNumber from './short_number'; + import { TransitionMotion, spring } from 'react-motion'; + import { reduceMotion } from '../initial_state'; +import ShortNumber from './short_number'; + const obfuscatedCount = (count: number) => { if (count < 0) { return 0; @@ -13,10 +16,10 @@ const obfuscatedCount = (count: number) => { } }; -type Props = { +interface Props { value: number; obfuscate?: boolean; -}; +} export const AnimatedNumber: React.FC = ({ value, obfuscate }) => { const [previousValue, setPreviousValue] = useState(value); const [direction, setDirection] = useState<1 | -1>(1); @@ -64,7 +67,11 @@ export const AnimatedNumber: React.FC = ({ value, obfuscate }) => { transform: `translateY(${style.y * 100}%)`, }} > - {obfuscate ? obfuscatedCount(data) : } + {obfuscate ? ( + obfuscatedCount(data as number) + ) : ( + + )} ))} diff --git a/app/javascript/mastodon/components/avatar.tsx b/app/javascript/mastodon/components/avatar.tsx index 8be94d3f53..2b46b05d65 100644 --- a/app/javascript/mastodon/components/avatar.tsx +++ b/app/javascript/mastodon/components/avatar.tsx @@ -1,16 +1,18 @@ import * as React from 'react'; + import classNames from 'classnames'; -import { autoPlayGif } from '../initial_state'; + import { useHovering } from '../../hooks/useHovering'; import type { Account } from '../../types/resources'; +import { autoPlayGif } from '../initial_state'; -type Props = { +interface Props { account: Account; size: number; style?: React.CSSProperties; inline?: boolean; animate?: boolean; -}; +} export const Avatar: React.FC = ({ account, diff --git a/app/javascript/mastodon/components/avatar_overlay.tsx b/app/javascript/mastodon/components/avatar_overlay.tsx index 1dbd533230..d1d1581268 100644 --- a/app/javascript/mastodon/components/avatar_overlay.tsx +++ b/app/javascript/mastodon/components/avatar_overlay.tsx @@ -1,15 +1,16 @@ import React from 'react'; -import type { Account } from '../../types/resources'; + import { useHovering } from '../../hooks/useHovering'; +import type { Account } from '../../types/resources'; import { autoPlayGif } from '../initial_state'; -type Props = { +interface Props { account: Account; friend: Account; size?: number; baseSize?: number; overlaySize?: number; -}; +} export const AvatarOverlay: React.FC = ({ account, diff --git a/app/javascript/mastodon/components/blurhash.tsx b/app/javascript/mastodon/components/blurhash.tsx index 7005136765..1550d0b7a5 100644 --- a/app/javascript/mastodon/components/blurhash.tsx +++ b/app/javascript/mastodon/components/blurhash.tsx @@ -1,14 +1,14 @@ -import { decode } from 'blurhash'; import React, { useRef, useEffect } from 'react'; -type Props = { +import { decode } from 'blurhash'; + +interface Props extends React.HTMLAttributes { hash: string; width?: number; height?: number; dummy?: boolean; // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched children?: never; - [key: string]: any; -}; +} const Blurhash: React.FC = ({ hash, width = 32, @@ -21,6 +21,7 @@ const Blurhash: React.FC = ({ useEffect(() => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const canvas = canvasRef.current!; + // eslint-disable-next-line no-self-assign canvas.width = canvas.width; // resets canvas diff --git a/app/javascript/mastodon/components/display_name.tsx b/app/javascript/mastodon/components/display_name.tsx index 0452dba794..d3308ff216 100644 --- a/app/javascript/mastodon/components/display_name.tsx +++ b/app/javascript/mastodon/components/display_name.tsx @@ -1,14 +1,17 @@ import React from 'react'; -import { autoPlayGif } from '..//initial_state'; -import Skeleton from './skeleton'; -import { Account } from '../../types/resources'; -import { List } from 'immutable'; -type Props = { +import type { List } from 'immutable'; + +import type { Account } from '../../types/resources'; +import { autoPlayGif } from '../initial_state'; + +import Skeleton from './skeleton'; + +interface Props { account: Account; others: List; localDomain: string; -}; +} export class DisplayName extends React.PureComponent { handleMouseEnter: React.ReactEventHandler = ({ currentTarget, diff --git a/app/javascript/mastodon/components/domain.tsx b/app/javascript/mastodon/components/domain.tsx index af0fec35af..9e8e04b65c 100644 --- a/app/javascript/mastodon/components/domain.tsx +++ b/app/javascript/mastodon/components/domain.tsx @@ -1,6 +1,9 @@ import React, { useCallback } from 'react'; + +import type { InjectedIntl } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; + import { IconButton } from './icon_button'; -import { InjectedIntl, defineMessages, injectIntl } from 'react-intl'; const messages = defineMessages({ unblockDomain: { @@ -9,11 +12,11 @@ const messages = defineMessages({ }, }); -type Props = { +interface Props { domain: string; onUnblockDomain: (domain: string) => void; intl: InjectedIntl; -}; +} const _Domain: React.FC = ({ domain, onUnblockDomain, intl }) => { const handleDomainUnblock = useCallback(() => { onUnblockDomain(domain); diff --git a/app/javascript/mastodon/components/gifv.tsx b/app/javascript/mastodon/components/gifv.tsx index 72914ba741..c606a29048 100644 --- a/app/javascript/mastodon/components/gifv.tsx +++ b/app/javascript/mastodon/components/gifv.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useState } from 'react'; -type Props = { +interface Props { src: string; key: string; alt?: string; @@ -8,7 +8,7 @@ type Props = { width: number; height: number; onClick?: () => void; -}; +} export const GIFV: React.FC = ({ src, diff --git a/app/javascript/mastodon/components/icon.tsx b/app/javascript/mastodon/components/icon.tsx index 4eb948dc76..6bd15da6ac 100644 --- a/app/javascript/mastodon/components/icon.tsx +++ b/app/javascript/mastodon/components/icon.tsx @@ -1,13 +1,14 @@ import React from 'react'; + import classNames from 'classnames'; -type Props = { +interface Props extends React.HTMLAttributes { id: string; className?: string; fixedWidth?: boolean; children?: never; - [key: string]: any; -}; +} + export const Icon: React.FC = ({ id, className, diff --git a/app/javascript/mastodon/components/icon_button.tsx b/app/javascript/mastodon/components/icon_button.tsx index 1786414009..c995ed0ebe 100644 --- a/app/javascript/mastodon/components/icon_button.tsx +++ b/app/javascript/mastodon/components/icon_button.tsx @@ -1,9 +1,11 @@ import React from 'react'; -import classNames from 'classnames'; -import { Icon } from './icon'; -import { AnimatedNumber } from './animated_number'; -type Props = { +import classNames from 'classnames'; + +import { AnimatedNumber } from './animated_number'; +import { Icon } from './icon'; + +interface Props { className?: string; title: string; icon: string; @@ -25,11 +27,11 @@ type Props = { obfuscateCount?: boolean; href?: string; ariaHidden: boolean; -}; -type States = { +} +interface States { activate: boolean; deactivate: boolean; -}; +} export class IconButton extends React.PureComponent { static defaultProps = { size: 18, diff --git a/app/javascript/mastodon/components/icon_with_badge.tsx b/app/javascript/mastodon/components/icon_with_badge.tsx index bf86814c03..e427b7172b 100644 --- a/app/javascript/mastodon/components/icon_with_badge.tsx +++ b/app/javascript/mastodon/components/icon_with_badge.tsx @@ -1,14 +1,15 @@ import React from 'react'; + import { Icon } from './icon'; const formatNumber = (num: number): number | string => (num > 40 ? '40+' : num); -type Props = { +interface Props { id: string; count: number; issueBadge: boolean; className: string; -}; +} export const IconWithBadge: React.FC = ({ id, count, diff --git a/app/javascript/mastodon/components/logo.tsx b/app/javascript/mastodon/components/logo.tsx index c3f409947a..6594ef1fd4 100644 --- a/app/javascript/mastodon/components/logo.tsx +++ b/app/javascript/mastodon/components/logo.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import logo from 'mastodon/../images/logo.svg'; export const WordmarkLogo: React.FC = () => ( diff --git a/app/javascript/mastodon/components/not_signed_in_indicator.tsx b/app/javascript/mastodon/components/not_signed_in_indicator.tsx index 53945d6a7a..7a71f6539d 100644 --- a/app/javascript/mastodon/components/not_signed_in_indicator.tsx +++ b/app/javascript/mastodon/components/not_signed_in_indicator.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { FormattedMessage } from 'react-intl'; export const NotSignedInIndicator: React.FC = () => ( diff --git a/app/javascript/mastodon/components/radio_button.tsx b/app/javascript/mastodon/components/radio_button.tsx index 829f471747..67acb09f42 100644 --- a/app/javascript/mastodon/components/radio_button.tsx +++ b/app/javascript/mastodon/components/radio_button.tsx @@ -1,13 +1,14 @@ import React from 'react'; + import classNames from 'classnames'; -type Props = { +interface Props { value: string; checked: boolean; name: string; onChange: (event: React.ChangeEvent) => void; label: React.ReactNode; -}; +} export const RadioButton: React.FC = ({ name, diff --git a/app/javascript/mastodon/components/relative_timestamp.tsx b/app/javascript/mastodon/components/relative_timestamp.tsx index 65d9d27cb2..e0e0d4bb53 100644 --- a/app/javascript/mastodon/components/relative_timestamp.tsx +++ b/app/javascript/mastodon/components/relative_timestamp.tsx @@ -1,5 +1,7 @@ import React from 'react'; -import { injectIntl, defineMessages, InjectedIntl } from 'react-intl'; + +import type { InjectedIntl } from 'react-intl'; +import { injectIntl, defineMessages } from 'react-intl'; const messages = defineMessages({ today: { id: 'relative_time.today', defaultMessage: 'today' }, @@ -187,16 +189,16 @@ const timeRemainingString = ( return relativeTime; }; -type Props = { +interface Props { intl: InjectedIntl; timestamp: string; year: number; futureDate?: boolean; short?: boolean; -}; -type States = { +} +interface States { now: number; -}; +} class RelativeTimestamp extends React.Component { state = { now: this.props.intl.now(), diff --git a/app/javascript/mastodon/components/server_hero_image.tsx b/app/javascript/mastodon/components/server_hero_image.tsx index d10b8a620b..973d1d1b37 100644 --- a/app/javascript/mastodon/components/server_hero_image.tsx +++ b/app/javascript/mastodon/components/server_hero_image.tsx @@ -1,13 +1,15 @@ import React, { useCallback, useState } from 'react'; -import { Blurhash } from './blurhash'; + import classNames from 'classnames'; -type Props = { +import { Blurhash } from './blurhash'; + +interface Props { src: string; srcSet?: string; blurhash?: string; className?: string; -}; +} export const ServerHeroImage: React.FC = ({ src, diff --git a/app/javascript/mastodon/components/verified_badge.tsx b/app/javascript/mastodon/components/verified_badge.tsx index da3cab9fe5..4c5de31203 100644 --- a/app/javascript/mastodon/components/verified_badge.tsx +++ b/app/javascript/mastodon/components/verified_badge.tsx @@ -1,9 +1,10 @@ import React from 'react'; + import { Icon } from './icon'; -type Props = { +interface Props { link: string; -}; +} export const VerifiedBadge: React.FC = ({ link }) => ( diff --git a/app/javascript/mastodon/features/account_timeline/index.jsx b/app/javascript/mastodon/features/account_timeline/index.jsx index bc66f128d9..2a05305268 100644 --- a/app/javascript/mastodon/features/account_timeline/index.jsx +++ b/app/javascript/mastodon/features/account_timeline/index.jsx @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { lookupAccount, fetchAccount } from '../../actions/accounts'; -import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines'; +import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from '../../actions/timelines'; import StatusList from '../../components/status_list'; import LoadingIndicator from '../../components/loading_indicator'; import Column from '../ui/components/column'; @@ -14,7 +14,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { FormattedMessage } from 'react-intl'; import TimelineHint from 'mastodon/components/timeline_hint'; import { me } from 'mastodon/initial_state'; -import { connectTimeline, disconnectTimeline } from 'mastodon/actions/timelines'; import LimitedAccountHint from './components/limited_account_hint'; import { getAccountHidden } from 'mastodon/selectors'; import { fetchFeaturedTags } from '../../actions/featured_tags'; diff --git a/app/javascript/mastodon/features/ui/components/columns_area.jsx b/app/javascript/mastodon/features/ui/components/columns_area.jsx index 90be614b1f..86911efa38 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.jsx +++ b/app/javascript/mastodon/features/ui/components/columns_area.jsx @@ -18,7 +18,7 @@ import { BookmarkedStatuses, ListTimeline, Directory, -} from '../../ui/util/async-components'; +} from '../util/async-components'; import ComposePanel from './compose_panel'; import NavigationPanel from './navigation_panel'; import { supportsPassiveEvents } from 'detect-passive-events'; diff --git a/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx index eda550b04d..d6f15e276b 100644 --- a/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/focal_point_modal.jsx @@ -5,11 +5,10 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; import classNames from 'classnames'; import { changeUploadCompose, uploadThumbnail, onChangeMediaDescription, onChangeMediaFocus } from '../../../actions/compose'; -import { getPointerPosition } from '../../video'; +import Video, { getPointerPosition } from '../../video'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { IconButton } from 'mastodon/components/icon_button'; import Button from 'mastodon/components/button'; -import Video from 'mastodon/features/video'; import Audio from 'mastodon/features/audio'; import Textarea from 'react-textarea-autosize'; import UploadProgress from 'mastodon/features/compose/components/upload_progress'; diff --git a/app/javascript/mastodon/features/ui/components/upload_area.jsx b/app/javascript/mastodon/features/ui/components/upload_area.jsx index 035fe7a26f..cec4cf5b1e 100644 --- a/app/javascript/mastodon/features/ui/components/upload_area.jsx +++ b/app/javascript/mastodon/features/ui/components/upload_area.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Motion from '../../ui/util/optional_motion'; +import Motion from '../util/optional_motion'; import spring from 'react-motion/lib/spring'; import { FormattedMessage } from 'react-intl'; diff --git a/app/javascript/mastodon/is_mobile.ts b/app/javascript/mastodon/is_mobile.ts index b2918eb4bf..36cde21332 100644 --- a/app/javascript/mastodon/is_mobile.ts +++ b/app/javascript/mastodon/is_mobile.ts @@ -1,4 +1,5 @@ import { supportsPassiveEvents } from 'detect-passive-events'; + import { forceSingleColumn } from './initial_state'; const LAYOUT_BREAKPOINT = 630; diff --git a/app/javascript/mastodon/locales/locale-data/co.js b/app/javascript/mastodon/locales/locale-data/co.js index 2b071ecbd4..dff8a54dac 100644 --- a/app/javascript/mastodon/locales/locale-data/co.js +++ b/app/javascript/mastodon/locales/locale-data/co.js @@ -2,7 +2,7 @@ /*eslint no-nested-ternary: "off"*/ /*eslint quotes: "off"*/ -export default [{ +const rules = [{ locale: "co", pluralRuleFunction: function (e, a) { return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other"; @@ -106,3 +106,5 @@ export default [{ }, }, }]; + +export default rules; diff --git a/app/javascript/mastodon/locales/locale-data/oc.js b/app/javascript/mastodon/locales/locale-data/oc.js index d4adc42ebb..6ab306b8cf 100644 --- a/app/javascript/mastodon/locales/locale-data/oc.js +++ b/app/javascript/mastodon/locales/locale-data/oc.js @@ -2,7 +2,7 @@ /*eslint no-nested-ternary: "off"*/ /*eslint quotes: "off"*/ -export default [{ +const rules = [{ locale: "oc", pluralRuleFunction: function (e, a) { return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other"; @@ -106,3 +106,5 @@ export default [{ }, }, }]; + +export default rules; diff --git a/app/javascript/mastodon/locales/locale-data/sa.js b/app/javascript/mastodon/locales/locale-data/sa.js index 946dfde0fb..65e09e97f2 100644 --- a/app/javascript/mastodon/locales/locale-data/sa.js +++ b/app/javascript/mastodon/locales/locale-data/sa.js @@ -2,9 +2,8 @@ /*eslint no-nested-ternary: "off"*/ /*eslint quotes: "off"*/ /*eslint comma-dangle: "off"*/ -/*eslint semi: "off"*/ -export default [ +const rules = [ { locale: "sa", fields: { @@ -94,4 +93,6 @@ export default [ } } } -] +]; + +export default rules; diff --git a/app/javascript/mastodon/polyfills/base_polyfills.ts b/app/javascript/mastodon/polyfills/base_polyfills.ts index 64211c11e9..e008d8f025 100644 --- a/app/javascript/mastodon/polyfills/base_polyfills.ts +++ b/app/javascript/mastodon/polyfills/base_polyfills.ts @@ -10,8 +10,13 @@ if (!HTMLCanvasElement.prototype.toBlob) { const BASE64_MARKER = ';base64,'; Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { - value(callback: BlobCallback, type = 'image/png', quality: any) { - const dataURL = this.toDataURL(type, quality); + value: function ( + this: HTMLCanvasElement, + callback: BlobCallback, + type = 'image/png', + quality: unknown + ) { + const dataURL: string = this.toDataURL(type, quality); let data; if (dataURL.indexOf(BASE64_MARKER) >= 0) { diff --git a/app/javascript/mastodon/reducers/index.ts b/app/javascript/mastodon/reducers/index.ts index 518d8cd792..29c9abe68b 100644 --- a/app/javascript/mastodon/reducers/index.ts +++ b/app/javascript/mastodon/reducers/index.ts @@ -1,46 +1,47 @@ -import { combineReducers } from 'redux-immutable'; -import dropdown_menu from './dropdown_menu'; -import timelines from './timelines'; -import meta from './meta'; -import alerts from './alerts'; import { loadingBarReducer } from 'react-redux-loading-bar'; -import modal from './modal'; -import user_lists from './user_lists'; -import domain_lists from './domain_lists'; +import { combineReducers } from 'redux-immutable'; + import accounts from './accounts'; import accounts_counters from './accounts_counters'; -import statuses from './statuses'; -import relationships from './relationships'; -import settings from './settings'; -import push_notifications from './push_notifications'; -import status_lists from './status_lists'; -import mutes from './mutes'; +import accounts_map from './accounts_map'; +import alerts from './alerts'; +import announcements from './announcements'; import blocks from './blocks'; import boosts from './boosts'; -import server from './server'; -import contexts from './contexts'; import compose from './compose'; -import search from './search'; -import media_attachments from './media_attachments'; -import notifications from './notifications'; -import height_cache from './height_cache'; -import custom_emojis from './custom_emojis'; -import lists from './lists'; -import listEditor from './list_editor'; -import listAdder from './list_adder'; -import filters from './filters'; +import contexts from './contexts'; import conversations from './conversations'; -import suggestions from './suggestions'; -import polls from './polls'; -import trends from './trends'; -import { missedUpdatesReducer } from './missed_updates'; -import announcements from './announcements'; -import markers from './markers'; -import picture_in_picture from './picture_in_picture'; -import accounts_map from './accounts_map'; -import history from './history'; -import tags from './tags'; +import custom_emojis from './custom_emojis'; +import domain_lists from './domain_lists'; +import dropdown_menu from './dropdown_menu'; +import filters from './filters'; import followed_tags from './followed_tags'; +import height_cache from './height_cache'; +import history from './history'; +import listAdder from './list_adder'; +import listEditor from './list_editor'; +import lists from './lists'; +import markers from './markers'; +import media_attachments from './media_attachments'; +import meta from './meta'; +import { missedUpdatesReducer } from './missed_updates'; +import modal from './modal'; +import mutes from './mutes'; +import notifications from './notifications'; +import picture_in_picture from './picture_in_picture'; +import polls from './polls'; +import push_notifications from './push_notifications'; +import relationships from './relationships'; +import search from './search'; +import server from './server'; +import settings from './settings'; +import status_lists from './status_lists'; +import statuses from './statuses'; +import suggestions from './suggestions'; +import tags from './tags'; +import timelines from './timelines'; +import trends from './trends'; +import user_lists from './user_lists'; const reducers = { announcements, diff --git a/app/javascript/mastodon/reducers/markers.js b/app/javascript/mastodon/reducers/markers.js index e3d1b1936b..3e8b1780a7 100644 --- a/app/javascript/mastodon/reducers/markers.js +++ b/app/javascript/mastodon/reducers/markers.js @@ -2,13 +2,13 @@ import { MARKERS_SUBMIT_SUCCESS, } from '../actions/markers'; +import { Map as ImmutableMap } from 'immutable'; + const initialState = ImmutableMap({ home: '0', notifications: '0', }); -import { Map as ImmutableMap } from 'immutable'; - export default function markers(state = initialState, action) { switch(action.type) { case MARKERS_SUBMIT_SUCCESS: diff --git a/app/javascript/mastodon/reducers/missed_updates.ts b/app/javascript/mastodon/reducers/missed_updates.ts index 9c1a5cbd26..a587fcb036 100644 --- a/app/javascript/mastodon/reducers/missed_updates.ts +++ b/app/javascript/mastodon/reducers/missed_updates.ts @@ -1,12 +1,14 @@ import { Record } from 'immutable'; -import type { Action } from 'redux'; -import { NOTIFICATIONS_UPDATE } from '../actions/notifications'; -import { focusApp, unfocusApp } from '../actions/app'; -type MissedUpdatesState = { +import type { Action } from 'redux'; + +import { focusApp, unfocusApp } from '../actions/app'; +import { NOTIFICATIONS_UPDATE } from '../actions/notifications'; + +interface MissedUpdatesState { focused: boolean; unread: number; -}; +} const initialState = Record({ focused: true, unread: 0, diff --git a/app/javascript/mastodon/store/index.ts b/app/javascript/mastodon/store/index.ts index f7e1be6b7b..1ef9cb8184 100644 --- a/app/javascript/mastodon/store/index.ts +++ b/app/javascript/mastodon/store/index.ts @@ -1,9 +1,13 @@ +import type { TypedUseSelectorHook } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; + import { configureStore } from '@reduxjs/toolkit'; + import { rootReducer } from '../reducers'; -import { loadingBarMiddleware } from './middlewares/loading_bar'; + import { errorsMiddleware } from './middlewares/errors'; +import { loadingBarMiddleware } from './middlewares/loading_bar'; import { soundsMiddleware } from './middlewares/sounds'; -import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; export const store = configureStore({ reducer: rootReducer, diff --git a/app/javascript/mastodon/store/middlewares/errors.ts b/app/javascript/mastodon/store/middlewares/errors.ts index a5e99d04e6..4e720bfed4 100644 --- a/app/javascript/mastodon/store/middlewares/errors.ts +++ b/app/javascript/mastodon/store/middlewares/errors.ts @@ -1,17 +1,18 @@ -import { Middleware } from 'redux'; +import type { AnyAction, Middleware } from 'redux'; + +import type { RootState } from '..'; import { showAlertForError } from '../../actions/alerts'; -import { RootState } from '..'; const defaultFailSuffix = 'FAIL'; export const errorsMiddleware: Middleware, RootState> = ({ dispatch }) => (next) => - (action) => { + (action: AnyAction & { skipAlert?: boolean; skipNotFound?: boolean }) => { if (action.type && !action.skipAlert) { const isFail = new RegExp(`${defaultFailSuffix}$`, 'g'); - if (action.type.match(isFail)) { + if (typeof action.type === 'string' && action.type.match(isFail)) { dispatch(showAlertForError(action.error, action.skipNotFound)); } } diff --git a/app/javascript/mastodon/store/middlewares/loading_bar.ts b/app/javascript/mastodon/store/middlewares/loading_bar.ts index 183c0cf9d6..0f997fd349 100644 --- a/app/javascript/mastodon/store/middlewares/loading_bar.ts +++ b/app/javascript/mastodon/store/middlewares/loading_bar.ts @@ -1,6 +1,7 @@ import { showLoading, hideLoading } from 'react-redux-loading-bar'; -import { Middleware } from 'redux'; -import { RootState } from '..'; +import type { AnyAction, Middleware } from 'redux'; + +import type { RootState } from '..'; interface Config { promiseTypeSuffixes?: string[]; @@ -19,7 +20,7 @@ export const loadingBarMiddleware = ( return ({ dispatch }) => (next) => - (action) => { + (action: AnyAction) => { if (action.type && !action.skipLoading) { const [PENDING, FULFILLED, REJECTED] = promiseTypeSuffixes; @@ -27,13 +28,15 @@ export const loadingBarMiddleware = ( const isFulfilled = new RegExp(`${FULFILLED}$`, 'g'); const isRejected = new RegExp(`${REJECTED}$`, 'g'); - if (action.type.match(isPending)) { - dispatch(showLoading()); - } else if ( - action.type.match(isFulfilled) || - action.type.match(isRejected) - ) { - dispatch(hideLoading()); + if (typeof action.type === 'string') { + if (action.type.match(isPending)) { + dispatch(showLoading()); + } else if ( + action.type.match(isFulfilled) || + action.type.match(isRejected) + ) { + dispatch(hideLoading()); + } } } diff --git a/app/javascript/mastodon/store/middlewares/sounds.ts b/app/javascript/mastodon/store/middlewares/sounds.ts index e7c87df7ec..6005d3649e 100644 --- a/app/javascript/mastodon/store/middlewares/sounds.ts +++ b/app/javascript/mastodon/store/middlewares/sounds.ts @@ -1,5 +1,6 @@ -import { Middleware, AnyAction } from 'redux'; -import { RootState } from '..'; +import type { Middleware, AnyAction } from 'redux'; + +import type { RootState } from '..'; interface AudioSource { src: string; @@ -27,7 +28,7 @@ const play = (audio: HTMLAudioElement) => { } } - audio.play(); + void audio.play(); }; export const soundsMiddleware = (): Middleware< @@ -47,13 +48,15 @@ export const soundsMiddleware = (): Middleware< ]), }; - return () => (next) => (action: AnyAction) => { - const sound = action?.meta?.sound; + return () => + (next) => + (action: AnyAction & { meta?: { sound?: string } }) => { + const sound = action?.meta?.sound; - if (sound && soundCache[sound]) { - play(soundCache[sound]); - } + if (sound && soundCache[sound]) { + play(soundCache[sound]); + } - return next(action); - }; + return next(action); + }; }; diff --git a/app/javascript/mastodon/uuid.ts b/app/javascript/mastodon/uuid.ts index 6cadbd6bb0..0b4d55beb6 100644 --- a/app/javascript/mastodon/uuid.ts +++ b/app/javascript/mastodon/uuid.ts @@ -1,8 +1,9 @@ export function uuid(a?: string): string { return a ? ( - (a as any as number) ^ - ((Math.random() * 16) >> ((a as any as number) / 4)) + (a as unknown as number) ^ + ((Math.random() * 16) >> ((a as unknown as number) / 4)) ).toString(16) - : ('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid); + : // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + ('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid); } diff --git a/app/javascript/types/resources.ts b/app/javascript/types/resources.ts index 0906504150..63ec2993bb 100644 --- a/app/javascript/types/resources.ts +++ b/app/javascript/types/resources.ts @@ -12,7 +12,7 @@ type AccountField = Record<{ verified_at: string | null; }>; -type AccountApiResponseValues = { +interface AccountApiResponseValues { acct: string; avatar: string; avatar_static: string; @@ -34,7 +34,7 @@ type AccountApiResponseValues = { statuses_count: number; url: string; username: string; -}; +} type NormalizedAccountField = Record<{ name_emojified: string; @@ -42,12 +42,12 @@ type NormalizedAccountField = Record<{ value_plain: string; }>; -type NormalizedAccountValues = { +interface NormalizedAccountValues { display_name_html: string; fields: NormalizedAccountField[]; note_emojified: string; note_plain: string; -}; +} export type Account = Record< AccountApiResponseValues & NormalizedAccountValues diff --git a/package.json b/package.json index b326dfd45a..7632388e5f 100644 --- a/package.json +++ b/package.json @@ -181,6 +181,7 @@ "babel-jest": "^29.5.0", "eslint": "^8.39.0", "eslint-config-prettier": "^8.8.0", + "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-formatjs": "^4.10.1", "eslint-plugin-import": "~2.27.5", "eslint-plugin-jsdoc": "^43.1.1", diff --git a/yarn.lock b/yarn.lock index cd0abca76a..ed7ab9e579 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1678,6 +1678,18 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@pkgr/utils@^2.3.1": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@pkgr/utils/-/utils-2.4.0.tgz#b6373d2504aedaf2fc7cdf2d13ab1f48fa5f12d5" + integrity sha512-2OCURAmRtdlL8iUDTypMrrxfwe8frXTeXaxGsVOaYtc/wrUyk8Z/0OBetM7cdlsy7ZFWlMX72VogKeh+A4Xcjw== + dependencies: + cross-spawn "^7.0.3" + fast-glob "^3.2.12" + is-glob "^4.0.3" + open "^9.1.0" + picocolors "^1.0.0" + tslib "^2.5.0" + "@polka/url@^1.0.0-next.9": version "1.0.0-next.11" resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.11.tgz#aeb16f50649a91af79dbe36574b66d0f9e4d9f71" @@ -3374,6 +3386,11 @@ batch@0.6.1: resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= +big-integer@^1.6.44: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -3456,6 +3473,13 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +bplist-parser@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" + integrity sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw== + dependencies: + big-integer "^1.6.44" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3631,6 +3655,13 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= +bundle-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" + integrity sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw== + dependencies: + run-applescript "^5.0.0" + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -4531,6 +4562,24 @@ deepmerge@^4.0, deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +default-browser-id@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-3.0.0.tgz#bee7bbbef1f4e75d31f98f4d3f1556a14cea790c" + integrity sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA== + dependencies: + bplist-parser "^0.2.0" + untildify "^4.0.0" + +default-browser@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-4.0.0.tgz#53c9894f8810bf86696de117a6ce9085a3cbc7da" + integrity sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA== + dependencies: + bundle-name "^3.0.0" + default-browser-id "^3.0.0" + execa "^7.1.1" + titleize "^3.0.0" + default-gateway@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" @@ -4539,6 +4588,11 @@ default-gateway@^4.2.0: execa "^1.0.0" ip-regex "^2.1.0" +define-lazy-prop@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" + integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== + define-properties@^1.1.3, define-properties@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" @@ -4877,6 +4931,14 @@ enhanced-resolve@^4.1.1, enhanced-resolve@^4.5.0: memory-fs "^0.5.0" tapable "^1.0.0" +enhanced-resolve@^5.12.0: + version "5.13.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz#26d1ecc448c02de997133217b5c1053f34a0a275" + integrity sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + entities@^4.2.0, entities@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" @@ -5015,6 +5077,20 @@ eslint-import-resolver-node@^0.3.7: is-core-module "^2.11.0" resolve "^1.22.1" +eslint-import-resolver-typescript@^3.5.5: + version "3.5.5" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.5.tgz#0a9034ae7ed94b254a360fbea89187b60ea7456d" + integrity sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw== + dependencies: + debug "^4.3.4" + enhanced-resolve "^5.12.0" + eslint-module-utils "^2.7.4" + get-tsconfig "^4.5.0" + globby "^13.1.3" + is-core-module "^2.11.0" + is-glob "^4.0.3" + synckit "^0.8.5" + eslint-module-utils@^2.7.4: version "2.7.4" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974" @@ -5320,7 +5396,7 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@^7.0.0: +execa@^7.0.0, execa@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-7.1.1.tgz#3eb3c83d239488e7b409d48e8813b76bb55c9c43" integrity sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q== @@ -5447,7 +5523,7 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^3.2.12, fast-glob@^3.2.9: +fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.9: version "3.2.12" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== @@ -5814,6 +5890,11 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" +get-tsconfig@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.5.0.tgz#6d52d1c7b299bd3ee9cd7638561653399ac77b0f" + integrity sha512-MjhiaIWCJ1sAU4pIQ5i5OfOuHHxVo1oYeNsWTON7jxYkod8pHocXeh+SSbmu5OZZZK73B6cbJ2XADzXehLyovQ== + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -5924,6 +6005,17 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +globby@^13.1.3: + version "13.1.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.4.tgz#2f91c116066bcec152465ba36e5caa4a13c01317" + integrity sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g== + dependencies: + dir-glob "^3.0.1" + fast-glob "^3.2.11" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^4.0.0" + globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -5952,6 +6044,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== +graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + grapheme-splitter@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" @@ -6607,6 +6704,16 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-docker@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== + is-electron@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-electron/-/is-electron-2.2.0.tgz#8943084f09e8b731b3a7a0298a7b5d56f6b7eef0" @@ -6663,6 +6770,13 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-inside-container@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== + dependencies: + is-docker "^3.0.0" + is-map@^2.0.1, is-map@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" @@ -6846,6 +6960,13 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -8493,6 +8614,16 @@ onetime@^6.0.0: dependencies: mimic-fn "^4.0.0" +open@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/open/-/open-9.1.0.tgz#684934359c90ad25742f5a26151970ff8c6c80b6" + integrity sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg== + dependencies: + default-browser "^4.0.0" + define-lazy-prop "^3.0.0" + is-inside-container "^1.0.0" + is-wsl "^2.2.0" + opencollective-postinstall@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" @@ -10099,6 +10230,13 @@ rrweb-cssom@^0.6.0: resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1" integrity sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw== +run-applescript@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-5.0.0.tgz#e11e1c932e055d5c6b40d98374e0268d9b11899c" + integrity sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg== + dependencies: + execa "^5.0.0" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -10411,6 +10549,11 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" + integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== + slice-ansi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" @@ -11055,6 +11198,14 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== +synckit@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3" + integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q== + dependencies: + "@pkgr/utils" "^2.3.1" + tslib "^2.5.0" + table@^6.8.1: version "6.8.1" resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf" @@ -11071,6 +11222,11 @@ tapable@^1.0, tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== +tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + tar@^6.0.2: version "6.1.11" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" @@ -11193,6 +11349,11 @@ tiny-warning@^1.0.0: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== +titleize@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/titleize/-/titleize-3.0.0.tgz#71c12eb7fdd2558aa8a44b0be83b8a76694acd53" + integrity sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ== + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -11301,7 +11462,7 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.5.0, tslib@^2.1.0, tslib@^2.4.0: +tslib@2.5.0, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== @@ -11519,6 +11680,11 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" +untildify@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" + integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== + upath@^1.1.1, upath@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" From de95d0ca8ff2ea5292491202256b25aeefa1d5f1 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 10 May 2023 17:22:34 +0200 Subject: [PATCH 07/98] Fix videos being improperly positioned on safari (#24943) --- app/javascript/styles/mastodon/components.scss | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 48386675ce..1842d3bc8b 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -6447,13 +6447,6 @@ a.status-card.compact:hover { &--wide { grid-column: span 2; } - - &.standalone { - .media-gallery__item-gifv-thumbnail { - transform: none; - top: 0; - } - } } .media-gallery__item-thumbnail { @@ -6501,11 +6494,7 @@ a.status-card.compact:hover { cursor: zoom-in; height: 100%; object-fit: cover; - position: relative; - top: 50%; - transform: translateY(-50%); width: 100%; - z-index: 1; } .media-gallery__item-thumbnail-label { @@ -6604,6 +6593,8 @@ a.status-card.compact:hover { border-radius: 4px; box-sizing: border-box; color: $white; + display: flex; + align-items: center; &.editable { border-radius: 0; @@ -6638,9 +6629,6 @@ a.status-card.compact:hover { &.inline { video { object-fit: contain; - position: relative; - top: 50%; - transform: translateY(-50%); } } From d05ff56746a80723dbb3416e72992674f44f1d28 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 10 May 2023 20:17:55 +0200 Subject: [PATCH 08/98] Change "Sign in" to "Login" (#24942) --- .../mastodon/components/not_signed_in_indicator.tsx | 2 +- .../mastodon/features/interaction_modal/index.jsx | 2 +- .../mastodon/features/ui/components/header.jsx | 6 +++--- .../mastodon/features/ui/components/sign_in_banner.jsx | 8 ++++---- app/javascript/mastodon/locales/defaultMessages.json | 10 +++++----- app/javascript/mastodon/locales/en.json | 6 +++--- config/locales/devise.en.yml | 6 +++--- config/locales/en.yml | 6 +++--- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/app/javascript/mastodon/components/not_signed_in_indicator.tsx b/app/javascript/mastodon/components/not_signed_in_indicator.tsx index 7a71f6539d..ce94c5d873 100644 --- a/app/javascript/mastodon/components/not_signed_in_indicator.tsx +++ b/app/javascript/mastodon/components/not_signed_in_indicator.tsx @@ -7,7 +7,7 @@ export const NotSignedInIndicator: React.FC = () => (
diff --git a/app/javascript/mastodon/features/interaction_modal/index.jsx b/app/javascript/mastodon/features/interaction_modal/index.jsx index f3db2c78d5..d9eff63cba 100644 --- a/app/javascript/mastodon/features/interaction_modal/index.jsx +++ b/app/javascript/mastodon/features/interaction_modal/index.jsx @@ -143,7 +143,7 @@ class InteractionModal extends React.PureComponent {

- + {signupButton}
diff --git a/app/javascript/mastodon/features/ui/components/header.jsx b/app/javascript/mastodon/features/ui/components/header.jsx index af18ac3310..6419dbc50d 100644 --- a/app/javascript/mastodon/features/ui/components/header.jsx +++ b/app/javascript/mastodon/features/ui/components/header.jsx @@ -51,13 +51,13 @@ class Header extends React.PureComponent { if (registrationsOpen) { signupButton = ( - + ); } else { signupButton = ( - ); @@ -65,8 +65,8 @@ class Header extends React.PureComponent { content = ( <> - {signupButton} + ); } diff --git a/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx b/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx index 86fcc11b56..39f0c71c34 100644 --- a/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx +++ b/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx @@ -16,13 +16,13 @@ const SignInBanner = () => { if (registrationsOpen) { signupButton = ( - + ); } else { signupButton = ( - ); @@ -30,9 +30,9 @@ const SignInBanner = () => { return (
-

- +

{signupButton} +
); }; diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index a735d2ff36..6b1b51702f 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -356,7 +356,7 @@ { "descriptors": [ { - "defaultMessage": "You need to sign in to access this resource.", + "defaultMessage": "You need to login to access this resource.", "id": "not_signed_in_indicator.not_signed_in" } ], @@ -2623,7 +2623,7 @@ "id": "interaction_modal.on_this_server" }, { - "defaultMessage": "Sign in", + "defaultMessage": "Login", "id": "sign_in_banner.sign_in" }, { @@ -4175,7 +4175,7 @@ "id": "sign_in_banner.create_account" }, { - "defaultMessage": "Sign in", + "defaultMessage": "Login", "id": "sign_in_banner.sign_in" } ], @@ -4374,11 +4374,11 @@ "id": "sign_in_banner.create_account" }, { - "defaultMessage": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.", + "defaultMessage": "Login to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.", "id": "sign_in_banner.text" }, { - "defaultMessage": "Sign in", + "defaultMessage": "Login", "id": "sign_in_banner.sign_in" } ], diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 85040bf5b3..a9c9f534f6 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -391,7 +391,7 @@ "navigation_bar.public_timeline": "Federated timeline", "navigation_bar.search": "Search", "navigation_bar.security": "Security", - "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.", + "not_signed_in_indicator.not_signed_in": "You need to login to access this resource.", "notification.admin.report": "{name} reported {target}", "notification.admin.sign_up": "{name} signed up", "notification.favourite": "{name} favourited your post", @@ -573,8 +573,8 @@ "server_banner.learn_more": "Learn more", "server_banner.server_stats": "Server stats:", "sign_in_banner.create_account": "Create account", - "sign_in_banner.sign_in": "Sign in", - "sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.", + "sign_in_banner.sign_in": "Login", + "sign_in_banner.text": "Login to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.", "status.admin_account": "Open moderation interface for @{name}", "status.admin_domain": "Open moderation interface for {domain}", "status.admin_status": "Open this post in the moderation interface", diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index 458fa6d759..eef8214817 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -13,8 +13,8 @@ en: locked: Your account is locked. not_found_in_database: Invalid %{authentication_keys} or password. pending: Your account is still under review. - timeout: Your session expired. Please sign in again to continue. - unauthenticated: You need to sign in or sign up before continuing. + timeout: Your session expired. Please login again to continue. + unauthenticated: You need to login or sign up before continuing. unconfirmed: You have to confirm your email address before continuing. mailer: confirmation_instructions: @@ -102,7 +102,7 @@ en: unlocks: send_instructions: You will receive an email with instructions for how to unlock your account in a few minutes. Please check your spam folder if you didn't receive this email. send_paranoid_instructions: If your account exists, you will receive an email with instructions for how to unlock it in a few minutes. Please check your spam folder if you didn't receive this email. - unlocked: Your account has been unlocked successfully. Please sign in to continue. + unlocked: Your account has been unlocked successfully. Please login to continue. errors: messages: already_confirmed: was already confirmed, please try signing in diff --git a/config/locales/en.yml b/config/locales/en.yml index 0188519c26..29abec9437 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1027,8 +1027,8 @@ en: new_confirmation_instructions_sent: You will receive a new e-mail with the confirmation link in a few minutes! title: Check your inbox sign_in: - preamble_html: Sign in with your %{domain} credentials. If your account is hosted on a different server, you will not be able to log in here. - title: Sign in to %{domain} + preamble_html: Login with your %{domain} credentials. If your account is hosted on a different server, you will not be able to log in here. + title: Login to %{domain} sign_up: manual_review: Sign-ups on %{domain} go through manual review by our moderators. To help us process your registration, write a bit about yourself and why you want an account on %{domain}. preamble: With an account on this Mastodon server, you'll be able to follow any other person on the network, regardless of where their account is hosted. @@ -1595,7 +1595,7 @@ en: show_newer: Show newer show_older: Show older show_thread: Show thread - sign_in_to_participate: Sign in to participate in the conversation + sign_in_to_participate: Login to participate in the conversation title: '%{name}: "%{quote}"' visibilities: direct: Direct From c752e939d511519ec43f0276be060debeb5fa582 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 11 May 2023 04:40:03 +0200 Subject: [PATCH 09/98] Change AccessTokensVacuum to also delete expired tokens (#24868) --- app/lib/vacuum/access_tokens_vacuum.rb | 6 ++++-- spec/lib/vacuum/access_tokens_vacuum_spec.rb | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/lib/vacuum/access_tokens_vacuum.rb b/app/lib/vacuum/access_tokens_vacuum.rb index 7b91f74a51..a224f6d638 100644 --- a/app/lib/vacuum/access_tokens_vacuum.rb +++ b/app/lib/vacuum/access_tokens_vacuum.rb @@ -9,10 +9,12 @@ class Vacuum::AccessTokensVacuum private def vacuum_revoked_access_tokens! - Doorkeeper::AccessToken.where.not(revoked_at: nil).where('revoked_at < NOW()').delete_all + Doorkeeper::AccessToken.where.not(expires_in: nil).where('created_at + make_interval(secs => expires_in) < NOW()').in_batches.delete_all + Doorkeeper::AccessToken.where.not(revoked_at: nil).where('revoked_at < NOW()').in_batches.delete_all end def vacuum_revoked_access_grants! - Doorkeeper::AccessGrant.where.not(revoked_at: nil).where('revoked_at < NOW()').delete_all + Doorkeeper::AccessGrant.where.not(expires_in: nil).where('created_at + make_interval(secs => expires_in) < NOW()').in_batches.delete_all + Doorkeeper::AccessGrant.where.not(revoked_at: nil).where('revoked_at < NOW()').in_batches.delete_all end end diff --git a/spec/lib/vacuum/access_tokens_vacuum_spec.rb b/spec/lib/vacuum/access_tokens_vacuum_spec.rb index 6b72340655..54760c41bd 100644 --- a/spec/lib/vacuum/access_tokens_vacuum_spec.rb +++ b/spec/lib/vacuum/access_tokens_vacuum_spec.rb @@ -7,9 +7,11 @@ RSpec.describe Vacuum::AccessTokensVacuum do describe '#perform' do let!(:revoked_access_token) { Fabricate(:access_token, revoked_at: 1.minute.ago) } + let!(:expired_access_token) { Fabricate(:access_token, expires_in: 59.minutes.to_i, created_at: 1.hour.ago) } let!(:active_access_token) { Fabricate(:access_token) } let!(:revoked_access_grant) { Fabricate(:access_grant, revoked_at: 1.minute.ago) } + let!(:expired_access_grant) { Fabricate(:access_grant, expires_in: 59.minutes.to_i, created_at: 1.hour.ago) } let!(:active_access_grant) { Fabricate(:access_grant) } before do @@ -20,10 +22,18 @@ RSpec.describe Vacuum::AccessTokensVacuum do expect { revoked_access_token.reload }.to raise_error ActiveRecord::RecordNotFound end + it 'deletes expired access tokens' do + expect { expired_access_token.reload }.to raise_error ActiveRecord::RecordNotFound + end + it 'deletes revoked access grants' do expect { revoked_access_grant.reload }.to raise_error ActiveRecord::RecordNotFound end + it 'deletes expired access grants' do + expect { expired_access_grant.reload }.to raise_error ActiveRecord::RecordNotFound + end + it 'does not delete active access tokens' do expect { active_access_token.reload }.to_not raise_error end From db07cfb1509877f3f073b341ce0bfc6cc7ac800a Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Thu, 11 May 2023 07:55:10 +0200 Subject: [PATCH 10/98] Fix Onboarding Errors (#24883) --- .../mastodon/components/account.jsx | 17 ++-------- .../mastodon/components/display_name.tsx | 25 +++++++------- .../mastodon/components/empty_account.tsx | 33 +++++++++++++++++++ .../mastodon/features/onboarding/follows.jsx | 9 ++--- .../mastodon/features/onboarding/index.jsx | 7 ++-- .../mastodon/features/onboarding/share.jsx | 5 +-- 6 files changed, 61 insertions(+), 35 deletions(-) create mode 100644 app/javascript/mastodon/components/empty_account.tsx diff --git a/app/javascript/mastodon/components/account.jsx b/app/javascript/mastodon/components/account.jsx index f110bce7a8..08dfb4793d 100644 --- a/app/javascript/mastodon/components/account.jsx +++ b/app/javascript/mastodon/components/account.jsx @@ -8,12 +8,12 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { me } from '../initial_state'; import { RelativeTimestamp } from './relative_timestamp'; -import Skeleton from 'mastodon/components/skeleton'; import { Link } from 'react-router-dom'; import { counterRenderer } from 'mastodon/components/common_counter'; import ShortNumber from 'mastodon/components/short_number'; import classNames from 'classnames'; import { VerifiedBadge } from 'mastodon/components/verified_badge'; +import { EmptyAccount } from 'mastodon/components/empty_account'; const messages = defineMessages({ follow: { id: 'account.follow', defaultMessage: 'Follow' }, @@ -77,20 +77,7 @@ class Account extends ImmutablePureComponent { const { account, intl, hidden, onActionClick, actionIcon, actionTitle, defaultAction, size, minimal } = this.props; if (!account) { - return ( -
-
-
-
- -
- - -
-
-
-
- ); + return ; } if (hidden) { diff --git a/app/javascript/mastodon/components/display_name.tsx b/app/javascript/mastodon/components/display_name.tsx index d3308ff216..ce435066d6 100644 --- a/app/javascript/mastodon/components/display_name.tsx +++ b/app/javascript/mastodon/components/display_name.tsx @@ -8,10 +8,11 @@ import { autoPlayGif } from '../initial_state'; import Skeleton from './skeleton'; interface Props { - account: Account; - others: List; - localDomain: string; + account?: Account; + others?: List; + localDomain?: string; } + export class DisplayName extends React.PureComponent { handleMouseEnter: React.ReactEventHandler = ({ currentTarget, @@ -48,7 +49,15 @@ export class DisplayName extends React.PureComponent { render() { const { others, localDomain } = this.props; - let displayName: React.ReactNode, suffix: React.ReactNode, account: Account; + let displayName: React.ReactNode, + suffix: React.ReactNode, + account: Account | undefined; + + if (others && others.size > 0) { + account = others.first(); + } else if (this.props.account) { + account = this.props.account; + } if (others && others.size > 1) { displayName = others @@ -66,13 +75,7 @@ export class DisplayName extends React.PureComponent { if (others.size - 2 > 0) { suffix = `+${others.size - 2}`; } - } else if ((others && others.size > 0) || this.props.account) { - if (others && others.size > 0) { - account = others.first(); - } else { - account = this.props.account; - } - + } else if (account) { let acct = account.get('acct'); if (acct.indexOf('@') === -1 && localDomain) { diff --git a/app/javascript/mastodon/components/empty_account.tsx b/app/javascript/mastodon/components/empty_account.tsx new file mode 100644 index 0000000000..3adb5b20f8 --- /dev/null +++ b/app/javascript/mastodon/components/empty_account.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +import classNames from 'classnames'; + +import { DisplayName } from 'mastodon/components/display_name'; +import Skeleton from 'mastodon/components/skeleton'; + +interface Props { + size?: number; + minimal?: boolean; +} + +export const EmptyAccount: React.FC = ({ + size = 46, + minimal = false, +}) => { + return ( +
+
+
+
+ +
+ +
+ + +
+
+
+
+ ); +}; diff --git a/app/javascript/mastodon/features/onboarding/follows.jsx b/app/javascript/mastodon/features/onboarding/follows.jsx index 7cccdefb3d..c96c69055b 100644 --- a/app/javascript/mastodon/features/onboarding/follows.jsx +++ b/app/javascript/mastodon/features/onboarding/follows.jsx @@ -7,7 +7,7 @@ import { fetchSuggestions } from 'mastodon/actions/suggestions'; import { markAsPartial } from 'mastodon/actions/timelines'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Account from 'mastodon/containers/account_container'; -import EmptyAccount from 'mastodon/components/account'; +import { EmptyAccount } from 'mastodon/components/empty_account'; import { FormattedMessage, FormattedHTMLMessage } from 'react-intl'; import { makeGetAccount } from 'mastodon/selectors'; import { me } from 'mastodon/initial_state'; @@ -31,6 +31,7 @@ class Follows extends React.PureComponent { suggestions: ImmutablePropTypes.list, account: ImmutablePropTypes.map, isLoading: PropTypes.bool, + multiColumn: PropTypes.bool, }; componentDidMount () { @@ -44,7 +45,7 @@ class Follows extends React.PureComponent { } render () { - const { onBack, isLoading, suggestions, account } = this.props; + const { onBack, isLoading, suggestions, account, multiColumn } = this.props; let loadedContent; @@ -58,7 +59,7 @@ class Follows extends React.PureComponent { return ( - +
@@ -84,4 +85,4 @@ class Follows extends React.PureComponent { } -export default connect(mapStateToProps)(Follows); \ No newline at end of file +export default connect(mapStateToProps)(Follows); diff --git a/app/javascript/mastodon/features/onboarding/index.jsx b/app/javascript/mastodon/features/onboarding/index.jsx index 388734055e..8b373a014c 100644 --- a/app/javascript/mastodon/features/onboarding/index.jsx +++ b/app/javascript/mastodon/features/onboarding/index.jsx @@ -40,6 +40,7 @@ class Onboarding extends ImmutablePureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, account: ImmutablePropTypes.map, + multiColumn: PropTypes.bool, }; state = { @@ -93,14 +94,14 @@ class Onboarding extends ImmutablePureComponent { } render () { - const { account } = this.props; + const { account, multiColumn } = this.props; const { step, shareClicked } = this.state; switch(step) { case 'follows': - return ; + return ; case 'share': - return ; + return ; } return ( diff --git a/app/javascript/mastodon/features/onboarding/share.jsx b/app/javascript/mastodon/features/onboarding/share.jsx index 5f7cfb8a6b..82fdada413 100644 --- a/app/javascript/mastodon/features/onboarding/share.jsx +++ b/app/javascript/mastodon/features/onboarding/share.jsx @@ -140,17 +140,18 @@ class Share extends React.PureComponent { static propTypes = { onBack: PropTypes.func, account: ImmutablePropTypes.map, + multiColumn: PropTypes.bool, intl: PropTypes.object, }; render () { - const { onBack, account, intl } = this.props; + const { onBack, account, multiColumn, intl } = this.props; const url = (new URL(`/@${account.get('username')}`, document.baseURI)).href; return ( - +
From 9c5c803c7cbdbed3ff1e081e899fd7cde5760edf Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Thu, 11 May 2023 05:19:24 -0300 Subject: [PATCH 11/98] Add test coverage for `Mastodon::IpBlocksCLI` (#24935) --- spec/lib/mastodon/ip_blocks_cli_spec.rb | 292 ++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 spec/lib/mastodon/ip_blocks_cli_spec.rb diff --git a/spec/lib/mastodon/ip_blocks_cli_spec.rb b/spec/lib/mastodon/ip_blocks_cli_spec.rb new file mode 100644 index 0000000000..27c005772b --- /dev/null +++ b/spec/lib/mastodon/ip_blocks_cli_spec.rb @@ -0,0 +1,292 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/ip_blocks_cli' + +RSpec.describe Mastodon::IpBlocksCLI do + let(:cli) { described_class.new } + + describe '#add' do + let(:ip_list) do + [ + '192.0.2.1', + '172.16.0.1', + '192.0.2.0/24', + '172.16.0.0/16', + '10.0.0.0/8', + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'fe80::1', + '::1', + '2001:0db8::/32', + 'fe80::/10', + '::/128', + ] + end + let(:options) { { severity: 'no_access' } } + + shared_examples 'ip address blocking' do + it 'blocks all specified IP addresses' do + cli.invoke(:add, ip_list, options) + + blocked_ip_addresses = IpBlock.where(ip: ip_list).pluck(:ip) + expected_ip_addresses = ip_list.map { |ip| IPAddr.new(ip) } + + expect(blocked_ip_addresses).to match_array(expected_ip_addresses) + end + + it 'sets the severity for all blocked IP addresses' do + cli.invoke(:add, ip_list, options) + + blocked_ips_severity = IpBlock.where(ip: ip_list).pluck(:severity).all?(options[:severity]) + + expect(blocked_ips_severity).to be(true) + end + + it 'displays a success message with a summary' do + expect { cli.invoke(:add, ip_list, options) }.to output( + a_string_including("Added #{ip_list.size}, skipped 0, failed 0") + ).to_stdout + end + end + + context 'with valid IP addresses' do + include_examples 'ip address blocking' + end + + context 'when a specified IP address is already blocked' do + let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: options[:severity]) } + + it 'skips the already blocked IP address' do + allow(IpBlock).to receive(:new).and_call_original + + cli.invoke(:add, ip_list, options) + + expect(IpBlock).to_not have_received(:new).with(ip: ip_list.last) + end + + it 'displays the correct summary' do + expect { cli.invoke(:add, ip_list, options) }.to output( + a_string_including("#{ip_list.last} is already blocked\nAdded #{ip_list.size - 1}, skipped 1, failed 0") + ).to_stdout + end + + context 'with --force option' do + let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: 'no_access') } + let(:options) { { severity: 'sign_up_requires_approval', force: true } } + + it 'overwrites the existing IP block record' do + expect { cli.invoke(:add, ip_list, options) } + .to change { blocked_ip.reload.severity } + .from('no_access') + .to('sign_up_requires_approval') + end + + include_examples 'ip address blocking' + end + end + + context 'when a specified IP address is invalid' do + let(:ip_list) { ['320.15.175.0', '9.5.105.255', '0.0.0.0'] } + + it 'displays the correct summary' do + expect { cli.invoke(:add, ip_list, options) }.to output( + a_string_including("#{ip_list.first} is invalid\nAdded #{ip_list.size - 1}, skipped 0, failed 1") + ).to_stdout + end + end + + context 'with --comment option' do + let(:options) { { severity: 'no_access', comment: 'Spam' } } + + include_examples 'ip address blocking' + end + + context 'with --duration option' do + let(:options) { { severity: 'no_access', duration: 10.days } } + + include_examples 'ip address blocking' + end + + context 'with "sign_up_requires_approval" severity' do + let(:options) { { severity: 'sign_up_requires_approval' } } + + include_examples 'ip address blocking' + end + + context 'with "sign_up_block" severity' do + let(:options) { { severity: 'sign_up_block' } } + + include_examples 'ip address blocking' + end + + context 'when a specified IP address fails to be blocked' do + let(:ip_address) { '127.0.0.1' } + let(:ip_block) { instance_double(IpBlock, ip: ip_address, save: false) } + + before do + allow(IpBlock).to receive(:new).and_return(ip_block) + allow(ip_block).to receive(:severity=) + allow(ip_block).to receive(:expires_in=) + end + + it 'displays an error message' do + expect { cli.invoke(:add, [ip_address], options) } + .to output( + a_string_including("#{ip_address} could not be saved") + ).to_stdout + end + end + + context 'when no IP address is provided' do + it 'exits with an error message' do + expect { cli.add }.to output( + a_string_including('No IP(s) given') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + + describe '#remove' do + context 'when removing exact matches' do + let(:ip_list) do + [ + '192.0.2.1', + '172.16.0.1', + '192.0.2.0/24', + '172.16.0.0/16', + '10.0.0.0/8', + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'fe80::1', + '::1', + '2001:0db8::/32', + 'fe80::/10', + '::/128', + ] + end + + before do + ip_list.each { |ip| IpBlock.create(ip: ip, severity: :no_access) } + end + + it 'removes exact IP blocks' do + cli.invoke(:remove, ip_list) + + expect(IpBlock.where(ip: ip_list)).to_not exist + end + + it 'displays success message with a summary' do + expect { cli.invoke(:remove, ip_list) }.to output( + a_string_including("Removed #{ip_list.size}, skipped 0") + ).to_stdout + end + end + + context 'with --force option' do + let!(:block1) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) } + let!(:block2) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) } + let!(:block3) { IpBlock.create(ip: '172.16.0.0/20', severity: :no_access) } + let(:arguments) { ['192.168.0.5', '10.0.1.50'] } + let(:options) { { force: true } } + + it 'removes blocks for IP ranges that cover given IP(s)' do + cli.invoke(:remove, arguments, options) + + expect(IpBlock.where(id: [block1.id, block2.id])).to_not exist + end + + it 'does not remove other IP ranges' do + cli.invoke(:remove, arguments, options) + + expect(IpBlock.where(id: block3.id)).to exist + end + end + + context 'when a specified IP address is not blocked' do + let(:unblocked_ip) { '192.0.2.1' } + + it 'skips the IP address' do + expect { cli.invoke(:remove, [unblocked_ip]) }.to output( + a_string_including("#{unblocked_ip} is not yet blocked") + ).to_stdout + end + + it 'displays the summary correctly' do + expect { cli.invoke(:remove, [unblocked_ip]) }.to output( + a_string_including('Removed 0, skipped 1') + ).to_stdout + end + end + + context 'when a specified IP address is invalid' do + let(:invalid_ip) { '320.15.175.0' } + + it 'skips the invalid IP address' do + expect { cli.invoke(:remove, [invalid_ip]) }.to output( + a_string_including("#{invalid_ip} is invalid") + ).to_stdout + end + + it 'displays the summary correctly' do + expect { cli.invoke(:remove, [invalid_ip]) }.to output( + a_string_including('Removed 0, skipped 1') + ).to_stdout + end + end + + context 'when no IP address is provided' do + it 'exits with an error message' do + expect { cli.remove }.to output( + a_string_including('No IP(s) given') + ).to_stdout + .and raise_error(SystemExit) + end + end + end + + describe '#export' do + let(:block1) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) } + let(:block2) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) } + let(:block3) { IpBlock.create(ip: '127.0.0.1', severity: :sign_up_block) } + + context 'when --format option is set to "plain"' do + let(:options) { { format: 'plain' } } + + it 'exports blocked IPs with "no_access" severity in plain format' do + expect { cli.invoke(:export, nil, options) }.to output( + a_string_including("#{block1.ip}/#{block1.ip.prefix}\n#{block2.ip}/#{block2.ip.prefix}") + ).to_stdout + end + + it 'does not export bloked IPs with different severities' do + expect { cli.invoke(:export, nil, options) }.to_not output( + a_string_including("#{block3.ip}/#{block1.ip.prefix}") + ).to_stdout + end + end + + context 'when --format option is set to "nginx"' do + let(:options) { { format: 'nginx' } } + + it 'exports blocked IPs with "no_access" severity in plain format' do + expect { cli.invoke(:export, nil, options) }.to output( + a_string_including("deny #{block1.ip}/#{block1.ip.prefix};\ndeny #{block2.ip}/#{block2.ip.prefix};") + ).to_stdout + end + + it 'does not export bloked IPs with different severities' do + expect { cli.invoke(:export, nil, options) }.to_not output( + a_string_including("deny #{block3.ip}/#{block1.ip.prefix};") + ).to_stdout + end + end + + context 'when --format option is not provided' do + it 'exports blocked IPs in plain format by default' do + expect { cli.export }.to output( + a_string_including("#{block1.ip}/#{block1.ip.prefix}\n#{block2.ip}/#{block2.ip.prefix}") + ).to_stdout + end + end + end +end From 1598cf90439d4256f074604eafcb75280c0d8570 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 11 May 2023 04:32:09 -0400 Subject: [PATCH 12/98] Fix RSpec/ScatteredSetup cop (#24848) --- .rubocop_todo.yml | 8 -------- .../followers_synchronizations_controller_spec.rb | 2 -- .../activitypub/outboxes_controller_spec.rb | 2 -- .../admin/disputes/appeals_controller_spec.rb | 10 +++++----- spec/controllers/auth/registrations_controller_spec.rb | 4 ++-- .../activitypub/process_account_service_spec.rb | 6 ++---- 6 files changed, 9 insertions(+), 23 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 77b8e95f8b..a83fec8ae7 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -648,14 +648,6 @@ RSpec/RepeatedExampleGroupDescription: - 'spec/controllers/admin/reports/actions_controller_spec.rb' - 'spec/policies/report_note_policy_spec.rb' -RSpec/ScatteredSetup: - Exclude: - - 'spec/controllers/activitypub/followers_synchronizations_controller_spec.rb' - - 'spec/controllers/activitypub/outboxes_controller_spec.rb' - - 'spec/controllers/admin/disputes/appeals_controller_spec.rb' - - 'spec/controllers/auth/registrations_controller_spec.rb' - - 'spec/services/activitypub/process_account_service_spec.rb' - # This cop supports safe autocorrection (--autocorrect). RSpec/SharedContext: Exclude: diff --git a/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb b/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb index 8fcce165b3..e544585ec1 100644 --- a/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb +++ b/spec/controllers/activitypub/followers_synchronizations_controller_spec.rb @@ -14,9 +14,7 @@ RSpec.describe ActivityPub::FollowersSynchronizationsController do follower_2.follow!(account) follower_3.follow!(account) follower_4.follow!(account) - end - before do allow(controller).to receive(:signed_request_actor).and_return(remote_account) end diff --git a/spec/controllers/activitypub/outboxes_controller_spec.rb b/spec/controllers/activitypub/outboxes_controller_spec.rb index 8823d9fe7e..6946fdfcff 100644 --- a/spec/controllers/activitypub/outboxes_controller_spec.rb +++ b/spec/controllers/activitypub/outboxes_controller_spec.rb @@ -27,9 +27,7 @@ RSpec.describe ActivityPub::OutboxesController do Fabricate(:status, account: account, visibility: :private) Fabricate(:status, account: account, visibility: :direct) Fabricate(:status, account: account, visibility: :limited) - end - before do allow(controller).to receive(:signed_request_actor).and_return(remote_account) end diff --git a/spec/controllers/admin/disputes/appeals_controller_spec.rb b/spec/controllers/admin/disputes/appeals_controller_spec.rb index 371c4f483d..99b19298c6 100644 --- a/spec/controllers/admin/disputes/appeals_controller_spec.rb +++ b/spec/controllers/admin/disputes/appeals_controller_spec.rb @@ -5,16 +5,16 @@ require 'rails_helper' RSpec.describe Admin::Disputes::AppealsController do render_views - before { sign_in current_user, scope: :user } + before do + sign_in current_user, scope: :user + + target_account.suspend! + end let(:target_account) { Fabricate(:account) } let(:strike) { Fabricate(:account_warning, target_account: target_account, action: :suspend) } let(:appeal) { Fabricate(:appeal, strike: strike, account: target_account) } - before do - target_account.suspend! - end - describe 'POST #approve' do let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb index ad8465e2ac..42b2606ad2 100644 --- a/spec/controllers/auth/registrations_controller_spec.rb +++ b/spec/controllers/auth/registrations_controller_spec.rb @@ -101,6 +101,8 @@ RSpec.describe Auth::RegistrationsController do before do session[:registration_form_time] = 5.seconds.ago + + request.env['devise.mapping'] = Devise.mappings[:user] end around do |example| @@ -109,8 +111,6 @@ RSpec.describe Auth::RegistrationsController do end end - before { request.env['devise.mapping'] = Devise.mappings[:user] } - context do subject do Setting.registrations_mode = 'open' diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb index ffbc51b641..4c5e6b6cc3 100644 --- a/spec/services/activitypub/process_account_service_spec.rb +++ b/spec/services/activitypub/process_account_service_spec.rb @@ -139,10 +139,6 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do end context 'when Accounts referencing other accounts' do - before do - stub_const 'ActivityPub::ProcessAccountService::DISCOVERIES_PER_REQUEST', 5 - end - let(:payload) do { '@context': ['https://www.w3.org/ns/activitystreams'], @@ -155,6 +151,8 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do end before do + stub_const 'ActivityPub::ProcessAccountService::DISCOVERIES_PER_REQUEST', 5 + 8.times do |i| actor_json = { '@context': ['https://www.w3.org/ns/activitystreams'], From 778fd4d29b00650a2be44efab69c7d6901834b45 Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Thu, 11 May 2023 12:41:55 +0200 Subject: [PATCH 13/98] Fix UI crash in moderation interface when opening the media modal (#24816) --- .../mastodon/components/media_gallery.jsx | 2 +- app/javascript/mastodon/components/status.jsx | 10 ++++++---- .../mastodon/containers/media_container.jsx | 10 ++++++---- .../mastodon/containers/status_container.jsx | 8 ++++---- .../mastodon/features/account_gallery/index.jsx | 7 ++++--- .../containers/detailed_status_container.js | 8 ++++---- .../mastodon/features/status/index.jsx | 8 ++++---- .../features/ui/components/media_modal.jsx | 16 ++++++---------- app/javascript/mastodon/features/video/index.jsx | 2 +- .../admin/reports/_media_attachments.html.haml | 6 +++--- 10 files changed, 39 insertions(+), 38 deletions(-) diff --git a/app/javascript/mastodon/components/media_gallery.jsx b/app/javascript/mastodon/components/media_gallery.jsx index 1eeb63fa45..6653f8632d 100644 --- a/app/javascript/mastodon/components/media_gallery.jsx +++ b/app/javascript/mastodon/components/media_gallery.jsx @@ -256,7 +256,7 @@ class MediaGallery extends React.PureComponent { }; handleClick = (index) => { - this.props.onOpenMedia(this.props.media, index); + this.props.onOpenMedia(this.props.media, index, this.props.lang); }; handleRef = c => { diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 8cd322edab..070ec4672a 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -194,11 +194,12 @@ class Status extends ImmutablePureComponent { handleOpenVideo = (options) => { const status = this._properStatus(); - this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options); + this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), status.get('language'), options); }; handleOpenMedia = (media, index) => { - this.props.onOpenMedia(this._properStatus().get('id'), media, index); + const status = this._properStatus(); + this.props.onOpenMedia(status.get('id'), media, index, status.get('language')); }; handleHotkeyOpenMedia = e => { @@ -208,10 +209,11 @@ class Status extends ImmutablePureComponent { e.preventDefault(); if (status.get('media_attachments').size > 0) { + const lang = status.get('language'); if (status.getIn(['media_attachments', 0, 'type']) === 'video') { - onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), { startTime: 0 }); + onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), lang, { startTime: 0 }); } else { - onOpenMedia(status.get('id'), status.get('media_attachments'), 0); + onOpenMedia(status.get('id'), status.get('media_attachments'), 0, lang); } } }; diff --git a/app/javascript/mastodon/containers/media_container.jsx b/app/javascript/mastodon/containers/media_container.jsx index f9244f8dd3..0b5ff99dda 100644 --- a/app/javascript/mastodon/containers/media_container.jsx +++ b/app/javascript/mastodon/containers/media_container.jsx @@ -29,19 +29,20 @@ export default class MediaContainer extends PureComponent { state = { media: null, index: null, + lang: null, time: null, backgroundColor: null, options: null, }; - handleOpenMedia = (media, index) => { + handleOpenMedia = (media, index, lang) => { document.body.classList.add('with-modals--active'); document.documentElement.style.marginRight = `${getScrollbarWidth()}px`; - this.setState({ media, index }); + this.setState({ media, index, lang }); }; - handleOpenVideo = (options) => { + handleOpenVideo = (lang, options) => { const { components } = this.props; const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props')); const mediaList = fromJS(media); @@ -49,7 +50,7 @@ export default class MediaContainer extends PureComponent { document.body.classList.add('with-modals--active'); document.documentElement.style.marginRight = `${getScrollbarWidth()}px`; - this.setState({ media: mediaList, options }); + this.setState({ media: mediaList, lang, options }); }; handleCloseMedia = () => { @@ -105,6 +106,7 @@ export default class MediaContainer extends PureComponent { ({ dispatch(mentionCompose(account, router)); }, - onOpenMedia (statusId, media, index) { - dispatch(openModal('MEDIA', { statusId, media, index })); + onOpenMedia (statusId, media, index, lang) { + dispatch(openModal('MEDIA', { statusId, media, index, lang })); }, - onOpenVideo (statusId, media, options) { - dispatch(openModal('VIDEO', { statusId, media, options })); + onOpenVideo (statusId, media, lang, options) { + dispatch(openModal('VIDEO', { statusId, media, lang, options })); }, onBlock (status) { diff --git a/app/javascript/mastodon/features/account_gallery/index.jsx b/app/javascript/mastodon/features/account_gallery/index.jsx index b876df6a29..8c44fa346f 100644 --- a/app/javascript/mastodon/features/account_gallery/index.jsx +++ b/app/javascript/mastodon/features/account_gallery/index.jsx @@ -136,16 +136,17 @@ class AccountGallery extends ImmutablePureComponent { handleOpenMedia = attachment => { const { dispatch } = this.props; const statusId = attachment.getIn(['status', 'id']); + const lang = attachment.getIn(['status', 'language']); if (attachment.get('type') === 'video') { - dispatch(openModal('VIDEO', { media: attachment, statusId, options: { autoPlay: true } })); + dispatch(openModal('VIDEO', { media: attachment, statusId, lang, options: { autoPlay: true } })); } else if (attachment.get('type') === 'audio') { - dispatch(openModal('AUDIO', { media: attachment, statusId, options: { autoPlay: true } })); + dispatch(openModal('AUDIO', { media: attachment, statusId, lang, options: { autoPlay: true } })); } else { const media = attachment.getIn(['status', 'media_attachments']); const index = media.findIndex(x => x.get('id') === attachment.get('id')); - dispatch(openModal('MEDIA', { media, index, statusId })); + dispatch(openModal('MEDIA', { media, index, statusId, lang })); } }; diff --git a/app/javascript/mastodon/features/status/containers/detailed_status_container.js b/app/javascript/mastodon/features/status/containers/detailed_status_container.js index bfed166200..835bb41b51 100644 --- a/app/javascript/mastodon/features/status/containers/detailed_status_container.js +++ b/app/javascript/mastodon/features/status/containers/detailed_status_container.js @@ -128,12 +128,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ dispatch(mentionCompose(account, router)); }, - onOpenMedia (media, index) { - dispatch(openModal('MEDIA', { media, index })); + onOpenMedia (media, index, lang) { + dispatch(openModal('MEDIA', { media, index, lang })); }, - onOpenVideo (media, options) { - dispatch(openModal('VIDEO', { media, options })); + onOpenVideo (media, lang, options) { + dispatch(openModal('VIDEO', { media, lang, options })); }, onBlock (status) { diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index e77d687e8d..5f1715c27e 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -345,12 +345,12 @@ class Status extends ImmutablePureComponent { this.props.dispatch(mentionCompose(account, router)); }; - handleOpenMedia = (media, index) => { - this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index })); + handleOpenMedia = (media, index, lang) => { + this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index, lang })); }; - handleOpenVideo = (media, options) => { - this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, options })); + handleOpenVideo = (media, lang, options) => { + this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, lang, options })); }; handleHotkeyOpenMedia = e => { diff --git a/app/javascript/mastodon/features/ui/components/media_modal.jsx b/app/javascript/mastodon/features/ui/components/media_modal.jsx index e8293c2e78..594f5cf64f 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/media_modal.jsx @@ -3,7 +3,6 @@ import ReactSwipeableViews from 'react-swipeable-views'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import Video from 'mastodon/features/video'; -import { connect } from 'react-redux'; import classNames from 'classnames'; import { defineMessages, injectIntl } from 'react-intl'; import { IconButton } from 'mastodon/components/icon_button'; @@ -21,15 +20,12 @@ const messages = defineMessages({ next: { id: 'lightbox.next', defaultMessage: 'Next' }, }); -const mapStateToProps = (state, { statusId }) => ({ - language: state.getIn(['statuses', statusId, 'language']), -}); - class MediaModal extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.list.isRequired, statusId: PropTypes.string, + lang: PropTypes.string, index: PropTypes.number.isRequired, onClose: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, @@ -133,7 +129,7 @@ class MediaModal extends ImmutablePureComponent { }; render () { - const { media, language, statusId, intl, onClose } = this.props; + const { media, statusId, lang, intl, onClose } = this.props; const { navigationHidden } = this.state; const index = this.getIndex(); @@ -153,7 +149,7 @@ class MediaModal extends ImmutablePureComponent { width={width} height={height} alt={image.get('description')} - lang={language} + lang={lang} key={image.get('url')} onClick={this.toggleNavigation} zoomButtonHidden={this.state.zoomButtonHidden} @@ -176,7 +172,7 @@ class MediaModal extends ImmutablePureComponent { onCloseVideo={onClose} detailed alt={image.get('description')} - lang={language} + lang={lang} key={image.get('url')} /> ); @@ -188,7 +184,7 @@ class MediaModal extends ImmutablePureComponent { height={height} key={image.get('url')} alt={image.get('description')} - lang={language} + lang={lang} onClick={this.toggleNavigation} /> ); @@ -256,4 +252,4 @@ class MediaModal extends ImmutablePureComponent { } -export default connect(mapStateToProps, null, null, { forwardRef: true })(injectIntl(MediaModal)); +export default injectIntl(MediaModal); diff --git a/app/javascript/mastodon/features/video/index.jsx b/app/javascript/mastodon/features/video/index.jsx index 3d9d1aec6c..560dbc6d6a 100644 --- a/app/javascript/mastodon/features/video/index.jsx +++ b/app/javascript/mastodon/features/video/index.jsx @@ -469,7 +469,7 @@ class Video extends React.PureComponent { handleOpenVideo = () => { this.video.pause(); - this.props.onOpenVideo({ + this.props.onOpenVideo(this.props.lang, { startTime: this.video.currentTime, autoPlay: !this.state.paused, defaultVolume: this.state.volume, diff --git a/app/views/admin/reports/_media_attachments.html.haml b/app/views/admin/reports/_media_attachments.html.haml index d0b7d52c32..2305805a75 100644 --- a/app/views/admin/reports/_media_attachments.html.haml +++ b/app/views/admin/reports/_media_attachments.html.haml @@ -1,8 +1,8 @@ - if status.ordered_media_attachments.first.video? - video = status.ordered_media_attachments.first - = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), frameRate: video.file.meta.dig('original', 'frame_rate'), blurhash: video.blurhash, sensitive: status.sensitive?, visible: false, width: 610, height: 343, inline: true, alt: video.description, media: [ActiveModelSerializers::SerializableResource.new(video, serializer: REST::MediaAttachmentSerializer)].as_json + = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), frameRate: video.file.meta.dig('original', 'frame_rate'), blurhash: video.blurhash, sensitive: status.sensitive?, visible: false, width: 610, height: 343, inline: true, alt: video.description, lang: status.language, media: [ActiveModelSerializers::SerializableResource.new(video, serializer: REST::MediaAttachmentSerializer)].as_json - elsif status.ordered_media_attachments.first.audio? - audio = status.ordered_media_attachments.first - = react_component :audio, src: audio.file.url(:original), height: 110, alt: audio.description, duration: audio.file.meta.dig(:original, :duration) + = react_component :audio, src: audio.file.url(:original), height: 110, alt: audio.description, lang: status.language, duration: audio.file.meta.dig(:original, :duration) - else - = react_component :media_gallery, height: 343, sensitive: status.sensitive?, visible: false, media: status.ordered_media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } + = react_component :media_gallery, height: 343, sensitive: status.sensitive?, visible: false, lang: status.language, media: status.ordered_media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } From bf1a16e27886b854738b9447b311eb2a06b5a436 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 12 May 2023 03:25:43 -0400 Subject: [PATCH 14/98] Fix RSpec/SharedContext cop (#24847) --- .rubocop_todo.yml | 5 ----- spec/services/unsuspend_account_service_spec.rb | 6 +++--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a83fec8ae7..69d34dac31 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -648,11 +648,6 @@ RSpec/RepeatedExampleGroupDescription: - 'spec/controllers/admin/reports/actions_controller_spec.rb' - 'spec/policies/report_note_policy_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -RSpec/SharedContext: - Exclude: - - 'spec/services/unsuspend_account_service_spec.rb' - RSpec/StubbedMock: Exclude: - 'spec/controllers/api/base_controller_spec.rb' diff --git a/spec/services/unsuspend_account_service_spec.rb b/spec/services/unsuspend_account_service_spec.rb index 5d70120935..80285cc12b 100644 --- a/spec/services/unsuspend_account_service_spec.rb +++ b/spec/services/unsuspend_account_service_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe UnsuspendAccountService, type: :service do - shared_examples 'common behavior' do + shared_context 'with common context' do subject { described_class.new.call(account) } let!(:local_follower) { Fabricate(:user, current_sign_in_at: 1.hour.ago).account } @@ -36,7 +36,7 @@ RSpec.describe UnsuspendAccountService, type: :service do expect { subject }.to_not change { account.suspended? } end - include_examples 'common behavior' do + include_examples 'with common context' do let!(:account) { Fabricate(:account) } let!(:remote_follower) { Fabricate(:account, uri: 'https://alice.com', inbox_url: 'https://alice.com/inbox', protocol: :activitypub) } let!(:remote_reporter) { Fabricate(:account, uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) } @@ -61,7 +61,7 @@ RSpec.describe UnsuspendAccountService, type: :service do end describe 'unsuspending a remote account' do - include_examples 'common behavior' do + include_examples 'with common context' do let!(:account) { Fabricate(:account, domain: 'bob.com', uri: 'https://bob.com', inbox_url: 'https://bob.com/inbox', protocol: :activitypub) } let!(:resolve_account_service) { double } From 25002337093ecc90e9e207806e7e726dbc98825a Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 12 May 2023 06:25:32 -0400 Subject: [PATCH 15/98] Fix RSpec/RepeatedExampleGroupDescription cop (#24850) Co-authored-by: Claire --- .rubocop_todo.yml | 5 ----- .../admin/reports/actions_controller_spec.rb | 4 ++-- spec/policies/report_note_policy_spec.rb | 20 +++++++++---------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 69d34dac31..918a8efbad 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -643,11 +643,6 @@ RSpec/RepeatedExampleGroupBody: Exclude: - 'spec/controllers/statuses_controller_spec.rb' -RSpec/RepeatedExampleGroupDescription: - Exclude: - - 'spec/controllers/admin/reports/actions_controller_spec.rb' - - 'spec/policies/report_note_policy_spec.rb' - RSpec/StubbedMock: Exclude: - 'spec/controllers/api/base_controller_spec.rb' diff --git a/spec/controllers/admin/reports/actions_controller_spec.rb b/spec/controllers/admin/reports/actions_controller_spec.rb index a84f2324e1..701855f92e 100644 --- a/spec/controllers/admin/reports/actions_controller_spec.rb +++ b/spec/controllers/admin/reports/actions_controller_spec.rb @@ -146,13 +146,13 @@ describe Admin::Reports::ActionsController do end end - context 'with Action as submit button' do + context 'with action as submit button' do subject { post :create, params: common_params.merge({ action => '' }) } it_behaves_like 'all action types' end - context 'with Action as submit button' do + context 'with moderation action as an extra field' do subject { post :create, params: common_params.merge({ moderation_action: action }) } it_behaves_like 'all action types' diff --git a/spec/policies/report_note_policy_spec.rb b/spec/policies/report_note_policy_spec.rb index ea2a62ada9..a657fce4bd 100644 --- a/spec/policies/report_note_policy_spec.rb +++ b/spec/policies/report_note_policy_spec.rb @@ -30,19 +30,17 @@ RSpec.describe ReportNotePolicy do end end - context 'when admin?' do - context 'when owner?' do - it 'permit' do - report_note = Fabricate(:report_note, account: john) - expect(subject).to permit(john, report_note) - end + context 'when owner?' do + it 'permit' do + report_note = Fabricate(:report_note, account: john) + expect(subject).to permit(john, report_note) end + end - context 'with !owner?' do - it 'denies' do - report_note = Fabricate(:report_note) - expect(subject).to_not permit(john, report_note) - end + context 'with !owner?' do + it 'denies' do + report_note = Fabricate(:report_note) + expect(subject).to_not permit(john, report_note) end end end From c8998a2678c241bc2e6761c6cea97c1111ff38b2 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 12 May 2023 13:13:04 +0200 Subject: [PATCH 16/98] Change profile updates to be sent to recently-mentioned servers (#24852) --- app/lib/account_reach_finder.rb | 9 ++++- spec/lib/account_reach_finder_spec.rb | 53 +++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 spec/lib/account_reach_finder_spec.rb diff --git a/app/lib/account_reach_finder.rb b/app/lib/account_reach_finder.rb index 706ce8c1fb..481e254396 100644 --- a/app/lib/account_reach_finder.rb +++ b/app/lib/account_reach_finder.rb @@ -6,7 +6,7 @@ class AccountReachFinder end def inboxes - (followers_inboxes + reporters_inboxes + relay_inboxes).uniq + (followers_inboxes + reporters_inboxes + recently_mentioned_inboxes + relay_inboxes).uniq end private @@ -19,6 +19,13 @@ class AccountReachFinder Account.where(id: @account.targeted_reports.select(:account_id)).inboxes end + def recently_mentioned_inboxes + cutoff_id = Mastodon::Snowflake.id_at(2.days.ago, with_random: false) + recent_statuses = @account.statuses.recent.where(id: cutoff_id...).limit(200) + + Account.joins(:mentions).where(mentions: { status: recent_statuses }).inboxes.take(2000) + end + def relay_inboxes Relay.enabled.pluck(:inbox_url) end diff --git a/spec/lib/account_reach_finder_spec.rb b/spec/lib/account_reach_finder_spec.rb new file mode 100644 index 0000000000..1da95ba6b3 --- /dev/null +++ b/spec/lib/account_reach_finder_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe AccountReachFinder do + let(:account) { Fabricate(:account) } + + let(:follower1) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-1') } + let(:follower2) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-2') } + let(:follower3) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/users/a/inbox', shared_inbox_url: 'https://foo.bar/inbox') } + + let(:mentioned1) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://foo.bar/users/b/inbox', shared_inbox_url: 'https://foo.bar/inbox') } + let(:mentioned2) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-3') } + let(:mentioned3) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-4') } + + let(:unrelated_account) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/unrelated-inbox') } + + before do + follower1.follow!(account) + follower2.follow!(account) + follower3.follow!(account) + + Fabricate(:status, account: account).tap do |status| + status.mentions << Mention.new(account: follower1) + status.mentions << Mention.new(account: mentioned1) + end + + Fabricate(:status, account: account) + + Fabricate(:status, account: account).tap do |status| + status.mentions << Mention.new(account: mentioned2) + status.mentions << Mention.new(account: mentioned3) + end + + Fabricate(:status).tap do |status| + status.mentions << Mention.new(account: unrelated_account) + end + end + + describe '#inboxes' do + it 'includes the preferred inbox URL of followers' do + expect(described_class.new(account).inboxes).to include(*[follower1, follower2, follower3].map(&:preferred_inbox_url)) + end + + it 'includes the preferred inbox URL of recently-mentioned accounts' do + expect(described_class.new(account).inboxes).to include(*[mentioned1, mentioned2, mentioned3].map(&:preferred_inbox_url)) + end + + it 'does not include the inbox of unrelated users' do + expect(described_class.new(account).inboxes).to_not include(unrelated_account.preferred_inbox_url) + end + end +end From 7cf0ce19e25e15a595f28b0ca13c4ecbc30d599b Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Fri, 12 May 2023 08:46:16 -0300 Subject: [PATCH 17/98] Fix uncaught NoMethodError error in `/api/v1/admin/canonical_email_blocks/test` (#24947) Co-authored-by: Claire --- .../canonical_email_blocks_controller.rb | 2 +- .../canonical_email_blocks_controller_spec.rb | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb b/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb index 9ef1b3be71..7b192b979f 100644 --- a/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb +++ b/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb @@ -58,7 +58,7 @@ class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController end def set_canonical_email_blocks_from_test - @canonical_email_blocks = CanonicalEmailBlock.matching_email(params[:email]) + @canonical_email_blocks = CanonicalEmailBlock.matching_email(params.require(:email)) end def set_canonical_email_block diff --git a/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb b/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb index 3acae843ad..e5ee288827 100644 --- a/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb +++ b/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb @@ -20,4 +20,52 @@ describe Api::V1::Admin::CanonicalEmailBlocksController do expect(response).to have_http_status(200) end end + + describe 'POST #test' do + context 'when required email is not provided' do + it 'returns http bad request' do + post :test + + expect(response).to have_http_status(400) + end + end + + context 'when required email is provided' do + let(:params) { { email: 'example@email.com' } } + + context 'when there is a matching canonical email block' do + let!(:canonical_email_block) { CanonicalEmailBlock.create(params) } + + it 'returns http success' do + post :test, params: params + + expect(response).to have_http_status(200) + end + + it 'returns expected canonical email hash' do + post :test, params: params + + json = body_as_json + + expect(json[0][:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) + end + end + + context 'when there is no matching canonical email block' do + it 'returns http success' do + post :test, params: params + + expect(response).to have_http_status(200) + end + + it 'returns an empty list' do + post :test, params: params + + json = body_as_json + + expect(json).to be_empty + end + end + end + end end From 42ca35dcbc24272db7908278bfe6a0a687caba6c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 12 May 2023 12:53:30 -0400 Subject: [PATCH 18/98] I18n pluralization errors (#24963) --- Gemfile.lock | 2 +- .../shared/_error_messages.html.haml_spec.rb | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 spec/views/shared/_error_messages.html.haml_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index 594713c00e..a062ec88b2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -329,7 +329,7 @@ GEM httplog (1.6.2) rack (>= 2.0) rainbow (>= 2.0.0) - i18n (1.12.0) + i18n (1.13.0) concurrent-ruby (~> 1.0) i18n-tasks (1.0.12) activesupport (>= 4.0.2) diff --git a/spec/views/shared/_error_messages.html.haml_spec.rb b/spec/views/shared/_error_messages.html.haml_spec.rb new file mode 100644 index 0000000000..e7631345f1 --- /dev/null +++ b/spec/views/shared/_error_messages.html.haml_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'shared/_error_messages.html.haml' do + let(:status) { Status.new } + + before { status.errors.add :base, :invalid } + + context 'with a locale that has `one` and `other` plural values' do + around do |example| + I18n.with_locale(:en) do + example.run + end + end + + it 'renders the view with one error' do + render partial: 'shared/error_messages', locals: { object: status } + + expect(rendered).to match(/is invalid/) + end + end + + context 'with a locale that has only `other` plural value' do + around do |example| + I18n.with_locale(:my) do + example.run + end + end + + it 'renders the view with one error' do + render partial: 'shared/error_messages', locals: { object: status } + + expect(rendered).to match(/is invalid/) + end + end +end From 68d5c67b9c93d364cd9bf5b568780f3877307494 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 May 2023 15:09:33 +0200 Subject: [PATCH 19/98] Bump eslint from 8.39.0 to 8.40.0 (#24919) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 52 ++++++++++++++++++++++++++-------------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 7632388e5f..fbc4b1f37c 100644 --- a/package.json +++ b/package.json @@ -179,7 +179,7 @@ "@typescript-eslint/eslint-plugin": "^5.59.5", "@typescript-eslint/parser": "^5.59.5", "babel-jest": "^29.5.0", - "eslint": "^8.39.0", + "eslint": "^8.40.0", "eslint-config-prettier": "^8.8.0", "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-formatjs": "^4.10.1", diff --git a/yarn.lock b/yarn.lock index ed7ab9e579..e10064228d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1245,14 +1245,14 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.4.0.tgz#3e61c564fcd6b921cb789838631c5ee44df09403" integrity sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ== -"@eslint/eslintrc@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.2.tgz#01575e38707add677cf73ca1589abba8da899a02" - integrity sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ== +"@eslint/eslintrc@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331" + integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.5.1" + espree "^9.5.2" globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -1260,10 +1260,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.39.0": - version "8.39.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.39.0.tgz#58b536bcc843f4cd1e02a7e6171da5c040f4d44b" - integrity sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng== +"@eslint/js@8.40.0": + version "8.40.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.40.0.tgz#3ba73359e11f5a7bd3e407f70b3528abfae69cec" + integrity sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA== "@floating-ui/core@^1.0.1": version "1.0.1" @@ -5234,20 +5234,20 @@ eslint-scope@^7.2.0: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" - integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" + integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== -eslint@^8.39.0: - version "8.39.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.39.0.tgz#7fd20a295ef92d43809e914b70c39fd5a23cf3f1" - integrity sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og== +eslint@^8.40.0: + version "8.40.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.40.0.tgz#a564cd0099f38542c4e9a2f630fa45bf33bc42a4" + integrity sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" - "@eslint/eslintrc" "^2.0.2" - "@eslint/js" "8.39.0" + "@eslint/eslintrc" "^2.0.3" + "@eslint/js" "8.40.0" "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -5258,8 +5258,8 @@ eslint@^8.39.0: doctrine "^3.0.0" escape-string-regexp "^4.0.0" eslint-scope "^7.2.0" - eslint-visitor-keys "^3.4.0" - espree "^9.5.1" + eslint-visitor-keys "^3.4.1" + espree "^9.5.2" esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -5285,14 +5285,14 @@ eslint@^8.39.0: strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.5.1: - version "9.5.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4" - integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg== +espree@^9.5.2: + version "9.5.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b" + integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw== dependencies: acorn "^8.8.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.0" + eslint-visitor-keys "^3.4.1" esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" From dae330bf3280b874ddd47d89a56f87b9633c89b0 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Mon, 15 May 2023 09:40:24 +0200 Subject: [PATCH 20/98] Bump `mkdirp` major version (#24978) --- config/webpack/generateLocalePacks.js | 4 +--- package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/config/webpack/generateLocalePacks.js b/config/webpack/generateLocalePacks.js index b71cf2ade3..be2f35ef4e 100644 --- a/config/webpack/generateLocalePacks.js +++ b/config/webpack/generateLocalePacks.js @@ -5,7 +5,7 @@ const fs = require('fs'); const path = require('path'); const rimraf = require('rimraf'); -const mkdirp = require('mkdirp'); +const { mkdirp } = require('mkdirp'); const localesJsonPath = path.join(__dirname, '../../app/javascript/mastodon/locales'); const locales = fs.readdirSync(localesJsonPath).filter(filename => { @@ -48,5 +48,3 @@ setLocale({messages, localeData}); }); module.exports = outPaths; - - diff --git a/package.json b/package.json index fbc4b1f37c..d1d1ba99ea 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "mark-loader": "^0.1.6", "marky": "^1.2.5", "mini-css-extract-plugin": "^1.6.2", - "mkdirp": "^2.1.6", + "mkdirp": "^3.0.1", "npmlog": "^7.0.1", "path-complete-extname": "^1.0.0", "pg": "^8.5.0", diff --git a/yarn.lock b/yarn.lock index e10064228d..9b8228bd8a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8251,10 +8251,10 @@ mkdirp@^1.0, mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mkdirp@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" - integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== +mkdirp@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== mousetrap@^1.5.2: version "1.6.5" From 540b446f77a06d38261fc4ca55c7dd450c4a6cfa Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 15 May 2023 11:25:04 -0400 Subject: [PATCH 21/98] Replace i18n view spec with helper spec (#24966) --- spec/locales/i18n_spec.rb | 35 ++++++++++++++++++ .../shared/_error_messages.html.haml_spec.rb | 37 ------------------- 2 files changed, 35 insertions(+), 37 deletions(-) create mode 100644 spec/locales/i18n_spec.rb delete mode 100644 spec/views/shared/_error_messages.html.haml_spec.rb diff --git a/spec/locales/i18n_spec.rb b/spec/locales/i18n_spec.rb new file mode 100644 index 0000000000..cfce8e2234 --- /dev/null +++ b/spec/locales/i18n_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'I18n' do + describe 'Pluralizing locale translations' do + subject { I18n.t('generic.validation_errors', count: 1) } + + context 'with the `en` locale which has `one` and `other` plural values' do + around do |example| + I18n.with_locale(:en) do + example.run + end + end + + it 'translates to `en` correctly and without error' do + expect { subject }.to_not raise_error + expect(subject).to match(/the error below/) + end + end + + context 'with the `my` locale which has only `other` plural value' do + around do |example| + I18n.with_locale(:my) do + example.run + end + end + + it 'translates to `my` correctly and without error' do + expect { subject }.to_not raise_error + expect(subject).to match(/1/) + end + end + end +end diff --git a/spec/views/shared/_error_messages.html.haml_spec.rb b/spec/views/shared/_error_messages.html.haml_spec.rb deleted file mode 100644 index e7631345f1..0000000000 --- a/spec/views/shared/_error_messages.html.haml_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe 'shared/_error_messages.html.haml' do - let(:status) { Status.new } - - before { status.errors.add :base, :invalid } - - context 'with a locale that has `one` and `other` plural values' do - around do |example| - I18n.with_locale(:en) do - example.run - end - end - - it 'renders the view with one error' do - render partial: 'shared/error_messages', locals: { object: status } - - expect(rendered).to match(/is invalid/) - end - end - - context 'with a locale that has only `other` plural value' do - around do |example| - I18n.with_locale(:my) do - example.run - end - end - - it 'renders the view with one error' do - render partial: 'shared/error_messages', locals: { object: status } - - expect(rendered).to match(/is invalid/) - end - end -end From 74c77c5274c57ffcdaa3bcdb63af844fe07475df Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 15 May 2023 14:20:13 -0400 Subject: [PATCH 22/98] Remove usage of random sample values in specs (#24869) --- spec/controllers/auth/registrations_controller_spec.rb | 2 +- spec/fabricators/notification_fabricator.rb | 2 +- spec/mailers/notification_mailer_spec.rb | 2 +- spec/mailers/user_mailer_spec.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb index 42b2606ad2..a80273635d 100644 --- a/spec/controllers/auth/registrations_controller_spec.rb +++ b/spec/controllers/auth/registrations_controller_spec.rb @@ -97,7 +97,7 @@ RSpec.describe Auth::RegistrationsController do end describe 'POST #create' do - let(:accept_language) { Rails.application.config.i18n.available_locales.sample.to_s } + let(:accept_language) { 'de' } before do session[:registration_form_time] = 5.seconds.ago diff --git a/spec/fabricators/notification_fabricator.rb b/spec/fabricators/notification_fabricator.rb index 959fda913b..1e0c809874 100644 --- a/spec/fabricators/notification_fabricator.rb +++ b/spec/fabricators/notification_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:notification) do - activity fabricator: [:mention, :status, :follow, :follow_request, :favourite].sample + activity fabricator: :status account end diff --git a/spec/mailers/notification_mailer_spec.rb b/spec/mailers/notification_mailer_spec.rb index 3113d05c37..73c751def1 100644 --- a/spec/mailers/notification_mailer_spec.rb +++ b/spec/mailers/notification_mailer_spec.rb @@ -10,7 +10,7 @@ RSpec.describe NotificationMailer do shared_examples 'localized subject' do |*args, **kwrest| it 'renders subject localized for the locale of the receiver' do - locale = %i(de en).sample + locale = :de receiver.update!(locale: locale) expect(mail.subject).to eq I18n.t(*args, **kwrest.merge(locale: locale)) end diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 6144b2bbb8..a4f6c145ab 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -7,7 +7,7 @@ describe UserMailer do shared_examples 'localized subject' do |*args, **kwrest| it 'renders subject localized for the locale of the receiver' do - locale = I18n.available_locales.sample + locale = :de receiver.update!(locale: locale) expect(mail.subject).to eq I18n.t(*args, **kwrest.merge(locale: locale)) end From da2d3cc79570baec1454d53bc7f6e0cdb49178d4 Mon Sep 17 00:00:00 2001 From: Steven Munn Date: Mon, 15 May 2023 13:42:07 -0700 Subject: [PATCH 23/98] Fix spelling of "Lets" on the onboarding page after clicking the confirmation email (#24959) Co-authored-by: Steven Munn Co-authored-by: Claire --- app/javascript/mastodon/features/onboarding/index.jsx | 2 +- app/javascript/mastodon/locales/defaultMessages.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/mastodon/features/onboarding/index.jsx b/app/javascript/mastodon/features/onboarding/index.jsx index 8b373a014c..ca4a8bcf13 100644 --- a/app/javascript/mastodon/features/onboarding/index.jsx +++ b/app/javascript/mastodon/features/onboarding/index.jsx @@ -115,7 +115,7 @@ class Onboarding extends ImmutablePureComponent {
0 && account.get('note').length > 0)} icon='address-book-o' label={} description={} /> - = 7} icon='user-plus' label={} description={} /> + = 7} icon='user-plus' label={} description={} /> = 1} icon='pencil-square-o' label={} description={} /> } description={} />
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 6b1b51702f..c64a970ead 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -3236,7 +3236,7 @@ "id": "onboarding.steps.follow_people.title" }, { - "defaultMessage": "You curate your own feed. Lets fill it with interesting people.", + "defaultMessage": "You curate your own feed. Let's fill it with interesting people.", "id": "onboarding.steps.follow_people.body" }, { From 1e31cc01b83ce115546a77dc6144bde9c3f3d296 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 10:10:02 +0200 Subject: [PATCH 24/98] Bump eslint-plugin-jsdoc from 43.1.1 to 44.2.4 (#24994) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index d1d1ba99ea..6691d5bdde 100644 --- a/package.json +++ b/package.json @@ -184,7 +184,7 @@ "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-formatjs": "^4.10.1", "eslint-plugin-import": "~2.27.5", - "eslint-plugin-jsdoc": "^43.1.1", + "eslint-plugin-jsdoc": "^44.2.4", "eslint-plugin-jsx-a11y": "~6.7.1", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-promise": "~6.1.1", diff --git a/yarn.lock b/yarn.lock index 9b8228bd8a..13c25877bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1224,10 +1224,10 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== -"@es-joy/jsdoccomment@~0.37.1": - version "0.37.1" - resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.37.1.tgz#fa32a41ba12097452693343e09ad4d26d157aedd" - integrity sha512-5vxWJ1gEkEF0yRd0O+uK6dHJf7adrxwQSX8PuRiPfFSAbNLnY0ZJfXaZucoz14Jj2N11xn2DnlEPwWRpYpvRjg== +"@es-joy/jsdoccomment@~0.39.3": + version "0.39.3" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.39.3.tgz#76b55203bf447d608e4e299ecb62d7ef14db72bb" + integrity sha512-q6pObzaS+aTA96kl4DF91QILNpSiDE8S89cQdJnhIc7hWzwIHPnfBnsiBVa0Z/R9pLHdZTnXEMnggGMmCq7HmA== dependencies: comment-parser "1.3.1" esquery "^1.5.0" @@ -5136,18 +5136,18 @@ eslint-plugin-import@~2.27.5: semver "^6.3.0" tsconfig-paths "^3.14.1" -eslint-plugin-jsdoc@^43.1.1: - version "43.1.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-43.1.1.tgz#fc72ba21597cc99b1a0dc988aebb9bb57d0ec492" - integrity sha512-J2kjjsJ5vBXSyNzqJhceeSGTAgVgZHcPSJKo3vD4tNjUdfky98rR2VfZUDsS1GKL6isyVa8GWvr+Az7Vyg2HXA== +eslint-plugin-jsdoc@^44.2.4: + version "44.2.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-44.2.4.tgz#0bdc163771504ec7330414eda6a7dbae67156ddb" + integrity sha512-/EMMxCyRh1SywhCb66gAqoGX4Yv6Xzc4bsSkF1AiY2o2+bQmGMQ05QZ5+JjHbdFTPDZY9pfn+DsSNP0a5yQpIg== dependencies: - "@es-joy/jsdoccomment" "~0.37.1" + "@es-joy/jsdoccomment" "~0.39.3" are-docs-informative "^0.0.2" comment-parser "1.3.1" debug "^4.3.4" escape-string-regexp "^4.0.0" esquery "^1.5.0" - semver "^7.5.0" + semver "^7.5.1" spdx-expression-parse "^3.0.1" eslint-plugin-jsx-a11y@~6.7.1: @@ -10374,10 +10374,10 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.0: - version "7.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.0.tgz#ed8c5dc8efb6c629c88b23d41dc9bf40c1d96cd0" - integrity sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA== +semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.1: + version "7.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" + integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== dependencies: lru-cache "^6.0.0" From 097d4dded10b4f6c207af585dcc4afe43251ddee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 10:45:53 +0200 Subject: [PATCH 25/98] Bump @typescript-eslint/parser from 5.59.5 to 5.59.6 (#25000) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 48 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 6691d5bdde..f4708f608a 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "@types/webpack": "^4.41.33", "@types/yargs": "^17.0.24", "@typescript-eslint/eslint-plugin": "^5.59.5", - "@typescript-eslint/parser": "^5.59.5", + "@typescript-eslint/parser": "^5.59.6", "babel-jest": "^29.5.0", "eslint": "^8.40.0", "eslint-config-prettier": "^8.8.0", diff --git a/yarn.lock b/yarn.lock index 13c25877bd..77f93390a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2503,14 +2503,14 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.5.tgz#63064f5eafbdbfb5f9dfbf5c4503cdf949852981" - integrity sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw== +"@typescript-eslint/parser@^5.59.6": + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.6.tgz#bd36f71f5a529f828e20b627078d3ed6738dbb40" + integrity sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA== dependencies: - "@typescript-eslint/scope-manager" "5.59.5" - "@typescript-eslint/types" "5.59.5" - "@typescript-eslint/typescript-estree" "5.59.5" + "@typescript-eslint/scope-manager" "5.59.6" + "@typescript-eslint/types" "5.59.6" + "@typescript-eslint/typescript-estree" "5.59.6" debug "^4.3.4" "@typescript-eslint/scope-manager@5.59.5": @@ -2521,6 +2521,14 @@ "@typescript-eslint/types" "5.59.5" "@typescript-eslint/visitor-keys" "5.59.5" +"@typescript-eslint/scope-manager@5.59.6": + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz#d43a3687aa4433868527cfe797eb267c6be35f19" + integrity sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ== + dependencies: + "@typescript-eslint/types" "5.59.6" + "@typescript-eslint/visitor-keys" "5.59.6" + "@typescript-eslint/type-utils@5.59.5": version "5.59.5" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.5.tgz#485b0e2c5b923460bc2ea6b338c595343f06fc9b" @@ -2541,6 +2549,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.5.tgz#e63c5952532306d97c6ea432cee0981f6d2258c7" integrity sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w== +"@typescript-eslint/types@5.59.6": + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.6.tgz#5a6557a772af044afe890d77c6a07e8c23c2460b" + integrity sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA== + "@typescript-eslint/typescript-estree@5.59.0": version "5.59.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.0.tgz#8869156ee1dcfc5a95be3ed0e2809969ea28e965" @@ -2567,6 +2580,19 @@ semver "^7.3.7" tsutils "^3.21.0" +"@typescript-eslint/typescript-estree@5.59.6": + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz#2fb80522687bd3825504925ea7e1b8de7bb6251b" + integrity sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA== + dependencies: + "@typescript-eslint/types" "5.59.6" + "@typescript-eslint/visitor-keys" "5.59.6" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + "@typescript-eslint/utils@5.59.5": version "5.59.5" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.5.tgz#15b3eb619bb223302e60413adb0accd29c32bcae" @@ -2597,6 +2623,14 @@ "@typescript-eslint/types" "5.59.5" eslint-visitor-keys "^3.3.0" +"@typescript-eslint/visitor-keys@5.59.6": + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz#673fccabf28943847d0c8e9e8d008e3ada7be6bb" + integrity sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q== + dependencies: + "@typescript-eslint/types" "5.59.6" + eslint-visitor-keys "^3.3.0" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" From 46c90ce2a579a18424e7336700d805d35e53ce24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 10:51:02 +0200 Subject: [PATCH 26/98] Bump @typescript-eslint/eslint-plugin from 5.59.5 to 5.59.6 (#24998) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 74 ++++++++++++++-------------------------------------- 2 files changed, 21 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index f4708f608a..ba309ffe39 100644 --- a/package.json +++ b/package.json @@ -176,7 +176,7 @@ "@types/uuid": "^9.0.0", "@types/webpack": "^4.41.33", "@types/yargs": "^17.0.24", - "@typescript-eslint/eslint-plugin": "^5.59.5", + "@typescript-eslint/eslint-plugin": "^5.59.6", "@typescript-eslint/parser": "^5.59.6", "babel-jest": "^29.5.0", "eslint": "^8.40.0", diff --git a/yarn.lock b/yarn.lock index 77f93390a5..fc387b684b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2487,15 +2487,15 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.5.tgz#f156827610a3f8cefc56baeaa93cd4a5f32966b4" - integrity sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg== +"@typescript-eslint/eslint-plugin@^5.59.6": + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz#a350faef1baa1e961698240f922d8de1761a9e2b" + integrity sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.59.5" - "@typescript-eslint/type-utils" "5.59.5" - "@typescript-eslint/utils" "5.59.5" + "@typescript-eslint/scope-manager" "5.59.6" + "@typescript-eslint/type-utils" "5.59.6" + "@typescript-eslint/utils" "5.59.6" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" @@ -2513,14 +2513,6 @@ "@typescript-eslint/typescript-estree" "5.59.6" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.5.tgz#33ffc7e8663f42cfaac873de65ebf65d2bce674d" - integrity sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A== - dependencies: - "@typescript-eslint/types" "5.59.5" - "@typescript-eslint/visitor-keys" "5.59.5" - "@typescript-eslint/scope-manager@5.59.6": version "5.59.6" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz#d43a3687aa4433868527cfe797eb267c6be35f19" @@ -2529,13 +2521,13 @@ "@typescript-eslint/types" "5.59.6" "@typescript-eslint/visitor-keys" "5.59.6" -"@typescript-eslint/type-utils@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.5.tgz#485b0e2c5b923460bc2ea6b338c595343f06fc9b" - integrity sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg== +"@typescript-eslint/type-utils@5.59.6": + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz#37c51d2ae36127d8b81f32a0a4d2efae19277c48" + integrity sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ== dependencies: - "@typescript-eslint/typescript-estree" "5.59.5" - "@typescript-eslint/utils" "5.59.5" + "@typescript-eslint/typescript-estree" "5.59.6" + "@typescript-eslint/utils" "5.59.6" debug "^4.3.4" tsutils "^3.21.0" @@ -2544,11 +2536,6 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.0.tgz#3fcdac7dbf923ec5251545acdd9f1d42d7c4fe32" integrity sha512-yR2h1NotF23xFFYKHZs17QJnB51J/s+ud4PYU4MqdZbzeNxpgUr05+dNeCN/bb6raslHvGdd6BFCkVhpPk/ZeA== -"@typescript-eslint/types@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.5.tgz#e63c5952532306d97c6ea432cee0981f6d2258c7" - integrity sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w== - "@typescript-eslint/types@5.59.6": version "5.59.6" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.6.tgz#5a6557a772af044afe890d77c6a07e8c23c2460b" @@ -2567,19 +2554,6 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz#9b252ce55dd765e972a7a2f99233c439c5101e42" - integrity sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg== - dependencies: - "@typescript-eslint/types" "5.59.5" - "@typescript-eslint/visitor-keys" "5.59.5" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - "@typescript-eslint/typescript-estree@5.59.6": version "5.59.6" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz#2fb80522687bd3825504925ea7e1b8de7bb6251b" @@ -2593,17 +2567,17 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.5.tgz#15b3eb619bb223302e60413adb0accd29c32bcae" - integrity sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA== +"@typescript-eslint/utils@5.59.6": + version "5.59.6" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.6.tgz#82960fe23788113fc3b1f9d4663d6773b7907839" + integrity sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.59.5" - "@typescript-eslint/types" "5.59.5" - "@typescript-eslint/typescript-estree" "5.59.5" + "@typescript-eslint/scope-manager" "5.59.6" + "@typescript-eslint/types" "5.59.6" + "@typescript-eslint/typescript-estree" "5.59.6" eslint-scope "^5.1.1" semver "^7.3.7" @@ -2615,14 +2589,6 @@ "@typescript-eslint/types" "5.59.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@5.59.5": - version "5.59.5" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.5.tgz#ba5b8d6791a13cf9fea6716af1e7626434b29b9b" - integrity sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA== - dependencies: - "@typescript-eslint/types" "5.59.5" - eslint-visitor-keys "^3.3.0" - "@typescript-eslint/visitor-keys@5.59.6": version "5.59.6" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz#673fccabf28943847d0c8e9e8d008e3ada7be6bb" From 67c5ac497134021468151c423f9773710ef9140c Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 16 May 2023 04:51:59 -0400 Subject: [PATCH 27/98] Autofix Rubocop Lint/AmbiguousOperatorPrecedence (#25002) --- .rubocop_todo.yml | 5 ----- config/initializers/rack_attack.rb | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 918a8efbad..c1a845bfa4 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -94,11 +94,6 @@ Lint/AmbiguousBlockAssociation: - 'spec/services/unsuspend_account_service_spec.rb' - 'spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -Lint/AmbiguousOperatorPrecedence: - Exclude: - - 'config/initializers/rack_attack.rb' - # Configuration parameters: AllowComments, AllowEmptyLambdas. Lint/EmptyBlock: Exclude: diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index 54b5746661..5cc4e9380a 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -145,7 +145,7 @@ class Rack::Attack 'Content-Type' => 'application/json', 'X-RateLimit-Limit' => match_data[:limit].to_s, 'X-RateLimit-Remaining' => '0', - 'X-RateLimit-Reset' => (now + (match_data[:period] - now.to_i % match_data[:period])).iso8601(6), + 'X-RateLimit-Reset' => (now + (match_data[:period] - (now.to_i % match_data[:period]))).iso8601(6), } [429, headers, [{ error: I18n.t('errors.429') }.to_json]] From 6bef989cf80d33f18692d1ff14d86f950d5af12f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 10:52:07 +0200 Subject: [PATCH 28/98] Bump capybara from 3.39.0 to 3.39.1 (#25004) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a062ec88b2..e17ddcfcfa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -166,7 +166,7 @@ GEM sshkit (~> 1.3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (3.39.0) + capybara (3.39.1) addressable matrix mini_mime (>= 0.1.3) @@ -416,7 +416,7 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2023.0218.1) mini_mime (1.1.2) - mini_portile2 (2.8.1) + mini_portile2 (2.8.2) minitest (5.18.0) msgpack (1.7.0) multi_json (1.15.0) From 250091927e0ae9d09b1e793b7ce6321c294fe93d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 10:58:36 +0200 Subject: [PATCH 29/98] Bump thor from 1.2.1 to 1.2.2 (#25001) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e17ddcfcfa..20cb29355d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -696,7 +696,7 @@ GEM unicode-display_width (>= 1.1.1, < 3) terrapin (0.6.0) climate_control (>= 0.0.3, < 1.0) - thor (1.2.1) + thor (1.2.2) tilt (2.1.0) timeout (0.3.2) tpm-key_attestation (0.12.0) From 99f1302cc39e7024b1da36ac0f6ecce0711244b0 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 16 May 2023 14:56:49 +0200 Subject: [PATCH 30/98] Fix reports not being closed when performing batch suspensions (#24988) --- app/models/form/account_batch.rb | 11 +++++ spec/models/form/account_batch_spec.rb | 63 ++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 spec/models/form/account_batch_spec.rb diff --git a/app/models/form/account_batch.rb b/app/models/form/account_batch.rb index 6a05f8163a..4665a58679 100644 --- a/app/models/form/account_batch.rb +++ b/app/models/form/account_batch.rb @@ -123,7 +123,18 @@ class Form::AccountBatch account: current_account, action: :suspend ) + Admin::SuspensionWorker.perform_async(account.id) + + # Suspending a single account closes their associated reports, so + # mass-suspending would be consistent. + Report.where(target_account: account).unresolved.find_each do |report| + authorize(report, :update?) + log_action(:resolve, report) + report.resolve!(current_account) + rescue Mastodon::NotPermittedError + # This should not happen, but just in case, do not fail early + end end def approve_account(account) diff --git a/spec/models/form/account_batch_spec.rb b/spec/models/form/account_batch_spec.rb new file mode 100644 index 0000000000..fd8e909010 --- /dev/null +++ b/spec/models/form/account_batch_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Form::AccountBatch do + let(:account_batch) { described_class.new } + + describe '#save' do + subject { account_batch.save } + + let(:account) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account } + let(:account_ids) { [] } + let(:query) { Account.none } + + before do + account_batch.assign_attributes( + action: action, + current_account: account, + account_ids: account_ids, + query: query, + select_all_matching: select_all_matching + ) + end + + context 'when action is "suspend"' do + let(:action) { 'suspend' } + + let(:target_account) { Fabricate(:account) } + let(:target_account2) { Fabricate(:account) } + + before do + Fabricate(:report, target_account: target_account) + Fabricate(:report, target_account: target_account2) + end + + context 'when accounts are passed as account_ids' do + let(:select_all_matching) { '0' } + let(:account_ids) { [target_account.id, target_account2.id] } + + it 'suspends the expected users' do + expect { subject }.to change { [target_account.reload.suspended?, target_account2.reload.suspended?] }.from([false, false]).to([true, true]) + end + + it 'closes open reports targeting the suspended users' do + expect { subject }.to change { Report.unresolved.where(target_account: [target_account, target_account2]).count }.from(2).to(0) + end + end + + context 'when accounts are passed as a query' do + let(:select_all_matching) { '1' } + let(:query) { Account.where(id: [target_account.id, target_account2.id]) } + + it 'suspends the expected users' do + expect { subject }.to change { [target_account.reload.suspended?, target_account2.reload.suspended?] }.from([false, false]).to([true, true]) + end + + it 'closes open reports targeting the suspended users' do + expect { subject }.to change { Report.unresolved.where(target_account: [target_account, target_account2]).count }.from(2).to(0) + end + end + end + end +end From 74ff0cd2d5a3fe7e48172b84b37fdfbf9882f29d Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 16 May 2023 14:59:44 +0200 Subject: [PATCH 31/98] Change composer highlight border size to be more noticeable (#25010) --- app/javascript/styles/mastodon/components.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 1842d3bc8b..667bc03ffc 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3118,7 +3118,7 @@ $ui-header-height: 55px; &.active { transition: none; - box-shadow: 0 0 0 2px rgba(lighten($highlight-text-color, 8%), 0.7); + box-shadow: 0 0 0 6px rgba(lighten($highlight-text-color, 8%), 0.7); } } From d2eef78d1c9689879fb81fc9dacc02b5d70220f9 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 16 May 2023 15:36:25 +0200 Subject: [PATCH 32/98] Fix being unable to load past a full page of filtered posts in Home timeline (#24930) --- app/javascript/mastodon/components/status_list.jsx | 4 +++- .../mastodon/features/ui/containers/status_list_container.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/status_list.jsx b/app/javascript/mastodon/components/status_list.jsx index 3d513bbf86..34b7732787 100644 --- a/app/javascript/mastodon/components/status_list.jsx +++ b/app/javascript/mastodon/components/status_list.jsx @@ -26,6 +26,7 @@ export default class StatusList extends ImmutablePureComponent { alwaysPrepend: PropTypes.bool, withCounters: PropTypes.bool, timelineId: PropTypes.string, + lastId: PropTypes.string, }; static defaultProps = { @@ -55,7 +56,8 @@ export default class StatusList extends ImmutablePureComponent { }; handleLoadOlder = debounce(() => { - this.props.onLoadMore(this.props.statusIds.size > 0 ? this.props.statusIds.last() : undefined); + const { statusIds, lastId, onLoadMore } = this.props; + onLoadMore(lastId || (statusIds.size > 0 ? statusIds.last() : undefined)); }, 300, { leading: true }); _selectChild (index, align_top) { diff --git a/app/javascript/mastodon/features/ui/containers/status_list_container.js b/app/javascript/mastodon/features/ui/containers/status_list_container.js index 4ce4ac6c8c..8e97460c4b 100644 --- a/app/javascript/mastodon/features/ui/containers/status_list_container.js +++ b/app/javascript/mastodon/features/ui/containers/status_list_container.js @@ -37,6 +37,7 @@ const makeMapStateToProps = () => { const mapStateToProps = (state, { timelineId }) => ({ statusIds: getStatusIds(state, { type: timelineId }), + lastId: state.getIn(['timelines', timelineId, 'items'])?.last(), isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true), isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false), hasMore: state.getIn(['timelines', timelineId, 'hasMore']), From afb74b97b237a32c5e1db2a6a9968d587a3d5dd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 15:39:18 +0200 Subject: [PATCH 33/98] Bump pg-connection-string from 2.5.0 to 2.6.0 (#24999) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index ba309ffe39..5cf1f5aba1 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "npmlog": "^7.0.1", "path-complete-extname": "^1.0.0", "pg": "^8.5.0", - "pg-connection-string": "^2.5.0", + "pg-connection-string": "^2.6.0", "postcss": "^8.4.23", "postcss-loader": "^4.3.0", "prop-types": "^15.8.1", diff --git a/yarn.lock b/yarn.lock index fc387b684b..58b8e03e54 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8902,15 +8902,10 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -pg-connection-string@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.4.0.tgz#c979922eb47832999a204da5dbe1ebf2341b6a10" - integrity sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ== - -pg-connection-string@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" - integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== +pg-connection-string@^2.4.0, pg-connection-string@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.0.tgz#12a36cc4627df19c25cc1b9b736cc39ee1f73ae8" + integrity sha512-x14ibktcwlHKoHxx9X3uTVW9zIGR41ZB6QNhHb21OPNdCCO3NaRnpJuwKIQSR4u+Yqjx4HCvy7Hh7VSy1U4dGg== pg-int8@1.0.1: version "1.0.1" From 86961b5b7b459fd778a9b697bbdfbda03512babb Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 16 May 2023 18:03:52 +0200 Subject: [PATCH 34/98] Add polling and automatic redirection to `/start` on email confirmation (#25013) --- .../api/v1/emails/confirmations_controller.rb | 11 ++- app/javascript/packs/sign_up.js | 15 ++++ app/views/auth/setup/show.html.haml | 2 + config/routes/api.rb | 1 + .../emails/confirmations_controller_spec.rb | 68 +++++++++++++++++++ 5 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 app/javascript/packs/sign_up.js diff --git a/app/controllers/api/v1/emails/confirmations_controller.rb b/app/controllers/api/v1/emails/confirmations_controller.rb index 32fb8e39fa..29ff897b91 100644 --- a/app/controllers/api/v1/emails/confirmations_controller.rb +++ b/app/controllers/api/v1/emails/confirmations_controller.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true class Api::V1::Emails::ConfirmationsController < Api::BaseController - before_action -> { doorkeeper_authorize! :write, :'write:accounts' } - before_action :require_user_owned_by_application! - before_action :require_user_not_confirmed! + before_action -> { authorize_if_got_token! :read, :'read:accounts' }, only: :check + before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :check + before_action :require_user_owned_by_application!, except: :check + before_action :require_user_not_confirmed!, except: :check def create current_user.update!(email: params[:email]) if params.key?(:email) @@ -12,6 +13,10 @@ class Api::V1::Emails::ConfirmationsController < Api::BaseController render_empty end + def check + render json: current_user.confirmed? + end + private def require_user_owned_by_application! diff --git a/app/javascript/packs/sign_up.js b/app/javascript/packs/sign_up.js new file mode 100644 index 0000000000..260e404eb7 --- /dev/null +++ b/app/javascript/packs/sign_up.js @@ -0,0 +1,15 @@ +import './public-path'; +import ready from '../mastodon/ready'; +import axios from 'axios'; + +ready(() => { + setInterval(() => { + axios.get('/api/v1/emails/check_confirmation').then((response) => { + if (response.data) { + window.location = '/start'; + } + }).catch(error => { + console.error(error); + }); + }, 5000); +}); diff --git a/app/views/auth/setup/show.html.haml b/app/views/auth/setup/show.html.haml index 913b0c9132..64deca3345 100644 --- a/app/views/auth/setup/show.html.haml +++ b/app/views/auth/setup/show.html.haml @@ -1,6 +1,8 @@ - content_for :page_title do = t('auth.setup.title') += javascript_pack_tag 'sign_up', crossorigin: 'anonymous' + = simple_form_for(@user, url: auth_setup_path) do |f| = render 'auth/shared/progress', stage: 'confirm' diff --git a/config/routes/api.rb b/config/routes/api.rb index 91d5def821..19c583b3e1 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -109,6 +109,7 @@ namespace :api, format: false do namespace :emails do resources :confirmations, only: [:create] + get :check_confirmation, to: 'confirmations#check' end resource :instance, only: [:show] do diff --git a/spec/controllers/api/v1/emails/confirmations_controller_spec.rb b/spec/controllers/api/v1/emails/confirmations_controller_spec.rb index 2a0703ed9c..219b5075df 100644 --- a/spec/controllers/api/v1/emails/confirmations_controller_spec.rb +++ b/spec/controllers/api/v1/emails/confirmations_controller_spec.rb @@ -63,4 +63,72 @@ RSpec.describe Api::V1::Emails::ConfirmationsController do end end end + + describe '#check' do + let(:scopes) { 'read' } + + context 'with an oauth token' do + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + context 'when the account is not confirmed' do + it 'returns http success' do + get :check + expect(response).to have_http_status(200) + end + + it 'returns false' do + get :check + expect(body_as_json).to be false + end + end + + context 'when the account is confirmed' do + let(:confirmed_at) { Time.now.utc } + + it 'returns http success' do + get :check + expect(response).to have_http_status(200) + end + + it 'returns true' do + get :check + expect(body_as_json).to be true + end + end + end + + context 'with an authentication cookie' do + before do + sign_in user, scope: :user + end + + context 'when the account is not confirmed' do + it 'returns http success' do + get :check + expect(response).to have_http_status(200) + end + + it 'returns false' do + get :check + expect(body_as_json).to be false + end + end + + context 'when the account is confirmed' do + let(:confirmed_at) { Time.now.utc } + + it 'returns http success' do + get :check + expect(response).to have_http_status(200) + end + + it 'returns true' do + get :check + expect(body_as_json).to be true + end + end + end + end end From 844d8e101e84cc1c27c99005b7059a975ac826ca Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 16 May 2023 23:27:35 +0200 Subject: [PATCH 35/98] Add hCaptcha support (#25019) --- Gemfile | 2 + Gemfile.lock | 3 + .../auth/confirmations_controller.rb | 42 +++++++++++++ app/controllers/concerns/captcha_concern.rb | 59 +++++++++++++++++++ app/helpers/admin/settings_helper.rb | 3 + app/javascript/styles/mastodon/forms.scss | 8 +++ app/models/form/admin_settings.rb | 2 + .../settings/registrations/show.html.haml | 4 ++ .../auth/confirmations/captcha.html.haml | 15 +++++ config/locales/en.yml | 6 ++ config/routes.rb | 1 + config/settings.yml | 1 + 12 files changed, 146 insertions(+) create mode 100644 app/controllers/concerns/captcha_concern.rb create mode 100644 app/views/auth/confirmations/captcha.html.haml diff --git a/Gemfile b/Gemfile index 63b7713395..8c718b3569 100644 --- a/Gemfile +++ b/Gemfile @@ -160,3 +160,5 @@ gem 'cocoon', '~> 1.2' gem 'net-http', '~> 0.3.2' gem 'rubyzip', '~> 2.3' + +gem 'hcaptcha', '~> 7.1' diff --git a/Gemfile.lock b/Gemfile.lock index 20cb29355d..b5d277097a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -312,6 +312,8 @@ GEM sysexits (~> 1.1) hashdiff (1.0.1) hashie (5.0.0) + hcaptcha (7.1.0) + json highline (2.1.0) hiredis (0.6.3) hkdf (0.3.0) @@ -806,6 +808,7 @@ DEPENDENCIES fuubar (~> 2.5) haml-rails (~> 2.0) haml_lint + hcaptcha (~> 7.1) hiredis (~> 0.6) htmlentities (~> 4.3) http (~> 5.1) diff --git a/app/controllers/auth/confirmations_controller.rb b/app/controllers/auth/confirmations_controller.rb index 010fd37556..c57eb946e1 100644 --- a/app/controllers/auth/confirmations_controller.rb +++ b/app/controllers/auth/confirmations_controller.rb @@ -1,21 +1,63 @@ # frozen_string_literal: true class Auth::ConfirmationsController < Devise::ConfirmationsController + include CaptchaConcern + layout 'auth' before_action :set_body_classes + before_action :set_confirmation_user!, only: [:show, :confirm_captcha] before_action :require_unconfirmed! + before_action :extend_csp_for_captcha!, only: [:show, :confirm_captcha] + before_action :require_captcha_if_needed!, only: [:show] + skip_before_action :require_functional! + def show + old_session_values = session.to_hash + reset_session + session.update old_session_values.except('session_id') + + super + end + def new super resource.email = current_user.unconfirmed_email || current_user.email if user_signed_in? end + def confirm_captcha + check_captcha! do |message| + flash.now[:alert] = message + render :captcha + return + end + + show + end + private + def require_captcha_if_needed! + render :captcha if captcha_required? + end + + def set_confirmation_user! + # We need to reimplement looking up the user because + # Devise::ConfirmationsController#show looks up and confirms in one + # step. + confirmation_token = params[:confirmation_token] + return if confirmation_token.nil? + + @confirmation_user = User.find_first_by_auth_conditions(confirmation_token: confirmation_token) + end + + def captcha_user_bypass? + return true if @confirmation_user.nil? || @confirmation_user.confirmed? + end + def require_unconfirmed! if user_signed_in? && current_user.confirmed? && current_user.unconfirmed_email.blank? redirect_to(current_user.approved? ? root_path : edit_user_registration_path) diff --git a/app/controllers/concerns/captcha_concern.rb b/app/controllers/concerns/captcha_concern.rb new file mode 100644 index 0000000000..538c1ffb14 --- /dev/null +++ b/app/controllers/concerns/captcha_concern.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module CaptchaConcern + extend ActiveSupport::Concern + include Hcaptcha::Adapters::ViewMethods + + included do + helper_method :render_captcha + end + + def captcha_available? + ENV['HCAPTCHA_SECRET_KEY'].present? && ENV['HCAPTCHA_SITE_KEY'].present? + end + + def captcha_enabled? + captcha_available? && Setting.captcha_enabled + end + + def captcha_user_bypass? + false + end + + def captcha_required? + captcha_enabled? && !captcha_user_bypass? + end + + def check_captcha! + return true unless captcha_required? + + if verify_hcaptcha + true + else + if block_given? + message = flash[:hcaptcha_error] + flash.delete(:hcaptcha_error) + yield message + end + false + end + end + + def extend_csp_for_captcha! + policy = request.content_security_policy + return unless captcha_required? && policy.present? + + %w(script_src frame_src style_src connect_src).each do |directive| + values = policy.send(directive) + values << 'https://hcaptcha.com' unless values.include?('https://hcaptcha.com') || values.include?('https:') + values << 'https://*.hcaptcha.com' unless values.include?('https://*.hcaptcha.com') || values.include?('https:') + policy.send(directive, *values) + end + end + + def render_captcha + return unless captcha_required? + + hcaptcha_tags + end +end diff --git a/app/helpers/admin/settings_helper.rb b/app/helpers/admin/settings_helper.rb index a133b4e7d9..552a3ee5a8 100644 --- a/app/helpers/admin/settings_helper.rb +++ b/app/helpers/admin/settings_helper.rb @@ -1,4 +1,7 @@ # frozen_string_literal: true module Admin::SettingsHelper + def captcha_available? + ENV['HCAPTCHA_SECRET_KEY'].present? && ENV['HCAPTCHA_SITE_KEY'].present? + end end diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 03e06c1000..57f077c4e8 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -136,6 +136,10 @@ code { line-height: 22px; color: $secondary-text-color; margin-bottom: 30px; + + a { + color: $highlight-text-color; + } } .rules-list { @@ -1039,6 +1043,10 @@ code { } } +.simple_form .h-captcha { + text-align: center; +} + .permissions-list { &__item { padding: 15px; diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index de965cb0ba..a6be55fd7b 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -33,6 +33,7 @@ class Form::AdminSettings content_cache_retention_period backups_retention_period status_page_url + captcha_enabled ).freeze INTEGER_KEYS = %i( @@ -52,6 +53,7 @@ class Form::AdminSettings trendable_by_default noindex require_invite_text + captcha_enabled ).freeze UPLOAD_KEYS = %i( diff --git a/app/views/admin/settings/registrations/show.html.haml b/app/views/admin/settings/registrations/show.html.haml index 0db9f3536f..84492a08a1 100644 --- a/app/views/admin/settings/registrations/show.html.haml +++ b/app/views/admin/settings/registrations/show.html.haml @@ -20,6 +20,10 @@ .fields-row__column.fields-row__column-6.fields-group = f.input :require_invite_text, as: :boolean, wrapper: :with_label, disabled: !approved_registrations? + - if captcha_available? + .fields-group + = f.input :captcha_enabled, as: :boolean, wrapper: :with_label, label: t('admin.settings.captcha_enabled.title'), hint: t('admin.settings.captcha_enabled.desc_html') + .fields-group = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, input_html: { rows: 2 } diff --git a/app/views/auth/confirmations/captcha.html.haml b/app/views/auth/confirmations/captcha.html.haml new file mode 100644 index 0000000000..1f577383eb --- /dev/null +++ b/app/views/auth/confirmations/captcha.html.haml @@ -0,0 +1,15 @@ +- content_for :page_title do + = t('auth.captcha_confirmation.title') + += form_tag auth_captcha_confirmation_url, method: 'POST', class: 'simple_form' do + = render 'auth/shared/progress', stage: 'confirm' + + = hidden_field_tag :confirmation_token, params[:confirmation_token] + + %p.lead= t('auth.captcha_confirmation.hint_html') + + .field-group + = render_captcha + + .actions + %button.button= t('challenge.confirm') diff --git a/config/locales/en.yml b/config/locales/en.yml index 29abec9437..aea9656602 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -731,6 +731,9 @@ en: branding: preamble: Your server's branding differentiates it from other servers in the network. This information may be displayed across a variety of environments, such as Mastodon's web interface, native applications, in link previews on other websites and within messaging apps, and so on. For this reason, it is best to keep this information clear, short and concise. title: Branding + captcha_enabled: + desc_html: This relies on external scripts from hCaptcha, which may be a security and privacy concern. In addition, this can make the registration process significantly less accessible to some (especially disabled) people. For these reasons, please consider alternative measures such as approval-based or invite-based registration. + title: Require new users to solve a CAPTCHA to confirm their account content_retention: preamble: Control how user-generated content is stored in Mastodon. title: Content retention @@ -979,6 +982,9 @@ en: your_token: Your access token auth: apply_for_account: Request an account + captcha_confirmation: + hint_html: Just one more step! To confirm your account, this server requires you to solve a CAPTCHA. You can contact the server administrator if you have questions or need assistance with confirming your account. + title: User verification change_password: Password confirmations: wrong_email_hint: If that e-mail address is not correct, you can change it in account settings. diff --git a/config/routes.rb b/config/routes.rb index 04d5ec2a65..45b3d90e0b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -71,6 +71,7 @@ Rails.application.routes.draw do resource :setup, only: [:show, :update], controller: :setup resource :challenge, only: [:create], controller: :challenges get 'sessions/security_key_options', to: 'sessions#webauthn_options' + post 'captcha_confirmation', to: 'confirmations#confirm_captcha', as: :captcha_confirmation end end diff --git a/config/settings.yml b/config/settings.yml index 4ac521a4b0..67297c26ce 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -37,6 +37,7 @@ defaults: &defaults show_domain_blocks_rationale: 'disabled' require_invite_text: false backups_retention_period: 7 + captcha_enabled: false development: <<: *defaults From b030441368766a4ccb84fef256428351608eded1 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 17 May 2023 00:08:42 +0200 Subject: [PATCH 36/98] Fix being able to vote on your own polls (#25015) --- app/validators/vote_validator.rb | 6 +++++- config/locales/en.yml | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/validators/vote_validator.rb b/app/validators/vote_validator.rb index 9bd17fbe80..fa2bd223dc 100644 --- a/app/validators/vote_validator.rb +++ b/app/validators/vote_validator.rb @@ -3,8 +3,8 @@ class VoteValidator < ActiveModel::Validator def validate(vote) vote.errors.add(:base, I18n.t('polls.errors.expired')) if vote.poll_expired? - vote.errors.add(:base, I18n.t('polls.errors.invalid_choice')) if invalid_choice?(vote) + vote.errors.add(:base, I18n.t('polls.errors.self_vote')) if self_vote?(vote) vote.errors.add(:base, I18n.t('polls.errors.already_voted')) if additional_voting_not_allowed?(vote) end @@ -27,6 +27,10 @@ class VoteValidator < ActiveModel::Validator vote.choice.negative? || vote.choice >= vote.poll.options.size end + def self_vote?(vote) + vote.account_id == vote.poll.account_id + end + def already_voted_for_same_choice_on_multiple_poll?(vote) if vote.persisted? account_votes_on_same_poll(vote).where(choice: vote.choice).where.not(poll_votes: { id: vote }).exists? diff --git a/config/locales/en.yml b/config/locales/en.yml index aea9656602..76198763a4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1446,6 +1446,7 @@ en: expired: The poll has already ended invalid_choice: The chosen vote option does not exist over_character_limit: cannot be longer than %{max} characters each + self_vote: You cannot vote in your own polls too_few_options: must have more than one item too_many_options: can't contain more than %{max} items preferences: From 5bf9e2ebec8fcb79e389347d58e1916078d766ea Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 17 May 2023 00:09:21 +0200 Subject: [PATCH 37/98] Fix race condition when reblogging a status (#25016) --- app/controllers/api/v1/statuses/reblogs_controller.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/statuses/reblogs_controller.rb b/app/controllers/api/v1/statuses/reblogs_controller.rb index 1be15a5a43..e3769437b7 100644 --- a/app/controllers/api/v1/statuses/reblogs_controller.rb +++ b/app/controllers/api/v1/statuses/reblogs_controller.rb @@ -2,6 +2,8 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController include Authorization + include Redisable + include Lockable before_action -> { doorkeeper_authorize! :write, :'write:statuses' } before_action :require_user! @@ -10,7 +12,9 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController override_rate_limit_headers :create, family: :statuses def create - @status = ReblogService.new.call(current_account, @reblog, reblog_params) + with_redis_lock("reblog:#{current_account.id}:#{@reblog.id}") do + @status = ReblogService.new.call(current_account, @reblog, reblog_params) + end render json: @status, serializer: REST::StatusSerializer end From 9082e5300203bccc8c40de67b5de769ef6bfb56f Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 19 May 2023 04:53:50 -0400 Subject: [PATCH 38/98] Add spec for migration warning module (#25033) --- spec/lib/mastodon/migration_warning_spec.rb | 34 +++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 spec/lib/mastodon/migration_warning_spec.rb diff --git a/spec/lib/mastodon/migration_warning_spec.rb b/spec/lib/mastodon/migration_warning_spec.rb new file mode 100644 index 0000000000..4adf0837ab --- /dev/null +++ b/spec/lib/mastodon/migration_warning_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'mastodon/migration_warning' + +describe Mastodon::MigrationWarning do + describe 'migration_duration_warning' do + before do + allow(migration).to receive(:valid_environment?).and_return(true) + allow(migration).to receive(:sleep).with(1) + end + + let(:migration) { Class.new(ActiveRecord::Migration[6.1]).extend(described_class) } + + context 'with the default message' do + it 'warns about long migrations' do + expectation = expect { migration.migration_duration_warning } + + expectation.to output(/interrupt this migration/).to_stdout + expectation.to output(/Continuing in 5/).to_stdout + end + end + + context 'with an additional message' do + it 'warns about long migrations' do + expectation = expect { migration.migration_duration_warning('Get ready for it') } + + expectation.to output(/interrupt this migration/).to_stdout + expectation.to output(/Get ready for it/).to_stdout + expectation.to output(/Continuing in 5/).to_stdout + end + end + end +end From a76777c33bfb326f0b6868d553399af4e443c432 Mon Sep 17 00:00:00 2001 From: Essem Date: Fri, 19 May 2023 04:27:10 -0500 Subject: [PATCH 39/98] Fix oversight in backup service (#25034) --- app/services/backup_service.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb index 670b34ea86..4cad11fde9 100644 --- a/app/services/backup_service.rb +++ b/app/services/backup_service.rb @@ -101,8 +101,8 @@ class BackupService < BaseService actor[:likes] = 'likes.json' actor[:bookmarks] = 'bookmarks.json' - download_to_zip(tar, account.avatar, "avatar#{File.extname(account.avatar.path)}") if account.avatar.exists? - download_to_zip(tar, account.header, "header#{File.extname(account.header.path)}") if account.header.exists? + download_to_zip(zipfile, account.avatar, "avatar#{File.extname(account.avatar.path)}") if account.avatar.exists? + download_to_zip(zipfile, account.header, "header#{File.extname(account.header.path)}") if account.header.exists? json = Oj.dump(actor) From 69a440cba16fb60911f34d1953586fd96cedd4e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 May 2023 11:28:56 +0200 Subject: [PATCH 40/98] Bump jsdom from 21.1.2 to 22.0.0 (#24828) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 5cf1f5aba1..93b63042c9 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "intl-messageformat": "^2.2.0", "intl-relativeformat": "^6.4.3", "js-yaml": "^4.1.0", - "jsdom": "^21.1.2", + "jsdom": "^22.0.0", "lodash": "^4.17.21", "mark-loader": "^0.1.6", "marky": "^1.2.5", diff --git a/yarn.lock b/yarn.lock index 58b8e03e54..05bb4a349b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2800,7 +2800,7 @@ acorn@^6.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== -acorn@^8.0.4, acorn@^8.1.0, acorn@^8.5.0, acorn@^8.8.0, acorn@^8.8.1, acorn@^8.8.2: +acorn@^8.0.4, acorn@^8.1.0, acorn@^8.5.0, acorn@^8.8.0, acorn@^8.8.1: version "8.8.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== @@ -7522,19 +7522,16 @@ jsdom@^20.0.0: ws "^8.11.0" xml-name-validator "^4.0.0" -jsdom@^21.1.2: - version "21.1.2" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-21.1.2.tgz#6433f751b8718248d646af1cdf6662dc8a1ca7f9" - integrity sha512-sCpFmK2jv+1sjff4u7fzft+pUh2KSUbUrEHYHyfSIbGTIcmnjyp83qg6qLwdJ/I3LpTXx33ACxeRL7Lsyc6lGQ== +jsdom@^22.0.0: + version "22.0.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-22.0.0.tgz#3295c6992c70089c4b8f5cf060489fddf7ee9816" + integrity sha512-p5ZTEb5h+O+iU02t0GfEjAnkdYPrQSkfuTSMkMYyIoMvUNEHsbG0bHHbfXIcfTqD2UfvjQX7mmgiFsyRwGscVw== dependencies: abab "^2.0.6" - acorn "^8.8.2" - acorn-globals "^7.0.0" cssstyle "^3.0.0" data-urls "^4.0.0" decimal.js "^10.4.3" domexception "^4.0.0" - escodegen "^2.0.0" form-data "^4.0.0" html-encoding-sniffer "^3.0.0" http-proxy-agent "^5.0.0" From 9011b008c20bbf025febb18745a6d94ad1ceca4d Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 19 May 2023 12:04:18 +0200 Subject: [PATCH 41/98] Add tests for avatar/header in backup service (#25037) --- spec/services/backup_service_spec.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/spec/services/backup_service_spec.rb b/spec/services/backup_service_spec.rb index b961b7f675..73e0b42adb 100644 --- a/spec/services/backup_service_spec.rb +++ b/spec/services/backup_service_spec.rb @@ -21,6 +21,27 @@ RSpec.describe BackupService, type: :service do end end + context 'when the user has an avatar and header' do + before do + user.account.update!(avatar: attachment_fixture('avatar.gif')) + user.account.update!(header: attachment_fixture('emojo.png')) + end + + it 'stores them as expected' do + service_call + + json = Oj.load(read_zip_file(backup, 'actor.json')) + avatar_path = json.dig('icon', 'url') + header_path = json.dig('image', 'url') + + expect(avatar_path).to_not be_nil + expect(header_path).to_not be_nil + + expect(read_zip_file(backup, avatar_path)).to be_present + expect(read_zip_file(backup, header_path)).to be_present + end + end + it 'marks the backup as processed' do expect { service_call }.to change(backup, :processed).from(false).to(true) end From c4f2b1e86aeb61a8daac8254e981c36c9e19d97e Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Fri, 19 May 2023 11:13:29 -0400 Subject: [PATCH 42/98] Fix minor typos in comments and spec names (#21831) --- Dockerfile | 2 +- app/javascript/mastodon/utils/__tests__/html-test.js | 2 +- app/workers/post_process_media_worker.rb | 2 +- lib/mastodon/media_cli.rb | 2 +- lib/tasks/tests.rake | 2 +- spec/controllers/admin/confirmations_controller_spec.rb | 2 +- spec/controllers/concerns/signature_verification_spec.rb | 2 +- spec/models/account_migration_spec.rb | 2 +- spec/models/account_spec.rb | 2 +- spec/models/user_settings/setting_spec.rb | 2 +- spec/services/activitypub/process_account_service_spec.rb | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 91c26d2ac0..cb5096581c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,7 +55,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] ENV DEBIAN_FRONTEND="noninteractive" \ PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" -# Ignoreing these here since we don't want to pin any versions and the Debian image removes apt-get content after use +# Ignoring these here since we don't want to pin any versions and the Debian image removes apt-get content after use # hadolint ignore=DL3008,DL3009 RUN apt-get update && \ echo "Etc/UTC" > /etc/localtime && \ diff --git a/app/javascript/mastodon/utils/__tests__/html-test.js b/app/javascript/mastodon/utils/__tests__/html-test.js index ef9296e6c3..d948cf4c5d 100644 --- a/app/javascript/mastodon/utils/__tests__/html-test.js +++ b/app/javascript/mastodon/utils/__tests__/html-test.js @@ -1,7 +1,7 @@ import * as html from '../html'; describe('html', () => { - describe('unsecapeHTML', () => { + describe('unescapeHTML', () => { it('returns unescaped HTML', () => { const output = html.unescapeHTML('

lorem

ipsum


<br>'); expect(output).toEqual('lorem\n\nipsum\n
'); diff --git a/app/workers/post_process_media_worker.rb b/app/workers/post_process_media_worker.rb index 996d5def91..2d11b00c20 100644 --- a/app/workers/post_process_media_worker.rb +++ b/app/workers/post_process_media_worker.rb @@ -24,7 +24,7 @@ class PostProcessMediaWorker media_attachment.processing = :in_progress media_attachment.save - # Because paperclip-av-transcover overwrites this attribute + # Because paperclip-av-transcoder overwrites this attribute # we will save it here and restore it after reprocess is done previous_meta = media_attachment.file_meta diff --git a/lib/mastodon/media_cli.rb b/lib/mastodon/media_cli.rb index 7dacd8d3d4..1bedcd9beb 100644 --- a/lib/mastodon/media_cli.rb +++ b/lib/mastodon/media_cli.rb @@ -24,7 +24,7 @@ module Mastodon desc 'remove', 'Remove remote media files, headers or avatars' long_desc <<-DESC Removes locally cached copies of media attachments (and optionally profile - headers and avatars) from other servers. By default, only media attachements + headers and avatars) from other servers. By default, only media attachments are removed. The --days option specifies how old media attachments have to be before they are removed. In case of avatars and headers, it specifies how old diff --git a/lib/tasks/tests.rake b/lib/tasks/tests.rake index ceb421f4b2..60399c9de1 100644 --- a/lib/tasks/tests.rake +++ b/lib/tasks/tests.rake @@ -25,7 +25,7 @@ namespace :tests do end if Account.where(domain: Rails.configuration.x.local_domain).exists? - puts 'Faux remote accounts not properly claned up' + puts 'Faux remote accounts not properly cleaned up' exit(1) end diff --git a/spec/controllers/admin/confirmations_controller_spec.rb b/spec/controllers/admin/confirmations_controller_spec.rb index ffab56d9aa..181616a66e 100644 --- a/spec/controllers/admin/confirmations_controller_spec.rb +++ b/spec/controllers/admin/confirmations_controller_spec.rb @@ -32,7 +32,7 @@ RSpec.describe Admin::ConfirmationsController do end end - describe 'POST #resernd' do + describe 'POST #resend' do subject { post :resend, params: { account_id: user.account.id } } let!(:user) { Fabricate(:user, confirmed_at: confirmed_at) } diff --git a/spec/controllers/concerns/signature_verification_spec.rb b/spec/controllers/concerns/signature_verification_spec.rb index df20a5d7ef..e6b7f18498 100644 --- a/spec/controllers/concerns/signature_verification_spec.rb +++ b/spec/controllers/concerns/signature_verification_spec.rb @@ -129,7 +129,7 @@ describe ApplicationController do end end - context 'with request with unparseable Date header' do + context 'with request with unparsable Date header' do before do get :success diff --git a/spec/models/account_migration_spec.rb b/spec/models/account_migration_spec.rb index 0d97ea7e77..519b9a97a5 100644 --- a/spec/models/account_migration_spec.rb +++ b/spec/models/account_migration_spec.rb @@ -25,7 +25,7 @@ RSpec.describe AccountMigration do end end - context 'with unresolveable account' do + context 'with unresolvable account' do let(:target_acct) { 'target@remote' } before do diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index bbe35f5793..6e9c608ab0 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -698,7 +698,7 @@ RSpec.describe Account do expect(subject.match('Check this out https://medium.com/@alice/some-article#.abcdef123')).to be_nil end - xit 'does not match URL querystring' do + xit 'does not match URL query string' do expect(subject.match('https://example.com/?x=@alice')).to be_nil end end diff --git a/spec/models/user_settings/setting_spec.rb b/spec/models/user_settings/setting_spec.rb index 9884ae4f89..8c8d31ec54 100644 --- a/spec/models/user_settings/setting_spec.rb +++ b/spec/models/user_settings/setting_spec.rb @@ -90,7 +90,7 @@ RSpec.describe UserSettings::Setting do describe '#key' do context 'when there is no namespace' do - it 'returnsn a symbol' do + it 'returns a symbol' do expect(subject.key).to eq :foo end end diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb index 4c5e6b6cc3..db454d7ad9 100644 --- a/spec/services/activitypub/process_account_service_spec.rb +++ b/spec/services/activitypub/process_account_service_spec.rb @@ -181,7 +181,7 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do '@context': ['https://www.w3.org/ns/activitystreams'], id: "https://foo.test/users/#{i}/featured", type: 'OrderedCollection', - totelItems: 1, + totalItems: 1, orderedItems: [status_json], }.with_indifferent_access webfinger = { From 73dda295c343cdbaa2bb28bdeade1188f374bec1 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Fri, 19 May 2023 11:14:15 -0400 Subject: [PATCH 43/98] Add Postgres 15 testing for migrations (#23887) --- .github/workflows/test-migrations-one-step.yml | 10 +++++++++- .github/workflows/test-migrations-two-step.yml | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-migrations-one-step.yml b/.github/workflows/test-migrations-one-step.yml index d7e424a8c4..212b2cfe73 100644 --- a/.github/workflows/test-migrations-one-step.yml +++ b/.github/workflows/test-migrations-one-step.yml @@ -23,9 +23,17 @@ jobs: needs: pre_job if: needs.pre_job.outputs.should_skip != 'true' + strategy: + fail-fast: false + + matrix: + postgres: + - 14-alpine + - 15-alpine + services: postgres: - image: postgres:14-alpine + image: postgres:${{ matrix.postgres}} env: POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres diff --git a/.github/workflows/test-migrations-two-step.yml b/.github/workflows/test-migrations-two-step.yml index 25bf5ba87f..310153929d 100644 --- a/.github/workflows/test-migrations-two-step.yml +++ b/.github/workflows/test-migrations-two-step.yml @@ -23,9 +23,17 @@ jobs: needs: pre_job if: needs.pre_job.outputs.should_skip != 'true' + strategy: + fail-fast: false + + matrix: + postgres: + - 14-alpine + - 15-alpine + services: postgres: - image: postgres:14-alpine + image: postgres:${{ matrix.postgres}} env: POSTGRES_PASSWORD: postgres POSTGRES_USER: postgres From e9661fee15b3c57bbc2237b355e6704ee3174ecc Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Fri, 19 May 2023 11:48:15 -0400 Subject: [PATCH 44/98] Cleanup and document bundle test/dev deps (#24457) Co-authored-by: Claire --- Gemfile | 85 ++++++++++++++++++++++++++++++++++++---------------- Gemfile.lock | 5 +--- 2 files changed, 60 insertions(+), 30 deletions(-) diff --git a/Gemfile b/Gemfile index 8c718b3569..e97b3e52ee 100644 --- a/Gemfile +++ b/Gemfile @@ -99,54 +99,87 @@ gem 'json-ld' gem 'json-ld-preloaded', '~> 3.2' gem 'rdf-normalize', '~> 0.5' -group :development, :test do - gem 'fabrication', '~> 2.30' - gem 'fuubar', '~> 2.5' - gem 'i18n-tasks', '~> 1.0', require: false - gem 'rspec-rails', '~> 6.0' - gem 'rspec_chunked', '~> 0.6' - - gem 'rubocop-capybara', require: false - gem 'rubocop-performance', require: false - gem 'rubocop-rails', require: false - gem 'rubocop-rspec', require: false - gem 'rubocop', require: false -end - -group :production, :test do - gem 'private_address_check', '~> 0.5' -end +gem 'private_address_check', '~> 0.5' group :test do - gem 'capybara', '~> 3.39' - gem 'climate_control' - gem 'faker', '~> 3.2' - gem 'json-schema', '~> 4.0' - gem 'rack-test', '~> 2.1' - gem 'rails-controller-testing', '~> 1.0' - gem 'rspec_junit_formatter', '~> 0.6' + # RSpec runner for rails + gem 'rspec-rails', '~> 6.0' + + # Used to split testing into chunks in CI + gem 'rspec_chunked', '~> 0.6' + + # RSpec progress bar formatter + gem 'fuubar', '~> 2.5' + + # Extra RSpec extenion methods and helpers for sidekiq gem 'rspec-sidekiq', '~> 3.1' + + # Browser integration testing + gem 'capybara', '~> 3.39' + + # Used to mock environment variables + gem 'climate_control', '~> 0.2' + + # Generating fake data for specs + gem 'faker', '~> 3.2' + + # Generate test objects for specs + gem 'fabrication', '~> 2.30' + + # Add back helpers functions removed in Rails 5.1 + gem 'rails-controller-testing', '~> 1.0' + + # Validate schemas in specs + gem 'json-schema', '~> 4.0' + + # Test harness fo rack components + gem 'rack-test', '~> 2.1' + + # Coverage formatter for RSpec test if DISABLE_SIMPLECOV is false gem 'simplecov', '~> 0.22', require: false + + # Stub web requests for specs gem 'webmock', '~> 3.18' end group :development do + # Code linting CLI and plugins + gem 'rubocop', require: false + gem 'rubocop-capybara', require: false + gem 'rubocop-performance', require: false + gem 'rubocop-rails', require: false + gem 'rubocop-rspec', require: false + + # Annotates modules with schema gem 'annotate', '~> 3.2' + + # Enhanced error message pages for development gem 'better_errors', '~> 2.9' gem 'binding_of_caller', '~> 1.0' + + # Preview mail in the browser gem 'letter_opener', '~> 1.8' gem 'letter_opener_web', '~> 2.0' - gem 'memory_profiler' + + # Security analysis CLI tools gem 'brakeman', '~> 5.4', require: false gem 'bundler-audit', '~> 0.9', require: false + + # Linter CLI for HAML files gem 'haml_lint', require: false + # Deployment automation gem 'capistrano', '~> 3.17' gem 'capistrano-rails', '~> 1.6' gem 'capistrano-rbenv', '~> 2.2' gem 'capistrano-yarn', '~> 2.0' - gem 'stackprof' + # Validate missing i18n keys + gem 'i18n-tasks', '~> 1.0', require: false + + # Profiling tools + gem 'memory_profiler', require: false + gem 'stackprof', require: false end group :production do diff --git a/Gemfile.lock b/Gemfile.lock index b5d277097a..acea3bbbed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -601,8 +601,6 @@ GEM sidekiq (>= 2.4.0) rspec-support (3.12.0) rspec_chunked (0.6) - rspec_junit_formatter (0.6.0) - rspec-core (>= 2, < 4, != 2.12.0) rubocop (1.50.2) json (~> 2.3) parallel (~> 1.10) @@ -787,7 +785,7 @@ DEPENDENCIES capybara (~> 3.39) charlock_holmes (~> 0.7.7) chewy (~> 7.3) - climate_control + climate_control (~> 0.2) cocoon (~> 1.2) color_diff (~> 0.1) concurrent-ruby @@ -866,7 +864,6 @@ DEPENDENCIES rspec-rails (~> 6.0) rspec-sidekiq (~> 3.1) rspec_chunked (~> 0.6) - rspec_junit_formatter (~> 0.6) rubocop rubocop-capybara rubocop-performance From 9cf831be0b5e5c13e0f0e5a1d8b9469268b63efe Mon Sep 17 00:00:00 2001 From: Frankie Roberto Date: Mon, 22 May 2023 10:40:00 +0100 Subject: [PATCH 45/98] Order sessions by most-recent to least-recently updated (#25005) --- app/controllers/auth/registrations_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index b948cd1544..e70ae5b1b8 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -127,7 +127,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController end def set_sessions - @sessions = current_user.session_activations + @sessions = current_user.session_activations.order(updated_at: :desc) end def set_strikes From 0635b1c2280f6bbd050fef8be3f349ec8295759f Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 22 May 2023 12:25:56 +0200 Subject: [PATCH 46/98] Change OpenGraph-based embeds to allow fullscreen (#25058) --- app/lib/link_details_extractor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/link_details_extractor.rb b/app/lib/link_details_extractor.rb index f8a0be636e..dfed69285f 100644 --- a/app/lib/link_details_extractor.rb +++ b/app/lib/link_details_extractor.rb @@ -140,7 +140,7 @@ class LinkDetailsExtractor end def html - player_url.present? ? content_tag(:iframe, nil, src: player_url, width: width, height: height, allowtransparency: 'true', scrolling: 'no', frameborder: '0') : nil + player_url.present? ? content_tag(:iframe, nil, src: player_url, width: width, height: height, allowfullscreen: 'true', allowtransparency: 'true', scrolling: 'no', frameborder: '0') : nil end def width From 9a02b3b3506e2029ddfef08617cbee44d33f547b Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Mon, 22 May 2023 06:45:29 -0400 Subject: [PATCH 47/98] Remove requestAnimationFrame test polyfill (#25056) --- jest.config.js | 1 - package.json | 2 -- yarn.lock | 7 +------ 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/jest.config.js b/jest.config.js index f447cf285d..42c2b41522 100644 --- a/jest.config.js +++ b/jest.config.js @@ -9,7 +9,6 @@ const config = { '/public/', '/tmp/', ], - setupFiles: ['raf/polyfill'], setupFilesAfterEnv: ['/app/javascript/mastodon/test_setup.js'], collectCoverageFrom: [ 'app/javascript/mastodon/**/*.{js,jsx,ts,tsx}', diff --git a/package.json b/package.json index 93b63042c9..435885ffa6 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,6 @@ "@types/pg": "^8.6.6", "@types/prop-types": "^15.7.5", "@types/punycode": "^2.1.0", - "@types/raf": "^3.4.0", "@types/react": "^16.14.38", "@types/react-dom": "^16.9.18", "@types/react-helmet": "^6.1.6", @@ -195,7 +194,6 @@ "jest-environment-jsdom": "^29.5.0", "lint-staged": "^13.2.2", "prettier": "^2.8.8", - "raf": "^3.4.1", "react-intl-translations-manager": "^5.0.3", "react-test-renderer": "^16.14.0", "stylelint": "^15.6.1", diff --git a/yarn.lock b/yarn.lock index 05bb4a349b..4f0a6612c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2184,11 +2184,6 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== -"@types/raf@^3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@types/raf/-/raf-3.4.0.tgz#2b72cbd55405e071f1c4d29992638e022b20acc2" - integrity sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw== - "@types/range-parser@*": version "1.2.4" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" @@ -9502,7 +9497,7 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== -raf@^3.1.0, raf@^3.4.1: +raf@^3.1.0: version "3.4.1" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== From 26cdb19dc62f27b85435bda8993dcce41da09fa2 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Mon, 22 May 2023 06:46:20 -0400 Subject: [PATCH 48/98] Remove duplicate JPG type (#25054) --- app/javascript/types/image.d.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/javascript/types/image.d.ts b/app/javascript/types/image.d.ts index fae2ed7014..15f0007af5 100644 --- a/app/javascript/types/image.d.ts +++ b/app/javascript/types/image.d.ts @@ -14,11 +14,6 @@ declare module '*.jpg' { export default path; } -declare module '*.jpg' { - const path: string; - export default path; -} - declare module '*.png' { const path: string; export default path; From 90466ee43a364830cf311beb8112d372f9b9fdf7 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 22 May 2023 07:50:44 -0300 Subject: [PATCH 49/98] Improve test coverage for `/api/v1/admin/ip_blocks_controller` (#25031) --- .../api/v1/admin/ip_blocks_controller_spec.rb | 294 +++++++++++++++++- 1 file changed, 290 insertions(+), 4 deletions(-) diff --git a/spec/controllers/api/v1/admin/ip_blocks_controller_spec.rb b/spec/controllers/api/v1/admin/ip_blocks_controller_spec.rb index 50e2ae9687..a5787883ee 100644 --- a/spec/controllers/api/v1/admin/ip_blocks_controller_spec.rb +++ b/spec/controllers/api/v1/admin/ip_blocks_controller_spec.rb @@ -5,19 +5,305 @@ require 'rails_helper' describe Api::V1::Admin::IpBlocksController do render_views - let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') } - let(:account) { Fabricate(:account) } + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'admin:read:ip_blocks admin:write:ip_blocks' } before do allow(controller).to receive(:doorkeeper_token) { token } end + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + describe 'GET #index' do + context 'with wrong scope' do + before do + get :index + end + + it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks' + end + + context 'with wrong role' do + before do + get :index + end + + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + end + it 'returns http success' do - get :index, params: { account_id: account.id, limit: 2 } + get :index expect(response).to have_http_status(200) end + + context 'when there is no ip block' do + it 'returns an empty body' do + get :index + + json = body_as_json + + expect(json).to be_empty + end + end + + context 'when there are ip blocks' do + let!(:ip_blocks) do + [ + IpBlock.create(ip: '192.0.2.0/24', severity: :no_access), + IpBlock.create(ip: '172.16.0.1', severity: :sign_up_requires_approval, comment: 'Spam'), + IpBlock.create(ip: '2001:0db8::/32', severity: :sign_up_block, expires_in: 10.days), + ] + end + let(:expected_response) do + ip_blocks.map do |ip_block| + { + id: ip_block.id.to_s, + ip: ip_block.ip, + severity: ip_block.severity.to_s, + comment: ip_block.comment, + created_at: ip_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), + expires_at: ip_block.expires_at&.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), + } + end + end + + it 'returns the correct blocked ips' do + get :index + + json = body_as_json + + expect(json).to match_array(expected_response) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of ip blocks' do + get :index, params: params + + json = body_as_json + + expect(json.size).to eq(params[:limit]) + end + end + end + end + + describe 'GET #show' do + let!(:ip_block) { IpBlock.create(ip: '192.0.2.0/24', severity: :no_access) } + let(:params) { { id: ip_block.id } } + + context 'with wrong scope' do + before do + get :show, params: params + end + + it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks' + end + + context 'with wrong role' do + before do + get :show, params: params + end + + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + end + + it 'returns http success' do + get :show, params: params + + expect(response).to have_http_status(200) + end + + it 'returns the correct ip block' do + get :show, params: params + + json = body_as_json + + expect(json[:ip]).to eq("#{ip_block.ip}/#{ip_block.ip.prefix}") + expect(json[:severity]).to eq(ip_block.severity.to_s) + end + + context 'when ip block does not exist' do + it 'returns http not found' do + get :show, params: { id: 0 } + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST #create' do + let(:params) { { ip: '151.0.32.55', severity: 'no_access', comment: 'Spam' } } + + context 'with wrong scope' do + before do + post :create, params: params + end + + it_behaves_like 'forbidden for wrong scope', 'admin:read:ip_blocks' + end + + context 'with wrong role' do + before do + post :create, params: params + end + + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + end + + it 'returns http success' do + post :create, params: params + + expect(response).to have_http_status(200) + end + + it 'returns the correct ip block' do + post :create, params: params + + json = body_as_json + + expect(json[:ip]).to eq("#{params[:ip]}/32") + expect(json[:severity]).to eq(params[:severity]) + expect(json[:comment]).to eq(params[:comment]) + end + + context 'when ip is not provided' do + let(:params) { { ip: '', severity: 'no_access' } } + + it 'returns http unprocessable entity' do + post :create, params: params + + expect(response).to have_http_status(422) + end + end + + context 'when severity is not provided' do + let(:params) { { ip: '173.65.23.1', severity: '' } } + + it 'returns http unprocessable entity' do + post :create, params: params + + expect(response).to have_http_status(422) + end + end + + context 'when provided ip is already blocked' do + before do + IpBlock.create(params) + end + + it 'returns http unprocessable entity' do + post :create, params: params + + expect(response).to have_http_status(422) + end + end + + context 'when provided ip address is invalid' do + let(:params) { { ip: '520.13.54.120', severity: 'no_access' } } + + it 'returns http unprocessable entity' do + post :create, params: params + + expect(response).to have_http_status(422) + end + end + end + + describe 'PUT #update' do + context 'when ip block exists' do + let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access', comment: 'Spam', expires_in: 48.hours) } + let(:params) { { id: ip_block.id, severity: 'sign_up_requires_approval', comment: 'Decreasing severity' } } + + it 'returns http success' do + put :update, params: params + + expect(response).to have_http_status(200) + end + + it 'returns the correct ip block' do + put :update, params: params + + json = body_as_json + + expect(json).to match(hash_including({ + ip: "#{ip_block.ip}/#{ip_block.ip.prefix}", + severity: 'sign_up_requires_approval', + comment: 'Decreasing severity', + })) + end + + it 'updates the severity correctly' do + expect { put :update, params: params }.to change { ip_block.reload.severity }.from('no_access').to('sign_up_requires_approval') + end + + it 'updates the comment correctly' do + expect { put :update, params: params }.to change { ip_block.reload.comment }.from('Spam').to('Decreasing severity') + end + end + + context 'when ip block does not exist' do + it 'returns http not found' do + put :update, params: { id: 0 } + + expect(response).to have_http_status(404) + end + end + end + + describe 'DELETE #destroy' do + context 'when ip block exists' do + let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access') } + let(:params) { { id: ip_block.id } } + + it 'returns http success' do + delete :destroy, params: params + + expect(response).to have_http_status(200) + end + + it 'returns an empty body' do + delete :destroy, params: params + + json = body_as_json + + expect(json).to be_empty + end + + it 'deletes the ip block' do + delete :destroy, params: params + + expect(IpBlock.find_by(id: ip_block.id)).to be_nil + end + end + + context 'when ip block does not exist' do + it 'returns http not found' do + delete :destroy, params: { id: 0 } + + expect(response).to have_http_status(404) + end + end end end From 8b4d7b22ed128da7c7e7e04416fa3900321641f9 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Mon, 22 May 2023 13:15:21 +0200 Subject: [PATCH 50/98] Allow reports with long comments from remote instances, but truncate (#25028) --- app/lib/activitypub/activity/flag.rb | 6 ++++- app/lib/activitypub/tag_manager.rb | 4 +++ app/models/report.rb | 9 +++---- spec/lib/activitypub/activity/flag_spec.rb | 31 ++++++++++++++++++++++ spec/models/report_spec.rb | 11 ++++++-- spec/services/report_service_spec.rb | 12 ++++++--- 6 files changed, 62 insertions(+), 11 deletions(-) diff --git a/app/lib/activitypub/activity/flag.rb b/app/lib/activitypub/activity/flag.rb index dc808ad364..dc1932f597 100644 --- a/app/lib/activitypub/activity/flag.rb +++ b/app/lib/activitypub/activity/flag.rb @@ -16,7 +16,7 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity @account, target_account, status_ids: target_statuses.nil? ? [] : target_statuses.map(&:id), - comment: @json['content'] || '', + comment: report_comment, uri: report_uri ) end @@ -35,4 +35,8 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity def report_uri @json['id'] unless @json['id'].nil? || non_matching_uri_hosts?(@account.uri, @json['id']) end + + def report_comment + (@json['content'] || '')[0...5000] + end end diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb index a65a9565ab..8643286317 100644 --- a/app/lib/activitypub/tag_manager.rb +++ b/app/lib/activitypub/tag_manager.rb @@ -28,6 +28,8 @@ class ActivityPub::TagManager return activity_account_status_url(target.account, target) if target.reblog? short_account_status_url(target.account, target) + when :flag + target.uri end end @@ -43,6 +45,8 @@ class ActivityPub::TagManager account_status_url(target.account, target) when :emoji emoji_url(target) + when :flag + target.uri end end diff --git a/app/models/report.rb b/app/models/report.rb index c3a0c4c8b2..e738281adc 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -40,7 +40,10 @@ class Report < ApplicationRecord scope :resolved, -> { where.not(action_taken_at: nil) } scope :with_accounts, -> { includes([:account, :target_account, :action_taken_by_account, :assigned_account].index_with({ user: [:invite_request, :invite] })) } - validates :comment, length: { maximum: 1_000 } + # A report is considered local if the reporter is local + delegate :local?, to: :account + + validates :comment, length: { maximum: 1_000 }, if: :local? validates :rule_ids, absence: true, unless: :violation? validate :validate_rule_ids @@ -51,10 +54,6 @@ class Report < ApplicationRecord violation: 2_000, } - def local? - false # Force uri_for to use uri attribute - end - before_validation :set_uri, only: :create after_create_commit :trigger_webhooks diff --git a/spec/lib/activitypub/activity/flag_spec.rb b/spec/lib/activitypub/activity/flag_spec.rb index 005e185e6b..601473069b 100644 --- a/spec/lib/activitypub/activity/flag_spec.rb +++ b/spec/lib/activitypub/activity/flag_spec.rb @@ -39,6 +39,37 @@ RSpec.describe ActivityPub::Activity::Flag do end end + context 'when the report comment is excessively long' do + subject do + described_class.new({ + '@context': 'https://www.w3.org/ns/activitystreams', + id: flag_id, + type: 'Flag', + content: long_comment, + actor: ActivityPub::TagManager.instance.uri_for(sender), + object: [ + ActivityPub::TagManager.instance.uri_for(flagged), + ActivityPub::TagManager.instance.uri_for(status), + ], + }.with_indifferent_access, sender) + end + + let(:long_comment) { Faker::Lorem.characters(number: 6000) } + + before do + subject.perform + end + + it 'creates a report but with a truncated comment' do + report = Report.find_by(account: sender, target_account: flagged) + + expect(report).to_not be_nil + expect(report.comment.length).to eq 5000 + expect(report.comment).to eq long_comment[0...5000] + expect(report.status_ids).to eq [status.id] + end + end + context 'when the reported status is private and should not be visible to the remote server' do let(:status) { Fabricate(:status, account: flagged, uri: 'foobar', visibility: :private) } diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb index b006f60bb6..0093dcd8de 100644 --- a/spec/models/report_spec.rb +++ b/spec/models/report_spec.rb @@ -121,10 +121,17 @@ describe Report do end describe 'validations' do - it 'is invalid if comment is longer than 1000 characters' do + let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') } + + it 'is invalid if comment is longer than 1000 characters only if reporter is local' do report = Fabricate.build(:report, comment: Faker::Lorem.characters(number: 1001)) - report.valid? + expect(report.valid?).to be false expect(report).to model_have_error_on_field(:comment) end + + it 'is valid if comment is longer than 1000 characters and reporter is not local' do + report = Fabricate.build(:report, account: remote_account, comment: Faker::Lorem.characters(number: 1001)) + expect(report.valid?).to be true + end end end diff --git a/spec/services/report_service_spec.rb b/spec/services/report_service_spec.rb index 29207462a0..b8ceedb851 100644 --- a/spec/services/report_service_spec.rb +++ b/spec/services/report_service_spec.rb @@ -6,6 +6,14 @@ RSpec.describe ReportService, type: :service do subject { described_class.new } let(:source_account) { Fabricate(:account) } + let(:target_account) { Fabricate(:account) } + + context 'with a local account' do + it 'has a uri' do + report = subject.call(source_account, target_account) + expect(report.uri).to_not be_nil + end + end context 'with a remote account' do let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') } @@ -35,7 +43,6 @@ RSpec.describe ReportService, type: :service do -> { described_class.new.call(source_account, target_account, status_ids: [status.id]) } end - let(:target_account) { Fabricate(:account) } let(:status) { Fabricate(:status, account: target_account, visibility: :direct) } context 'when it is addressed to the reporter' do @@ -91,8 +98,7 @@ RSpec.describe ReportService, type: :service do -> { described_class.new.call(source_account, target_account) } end - let!(:target_account) { Fabricate(:account) } - let!(:other_report) { Fabricate(:report, target_account: target_account) } + let!(:other_report) { Fabricate(:report, target_account: target_account) } before do ActionMailer::Base.deliveries.clear From 44b7c24bd0f1429cee4cc8476cb114f4588732a4 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Mon, 22 May 2023 07:17:56 -0400 Subject: [PATCH 51/98] Autofix Rubocop spacing in config (#25022) --- .rubocop_todo.yml | 33 -------------------------------- config/initializers/ffmpeg.rb | 2 +- config/initializers/omniauth.rb | 2 +- config/initializers/paperclip.rb | 8 ++++---- config/initializers/webauthn.rb | 2 +- 5 files changed, 7 insertions(+), 40 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c1a845bfa4..98725535ff 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -21,12 +21,6 @@ Layout/ArgumentAlignment: - 'config/initializers/cors.rb' - 'config/initializers/session_store.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. -Layout/ExtraSpacing: - Exclude: - - 'config/initializers/omniauth.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. # SupportedHashRocketStyles: key, separator, table @@ -39,12 +33,6 @@ Layout/HashAlignment: - 'config/initializers/rack_attack.rb' - 'config/routes.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Width, AllowedPatterns. -Layout/IndentationWidth: - Exclude: - - 'config/initializers/ffmpeg.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowDoxygenCommentStyle, AllowGemfileRubyComment. Layout/LeadingCommentSpace: @@ -52,14 +40,6 @@ Layout/LeadingCommentSpace: - 'config/application.rb' - 'config/initializers/omniauth.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. -# SupportedStyles: space, no_space -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceBeforeBlockBraces: - Exclude: - - 'config/initializers/paperclip.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: require_no_space, require_space @@ -68,19 +48,6 @@ Layout/SpaceInLambdaLiteral: - 'config/environments/production.rb' - 'config/initializers/content_security_policy.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, no_space -Layout/SpaceInsideStringInterpolation: - Exclude: - - 'config/initializers/webauthn.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowInHeredoc. -Layout/TrailingWhitespace: - Exclude: - - 'config/initializers/paperclip.rb' - # Configuration parameters: AllowedMethods, AllowedPatterns. Lint/AmbiguousBlockAssociation: Exclude: diff --git a/config/initializers/ffmpeg.rb b/config/initializers/ffmpeg.rb index 4c0bf779d8..cd5914eb55 100644 --- a/config/initializers/ffmpeg.rb +++ b/config/initializers/ffmpeg.rb @@ -1,3 +1,3 @@ if ENV['FFMPEG_BINARY'].present? - FFMPEG.ffmpeg_binary = ENV['FFMPEG_BINARY'] + FFMPEG.ffmpeg_binary = ENV['FFMPEG_BINARY'] end diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb index f016701467..c2cd444f08 100644 --- a/config/initializers/omniauth.rb +++ b/config/initializers/omniauth.rb @@ -73,7 +73,7 @@ Devise.setup do |config| oidc_options[:display_name] = ENV['OIDC_DISPLAY_NAME'] #OPTIONAL oidc_options[:issuer] = ENV['OIDC_ISSUER'] if ENV['OIDC_ISSUER'] #NEED oidc_options[:discovery] = ENV['OIDC_DISCOVERY'] == 'true' if ENV['OIDC_DISCOVERY'] #OPTIONAL (default: false) - oidc_options[:client_auth_method] = ENV['OIDC_CLIENT_AUTH_METHOD'] if ENV['OIDC_CLIENT_AUTH_METHOD'] #OPTIONAL (default: basic) + oidc_options[:client_auth_method] = ENV['OIDC_CLIENT_AUTH_METHOD'] if ENV['OIDC_CLIENT_AUTH_METHOD'] #OPTIONAL (default: basic) scope_string = ENV['OIDC_SCOPE'] if ENV['OIDC_SCOPE'] #NEED scopes = scope_string.split(',') oidc_options[:scope] = scopes.map { |x| x.to_sym } diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb index 9f0ffc6dc7..093d2ba9ae 100644 --- a/config/initializers/paperclip.rb +++ b/config/initializers/paperclip.rb @@ -61,13 +61,13 @@ if ENV['S3_ENABLED'] == 'true' s3_options: { signature_version: ENV.fetch('S3_SIGNATURE_VERSION') { 'v4' }, - http_open_timeout: ENV.fetch('S3_OPEN_TIMEOUT'){ '5' }.to_i, - http_read_timeout: ENV.fetch('S3_READ_TIMEOUT'){ '5' }.to_i, + http_open_timeout: ENV.fetch('S3_OPEN_TIMEOUT') { '5' }.to_i, + http_read_timeout: ENV.fetch('S3_READ_TIMEOUT') { '5' }.to_i, http_idle_timeout: 5, retry_limit: 0, } ) - + Paperclip::Attachment.default_options[:s3_permissions] = ->(*) { nil } if ENV['S3_PERMISSION'] == '' if ENV.has_key?('S3_ENDPOINT') @@ -124,7 +124,7 @@ elsif ENV['SWIFT_ENABLED'] == 'true' openstack_cache_ttl: ENV.fetch('SWIFT_CACHE_TTL') { 60 }, openstack_temp_url_key: ENV['SWIFT_TEMP_URL_KEY'], }, - + fog_file: { 'Cache-Control' => 'public, max-age=315576000, immutable' }, fog_directory: ENV['SWIFT_CONTAINER'], diff --git a/config/initializers/webauthn.rb b/config/initializers/webauthn.rb index a0a5b81537..a4f027947c 100644 --- a/config/initializers/webauthn.rb +++ b/config/initializers/webauthn.rb @@ -1,7 +1,7 @@ WebAuthn.configure do |config| # This value needs to match `window.location.origin` evaluated by # the User Agent during registration and authentication ceremonies. - config.origin = "#{Rails.configuration.x.use_https ? 'https' : 'http' }://#{Rails.configuration.x.web_domain}" + config.origin = "#{Rails.configuration.x.use_https ? 'https' : 'http'}://#{Rails.configuration.x.web_domain}" # Relying Party name for display purposes config.rp_name = "Mastodon" From 67d0dfbc67c0b2cc51a902910251af19cae844af Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 22 May 2023 08:28:11 -0300 Subject: [PATCH 52/98] Improve test coverage for `/api/v1/admin/email_domain_blocks` (#25017) --- .../email_domain_blocks_controller_spec.rb | 267 +++++++++++++++++- 1 file changed, 264 insertions(+), 3 deletions(-) diff --git a/spec/controllers/api/v1/admin/email_domain_blocks_controller_spec.rb b/spec/controllers/api/v1/admin/email_domain_blocks_controller_spec.rb index a92a298699..3643eb0f3d 100644 --- a/spec/controllers/api/v1/admin/email_domain_blocks_controller_spec.rb +++ b/spec/controllers/api/v1/admin/email_domain_blocks_controller_spec.rb @@ -5,19 +5,280 @@ require 'rails_helper' describe Api::V1::Admin::EmailDomainBlocksController do render_views - let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') } + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:account) { Fabricate(:account) } + let(:scopes) { 'admin:read:email_domain_blocks admin:write:email_domain_blocks' } before do allow(controller).to receive(:doorkeeper_token) { token } end + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + describe 'GET #index' do + context 'with wrong scope' do + before do + get :index + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'with wrong role' do + before do + get :index + end + + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + end + it 'returns http success' do - get :index, params: { account_id: account.id, limit: 2 } + get :index expect(response).to have_http_status(200) end + + context 'when there is no email domain block' do + it 'returns an empty list' do + get :index + + json = body_as_json + + expect(json).to be_empty + end + end + + context 'when there are email domain blocks' do + let!(:email_domain_blocks) { Fabricate.times(5, :email_domain_block) } + let(:blocked_email_domains) { email_domain_blocks.pluck(:domain) } + + it 'return the correct blocked email domains' do + get :index + + json = body_as_json + + expect(json.pluck(:domain)).to match_array(blocked_email_domains) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of email domain blocks' do + get :index, params: params + + json = body_as_json + + expect(json.size).to eq(params[:limit]) + end + end + + context 'with since_id param' do + let(:params) { { since_id: email_domain_blocks[1].id } } + + it 'returns only the email domain blocks after since_id' do + get :index, params: params + + email_domain_blocks_ids = email_domain_blocks.pluck(:id).map(&:to_s) + json = body_as_json + + expect(json.pluck(:id)).to match_array(email_domain_blocks_ids[2..]) + end + end + + context 'with max_id param' do + let(:params) { { max_id: email_domain_blocks[3].id } } + + it 'returns only the email domain blocks before max_id' do + get :index, params: params + + email_domain_blocks_ids = email_domain_blocks.pluck(:id).map(&:to_s) + json = body_as_json + + expect(json.pluck(:id)).to match_array(email_domain_blocks_ids[..2]) + end + end + end + end + + describe 'GET #show' do + let!(:email_domain_block) { Fabricate(:email_domain_block) } + let(:params) { { id: email_domain_block.id } } + + context 'with wrong scope' do + before do + get :show, params: params + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'with wrong role' do + before do + get :show, params: params + end + + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + end + + context 'when email domain block exists' do + it 'returns http success' do + get :show, params: params + + expect(response).to have_http_status(200) + end + + it 'returns the correct blocked domain' do + get :show, params: params + + json = body_as_json + + expect(json[:domain]).to eq(email_domain_block.domain) + end + end + + context 'when email domain block does not exist' do + it 'returns http not found' do + get :show, params: { id: 0 } + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST #create' do + let(:params) { { domain: 'example.com' } } + + context 'with wrong scope' do + before do + post :create, params: params + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'with wrong role' do + before do + post :create, params: params + end + + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + end + + it 'returns http success' do + post :create, params: params + + expect(response).to have_http_status(200) + end + + it 'returns the correct blocked email domain' do + post :create, params: params + + json = body_as_json + + expect(json[:domain]).to eq(params[:domain]) + end + + context 'when domain param is not provided' do + let(:params) { { domain: '' } } + + it 'returns http unprocessable entity' do + post :create, params: params + + expect(response).to have_http_status(422) + end + end + + context 'when provided domain name has an invalid character' do + let(:params) { { domain: 'do\uD800.com' } } + + it 'returns http unprocessable entity' do + post :create, params: params + + expect(response).to have_http_status(422) + end + end + + context 'when provided domain is already blocked' do + before do + EmailDomainBlock.create(params) + end + + it 'returns http unprocessable entity' do + post :create, params: params + + expect(response).to have_http_status(422) + end + end + end + + describe 'DELETE #destroy' do + let!(:email_domain_block) { Fabricate(:email_domain_block) } + let(:params) { { id: email_domain_block.id } } + + context 'with wrong scope' do + before do + delete :destroy, params: params + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'with wrong role' do + before do + delete :destroy, params: params + end + + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + end + + it 'returns http success' do + delete :destroy, params: params + + expect(response).to have_http_status(200) + end + + it 'returns an empty body' do + delete :destroy, params: params + + json = body_as_json + + expect(json).to be_empty + end + + it 'deletes email domain block' do + delete :destroy, params: params + + email_domain_block = EmailDomainBlock.find_by(id: params[:id]) + + expect(email_domain_block).to be_nil + end + + context 'when email domain block does not exist' do + it 'returns http not found' do + delete :destroy, params: { id: 0 } + + expect(response).to have_http_status(404) + end + end end end From e0e7e73cb47abb4bbecee7625e5f786af6414846 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 22 May 2023 07:43:05 -0400 Subject: [PATCH 53/98] Implement pending specs for StatusesController (#23969) --- spec/controllers/statuses_controller_spec.rb | 141 +++++++++++++++++-- 1 file changed, 128 insertions(+), 13 deletions(-) diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb index c846dd1d63..1885814cda 100644 --- a/spec/controllers/statuses_controller_spec.rb +++ b/spec/controllers/statuses_controller_spec.rb @@ -719,65 +719,180 @@ describe StatusesController do end context 'when status is public' do - pending + before do + status.update(visibility: :public) + get :activity, params: { account_username: account.username, id: status.id } + end + + it 'returns http success' do + expect(response).to have_http_status(:success) + end end context 'when status is private' do - pending + before do + status.update(visibility: :private) + get :activity, params: { account_username: account.username, id: status.id } + end + + it 'returns http not_found' do + expect(response).to have_http_status(404) + end end context 'when status is direct' do - pending + before do + status.update(visibility: :direct) + get :activity, params: { account_username: account.username, id: status.id } + end + + it 'returns http not_found' do + expect(response).to have_http_status(404) + end end context 'when signed-in' do + let(:user) { Fabricate(:user) } + + before do + sign_in(user) + end + context 'when status is public' do - pending + before do + status.update(visibility: :public) + get :activity, params: { account_username: account.username, id: status.id } + end + + it 'returns http success' do + expect(response).to have_http_status(:success) + end end context 'when status is private' do + before do + status.update(visibility: :private) + end + context 'when user is authorized to see it' do - pending + before do + user.account.follow!(account) + get :activity, params: { account_username: account.username, id: status.id } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end end context 'when user is not authorized to see it' do - pending + before do + get :activity, params: { account_username: account.username, id: status.id } + end + + it 'returns http not_found' do + expect(response).to have_http_status(404) + end end end context 'when status is direct' do + before do + status.update(visibility: :direct) + end + context 'when user is authorized to see it' do - pending + before do + Fabricate(:mention, account: user.account, status: status) + get :activity, params: { account_username: account.username, id: status.id } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end end context 'when user is not authorized to see it' do - pending + before do + get :activity, params: { account_username: account.username, id: status.id } + end + + it 'returns http not_found' do + expect(response).to have_http_status(404) + end end end end context 'with signature' do + let(:remote_account) { Fabricate(:account, domain: 'example.com') } + + before do + allow(controller).to receive(:signed_request_actor).and_return(remote_account) + end + context 'when status is public' do - pending + before do + status.update(visibility: :public) + get :activity, params: { account_username: account.username, id: status.id } + end + + it 'returns http success' do + expect(response).to have_http_status(:success) + end end context 'when status is private' do + before do + status.update(visibility: :private) + end + context 'when user is authorized to see it' do - pending + before do + remote_account.follow!(account) + get :activity, params: { account_username: account.username, id: status.id } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end end context 'when user is not authorized to see it' do - pending + before do + get :activity, params: { account_username: account.username, id: status.id } + end + + it 'returns http not_found' do + expect(response).to have_http_status(404) + end end end context 'when status is direct' do + before do + status.update(visibility: :direct) + end + context 'when user is authorized to see it' do - pending + before do + Fabricate(:mention, account: remote_account, status: status) + get :activity, params: { account_username: account.username, id: status.id } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end end context 'when user is not authorized to see it' do - pending + before do + get :activity, params: { account_username: account.username, id: status.id } + end + + it 'returns http not_found' do + expect(response).to have_http_status(404) + end end end end From 4d077931506087080fbf2b4eb85e5934a1b19046 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 22 May 2023 08:44:49 -0300 Subject: [PATCH 54/98] Fix POST `/api/v1/admin/domain_allows` returning 200 when no domain is specified (#24958) --- app/controllers/api/v1/admin/domain_allows_controller.rb | 2 +- .../api/v1/admin/domain_allows_controller_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/admin/domain_allows_controller.rb b/app/controllers/api/v1/admin/domain_allows_controller.rb index 61e1d481c7..dd54d67106 100644 --- a/app/controllers/api/v1/admin/domain_allows_controller.rb +++ b/app/controllers/api/v1/admin/domain_allows_controller.rb @@ -29,7 +29,7 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController def create authorize :domain_allow, :create? - @domain_allow = DomainAllow.find_by(resource_params) + @domain_allow = DomainAllow.find_by(domain: resource_params[:domain]) if @domain_allow.nil? @domain_allow = DomainAllow.create!(resource_params) diff --git a/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb b/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb index 9db8a35b46..ca63ea5a7e 100644 --- a/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb +++ b/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb @@ -128,5 +128,13 @@ RSpec.describe Api::V1::Admin::DomainAllowsController do expect(response).to have_http_status(422) end end + + context 'when domain name is not specified' do + it 'returns http unprocessable entity' do + post :create + + expect(response).to have_http_status(422) + end + end end end From da9237adb51b16bd3da1f640f406086a90810411 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 22 May 2023 14:03:38 +0200 Subject: [PATCH 55/98] =?UTF-8?q?Fix=20=E2=80=9CAuthorized=20applications?= =?UTF-8?q?=E2=80=9D=20inefficiently=20and=20incorrectly=20getting=20last?= =?UTF-8?q?=20use=20date=20(#25060)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth/authorized_applications_controller.rb | 12 ++++++++++++ app/lib/application_extension.rb | 4 ---- .../oauth/authorized_applications/index.html.haml | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb index e3a3c4fc1e..350ae2e906 100644 --- a/app/controllers/oauth/authorized_applications_controller.rb +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -9,6 +9,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio before_action :set_body_classes before_action :set_cache_headers + before_action :set_last_used_at_by_app, only: :index, unless: -> { request.format == :json } + skip_before_action :require_functional! include Localized @@ -35,4 +37,14 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio def set_cache_headers response.cache_control.replace(private: true, no_store: true) end + + def set_last_used_at_by_app + @last_used_at_by_app = Doorkeeper::AccessToken + .select('DISTINCT ON (application_id) application_id, last_used_at') + .where(resource_owner_id: current_resource_owner.id) + .where.not(last_used_at: nil) + .order(application_id: :desc, last_used_at: :desc) + .pluck(:application_id, :last_used_at) + .to_h + end end diff --git a/app/lib/application_extension.rb b/app/lib/application_extension.rb index d61ec0e6e7..4de69c1ead 100644 --- a/app/lib/application_extension.rb +++ b/app/lib/application_extension.rb @@ -9,10 +9,6 @@ module ApplicationExtension validates :redirect_uri, length: { maximum: 2_000 } end - def most_recently_used_access_token - @most_recently_used_access_token ||= access_tokens.where.not(last_used_at: nil).order(last_used_at: :desc).first - end - def confirmation_redirect_uri redirect_uri.lines.first.strip end diff --git a/app/views/oauth/authorized_applications/index.html.haml b/app/views/oauth/authorized_applications/index.html.haml index 0280d8aef8..55d8524dbe 100644 --- a/app/views/oauth/authorized_applications/index.html.haml +++ b/app/views/oauth/authorized_applications/index.html.haml @@ -18,8 +18,8 @@ .announcements-list__item__action-bar .announcements-list__item__meta - - if application.most_recently_used_access_token - = t('doorkeeper.authorized_applications.index.last_used_at', date: l(application.most_recently_used_access_token.last_used_at.to_date)) + - if @last_used_at_by_app[application.id] + = t('doorkeeper.authorized_applications.index.last_used_at', date: l(@last_used_at_by_app[application.id].to_date)) - else = t('doorkeeper.authorized_applications.index.never_used') From bc020264c7f79d4147bb43f73b15ef78acf7ebab Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 22 May 2023 08:49:10 -0400 Subject: [PATCH 56/98] Regenerate rubocop-todo (#24846) --- .rubocop_todo.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 98725535ff..bd1e5bc146 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -585,7 +585,6 @@ RSpec/NoExpectationExample: RSpec/PendingWithoutReason: Exclude: - - 'spec/controllers/statuses_controller_spec.rb' - 'spec/models/account_spec.rb' # This cop supports unsafe autocorrection (--autocorrect-all). @@ -601,10 +600,6 @@ RSpec/RepeatedExample: Exclude: - 'spec/policies/status_policy_spec.rb' -RSpec/RepeatedExampleGroupBody: - Exclude: - - 'spec/controllers/statuses_controller_spec.rb' - RSpec/StubbedMock: Exclude: - 'spec/controllers/api/base_controller_spec.rb' From cc5d2e22dd4b7afb9035cf999979e3cd36d97e46 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 22 May 2023 10:27:35 -0300 Subject: [PATCH 57/98] Improve test coverage for `/api/v1/admin/canonical_email_blocks` (#24985) --- .../canonical_email_blocks_controller_spec.rb | 295 +++++++++++++++++- .../canonical_email_block_fabricator.rb | 2 +- 2 files changed, 292 insertions(+), 5 deletions(-) diff --git a/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb b/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb index e5ee288827..fe39596dfd 100644 --- a/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb +++ b/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb @@ -5,23 +5,182 @@ require 'rails_helper' describe Api::V1::Admin::CanonicalEmailBlocksController do render_views - let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') } - let(:account) { Fabricate(:account) } + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'admin:read:canonical_email_blocks admin:write:canonical_email_blocks' } before do allow(controller).to receive(:doorkeeper_token) { token } end + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + describe 'GET #index' do + context 'with wrong scope' do + before do + get :index + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'with wrong role' do + before do + get :index + end + + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + end + it 'returns http success' do - get :index, params: { account_id: account.id, limit: 2 } + get :index expect(response).to have_http_status(200) end + + context 'when there is no canonical email block' do + it 'returns an empty list' do + get :index + + body = body_as_json + + expect(body).to be_empty + end + end + + context 'when there are canonical email blocks' do + let!(:canonical_email_blocks) { Fabricate.times(5, :canonical_email_block) } + let(:expected_email_hashes) { canonical_email_blocks.pluck(:canonical_email_hash) } + + it 'returns the correct canonical email hashes' do + get :index + + json = body_as_json + + expect(json.pluck(:canonical_email_hash)).to match_array(expected_email_hashes) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of canonical email blocks' do + get :index, params: params + + json = body_as_json + + expect(json.size).to eq(params[:limit]) + end + end + + context 'with since_id param' do + let(:params) { { since_id: canonical_email_blocks[1].id } } + + it 'returns only the canonical email blocks after since_id' do + get :index, params: params + + canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s) + json = body_as_json + + expect(json.pluck(:id)).to match_array(canonical_email_blocks_ids[2..]) + end + end + + context 'with max_id param' do + let(:params) { { max_id: canonical_email_blocks[3].id } } + + it 'returns only the canonical email blocks before max_id' do + get :index, params: params + + canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s) + json = body_as_json + + expect(json.pluck(:id)).to match_array(canonical_email_blocks_ids[..2]) + end + end + end + end + + describe 'GET #show' do + let!(:canonical_email_block) { Fabricate(:canonical_email_block) } + let(:params) { { id: canonical_email_block.id } } + + context 'with wrong scope' do + before do + get :show, params: params + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'with wrong role' do + before do + get :show, params: params + end + + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + end + + context 'when canonical email block exists' do + it 'returns http success' do + get :show, params: params + + expect(response).to have_http_status(200) + end + + it 'returns canonical email block data correctly' do + get :show, params: params + + json = body_as_json + + expect(json[:id]).to eq(canonical_email_block.id.to_s) + expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) + end + end + + context 'when canonical block does not exist' do + it 'returns http not found' do + get :show, params: { id: 0 } + + expect(response).to have_http_status(404) + end + end end describe 'POST #test' do + context 'with wrong scope' do + before do + post :test + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'with wrong role' do + before do + post :test, params: { email: 'whatever@email.com' } + end + + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + end + context 'when required email is not provided' do it 'returns http bad request' do post :test @@ -68,4 +227,132 @@ describe Api::V1::Admin::CanonicalEmailBlocksController do end end end + + describe 'POST #create' do + let(:params) { { email: 'example@email.com' } } + let(:canonical_email_block) { CanonicalEmailBlock.new(email: params[:email]) } + + context 'with wrong scope' do + before do + post :create, params: params + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'with wrong role' do + before do + post :create, params: params + end + + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + end + + it 'returns http success' do + post :create, params: params + + expect(response).to have_http_status(200) + end + + it 'returns canonical_email_hash correctly' do + post :create, params: params + + json = body_as_json + + expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) + end + + context 'when required email param is not provided' do + it 'returns http unprocessable entity' do + post :create + + expect(response).to have_http_status(422) + end + end + + context 'when canonical_email_hash param is provided instead of email' do + let(:params) { { canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } } + + it 'returns http success' do + post :create, params: params + + expect(response).to have_http_status(200) + end + + it 'returns correct canonical_email_hash' do + post :create, params: params + + json = body_as_json + + expect(json[:canonical_email_hash]).to eq(params[:canonical_email_hash]) + end + end + + context 'when both email and canonical_email_hash params are provided' do + let(:params) { { email: 'example@email.com', canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } } + + it 'returns http success' do + post :create, params: params + + expect(response).to have_http_status(200) + end + + it 'ignores canonical_email_hash param' do + post :create, params: params + + json = body_as_json + + expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) + end + end + + context 'when canonical email was already blocked' do + before do + canonical_email_block.save + end + + it 'returns http unprocessable entity' do + post :create, params: params + + expect(response).to have_http_status(422) + end + end + end + + describe 'DELETE #destroy' do + let!(:canonical_email_block) { Fabricate(:canonical_email_block) } + let(:params) { { id: canonical_email_block.id } } + + context 'with wrong scope' do + before do + delete :destroy, params: params + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'with wrong role' do + before do + delete :destroy, params: params + end + + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + end + + it 'returns http success' do + delete :destroy, params: params + + expect(response).to have_http_status(200) + end + + context 'when canonical email block is not found' do + it 'returns http not found' do + delete :destroy, params: { id: 0 } + + expect(response).to have_http_status(404) + end + end + end end diff --git a/spec/fabricators/canonical_email_block_fabricator.rb b/spec/fabricators/canonical_email_block_fabricator.rb index 21d7c24023..3a018059fc 100644 --- a/spec/fabricators/canonical_email_block_fabricator.rb +++ b/spec/fabricators/canonical_email_block_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:canonical_email_block) do - email 'test@example.com' + email { sequence(:email) { |i| "#{i}#{Faker::Internet.email}" } } reference_account { Fabricate(:account) } end From ab029328cf94940dce7ed26d005af3d96a857a59 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Mon, 22 May 2023 15:48:01 +0200 Subject: [PATCH 58/98] Upgrade to React 18 (#24916) --- app/javascript/mastodon/components/column.jsx | 10 +- .../mastodon/components/dropdown_menu.jsx | 11 +- .../mastodon/components/scrollable_list.jsx | 10 +- .../mastodon/containers/media_container.jsx | 4 +- .../components/emoji_picker_dropdown.jsx | 10 +- .../compose/components/language_dropdown.jsx | 7 +- .../compose/components/privacy_dropdown.jsx | 7 +- app/javascript/mastodon/main.jsx | 5 +- app/javascript/packs/admin.jsx | 10 +- app/javascript/packs/public.jsx | 5 +- app/javascript/packs/share.jsx | 5 +- package.json | 13 +- yarn.lock | 178 +++++++----------- 13 files changed, 127 insertions(+), 148 deletions(-) diff --git a/app/javascript/mastodon/components/column.jsx b/app/javascript/mastodon/components/column.jsx index 5780a1397d..c9ea5f7ac5 100644 --- a/app/javascript/mastodon/components/column.jsx +++ b/app/javascript/mastodon/components/column.jsx @@ -3,6 +3,8 @@ import PropTypes from 'prop-types'; import { supportsPassiveEvents } from 'detect-passive-events'; import { scrollTop } from '../scroll'; +const listenerOptions = supportsPassiveEvents ? { passive: true } : false; + export default class Column extends React.PureComponent { static propTypes = { @@ -35,17 +37,17 @@ export default class Column extends React.PureComponent { componentDidMount () { if (this.props.bindToDocument) { - document.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); + document.addEventListener('wheel', this.handleWheel, listenerOptions); } else { - this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); + this.node.addEventListener('wheel', this.handleWheel, listenerOptions); } } componentWillUnmount () { if (this.props.bindToDocument) { - document.removeEventListener('wheel', this.handleWheel); + document.removeEventListener('wheel', this.handleWheel, listenerOptions); } else { - this.node.removeEventListener('wheel', this.handleWheel); + this.node.removeEventListener('wheel', this.handleWheel, listenerOptions); } } diff --git a/app/javascript/mastodon/components/dropdown_menu.jsx b/app/javascript/mastodon/components/dropdown_menu.jsx index 0ed3e904f5..0336585f14 100644 --- a/app/javascript/mastodon/components/dropdown_menu.jsx +++ b/app/javascript/mastodon/components/dropdown_menu.jsx @@ -7,7 +7,7 @@ import { supportsPassiveEvents } from 'detect-passive-events'; import classNames from 'classnames'; import { CircularProgress } from 'mastodon/components/loading_indicator'; -const listenerOptions = supportsPassiveEvents ? { passive: true } : false; +const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; let id = 0; class DropdownMenu extends React.PureComponent { @@ -35,12 +35,13 @@ class DropdownMenu extends React.PureComponent { handleDocumentClick = e => { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); + e.stopPropagation(); } }; componentDidMount () { - document.addEventListener('click', this.handleDocumentClick, false); - document.addEventListener('keydown', this.handleKeyDown, false); + document.addEventListener('click', this.handleDocumentClick, { capture: true }); + document.addEventListener('keydown', this.handleKeyDown, { capture: true }); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); if (this.focusedItem && this.props.openedViaKeyboard) { @@ -49,8 +50,8 @@ class DropdownMenu extends React.PureComponent { } componentWillUnmount () { - document.removeEventListener('click', this.handleDocumentClick, false); - document.removeEventListener('keydown', this.handleKeyDown, false); + document.removeEventListener('click', this.handleDocumentClick, { capture: true }); + document.removeEventListener('keydown', this.handleKeyDown, { capture: true }); document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); } diff --git a/app/javascript/mastodon/components/scrollable_list.jsx b/app/javascript/mastodon/components/scrollable_list.jsx index 3f4e4a59c6..9b9e1e7449 100644 --- a/app/javascript/mastodon/components/scrollable_list.jsx +++ b/app/javascript/mastodon/components/scrollable_list.jsx @@ -15,6 +15,8 @@ import { connect } from 'react-redux'; const MOUSE_IDLE_DELAY = 300; +const listenerOptions = supportsPassiveEvents ? { passive: true } : false; + const mapStateToProps = (state, { scrollKey }) => { return { preventScroll: scrollKey === state.getIn(['dropdown_menu', 'scroll_key']), @@ -237,20 +239,20 @@ class ScrollableList extends PureComponent { attachScrollListener () { if (this.props.bindToDocument) { document.addEventListener('scroll', this.handleScroll); - document.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : undefined); + document.addEventListener('wheel', this.handleWheel, listenerOptions); } else { this.node.addEventListener('scroll', this.handleScroll); - this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : undefined); + this.node.addEventListener('wheel', this.handleWheel, listenerOptions); } } detachScrollListener () { if (this.props.bindToDocument) { document.removeEventListener('scroll', this.handleScroll); - document.removeEventListener('wheel', this.handleWheel); + document.removeEventListener('wheel', this.handleWheel, listenerOptions); } else { this.node.removeEventListener('scroll', this.handleScroll); - this.node.removeEventListener('wheel', this.handleWheel); + this.node.removeEventListener('wheel', this.handleWheel, listenerOptions); } } diff --git a/app/javascript/mastodon/containers/media_container.jsx b/app/javascript/mastodon/containers/media_container.jsx index 0b5ff99dda..1b6caaba8c 100644 --- a/app/javascript/mastodon/containers/media_container.jsx +++ b/app/javascript/mastodon/containers/media_container.jsx @@ -1,5 +1,5 @@ import React, { PureComponent, Fragment } from 'react'; -import ReactDOM from 'react-dom'; +import { createPortal } from 'react-dom'; import PropTypes from 'prop-types'; import { IntlProvider, addLocaleData } from 'react-intl'; import { fromJS } from 'immutable'; @@ -95,7 +95,7 @@ export default class MediaContainer extends PureComponent { }), }); - return ReactDOM.createPortal( + return createPortal( , component, ); diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx index 095e33cf7f..c12d56e4a9 100644 --- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx +++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.jsx @@ -27,7 +27,7 @@ const messages = defineMessages({ let EmojiPicker, Emoji; // load asynchronously -const listenerOptions = supportsPassiveEvents ? { passive: true } : false; +const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; const backgroundImageFn = () => `${assetHost}/emoji/sheet_13.png`; @@ -78,12 +78,12 @@ class ModifierPickerMenu extends React.PureComponent { }; attachListeners () { - document.addEventListener('click', this.handleDocumentClick, false); + document.addEventListener('click', this.handleDocumentClick, { capture: true }); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); } removeListeners () { - document.removeEventListener('click', this.handleDocumentClick, false); + document.removeEventListener('click', this.handleDocumentClick, { capture: true }); document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); } @@ -176,7 +176,7 @@ class EmojiPickerMenuImpl extends React.PureComponent { }; componentDidMount () { - document.addEventListener('click', this.handleDocumentClick, false); + document.addEventListener('click', this.handleDocumentClick, { capture: true }); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need @@ -191,7 +191,7 @@ class EmojiPickerMenuImpl extends React.PureComponent { } componentWillUnmount () { - document.removeEventListener('click', this.handleDocumentClick, false); + document.removeEventListener('click', this.handleDocumentClick, { capture: true }); document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); } diff --git a/app/javascript/mastodon/features/compose/components/language_dropdown.jsx b/app/javascript/mastodon/features/compose/components/language_dropdown.jsx index 08542e3a95..731d7b38de 100644 --- a/app/javascript/mastodon/features/compose/components/language_dropdown.jsx +++ b/app/javascript/mastodon/features/compose/components/language_dropdown.jsx @@ -15,7 +15,7 @@ const messages = defineMessages({ clear: { id: 'emoji_button.clear', defaultMessage: 'Clear' }, }); -const listenerOptions = supportsPassiveEvents ? { passive: true } : false; +const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; class LanguageDropdownMenu extends React.PureComponent { @@ -39,11 +39,12 @@ class LanguageDropdownMenu extends React.PureComponent { handleDocumentClick = e => { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); + e.stopPropagation(); } }; componentDidMount () { - document.addEventListener('click', this.handleDocumentClick, false); + document.addEventListener('click', this.handleDocumentClick, { capture: true }); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need @@ -57,7 +58,7 @@ class LanguageDropdownMenu extends React.PureComponent { } componentWillUnmount () { - document.removeEventListener('click', this.handleDocumentClick, false); + document.removeEventListener('click', this.handleDocumentClick, { capture: true }); document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); } diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx index bbc4b0d064..dd20cccc28 100644 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx @@ -19,7 +19,7 @@ const messages = defineMessages({ change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' }, }); -const listenerOptions = supportsPassiveEvents ? { passive: true } : false; +const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; class PrivacyDropdownMenu extends React.PureComponent { @@ -34,6 +34,7 @@ class PrivacyDropdownMenu extends React.PureComponent { handleDocumentClick = e => { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); + e.stopPropagation(); } }; @@ -91,13 +92,13 @@ class PrivacyDropdownMenu extends React.PureComponent { }; componentDidMount () { - document.addEventListener('click', this.handleDocumentClick, false); + document.addEventListener('click', this.handleDocumentClick, { capture: true }); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); if (this.focusedItem) this.focusedItem.focus({ preventScroll: true }); } componentWillUnmount () { - document.removeEventListener('click', this.handleDocumentClick, false); + document.removeEventListener('click', this.handleDocumentClick, { capture: true }); document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); } diff --git a/app/javascript/mastodon/main.jsx b/app/javascript/mastodon/main.jsx index c5960477db..b31540cb55 100644 --- a/app/javascript/mastodon/main.jsx +++ b/app/javascript/mastodon/main.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { setupBrowserNotifications } from 'mastodon/actions/notifications'; import Mastodon from 'mastodon/containers/mastodon'; import { store } from 'mastodon/store'; @@ -17,7 +17,8 @@ function main() { const mountNode = document.getElementById('mastodon'); const props = JSON.parse(mountNode.getAttribute('data-props')); - ReactDOM.render(, mountNode); + const root = createRoot(mountNode); + root.render(); store.dispatch(setupBrowserNotifications()); if (process.env.NODE_ENV === 'production' && me && 'serviceWorker' in navigator) { diff --git a/app/javascript/packs/admin.jsx b/app/javascript/packs/admin.jsx index 99e903eef3..5f52677afa 100644 --- a/app/javascript/packs/admin.jsx +++ b/app/javascript/packs/admin.jsx @@ -2,7 +2,7 @@ import './public-path'; import { delegate } from '@rails/ujs'; import ready from '../mastodon/ready'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; const setAnnouncementEndsAttributes = (target) => { const valid = target?.value && target?.validity?.valid; @@ -231,11 +231,13 @@ ready(() => { import('../mastodon/containers/admin_component').then(({ default: AdminComponent }) => { return import('../mastodon/components/admin/' + componentName).then(({ default: Component }) => { - ReactDOM.render(( + const root = createRoot(element); + + root.render ( - - ), element); + , + ); }); }).catch(error => { console.error(error); diff --git a/app/javascript/packs/public.jsx b/app/javascript/packs/public.jsx index 1ea39e05a6..ab4ef573b9 100644 --- a/app/javascript/packs/public.jsx +++ b/app/javascript/packs/public.jsx @@ -15,7 +15,7 @@ import { delegate } from '@rails/ujs'; import emojify from '../mastodon/features/emoji/emoji'; import { getLocale } from '../mastodon/locales'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { createBrowserHistory } from 'history'; start(); @@ -153,7 +153,8 @@ function loaded() { const content = document.createElement('div'); - ReactDOM.render(, content); + const root = createRoot(content); + root.render(); document.body.appendChild(content); scrollToDetailedStatus(); }) diff --git a/app/javascript/packs/share.jsx b/app/javascript/packs/share.jsx index 542a2f3ae8..6a87eccda3 100644 --- a/app/javascript/packs/share.jsx +++ b/app/javascript/packs/share.jsx @@ -4,7 +4,7 @@ import { start } from '../mastodon/common'; import ready from '../mastodon/ready'; import ComposeContainer from '../mastodon/containers/compose_container'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; start(); @@ -16,7 +16,8 @@ function loaded() { if(!attr) return; const props = JSON.parse(attr); - ReactDOM.render(, mountNode); + const root = createRoot(mountNode); + root.render(); } } diff --git a/package.json b/package.json index 435885ffa6..57dcf8c9e7 100644 --- a/package.json +++ b/package.json @@ -87,8 +87,8 @@ "postcss-loader": "^4.3.0", "prop-types": "^15.8.1", "punycode": "^2.3.0", - "react": "^16.14.0", - "react-dom": "^16.14.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-helmet": "^6.1.0", "react-hotkeys": "^1.1.4", "react-immutable-proptypes": "^2.2.0", @@ -140,7 +140,7 @@ }, "devDependencies": { "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^12.1.5", + "@testing-library/react": "^14.0.0", "@types/babel__core": "^7.20.0", "@types/emoji-mart": "^3.0.9", "@types/escape-html": "^1.0.2", @@ -155,8 +155,8 @@ "@types/pg": "^8.6.6", "@types/prop-types": "^15.7.5", "@types/punycode": "^2.1.0", - "@types/react": "^16.14.38", - "@types/react-dom": "^16.9.18", + "@types/react": "^18.0.26", + "@types/react-dom": "^18.2.4", "@types/react-helmet": "^6.1.6", "@types/react-immutable-proptypes": "^2.1.0", "@types/react-intl": "2.3.18", @@ -195,7 +195,7 @@ "lint-staged": "^13.2.2", "prettier": "^2.8.8", "react-intl-translations-manager": "^5.0.3", - "react-test-renderer": "^16.14.0", + "react-test-renderer": "^18.2.0", "stylelint": "^15.6.1", "stylelint-config-standard-scss": "^9.0.0", "typescript": "^5.0.4", @@ -203,6 +203,7 @@ "yargs": "^17.7.2" }, "resolutions": { + "@types/react": "^18.0.26", "kind-of": "^6.0.3", "webpack/terser-webpack-plugin": "^4.2.3" }, diff --git a/yarn.lock b/yarn.lock index 4f0a6612c3..2a480cea3a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1055,14 +1055,6 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime-corejs3@^7.10.2": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.3.tgz#931ed6941d3954924a7aa967ee440e60c507b91a" - integrity sha512-HA7RPj5xvJxQl429r5Cxr2trJwOfPjKiqhCXcdQPSqO2G0RHPZpXu4fkYmBaTKCp2c/jRaMK9GB/lN+7zvvFPw== - dependencies: - core-js-pure "^3.0.0" - regenerator-runtime "^0.13.4" - "@babel/runtime@7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0.tgz#adeb78fedfc855aa05bc041640f3f6f98e85424c" @@ -1070,7 +1062,7 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q== @@ -1822,18 +1814,18 @@ magic-string "^0.25.0" string.prototype.matchall "^4.0.6" -"@testing-library/dom@^8.0.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.1.0.tgz#f8358b1883844ea569ba76b7e94582168df5370d" - integrity sha512-kmW9alndr19qd6DABzQ978zKQ+J65gU2Rzkl8hriIetPnwpesRaK4//jEQyYh8fEALmGhomD/LBQqt+o+DL95Q== +"@testing-library/dom@^9.0.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.2.0.tgz#0e1f45e956f2a16f471559c06edd8827c4832f04" + integrity sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.12.5" - "@types/aria-query" "^4.2.0" - aria-query "^4.2.2" + "@types/aria-query" "^5.0.1" + aria-query "^5.0.0" chalk "^4.1.0" - dom-accessibility-api "^0.5.6" - lz-string "^1.4.4" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" pretty-format "^27.0.2" "@testing-library/jest-dom@^5.16.5": @@ -1851,14 +1843,14 @@ lodash "^4.17.15" redent "^3.0.0" -"@testing-library/react@^12.1.5": - version "12.1.5" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" - integrity sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg== +"@testing-library/react@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-14.0.0.tgz#59030392a6792450b9ab8e67aea5f3cc18d6347c" + integrity sha512-S04gSNJbYE30TlIMLTzv6QCTzt9AqIF5y6s6SzVFILNcNvbV/jU96GeiTPillGQo+Ny64M/5PV7klNYYgv5Dfg== dependencies: "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^8.0.0" - "@types/react-dom" "<18.0.0" + "@testing-library/dom" "^9.0.0" + "@types/react-dom" "^18.0.0" "@tootallnate/once@2": version "2.0.0" @@ -1870,10 +1862,10 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== -"@types/aria-query@^4.2.0": - version "4.2.0" - resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0" - integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A== +"@types/aria-query@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.1.tgz#3286741fb8f1e1580ac28784add4c7a1d49bdfbc" + integrity sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q== "@types/babel__core@^7.1.12", "@types/babel__core@^7.1.14", "@types/babel__core@^7.1.3": version "7.1.18" @@ -2189,19 +2181,12 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/react-dom@<18.0.0": - version "17.0.15" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.15.tgz#f2c8efde11521a4b7991e076cb9c70ba3bb0d156" - integrity sha512-Tr9VU9DvNoHDWlmecmcsE5ZZiUkYx+nKBzum4Oxe1K0yJVyBlfbq7H3eXjxXqJczBKqPGq3EgfTru4MgKb9+Yw== +"@types/react-dom@^18.0.0", "@types/react-dom@^18.2.4": + version "18.2.4" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.4.tgz#13f25bfbf4e404d26f62ac6e406591451acba9e0" + integrity sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw== dependencies: - "@types/react" "^17" - -"@types/react-dom@^16.9.18": - version "16.9.18" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.18.tgz#1fda8b84370b1339d639a797a84c16d5a195b419" - integrity sha512-lmNARUX3+rNF/nmoAFqasG0jAA7q6MeGZK/fdeLwY3kAA4NPgHHrG5bNQe2B5xmD4B+x6Z6h0rEJQ7MEEgQxsw== - dependencies: - "@types/react" "^16" + "@types/react" "*" "@types/react-helmet@^6.1.6": version "6.1.6" @@ -2323,28 +2308,10 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^17": - version "17.0.44" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.44.tgz#c3714bd34dd551ab20b8015d9d0dbec812a51ec7" - integrity sha512-Ye0nlw09GeMp2Suh8qoOv0odfgCoowfM/9MG6WeRD60Gq9wS90bdkdRtYbRkNhXOpG4H+YXGvj4wOWhAC0LJ1g== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/react@>=16.9.11": - version "18.0.26" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917" - integrity sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug== - dependencies: - "@types/prop-types" "*" - "@types/scheduler" "*" - csstype "^3.0.2" - -"@types/react@^16", "@types/react@^16.14.38": - version "16.14.38" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.38.tgz#b814d157ca8906603593d5106f6d733af9b79df4" - integrity sha512-PbEjuhwkdH6IB5Sak6BFAqpVMHY/wJxa0EG3bKkr0vWA2hSDIq3iEMhHyqjXrDFMqRzkiQkdyNXOnoELrh/9aQ== +"@types/react@*", "@types/react@>=16.9.11", "@types/react@^18.0.26": + version "18.2.6" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.6.tgz#5cd53ee0d30ffc193b159d3516c8c8ad2f19d571" + integrity sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -2975,14 +2942,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" - integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== - dependencies: - "@babel/runtime" "^7.10.2" - "@babel/runtime-corejs3" "^7.10.2" - aria-query@^5.0.0, aria-query@^5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" @@ -4151,11 +4110,6 @@ core-js-compat@^3.25.1: dependencies: browserslist "^4.21.4" -core-js-pure@^3.0.0: - version "3.6.5" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" - integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== - core-js@^2.5.0: version "2.6.12" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" @@ -4756,6 +4710,11 @@ dom-accessibility-api@^0.5.6: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.6.tgz#3f5d43b52c7a3bd68b5fb63fa47b4e4c1fdf65a9" integrity sha512-DplGLZd8L1lN64jlT27N9TVSESFR5STaEJvX+thCby7fuCHonfPpAlodYc3vuUYbDuDec5w8AMP7oCM5TWFsqw== +dom-accessibility-api@^0.5.9: + version "0.5.16" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + dom-helpers@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" @@ -7903,10 +7862,10 @@ lru-cache@^9.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-9.0.1.tgz#ac061ed291f8b9adaca2b085534bb1d3b61bef83" integrity sha512-C8QsKIN1UIXeOs3iWmiZ1lQY+EnKDojWd37fXy1aSbJvH4iSma1uy2OWuoB3m4SYRli5+CUjDv3Dij5DVoetmg== -lz-string@^1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" - integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== magic-string@^0.25.0, magic-string@^0.25.7: version "0.25.9" @@ -9534,15 +9493,13 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" -react-dom@^16.14.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89" - integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw== +react-dom@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.1" + scheduler "^0.23.0" react-event-listener@^0.6.0: version "0.6.6" @@ -9612,7 +9569,12 @@ react-intl@^2.9.0: intl-relativeformat "^2.1.0" invariant "^2.1.1" -react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.6: +"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -9730,6 +9692,14 @@ react-select@*, react-select@^5.7.3: react-transition-group "^4.3.0" use-isomorphic-layout-effect "^1.1.2" +react-shallow-renderer@^16.15.0: + version "16.15.0" + resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz#48fb2cf9b23d23cde96708fe5273a7d3446f4457" + integrity sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA== + dependencies: + object-assign "^4.1.1" + react-is "^16.12.0 || ^17.0.0 || ^18.0.0" + react-side-effect@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.2.tgz#dc6345b9e8f9906dc2eeb68700b615e0b4fe752a" @@ -9773,15 +9743,14 @@ react-swipeable-views@^0.14.0: react-swipeable-views-utils "^0.14.0" warning "^4.0.1" -react-test-renderer@^16.14.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz#e98360087348e260c56d4fe2315e970480c228ae" - integrity sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg== +react-test-renderer@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-18.2.0.tgz#1dd912bd908ff26da5b9fca4fd1c489b9523d37e" + integrity sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA== dependencies: - object-assign "^4.1.1" - prop-types "^15.6.2" - react-is "^16.8.6" - scheduler "^0.19.1" + react-is "^18.2.0" + react-shallow-renderer "^16.15.0" + scheduler "^0.23.0" react-textarea-autosize@*, react-textarea-autosize@^8.4.1: version "8.4.1" @@ -9809,14 +9778,12 @@ react-transition-group@^4.3.0: loose-envify "^1.4.0" prop-types "^15.6.2" -react@^16.14.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" - integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" read-pkg-up@^7.0.1: version "7.0.1" @@ -9946,7 +9913,7 @@ regenerator-runtime@^0.12.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== -regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4: +regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.3: version "0.13.11" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== @@ -10296,13 +10263,12 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" -scheduler@^0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" - integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" schema-utils@^1.0.0: version "1.0.0" From c59cbf1a38b5b429669e2664d4109c4f7cedf684 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 22 May 2023 17:38:05 +0200 Subject: [PATCH 59/98] Fix account confirmation flow not returning to app after captcha validation (#25057) --- .../auth/confirmations/captcha.html.haml | 1 + spec/features/captcha_spec.rb | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 spec/features/captcha_spec.rb diff --git a/app/views/auth/confirmations/captcha.html.haml b/app/views/auth/confirmations/captcha.html.haml index 1f577383eb..77f4b35b4f 100644 --- a/app/views/auth/confirmations/captcha.html.haml +++ b/app/views/auth/confirmations/captcha.html.haml @@ -5,6 +5,7 @@ = render 'auth/shared/progress', stage: 'confirm' = hidden_field_tag :confirmation_token, params[:confirmation_token] + = hidden_field_tag :redirect_to_app, params[:redirect_to_app] %p.lead= t('auth.captcha_confirmation.hint_html') diff --git a/spec/features/captcha_spec.rb b/spec/features/captcha_spec.rb new file mode 100644 index 0000000000..db89ff3e61 --- /dev/null +++ b/spec/features/captcha_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'email confirmation flow when captcha is enabled' do + let(:user) { Fabricate(:user, confirmed_at: nil, confirmation_token: 'foobar', created_by_application: client_app) } + let(:client_app) { nil } + + before do + # rubocop:disable RSpec/AnyInstance -- easiest way to deal with that that I know of + allow_any_instance_of(Auth::ConfirmationsController).to receive(:captcha_enabled?).and_return(true) + allow_any_instance_of(Auth::ConfirmationsController).to receive(:check_captcha!).and_return(true) + allow_any_instance_of(Auth::ConfirmationsController).to receive(:render_captcha).and_return(nil) + # rubocop:enable RSpec/AnyInstance + end + + context 'when the user signed up through an app' do + let(:client_app) { Fabricate(:application) } + + it 'logs in' do + visit "/auth/confirmation?confirmation_token=#{user.confirmation_token}&redirect_to_app=true" + + # It presents the user with a captcha form + expect(page).to have_title(I18n.t('auth.captcha_confirmation.title')) + + # It does not confirm the user just yet + expect(user.reload.confirmed?).to be false + + # It redirects to app and confirms user + click_on I18n.t('challenge.confirm') + expect(user.reload.confirmed?).to be true + expect(page).to have_current_path(/\A#{client_app.confirmation_redirect_uri}/, url: true) + end + end +end From 5009e9aed5cb5d22ebb241f1c19a7e03bb0d3f9b Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 22 May 2023 13:11:28 -0300 Subject: [PATCH 60/98] Fix uncaught NoMethodError in POST `/api/v1/featured_tags` (#25063) --- app/controllers/api/v1/featured_tags_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/v1/featured_tags_controller.rb b/app/controllers/api/v1/featured_tags_controller.rb index edb42a94ea..3af0dd2610 100644 --- a/app/controllers/api/v1/featured_tags_controller.rb +++ b/app/controllers/api/v1/featured_tags_controller.rb @@ -33,6 +33,6 @@ class Api::V1::FeaturedTagsController < Api::BaseController end def featured_tag_params - params.permit(:name) + params.require(:name) end end From 6e38ffcaeed7c7a2c31086222962ec111720e6f6 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 22 May 2023 14:14:54 -0300 Subject: [PATCH 61/98] Fix uncaught TypeError in POST `/api/v1/featured_tags` (#25072) Co-authored-by: Claire --- app/controllers/api/v1/featured_tags_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/v1/featured_tags_controller.rb b/app/controllers/api/v1/featured_tags_controller.rb index 3af0dd2610..5c81877bd9 100644 --- a/app/controllers/api/v1/featured_tags_controller.rb +++ b/app/controllers/api/v1/featured_tags_controller.rb @@ -13,7 +13,7 @@ class Api::V1::FeaturedTagsController < Api::BaseController end def create - featured_tag = CreateFeaturedTagService.new.call(current_account, featured_tag_params[:name]) + featured_tag = CreateFeaturedTagService.new.call(current_account, params.require(:name)) render json: featured_tag, serializer: REST::FeaturedTagSerializer end From 18361ae0c53b462fa32c0e178cbd94a58a62f624 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 22 May 2023 21:18:21 +0200 Subject: [PATCH 62/98] Allow scripts in post embed previews (#25071) --- app/javascript/mastodon/features/ui/components/embed_modal.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/ui/components/embed_modal.jsx b/app/javascript/mastodon/features/ui/components/embed_modal.jsx index 3e0bcc93cb..949c640421 100644 --- a/app/javascript/mastodon/features/ui/components/embed_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/embed_modal.jsx @@ -85,7 +85,7 @@ class EmbedModal extends ImmutablePureComponent { className='embed-modal__iframe' frameBorder='0' ref={this.setIframeRef} - sandbox='allow-same-origin' + sandbox='allow-scripts allow-same-origin' title='preview' />
From 0b87919abfdf288412062342307bae3695e71f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=84=E3=81=A1=20=E3=81=B2?= Date: Tue, 23 May 2023 15:58:08 +0900 Subject: [PATCH 63/98] Rewrite `` as FC and TS (#25055) --- app/javascript/mastodon/components/admin/Counter.jsx | 2 +- .../mastodon/components/admin/Dimension.jsx | 2 +- app/javascript/mastodon/components/display_name.tsx | 2 +- app/javascript/mastodon/components/empty_account.tsx | 2 +- app/javascript/mastodon/components/hashtag.jsx | 2 +- app/javascript/mastodon/components/server_banner.jsx | 2 +- app/javascript/mastodon/components/skeleton.jsx | 11 ----------- app/javascript/mastodon/components/skeleton.tsx | 12 ++++++++++++ app/javascript/mastodon/features/about/index.jsx | 2 +- .../mastodon/features/explore/components/story.jsx | 2 +- .../mastodon/features/privacy_policy/index.jsx | 2 +- 11 files changed, 21 insertions(+), 20 deletions(-) delete mode 100644 app/javascript/mastodon/components/skeleton.jsx create mode 100644 app/javascript/mastodon/components/skeleton.tsx diff --git a/app/javascript/mastodon/components/admin/Counter.jsx b/app/javascript/mastodon/components/admin/Counter.jsx index 5a5b2b869e..569f8628a9 100644 --- a/app/javascript/mastodon/components/admin/Counter.jsx +++ b/app/javascript/mastodon/components/admin/Counter.jsx @@ -4,7 +4,7 @@ import api from 'mastodon/api'; import { FormattedNumber } from 'react-intl'; import { Sparklines, SparklinesCurve } from 'react-sparklines'; import classNames from 'classnames'; -import Skeleton from 'mastodon/components/skeleton'; +import { Skeleton } from 'mastodon/components/skeleton'; const percIncrease = (a, b) => { let percent; diff --git a/app/javascript/mastodon/components/admin/Dimension.jsx b/app/javascript/mastodon/components/admin/Dimension.jsx index 977c8208df..3005c15ae9 100644 --- a/app/javascript/mastodon/components/admin/Dimension.jsx +++ b/app/javascript/mastodon/components/admin/Dimension.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import api from 'mastodon/api'; import { FormattedNumber } from 'react-intl'; import { roundTo10 } from 'mastodon/utils/numbers'; -import Skeleton from 'mastodon/components/skeleton'; +import { Skeleton } from 'mastodon/components/skeleton'; export default class Dimension extends React.PureComponent { diff --git a/app/javascript/mastodon/components/display_name.tsx b/app/javascript/mastodon/components/display_name.tsx index ce435066d6..c537cd24ce 100644 --- a/app/javascript/mastodon/components/display_name.tsx +++ b/app/javascript/mastodon/components/display_name.tsx @@ -5,7 +5,7 @@ import type { List } from 'immutable'; import type { Account } from '../../types/resources'; import { autoPlayGif } from '../initial_state'; -import Skeleton from './skeleton'; +import { Skeleton } from './skeleton'; interface Props { account?: Account; diff --git a/app/javascript/mastodon/components/empty_account.tsx b/app/javascript/mastodon/components/empty_account.tsx index 3adb5b20f8..a4a6b7f823 100644 --- a/app/javascript/mastodon/components/empty_account.tsx +++ b/app/javascript/mastodon/components/empty_account.tsx @@ -3,7 +3,7 @@ import React from 'react'; import classNames from 'classnames'; import { DisplayName } from 'mastodon/components/display_name'; -import Skeleton from 'mastodon/components/skeleton'; +import { Skeleton } from 'mastodon/components/skeleton'; interface Props { size?: number; diff --git a/app/javascript/mastodon/components/hashtag.jsx b/app/javascript/mastodon/components/hashtag.jsx index d03b1a45a7..3efd679a52 100644 --- a/app/javascript/mastodon/components/hashtag.jsx +++ b/app/javascript/mastodon/components/hashtag.jsx @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { Link } from 'react-router-dom'; import ShortNumber from 'mastodon/components/short_number'; -import Skeleton from 'mastodon/components/skeleton'; +import { Skeleton } from 'mastodon/components/skeleton'; import classNames from 'classnames'; class SilentErrorBoundary extends React.Component { diff --git a/app/javascript/mastodon/components/server_banner.jsx b/app/javascript/mastodon/components/server_banner.jsx index 9669decc85..6c3abd5b6f 100644 --- a/app/javascript/mastodon/components/server_banner.jsx +++ b/app/javascript/mastodon/components/server_banner.jsx @@ -4,7 +4,7 @@ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { fetchServer } from 'mastodon/actions/server'; import ShortNumber from 'mastodon/components/short_number'; -import Skeleton from 'mastodon/components/skeleton'; +import { Skeleton } from 'mastodon/components/skeleton'; import Account from 'mastodon/containers/account_container'; import { domain } from 'mastodon/initial_state'; import { ServerHeroImage } from 'mastodon/components/server_hero_image'; diff --git a/app/javascript/mastodon/components/skeleton.jsx b/app/javascript/mastodon/components/skeleton.jsx deleted file mode 100644 index 6a17ffb261..0000000000 --- a/app/javascript/mastodon/components/skeleton.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const Skeleton = ({ width, height }) => ; - -Skeleton.propTypes = { - width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), -}; - -export default Skeleton; diff --git a/app/javascript/mastodon/components/skeleton.tsx b/app/javascript/mastodon/components/skeleton.tsx new file mode 100644 index 0000000000..8d43e6827d --- /dev/null +++ b/app/javascript/mastodon/components/skeleton.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +interface Props { + width?: number | string; + height?: number | string; +} + +export const Skeleton: React.FC = ({ width, height }) => ( + + ‌ + +); diff --git a/app/javascript/mastodon/features/about/index.jsx b/app/javascript/mastodon/features/about/index.jsx index f025a9633a..61a9180a10 100644 --- a/app/javascript/mastodon/features/about/index.jsx +++ b/app/javascript/mastodon/features/about/index.jsx @@ -8,7 +8,7 @@ import LinkFooter from 'mastodon/features/ui/components/link_footer'; import { Helmet } from 'react-helmet'; import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'mastodon/actions/server'; import Account from 'mastodon/containers/account_container'; -import Skeleton from 'mastodon/components/skeleton'; +import { Skeleton } from 'mastodon/components/skeleton'; import { Icon } from 'mastodon/components/icon'; import classNames from 'classnames'; import { ServerHeroImage } from 'mastodon/components/server_hero_image'; diff --git a/app/javascript/mastodon/features/explore/components/story.jsx b/app/javascript/mastodon/features/explore/components/story.jsx index c7320c886d..6e8db62307 100644 --- a/app/javascript/mastodon/features/explore/components/story.jsx +++ b/app/javascript/mastodon/features/explore/components/story.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { Blurhash } from 'mastodon/components/blurhash'; import { accountsCountRenderer } from 'mastodon/components/hashtag'; import ShortNumber from 'mastodon/components/short_number'; -import Skeleton from 'mastodon/components/skeleton'; +import { Skeleton } from 'mastodon/components/skeleton'; import classNames from 'classnames'; export default class Story extends React.PureComponent { diff --git a/app/javascript/mastodon/features/privacy_policy/index.jsx b/app/javascript/mastodon/features/privacy_policy/index.jsx index d5bbda6a33..07a6914989 100644 --- a/app/javascript/mastodon/features/privacy_policy/index.jsx +++ b/app/javascript/mastodon/features/privacy_policy/index.jsx @@ -4,7 +4,7 @@ import { Helmet } from 'react-helmet'; import { FormattedMessage, FormattedDate, injectIntl, defineMessages } from 'react-intl'; import Column from 'mastodon/components/column'; import api from 'mastodon/api'; -import Skeleton from 'mastodon/components/skeleton'; +import { Skeleton } from 'mastodon/components/skeleton'; const messages = defineMessages({ title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' }, From 53ecf11b105bde699716095d300e04bcb3254d9d Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 23 May 2023 02:58:40 -0400 Subject: [PATCH 64/98] Don't run Rubocop excluded files for lint-staged (#25090) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 57dcf8c9e7..977ce113fd 100644 --- a/package.json +++ b/package.json @@ -213,7 +213,7 @@ }, "lint-staged": { "*": "prettier --ignore-unknown --write", - "Capfile|Gemfile|*.{rb,ruby,ru,rake}": "bundle exec rubocop -a", + "Capfile|Gemfile|*.{rb,ruby,ru,rake}": "bundle exec rubocop --force-exclusion -a", "*.{js,jsx,ts,tsx}": "eslint --fix", "*.{css,scss}": "stylelint --fix" } From ffce0d14bd914753440025da8732e330747a7f30 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Tue, 23 May 2023 04:01:11 -0300 Subject: [PATCH 65/98] Improve test coverage for `/api/v1/featured_tags` (#25076) --- .../api/v1/featured_tags_controller_spec.rb | 23 -- spec/fabricators/featured_tag_fabricator.rb | 2 +- spec/requests/api/v1/featured_tags_spec.rb | 201 ++++++++++++++++++ 3 files changed, 202 insertions(+), 24 deletions(-) delete mode 100644 spec/controllers/api/v1/featured_tags_controller_spec.rb create mode 100644 spec/requests/api/v1/featured_tags_spec.rb diff --git a/spec/controllers/api/v1/featured_tags_controller_spec.rb b/spec/controllers/api/v1/featured_tags_controller_spec.rb deleted file mode 100644 index aac9429015..0000000000 --- a/spec/controllers/api/v1/featured_tags_controller_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Api::V1::FeaturedTagsController do - render_views - - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } - let(:account) { Fabricate(:account) } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'GET #index' do - it 'returns http success' do - get :index, params: { account_id: account.id, limit: 2 } - - expect(response).to have_http_status(200) - end - end -end diff --git a/spec/fabricators/featured_tag_fabricator.rb b/spec/fabricators/featured_tag_fabricator.rb index 747d8e36a5..838364056b 100644 --- a/spec/fabricators/featured_tag_fabricator.rb +++ b/spec/fabricators/featured_tag_fabricator.rb @@ -3,5 +3,5 @@ Fabricator(:featured_tag) do account tag - name 'Tag' + name { sequence(:name) { |i| "Tag#{i}" } } end diff --git a/spec/requests/api/v1/featured_tags_spec.rb b/spec/requests/api/v1/featured_tags_spec.rb new file mode 100644 index 0000000000..8a552c1d4b --- /dev/null +++ b/spec/requests/api/v1/featured_tags_spec.rb @@ -0,0 +1,201 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'FeaturedTags' do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:accounts write:accounts' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + describe 'GET /api/v1/featured_tags' do + context 'with wrong scope' do + before do + get '/api/v1/featured_tags', headers: headers + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'when Authorization header is missing' do + it 'returns http unauthorized' do + get '/api/v1/featured_tags' + + expect(response).to have_http_status(401) + end + end + + it 'returns http success' do + get '/api/v1/featured_tags', headers: headers + + expect(response).to have_http_status(200) + end + + context 'when the requesting user has no featured tag' do + before { Fabricate.times(3, :featured_tag) } + + it 'returns an empty body' do + get '/api/v1/featured_tags', headers: headers + + body = body_as_json + + expect(body).to be_empty + end + end + + context 'when the requesting user has featured tags' do + let!(:user_featured_tags) { Fabricate.times(5, :featured_tag, account: user.account) } + + it 'returns only the featured tags belonging to the requesting user' do + get '/api/v1/featured_tags', headers: headers + + body = body_as_json + expected_ids = user_featured_tags.pluck(:id).map(&:to_s) + + expect(body.pluck(:id)).to match_array(expected_ids) + end + end + end + + describe 'POST /api/v1/featured_tags' do + let(:params) { { name: 'tag' } } + + it 'returns http success' do + post '/api/v1/featured_tags', headers: headers, params: params + + expect(response).to have_http_status(200) + end + + it 'returns the correct tag name' do + post '/api/v1/featured_tags', headers: headers, params: params + + body = body_as_json + + expect(body[:name]).to eq(params[:name]) + end + + it 'creates a new featured tag for the requesting user' do + post '/api/v1/featured_tags', headers: headers, params: params + + featured_tag = FeaturedTag.find_by(name: params[:name], account: user.account) + + expect(featured_tag).to be_present + end + + context 'with wrong scope' do + before do + post '/api/v1/featured_tags', headers: headers, params: params + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'when Authorization header is missing' do + it 'returns http unauthorized' do + post '/api/v1/featured_tags', params: params + + expect(response).to have_http_status(401) + end + end + + context 'when required param "name" is not provided' do + it 'returns http bad request' do + post '/api/v1/featured_tags', headers: headers + + expect(response).to have_http_status(400) + end + end + + context 'when provided tag name is invalid' do + let(:params) { { name: 'asj&*!' } } + + it 'returns http unprocessable entity' do + post '/api/v1/featured_tags', headers: headers, params: params + + expect(response).to have_http_status(422) + end + end + + context 'when tag name is already taken' do + before do + FeaturedTag.create(name: params[:name], account: user.account) + end + + it 'returns http unprocessable entity' do + post '/api/v1/featured_tags', headers: headers, params: params + + expect(response).to have_http_status(422) + end + end + end + + describe 'DELETE /api/v1/featured_tags' do + let!(:featured_tag) { FeaturedTag.create(name: 'tag', account: user.account) } + let(:id) { featured_tag.id } + + it 'returns http success' do + delete "/api/v1/featured_tags/#{id}", headers: headers + + expect(response).to have_http_status(200) + end + + it 'returns an empty body' do + delete "/api/v1/featured_tags/#{id}", headers: headers + + body = body_as_json + + expect(body).to be_empty + end + + it 'deletes the featured tag' do + delete "/api/v1/featured_tags/#{id}", headers: headers + + featured_tag = FeaturedTag.find_by(id: id) + + expect(featured_tag).to be_nil + end + + context 'with wrong scope' do + before do + delete "/api/v1/featured_tags/#{id}", headers: headers + end + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + end + + context 'when Authorization header is missing' do + it 'returns http unauthorized' do + delete "/api/v1/featured_tags/#{id}" + + expect(response).to have_http_status(401) + end + end + + context 'when featured tag with given id does not exist' do + it 'returns http not found' do + delete '/api/v1/featured_tags/0', headers: headers + + expect(response).to have_http_status(404) + end + end + + context 'when deleting a featured tag of another user' do + let!(:other_user_featured_tag) { Fabricate(:featured_tag) } + let(:id) { other_user_featured_tag.id } + + it 'returns http not found' do + delete "/api/v1/featured_tags/#{id}", headers: headers + + expect(response).to have_http_status(404) + end + end + end +end From 83648911a6c08c0aac127661b83b3be5582d53b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 09:06:43 +0200 Subject: [PATCH 66/98] Bump rspec-rails from 6.0.1 to 6.0.2 (#25092) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index acea3bbbed..d9086a9c21 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -398,9 +398,9 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.20.0) + loofah (2.21.3) crass (~> 1.0.2) - nokogiri (>= 1.5.9) + nokogiri (>= 1.12.0) mail (2.8.1) mini_mime (>= 0.1.1) net-imap @@ -588,14 +588,14 @@ GEM rspec-mocks (3.12.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) - rspec-rails (6.0.1) + rspec-rails (6.0.2) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) - rspec-core (~> 3.11) - rspec-expectations (~> 3.11) - rspec-mocks (~> 3.11) - rspec-support (~> 3.11) + rspec-core (~> 3.12) + rspec-expectations (~> 3.12) + rspec-mocks (~> 3.12) + rspec-support (~> 3.12) rspec-sidekiq (3.1.0) rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) @@ -761,7 +761,7 @@ GEM xorcist (1.1.3) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.7) + zeitwerk (2.6.8) PLATFORMS ruby From b2c5340260bb05656b843dc9ef8c316c3b24e580 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 09:07:01 +0200 Subject: [PATCH 67/98] Bump connection_pool from 2.4.0 to 2.4.1 (#25088) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index d9086a9c21..a7e624da7c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -189,7 +189,7 @@ GEM coderay (1.1.3) color_diff (0.1) concurrent-ruby (1.2.2) - connection_pool (2.4.0) + connection_pool (2.4.1) cose (1.3.0) cbor (~> 0.5.9) openssl-signature_algorithm (~> 1.0) From e27d9a9130778208d1e587d6fdca7913214bf9be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 09:15:43 +0200 Subject: [PATCH 68/98] Bump @typescript-eslint/parser from 5.59.6 to 5.59.7 (#25080) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 48 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 977ce113fd..a872d25766 100644 --- a/package.json +++ b/package.json @@ -176,7 +176,7 @@ "@types/webpack": "^4.41.33", "@types/yargs": "^17.0.24", "@typescript-eslint/eslint-plugin": "^5.59.6", - "@typescript-eslint/parser": "^5.59.6", + "@typescript-eslint/parser": "^5.59.7", "babel-jest": "^29.5.0", "eslint": "^8.40.0", "eslint-config-prettier": "^8.8.0", diff --git a/yarn.lock b/yarn.lock index 2a480cea3a..940d092925 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2465,14 +2465,14 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.59.6": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.6.tgz#bd36f71f5a529f828e20b627078d3ed6738dbb40" - integrity sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA== +"@typescript-eslint/parser@^5.59.7": + version "5.59.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.7.tgz#02682554d7c1028b89aa44a48bf598db33048caa" + integrity sha512-VhpsIEuq/8i5SF+mPg9jSdIwgMBBp0z9XqjiEay+81PYLJuroN+ET1hM5IhkiYMJd9MkTz8iJLt7aaGAgzWUbQ== dependencies: - "@typescript-eslint/scope-manager" "5.59.6" - "@typescript-eslint/types" "5.59.6" - "@typescript-eslint/typescript-estree" "5.59.6" + "@typescript-eslint/scope-manager" "5.59.7" + "@typescript-eslint/types" "5.59.7" + "@typescript-eslint/typescript-estree" "5.59.7" debug "^4.3.4" "@typescript-eslint/scope-manager@5.59.6": @@ -2483,6 +2483,14 @@ "@typescript-eslint/types" "5.59.6" "@typescript-eslint/visitor-keys" "5.59.6" +"@typescript-eslint/scope-manager@5.59.7": + version "5.59.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.7.tgz#0243f41f9066f3339d2f06d7f72d6c16a16769e2" + integrity sha512-FL6hkYWK9zBGdxT2wWEd2W8ocXMu3K94i3gvMrjXpx+koFYdYV7KprKfirpgY34vTGzEPPuKoERpP8kD5h7vZQ== + dependencies: + "@typescript-eslint/types" "5.59.7" + "@typescript-eslint/visitor-keys" "5.59.7" + "@typescript-eslint/type-utils@5.59.6": version "5.59.6" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz#37c51d2ae36127d8b81f32a0a4d2efae19277c48" @@ -2503,6 +2511,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.6.tgz#5a6557a772af044afe890d77c6a07e8c23c2460b" integrity sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA== +"@typescript-eslint/types@5.59.7": + version "5.59.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.7.tgz#6f4857203fceee91d0034ccc30512d2939000742" + integrity sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A== + "@typescript-eslint/typescript-estree@5.59.0": version "5.59.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.0.tgz#8869156ee1dcfc5a95be3ed0e2809969ea28e965" @@ -2529,6 +2542,19 @@ semver "^7.3.7" tsutils "^3.21.0" +"@typescript-eslint/typescript-estree@5.59.7": + version "5.59.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.7.tgz#b887acbd4b58e654829c94860dbff4ac55c5cff8" + integrity sha512-4A1NtZ1I3wMN2UGDkU9HMBL+TIQfbrh4uS0WDMMpf3xMRursDbqEf1ahh6vAAe3mObt8k3ZATnezwG4pdtWuUQ== + dependencies: + "@typescript-eslint/types" "5.59.7" + "@typescript-eslint/visitor-keys" "5.59.7" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + "@typescript-eslint/utils@5.59.6": version "5.59.6" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.6.tgz#82960fe23788113fc3b1f9d4663d6773b7907839" @@ -2559,6 +2585,14 @@ "@typescript-eslint/types" "5.59.6" eslint-visitor-keys "^3.3.0" +"@typescript-eslint/visitor-keys@5.59.7": + version "5.59.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz#09c36eaf268086b4fbb5eb9dc5199391b6485fc5" + integrity sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ== + dependencies: + "@typescript-eslint/types" "5.59.7" + eslint-visitor-keys "^3.3.0" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" From 1f88c425df568c5bc8113f5e2001cb9ee921d40c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 09:25:20 +0200 Subject: [PATCH 69/98] Bump @typescript-eslint/eslint-plugin from 5.59.6 to 5.59.7 (#25085) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 74 ++++++++++++++-------------------------------------- 2 files changed, 21 insertions(+), 55 deletions(-) diff --git a/package.json b/package.json index a872d25766..71ae589953 100644 --- a/package.json +++ b/package.json @@ -175,7 +175,7 @@ "@types/uuid": "^9.0.0", "@types/webpack": "^4.41.33", "@types/yargs": "^17.0.24", - "@typescript-eslint/eslint-plugin": "^5.59.6", + "@typescript-eslint/eslint-plugin": "^5.59.7", "@typescript-eslint/parser": "^5.59.7", "babel-jest": "^29.5.0", "eslint": "^8.40.0", diff --git a/yarn.lock b/yarn.lock index 940d092925..66461c3081 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2449,15 +2449,15 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.59.6": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz#a350faef1baa1e961698240f922d8de1761a9e2b" - integrity sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw== +"@typescript-eslint/eslint-plugin@^5.59.7": + version "5.59.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.7.tgz#e470af414f05ecfdc05a23e9ce6ec8f91db56fe2" + integrity sha512-BL+jYxUFIbuYwy+4fF86k5vdT9lT0CNJ6HtwrIvGh0PhH8s0yy5rjaKH2fDCrz5ITHy07WCzVGNvAmjJh4IJFA== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.59.6" - "@typescript-eslint/type-utils" "5.59.6" - "@typescript-eslint/utils" "5.59.6" + "@typescript-eslint/scope-manager" "5.59.7" + "@typescript-eslint/type-utils" "5.59.7" + "@typescript-eslint/utils" "5.59.7" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" @@ -2475,14 +2475,6 @@ "@typescript-eslint/typescript-estree" "5.59.7" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.59.6": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz#d43a3687aa4433868527cfe797eb267c6be35f19" - integrity sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ== - dependencies: - "@typescript-eslint/types" "5.59.6" - "@typescript-eslint/visitor-keys" "5.59.6" - "@typescript-eslint/scope-manager@5.59.7": version "5.59.7" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.7.tgz#0243f41f9066f3339d2f06d7f72d6c16a16769e2" @@ -2491,13 +2483,13 @@ "@typescript-eslint/types" "5.59.7" "@typescript-eslint/visitor-keys" "5.59.7" -"@typescript-eslint/type-utils@5.59.6": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz#37c51d2ae36127d8b81f32a0a4d2efae19277c48" - integrity sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ== +"@typescript-eslint/type-utils@5.59.7": + version "5.59.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.7.tgz#89c97291371b59eb18a68039857c829776f1426d" + integrity sha512-ozuz/GILuYG7osdY5O5yg0QxXUAEoI4Go3Do5xeu+ERH9PorHBPSdvD3Tjp2NN2bNLh1NJQSsQu2TPu/Ly+HaQ== dependencies: - "@typescript-eslint/typescript-estree" "5.59.6" - "@typescript-eslint/utils" "5.59.6" + "@typescript-eslint/typescript-estree" "5.59.7" + "@typescript-eslint/utils" "5.59.7" debug "^4.3.4" tsutils "^3.21.0" @@ -2506,11 +2498,6 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.0.tgz#3fcdac7dbf923ec5251545acdd9f1d42d7c4fe32" integrity sha512-yR2h1NotF23xFFYKHZs17QJnB51J/s+ud4PYU4MqdZbzeNxpgUr05+dNeCN/bb6raslHvGdd6BFCkVhpPk/ZeA== -"@typescript-eslint/types@5.59.6": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.6.tgz#5a6557a772af044afe890d77c6a07e8c23c2460b" - integrity sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA== - "@typescript-eslint/types@5.59.7": version "5.59.7" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.7.tgz#6f4857203fceee91d0034ccc30512d2939000742" @@ -2529,19 +2516,6 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@5.59.6": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz#2fb80522687bd3825504925ea7e1b8de7bb6251b" - integrity sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA== - dependencies: - "@typescript-eslint/types" "5.59.6" - "@typescript-eslint/visitor-keys" "5.59.6" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - "@typescript-eslint/typescript-estree@5.59.7": version "5.59.7" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.7.tgz#b887acbd4b58e654829c94860dbff4ac55c5cff8" @@ -2555,17 +2529,17 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.59.6": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.6.tgz#82960fe23788113fc3b1f9d4663d6773b7907839" - integrity sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg== +"@typescript-eslint/utils@5.59.7": + version "5.59.7" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.7.tgz#7adf068b136deae54abd9a66ba5a8780d2d0f898" + integrity sha512-yCX9WpdQKaLufz5luG4aJbOpdXf/fjwGMcLFXZVPUz3QqLirG5QcwwnIHNf8cjLjxK4qtzTO8udUtMQSAToQnQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.59.6" - "@typescript-eslint/types" "5.59.6" - "@typescript-eslint/typescript-estree" "5.59.6" + "@typescript-eslint/scope-manager" "5.59.7" + "@typescript-eslint/types" "5.59.7" + "@typescript-eslint/typescript-estree" "5.59.7" eslint-scope "^5.1.1" semver "^7.3.7" @@ -2577,14 +2551,6 @@ "@typescript-eslint/types" "5.59.0" eslint-visitor-keys "^3.3.0" -"@typescript-eslint/visitor-keys@5.59.6": - version "5.59.6" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz#673fccabf28943847d0c8e9e8d008e3ada7be6bb" - integrity sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q== - dependencies: - "@typescript-eslint/types" "5.59.6" - eslint-visitor-keys "^3.3.0" - "@typescript-eslint/visitor-keys@5.59.7": version "5.59.7" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz#09c36eaf268086b4fbb5eb9dc5199391b6485fc5" From 7a0630ddcf441e64cb4ed5f827e2be6ad377063d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 09:25:32 +0200 Subject: [PATCH 70/98] Bump rubocop-performance from 1.17.1 to 1.18.0 (#25089) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a7e624da7c..0cc08e536b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -611,11 +611,11 @@ GEM rubocop-ast (>= 1.28.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.28.0) + rubocop-ast (1.28.1) parser (>= 3.2.1.0) rubocop-capybara (2.18.0) rubocop (~> 1.41) - rubocop-performance (1.17.1) + rubocop-performance (1.18.0) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) rubocop-rails (2.19.1) From 063bdf36f54f9508102e14fe050f075c8320fd37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 09:25:43 +0200 Subject: [PATCH 71/98] Bump rqrcode from 2.1.2 to 2.2.0 (#25086) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index e97b3e52ee..363fc6bd3b 100644 --- a/Gemfile +++ b/Gemfile @@ -75,7 +75,7 @@ gem 'rails-settings-cached', '~> 0.6', git: 'https://github.com/mastodon/rails-s gem 'redcarpet', '~> 3.6' gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis'] gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' -gem 'rqrcode', '~> 2.1' +gem 'rqrcode', '~> 2.2' gem 'ruby-progressbar', '~> 1.13' gem 'sanitize', '~> 6.0' gem 'scenic', '~> 1.7' diff --git a/Gemfile.lock b/Gemfile.lock index 0cc08e536b..7c2d88053f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -576,7 +576,7 @@ GEM rexml (3.2.5) rotp (6.2.2) rpam2 (4.0.2) - rqrcode (2.1.2) + rqrcode (2.2.0) chunky_png (~> 1.0) rqrcode_core (~> 1.0) rqrcode_core (1.2.0) @@ -860,7 +860,7 @@ DEPENDENCIES redcarpet (~> 3.6) redis (~> 4.5) redis-namespace (~> 1.10) - rqrcode (~> 2.1) + rqrcode (~> 2.2) rspec-rails (~> 6.0) rspec-sidekiq (~> 3.1) rspec_chunked (~> 0.6) From 167b319cefc874397ccc5adbe5491cbc53436ab6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 09:27:55 +0200 Subject: [PATCH 72/98] Bump webpack-merge from 5.8.0 to 5.9.0 (#25084) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 71ae589953..2b3006cf91 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "webpack-assets-manifest": "^4.0.6", "webpack-bundle-analyzer": "^4.8.0", "webpack-cli": "^3.3.12", - "webpack-merge": "^5.8.0", + "webpack-merge": "^5.9.0", "wicg-inert": "^3.1.2", "workbox-expiration": "^6.5.4", "workbox-precaching": "^6.5.4", diff --git a/yarn.lock b/yarn.lock index 66461c3081..3a1f31a87c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11953,10 +11953,10 @@ webpack-log@^2.0.0: ansi-colors "^3.0.0" uuid "^3.3.2" -webpack-merge@^5.8.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" - integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== +webpack-merge@^5.9.0: + version "5.9.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.9.0.tgz#dc160a1c4cf512ceca515cc231669e9ddb133826" + integrity sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg== dependencies: clone-deep "^4.0.1" wildcard "^2.0.0" From 580f35827f099fb3713a8c69dcd1b8daa2178463 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 09:34:31 +0200 Subject: [PATCH 73/98] Bump glob from 10.2.2 to 10.2.6 (#25083) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 25 +++++++++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 2b3006cf91..ae7685e896 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "file-loader": "^6.2.0", "font-awesome": "^4.7.0", "fuzzysort": "^2.0.4", - "glob": "^10.2.2", + "glob": "^10.2.6", "history": "^4.10.1", "http-link-header": "^1.1.1", "immutable": "^4.3.0", diff --git a/yarn.lock b/yarn.lock index 3a1f31a87c..58f2e1464e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5876,15 +5876,15 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^10.0.0, glob@^10.2.2: - version "10.2.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.2.tgz#ce2468727de7e035e8ecf684669dc74d0526ab75" - integrity sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ== +glob@^10.0.0, glob@^10.2.6: + version "10.2.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.6.tgz#1e27edbb3bbac055cb97113e27a066c100a4e5e1" + integrity sha512-U/rnDpXJGF414QQQZv5uVsabTVxMSwzS5CH0p3DRCIV6ownl4f7PzGnkGmvlum2wB+9RlJWJZ6ACU1INnBqiPA== dependencies: foreground-child "^3.1.0" jackspeak "^2.0.3" - minimatch "^9.0.0" - minipass "^5.0.0" + minimatch "^9.0.1" + minipass "^5.0.0 || ^6.0.2" path-scurry "^1.7.0" glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: @@ -8120,10 +8120,10 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.0.tgz#bfc8e88a1c40ffd40c172ddac3decb8451503b56" - integrity sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w== +minimatch@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.1.tgz#8a555f541cf976c622daf078bb28f29fb927c253" + integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w== dependencies: brace-expansion "^2.0.1" @@ -8174,6 +8174,11 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== +"minipass@^5.0.0 || ^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-6.0.2.tgz#542844b6c4ce95b202c0995b0a471f1229de4c81" + integrity sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w== + minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" From 361e7ccf88abd38d0b307c0f490026e2772b02d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 09:44:14 +0200 Subject: [PATCH 74/98] Bump rimraf from 5.0.0 to 5.0.1 (#25082) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index ae7685e896..ba520a0340 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "regenerator-runtime": "^0.13.11", "requestidlecallback": "^0.3.0", "reselect": "^4.1.8", - "rimraf": "^5.0.0", + "rimraf": "^5.0.1", "sass": "^1.62.1", "sass-loader": "^10.2.0", "stacktrace-js": "^2.0.2", diff --git a/yarn.lock b/yarn.lock index 58f2e1464e..f6cfa99287 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5876,7 +5876,7 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^10.0.0, glob@^10.2.6: +glob@^10.2.5, glob@^10.2.6: version "10.2.6" resolved "https://registry.yarnpkg.com/glob/-/glob-10.2.6.tgz#1e27edbb3bbac055cb97113e27a066c100a4e5e1" integrity sha512-U/rnDpXJGF414QQQZv5uVsabTVxMSwzS5CH0p3DRCIV6ownl4f7PzGnkGmvlum2wB+9RlJWJZ6ACU1INnBqiPA== @@ -10152,12 +10152,12 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -rimraf@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.0.tgz#5bda14e410d7e4dd522154891395802ce032c2cb" - integrity sha512-Jf9llaP+RvaEVS5nPShYFhtXIrb3LRKP281ib3So0KkeZKo2wIKyq0Re7TOSwanasA423PSr6CCIL4bP6T040g== +rimraf@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.1.tgz#0881323ab94ad45fec7c0221f27ea1a142f3f0d0" + integrity sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg== dependencies: - glob "^10.0.0" + glob "^10.2.5" ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" From 97ddf1b401f4d3d56fc0d0b55a6b0cddd0edd438 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 09:44:47 +0200 Subject: [PATCH 75/98] Bump aws-sdk-s3 from 1.121.0 to 1.122.0 (#24923) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile | 2 +- Gemfile.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index 363fc6bd3b..6a444acd25 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ gem 'makara', '~> 0.5' gem 'pghero' gem 'dotenv-rails', '~> 2.8' -gem 'aws-sdk-s3', '~> 1.120', require: false +gem 'aws-sdk-s3', '~> 1.122', require: false gem 'fog-core', '<= 2.4.0' gem 'fog-openstack', '~> 0.3', require: false gem 'kt-paperclip', '~> 7.1', github: 'kreeti/kt-paperclip', ref: '11abf222dc31bff71160a1d138b445214f434b2b' diff --git a/Gemfile.lock b/Gemfile.lock index 7c2d88053f..d0547ea509 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -109,16 +109,16 @@ GEM attr_required (1.0.1) awrence (1.2.1) aws-eventstream (1.2.0) - aws-partitions (1.752.0) - aws-sdk-core (3.171.0) + aws-partitions (1.761.0) + aws-sdk-core (3.172.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.63.0) + aws-sdk-kms (1.64.0) aws-sdk-core (~> 3, >= 3.165.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.121.0) + aws-sdk-s3 (1.122.0) aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) @@ -770,7 +770,7 @@ DEPENDENCIES active_model_serializers (~> 0.10) addressable (~> 2.8) annotate (~> 3.2) - aws-sdk-s3 (~> 1.120) + aws-sdk-s3 (~> 1.122) better_errors (~> 2.9) binding_of_caller (~> 1.0) blurhash (~> 0.1) From 530151f6c842919fe747c7056b201052cc29a3df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 09:47:59 +0200 Subject: [PATCH 76/98] Bump stylelint from 15.6.1 to 15.6.2 (#25078) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ba520a0340..38eaa7bf87 100644 --- a/package.json +++ b/package.json @@ -196,7 +196,7 @@ "prettier": "^2.8.8", "react-intl-translations-manager": "^5.0.3", "react-test-renderer": "^18.2.0", - "stylelint": "^15.6.1", + "stylelint": "^15.6.2", "stylelint-config-standard-scss": "^9.0.0", "typescript": "^5.0.4", "webpack-dev-server": "^3.11.3", diff --git a/yarn.lock b/yarn.lock index f6cfa99287..88603113bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11035,10 +11035,10 @@ stylelint-scss@^4.6.0: postcss-selector-parser "^6.0.11" postcss-value-parser "^4.2.0" -stylelint@^15.6.1: - version "15.6.1" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.6.1.tgz#e4cd33a3af88587b99a5d1328aedd8c298b6dc81" - integrity sha512-d8icFBlVl93Elf3Z5ABQNOCe4nx69is3D/NZhDLAie1eyYnpxfeKe7pCfqzT5W4F8vxHCLSDfV8nKNJzogvV2Q== +stylelint@^15.6.2: + version "15.6.2" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.6.2.tgz#06d9005b62a83b72887eed623520e9b472af8c15" + integrity sha512-fjQWwcdUye4DU+0oIxNGwawIPC5DvG5kdObY5Sg4rc87untze3gC/5g/ikePqVjrAsBUZjwMN+pZsAYbDO6ArQ== dependencies: "@csstools/css-parser-algorithms" "^2.1.1" "@csstools/css-tokenizer" "^2.1.1" From f171930b1fd6cb753a015e06a1f08986c57fdde1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 09:52:54 +0200 Subject: [PATCH 77/98] Bump rubocop from 1.50.2 to 1.51.0 (#24995) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index d0547ea509..21647e1b69 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -601,7 +601,7 @@ GEM sidekiq (>= 2.4.0) rspec-support (3.12.0) rspec_chunked (0.6) - rubocop (1.50.2) + rubocop (1.51.0) json (~> 2.3) parallel (~> 1.10) parser (>= 3.2.0.0) From 0087959e0b26e728bfac14ff840eb6450da7d033 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 23 May 2023 04:03:51 -0400 Subject: [PATCH 78/98] Add specs for admin/announcements CRUD actions (#25077) --- .../admin/announcements_controller_spec.rb | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/spec/controllers/admin/announcements_controller_spec.rb b/spec/controllers/admin/announcements_controller_spec.rb index 288ac1d713..a8905160f5 100644 --- a/spec/controllers/admin/announcements_controller_spec.rb +++ b/spec/controllers/admin/announcements_controller_spec.rb @@ -18,4 +18,59 @@ describe Admin::AnnouncementsController do expect(response).to have_http_status(:success) end end + + describe 'GET #new' do + it 'returns http success and renders new' do + get :new + + expect(response).to have_http_status(:success) + expect(response).to render_template(:new) + end + end + + describe 'GET #edit' do + let(:announcement) { Fabricate(:announcement) } + + it 'returns http success and renders edit' do + get :edit, params: { id: announcement.id } + + expect(response).to have_http_status(:success) + expect(response).to render_template(:edit) + end + end + + describe 'POST #create' do + it 'creates a new announcement and redirects' do + expect do + post :create, params: { announcement: { text: 'The announcement message.' } } + end.to change(Announcement, :count).by(1) + + expect(response).to redirect_to(admin_announcements_path) + expect(flash.notice).to match(I18n.t('admin.announcements.published_msg')) + end + end + + describe 'PUT #update' do + let(:announcement) { Fabricate(:announcement, text: 'Original text') } + + it 'updates an announcement and redirects' do + put :update, params: { id: announcement.id, announcement: { text: 'Updated text.' } } + + expect(response).to redirect_to(admin_announcements_path) + expect(flash.notice).to match(I18n.t('admin.announcements.updated_msg')) + end + end + + describe 'DELETE #destroy' do + let!(:announcement) { Fabricate(:announcement, text: 'Original text') } + + it 'destroys an announcement and redirects' do + expect do + delete :destroy, params: { id: announcement.id } + end.to change(Announcement, :count).by(-1) + + expect(response).to redirect_to(admin_announcements_path) + expect(flash.notice).to match(I18n.t('admin.announcements.destroyed_msg')) + end + end end From 9cfab995900d9454171720b65e7174b8cfdf4b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=84=E3=81=A1=20=E3=81=B2?= Date: Tue, 23 May 2023 17:04:10 +0900 Subject: [PATCH 79/98] Rewrite `` as FC and TS (#25091) --- .../mastodon/components/timeline_hint.jsx | 18 ------------- .../mastodon/components/timeline_hint.tsx | 27 +++++++++++++++++++ .../features/account_timeline/index.jsx | 2 +- .../mastodon/features/followers/index.jsx | 2 +- .../mastodon/features/following/index.jsx | 2 +- 5 files changed, 30 insertions(+), 21 deletions(-) delete mode 100644 app/javascript/mastodon/components/timeline_hint.jsx create mode 100644 app/javascript/mastodon/components/timeline_hint.tsx diff --git a/app/javascript/mastodon/components/timeline_hint.jsx b/app/javascript/mastodon/components/timeline_hint.jsx deleted file mode 100644 index ac9a79dcc0..0000000000 --- a/app/javascript/mastodon/components/timeline_hint.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; - -const TimelineHint = ({ resource, url }) => ( -
- -
- -
-); - -TimelineHint.propTypes = { - resource: PropTypes.node.isRequired, - url: PropTypes.string.isRequired, -}; - -export default TimelineHint; diff --git a/app/javascript/mastodon/components/timeline_hint.tsx b/app/javascript/mastodon/components/timeline_hint.tsx new file mode 100644 index 0000000000..712b4c293b --- /dev/null +++ b/app/javascript/mastodon/components/timeline_hint.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import { FormattedMessage } from 'react-intl'; + +interface Props { + resource: JSX.Element; + url: string; +} + +export const TimelineHint: React.FC = ({ resource, url }) => ( +
+ + + +
+ + + +
+); diff --git a/app/javascript/mastodon/features/account_timeline/index.jsx b/app/javascript/mastodon/features/account_timeline/index.jsx index 2a05305268..ce9485216e 100644 --- a/app/javascript/mastodon/features/account_timeline/index.jsx +++ b/app/javascript/mastodon/features/account_timeline/index.jsx @@ -12,7 +12,7 @@ import ColumnBackButton from '../../components/column_back_button'; import { List as ImmutableList } from 'immutable'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { FormattedMessage } from 'react-intl'; -import TimelineHint from 'mastodon/components/timeline_hint'; +import { TimelineHint } from 'mastodon/components/timeline_hint'; import { me } from 'mastodon/initial_state'; import LimitedAccountHint from './components/limited_account_hint'; import { getAccountHidden } from 'mastodon/selectors'; diff --git a/app/javascript/mastodon/features/followers/index.jsx b/app/javascript/mastodon/features/followers/index.jsx index cdd65c9ef0..1a1fdf578b 100644 --- a/app/javascript/mastodon/features/followers/index.jsx +++ b/app/javascript/mastodon/features/followers/index.jsx @@ -17,7 +17,7 @@ import Column from '../ui/components/column'; import HeaderContainer from '../account_timeline/containers/header_container'; import ColumnBackButton from '../../components/column_back_button'; import ScrollableList from '../../components/scrollable_list'; -import TimelineHint from 'mastodon/components/timeline_hint'; +import { TimelineHint } from 'mastodon/components/timeline_hint'; import LimitedAccountHint from '../account_timeline/components/limited_account_hint'; import { getAccountHidden } from 'mastodon/selectors'; import { normalizeForLookup } from 'mastodon/reducers/accounts_map'; diff --git a/app/javascript/mastodon/features/following/index.jsx b/app/javascript/mastodon/features/following/index.jsx index 26dee213d8..c024ff063b 100644 --- a/app/javascript/mastodon/features/following/index.jsx +++ b/app/javascript/mastodon/features/following/index.jsx @@ -17,7 +17,7 @@ import Column from '../ui/components/column'; import HeaderContainer from '../account_timeline/containers/header_container'; import ColumnBackButton from '../../components/column_back_button'; import ScrollableList from '../../components/scrollable_list'; -import TimelineHint from 'mastodon/components/timeline_hint'; +import { TimelineHint } from 'mastodon/components/timeline_hint'; import LimitedAccountHint from '../account_timeline/components/limited_account_hint'; import { getAccountHidden } from 'mastodon/selectors'; import { normalizeForLookup } from 'mastodon/reducers/accounts_map'; From d64a2c7aa80db1193d602149108d4a15b4f22f5f Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 23 May 2023 04:16:50 -0400 Subject: [PATCH 80/98] Fix Performance/StartWith cop (#24818) --- .rubocop_todo.yml | 6 ------ app/lib/extractor.rb | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index bd1e5bc146..e2441c9344 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -255,12 +255,6 @@ Performance/MapCompact: - 'db/migrate/20200407202420_migrate_unavailable_inboxes.rb' - 'spec/presenters/status_relationships_presenter_spec.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: SafeMultiline. -Performance/StartWith: - Exclude: - - 'app/lib/extractor.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Performance/UnfreezeString: Exclude: diff --git a/app/lib/extractor.rb b/app/lib/extractor.rb index 540bbe1a92..9090773ae9 100644 --- a/app/lib/extractor.rb +++ b/app/lib/extractor.rb @@ -64,7 +64,7 @@ module Extractor end_position = match_data.char_end(1) after = ::Regexp.last_match.post_match - if %r{\A://}.match?(after) + if after.start_with?('://') hash_text.match(/(.+)(https?\Z)/) do |matched| hash_text = matched[1] end_position -= matched[2].codepoint_length From 9e8323fdb94f1469003cb2a911fd9d1bc4db9b01 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 23 May 2023 04:49:12 -0400 Subject: [PATCH 81/98] Fix Performance/MapCompact cop (#24797) Co-authored-by: Claire --- .rubocop_todo.yml | 19 ------------------- app/lib/admin/metrics/dimension.rb | 4 ++-- app/lib/admin/metrics/measure.rb | 4 ++-- app/lib/feed_manager.rb | 8 ++++---- app/models/account.rb | 4 ++-- app/models/account_statuses_cleanup_policy.rb | 4 ++-- .../account_suggestions/setting_source.rb | 4 ++-- app/models/account_suggestions/source.rb | 2 +- app/models/follow_recommendation_filter.rb | 2 +- app/models/notification.rb | 2 +- app/models/user_role.rb | 2 +- app/models/webhook.rb | 2 +- app/services/process_mentions_service.rb | 2 +- app/validators/existing_username_validator.rb | 4 ++-- ...00407202420_migrate_unavailable_inboxes.rb | 4 ++-- .../status_relationships_presenter_spec.rb | 2 +- 16 files changed, 25 insertions(+), 44 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e2441c9344..6fb910005a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -236,25 +236,6 @@ Naming/VariableNumber: - 'spec/models/user_spec.rb' - 'spec/services/activitypub/fetch_featured_collection_service_spec.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -Performance/MapCompact: - Exclude: - - 'app/lib/admin/metrics/dimension.rb' - - 'app/lib/admin/metrics/measure.rb' - - 'app/lib/feed_manager.rb' - - 'app/models/account.rb' - - 'app/models/account_statuses_cleanup_policy.rb' - - 'app/models/account_suggestions/setting_source.rb' - - 'app/models/account_suggestions/source.rb' - - 'app/models/follow_recommendation_filter.rb' - - 'app/models/notification.rb' - - 'app/models/user_role.rb' - - 'app/models/webhook.rb' - - 'app/services/process_mentions_service.rb' - - 'app/validators/existing_username_validator.rb' - - 'db/migrate/20200407202420_migrate_unavailable_inboxes.rb' - - 'spec/presenters/status_relationships_presenter_spec.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Performance/UnfreezeString: Exclude: diff --git a/app/lib/admin/metrics/dimension.rb b/app/lib/admin/metrics/dimension.rb index 81b89d9b32..e0122a65b5 100644 --- a/app/lib/admin/metrics/dimension.rb +++ b/app/lib/admin/metrics/dimension.rb @@ -14,9 +14,9 @@ class Admin::Metrics::Dimension }.freeze def self.retrieve(dimension_keys, start_at, end_at, limit, params) - Array(dimension_keys).map do |key| + Array(dimension_keys).filter_map do |key| klass = DIMENSIONS[key.to_sym] klass&.new(start_at, end_at, limit, klass.with_params? ? params.require(key.to_sym) : nil) - end.compact + end end end diff --git a/app/lib/admin/metrics/measure.rb b/app/lib/admin/metrics/measure.rb index 0b510eb256..fe7e049290 100644 --- a/app/lib/admin/metrics/measure.rb +++ b/app/lib/admin/metrics/measure.rb @@ -19,9 +19,9 @@ class Admin::Metrics::Measure }.freeze def self.retrieve(measure_keys, start_at, end_at, params) - Array(measure_keys).map do |key| + Array(measure_keys).filter_map do |key| klass = MEASURES[key.to_sym] klass&.new(start_at, end_at, klass.with_params? ? params.require(key.to_sym) : nil) - end.compact + end end end diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 8e619f9cc9..643e6828d2 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -188,7 +188,7 @@ class FeedManager timeline_key = key(:home, account.id) timeline_status_ids = redis.zrange(timeline_key, 0, -1) statuses = Status.where(id: timeline_status_ids).select(:id, :reblog_of_id, :account_id).to_a - reblogged_ids = Status.where(id: statuses.map(&:reblog_of_id).compact, account: target_account).pluck(:id) + reblogged_ids = Status.where(id: statuses.filter_map(&:reblog_of_id), account: target_account).pluck(:id) with_mentions_ids = Mention.active.where(status_id: statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact, account: target_account).pluck(:status_id) target_statuses = statuses.select do |status| @@ -208,7 +208,7 @@ class FeedManager timeline_key = key(:list, list.id) timeline_status_ids = redis.zrange(timeline_key, 0, -1) statuses = Status.where(id: timeline_status_ids).select(:id, :reblog_of_id, :account_id).to_a - reblogged_ids = Status.where(id: statuses.map(&:reblog_of_id).compact, account: target_account).pluck(:id) + reblogged_ids = Status.where(id: statuses.filter_map(&:reblog_of_id), account: target_account).pluck(:id) with_mentions_ids = Mention.active.where(status_id: statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact, account: target_account).pluck(:status_id) target_statuses = statuses.select do |status| @@ -543,9 +543,9 @@ class FeedManager arr end - crutches[:following] = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:in_reply_to_account_id).compact).pluck(:target_account_id).index_with(true) + crutches[:following] = Follow.where(account_id: receiver_id, target_account_id: statuses.filter_map(&:in_reply_to_account_id)).pluck(:target_account_id).index_with(true) crutches[:languages] = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:account_id)).pluck(:target_account_id, :languages).to_h - crutches[:hiding_reblogs] = Follow.where(account_id: receiver_id, target_account_id: statuses.map { |s| s.account_id if s.reblog? }.compact, show_reblogs: false).pluck(:target_account_id).index_with(true) + crutches[:hiding_reblogs] = Follow.where(account_id: receiver_id, target_account_id: statuses.filter_map { |s| s.account_id if s.reblog? }, show_reblogs: false).pluck(:target_account_id).index_with(true) crutches[:blocking] = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).index_with(true) crutches[:muting] = Mute.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).index_with(true) crutches[:domain_blocking] = AccountDomainBlock.where(account_id: receiver_id, domain: statuses.flat_map { |s| [s.account.domain, s.reblog&.account&.domain] }.compact).pluck(:domain).index_with(true) diff --git a/app/models/account.rb b/app/models/account.rb index 32e9898160..f17d06be58 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -299,11 +299,11 @@ class Account < ApplicationRecord end def fields - (self[:fields] || []).map do |f| + (self[:fields] || []).filter_map do |f| Account::Field.new(self, f) rescue nil - end.compact + end end def fields_attributes=(attributes) diff --git a/app/models/account_statuses_cleanup_policy.rb b/app/models/account_statuses_cleanup_policy.rb index 63582b9f9e..a102795446 100644 --- a/app/models/account_statuses_cleanup_policy.rb +++ b/app/models/account_statuses_cleanup_policy.rb @@ -117,12 +117,12 @@ class AccountStatusesCleanupPolicy < ApplicationRecord private def update_last_inspected - if EXCEPTION_BOOLS.map { |name| attribute_change_to_be_saved(name) }.compact.include?([true, false]) + if EXCEPTION_BOOLS.filter_map { |name| attribute_change_to_be_saved(name) }.include?([true, false]) # Policy has been widened in such a way that any previously-inspected status # may need to be deleted, so we'll have to start again. redis.del("account_cleanup:#{account_id}") end - redis.del("account_cleanup:#{account_id}") if EXCEPTION_THRESHOLDS.map { |name| attribute_change_to_be_saved(name) }.compact.any? { |old, new| old.present? && (new.nil? || new > old) } + redis.del("account_cleanup:#{account_id}") if EXCEPTION_THRESHOLDS.filter_map { |name| attribute_change_to_be_saved(name) }.any? { |old, new| old.present? && (new.nil? || new > old) } end def validate_local_account diff --git a/app/models/account_suggestions/setting_source.rb b/app/models/account_suggestions/setting_source.rb index 7b8873e0c5..6185732b4b 100644 --- a/app/models/account_suggestions/setting_source.rb +++ b/app/models/account_suggestions/setting_source.rb @@ -48,14 +48,14 @@ class AccountSuggestions::SettingSource < AccountSuggestions::Source end def setting_to_usernames_and_domains - setting.split(',').map do |str| + setting.split(',').filter_map do |str| username, domain = str.strip.gsub(/\A@/, '').split('@', 2) domain = nil if TagManager.instance.local_domain?(domain) next if username.blank? [username.downcase, domain&.downcase] - end.compact + end end def setting diff --git a/app/models/account_suggestions/source.rb b/app/models/account_suggestions/source.rb index be462cd0f5..504d26a8bd 100644 --- a/app/models/account_suggestions/source.rb +++ b/app/models/account_suggestions/source.rb @@ -20,7 +20,7 @@ class AccountSuggestions::Source map = scope.index_by { |account| to_ordered_list_key(account) } - ordered_list.map { |ordered_list_key| map[ordered_list_key] }.compact.map do |account| + ordered_list.filter_map { |ordered_list_key| map[ordered_list_key] }.map do |account| AccountSuggestions::Suggestion.new( account: account, source: key diff --git a/app/models/follow_recommendation_filter.rb b/app/models/follow_recommendation_filter.rb index 5313326143..2fab975698 100644 --- a/app/models/follow_recommendation_filter.rb +++ b/app/models/follow_recommendation_filter.rb @@ -22,7 +22,7 @@ class FollowRecommendationFilter account_ids = redis.zrevrange("follow_recommendations:#{@language}", 0, -1).map(&:to_i) accounts = Account.where(id: account_ids).index_by(&:id) - account_ids.map { |id| accounts[id] }.compact + account_ids.filter_map { |id| accounts[id] } end end end diff --git a/app/models/notification.rb b/app/models/notification.rb index 8ba506fa1b..5527953afc 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -114,7 +114,7 @@ class Notification < ApplicationRecord ActiveRecord::Associations::Preloader.new.preload(grouped_notifications, associations) end - unique_target_statuses = notifications.map(&:target_status).compact.uniq + unique_target_statuses = notifications.filter_map(&:target_status).uniq # Call cache_collection in block cached_statuses_by_id = yield(unique_target_statuses).index_by(&:id) diff --git a/app/models/user_role.rb b/app/models/user_role.rb index a1b91dc0f5..5472646c60 100644 --- a/app/models/user_role.rb +++ b/app/models/user_role.rb @@ -125,7 +125,7 @@ class UserRole < ApplicationRecord end def permissions_as_keys=(value) - self.permissions = value.map(&:presence).compact.reduce(Flags::NONE) { |bitmask, privilege| FLAGS.key?(privilege.to_sym) ? (bitmask | FLAGS[privilege.to_sym]) : bitmask } + self.permissions = value.filter_map(&:presence).reduce(Flags::NONE) { |bitmask, privilege| FLAGS.key?(privilege.to_sym) ? (bitmask | FLAGS[privilege.to_sym]) : bitmask } end def can?(*any_of_privileges) diff --git a/app/models/webhook.rb b/app/models/webhook.rb index 9a056a3862..c556bcc2bb 100644 --- a/app/models/webhook.rb +++ b/app/models/webhook.rb @@ -53,7 +53,7 @@ class Webhook < ApplicationRecord end def strip_events - self.events = events.map { |str| str.strip.presence }.compact if events.present? + self.events = events.filter_map { |str| str.strip.presence } if events.present? end def generate_secret diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index b3b279147d..f3fbb80210 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -68,7 +68,7 @@ class ProcessMentionsService < BaseService def assign_mentions! # Make sure we never mention blocked accounts unless @current_mentions.empty? - mentioned_domains = @current_mentions.map { |m| m.account.domain }.compact.uniq + mentioned_domains = @current_mentions.filter_map { |m| m.account.domain }.uniq blocked_domains = Set.new(mentioned_domains.empty? ? [] : AccountDomainBlock.where(account_id: @status.account_id, domain: mentioned_domains)) mentioned_account_ids = @current_mentions.map(&:account_id) blocked_account_ids = Set.new(@status.account.block_relationships.where(target_account_id: mentioned_account_ids).pluck(:target_account_id)) diff --git a/app/validators/existing_username_validator.rb b/app/validators/existing_username_validator.rb index 45de4f4a44..037d92f39b 100644 --- a/app/validators/existing_username_validator.rb +++ b/app/validators/existing_username_validator.rb @@ -4,14 +4,14 @@ class ExistingUsernameValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) return if value.blank? - usernames_and_domains = value.split(',').map do |str| + usernames_and_domains = value.split(',').filter_map do |str| username, domain = str.strip.gsub(/\A@/, '').split('@', 2) domain = nil if TagManager.instance.local_domain?(domain) next if username.blank? [str, username, domain] - end.compact + end usernames_with_no_accounts = usernames_and_domains.filter_map do |(str, username, domain)| str unless Account.find_remote(username, domain) diff --git a/db/migrate/20200407202420_migrate_unavailable_inboxes.rb b/db/migrate/20200407202420_migrate_unavailable_inboxes.rb index 8f9c687942..05a01be284 100644 --- a/db/migrate/20200407202420_migrate_unavailable_inboxes.rb +++ b/db/migrate/20200407202420_migrate_unavailable_inboxes.rb @@ -5,9 +5,9 @@ class MigrateUnavailableInboxes < ActiveRecord::Migration[5.2] redis = RedisConfiguration.pool.checkout urls = redis.smembers('unavailable_inboxes') - hosts = urls.map do |url| + hosts = urls.filter_map do |url| Addressable::URI.parse(url).normalized_host - end.compact.uniq + end.uniq UnavailableDomain.delete_all diff --git a/spec/presenters/status_relationships_presenter_spec.rb b/spec/presenters/status_relationships_presenter_spec.rb index 11116cabd2..a62fa004a1 100644 --- a/spec/presenters/status_relationships_presenter_spec.rb +++ b/spec/presenters/status_relationships_presenter_spec.rb @@ -15,7 +15,7 @@ RSpec.describe StatusRelationshipsPresenter do let(:presenter) { StatusRelationshipsPresenter.new(statuses, current_account_id, **options) } let(:current_account_id) { Fabricate(:account).id } let(:statuses) { [Fabricate(:status)] } - let(:status_ids) { statuses.map(&:id) + statuses.map(&:reblog_of_id).compact } + let(:status_ids) { statuses.map(&:id) + statuses.filter_map(&:reblog_of_id) } let(:default_map) { { 1 => true } } context 'when options are not set' do From adaa7a61e9c87859251f4ac9be387d3d24c0a962 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 23 May 2023 04:49:23 -0400 Subject: [PATCH 82/98] Fix RSpec/RepeatedExample cop (#24849) --- .rubocop_todo.yml | 4 - spec/policies/status_policy_spec.rb | 174 +++++++++++++++------------- 2 files changed, 93 insertions(+), 85 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6fb910005a..de6857e071 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -571,10 +571,6 @@ RSpec/PredicateMatcher: - 'spec/models/user_spec.rb' - 'spec/services/post_status_service_spec.rb' -RSpec/RepeatedExample: - Exclude: - - 'spec/policies/status_policy_spec.rb' - RSpec/StubbedMock: Exclude: - 'spec/controllers/api/base_controller_spec.rb' diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb index 9ae54780eb..36ac8d8027 100644 --- a/spec/policies/status_policy_spec.rb +++ b/spec/policies/status_policy_spec.rb @@ -11,127 +11,139 @@ RSpec.describe StatusPolicy, type: :model do let(:bob) { Fabricate(:account, username: 'bob') } let(:status) { Fabricate(:status, account: alice) } - permissions :show?, :reblog? do - it 'grants access when no viewer' do - expect(subject).to permit(nil, status) - end + context 'with the permissions of show? and reblog?' do + permissions :show?, :reblog? do + it 'grants access when no viewer' do + expect(subject).to permit(nil, status) + end - it 'denies access when viewer is blocked' do - block = Fabricate(:block) - status.visibility = :private - status.account = block.target_account + it 'denies access when viewer is blocked' do + block = Fabricate(:block) + status.visibility = :private + status.account = block.target_account - expect(subject).to_not permit(block.account, status) + expect(subject).to_not permit(block.account, status) + end end end - permissions :show? do - it 'grants access when direct and account is viewer' do - status.visibility = :direct + context 'with the permission of show?' do + permissions :show? do + it 'grants access when direct and account is viewer' do + status.visibility = :direct - expect(subject).to permit(status.account, status) - end + expect(subject).to permit(status.account, status) + end - it 'grants access when direct and viewer is mentioned' do - status.visibility = :direct - status.mentions = [Fabricate(:mention, account: alice)] + it 'grants access when direct and viewer is mentioned' do + status.visibility = :direct + status.mentions = [Fabricate(:mention, account: alice)] - expect(subject).to permit(alice, status) - end + expect(subject).to permit(alice, status) + end - it 'grants access when direct and non-owner viewer is mentioned and mentions are loaded' do - status.visibility = :direct - status.mentions = [Fabricate(:mention, account: bob)] - status.mentions.load + it 'grants access when direct and non-owner viewer is mentioned and mentions are loaded' do + status.visibility = :direct + status.mentions = [Fabricate(:mention, account: bob)] + status.mentions.load - expect(subject).to permit(bob, status) - end + expect(subject).to permit(bob, status) + end - it 'denies access when direct and viewer is not mentioned' do - viewer = Fabricate(:account) - status.visibility = :direct + it 'denies access when direct and viewer is not mentioned' do + viewer = Fabricate(:account) + status.visibility = :direct - expect(subject).to_not permit(viewer, status) - end + expect(subject).to_not permit(viewer, status) + end - it 'grants access when private and account is viewer' do - status.visibility = :private + it 'grants access when private and account is viewer' do + status.visibility = :private - expect(subject).to permit(status.account, status) - end + expect(subject).to permit(status.account, status) + end - it 'grants access when private and account is following viewer' do - follow = Fabricate(:follow) - status.visibility = :private - status.account = follow.target_account + it 'grants access when private and account is following viewer' do + follow = Fabricate(:follow) + status.visibility = :private + status.account = follow.target_account - expect(subject).to permit(follow.account, status) - end + expect(subject).to permit(follow.account, status) + end - it 'grants access when private and viewer is mentioned' do - status.visibility = :private - status.mentions = [Fabricate(:mention, account: alice)] + it 'grants access when private and viewer is mentioned' do + status.visibility = :private + status.mentions = [Fabricate(:mention, account: alice)] - expect(subject).to permit(alice, status) - end + expect(subject).to permit(alice, status) + end - it 'denies access when private and viewer is not mentioned or followed' do - viewer = Fabricate(:account) - status.visibility = :private + it 'denies access when private and viewer is not mentioned or followed' do + viewer = Fabricate(:account) + status.visibility = :private - expect(subject).to_not permit(viewer, status) + expect(subject).to_not permit(viewer, status) + end end end - permissions :reblog? do - it 'denies access when private' do - viewer = Fabricate(:account) - status.visibility = :private + context 'with the permission of reblog?' do + permissions :reblog? do + it 'denies access when private' do + viewer = Fabricate(:account) + status.visibility = :private - expect(subject).to_not permit(viewer, status) - end + expect(subject).to_not permit(viewer, status) + end - it 'denies access when direct' do - viewer = Fabricate(:account) - status.visibility = :direct + it 'denies access when direct' do + viewer = Fabricate(:account) + status.visibility = :direct - expect(subject).to_not permit(viewer, status) + expect(subject).to_not permit(viewer, status) + end end end - permissions :destroy?, :unreblog? do - it 'grants access when account is deleter' do - expect(subject).to permit(status.account, status) - end + context 'with the permissions of destroy? and unreblog?' do + permissions :destroy?, :unreblog? do + it 'grants access when account is deleter' do + expect(subject).to permit(status.account, status) + end - it 'denies access when account is not deleter' do - expect(subject).to_not permit(bob, status) - end + it 'denies access when account is not deleter' do + expect(subject).to_not permit(bob, status) + end - it 'denies access when no deleter' do - expect(subject).to_not permit(nil, status) + it 'denies access when no deleter' do + expect(subject).to_not permit(nil, status) + end end end - permissions :favourite? do - it 'grants access when viewer is not blocked' do - follow = Fabricate(:follow) - status.account = follow.target_account + context 'with the permission of favourite?' do + permissions :favourite? do + it 'grants access when viewer is not blocked' do + follow = Fabricate(:follow) + status.account = follow.target_account - expect(subject).to permit(follow.account, status) - end + expect(subject).to permit(follow.account, status) + end - it 'denies when viewer is blocked' do - block = Fabricate(:block) - status.account = block.target_account + it 'denies when viewer is blocked' do + block = Fabricate(:block) + status.account = block.target_account - expect(subject).to_not permit(block.account, status) + expect(subject).to_not permit(block.account, status) + end end end - permissions :update? do - it 'grants access if owner' do - expect(subject).to permit(status.account, status) + context 'with the permission of update?' do + permissions :update? do + it 'grants access if owner' do + expect(subject).to permit(status.account, status) + end end end end From b3e018ce589befd6ed53ab9287bee6819a37c0c6 Mon Sep 17 00:00:00 2001 From: fusagiko / takayamaki <24884114+takayamaki@users.noreply.github.com> Date: Wed, 10 May 2023 06:08:28 +0900 Subject: [PATCH 83/98] [Glitch] Rename Image component to ServerHeroImage Port cbca95463259d7bf5586a253be902773a6487d14 to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/components/server_banner.jsx | 4 ++-- .../glitch/components/{image.tsx => server_hero_image.tsx} | 2 +- app/javascript/flavours/glitch/features/about/index.jsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename app/javascript/flavours/glitch/components/{image.tsx => server_hero_image.tsx} (93%) diff --git a/app/javascript/flavours/glitch/components/server_banner.jsx b/app/javascript/flavours/glitch/components/server_banner.jsx index 8b9c501538..ec264f1f1b 100644 --- a/app/javascript/flavours/glitch/components/server_banner.jsx +++ b/app/javascript/flavours/glitch/components/server_banner.jsx @@ -7,7 +7,7 @@ import ShortNumber from 'flavours/glitch/components/short_number'; import Skeleton from 'flavours/glitch/components/skeleton'; import Account from 'flavours/glitch/containers/account_container'; import { domain } from 'flavours/glitch/initial_state'; -import { Image } from 'flavours/glitch/components/image'; +import { ServerHeroImage } from 'flavours/glitch/components/server_hero_image'; import { Link } from 'react-router-dom'; const messages = defineMessages({ @@ -41,7 +41,7 @@ class ServerBanner extends React.PureComponent { {domain}
, mastodon: Mastodon }} />
- +
{isLoading ? ( diff --git a/app/javascript/flavours/glitch/components/image.tsx b/app/javascript/flavours/glitch/components/server_hero_image.tsx similarity index 93% rename from app/javascript/flavours/glitch/components/image.tsx rename to app/javascript/flavours/glitch/components/server_hero_image.tsx index 490543424a..d10b8a620b 100644 --- a/app/javascript/flavours/glitch/components/image.tsx +++ b/app/javascript/flavours/glitch/components/server_hero_image.tsx @@ -9,7 +9,7 @@ type Props = { className?: string; }; -export const Image: React.FC = ({ +export const ServerHeroImage: React.FC = ({ src, srcSet, blurhash, diff --git a/app/javascript/flavours/glitch/features/about/index.jsx b/app/javascript/flavours/glitch/features/about/index.jsx index e2556ec340..8ef05d1f8a 100644 --- a/app/javascript/flavours/glitch/features/about/index.jsx +++ b/app/javascript/flavours/glitch/features/about/index.jsx @@ -11,7 +11,7 @@ import Account from 'flavours/glitch/containers/account_container'; import Skeleton from 'flavours/glitch/components/skeleton'; import { Icon } from 'flavours/glitch/components/icon'; import classNames from 'classnames'; -import { Image } from 'flavours/glitch/components/image'; +import { ServerHeroImage } from 'flavours/glitch/components/server_hero_image'; const messages = defineMessages({ title: { id: 'column.about', defaultMessage: 'About' }, @@ -114,7 +114,7 @@ class About extends React.PureComponent {
- `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' /> + `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' />

{isLoading ? : server.get('domain')}

Mastodon }} />

From a3c2519ba08b135bfbb5450c90f92dec9f074bba Mon Sep 17 00:00:00 2001 From: fusagiko / takayamaki <24884114+takayamaki@users.noreply.github.com> Date: Wed, 10 May 2023 06:08:54 +0900 Subject: [PATCH 84/98] [Glitch] Add type annotation for DisplayName component Port a150bb4f2ef7078ffe26f27cf9adab2d72c0bca8 to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/components/account.jsx | 2 +- .../glitch/components/display_name.jsx | 83 ------------ .../glitch/components/display_name.tsx | 124 ++++++++++++++++++ .../glitch/components/status_header.jsx | 2 +- .../components/moved_note.jsx | 2 +- .../components/autosuggest_account.jsx | 2 +- .../directory/components/account_card.jsx | 2 +- .../components/account.jsx | 2 +- .../components/account_authorize.jsx | 2 +- .../list_adder/components/account.jsx | 2 +- .../list_editor/components/account.jsx | 2 +- .../components/follow_request.jsx | 2 +- .../picture_in_picture/components/header.jsx | 2 +- .../report/components/status_check_box.jsx | 2 +- .../status/components/detailed_status.jsx | 2 +- .../features/ui/components/actions_modal.jsx | 2 +- .../features/ui/components/boost_modal.jsx | 2 +- .../ui/components/favourite_modal.jsx | 2 +- 18 files changed, 140 insertions(+), 99 deletions(-) delete mode 100644 app/javascript/flavours/glitch/components/display_name.jsx create mode 100644 app/javascript/flavours/glitch/components/display_name.tsx diff --git a/app/javascript/flavours/glitch/components/account.jsx b/app/javascript/flavours/glitch/components/account.jsx index e9f04fcda7..66b7e3e4a3 100644 --- a/app/javascript/flavours/glitch/components/account.jsx +++ b/app/javascript/flavours/glitch/components/account.jsx @@ -2,7 +2,7 @@ import React, { Fragment } from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { Avatar } from './avatar'; -import DisplayName from './display_name'; +import { DisplayName } from './display_name'; import Permalink from './permalink'; import { IconButton } from './icon_button'; import { defineMessages, injectIntl } from 'react-intl'; diff --git a/app/javascript/flavours/glitch/components/display_name.jsx b/app/javascript/flavours/glitch/components/display_name.jsx deleted file mode 100644 index fceca5d961..0000000000 --- a/app/javascript/flavours/glitch/components/display_name.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { autoPlayGif } from 'flavours/glitch/initial_state'; -import Skeleton from 'flavours/glitch/components/skeleton'; - -export default class DisplayName extends React.PureComponent { - - static propTypes = { - account: ImmutablePropTypes.map, - others: ImmutablePropTypes.list, - localDomain: PropTypes.string, - inline: PropTypes.bool, - }; - - handleMouseEnter = ({ currentTarget }) => { - if (autoPlayGif) { - return; - } - - const emojis = currentTarget.querySelectorAll('.custom-emoji'); - - for (var i = 0; i < emojis.length; i++) { - let emoji = emojis[i]; - emoji.src = emoji.getAttribute('data-original'); - } - }; - - handleMouseLeave = ({ currentTarget }) => { - if (autoPlayGif) { - return; - } - - const emojis = currentTarget.querySelectorAll('.custom-emoji'); - - for (var i = 0; i < emojis.length; i++) { - let emoji = emojis[i]; - emoji.src = emoji.getAttribute('data-static'); - } - }; - - render () { - const { others, localDomain, inline } = this.props; - - let displayName, suffix, account; - - if (others && others.size > 1) { - displayName = others.take(2).map(a => ).reduce((prev, cur) => [prev, ', ', cur]); - - if (others.size - 2 > 0) { - suffix = `+${others.size - 2}`; - } - } else if ((others && others.size > 0) || this.props.account) { - if (others && others.size > 0) { - account = others.first(); - } else { - account = this.props.account; - } - - let acct = account.get('acct'); - - if (acct.indexOf('@') === -1 && localDomain) { - acct = `${acct}@${localDomain}`; - } - - displayName = ; - suffix = @{acct}; - } else { - displayName = ; - suffix = ; - } - - return ( - - {displayName} - {inline ? ' ' : null} - {suffix} - - ); - } - -} diff --git a/app/javascript/flavours/glitch/components/display_name.tsx b/app/javascript/flavours/glitch/components/display_name.tsx new file mode 100644 index 0000000000..4dfc51c785 --- /dev/null +++ b/app/javascript/flavours/glitch/components/display_name.tsx @@ -0,0 +1,124 @@ +import React from 'react'; + +import classNames from 'classnames'; + +import type { List } from 'immutable'; + +import type { Account } from 'flavours/glitch/types/resources'; + +import { autoPlayGif } from '../initial_state'; + +import Skeleton from './skeleton'; + +interface Props { + account: Account; + others: List; + localDomain: string; + inline?: boolean; +} +export class DisplayName extends React.PureComponent { + handleMouseEnter: React.ReactEventHandler = ({ + currentTarget, + }) => { + if (autoPlayGif) { + return; + } + + const emojis = + currentTarget.querySelectorAll('img.custom-emoji'); + + emojis.forEach((emoji) => { + const originalSrc = emoji.getAttribute('data-original'); + if (originalSrc != null) emoji.src = originalSrc; + }); + }; + + handleMouseLeave: React.ReactEventHandler = ({ + currentTarget, + }) => { + if (autoPlayGif) { + return; + } + + const emojis = + currentTarget.querySelectorAll('img.custom-emoji'); + + emojis.forEach((emoji) => { + const staticSrc = emoji.getAttribute('data-static'); + if (staticSrc != null) emoji.src = staticSrc; + }); + }; + + render() { + const { others, localDomain, inline } = this.props; + + let displayName: React.ReactNode, suffix: React.ReactNode, account: Account; + + if (others && others.size > 1) { + displayName = others + .take(2) + .map((a) => ( + + + + )) + .reduce((prev, cur) => [prev, ', ', cur]); + + if (others.size - 2 > 0) { + suffix = `+${others.size - 2}`; + } + } else if ((others && others.size > 0) || this.props.account) { + if (others && others.size > 0) { + account = others.first(); + } else { + account = this.props.account; + } + + let acct = account.get('acct'); + + if (acct.indexOf('@') === -1 && localDomain) { + acct = `${acct}@${localDomain}`; + } + + displayName = ( + + + + ); + suffix = @{acct}; + } else { + displayName = ( + + + + + + ); + suffix = ( + + + + ); + } + + return ( + + {displayName} + {inline ? ' ' : null} + {suffix} + + ); + } +} diff --git a/app/javascript/flavours/glitch/components/status_header.jsx b/app/javascript/flavours/glitch/components/status_header.jsx index 8813d19b9c..8f54613579 100644 --- a/app/javascript/flavours/glitch/components/status_header.jsx +++ b/app/javascript/flavours/glitch/components/status_header.jsx @@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; // Mastodon imports. import { Avatar } from './avatar'; import AvatarOverlay from './avatar_overlay'; -import DisplayName from './display_name'; +import { DisplayName } from './display_name'; export default class StatusHeader extends React.PureComponent { diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.jsx b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.jsx index 908b70c5f2..7d025ed418 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.jsx +++ b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.jsx @@ -4,7 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import AvatarOverlay from '../../../components/avatar_overlay'; -import DisplayName from '../../../components/display_name'; +import { DisplayName } from '../../../components/display_name'; import { Icon } from 'flavours/glitch/components/icon'; export default class MovedNote extends ImmutablePureComponent { diff --git a/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.jsx b/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.jsx index 8b79692502..db17c6e727 100644 --- a/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { Avatar } from 'flavours/glitch/components/avatar'; -import DisplayName from 'flavours/glitch/components/display_name'; +import { DisplayName } from 'flavours/glitch/components/display_name'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; diff --git a/app/javascript/flavours/glitch/features/directory/components/account_card.jsx b/app/javascript/flavours/glitch/features/directory/components/account_card.jsx index 79543389c6..da11f881fc 100644 --- a/app/javascript/flavours/glitch/features/directory/components/account_card.jsx +++ b/app/javascript/flavours/glitch/features/directory/components/account_card.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { makeGetAccount } from 'flavours/glitch/selectors'; import { Avatar } from 'flavours/glitch/components/avatar'; -import DisplayName from 'flavours/glitch/components/display_name'; +import { DisplayName } from 'flavours/glitch/components/display_name'; import Permalink from 'flavours/glitch/components/permalink'; import { IconButton } from 'flavours/glitch/components/icon_button'; import Button from 'flavours/glitch/components/button'; diff --git a/app/javascript/flavours/glitch/features/follow_recommendations/components/account.jsx b/app/javascript/flavours/glitch/features/follow_recommendations/components/account.jsx index e15bc14222..4eeffc402b 100644 --- a/app/javascript/flavours/glitch/features/follow_recommendations/components/account.jsx +++ b/app/javascript/flavours/glitch/features/follow_recommendations/components/account.jsx @@ -5,7 +5,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; import { makeGetAccount } from 'flavours/glitch/selectors'; import { Avatar } from 'flavours/glitch/components/avatar'; -import DisplayName from 'flavours/glitch/components/display_name'; +import { DisplayName } from 'flavours/glitch/components/display_name'; import Permalink from 'flavours/glitch/components/permalink'; import { IconButton } from 'flavours/glitch/components/icon_button'; import { injectIntl, defineMessages } from 'react-intl'; diff --git a/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx b/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx index b2dfc5dbb3..31e79f042e 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx +++ b/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Permalink from 'flavours/glitch/components/permalink'; import { Avatar } from 'flavours/glitch/components/avatar'; -import DisplayName from 'flavours/glitch/components/display_name'; +import { DisplayName } from 'flavours/glitch/components/display_name'; import { IconButton } from 'flavours/glitch/components/icon_button'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; diff --git a/app/javascript/flavours/glitch/features/list_adder/components/account.jsx b/app/javascript/flavours/glitch/features/list_adder/components/account.jsx index 64522a3a86..0e4b098d92 100644 --- a/app/javascript/flavours/glitch/features/list_adder/components/account.jsx +++ b/app/javascript/flavours/glitch/features/list_adder/components/account.jsx @@ -4,7 +4,7 @@ import { makeGetAccount } from '../../../selectors'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { Avatar } from '../../../components/avatar'; -import DisplayName from '../../../components/display_name'; +import { DisplayName } from '../../../components/display_name'; import { injectIntl } from 'react-intl'; const makeMapStateToProps = () => { diff --git a/app/javascript/flavours/glitch/features/list_editor/components/account.jsx b/app/javascript/flavours/glitch/features/list_editor/components/account.jsx index 69fbaf5042..b94b7e3f62 100644 --- a/app/javascript/flavours/glitch/features/list_editor/components/account.jsx +++ b/app/javascript/flavours/glitch/features/list_editor/components/account.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { Avatar } from 'flavours/glitch/components/avatar'; -import DisplayName from 'flavours/glitch/components/display_name'; +import { DisplayName } from 'flavours/glitch/components/display_name'; import { IconButton } from 'flavours/glitch/components/icon_button'; import { defineMessages } from 'react-intl'; diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx b/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx index d7c229404b..c61f656f69 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx @@ -2,7 +2,7 @@ import React, { Fragment } from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { Avatar } from 'flavours/glitch/components/avatar'; -import DisplayName from 'flavours/glitch/components/display_name'; +import { DisplayName } from 'flavours/glitch/components/display_name'; import Permalink from 'flavours/glitch/components/permalink'; import { IconButton } from 'flavours/glitch/components/icon_button'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/header.jsx b/app/javascript/flavours/glitch/features/picture_in_picture/components/header.jsx index b9c72c276c..43a910c505 100644 --- a/app/javascript/flavours/glitch/features/picture_in_picture/components/header.jsx +++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/header.jsx @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; import { IconButton } from 'flavours/glitch/components/icon_button'; import { Link } from 'react-router-dom'; import { Avatar } from 'flavours/glitch/components/avatar'; -import DisplayName from 'flavours/glitch/components/display_name'; +import { DisplayName } from 'flavours/glitch/components/display_name'; import { defineMessages, injectIntl } from 'react-intl'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/features/report/components/status_check_box.jsx b/app/javascript/flavours/glitch/features/report/components/status_check_box.jsx index 6b74aafeea..92ccd4092f 100644 --- a/app/javascript/flavours/glitch/features/report/components/status_check_box.jsx +++ b/app/javascript/flavours/glitch/features/report/components/status_check_box.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import StatusContent from 'flavours/glitch/components/status_content'; import { Avatar } from 'flavours/glitch/components/avatar'; -import DisplayName from 'flavours/glitch/components/display_name'; +import { DisplayName } from 'flavours/glitch/components/display_name'; import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; import Option from './option'; import MediaAttachments from 'flavours/glitch/components/media_attachments'; diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx index 992dd677dd..e36ab818fc 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { Avatar } from 'flavours/glitch/components/avatar'; -import DisplayName from 'flavours/glitch/components/display_name'; +import { DisplayName } from 'flavours/glitch/components/display_name'; import StatusContent from 'flavours/glitch/components/status_content'; import MediaGallery from 'flavours/glitch/components/media_gallery'; import AttachmentList from 'flavours/glitch/components/attachment_list'; diff --git a/app/javascript/flavours/glitch/features/ui/components/actions_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/actions_modal.jsx index 6a98e8b533..cf4bf46343 100644 --- a/app/javascript/flavours/glitch/features/ui/components/actions_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/actions_modal.jsx @@ -5,7 +5,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import StatusContent from 'flavours/glitch/components/status_content'; import { Avatar } from 'flavours/glitch/components/avatar'; import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; -import DisplayName from 'flavours/glitch/components/display_name'; +import { DisplayName } from 'flavours/glitch/components/display_name'; import classNames from 'classnames'; import { IconButton } from 'flavours/glitch/components/icon_button'; diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx index 31ae4f68fc..6128df2496 100644 --- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx @@ -7,7 +7,7 @@ import Button from 'flavours/glitch/components/button'; import StatusContent from 'flavours/glitch/components/status_content'; import { Avatar } from 'flavours/glitch/components/avatar'; import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; -import DisplayName from 'flavours/glitch/components/display_name'; +import { DisplayName } from 'flavours/glitch/components/display_name'; import AttachmentList from 'flavours/glitch/components/attachment_list'; import { Icon } from 'flavours/glitch/components/icon'; import ImmutablePureComponent from 'react-immutable-pure-component'; diff --git a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx index 4090b65003..766a35c866 100644 --- a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx @@ -6,7 +6,7 @@ import Button from 'flavours/glitch/components/button'; import StatusContent from 'flavours/glitch/components/status_content'; import { Avatar } from 'flavours/glitch/components/avatar'; import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; -import DisplayName from 'flavours/glitch/components/display_name'; +import { DisplayName } from 'flavours/glitch/components/display_name'; import AttachmentList from 'flavours/glitch/components/attachment_list'; import { Icon } from 'flavours/glitch/components/icon'; import ImmutablePureComponent from 'react-immutable-pure-component'; From afde99dc29705f9c3a5f6821998a559b552f0f15 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Wed, 10 May 2023 08:38:02 +0200 Subject: [PATCH 85/98] [Glitch] Disable RTK safety middlewares Port c9dfc55b22fbfde79b525754854d9a0f6bc466f7 to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/store/index.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/javascript/flavours/glitch/store/index.ts b/app/javascript/flavours/glitch/store/index.ts index 6c3e963d9e..f7e1be6b7b 100644 --- a/app/javascript/flavours/glitch/store/index.ts +++ b/app/javascript/flavours/glitch/store/index.ts @@ -8,7 +8,21 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; export const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => - getDefaultMiddleware() + getDefaultMiddleware({ + // In development, Redux Toolkit enables 2 default middlewares to detect + // common issues with states. Unfortunately, our use of ImmutableJS for state + // triggers both, so lets disable them until our state is fully refactored + + // https://redux-toolkit.js.org/api/serializabilityMiddleware + // This checks recursively that every values in the state are serializable in JSON + // Which is not the case, as we use ImmutableJS structures, but also File objects + serializableCheck: false, + + // https://redux-toolkit.js.org/api/immutabilityMiddleware + // This checks recursively if every value in the state is immutable (ie, a JS primitive type) + // But this is not the case, as our Root State is an ImmutableJS map, which is an object + immutableCheck: false, + }) .concat( loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'], From dc9dcd0fa9bd2e7ed682f833d678e54a61d1b77c Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Wed, 10 May 2023 03:05:32 -0400 Subject: [PATCH 86/98] [Glitch] Enable ESLint react/no-deprecated Port b0dd260a7a98f0a110ade6ee636435fe7efdb3a9 to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/components/autosuggest_input.jsx | 2 +- .../flavours/glitch/components/autosuggest_textarea.jsx | 2 +- app/javascript/flavours/glitch/components/media_gallery.jsx | 2 +- app/javascript/flavours/glitch/components/modal_root.jsx | 2 +- .../flavours/glitch/features/account_timeline/index.jsx | 2 +- app/javascript/flavours/glitch/features/audio/index.jsx | 2 +- app/javascript/flavours/glitch/features/blocks/index.jsx | 2 +- .../flavours/glitch/features/bookmarked_statuses/index.jsx | 2 +- .../features/compose/components/emoji_picker_dropdown.jsx | 2 +- .../flavours/glitch/features/domain_blocks/index.jsx | 2 +- .../flavours/glitch/features/favourited_statuses/index.jsx | 2 +- app/javascript/flavours/glitch/features/favourites/index.jsx | 4 ++-- .../flavours/glitch/features/follow_requests/index.jsx | 2 +- .../flavours/glitch/features/getting_started/index.jsx | 2 +- .../flavours/glitch/features/list_timeline/index.jsx | 2 +- app/javascript/flavours/glitch/features/lists/index.jsx | 2 +- app/javascript/flavours/glitch/features/mutes/index.jsx | 2 +- .../flavours/glitch/features/pinned_statuses/index.jsx | 2 +- app/javascript/flavours/glitch/features/reblogs/index.jsx | 4 ++-- .../flavours/glitch/features/status/components/card.jsx | 2 +- .../flavours/glitch/features/ui/components/bundle.jsx | 4 ++-- .../glitch/features/ui/components/onboarding_modal.jsx | 2 +- app/javascript/flavours/glitch/features/ui/index.jsx | 4 ++-- app/javascript/flavours/glitch/features/video/index.jsx | 2 +- 24 files changed, 28 insertions(+), 28 deletions(-) diff --git a/app/javascript/flavours/glitch/components/autosuggest_input.jsx b/app/javascript/flavours/glitch/components/autosuggest_input.jsx index ea9fd0828a..9e74590e60 100644 --- a/app/javascript/flavours/glitch/components/autosuggest_input.jsx +++ b/app/javascript/flavours/glitch/components/autosuggest_input.jsx @@ -154,7 +154,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { this.input.focus(); }; - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { this.setState({ suggestionsHidden: false }); } diff --git a/app/javascript/flavours/glitch/components/autosuggest_textarea.jsx b/app/javascript/flavours/glitch/components/autosuggest_textarea.jsx index a016e44b72..fc763146eb 100644 --- a/app/javascript/flavours/glitch/components/autosuggest_textarea.jsx +++ b/app/javascript/flavours/glitch/components/autosuggest_textarea.jsx @@ -153,7 +153,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { this.textarea.focus(); }; - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { this.setState({ suggestionsHidden: false }); } diff --git a/app/javascript/flavours/glitch/components/media_gallery.jsx b/app/javascript/flavours/glitch/components/media_gallery.jsx index 1b4ce05743..3c63124493 100644 --- a/app/javascript/flavours/glitch/components/media_gallery.jsx +++ b/app/javascript/flavours/glitch/components/media_gallery.jsx @@ -254,7 +254,7 @@ class MediaGallery extends React.PureComponent { window.removeEventListener('resize', this.handleResize); } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (!is(nextProps.media, this.props.media) && nextProps.visible === undefined) { this.setState({ visible: displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all' }); } else if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) { diff --git a/app/javascript/flavours/glitch/components/modal_root.jsx b/app/javascript/flavours/glitch/components/modal_root.jsx index 5a5563e873..f08542e7fc 100644 --- a/app/javascript/flavours/glitch/components/modal_root.jsx +++ b/app/javascript/flavours/glitch/components/modal_root.jsx @@ -62,7 +62,7 @@ export default class ModalRoot extends React.PureComponent { } } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (!!nextProps.children && !this.props.children) { this.activeElement = document.activeElement; diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.jsx b/app/javascript/flavours/glitch/features/account_timeline/index.jsx index 87c8d74675..0a4bb94888 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/account_timeline/index.jsx @@ -122,7 +122,7 @@ class AccountTimeline extends ImmutablePureComponent { } } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { const { dispatch } = this.props; if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) { diff --git a/app/javascript/flavours/glitch/features/audio/index.jsx b/app/javascript/flavours/glitch/features/audio/index.jsx index 8c13f6797e..25252c01c3 100644 --- a/app/javascript/flavours/glitch/features/audio/index.jsx +++ b/app/javascript/flavours/glitch/features/audio/index.jsx @@ -142,7 +142,7 @@ class Audio extends React.PureComponent { } } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) { this.setState({ revealed: nextProps.visible }); } diff --git a/app/javascript/flavours/glitch/features/blocks/index.jsx b/app/javascript/flavours/glitch/features/blocks/index.jsx index 461dac2ec0..669425c7f4 100644 --- a/app/javascript/flavours/glitch/features/blocks/index.jsx +++ b/app/javascript/flavours/glitch/features/blocks/index.jsx @@ -34,7 +34,7 @@ class Blocks extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchBlocks()); } diff --git a/app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx b/app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx index 90d8fd0ef1..d9c0746825 100644 --- a/app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx +++ b/app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx @@ -34,7 +34,7 @@ class Bookmarks extends ImmutablePureComponent { isLoading: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchBookmarkedStatuses()); } diff --git a/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.jsx b/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.jsx index 1b8991f009..7526899305 100644 --- a/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.jsx @@ -60,7 +60,7 @@ class ModifierPickerMenu extends React.PureComponent { this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1); }; - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (nextProps.active) { this.attachListeners(); } else { diff --git a/app/javascript/flavours/glitch/features/domain_blocks/index.jsx b/app/javascript/flavours/glitch/features/domain_blocks/index.jsx index 1ab7c36633..b9ef739593 100644 --- a/app/javascript/flavours/glitch/features/domain_blocks/index.jsx +++ b/app/javascript/flavours/glitch/features/domain_blocks/index.jsx @@ -34,7 +34,7 @@ class Blocks extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchDomainBlocks()); } diff --git a/app/javascript/flavours/glitch/features/favourited_statuses/index.jsx b/app/javascript/flavours/glitch/features/favourited_statuses/index.jsx index 60d281f974..9b63dec23b 100644 --- a/app/javascript/flavours/glitch/features/favourited_statuses/index.jsx +++ b/app/javascript/flavours/glitch/features/favourited_statuses/index.jsx @@ -34,7 +34,7 @@ class Favourites extends ImmutablePureComponent { isLoading: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchFavouritedStatuses()); } diff --git a/app/javascript/flavours/glitch/features/favourites/index.jsx b/app/javascript/flavours/glitch/features/favourites/index.jsx index 8755811d94..3287c5989d 100644 --- a/app/javascript/flavours/glitch/features/favourites/index.jsx +++ b/app/javascript/flavours/glitch/features/favourites/index.jsx @@ -32,13 +32,13 @@ class Favourites extends ImmutablePureComponent { intl: PropTypes.object.isRequired, }; - componentWillMount () { + UNSAFE_componentWillMount () { if (!this.props.accountIds) { this.props.dispatch(fetchFavourites(this.props.params.statusId)); } } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { this.props.dispatch(fetchFavourites(nextProps.params.statusId)); } diff --git a/app/javascript/flavours/glitch/features/follow_requests/index.jsx b/app/javascript/flavours/glitch/features/follow_requests/index.jsx index a9a35f54b8..7ca707ec95 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/index.jsx +++ b/app/javascript/flavours/glitch/features/follow_requests/index.jsx @@ -39,7 +39,7 @@ class FollowRequests extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchFollowRequests()); } diff --git a/app/javascript/flavours/glitch/features/getting_started/index.jsx b/app/javascript/flavours/glitch/features/getting_started/index.jsx index 2ef007da5b..8356e977ef 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.jsx +++ b/app/javascript/flavours/glitch/features/getting_started/index.jsx @@ -96,7 +96,7 @@ class GettingStarted extends ImmutablePureComponent { openSettings: PropTypes.func.isRequired, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.fetchLists(); } diff --git a/app/javascript/flavours/glitch/features/list_timeline/index.jsx b/app/javascript/flavours/glitch/features/list_timeline/index.jsx index f7abe248d4..8803664761 100644 --- a/app/javascript/flavours/glitch/features/list_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/list_timeline/index.jsx @@ -76,7 +76,7 @@ class ListTimeline extends React.PureComponent { this.disconnect = dispatch(connectListStream(id)); } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { const { dispatch } = this.props; const { id } = nextProps.params; diff --git a/app/javascript/flavours/glitch/features/lists/index.jsx b/app/javascript/flavours/glitch/features/lists/index.jsx index dce0dcd8f8..375ab5faed 100644 --- a/app/javascript/flavours/glitch/features/lists/index.jsx +++ b/app/javascript/flavours/glitch/features/lists/index.jsx @@ -42,7 +42,7 @@ class Lists extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchLists()); } diff --git a/app/javascript/flavours/glitch/features/mutes/index.jsx b/app/javascript/flavours/glitch/features/mutes/index.jsx index b699fdb278..e1a76e2fe3 100644 --- a/app/javascript/flavours/glitch/features/mutes/index.jsx +++ b/app/javascript/flavours/glitch/features/mutes/index.jsx @@ -35,7 +35,7 @@ class Mutes extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchMutes()); } diff --git a/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx b/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx index 41be2f7f33..5f6035cb1e 100644 --- a/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx +++ b/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx @@ -29,7 +29,7 @@ class PinnedStatuses extends ImmutablePureComponent { multiColumn: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { this.props.dispatch(fetchPinnedStatuses()); } diff --git a/app/javascript/flavours/glitch/features/reblogs/index.jsx b/app/javascript/flavours/glitch/features/reblogs/index.jsx index fe74f1fbcc..5b03500c2c 100644 --- a/app/javascript/flavours/glitch/features/reblogs/index.jsx +++ b/app/javascript/flavours/glitch/features/reblogs/index.jsx @@ -32,13 +32,13 @@ class Reblogs extends ImmutablePureComponent { intl: PropTypes.object.isRequired, }; - componentWillMount () { + UNSAFE_componentWillMount () { if (!this.props.accountIds) { this.props.dispatch(fetchReblogs(this.props.params.statusId)); } } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { this.props.dispatch(fetchReblogs(nextProps.params.statusId)); } diff --git a/app/javascript/flavours/glitch/features/status/components/card.jsx b/app/javascript/flavours/glitch/features/status/components/card.jsx index 9b84da06e5..dd91b9334e 100644 --- a/app/javascript/flavours/glitch/features/status/components/card.jsx +++ b/app/javascript/flavours/glitch/features/status/components/card.jsx @@ -57,7 +57,7 @@ export default class Card extends React.PureComponent { revealed: !this.props.sensitive, }; - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (!Immutable.is(this.props.card, nextProps.card)) { this.setState({ embedded: false, previewLoaded: false }); } diff --git a/app/javascript/flavours/glitch/features/ui/components/bundle.jsx b/app/javascript/flavours/glitch/features/ui/components/bundle.jsx index 27b13ecfe7..0e3a9ae048 100644 --- a/app/javascript/flavours/glitch/features/ui/components/bundle.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/bundle.jsx @@ -33,11 +33,11 @@ class Bundle extends React.Component { forceRender: false, }; - componentWillMount() { + UNSAFE_componentWillMount() { this.load(this.props); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.fetchComponent !== this.props.fetchComponent) { this.load(nextProps); } diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.jsx index a28be0e885..4e8e4b496d 100644 --- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.jsx @@ -184,7 +184,7 @@ class OnboardingModal extends React.PureComponent { currentIndex: 0, }; - componentWillMount() { + UNSAFE_componentWillMount() { const { myAccount, admin, domain, intl } = this.props; this.pages = [ , diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx index a29070bc7a..4fc2372b37 100644 --- a/app/javascript/flavours/glitch/features/ui/index.jsx +++ b/app/javascript/flavours/glitch/features/ui/index.jsx @@ -133,7 +133,7 @@ class SwitchingColumnsArea extends React.PureComponent { mobile: PropTypes.bool, }; - componentWillMount () { + UNSAFE_componentWillMount () { if (this.props.mobile) { document.body.classList.toggle('layout-single-column', true); document.body.classList.toggle('layout-multiple-columns', false); @@ -438,7 +438,7 @@ class UI extends React.Component { } } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (nextProps.layout_local_setting !== this.props.layout_local_setting) { const layout = layoutFromWindow(nextProps.layout_local_setting); diff --git a/app/javascript/flavours/glitch/features/video/index.jsx b/app/javascript/flavours/glitch/features/video/index.jsx index fb791fa9f3..e70702ee74 100644 --- a/app/javascript/flavours/glitch/features/video/index.jsx +++ b/app/javascript/flavours/glitch/features/video/index.jsx @@ -373,7 +373,7 @@ class Video extends React.PureComponent { } } - componentWillReceiveProps (nextProps) { + UNSAFE_componentWillReceiveProps (nextProps) { if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) { this.setState({ revealed: nextProps.visible }); } From e11d884c53cdbd7243009d06ead915d48edb95f1 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Wed, 10 May 2023 12:59:29 +0200 Subject: [PATCH 87/98] [Glitch] Add stricter ESLint rules for Typescript files Port 6a3211876c6aab15ecca36e4d41cbf6b26788ef5 to glitch-soc Signed-off-by: Claire --- .eslintrc.js | 2 +- app/javascript/flavours/glitch/actions/app.ts | 5 +- .../flavours/glitch/actions/pin_statuses.js | 4 +- .../glitch/components/animated_number.tsx | 15 ++-- .../flavours/glitch/components/avatar.tsx | 8 ++- .../flavours/glitch/components/blurhash.tsx | 9 +-- .../flavours/glitch/components/domain.tsx | 9 ++- .../flavours/glitch/components/gifv.tsx | 4 +- .../flavours/glitch/components/icon.tsx | 7 +- .../glitch/components/icon_button.tsx | 16 +++-- .../glitch/components/icon_with_badge.tsx | 5 +- .../components/not_signed_in_indicator.tsx | 1 + .../glitch/components/radio_button.tsx | 5 +- .../glitch/components/relative_timestamp.tsx | 12 ++-- .../glitch/components/server_hero_image.tsx | 8 ++- .../features/account_timeline/index.jsx | 2 +- .../features/ui/components/columns_area.jsx | 2 +- .../ui/components/focal_point_modal.jsx | 2 +- .../features/ui/components/upload_area.jsx | 2 +- .../flavours/glitch/features/ui/index.jsx | 2 +- app/javascript/flavours/glitch/is_mobile.ts | 1 + .../flavours/glitch/packs/common.js | 4 +- .../glitch/polyfills/base_polyfills.ts | 9 ++- .../flavours/glitch/reducers/index.ts | 71 ++++++++++--------- .../flavours/glitch/reducers/markers.js | 4 +- app/javascript/flavours/glitch/store/index.ts | 8 ++- .../glitch/store/middlewares/errors.ts | 10 +-- .../glitch/store/middlewares/loading_bar.ts | 23 +++--- .../glitch/store/middlewares/sounds.ts | 23 +++--- .../flavours/glitch/types/resources.ts | 8 +-- .../flavours/glitch/utils/resize_image.js | 4 +- app/javascript/flavours/glitch/uuid.ts | 7 +- tsconfig.json | 2 + 33 files changed, 171 insertions(+), 123 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 1800daa55d..badcb1dcb9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -319,7 +319,7 @@ module.exports = { }, // Internal packages { - pattern: '{mastodon/**}', + pattern: '{mastodon/**,flavours/glitch-soc/**}', group: 'internal', position: 'after', }, diff --git a/app/javascript/flavours/glitch/actions/app.ts b/app/javascript/flavours/glitch/actions/app.ts index 46b2cdf93c..6fbfc07f68 100644 --- a/app/javascript/flavours/glitch/actions/app.ts +++ b/app/javascript/flavours/glitch/actions/app.ts @@ -1,8 +1,9 @@ import { createAction } from '@reduxjs/toolkit'; + import type { LayoutType } from '../is_mobile'; -type ChangeLayoutPayload = { +interface ChangeLayoutPayload { layout: LayoutType; -}; +} export const changeLayout = createAction('APP_LAYOUT_CHANGE'); diff --git a/app/javascript/flavours/glitch/actions/pin_statuses.js b/app/javascript/flavours/glitch/actions/pin_statuses.js index d8c0a13737..a0d5933b1c 100644 --- a/app/javascript/flavours/glitch/actions/pin_statuses.js +++ b/app/javascript/flavours/glitch/actions/pin_statuses.js @@ -1,12 +1,12 @@ import api from '../api'; import { importFetchedStatuses } from './importer'; +import { me } from 'flavours/glitch/initial_state'; + export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST'; export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS'; export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL'; -import { me } from 'flavours/glitch/initial_state'; - export function fetchPinnedStatuses() { return (dispatch, getState) => { dispatch(fetchPinnedStatusesRequest()); diff --git a/app/javascript/flavours/glitch/components/animated_number.tsx b/app/javascript/flavours/glitch/components/animated_number.tsx index f6c77d35ff..b6b073161b 100644 --- a/app/javascript/flavours/glitch/components/animated_number.tsx +++ b/app/javascript/flavours/glitch/components/animated_number.tsx @@ -1,8 +1,11 @@ import React, { useCallback, useState } from 'react'; -import ShortNumber from './short_number'; + import { TransitionMotion, spring } from 'react-motion'; + import { reduceMotion } from '../initial_state'; +import ShortNumber from './short_number'; + const obfuscatedCount = (count: number) => { if (count < 0) { return 0; @@ -13,10 +16,10 @@ const obfuscatedCount = (count: number) => { } }; -type Props = { +interface Props { value: number; obfuscate?: boolean; -}; +} export const AnimatedNumber: React.FC = ({ value, obfuscate }) => { const [previousValue, setPreviousValue] = useState(value); const [direction, setDirection] = useState<1 | -1>(1); @@ -64,7 +67,11 @@ export const AnimatedNumber: React.FC = ({ value, obfuscate }) => { transform: `translateY(${style.y * 100}%)`, }} > - {obfuscate ? obfuscatedCount(data) : } + {obfuscate ? ( + obfuscatedCount(data as number) + ) : ( + + )} ))} diff --git a/app/javascript/flavours/glitch/components/avatar.tsx b/app/javascript/flavours/glitch/components/avatar.tsx index 1bd7739f5f..11253a8e94 100644 --- a/app/javascript/flavours/glitch/components/avatar.tsx +++ b/app/javascript/flavours/glitch/components/avatar.tsx @@ -1,16 +1,18 @@ import * as React from 'react'; + import classNames from 'classnames'; -import { autoPlayGif } from 'flavours/glitch/initial_state'; + import { useHovering } from 'flavours/glitch/hooks/useHovering'; +import { autoPlayGif } from 'flavours/glitch/initial_state'; import type { Account } from 'flavours/glitch/types/resources'; -type Props = { +interface Props { account: Account | undefined; className?: string; size: number; style?: React.CSSProperties; inline?: boolean; -}; +} export const Avatar: React.FC = ({ account, diff --git a/app/javascript/flavours/glitch/components/blurhash.tsx b/app/javascript/flavours/glitch/components/blurhash.tsx index 7005136765..1550d0b7a5 100644 --- a/app/javascript/flavours/glitch/components/blurhash.tsx +++ b/app/javascript/flavours/glitch/components/blurhash.tsx @@ -1,14 +1,14 @@ -import { decode } from 'blurhash'; import React, { useRef, useEffect } from 'react'; -type Props = { +import { decode } from 'blurhash'; + +interface Props extends React.HTMLAttributes { hash: string; width?: number; height?: number; dummy?: boolean; // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched children?: never; - [key: string]: any; -}; +} const Blurhash: React.FC = ({ hash, width = 32, @@ -21,6 +21,7 @@ const Blurhash: React.FC = ({ useEffect(() => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const canvas = canvasRef.current!; + // eslint-disable-next-line no-self-assign canvas.width = canvas.width; // resets canvas diff --git a/app/javascript/flavours/glitch/components/domain.tsx b/app/javascript/flavours/glitch/components/domain.tsx index af0fec35af..9e8e04b65c 100644 --- a/app/javascript/flavours/glitch/components/domain.tsx +++ b/app/javascript/flavours/glitch/components/domain.tsx @@ -1,6 +1,9 @@ import React, { useCallback } from 'react'; + +import type { InjectedIntl } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; + import { IconButton } from './icon_button'; -import { InjectedIntl, defineMessages, injectIntl } from 'react-intl'; const messages = defineMessages({ unblockDomain: { @@ -9,11 +12,11 @@ const messages = defineMessages({ }, }); -type Props = { +interface Props { domain: string; onUnblockDomain: (domain: string) => void; intl: InjectedIntl; -}; +} const _Domain: React.FC = ({ domain, onUnblockDomain, intl }) => { const handleDomainUnblock = useCallback(() => { onUnblockDomain(domain); diff --git a/app/javascript/flavours/glitch/components/gifv.tsx b/app/javascript/flavours/glitch/components/gifv.tsx index 72914ba741..c606a29048 100644 --- a/app/javascript/flavours/glitch/components/gifv.tsx +++ b/app/javascript/flavours/glitch/components/gifv.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useState } from 'react'; -type Props = { +interface Props { src: string; key: string; alt?: string; @@ -8,7 +8,7 @@ type Props = { width: number; height: number; onClick?: () => void; -}; +} export const GIFV: React.FC = ({ src, diff --git a/app/javascript/flavours/glitch/components/icon.tsx b/app/javascript/flavours/glitch/components/icon.tsx index 4eb948dc76..6bd15da6ac 100644 --- a/app/javascript/flavours/glitch/components/icon.tsx +++ b/app/javascript/flavours/glitch/components/icon.tsx @@ -1,13 +1,14 @@ import React from 'react'; + import classNames from 'classnames'; -type Props = { +interface Props extends React.HTMLAttributes { id: string; className?: string; fixedWidth?: boolean; children?: never; - [key: string]: any; -}; +} + export const Icon: React.FC = ({ id, className, diff --git a/app/javascript/flavours/glitch/components/icon_button.tsx b/app/javascript/flavours/glitch/components/icon_button.tsx index 80b20ae1b0..0b81288241 100644 --- a/app/javascript/flavours/glitch/components/icon_button.tsx +++ b/app/javascript/flavours/glitch/components/icon_button.tsx @@ -1,9 +1,11 @@ import React from 'react'; -import classNames from 'classnames'; -import { Icon } from './icon'; -import { AnimatedNumber } from './animated_number'; -type Props = { +import classNames from 'classnames'; + +import { AnimatedNumber } from './animated_number'; +import { Icon } from './icon'; + +interface Props { className?: string; title: string; icon: string; @@ -26,11 +28,11 @@ type Props = { obfuscateCount?: boolean; href?: string; ariaHidden: boolean; -}; -type States = { +} +interface States { activate: boolean; deactivate: boolean; -}; +} export class IconButton extends React.PureComponent { static defaultProps = { size: 18, diff --git a/app/javascript/flavours/glitch/components/icon_with_badge.tsx b/app/javascript/flavours/glitch/components/icon_with_badge.tsx index bf86814c03..e427b7172b 100644 --- a/app/javascript/flavours/glitch/components/icon_with_badge.tsx +++ b/app/javascript/flavours/glitch/components/icon_with_badge.tsx @@ -1,14 +1,15 @@ import React from 'react'; + import { Icon } from './icon'; const formatNumber = (num: number): number | string => (num > 40 ? '40+' : num); -type Props = { +interface Props { id: string; count: number; issueBadge: boolean; className: string; -}; +} export const IconWithBadge: React.FC = ({ id, count, diff --git a/app/javascript/flavours/glitch/components/not_signed_in_indicator.tsx b/app/javascript/flavours/glitch/components/not_signed_in_indicator.tsx index 53945d6a7a..7a71f6539d 100644 --- a/app/javascript/flavours/glitch/components/not_signed_in_indicator.tsx +++ b/app/javascript/flavours/glitch/components/not_signed_in_indicator.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { FormattedMessage } from 'react-intl'; export const NotSignedInIndicator: React.FC = () => ( diff --git a/app/javascript/flavours/glitch/components/radio_button.tsx b/app/javascript/flavours/glitch/components/radio_button.tsx index 829f471747..67acb09f42 100644 --- a/app/javascript/flavours/glitch/components/radio_button.tsx +++ b/app/javascript/flavours/glitch/components/radio_button.tsx @@ -1,13 +1,14 @@ import React from 'react'; + import classNames from 'classnames'; -type Props = { +interface Props { value: string; checked: boolean; name: string; onChange: (event: React.ChangeEvent) => void; label: React.ReactNode; -}; +} export const RadioButton: React.FC = ({ name, diff --git a/app/javascript/flavours/glitch/components/relative_timestamp.tsx b/app/javascript/flavours/glitch/components/relative_timestamp.tsx index 65d9d27cb2..e0e0d4bb53 100644 --- a/app/javascript/flavours/glitch/components/relative_timestamp.tsx +++ b/app/javascript/flavours/glitch/components/relative_timestamp.tsx @@ -1,5 +1,7 @@ import React from 'react'; -import { injectIntl, defineMessages, InjectedIntl } from 'react-intl'; + +import type { InjectedIntl } from 'react-intl'; +import { injectIntl, defineMessages } from 'react-intl'; const messages = defineMessages({ today: { id: 'relative_time.today', defaultMessage: 'today' }, @@ -187,16 +189,16 @@ const timeRemainingString = ( return relativeTime; }; -type Props = { +interface Props { intl: InjectedIntl; timestamp: string; year: number; futureDate?: boolean; short?: boolean; -}; -type States = { +} +interface States { now: number; -}; +} class RelativeTimestamp extends React.Component { state = { now: this.props.intl.now(), diff --git a/app/javascript/flavours/glitch/components/server_hero_image.tsx b/app/javascript/flavours/glitch/components/server_hero_image.tsx index d10b8a620b..973d1d1b37 100644 --- a/app/javascript/flavours/glitch/components/server_hero_image.tsx +++ b/app/javascript/flavours/glitch/components/server_hero_image.tsx @@ -1,13 +1,15 @@ import React, { useCallback, useState } from 'react'; -import { Blurhash } from './blurhash'; + import classNames from 'classnames'; -type Props = { +import { Blurhash } from './blurhash'; + +interface Props { src: string; srcSet?: string; blurhash?: string; className?: string; -}; +} export const ServerHeroImage: React.FC = ({ src, diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.jsx b/app/javascript/flavours/glitch/features/account_timeline/index.jsx index 0a4bb94888..3739ce23ca 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/account_timeline/index.jsx @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { lookupAccount, fetchAccount } from 'flavours/glitch/actions/accounts'; -import { expandAccountFeaturedTimeline, expandAccountTimeline } from 'flavours/glitch/actions/timelines'; +import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines'; import StatusList from '../../components/status_list'; import LoadingIndicator from '../../components/loading_indicator'; import Column from '../ui/components/column'; diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx b/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx index 458f48cde0..a466a3eb61 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx @@ -18,7 +18,7 @@ import { BookmarkedStatuses, ListTimeline, Directory, -} from '../../ui/util/async-components'; +} from '../util/async-components'; import ComposePanel from './compose_panel'; import NavigationPanel from './navigation_panel'; diff --git a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx index 2b61bad70e..250e781cf2 100644 --- a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; import classNames from 'classnames'; -import { changeUploadCompose, uploadThumbnail, onChangeMediaDescription, onChangeMediaFocus } from 'flavours/glitch/actions/compose'; +import { changeUploadCompose, uploadThumbnail, onChangeMediaDescription, onChangeMediaFocus } from '../../../actions/compose'; import Video, { getPointerPosition } from 'flavours/glitch/features/video'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { IconButton } from 'flavours/glitch/components/icon_button'; diff --git a/app/javascript/flavours/glitch/features/ui/components/upload_area.jsx b/app/javascript/flavours/glitch/features/ui/components/upload_area.jsx index 0e07b67f87..5b4d0ce60f 100644 --- a/app/javascript/flavours/glitch/features/ui/components/upload_area.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/upload_area.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Motion from '../../ui/util/optional_motion'; +import Motion from '../util/optional_motion'; import spring from 'react-motion/lib/spring'; import { FormattedMessage } from 'react-intl'; diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx index 4fc2372b37..15b94273db 100644 --- a/app/javascript/flavours/glitch/features/ui/index.jsx +++ b/app/javascript/flavours/glitch/features/ui/index.jsx @@ -64,7 +64,7 @@ import Header from './components/header'; // Dummy import, to make sure that ends up in the application bundle. // Without this it ends up in ~8 very commonly used bundles. -import '../../../glitch/components/status'; +import "../../components/status"; const messages = defineMessages({ beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' }, diff --git a/app/javascript/flavours/glitch/is_mobile.ts b/app/javascript/flavours/glitch/is_mobile.ts index c91ba4d53d..d69beb2402 100644 --- a/app/javascript/flavours/glitch/is_mobile.ts +++ b/app/javascript/flavours/glitch/is_mobile.ts @@ -1,4 +1,5 @@ import { supportsPassiveEvents } from 'detect-passive-events'; + import { forceSingleColumn } from 'flavours/glitch/initial_state'; const LAYOUT_BREAKPOINT = 630; diff --git a/app/javascript/flavours/glitch/packs/common.js b/app/javascript/flavours/glitch/packs/common.js index 7dc34eba93..a750c3668d 100644 --- a/app/javascript/flavours/glitch/packs/common.js +++ b/app/javascript/flavours/glitch/packs/common.js @@ -1,9 +1,9 @@ import 'packs/public-path'; import { start } from '@rails/ujs'; -start(); - import 'flavours/glitch/styles/index.scss'; +start(); + // This ensures that webpack compiles our images. require.context('../images', true); diff --git a/app/javascript/flavours/glitch/polyfills/base_polyfills.ts b/app/javascript/flavours/glitch/polyfills/base_polyfills.ts index 64211c11e9..e008d8f025 100644 --- a/app/javascript/flavours/glitch/polyfills/base_polyfills.ts +++ b/app/javascript/flavours/glitch/polyfills/base_polyfills.ts @@ -10,8 +10,13 @@ if (!HTMLCanvasElement.prototype.toBlob) { const BASE64_MARKER = ';base64,'; Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { - value(callback: BlobCallback, type = 'image/png', quality: any) { - const dataURL = this.toDataURL(type, quality); + value: function ( + this: HTMLCanvasElement, + callback: BlobCallback, + type = 'image/png', + quality: unknown + ) { + const dataURL: string = this.toDataURL(type, quality); let data; if (dataURL.indexOf(BASE64_MARKER) >= 0) { diff --git a/app/javascript/flavours/glitch/reducers/index.ts b/app/javascript/flavours/glitch/reducers/index.ts index da2174fc2c..eaef569f09 100644 --- a/app/javascript/flavours/glitch/reducers/index.ts +++ b/app/javascript/flavours/glitch/reducers/index.ts @@ -1,48 +1,49 @@ -import { combineReducers } from 'redux-immutable'; -import dropdown_menu from './dropdown_menu'; -import timelines from './timelines'; -import meta from './meta'; -import alerts from './alerts'; import { loadingBarReducer } from 'react-redux-loading-bar'; -import modal from './modal'; -import user_lists from './user_lists'; -import domain_lists from './domain_lists'; +import { combineReducers } from 'redux-immutable'; + +import account_notes from './account_notes'; import accounts from './accounts'; import accounts_counters from './accounts_counters'; -import statuses from './statuses'; -import relationships from './relationships'; -import settings from './settings'; -import local_settings from './local_settings'; -import push_notifications from './push_notifications'; -import status_lists from './status_lists'; -import mutes from './mutes'; +import accounts_map from './accounts_map'; +import alerts from './alerts'; +import announcements from './announcements'; import blocks from './blocks'; -import server from './server'; import boosts from './boosts'; -import contexts from './contexts'; import compose from './compose'; -import search from './search'; -import media_attachments from './media_attachments'; -import notifications from './notifications'; -import height_cache from './height_cache'; -import custom_emojis from './custom_emojis'; -import lists from './lists'; -import listEditor from './list_editor'; -import listAdder from './list_adder'; -import filters from './filters'; +import contexts from './contexts'; import conversations from './conversations'; -import suggestions from './suggestions'; +import custom_emojis from './custom_emojis'; +import domain_lists from './domain_lists'; +import dropdown_menu from './dropdown_menu'; +import filters from './filters'; +import followed_tags from './followed_tags'; +import height_cache from './height_cache'; +import history from './history'; +import listAdder from './list_adder'; +import listEditor from './list_editor'; +import lists from './lists'; +import local_settings from './local_settings'; +import markers from './markers'; +import media_attachments from './media_attachments'; +import meta from './meta'; +import modal from './modal'; +import mutes from './mutes'; +import notifications from './notifications'; +import picture_in_picture from './picture_in_picture'; import pinnedAccountsEditor from './pinned_accounts_editor'; import polls from './polls'; -import trends from './trends'; -import announcements from './announcements'; -import markers from './markers'; -import account_notes from './account_notes'; -import picture_in_picture from './picture_in_picture'; -import accounts_map from './accounts_map'; -import history from './history'; +import push_notifications from './push_notifications'; +import relationships from './relationships'; +import search from './search'; +import server from './server'; +import settings from './settings'; +import status_lists from './status_lists'; +import statuses from './statuses'; +import suggestions from './suggestions'; import tags from './tags'; -import followed_tags from './followed_tags'; +import timelines from './timelines'; +import trends from './trends'; +import user_lists from './user_lists'; const reducers = { announcements, diff --git a/app/javascript/flavours/glitch/reducers/markers.js b/app/javascript/flavours/glitch/reducers/markers.js index e3d1b1936b..3e8b1780a7 100644 --- a/app/javascript/flavours/glitch/reducers/markers.js +++ b/app/javascript/flavours/glitch/reducers/markers.js @@ -2,13 +2,13 @@ import { MARKERS_SUBMIT_SUCCESS, } from '../actions/markers'; +import { Map as ImmutableMap } from 'immutable'; + const initialState = ImmutableMap({ home: '0', notifications: '0', }); -import { Map as ImmutableMap } from 'immutable'; - export default function markers(state = initialState, action) { switch(action.type) { case MARKERS_SUBMIT_SUCCESS: diff --git a/app/javascript/flavours/glitch/store/index.ts b/app/javascript/flavours/glitch/store/index.ts index f7e1be6b7b..1ef9cb8184 100644 --- a/app/javascript/flavours/glitch/store/index.ts +++ b/app/javascript/flavours/glitch/store/index.ts @@ -1,9 +1,13 @@ +import type { TypedUseSelectorHook } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; + import { configureStore } from '@reduxjs/toolkit'; + import { rootReducer } from '../reducers'; -import { loadingBarMiddleware } from './middlewares/loading_bar'; + import { errorsMiddleware } from './middlewares/errors'; +import { loadingBarMiddleware } from './middlewares/loading_bar'; import { soundsMiddleware } from './middlewares/sounds'; -import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; export const store = configureStore({ reducer: rootReducer, diff --git a/app/javascript/flavours/glitch/store/middlewares/errors.ts b/app/javascript/flavours/glitch/store/middlewares/errors.ts index 5dec423af7..2697f80f18 100644 --- a/app/javascript/flavours/glitch/store/middlewares/errors.ts +++ b/app/javascript/flavours/glitch/store/middlewares/errors.ts @@ -1,17 +1,19 @@ -import { Middleware } from 'redux'; +import type { AnyAction, Middleware } from 'redux'; + import { showAlertForError } from 'flavours/glitch/actions/alerts'; -import { RootState } from '..'; + +import type { RootState } from '..'; const defaultFailSuffix = 'FAIL'; export const errorsMiddleware: Middleware, RootState> = ({ dispatch }) => (next) => - (action) => { + (action: AnyAction & { skipAlert?: boolean; skipNotFound?: boolean }) => { if (action.type && !action.skipAlert) { const isFail = new RegExp(`${defaultFailSuffix}$`, 'g'); - if (action.type.match(isFail)) { + if (typeof action.type === 'string' && action.type.match(isFail)) { dispatch(showAlertForError(action.error, action.skipNotFound)); } } diff --git a/app/javascript/flavours/glitch/store/middlewares/loading_bar.ts b/app/javascript/flavours/glitch/store/middlewares/loading_bar.ts index 183c0cf9d6..0f997fd349 100644 --- a/app/javascript/flavours/glitch/store/middlewares/loading_bar.ts +++ b/app/javascript/flavours/glitch/store/middlewares/loading_bar.ts @@ -1,6 +1,7 @@ import { showLoading, hideLoading } from 'react-redux-loading-bar'; -import { Middleware } from 'redux'; -import { RootState } from '..'; +import type { AnyAction, Middleware } from 'redux'; + +import type { RootState } from '..'; interface Config { promiseTypeSuffixes?: string[]; @@ -19,7 +20,7 @@ export const loadingBarMiddleware = ( return ({ dispatch }) => (next) => - (action) => { + (action: AnyAction) => { if (action.type && !action.skipLoading) { const [PENDING, FULFILLED, REJECTED] = promiseTypeSuffixes; @@ -27,13 +28,15 @@ export const loadingBarMiddleware = ( const isFulfilled = new RegExp(`${FULFILLED}$`, 'g'); const isRejected = new RegExp(`${REJECTED}$`, 'g'); - if (action.type.match(isPending)) { - dispatch(showLoading()); - } else if ( - action.type.match(isFulfilled) || - action.type.match(isRejected) - ) { - dispatch(hideLoading()); + if (typeof action.type === 'string') { + if (action.type.match(isPending)) { + dispatch(showLoading()); + } else if ( + action.type.match(isFulfilled) || + action.type.match(isRejected) + ) { + dispatch(hideLoading()); + } } } diff --git a/app/javascript/flavours/glitch/store/middlewares/sounds.ts b/app/javascript/flavours/glitch/store/middlewares/sounds.ts index e7c87df7ec..6005d3649e 100644 --- a/app/javascript/flavours/glitch/store/middlewares/sounds.ts +++ b/app/javascript/flavours/glitch/store/middlewares/sounds.ts @@ -1,5 +1,6 @@ -import { Middleware, AnyAction } from 'redux'; -import { RootState } from '..'; +import type { Middleware, AnyAction } from 'redux'; + +import type { RootState } from '..'; interface AudioSource { src: string; @@ -27,7 +28,7 @@ const play = (audio: HTMLAudioElement) => { } } - audio.play(); + void audio.play(); }; export const soundsMiddleware = (): Middleware< @@ -47,13 +48,15 @@ export const soundsMiddleware = (): Middleware< ]), }; - return () => (next) => (action: AnyAction) => { - const sound = action?.meta?.sound; + return () => + (next) => + (action: AnyAction & { meta?: { sound?: string } }) => { + const sound = action?.meta?.sound; - if (sound && soundCache[sound]) { - play(soundCache[sound]); - } + if (sound && soundCache[sound]) { + play(soundCache[sound]); + } - return next(action); - }; + return next(action); + }; }; diff --git a/app/javascript/flavours/glitch/types/resources.ts b/app/javascript/flavours/glitch/types/resources.ts index 0906504150..63ec2993bb 100644 --- a/app/javascript/flavours/glitch/types/resources.ts +++ b/app/javascript/flavours/glitch/types/resources.ts @@ -12,7 +12,7 @@ type AccountField = Record<{ verified_at: string | null; }>; -type AccountApiResponseValues = { +interface AccountApiResponseValues { acct: string; avatar: string; avatar_static: string; @@ -34,7 +34,7 @@ type AccountApiResponseValues = { statuses_count: number; url: string; username: string; -}; +} type NormalizedAccountField = Record<{ name_emojified: string; @@ -42,12 +42,12 @@ type NormalizedAccountField = Record<{ value_plain: string; }>; -type NormalizedAccountValues = { +interface NormalizedAccountValues { display_name_html: string; fields: NormalizedAccountField[]; note_emojified: string; note_plain: string; -}; +} export type Account = Record< AccountApiResponseValues & NormalizedAccountValues diff --git a/app/javascript/flavours/glitch/utils/resize_image.js b/app/javascript/flavours/glitch/utils/resize_image.js index fb8c3c11e6..e3d4e6a354 100644 --- a/app/javascript/flavours/glitch/utils/resize_image.js +++ b/app/javascript/flavours/glitch/utils/resize_image.js @@ -170,7 +170,7 @@ const resizeImage = (img, type = 'image/png') => new Promise((resolve, reject) = .catch(reject); }); -export default inputFile => new Promise((resolve) => { +const resizeFile = (inputFile) => new Promise((resolve) => { if (!inputFile.type.match(/image.*/) || inputFile.type === 'image/gif') { resolve(inputFile); return; @@ -187,3 +187,5 @@ export default inputFile => new Promise((resolve) => { .catch(() => resolve(inputFile)); }).catch(() => resolve(inputFile)); }); + +export default resizeFile; diff --git a/app/javascript/flavours/glitch/uuid.ts b/app/javascript/flavours/glitch/uuid.ts index 6cadbd6bb0..0b4d55beb6 100644 --- a/app/javascript/flavours/glitch/uuid.ts +++ b/app/javascript/flavours/glitch/uuid.ts @@ -1,8 +1,9 @@ export function uuid(a?: string): string { return a ? ( - (a as any as number) ^ - ((Math.random() * 16) >> ((a as any as number) / 4)) + (a as unknown as number) ^ + ((Math.random() * 16) >> ((a as unknown as number) / 4)) ).toString(16) - : ('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid); + : // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + ('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid); } diff --git a/tsconfig.json b/tsconfig.json index 022ab1f71e..d8f425f168 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,8 @@ "baseUrl": "./", "paths": { "locales": ["app/javascript/locales"], + "styles/*": ["app/javascript/styles/*"], + "packs/public-path": ["app/javascript/packs/public-path"], "flavours/glitch": ["app/javascript/flavours/glitch"], "flavours/glitch/*": ["app/javascript/flavours/glitch/*"], "mastodon": ["app/javascript/mastodon"], From 0042d5a2465f22e2476bf8a65ec5f9788546d25c Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 10 May 2023 17:22:34 +0200 Subject: [PATCH 88/98] [Glitch] Fix videos being improperly positioned on safari Port de95d0ca8ff2ea5292491202256b25aeefa1d5f1 to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/styles/components/media.scss | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss index a708d066ae..92633fc3fc 100644 --- a/app/javascript/flavours/glitch/styles/components/media.scss +++ b/app/javascript/flavours/glitch/styles/components/media.scss @@ -96,13 +96,6 @@ grid-column: span 2; } - &.standalone { - .media-gallery__item-gifv-thumbnail { - transform: none; - top: 0; - } - } - .full-width & { border-radius: 0; } @@ -161,8 +154,6 @@ cursor: zoom-in; height: 100%; width: 100%; - position: relative; - z-index: 1; object-fit: contain; user-select: none; @@ -455,6 +446,8 @@ border-radius: 4px; box-sizing: border-box; color: $white; + display: flex; + align-items: center; &.editable { border-radius: 0; @@ -497,9 +490,6 @@ &.inline { video { object-fit: contain; - position: relative; - top: 50%; - transform: translateY(-50%); } } From 12c177a34e24a40623772c1677a8613cc0a599f8 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 10 May 2023 20:17:55 +0200 Subject: [PATCH 89/98] [Glitch] Change "Sign in" to "Login" Port d05ff56746a80723dbb3416e72992674f44f1d28 to glitch-soc Signed-off-by: Claire --- .../glitch/components/not_signed_in_indicator.tsx | 2 +- .../flavours/glitch/features/interaction_modal/index.jsx | 2 +- .../flavours/glitch/features/ui/components/header.jsx | 6 +++--- .../glitch/features/ui/components/sign_in_banner.jsx | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/javascript/flavours/glitch/components/not_signed_in_indicator.tsx b/app/javascript/flavours/glitch/components/not_signed_in_indicator.tsx index 7a71f6539d..ce94c5d873 100644 --- a/app/javascript/flavours/glitch/components/not_signed_in_indicator.tsx +++ b/app/javascript/flavours/glitch/components/not_signed_in_indicator.tsx @@ -7,7 +7,7 @@ export const NotSignedInIndicator: React.FC = () => (
diff --git a/app/javascript/flavours/glitch/features/interaction_modal/index.jsx b/app/javascript/flavours/glitch/features/interaction_modal/index.jsx index 380edee659..515e47de2a 100644 --- a/app/javascript/flavours/glitch/features/interaction_modal/index.jsx +++ b/app/javascript/flavours/glitch/features/interaction_modal/index.jsx @@ -143,7 +143,7 @@ class InteractionModal extends React.PureComponent {

- + {signupButton}
diff --git a/app/javascript/flavours/glitch/features/ui/components/header.jsx b/app/javascript/flavours/glitch/features/ui/components/header.jsx index 519f917caa..3e56f9043a 100644 --- a/app/javascript/flavours/glitch/features/ui/components/header.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/header.jsx @@ -52,13 +52,13 @@ class Header extends React.PureComponent { if (registrationsOpen) { signupButton = ( - + ); } else { signupButton = ( - ); @@ -66,8 +66,8 @@ class Header extends React.PureComponent { content = ( <> - {signupButton} + ); } diff --git a/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx b/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx index c0d62aca00..86f96da9b1 100644 --- a/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx @@ -16,13 +16,13 @@ const SignInBanner = () => { if (registrationsOpen) { signupButton = ( - + ); } else { signupButton = ( - ); @@ -30,9 +30,9 @@ const SignInBanner = () => { return (
-

- +

{signupButton} +
); }; From 6cde4718c039925df02f3d6c62383b042c23894c Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Thu, 11 May 2023 12:41:55 +0200 Subject: [PATCH 90/98] [Glitch] Fix UI crash in moderation interface when opening the media modal Port 778fd4d29b00650a2be44efab69c7d6901834b45 to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/components/media_gallery.jsx | 2 +- .../flavours/glitch/components/status.jsx | 5 +++-- .../glitch/containers/media_container.jsx | 10 ++++++---- .../glitch/containers/status_container.js | 8 ++++---- .../glitch/features/account_gallery/index.jsx | 7 ++++--- .../containers/detailed_status_container.js | 8 ++++---- .../flavours/glitch/features/status/index.jsx | 8 ++++---- .../features/ui/components/media_modal.jsx | 16 ++++++---------- .../flavours/glitch/features/video/index.jsx | 2 +- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/app/javascript/flavours/glitch/components/media_gallery.jsx b/app/javascript/flavours/glitch/components/media_gallery.jsx index 3c63124493..33ddb8a29c 100644 --- a/app/javascript/flavours/glitch/components/media_gallery.jsx +++ b/app/javascript/flavours/glitch/components/media_gallery.jsx @@ -286,7 +286,7 @@ class MediaGallery extends React.PureComponent { }; handleClick = (index) => { - this.props.onOpenMedia(this.props.media, index); + this.props.onOpenMedia(this.props.media, index, this.props.lang); }; handleRef = (node) => { diff --git a/app/javascript/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index 78deb5f2aa..441936a431 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -388,11 +388,12 @@ class Status extends ImmutablePureComponent { handleOpenVideo = (options) => { const { status } = this.props; - this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options); + this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), status.get('language'), options); }; handleOpenMedia = (media, index) => { - this.props.onOpenMedia(this.props.status.get('id'), media, index); + const { status } = this.props; + this.props.onOpenMedia(status.get('id'), media, index, status.get('language')); }; handleHotkeyOpenMedia = e => { diff --git a/app/javascript/flavours/glitch/containers/media_container.jsx b/app/javascript/flavours/glitch/containers/media_container.jsx index f245e2f0a4..06db0a0030 100644 --- a/app/javascript/flavours/glitch/containers/media_container.jsx +++ b/app/javascript/flavours/glitch/containers/media_container.jsx @@ -29,19 +29,20 @@ export default class MediaContainer extends PureComponent { state = { media: null, index: null, + lang: null, time: null, backgroundColor: null, options: null, }; - handleOpenMedia = (media, index) => { + handleOpenMedia = (media, index, lang) => { document.body.classList.add('with-modals--active'); document.documentElement.style.marginRight = `${getScrollbarWidth()}px`; - this.setState({ media, index }); + this.setState({ media, index, lang }); }; - handleOpenVideo = (options) => { + handleOpenVideo = (lang, options) => { const { components } = this.props; const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props')); const mediaList = fromJS(media); @@ -49,7 +50,7 @@ export default class MediaContainer extends PureComponent { document.body.classList.add('with-modals--active'); document.documentElement.style.marginRight = `${getScrollbarWidth()}px`; - this.setState({ media: mediaList, options }); + this.setState({ media: mediaList, lang, options }); }; handleCloseMedia = () => { @@ -105,6 +106,7 @@ export default class MediaContainer extends PureComponent { ({ dispatch(mentionCompose(account, router)); }, - onOpenMedia (statusId, media, index) { - dispatch(openModal('MEDIA', { statusId, media, index })); + onOpenMedia (statusId, media, index, lang) { + dispatch(openModal('MEDIA', { statusId, media, index, lang })); }, - onOpenVideo (statusId, media, options) { - dispatch(openModal('VIDEO', { statusId, media, options })); + onOpenVideo (statusId, media, lang, options) { + dispatch(openModal('VIDEO', { statusId, media, lang, options })); }, onBlock (status) { diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.jsx b/app/javascript/flavours/glitch/features/account_gallery/index.jsx index b4fc8be721..26c45eff6d 100644 --- a/app/javascript/flavours/glitch/features/account_gallery/index.jsx +++ b/app/javascript/flavours/glitch/features/account_gallery/index.jsx @@ -142,16 +142,17 @@ class AccountGallery extends ImmutablePureComponent { handleOpenMedia = attachment => { const { dispatch } = this.props; const statusId = attachment.getIn(['status', 'id']); + const lang = attachment.getIn(['status', 'language']); if (attachment.get('type') === 'video') { - dispatch(openModal('VIDEO', { media: attachment, statusId, options: { autoPlay: true } })); + dispatch(openModal('VIDEO', { media: attachment, statusId, lang, options: { autoPlay: true } })); } else if (attachment.get('type') === 'audio') { - dispatch(openModal('AUDIO', { media: attachment, statusId, options: { autoPlay: true } })); + dispatch(openModal('AUDIO', { media: attachment, statusId, lang, options: { autoPlay: true } })); } else { const media = attachment.getIn(['status', 'media_attachments']); const index = media.findIndex(x => x.get('id') === attachment.get('id')); - dispatch(openModal('MEDIA', { media, index, statusId })); + dispatch(openModal('MEDIA', { media, index, statusId, lang })); } }; diff --git a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js index 25517a9f78..0b3e0a2f04 100644 --- a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js +++ b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js @@ -125,12 +125,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ dispatch(mentionCompose(account, router)); }, - onOpenMedia (media, index) { - dispatch(openModal('MEDIA', { media, index })); + onOpenMedia (media, index, lang) { + dispatch(openModal('MEDIA', { media, index, lang })); }, - onOpenVideo (media, options) { - dispatch(openModal('VIDEO', { media, options })); + onOpenVideo (media, lang, options) { + dispatch(openModal('VIDEO', { media, lang, options })); }, onBlock (status) { diff --git a/app/javascript/flavours/glitch/features/status/index.jsx b/app/javascript/flavours/glitch/features/status/index.jsx index 09c1aa9926..39a7780456 100644 --- a/app/javascript/flavours/glitch/features/status/index.jsx +++ b/app/javascript/flavours/glitch/features/status/index.jsx @@ -392,12 +392,12 @@ class Status extends ImmutablePureComponent { this.props.dispatch(mentionCompose(account, router)); }; - handleOpenMedia = (media, index) => { - this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index })); + handleOpenMedia = (media, index, lang) => { + this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index, lang })); }; - handleOpenVideo = (media, options) => { - this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, options })); + handleOpenVideo = (media, lang, options) => { + this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, lang, options })); }; handleHotkeyOpenMedia = e => { diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx index 1e51db0099..36d42d270a 100644 --- a/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx @@ -3,7 +3,6 @@ import ReactSwipeableViews from 'react-swipeable-views'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import Video from 'flavours/glitch/features/video'; -import { connect } from 'react-redux'; import classNames from 'classnames'; import { defineMessages, injectIntl } from 'react-intl'; import { IconButton } from 'flavours/glitch/components/icon_button'; @@ -21,10 +20,6 @@ const messages = defineMessages({ next: { id: 'lightbox.next', defaultMessage: 'Next' }, }); -const mapStateToProps = (state, { statusId }) => ({ - language: state.getIn(['statuses', statusId, 'language']), -}); - class MediaModal extends ImmutablePureComponent { static contextTypes = { @@ -34,6 +29,7 @@ class MediaModal extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.list.isRequired, statusId: PropTypes.string, + lang: PropTypes.string, index: PropTypes.number.isRequired, onClose: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, @@ -135,7 +131,7 @@ class MediaModal extends ImmutablePureComponent { } render () { - const { media, language, statusId, intl, onClose } = this.props; + const { media, statusId, lang, intl, onClose } = this.props; const { navigationHidden } = this.state; const index = this.getIndex(); @@ -155,7 +151,7 @@ class MediaModal extends ImmutablePureComponent { width={width} height={height} alt={image.get('description')} - lang={language} + lang={lang} key={image.get('url')} onClick={this.toggleNavigation} zoomButtonHidden={this.state.zoomButtonHidden} @@ -178,7 +174,7 @@ class MediaModal extends ImmutablePureComponent { onCloseVideo={onClose} detailed alt={image.get('description')} - lang={language} + lang={lang} key={image.get('url')} /> ); @@ -190,7 +186,7 @@ class MediaModal extends ImmutablePureComponent { height={height} key={image.get('url')} alt={image.get('description')} - lang={language} + lang={lang} onClick={this.toggleNavigation} /> ); @@ -258,4 +254,4 @@ class MediaModal extends ImmutablePureComponent { } -export default connect(mapStateToProps, null, null, { forwardRef: true })(injectIntl(MediaModal)); +export default injectIntl(MediaModal); diff --git a/app/javascript/flavours/glitch/features/video/index.jsx b/app/javascript/flavours/glitch/features/video/index.jsx index e70702ee74..8375ffe946 100644 --- a/app/javascript/flavours/glitch/features/video/index.jsx +++ b/app/javascript/flavours/glitch/features/video/index.jsx @@ -476,7 +476,7 @@ class Video extends React.PureComponent { handleOpenVideo = () => { this.video.pause(); - this.props.onOpenVideo({ + this.props.onOpenVideo(this.props.lang, { startTime: this.video.currentTime, autoPlay: !this.state.paused, defaultVolume: this.state.volume, From c3cca0eff4ff0ea5fa998b401704f1706d2a7273 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 16 May 2023 15:36:25 +0200 Subject: [PATCH 91/98] [Glitch] Fix being unable to load past a full page of filtered posts in Home timeline Port d2eef78d1c9689879fb81fc9dacc02b5d70220f9 to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/components/status_list.jsx | 4 +++- .../glitch/features/ui/containers/status_list_container.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/javascript/flavours/glitch/components/status_list.jsx b/app/javascript/flavours/glitch/components/status_list.jsx index a9c06f6932..a225883a82 100644 --- a/app/javascript/flavours/glitch/components/status_list.jsx +++ b/app/javascript/flavours/glitch/components/status_list.jsx @@ -26,6 +26,7 @@ export default class StatusList extends ImmutablePureComponent { alwaysPrepend: PropTypes.bool, withCounters: PropTypes.bool, timelineId: PropTypes.string.isRequired, + lastId: PropTypes.string, regex: PropTypes.string, }; @@ -56,7 +57,8 @@ export default class StatusList extends ImmutablePureComponent { }; handleLoadOlder = debounce(() => { - this.props.onLoadMore(this.props.statusIds.size > 0 ? this.props.statusIds.last() : undefined); + const { statusIds, lastId, onLoadMore } = this.props; + onLoadMore(lastId || (statusIds.size > 0 ? statusIds.last() : undefined)); }, 300, { leading: true }); _selectChild (index, align_top) { diff --git a/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js b/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js index 3cd0707f2d..92ddc01c86 100644 --- a/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js +++ b/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js @@ -60,6 +60,7 @@ const makeMapStateToProps = () => { const mapStateToProps = (state, { timelineId, regex }) => ({ statusIds: getStatusIds(state, { type: timelineId, regex }), + lastId: state.getIn(['timelines', timelineId, 'items'])?.last(), isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true), isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false), hasMore: state.getIn(['timelines', timelineId, 'hasMore']), From b55d56387bb522fb369887ee4786cd816b337b36 Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 25 May 2023 22:26:39 +0200 Subject: [PATCH 92/98] [Glitch] Add polling and automatic redirection to `/start` on email confirmation Port 86961b5b7b459fd778a9b697bbdfbda03512babb to glitch-soc --- app/controllers/auth/setup_controller.rb | 2 +- app/javascript/core/theme.yml | 1 + app/javascript/flavours/glitch/packs/sign_up.js | 15 +++++++++++++++ app/javascript/flavours/glitch/theme.yml | 1 + app/javascript/flavours/vanilla/theme.yml | 1 + app/views/auth/setup/show.html.haml | 2 -- 6 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 app/javascript/flavours/glitch/packs/sign_up.js diff --git a/app/controllers/auth/setup_controller.rb b/app/controllers/auth/setup_controller.rb index 3ee35d141c..8edca4d01b 100644 --- a/app/controllers/auth/setup_controller.rb +++ b/app/controllers/auth/setup_controller.rb @@ -45,6 +45,6 @@ class Auth::SetupController < ApplicationController end def set_pack - use_pack 'auth' + use_pack 'sign_up' end end diff --git a/app/javascript/core/theme.yml b/app/javascript/core/theme.yml index b9144e43aa..30676dcf58 100644 --- a/app/javascript/core/theme.yml +++ b/app/javascript/core/theme.yml @@ -16,4 +16,5 @@ pack: modal: public.js public: public.js settings: settings.js + sign_up: share: diff --git a/app/javascript/flavours/glitch/packs/sign_up.js b/app/javascript/flavours/glitch/packs/sign_up.js new file mode 100644 index 0000000000..ed03f1cf4b --- /dev/null +++ b/app/javascript/flavours/glitch/packs/sign_up.js @@ -0,0 +1,15 @@ +import 'packs/public-path'; +import ready from 'flavours/glitch/ready'; +import axios from 'axios'; + +ready(() => { + setInterval(() => { + axios.get('/api/v1/emails/check_confirmation').then((response) => { + if (response.data) { + window.location = '/start'; + } + }).catch(error => { + console.error(error); + }); + }, 5000); +}); diff --git a/app/javascript/flavours/glitch/theme.yml b/app/javascript/flavours/glitch/theme.yml index 672dd54406..0877e4d400 100644 --- a/app/javascript/flavours/glitch/theme.yml +++ b/app/javascript/flavours/glitch/theme.yml @@ -20,6 +20,7 @@ pack: modal: public: packs/public.jsx settings: packs/settings.js + sign_up: packs/sign_up.js share: packs/share.jsx # (OPTIONAL) The directory which contains localization files for diff --git a/app/javascript/flavours/vanilla/theme.yml b/app/javascript/flavours/vanilla/theme.yml index ccab925aa9..470ded5302 100644 --- a/app/javascript/flavours/vanilla/theme.yml +++ b/app/javascript/flavours/vanilla/theme.yml @@ -20,6 +20,7 @@ pack: modal: public: public.jsx settings: public.jsx + sign_up: sign_up.js share: share.jsx # (OPTIONAL) The directory which contains localization files for diff --git a/app/views/auth/setup/show.html.haml b/app/views/auth/setup/show.html.haml index 64deca3345..913b0c9132 100644 --- a/app/views/auth/setup/show.html.haml +++ b/app/views/auth/setup/show.html.haml @@ -1,8 +1,6 @@ - content_for :page_title do = t('auth.setup.title') -= javascript_pack_tag 'sign_up', crossorigin: 'anonymous' - = simple_form_for(@user, url: auth_setup_path) do |f| = render 'auth/shared/progress', stage: 'confirm' From 056dd396310702b2c171615eb0964437064f0ce0 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Mon, 22 May 2023 15:48:01 +0200 Subject: [PATCH 93/98] [Glitch] Upgrade to React 18 Port ab029328cf94940dce7ed26d005af3d96a857a59 to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/components/column.jsx | 10 ++++++---- .../flavours/glitch/components/dropdown_menu.jsx | 11 ++++++----- .../flavours/glitch/components/scrollable_list.jsx | 10 ++++++---- .../flavours/glitch/containers/media_container.jsx | 4 ++-- .../features/compose/components/dropdown_menu.jsx | 13 +++++++------ .../compose/components/emoji_picker_dropdown.jsx | 10 +++++----- .../compose/components/language_dropdown.jsx | 7 ++++--- app/javascript/flavours/glitch/main.jsx | 5 +++-- app/javascript/flavours/glitch/packs/admin.jsx | 10 ++++++---- app/javascript/flavours/glitch/packs/public.jsx | 5 +++-- app/javascript/flavours/glitch/packs/share.jsx | 7 ++++--- app/javascript/flavours/glitch/utils/dom_helpers.js | 4 ---- 12 files changed, 52 insertions(+), 44 deletions(-) diff --git a/app/javascript/flavours/glitch/components/column.jsx b/app/javascript/flavours/glitch/components/column.jsx index 47293ef18e..fce6a6c408 100644 --- a/app/javascript/flavours/glitch/components/column.jsx +++ b/app/javascript/flavours/glitch/components/column.jsx @@ -3,6 +3,8 @@ import PropTypes from 'prop-types'; import { supportsPassiveEvents } from 'detect-passive-events'; import { scrollTop } from '../scroll'; +const listenerOptions = supportsPassiveEvents ? { passive: true } : false; + export default class Column extends React.PureComponent { static propTypes = { @@ -37,17 +39,17 @@ export default class Column extends React.PureComponent { componentDidMount () { if (this.props.bindToDocument) { - document.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); + document.addEventListener('wheel', this.handleWheel, listenerOptions); } else { - this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); + this.node.addEventListener('wheel', this.handleWheel, listenerOptions); } } componentWillUnmount () { if (this.props.bindToDocument) { - document.removeEventListener('wheel', this.handleWheel); + document.removeEventListener('wheel', this.handleWheel, listenerOptions); } else { - this.node.removeEventListener('wheel', this.handleWheel); + this.node.removeEventListener('wheel', this.handleWheel, listenerOptions); } } diff --git a/app/javascript/flavours/glitch/components/dropdown_menu.jsx b/app/javascript/flavours/glitch/components/dropdown_menu.jsx index 3f17bc1299..901c7fe22f 100644 --- a/app/javascript/flavours/glitch/components/dropdown_menu.jsx +++ b/app/javascript/flavours/glitch/components/dropdown_menu.jsx @@ -7,7 +7,7 @@ import { supportsPassiveEvents } from 'detect-passive-events'; import classNames from 'classnames'; import { CircularProgress } from 'flavours/glitch/components/loading_indicator'; -const listenerOptions = supportsPassiveEvents ? { passive: true } : false; +const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; let id = 0; class DropdownMenu extends React.PureComponent { @@ -35,12 +35,13 @@ class DropdownMenu extends React.PureComponent { handleDocumentClick = e => { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); + e.stopPropagation(); } }; componentDidMount () { - document.addEventListener('click', this.handleDocumentClick, false); - document.addEventListener('keydown', this.handleKeyDown, false); + document.addEventListener('click', this.handleDocumentClick, { capture: true }); + document.addEventListener('keydown', this.handleKeyDown, { capture: true }); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); if (this.focusedItem && this.props.openedViaKeyboard) { @@ -49,8 +50,8 @@ class DropdownMenu extends React.PureComponent { } componentWillUnmount () { - document.removeEventListener('click', this.handleDocumentClick, false); - document.removeEventListener('keydown', this.handleKeyDown, false); + document.removeEventListener('click', this.handleDocumentClick, { capture: true }); + document.removeEventListener('keydown', this.handleKeyDown, { capture: true }); document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); } diff --git a/app/javascript/flavours/glitch/components/scrollable_list.jsx b/app/javascript/flavours/glitch/components/scrollable_list.jsx index 13701bcd3e..69925321bd 100644 --- a/app/javascript/flavours/glitch/components/scrollable_list.jsx +++ b/app/javascript/flavours/glitch/components/scrollable_list.jsx @@ -15,6 +15,8 @@ import { connect } from 'react-redux'; const MOUSE_IDLE_DELAY = 300; +const listenerOptions = supportsPassiveEvents ? { passive: true } : false; + const mapStateToProps = (state, { scrollKey }) => { return { preventScroll: scrollKey === state.getIn(['dropdown_menu', 'scroll_key']), @@ -237,20 +239,20 @@ class ScrollableList extends PureComponent { attachScrollListener () { if (this.props.bindToDocument) { document.addEventListener('scroll', this.handleScroll); - document.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : undefined); + document.addEventListener('wheel', this.handleWheel, listenerOptions); } else { this.node.addEventListener('scroll', this.handleScroll); - this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : undefined); + this.node.addEventListener('wheel', this.handleWheel, listenerOptions); } } detachScrollListener () { if (this.props.bindToDocument) { document.removeEventListener('scroll', this.handleScroll); - document.removeEventListener('wheel', this.handleWheel); + document.removeEventListener('wheel', this.handleWheel, listenerOptions); } else { this.node.removeEventListener('scroll', this.handleScroll); - this.node.removeEventListener('wheel', this.handleWheel); + this.node.removeEventListener('wheel', this.handleWheel, listenerOptions); } } diff --git a/app/javascript/flavours/glitch/containers/media_container.jsx b/app/javascript/flavours/glitch/containers/media_container.jsx index 06db0a0030..99e1ed55c3 100644 --- a/app/javascript/flavours/glitch/containers/media_container.jsx +++ b/app/javascript/flavours/glitch/containers/media_container.jsx @@ -1,5 +1,5 @@ import React, { PureComponent, Fragment } from 'react'; -import ReactDOM from 'react-dom'; +import { createPortal } from 'react-dom'; import PropTypes from 'prop-types'; import { IntlProvider, addLocaleData } from 'react-intl'; import { fromJS } from 'immutable'; @@ -95,7 +95,7 @@ export default class MediaContainer extends PureComponent { }), }); - return ReactDOM.createPortal( + return createPortal( , component, ); diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.jsx b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.jsx index 786f593070..28f316619c 100644 --- a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.jsx @@ -2,12 +2,12 @@ import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; +import { supportsPassiveEvents } from 'detect-passive-events'; // Components. import { Icon } from 'flavours/glitch/components/icon'; -// Utils. -import { withPassive } from 'flavours/glitch/utils/dom_helpers'; +const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; // The component. export default class ComposerOptionsDropdownContent extends React.PureComponent { @@ -41,6 +41,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent handleDocumentClick = (e) => { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); + e.stopPropagation(); } }; @@ -51,8 +52,8 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent // On mounting, we add our listeners. componentDidMount () { - document.addEventListener('click', this.handleDocumentClick, false); - document.addEventListener('touchend', this.handleDocumentClick, withPassive); + document.addEventListener('click', this.handleDocumentClick, { capture: true }); + document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); if (this.focusedItem) { this.focusedItem.focus({ preventScroll: true }); } else { @@ -62,8 +63,8 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent // On unmounting, we remove our listeners. componentWillUnmount () { - document.removeEventListener('click', this.handleDocumentClick, false); - document.removeEventListener('touchend', this.handleDocumentClick, withPassive); + document.removeEventListener('click', this.handleDocumentClick, { capture: true }); + document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); } handleClick = (e) => { diff --git a/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.jsx b/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.jsx index 7526899305..68ff37b80e 100644 --- a/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.jsx @@ -28,7 +28,7 @@ const messages = defineMessages({ let EmojiPicker, Emoji; // load asynchronously -const listenerOptions = supportsPassiveEvents ? { passive: true } : false; +const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; const backgroundImageFn = () => `${assetHost}/emoji/sheet_13.png`; @@ -79,12 +79,12 @@ class ModifierPickerMenu extends React.PureComponent { }; attachListeners () { - document.addEventListener('click', this.handleDocumentClick, false); + document.addEventListener('click', this.handleDocumentClick, { capture: true }); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); } removeListeners () { - document.removeEventListener('click', this.handleDocumentClick, false); + document.removeEventListener('click', this.handleDocumentClick, { capture: true }); document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); } @@ -177,7 +177,7 @@ class EmojiPickerMenuImpl extends React.PureComponent { }; componentDidMount () { - document.addEventListener('click', this.handleDocumentClick, false); + document.addEventListener('click', this.handleDocumentClick, { capture: true }); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need @@ -192,7 +192,7 @@ class EmojiPickerMenuImpl extends React.PureComponent { } componentWillUnmount () { - document.removeEventListener('click', this.handleDocumentClick, false); + document.removeEventListener('click', this.handleDocumentClick, { capture: true }); document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); } diff --git a/app/javascript/flavours/glitch/features/compose/components/language_dropdown.jsx b/app/javascript/flavours/glitch/features/compose/components/language_dropdown.jsx index 05614de011..2d4ae72d40 100644 --- a/app/javascript/flavours/glitch/features/compose/components/language_dropdown.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/language_dropdown.jsx @@ -15,7 +15,7 @@ const messages = defineMessages({ clear: { id: 'emoji_button.clear', defaultMessage: 'Clear' }, }); -const listenerOptions = supportsPassiveEvents ? { passive: true } : false; +const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; class LanguageDropdownMenu extends React.PureComponent { @@ -39,11 +39,12 @@ class LanguageDropdownMenu extends React.PureComponent { handleDocumentClick = e => { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); + e.stopPropagation(); } }; componentDidMount () { - document.addEventListener('click', this.handleDocumentClick, false); + document.addEventListener('click', this.handleDocumentClick, { capture: true }); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need @@ -57,7 +58,7 @@ class LanguageDropdownMenu extends React.PureComponent { } componentWillUnmount () { - document.removeEventListener('click', this.handleDocumentClick, false); + document.removeEventListener('click', this.handleDocumentClick, { capture: true }); document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); } diff --git a/app/javascript/flavours/glitch/main.jsx b/app/javascript/flavours/glitch/main.jsx index 1ac7d579ec..63195518be 100644 --- a/app/javascript/flavours/glitch/main.jsx +++ b/app/javascript/flavours/glitch/main.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { setupBrowserNotifications } from 'flavours/glitch/actions/notifications'; import Mastodon from 'flavours/glitch/containers/mastodon'; import { store } from 'flavours/glitch/store'; @@ -18,7 +18,8 @@ function main() { const mountNode = document.getElementById('mastodon'); const props = JSON.parse(mountNode.getAttribute('data-props')); - ReactDOM.render(, mountNode); + const root = createRoot(mountNode); + root.render(); store.dispatch(setupBrowserNotifications()); if (process.env.NODE_ENV === 'production' && me && 'serviceWorker' in navigator) { diff --git a/app/javascript/flavours/glitch/packs/admin.jsx b/app/javascript/flavours/glitch/packs/admin.jsx index 7544ae4e97..7b0917df85 100644 --- a/app/javascript/flavours/glitch/packs/admin.jsx +++ b/app/javascript/flavours/glitch/packs/admin.jsx @@ -1,7 +1,7 @@ import 'packs/public-path'; import ready from 'flavours/glitch/ready'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; ready(() => { [].forEach.call(document.querySelectorAll('[data-admin-component]'), element => { @@ -10,11 +10,13 @@ ready(() => { import('flavours/glitch/containers/admin_component').then(({ default: AdminComponent }) => { return import('flavours/glitch/components/admin/' + componentName).then(({ default: Component }) => { - ReactDOM.render(( + const root = createRoot(element); + + root.render ( - - ), element); + , + ); }); }).catch(error => { console.error(error); diff --git a/app/javascript/flavours/glitch/packs/public.jsx b/app/javascript/flavours/glitch/packs/public.jsx index 34a8f35133..7bb85fd2f9 100644 --- a/app/javascript/flavours/glitch/packs/public.jsx +++ b/app/javascript/flavours/glitch/packs/public.jsx @@ -11,7 +11,7 @@ import { delegate } from '@rails/ujs'; import emojify from 'flavours/glitch/features/emoji/emoji'; import { getLocale } from 'locales'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { createBrowserHistory } from 'history'; const messages = defineMessages({ @@ -130,7 +130,8 @@ function main() { const content = document.createElement('div'); - ReactDOM.render(, content); + const root = createRoot(content); + root.render(); document.body.appendChild(content); scrollToDetailedStatus(); }) diff --git a/app/javascript/flavours/glitch/packs/share.jsx b/app/javascript/flavours/glitch/packs/share.jsx index 47a51a5b01..a835058ac8 100644 --- a/app/javascript/flavours/glitch/packs/share.jsx +++ b/app/javascript/flavours/glitch/packs/share.jsx @@ -1,9 +1,9 @@ import 'packs/public-path'; import { loadPolyfills } from 'flavours/glitch/polyfills'; +import ready from 'flavours/glitch/ready'; import ComposeContainer from 'flavours/glitch/containers/compose_container'; import React from 'react'; -import ReactDOM from 'react-dom'; -import ready from 'flavours/glitch/ready'; +import { createRoot } from 'react-dom/client'; function loaded() { const mountNode = document.getElementById('mastodon-compose'); @@ -13,7 +13,8 @@ function loaded() { if(!attr) return; const props = JSON.parse(attr); - ReactDOM.render(, mountNode); + const root = createRoot(mountNode); + root.render(); } } diff --git a/app/javascript/flavours/glitch/utils/dom_helpers.js b/app/javascript/flavours/glitch/utils/dom_helpers.js index d94aeb9d47..d9796954a3 100644 --- a/app/javascript/flavours/glitch/utils/dom_helpers.js +++ b/app/javascript/flavours/glitch/utils/dom_helpers.js @@ -1,10 +1,6 @@ // Package imports. import { supportsPassiveEvents } from 'detect-passive-events'; -// This will either be a passive lister options object (if passive -// events are supported), or `false`. -export const withPassive = supportsPassiveEvents ? { passive: true } : false; - // Focuses the root element. export function focusRoot () { let e; From 758a0ae1e9ae5884d7def78c56915f785f0aad7b Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 22 May 2023 21:18:21 +0200 Subject: [PATCH 94/98] [Glitch] Allow scripts in post embed previews Port 18361ae0c53b462fa32c0e178cbd94a58a62f624 to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/features/ui/components/embed_modal.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/flavours/glitch/features/ui/components/embed_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/embed_modal.jsx index 1c83517a17..7c961fa57e 100644 --- a/app/javascript/flavours/glitch/features/ui/components/embed_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/embed_modal.jsx @@ -85,7 +85,7 @@ class EmbedModal extends ImmutablePureComponent { className='embed-modal__iframe' frameBorder='0' ref={this.setIframeRef} - sandbox='allow-same-origin' + sandbox='allow-scripts allow-same-origin' title='preview' />
From 76e93863327bd01fe91495797cd8656e76c5e003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=84=E3=81=A1=20=E3=81=B2?= Date: Tue, 23 May 2023 15:58:08 +0900 Subject: [PATCH 95/98] [Glitch] Rewrite `` as FC and TS Port 0b87919abfdf288412062342307bae3695e71f8b to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/components/account.jsx | 2 +- .../flavours/glitch/components/admin/Counter.jsx | 2 +- .../flavours/glitch/components/admin/Dimension.jsx | 2 +- .../flavours/glitch/components/display_name.tsx | 2 +- .../flavours/glitch/components/hashtag.jsx | 2 +- .../flavours/glitch/components/server_banner.jsx | 2 +- .../flavours/glitch/components/skeleton.jsx | 11 ----------- .../flavours/glitch/components/skeleton.tsx | 12 ++++++++++++ .../flavours/glitch/features/about/index.jsx | 2 +- .../glitch/features/explore/components/story.jsx | 2 +- .../glitch/features/privacy_policy/index.jsx | 2 +- 11 files changed, 21 insertions(+), 20 deletions(-) delete mode 100644 app/javascript/flavours/glitch/components/skeleton.jsx create mode 100644 app/javascript/flavours/glitch/components/skeleton.tsx diff --git a/app/javascript/flavours/glitch/components/account.jsx b/app/javascript/flavours/glitch/components/account.jsx index 66b7e3e4a3..35f83beab1 100644 --- a/app/javascript/flavours/glitch/components/account.jsx +++ b/app/javascript/flavours/glitch/components/account.jsx @@ -9,7 +9,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { me } from 'flavours/glitch/initial_state'; import { RelativeTimestamp } from './relative_timestamp'; -import Skeleton from 'flavours/glitch/components/skeleton'; +import { Skeleton } from 'flavours/glitch/components/skeleton'; const messages = defineMessages({ follow: { id: 'account.follow', defaultMessage: 'Follow' }, diff --git a/app/javascript/flavours/glitch/components/admin/Counter.jsx b/app/javascript/flavours/glitch/components/admin/Counter.jsx index 5b6a19f8da..9c0b848d0b 100644 --- a/app/javascript/flavours/glitch/components/admin/Counter.jsx +++ b/app/javascript/flavours/glitch/components/admin/Counter.jsx @@ -4,7 +4,7 @@ import api from 'flavours/glitch/api'; import { FormattedNumber } from 'react-intl'; import { Sparklines, SparklinesCurve } from 'react-sparklines'; import classNames from 'classnames'; -import Skeleton from 'flavours/glitch/components/skeleton'; +import { Skeleton } from 'flavours/glitch/components/skeleton'; const percIncrease = (a, b) => { let percent; diff --git a/app/javascript/flavours/glitch/components/admin/Dimension.jsx b/app/javascript/flavours/glitch/components/admin/Dimension.jsx index 3dac8c6c24..8b8466ee78 100644 --- a/app/javascript/flavours/glitch/components/admin/Dimension.jsx +++ b/app/javascript/flavours/glitch/components/admin/Dimension.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import api from 'flavours/glitch/api'; import { FormattedNumber } from 'react-intl'; import { roundTo10 } from 'flavours/glitch/utils/numbers'; -import Skeleton from 'flavours/glitch/components/skeleton'; +import { Skeleton } from 'flavours/glitch/components/skeleton'; export default class Dimension extends React.PureComponent { diff --git a/app/javascript/flavours/glitch/components/display_name.tsx b/app/javascript/flavours/glitch/components/display_name.tsx index 4dfc51c785..7224ac3d73 100644 --- a/app/javascript/flavours/glitch/components/display_name.tsx +++ b/app/javascript/flavours/glitch/components/display_name.tsx @@ -8,7 +8,7 @@ import type { Account } from 'flavours/glitch/types/resources'; import { autoPlayGif } from '../initial_state'; -import Skeleton from './skeleton'; +import { Skeleton } from './skeleton'; interface Props { account: Account; diff --git a/app/javascript/flavours/glitch/components/hashtag.jsx b/app/javascript/flavours/glitch/components/hashtag.jsx index 235aaeb3c1..ced250b79a 100644 --- a/app/javascript/flavours/glitch/components/hashtag.jsx +++ b/app/javascript/flavours/glitch/components/hashtag.jsx @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Permalink from './permalink'; import ShortNumber from 'flavours/glitch/components/short_number'; -import Skeleton from 'flavours/glitch/components/skeleton'; +import { Skeleton } from 'flavours/glitch/components/skeleton'; import classNames from 'classnames'; class SilentErrorBoundary extends React.Component { diff --git a/app/javascript/flavours/glitch/components/server_banner.jsx b/app/javascript/flavours/glitch/components/server_banner.jsx index ec264f1f1b..25ec405603 100644 --- a/app/javascript/flavours/glitch/components/server_banner.jsx +++ b/app/javascript/flavours/glitch/components/server_banner.jsx @@ -4,7 +4,7 @@ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { fetchServer } from 'flavours/glitch/actions/server'; import ShortNumber from 'flavours/glitch/components/short_number'; -import Skeleton from 'flavours/glitch/components/skeleton'; +import { Skeleton } from 'flavours/glitch/components/skeleton'; import Account from 'flavours/glitch/containers/account_container'; import { domain } from 'flavours/glitch/initial_state'; import { ServerHeroImage } from 'flavours/glitch/components/server_hero_image'; diff --git a/app/javascript/flavours/glitch/components/skeleton.jsx b/app/javascript/flavours/glitch/components/skeleton.jsx deleted file mode 100644 index 6a17ffb261..0000000000 --- a/app/javascript/flavours/glitch/components/skeleton.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const Skeleton = ({ width, height }) => ; - -Skeleton.propTypes = { - width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), -}; - -export default Skeleton; diff --git a/app/javascript/flavours/glitch/components/skeleton.tsx b/app/javascript/flavours/glitch/components/skeleton.tsx new file mode 100644 index 0000000000..8d43e6827d --- /dev/null +++ b/app/javascript/flavours/glitch/components/skeleton.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +interface Props { + width?: number | string; + height?: number | string; +} + +export const Skeleton: React.FC = ({ width, height }) => ( + + ‌ + +); diff --git a/app/javascript/flavours/glitch/features/about/index.jsx b/app/javascript/flavours/glitch/features/about/index.jsx index 8ef05d1f8a..8b65d95716 100644 --- a/app/javascript/flavours/glitch/features/about/index.jsx +++ b/app/javascript/flavours/glitch/features/about/index.jsx @@ -8,7 +8,7 @@ import LinkFooter from 'flavours/glitch/features/ui/components/link_footer'; import { Helmet } from 'react-helmet'; import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server'; import Account from 'flavours/glitch/containers/account_container'; -import Skeleton from 'flavours/glitch/components/skeleton'; +import { Skeleton } from 'flavours/glitch/components/skeleton'; import { Icon } from 'flavours/glitch/components/icon'; import classNames from 'classnames'; import { ServerHeroImage } from 'flavours/glitch/components/server_hero_image'; diff --git a/app/javascript/flavours/glitch/features/explore/components/story.jsx b/app/javascript/flavours/glitch/features/explore/components/story.jsx index d70914d41a..cadc710d4d 100644 --- a/app/javascript/flavours/glitch/features/explore/components/story.jsx +++ b/app/javascript/flavours/glitch/features/explore/components/story.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { Blurhash } from 'flavours/glitch/components/blurhash'; import { accountsCountRenderer } from 'flavours/glitch/components/hashtag'; import ShortNumber from 'flavours/glitch/components/short_number'; -import Skeleton from 'flavours/glitch/components/skeleton'; +import { Skeleton } from 'flavours/glitch/components/skeleton'; import classNames from 'classnames'; export default class Story extends React.PureComponent { diff --git a/app/javascript/flavours/glitch/features/privacy_policy/index.jsx b/app/javascript/flavours/glitch/features/privacy_policy/index.jsx index a43befa73e..44cb9b0d0a 100644 --- a/app/javascript/flavours/glitch/features/privacy_policy/index.jsx +++ b/app/javascript/flavours/glitch/features/privacy_policy/index.jsx @@ -4,7 +4,7 @@ import { Helmet } from 'react-helmet'; import { FormattedMessage, FormattedDate, injectIntl, defineMessages } from 'react-intl'; import Column from 'flavours/glitch/components/column'; import api from 'flavours/glitch/api'; -import Skeleton from 'flavours/glitch/components/skeleton'; +import { Skeleton } from 'flavours/glitch/components/skeleton'; const messages = defineMessages({ title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' }, From 114ad2d4d1db29be439eb1c9aa50e30281c571b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=9F=E3=81=84=E3=81=A1=20=E3=81=B2?= Date: Tue, 23 May 2023 17:04:10 +0900 Subject: [PATCH 96/98] [Glitch] Rewrite `` as FC and TS Port 9cfab995900d9454171720b65e7174b8cfdf4b51 to glitch-soc Signed-off-by: Claire --- .../glitch/components/timeline_hint.jsx | 18 ------------- .../glitch/components/timeline_hint.tsx | 27 +++++++++++++++++++ .../features/account_timeline/index.jsx | 2 +- .../glitch/features/followers/index.jsx | 2 +- .../glitch/features/following/index.jsx | 2 +- 5 files changed, 30 insertions(+), 21 deletions(-) delete mode 100644 app/javascript/flavours/glitch/components/timeline_hint.jsx create mode 100644 app/javascript/flavours/glitch/components/timeline_hint.tsx diff --git a/app/javascript/flavours/glitch/components/timeline_hint.jsx b/app/javascript/flavours/glitch/components/timeline_hint.jsx deleted file mode 100644 index fb55a62cca..0000000000 --- a/app/javascript/flavours/glitch/components/timeline_hint.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; - -const TimelineHint = ({ resource, url }) => ( -
- -
- -
-); - -TimelineHint.propTypes = { - resource: PropTypes.node.isRequired, - url: PropTypes.string.isRequired, -}; - -export default TimelineHint; diff --git a/app/javascript/flavours/glitch/components/timeline_hint.tsx b/app/javascript/flavours/glitch/components/timeline_hint.tsx new file mode 100644 index 0000000000..712b4c293b --- /dev/null +++ b/app/javascript/flavours/glitch/components/timeline_hint.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import { FormattedMessage } from 'react-intl'; + +interface Props { + resource: JSX.Element; + url: string; +} + +export const TimelineHint: React.FC = ({ resource, url }) => ( +
+ + + +
+ + + +
+); diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.jsx b/app/javascript/flavours/glitch/features/account_timeline/index.jsx index 3739ce23ca..b90918a01c 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/account_timeline/index.jsx @@ -12,7 +12,7 @@ import HeaderContainer from './containers/header_container'; import { List as ImmutableList } from 'immutable'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { FormattedMessage } from 'react-intl'; -import TimelineHint from 'flavours/glitch/components/timeline_hint'; +import { TimelineHint } from 'flavours/glitch/components/timeline_hint'; import LimitedAccountHint from './components/limited_account_hint'; import { getAccountHidden } from 'flavours/glitch/selectors'; import { fetchFeaturedTags } from '../../actions/featured_tags'; diff --git a/app/javascript/flavours/glitch/features/followers/index.jsx b/app/javascript/flavours/glitch/features/followers/index.jsx index 2c4db665af..39eaed1285 100644 --- a/app/javascript/flavours/glitch/features/followers/index.jsx +++ b/app/javascript/flavours/glitch/features/followers/index.jsx @@ -17,7 +17,7 @@ import ProfileColumnHeader from 'flavours/glitch/features/account/components/pro import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ScrollableList from 'flavours/glitch/components/scrollable_list'; -import TimelineHint from 'flavours/glitch/components/timeline_hint'; +import { TimelineHint } from 'flavours/glitch/components/timeline_hint'; import LimitedAccountHint from '../account_timeline/components/limited_account_hint'; import { getAccountHidden } from 'flavours/glitch/selectors'; import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map'; diff --git a/app/javascript/flavours/glitch/features/following/index.jsx b/app/javascript/flavours/glitch/features/following/index.jsx index 39ff4c6032..39f87eef9c 100644 --- a/app/javascript/flavours/glitch/features/following/index.jsx +++ b/app/javascript/flavours/glitch/features/following/index.jsx @@ -17,7 +17,7 @@ import ProfileColumnHeader from 'flavours/glitch/features/account/components/pro import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ScrollableList from 'flavours/glitch/components/scrollable_list'; -import TimelineHint from 'flavours/glitch/components/timeline_hint'; +import { TimelineHint } from 'flavours/glitch/components/timeline_hint'; import LimitedAccountHint from '../account_timeline/components/limited_account_hint'; import { getAccountHidden } from 'flavours/glitch/selectors'; import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map'; From ab642ee227c3e3656798f38e8f7d60baa5431d35 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 26 May 2023 00:10:57 +0200 Subject: [PATCH 97/98] Fix glitch-soc-only tests being broken because of test refactor --- spec/policies/status_policy_spec.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb index a278bb4e84..725bd0bbb3 100644 --- a/spec/policies/status_policy_spec.rb +++ b/spec/policies/status_policy_spec.rb @@ -84,18 +84,18 @@ RSpec.describe StatusPolicy, type: :model do expect(subject).to_not permit(viewer, status) end - end - it 'denies access when local-only and the viewer is not logged in' do - allow(status).to receive(:local_only?).and_return(true) + it 'denies access when local-only and the viewer is not logged in' do + allow(status).to receive(:local_only?).and_return(true) - expect(subject).to_not permit(nil, status) - end + expect(subject).to_not permit(nil, status) + end - it 'denies access when local-only and the viewer is from another domain' do - viewer = Fabricate(:account, domain: 'remote-domain') - allow(status).to receive(:local_only?).and_return(true) - expect(subject).to_not permit(viewer, status) + it 'denies access when local-only and the viewer is from another domain' do + viewer = Fabricate(:account, domain: 'remote-domain') + allow(status).to receive(:local_only?).and_return(true) + expect(subject).to_not permit(viewer, status) + end end end From 20e8b117e333468275909e57d58d97a7daecb8cb Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 26 May 2023 18:44:18 +0200 Subject: [PATCH 98/98] fixup! [Glitch] Upgrade to React 18 --- app/javascript/flavours/glitch/utils/dom_helpers.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/javascript/flavours/glitch/utils/dom_helpers.js b/app/javascript/flavours/glitch/utils/dom_helpers.js index d9796954a3..bc5e200fcc 100644 --- a/app/javascript/flavours/glitch/utils/dom_helpers.js +++ b/app/javascript/flavours/glitch/utils/dom_helpers.js @@ -1,6 +1,3 @@ -// Package imports. -import { supportsPassiveEvents } from 'detect-passive-events'; - // Focuses the root element. export function focusRoot () { let e;