From ab7716cff4a1f590565b6a229282e899d7d02e96 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 991907a3f..9669decc8 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 490543424..d10b8a620 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 11aff67c6..f025a9633 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 349cae0b57ee65475a70311863959e7a0cf53065 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 0d040c4cd..afb6c4758 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 b0bbea7ce..f110bce7a 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 1dd9fb1d6..000000000 --- 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 000000000..0452dba79 --- /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 3ca840b72..8cd322eda 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 273583c0a..29861612c 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 2c92016e8..a635657d9 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 68cd7b080..b3f1b1b48 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 495142715..1ef9d6481 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 b370b33ec..5d0632b0f 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 410f1537a..5dc384aba 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 b46d0504a..fc1d2d607 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 0d930f0b1..d8b2ca1cc 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 c6d2a103d..c1c04da54 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 003cdc8e3..8d6091f77 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 72c902124..5019dfdb4 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 1f28927d6..4eeb30be0 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 6f8db56a01c772bb2ec11bdb67aa2997eb62837e 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 6c3e963d9..f7e1be6b7 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 2d5e2579389fb2bb41e98fd3502d760a33342908 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 60e8f40b2..c3f409947 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 ee1a83cc6..b62d216ae 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 b878e3d8dfaf3cf61f7af9d16afbe9c89b91ab62 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 9e965791b..cbac530d6 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 a68e2a01b..218faabb7 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 a627bc1ec..50cc24b00 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 54b414de2..1eeb63fa4 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 c0525c221..7671d2725 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 5201ebd4d..9a81b0aee 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 56e913ae3..5ed02d937 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 38616ba84..da28f12d7 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 46fff856e..1ffe7e768 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 4fb131b47..095e33cf7 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 ecb7accad..bbc4b0d06 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 0c22aa239..6a9f6e4cf 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 2cbf00291..161297114 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 4a8c1deb6..ed210dad5 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 a8875bbd3..779bc473e 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 c93305341..e1408d8ef 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 afd645a30..232b0c2d5 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 5c05d2f70..078d8779e 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 8a31c5db6..8b77374c4 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 24a0b6d2e..e58ce2bb8 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 fb503f40f..757ef0dd0 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 1d5edfc17..bc0266531 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 0c0b55748..e77d687e8 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 1b10a218b..c1e837b16 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 1dd6e34e8..90be614b1 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 6dc5177b5..26a777732 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 8b57cf3d1..3d9d1aec6 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 5eeb40bdbe4d5e7c5c60788c0e10311f4d125853 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 cbac530d6..1800daa55 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 50fd317a6..be1a5cced 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 e2de98ca9..1e4bd37bf 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 f6c77d35f..b6b073161 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 8be94d3f5..2b46b05d6 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 1dbd53323..d1d158126 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 700513676..1550d0b7a 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 0452dba79..d3308ff21 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 af0fec35a..9e8e04b65 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 72914ba74..c606a2904 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 4eb948dc7..6bd15da6a 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 178641400..c995ed0eb 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 bf86814c0..e427b7172 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 c3f409947..6594ef1fd 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 53945d6a7..7a71f6539 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 829f47174..67acb09f4 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 65d9d27cb..e0e0d4bb5 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 d10b8a620..973d1d1b3 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 da3cab9fe..4c5de3120 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 bc66f128d..2a0530526 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 90be614b1..86911efa3 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 eda550b04..d6f15e276 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 035fe7a26..cec4cf5b1 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 b2918eb4b..36cde2133 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 2b071ecbd..dff8a54da 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 d4adc42eb..6ab306b8c 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 946dfde0f..65e09e97f 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 64211c11e..e008d8f02 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 518d8cd79..29c9abe68 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 e3d1b1936..3e8b1780a 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 9c1a5cbd2..a587fcb03 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 f7e1be6b7..1ef9cb818 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 a5e99d04e..4e720bfed 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 183c0cf9d..0f997fd34 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 e7c87df7e..6005d3649 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 6cadbd6bb..0b4d55beb 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 090650415..63ec2993b 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 b326dfd45..7632388e5 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 cd0abca76..ed7ab9e57 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 0eed06073f6d80bd67ee87ef7d3b682943345e4b 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 48386675c..1842d3bc8 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 3869e8c21069a9bc927e93c904e00d68b004a5be 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 7a71f6539..ce94c5d87 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 f3db2c78d..d9eff63cb 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 af18ac331..6419dbc50 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 86fcc11b5..39f0c71c3 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 a735d2ff3..6b1b51702 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 85040bf5b..a9c9f534f 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 458fa6d75..eef821481 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 0188519c2..29abec943 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 6b0942d10702d58b5946f33f8fa66324d6d62491 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 7b91f74a5..a224f6d63 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 6b7234065..54760c41b 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 b8a2430642ac3a7d181ea078fb04654e2a200934 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 f110bce7a..08dfb4793 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 d3308ff21..ce435066d 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 000000000..3adb5b20f --- /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 7cccdefb3..c96c69055 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 388734055..8b373a014 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 5f7cfb8a6..82fdada41 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 9cbda99941c0d7fa65724f0c0865662e89a961df 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 000000000..27c005772 --- /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 a610a02d4f19e6c2b218d9792f0dbf272b1bbda3 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 77b8e95f8..a83fec8ae 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 8fcce165b..e544585ec 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 8823d9fe7..6946fdfcf 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 371c4f483..99b19298c 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 ad8465e2a..42b2606ad 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 ffbc51b64..4c5e6b6cc 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 5241f7b2fde593e27bc6ba13ec5b33d95f9768f8 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 1eeb63fa4..6653f8632 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 8cd322eda..070ec4672 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 f9244f8dd..0b5ff99dd 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 b876df6a2..8c44fa346 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 bfed16620..835bb41b5 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 e77d687e8..5f1715c27 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 e8293c2e7..594f5cf64 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 3d9d1aec6..560dbc6d6 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 d0b7d52c3..2305805a7 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 bf3ebeb42fdddf24fbe1fcf2688e1869226a4411 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 a83fec8ae..69d34dac3 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 5d7012093..80285cc12 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 2c2d924942339ad7906422a18598cdd5d7ce9a84 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 69d34dac3..918a8efba 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 a84f2324e..701855f92 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 ea2a62ada..a657fce4b 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 9015c2d646a1728d2fa135400644b6ce9e3ee052 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 706ce8c1f..481e25439 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 000000000..1da95ba6b --- /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 433ab0c9a3018393cdd2efc7e0513cb96b942320 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 9ef1b3be7..7b192b979 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 3acae843a..e5ee28882 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 679aca46da56228a3219d1c6f0c942adcfb8fdb1 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 594713c00..a062ec88b 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 000000000..e7631345f --- /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 713d217384fdb14dda44c3277f5a9ae4e389e4e2 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 7632388e5..fbc4b1f37 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 ed7ab9e57..e10064228 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 2e1c6e93adc5b573ccf368375f789f2a316f0541 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 b71cf2ade..be2f35ef4 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 fbc4b1f37..d1d1ba99e 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 e10064228..9b8228bd8 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 b84bc2de5df8ea0d3e67c574ca9142efed39715e 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 000000000..cfce8e223 --- /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 e7631345f..000000000 --- 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 604e1c2b11419c6208c0479522434dc64d4fa5e6 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 42b2606ad..a80273635 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 959fda913..1e0c80987 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 3113d05c3..73c751def 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 6144b2bbb..a4f6c145a 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 52d36f0f986f273c107aacbe929abb29dd0e2a71 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 8b373a014..ca4a8bcf1 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 6b1b51702..c64a970ea 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 4d6a8ee34a59a010112b11c44079891b14b7a2d7 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 d1d1ba99e..6691d5bdd 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 9b8228bd8..13c25877b 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 bbe4800b04891080ae07369c1b45f0ea8cf06a8a 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 6691d5bdd..f4708f608 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 13c25877b..77f93390a 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 520462799f6dcf81acb50595cd32a2044ea9252a 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 f4708f608..ba309ffe3 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 77f93390a..fc387b684 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 cee4369cf56ce0575246d860e90470e359ec2ce7 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 918a8efba..c1a845bfa 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 54b574666..5cc4e9380 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 e95e896f1001e898487cf6d67ed2be6e262cca27 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 a062ec88b..e17ddcfcf 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 6ce4fd1cf21ea80e7e3b641d4eec2729822fd012 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 e17ddcfcf..20cb29355 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 3ed3d54bf3b41ab67119cce4a500ea9b21c1471f 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 6a05f8163..4665a5867 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 000000000..fd8e90901 --- /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 aa4c9730f6da845c275d7b2a574954fafb05b88f 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 1842d3bc8..667bc03ff 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 7b54e47d034556a8709425ed8d7fc0a0da34c01a 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 3d513bbf8..34b773278 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 4ce4ac6c8..8e97460c4 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 2ce0b666a139726dc406e6c1887728553b947e59 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 ba309ffe3..5cf1f5aba 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 fc387b684..58b8e03e5 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 e60414792d86a99c0f401f3c1bab92ee37835d39 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 32fb8e39f..29ff897b9 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 000000000..260e404eb --- /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 913b0c913..64deca334 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 91d5def82..19c583b3e 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 2a0703ed9..219b5075d 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 bec6a1cad4c509c53deb378c7ba984ba7e2de5a9 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 63b771339..8c718b356 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 20cb29355..b5d277097 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 010fd3755..c57eb946e 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 000000000..538c1ffb1 --- /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 a133b4e7d..552a3ee5a 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 03e06c100..57f077c4e 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 de965cb0b..a6be55fd7 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 0db9f3536..84492a08a 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 000000000..1f577383e --- /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 29abec943..aea965660 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 04d5ec2a6..45b3d90e0 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 4ac521a4b..67297c26c 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 5cd55d8aaf9880ca36dfa16d08600d31e8a5ff17 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 9bd17fbe8..fa2bd223d 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 aea965660..76198763a 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 45ba9ada3455e5fbce955371bf2aff369e47db54 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 1be15a5a4..e3769437b 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 d34d94d08fbd834d314d859c0addca53d4503715 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 000000000..4adf0837a --- /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 5fd8d1e41773d2d658ea75f17240f748164e9673 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 670b34ea8..4cad11fde 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 27ec8f297dcb6b7ef601ee46c95b3185f210bfa3 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 5cf1f5aba..93b63042c 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 58b8e03e5..05bb4a349 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 b805b7f021910b957d4cee642026911d7790ce36 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 b961b7f67..73e0b42ad 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 99e2e9b81ff55bdf4fabb129a3aca4a3a659a902 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 91c26d2ac..cb5096581 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 ef9296e6c..d948cf4c5 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 996d5def9..2d11b00c2 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 7dacd8d3d..1bedcd9be 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 ceb421f4b..60399c9de 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 ffab56d9a..181616a66 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 df20a5d7e..e6b7f1849 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 0d97ea7e7..519b9a97a 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 bbe35f579..6e9c608ab 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 9884ae4f8..8c8d31ec5 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 4c5e6b6cc..db454d7ad 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 ed349b14e238a90cdf12acd0aaae20d59a36814a 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 d7e424a8c..212b2cfe7 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 25bf5ba87..310153929 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 c1e70a20720741c33ac740242a8a7082fab23557 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 8c718b356..e97b3e52e 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 b5d277097..acea3bbbe 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 36a77748b4305880840bd6194b1d491bae7158bd 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 b948cd154..e70ae5b1b 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 7bb8030cc1a89b307e447fcd6e1b1b64085f13e4 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 f8a0be636..dfed69285 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 7d805cfa90c6454bf98b6edbcdf7bcb2e71b1f0a 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 f447cf285..42c2b4152 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 93b63042c..435885ffa 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 05bb4a349..4f0a6612c 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 23a4ecf444d92242234eb0c9c0a12bc21267685b 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 fae2ed701..15f0007af 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 d51464283c42e23fcc1233c91daad98cf0f67362 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 50e2ae968..a5787883e 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 19f909855190cda30119b8f150c5db85c1579146 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 dc808ad36..dc1932f59 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 a65a9565a..864328631 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 c3a0c4c8b..e738281ad 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 005e185e6..601473069 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 b006f60bb..0093dcd8d 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 29207462a..b8ceedb85 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 c0b9664a31dd57883ee911f1a0881c5f8fc897ae 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 c1a845bfa..98725535f 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 4c0bf779d..cd5914eb5 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 f01670146..c2cd444f0 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 9f0ffc6dc..093d2ba9a 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 a0a5b8153..a4f027947 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 f3feb4c891859c6d111f36d59fdf1bb32e6c85ea 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 a92a29869..3643eb0f3 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 e328ab7e5aee78e0d7eb55de4cfed3f3d812b197 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 c846dd1d6..1885814cd 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 ce8b5899ae8bccd467b30e3a95e7ccc2eff8bc3f 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 61e1d481c..dd54d6710 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 9db8a35b4..ca63ea5a7 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 e13d2edd4733aad42a5404954421c92de6a644ff 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 e3a3c4fc1..350ae2e90 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 d61ec0e6e..4de69c1ea 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 0280d8aef..55d8524db 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 325d5f0183f129ded4da8ff05e9b95043fe281a3 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 98725535f..bd1e5bc14 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 4a22e72b9b1b8f14792efcc649b0db8bc27f0df2 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 e5ee28882..fe39596df 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 21d7c2402..3a018059f 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 8d6aea33260dedeacb3d22ac1a6d2f9cc3856a5e 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 5780a1397..c9ea5f7ac 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 0ed3e904f..0336585f1 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 3f4e4a59c..9b9e1e744 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 0b5ff99dd..1b6caaba8 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 095e33cf7..c12d56e4a 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 08542e3a9..731d7b38d 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 bbc4b0d06..dd20cccc2 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 c5960477d..b31540cb5 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 99e903eef..5f52677af 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 1ea39e05a..ab4ef573b 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 542a2f3ae..6a87eccda 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 435885ffa..57dcf8c9e 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 4f0a6612c..2a480cea3 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 2a61f147532a74f709a71145fd0b38776ca9719d 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 1f577383e..77f4b35b4 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 000000000..db89ff3e6 --- /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 45d98959aca118f8f44ca66d7d358354e43c7d4c 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 edb42a94e..3af0dd261 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 785e650ab44e9ffd28f4284f68ae82007f5609bb 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 3af0dd261..5c81877bd 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 711a03766032a94e0b409f8a545770bc90c14f61 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 3e0bcc93c..949c64042 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 8066118d1f558ed9c53d1c775a78553c6167161b 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 5a5b2b869..569f8628a 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 977c8208d..3005c15ae 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 ce435066d..c537cd24c 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 3adb5b20f..a4a6b7f82 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 d03b1a45a..3efd679a5 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 9669decc8..6c3abd5b6 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 6a17ffb26..000000000 --- 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 000000000..8d43e6827 --- /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 f025a9633..61a9180a1 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 c7320c886..6e8db6230 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 d5bbda6a3..07a691498 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 520e00a3c1601f5661fbb90d2d2153e8a9aa9dec 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 57dcf8c9e..977ce113f 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 b473df9a14d405cf4337103eb5a74fa157739560 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 aac942901..000000000 --- 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 747d8e36a..838364056 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 000000000..8a552c1d4 --- /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 a5fa30a2d205389b80d7f9a7cea1f61024550b1c 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 acea3bbbe..d9086a9c2 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 9628d949ef8d30a3b3245cc4bee61a902e0b0cfb 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 d9086a9c2..a7e624da7 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 16d3e76a710370ab7823fc65302a80e60f6ffee7 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 977ce113f..a872d2576 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 2a480cea3..940d09292 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 7cfa6424f1327162c12b2ecbb87d8b5a90ef193d 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 a872d2576..71ae58995 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 940d09292..66461c308 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 4ea24537cf9c1fafd0edf79acd8cf666be662fbe 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 a7e624da7..0cc08e536 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 d7fd2c5763b49678c8f5b19a6d30fec68c740f74 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 e97b3e52e..363fc6bd3 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 0cc08e536..7c2d88053 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 c7cbded282f799c5ced48a51dfc45103f55347ed 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 71ae58995..2b3006cf9 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 66461c308..3a1f31a87 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 9e59186f78a1b170d4d15197a4bb439f66fd7e8f 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 2b3006cf9..ae7685e89 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 3a1f31a87..58f2e1464 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 c2cbe90a89865c4bcf5a01398644a00a984800ec 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 ae7685e89..ba520a034 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 58f2e1464..f6cfa9928 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 c37ecbcd1027d5c72665fc857df59a4fc1c9732f 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 363fc6bd3..6a444acd2 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 7c2d88053..d0547ea50 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 5b332112fcac62705a7d93562ae99a90e3291ceb 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 ba520a034..38eaa7bf8 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 f6cfa9928..88603113b 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 753e6df04a77a4546d348f82206b899ae018c5de 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 d0547ea50..21647e1b6 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 2877c80dbc1323342bc697907fdfbf8c5b4045f8 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 288ac1d71..a8905160f 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 9a472efe7c21447ce4e246bacff5a857a341e4f3 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 ac9a79dcc..000000000 --- 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 000000000..712b4c293 --- /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 2a0530526..ce9485216 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 cdd65c9ef..1a1fdf578 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 26dee213d..c024ff063 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 0664704cd94a356b07870d63971ba11a07d24894 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 bd1e5bc14..e2441c934 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 540bbe1a9..9090773ae 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 9f5deb310b9e5c26eb3c0af018e92a4e6072661b 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 e2441c934..6fb910005 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 81b89d9b3..e0122a65b 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 0b510eb25..fe7e04929 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 8e619f9cc..643e6828d 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 32e989816..f17d06be5 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 63582b9f9..a10279544 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 7b8873e0c..6185732b4 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 be462cd0f..504d26a8b 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 531332614..2fab97569 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 8ba506fa1..5527953af 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 a1b91dc0f..5472646c6 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 9a056a386..c556bcc2b 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 b3b279147..f3fbb8021 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 45de4f4a4..037d92f39 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 8f9c68794..05a01be28 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 11116cabd..a62fa004a 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 e387175fc9a3ebfd72ab45ebfe43ecfabef7b0c3 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 6fb910005..de6857e07 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 9ae54780e..36ac8d802 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 ea1f9b4223baae32e6933c5bef0d8d3200fe4f2e 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 ab7716cff4a1f590565b6a229282e899d7d02e96 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 8b9c50153..ec264f1f1 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 490543424..d10b8a620 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 e2556ec34..8ef05d1f8 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 7d9b7f28b85be297ff512ffdb6ec9262506b601d 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 349cae0b57ee65475a70311863959e7a0cf53065 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 e9f04fcda..66b7e3e4a 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 fceca5d96..000000000 --- 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 000000000..4dfc51c78 --- /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 8813d19b9..8f5461357 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 908b70c5f..7d025ed41 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 8b7969250..db17c6e72 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 79543389c..da11f881f 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 e15bc1422..4eeffc402 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 b2dfc5dbb..31e79f042 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 64522a3a8..0e4b098d9 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 69fbaf504..b94b7e3f6 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 d7c229404..c61f656f6 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 b9c72c276..43a910c50 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 6b74aafee..92ccd4092 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 992dd677d..e36ab818f 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 6a98e8b53..cf4bf4634 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 31ae4f68f..6128df249 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 4090b6500..766a35c86 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 79c43b61a6b799287710ac2428a8ca047b2b9205 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 6f8db56a01c772bb2ec11bdb67aa2997eb62837e 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 6c3e963d9..f7e1be6b7 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 e8fc445e14f81cd3bf0dbddde5078b7fab690329 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 b878e3d8dfaf3cf61f7af9d16afbe9c89b91ab62 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 ea9fd0828..9e74590e6 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 a016e44b7..fc763146e 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 1b4ce0574..3c6312449 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 5a5563e87..f08542e7f 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 87c8d7467..0a4bb9488 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 8c13f6797..25252c01c 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 461dac2ec..669425c7f 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 90d8fd0ef..d9c074682 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 1b8991f00..752689930 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 1ab7c3663..b9ef73959 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 60d281f97..9b63dec23 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 8755811d9..3287c5989 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 a9a35f54b..7ca707ec9 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 2ef007da5..8356e977e 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 f7abe248d..880366476 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 dce0dcd8f..375ab5fae 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 b699fdb27..e1a76e2fe 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 41be2f7f3..5f6035cb1 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 fe74f1fbc..5b03500c2 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 9b84da06e..dd91b9334 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 27b13ecfe..0e3a9ae04 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 a28be0e88..4e8e4b496 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 a29070bc7..4fc2372b3 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 fb791fa9f..e70702ee7 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 e6a7cfd12e8a44a3f2193c174f9b4d51492e711a 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 5eeb40bdbe4d5e7c5c60788c0e10311f4d125853 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 1800daa55..badcb1dcb 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 46b2cdf93..6fbfc07f6 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 d8c0a1373..a0d5933b1 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 f6c77d35f..b6b073161 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 1bd7739f5..11253a8e9 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 700513676..1550d0b7a 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 af0fec35a..9e8e04b65 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 72914ba74..c606a2904 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 4eb948dc7..6bd15da6a 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 80b20ae1b..0b8128824 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 bf86814c0..e427b7172 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 53945d6a7..7a71f6539 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 829f47174..67acb09f4 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 65d9d27cb..e0e0d4bb5 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 d10b8a620..973d1d1b3 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 0a4bb9488..3739ce23c 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 458f48cde..a466a3eb6 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 2b61bad70..250e781cf 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 0e07b67f8..5b4d0ce60 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 4fc2372b3..15b94273d 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 c91ba4d53..d69beb240 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 7dc34eba9..a750c3668 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 64211c11e..e008d8f02 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 da2174fc2..eaef569f0 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 e3d1b1936..3e8b1780a 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 f7e1be6b7..1ef9cb818 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 5dec423af..2697f80f1 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 183c0cf9d..0f997fd34 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 e7c87df7e..6005d3649 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 090650415..63ec2993b 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 fb8c3c11e..e3d4e6a35 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 6cadbd6bb..0b4d55beb 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 022ab1f71..d8f425f16 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 602ae7f2f6147865798373ffedaaaa25e2499f79 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 0eed06073f6d80bd67ee87ef7d3b682943345e4b 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 a708d066a..92633fc3f 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 d72822212119417e18a0a4babc6d7bf33733c74e 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 3869e8c21069a9bc927e93c904e00d68b004a5be 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 7a71f6539..ce94c5d87 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 380edee65..515e47de2 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 519f917ca..3e56f9043 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 c0d62aca0..86f96da9b 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 4a1f4cb6a2ccf7cf3df6d90359b8abc773bff990 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 5241f7b2fde593e27bc6ba13ec5b33d95f9768f8 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 3c6312449..33ddb8a29 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 78deb5f2a..441936a43 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 f245e2f0a..06db0a003 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 b4fc8be72..26c45eff6 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 25517a9f7..0b3e0a2f0 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 09c1aa992..39a778045 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 1e51db009..36d42d270 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 e70702ee7..8375ffe94 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 b0ec3bfcf996565a08870a0f2b735f66b7fdf5b5 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 7b54e47d034556a8709425ed8d7fc0a0da34c01a 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 a9c06f693..a225883a8 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 3cd0707f2..92ddc01c8 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 ba73f0ea3a0bcfc21dc8f0111eac4f0bce7101ca 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 e60414792d86a99c0f401f3c1bab92ee37835d39 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 3ee35d141..8edca4d01 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 b9144e43a..30676dcf5 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 000000000..ed03f1cf4 --- /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 672dd5440..0877e4d40 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 ccab925aa..470ded530 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 64deca334..913b0c913 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 3b375ee3952f98278d235fe3fad04ee836169a62 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 8d6aea33260dedeacb3d22ac1a6d2f9cc3856a5e 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 47293ef18..fce6a6c40 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 3f17bc129..901c7fe22 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 13701bcd3..69925321b 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 06db0a003..99e1ed55c 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 786f59307..28f316619 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 752689930..68ff37b80 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 05614de01..2d4ae72d4 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 1ac7d579e..63195518b 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 7544ae4e9..7b0917df8 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 34a8f3513..7bb85fd2f 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 47a51a5b0..a835058ac 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 d94aeb9d4..d9796954a 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 892b3c16f5a38405646db3176a83c7af5d9f92f8 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 711a03766032a94e0b409f8a545770bc90c14f61 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 1c83517a1..7c961fa57 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 6746e5d4309fa8022e8a84674b85251543a31317 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 8066118d1f558ed9c53d1c775a78553c6167161b 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 66b7e3e4a..35f83beab 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 5b6a19f8d..9c0b848d0 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 3dac8c6c2..8b8466ee7 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 4dfc51c78..7224ac3d7 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 235aaeb3c..ced250b79 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 ec264f1f1..25ec40560 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 6a17ffb26..000000000 --- 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 000000000..8d43e6827 --- /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 8ef05d1f8..8b65d9571 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 d70914d41..cadc710d4 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 a43befa73..44cb9b0d0 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 60c7e559d8c6600b6e31246d58549987c3108b93 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 9a472efe7c21447ce4e246bacff5a857a341e4f3 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 fb55a62cc..000000000 --- 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 000000000..712b4c293 --- /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 3739ce23c..b90918a01 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 2c4db665a..39eaed128 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 39ff4c603..39f87eef9 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 61f6cd45e361736578c36a60d7f3e919db5e4327 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 a278bb4e8..725bd0bbb 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 1347ca6eb00dbe3993efb647369f7eb1f3dda50c 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 d9796954a..bc5e200fc 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;