diff --git a/.eslintrc.js b/.eslintrc.js
index 3bac9ed694..70506f60c4 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -236,7 +236,7 @@ module.exports = {
},
// Common React utilities
{
- pattern: '{classnames,react-helmet,react-router-dom}',
+ pattern: '{classnames,react-helmet,react-router,react-router-dom}',
group: 'external',
position: 'before',
},
diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml
index bcf63bb59e..d14a34f3c2 100644
--- a/.github/workflows/test-ruby.yml
+++ b/.github/workflows/test-ruby.yml
@@ -114,6 +114,7 @@ jobs:
BUNDLE_WITH: 'pam_authentication test'
CI_JOBS: ${{ matrix.ci_job }}/4
ES_ENABLED: false
+ GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }}
strategy:
fail-fast: false
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 28316b8252..2450afb582 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -48,27 +48,6 @@ Lint/UnusedBlockArgument:
- 'config/initializers/paperclip.rb'
- 'config/initializers/simple_form.rb'
-# This cop supports unsafe autocorrection (--autocorrect-all).
-Lint/UselessAssignment:
- Exclude:
- - 'app/services/activitypub/process_status_update_service.rb'
- - 'config/initializers/3_omniauth.rb'
- - 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb'
- - 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb'
- - 'spec/controllers/api/v1/favourites_controller_spec.rb'
- - 'spec/controllers/concerns/account_controller_concern_spec.rb'
- - 'spec/helpers/jsonld_helper_spec.rb'
- - 'spec/models/account_spec.rb'
- - 'spec/models/domain_block_spec.rb'
- - 'spec/models/status_spec.rb'
- - 'spec/models/user_spec.rb'
- - 'spec/models/webauthn_credentials_spec.rb'
- - 'spec/services/account_search_service_spec.rb'
- - 'spec/services/post_status_service_spec.rb'
- - 'spec/services/precompute_feed_service_spec.rb'
- - 'spec/services/resolve_url_service_spec.rb'
- - 'spec/views/statuses/show.html.haml_spec.rb'
-
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
Max: 144
@@ -86,26 +65,6 @@ Metrics/CyclomaticComplexity:
Metrics/PerceivedComplexity:
Max: 27
-# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
-# SupportedStyles: snake_case, normalcase, non_integer
-# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
-Naming/VariableNumber:
- Exclude:
- - 'db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb'
- - 'db/migrate/20180514140000_revert_index_change_on_statuses_for_api_v1_accounts_account_id_statuses.rb'
- - 'db/migrate/20190820003045_update_statuses_index.rb'
- - 'db/migrate/20190823221802_add_local_index_to_statuses.rb'
- - 'db/migrate/20200119112504_add_public_index_to_statuses.rb'
- - 'spec/models/account_spec.rb'
- - 'spec/models/domain_block_spec.rb'
- - 'spec/models/user_spec.rb'
-
-# This cop supports unsafe autocorrection (--autocorrect-all).
-# Configuration parameters: SafeMultiline.
-Performance/DeletePrefix:
- Exclude:
- - 'app/models/featured_tag.rb'
-
Performance/MapMethodChain:
Exclude:
- 'app/models/feed.rb'
diff --git a/app/controllers/api/v1/apps/credentials_controller.rb b/app/controllers/api/v1/apps/credentials_controller.rb
index 0475b2d4a2..6256bed64c 100644
--- a/app/controllers/api/v1/apps/credentials_controller.rb
+++ b/app/controllers/api/v1/apps/credentials_controller.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
class Api::V1::Apps::CredentialsController < Api::BaseController
- before_action -> { doorkeeper_authorize! :read }
-
def show
- render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key)
+ return doorkeeper_render_error unless valid_doorkeeper_token?
+
+ render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key client_id scopes)
end
end
diff --git a/app/helpers/admin/disputes_helper.rb b/app/helpers/admin/disputes_helper.rb
new file mode 100644
index 0000000000..366a470ed2
--- /dev/null
+++ b/app/helpers/admin/disputes_helper.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Admin
+ module DisputesHelper
+ def strike_action_label(appeal)
+ t(key_for_action(appeal),
+ scope: 'admin.strikes.actions',
+ name: content_tag(:span, appeal.strike.account.username, class: 'username'),
+ target: content_tag(:span, appeal.account.username, class: 'target'))
+ .html_safe
+ end
+
+ private
+
+ def key_for_action(appeal)
+ AccountWarning.actions.slice(appeal.strike.action).keys.first
+ end
+ end
+end
diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb
index 112c4256f5..f0faf69b43 100644
--- a/app/helpers/formatting_helper.rb
+++ b/app/helpers/formatting_helper.rb
@@ -9,6 +9,10 @@ module FormattingHelper
TextFormatter.new(text, options).to_s
end
+ def url_for_preview_card(preview_card)
+ preview_card.url
+ end
+
def extract_status_plain_text(status)
PlainTextFormatter.new(status.text, status.local?).to_s
end
diff --git a/app/javascript/mastodon/components/column_back_button.jsx b/app/javascript/mastodon/components/column_back_button.jsx
index 74a03b093a..b47718ed89 100644
--- a/app/javascript/mastodon/components/column_back_button.jsx
+++ b/app/javascript/mastodon/components/column_back_button.jsx
@@ -4,29 +4,28 @@ import { createPortal } from 'react-dom';
import { FormattedMessage } from 'react-intl';
+import { withRouter } from 'react-router-dom';
+
import { Icon } from 'mastodon/components/icon';
+import { WithRouterPropTypes } from 'mastodon/utils/react_router';
-export default class ColumnBackButton extends PureComponent {
-
- static contextTypes = {
- router: PropTypes.object,
- };
+class ColumnBackButton extends PureComponent {
static propTypes = {
multiColumn: PropTypes.bool,
onClick: PropTypes.func,
+ ...WithRouterPropTypes,
};
handleClick = () => {
- const { router } = this.context;
- const { onClick } = this.props;
+ const { onClick, history } = this.props;
if (onClick) {
onClick();
- } else if (router.history.location?.state?.fromMastodon) {
- router.history.goBack();
+ } else if (history.location?.state?.fromMastodon) {
+ history.goBack();
} else {
- router.history.push('/');
+ history.push('/');
}
};
@@ -60,3 +59,5 @@ export default class ColumnBackButton extends PureComponent {
}
}
+
+export default withRouter(ColumnBackButton);
diff --git a/app/javascript/mastodon/components/column_header.jsx b/app/javascript/mastodon/components/column_header.jsx
index 9d29bbae03..2896a501be 100644
--- a/app/javascript/mastodon/components/column_header.jsx
+++ b/app/javascript/mastodon/components/column_header.jsx
@@ -5,8 +5,10 @@ import { createPortal } from 'react-dom';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import classNames from 'classnames';
+import { withRouter } from 'react-router-dom';
import { Icon } from 'mastodon/components/icon';
+import { WithRouterPropTypes } from 'mastodon/utils/react_router';
const messages = defineMessages({
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
@@ -18,7 +20,6 @@ const messages = defineMessages({
class ColumnHeader extends PureComponent {
static contextTypes = {
- router: PropTypes.object,
identity: PropTypes.object,
};
@@ -38,6 +39,7 @@ class ColumnHeader extends PureComponent {
onClick: PropTypes.func,
appendContent: PropTypes.node,
collapseIssues: PropTypes.bool,
+ ...WithRouterPropTypes,
};
state = {
@@ -63,12 +65,12 @@ class ColumnHeader extends PureComponent {
};
handleBackClick = () => {
- const { router } = this.context;
+ const { history } = this.props;
- if (router.history.location?.state?.fromMastodon) {
- router.history.goBack();
+ if (history.location?.state?.fromMastodon) {
+ history.goBack();
} else {
- router.history.push('/');
+ history.push('/');
}
};
@@ -78,15 +80,14 @@ class ColumnHeader extends PureComponent {
handlePin = () => {
if (!this.props.pinned) {
- this.context.router.history.replace('/');
+ this.props.history.replace('/');
}
this.props.onPin();
};
render () {
- const { router } = this.context;
- const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props;
+ const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props;
const { collapsed, animating } = this.state;
const wrapperClassName = classNames('column-header__wrapper', {
@@ -129,7 +130,7 @@ class ColumnHeader extends PureComponent {
pinButton = ;
}
- if (!pinned && ((multiColumn && router.history.location?.state?.fromMastodon) || showBackButton)) {
+ if (!pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) {
backButton = (
);
diff --git a/app/javascript/mastodon/features/ui/components/list_panel.jsx b/app/javascript/mastodon/features/ui/components/list_panel.jsx
index 943ed06d81..9e898b03d4 100644
--- a/app/javascript/mastodon/features/ui/components/list_panel.jsx
+++ b/app/javascript/mastodon/features/ui/components/list_panel.jsx
@@ -1,7 +1,5 @@
import PropTypes from 'prop-types';
-import { withRouter } from 'react-router-dom';
-
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
@@ -71,4 +69,4 @@ class ListPanel extends ImmutablePureComponent {
}
-export default withRouter(connect(mapStateToProps)(ListPanel));
+export default connect(mapStateToProps)(ListPanel);
diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
index 5b412ba147..fd3974314e 100644
--- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
+++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx
@@ -6,7 +6,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { WordmarkLogo } from 'mastodon/components/logo';
-import NavigationPortal from 'mastodon/components/navigation_portal';
+import { NavigationPortal } from 'mastodon/components/navigation_portal';
import { enableDtlMenu, timelinePreview, trendsEnabled, dtlTag } from 'mastodon/initial_state';
import { transientSingleColumn } from 'mastodon/is_mobile';
@@ -41,7 +41,6 @@ const messages = defineMessages({
class NavigationPanel extends Component {
static contextTypes = {
- router: PropTypes.object.isRequired,
identity: PropTypes.object.isRequired,
};
@@ -66,25 +65,31 @@ class NavigationPanel extends Component {
) : (
));
+
+ let banner = undefined;
+
+ if(transientSingleColumn)
+ banner = ();
return (
-
- {transientSingleColumn ? (
-
- ) : (
-
- )}
+ {!banner &&
}
+ {banner &&
+
+ {banner}
+
+ }
+
{signedIn && (
<>
diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx
index 4fb9522c16..b40f3938e0 100644
--- a/app/javascript/mastodon/features/ui/index.jsx
+++ b/app/javascript/mastodon/features/ui/index.jsx
@@ -16,6 +16,7 @@ import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodo
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
import PictureInPicture from 'mastodon/features/picture_in_picture';
import { layoutFromWindow } from 'mastodon/is_mobile';
+import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
import { clearHeight } from '../../actions/height_cache';
@@ -278,7 +279,6 @@ class SwitchingColumnsArea extends PureComponent {
class UI extends PureComponent {
static contextTypes = {
- router: PropTypes.object.isRequired,
identity: PropTypes.object.isRequired,
};
@@ -289,12 +289,12 @@ class UI extends PureComponent {
hasComposingText: PropTypes.bool,
hasMediaAttachments: PropTypes.bool,
canUploadMore: PropTypes.bool,
- location: PropTypes.object,
intl: PropTypes.object.isRequired,
dropdownMenuIsOpen: PropTypes.bool,
layout: PropTypes.string.isRequired,
firstLaunch: PropTypes.bool,
username: PropTypes.string,
+ ...WithRouterPropTypes,
};
state = {
@@ -391,7 +391,7 @@ class UI extends PureComponent {
handleServiceWorkerPostMessage = ({ data }) => {
if (data.type === 'navigate') {
- this.context.router.history.push(data.path);
+ this.props.history.push(data.path);
} else {
console.warn('Unknown message type:', data.type);
}
@@ -512,12 +512,12 @@ class UI extends PureComponent {
};
handleHotkeyBack = () => {
- const { router } = this.context;
+ const { history } = this.props;
- if (router.history.location?.state?.fromMastodon) {
- router.history.goBack();
+ if (history.location?.state?.fromMastodon) {
+ history.goBack();
} else {
- router.history.push('/');
+ history.push('/');
}
};
@@ -527,38 +527,38 @@ class UI extends PureComponent {
handleHotkeyToggleHelp = () => {
if (this.props.location.pathname === '/keyboard-shortcuts') {
- this.context.router.history.goBack();
+ this.props.history.goBack();
} else {
- this.context.router.history.push('/keyboard-shortcuts');
+ this.props.history.push('/keyboard-shortcuts');
}
};
handleHotkeyGoToHome = () => {
- this.context.router.history.push('/home');
+ this.props.history.push('/home');
};
handleHotkeyGoToNotifications = () => {
- this.context.router.history.push('/notifications');
+ this.props.history.push('/notifications');
};
handleHotkeyGoToLocal = () => {
- this.context.router.history.push('/public/local');
+ this.props.history.push('/public/local');
};
handleHotkeyGoToFederated = () => {
- this.context.router.history.push('/public');
+ this.props.history.push('/public');
};
handleHotkeyGoToDirect = () => {
- this.context.router.history.push('/conversations');
+ this.props.history.push('/conversations');
};
handleHotkeyGoToStart = () => {
- this.context.router.history.push('/getting-started');
+ this.props.history.push('/getting-started');
};
handleHotkeyGoToFavourites = () => {
- this.context.router.history.push('/favourites');
+ this.props.history.push('/favourites');
};
handleHotkeyGoToEmojiReactions = () => {
@@ -566,23 +566,23 @@ class UI extends PureComponent {
};
handleHotkeyGoToPinned = () => {
- this.context.router.history.push('/pinned');
+ this.props.history.push('/pinned');
};
handleHotkeyGoToProfile = () => {
- this.context.router.history.push(`/@${this.props.username}`);
+ this.props.history.push(`/@${this.props.username}`);
};
handleHotkeyGoToBlocked = () => {
- this.context.router.history.push('/blocks');
+ this.props.history.push('/blocks');
};
handleHotkeyGoToMuted = () => {
- this.context.router.history.push('/mutes');
+ this.props.history.push('/mutes');
};
handleHotkeyGoToRequests = () => {
- this.context.router.history.push('/follow_requests');
+ this.props.history.push('/follow_requests');
};
render () {
diff --git a/app/javascript/mastodon/features/ui/util/react_router_helpers.jsx b/app/javascript/mastodon/features/ui/util/react_router_helpers.jsx
index 9927726857..c0ee31bf68 100644
--- a/app/javascript/mastodon/features/ui/util/react_router_helpers.jsx
+++ b/app/javascript/mastodon/features/ui/util/react_router_helpers.jsx
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
-import { Component, PureComponent, cloneElement, Children } from 'react';
+import { Component, cloneElement, Children } from 'react';
-import { Switch, Route } from 'react-router-dom';
+import { Switch, Route, useLocation } from 'react-router-dom';
import StackTrace from 'stacktrace-js';
@@ -10,27 +10,20 @@ import ColumnLoading from '../components/column_loading';
import BundleContainer from '../containers/bundle_container';
// Small wrapper to pass multiColumn to the route components
-export class WrappedSwitch extends PureComponent {
- static contextTypes = {
- router: PropTypes.object,
- };
+export const WrappedSwitch = ({ multiColumn, children }) => {
+ const location = useLocation();
- render () {
- const { multiColumn, children } = this.props;
- const { location } = this.context.router.route;
+ const decklessLocation = multiColumn && location.pathname.startsWith('/deck')
+ ? {...location, pathname: location.pathname.slice(5)}
+ : location;
- const decklessLocation = multiColumn && location.pathname.startsWith('/deck')
- ? {...location, pathname: location.pathname.slice(5)}
- : location;
+ return (
+
+ {Children.map(children, child => child ? cloneElement(child, { multiColumn }) : null)}
+
+ );
+};
- return (
-
- {Children.map(children, child => child ? cloneElement(child, { multiColumn }) : null)}
-
- );
- }
-
-}
WrappedSwitch.propTypes = {
multiColumn: PropTypes.bool,
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index a7a68533d6..aac4256ffd 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -71,11 +71,11 @@
"account.unmute_notifications_short": "Poista ilmoitusten mykistys",
"account.unmute_short": "Poista mykistys",
"account_note.placeholder": "Lisää muistiinpano napsauttamalla",
- "admin.dashboard.daily_retention": "Käyttäjän pysyminen rekisteröitymisen jälkeiseen päivään mennessä",
- "admin.dashboard.monthly_retention": "Käyttäjän pysyminen rekisteröitymisen jälkeiseen kuukauteen mennessä",
+ "admin.dashboard.daily_retention": "Käyttäjien pysyvyys rekisteröitymisen jälkeen päivittäin",
+ "admin.dashboard.monthly_retention": "Käyttäjien pysyvyys rekisteröitymisen jälkeen kuukausittain",
"admin.dashboard.retention.average": "Keskimäärin",
- "admin.dashboard.retention.cohort": "Kirjautumiset",
- "admin.dashboard.retention.cohort_size": "Uudet käyttäjät",
+ "admin.dashboard.retention.cohort": "Rekisteröitymis-kk.",
+ "admin.dashboard.retention.cohort_size": "Uusia käyttäjiä",
"admin.impact_report.instance_accounts": "Tilien profiilit, jotka tämä poistaisi",
"admin.impact_report.instance_followers": "Seuraajat, jotka käyttäjämme menettäisivät",
"admin.impact_report.instance_follows": "Seuraajat, jotka heidän käyttäjänsä menettäisivät",
@@ -114,7 +114,7 @@
"column.directory": "Selaa profiileja",
"column.domain_blocks": "Estetyt verkkotunnukset",
"column.favourites": "Suosikit",
- "column.firehose": "Live-syötteet",
+ "column.firehose": "Livesyötteet",
"column.follow_requests": "Seuraamispyynnöt",
"column.home": "Koti",
"column.lists": "Listat",
@@ -135,7 +135,7 @@
"community.column_settings.remote_only": "Vain etätilit",
"compose.language.change": "Vaihda kieli",
"compose.language.search": "Hae kieliä...",
- "compose.published.body": "Julkaisusi julkaistiin.",
+ "compose.published.body": "Julkaisu lähetetty.",
"compose.published.open": "Avaa",
"compose.saved.body": "Julkaisu tallennettu.",
"compose_form.direct_message_warning_learn_more": "Lisätietoja",
@@ -436,10 +436,10 @@
"notifications.clear": "Tyhjennä ilmoitukset",
"notifications.clear_confirmation": "Haluatko varmasti poistaa kaikki ilmoitukset pysyvästi?",
"notifications.column_settings.admin.report": "Uudet ilmoitukset:",
- "notifications.column_settings.admin.sign_up": "Uudet kirjautumiset:",
+ "notifications.column_settings.admin.sign_up": "Uudet rekisteröitymiset:",
"notifications.column_settings.alert": "Työpöytäilmoitukset",
"notifications.column_settings.favourite": "Suosikit:",
- "notifications.column_settings.filter_bar.advanced": "Näytä kaikki kategoriat",
+ "notifications.column_settings.filter_bar.advanced": "Näytä kaikki luokat",
"notifications.column_settings.filter_bar.category": "Pikasuodatuspalkki",
"notifications.column_settings.filter_bar.show_bar": "Näytä suodatinpalkki",
"notifications.column_settings.follow": "Uudet seuraajat:",
@@ -517,7 +517,7 @@
"privacy.private.short": "Vain seuraajat",
"privacy.public.long": "Näkyy kaikille",
"privacy.public.short": "Julkinen",
- "privacy.unlisted.long": "Näkyy kaikille, mutta jää pois löytämisominaisuuksista",
+ "privacy.unlisted.long": "Näkyy kaikille mutta jää pois löytämisominaisuuksista",
"privacy.unlisted.short": "Listaamaton",
"privacy_policy.last_updated": "Viimeksi päivitetty {date}",
"privacy_policy.title": "Tietosuojakäytäntö",
@@ -589,13 +589,13 @@
"search.quick_action.go_to_hashtag": "Siirry aihetunnisteeseen {x}",
"search.quick_action.open_url": "Avaa URL-osoite Mastodonissa",
"search.quick_action.status_search": "Julkaisut haulla {x}",
- "search.search_or_paste": "Hae tai kirjoita URL-osoite",
+ "search.search_or_paste": "Hae tai liitä URL-osoite",
"search_popout.full_text_search_disabled_message": "Ei saatavilla palvelimella {domain}.",
"search_popout.language_code": "ISO-kielikoodi",
"search_popout.options": "Hakuvalinnat",
"search_popout.quick_actions": "Pikatoiminnot",
"search_popout.recent": "Viimeaikaiset haut",
- "search_popout.specific_date": "tietty päivämäärä",
+ "search_popout.specific_date": "tarkka päiväys",
"search_popout.user": "käyttäjä",
"search_results.accounts": "Profiilit",
"search_results.all": "Kaikki",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index d1bcc8cd97..1477974d3a 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -209,7 +209,7 @@
"compose.language.search": "言語を検索...",
"compose.published.body": "投稿されました!",
"compose.published.open": "開く",
- "compose.saved.body": "投稿が保存されました",
+ "compose.saved.body": "変更を保存しました。",
"compose_form.direct_message_warning_learn_more": "もっと詳しく",
"compose_form.encryption_warning": "Mastodonの投稿はエンドツーエンド暗号化に対応していません。安全に送受信されるべき情報をMastodonで共有しないでください。",
"compose_form.hashtag_warning": "この投稿は公開設定ではないのでハッシュタグの一覧に表示されません。公開投稿だけがハッシュタグで検索できます。",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index f2237d693e..09c95e02cd 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -204,7 +204,7 @@
"dismissable_banner.explore_links": "이 소식들은 오늘 소셜 웹에서 가장 많이 공유된 내용들입니다. 새 소식을 더 많은 사람들이 공유할수록 높은 순위가 됩니다.",
"dismissable_banner.explore_statuses": "이 게시물들은 오늘 소셜 웹에서 호응을 얻고 있는 게시물들입니다. 부스트와 관심을 받는 새로운 글들이 높은 순위가 됩니다.",
"dismissable_banner.explore_tags": "이 해시태그들은 이 서버와 분산화된 네트워크의 다른 서버에서 사람들의 인기를 끌고 있는 것들입니다.",
- "dismissable_banner.public_timeline": "이것들은 {domain}에 있는 사람들이 팔로우한 사람들의 최신 게시물들입니다.",
+ "dismissable_banner.public_timeline": "{domain} 사람들이 팔로우하는 소셜 웹 사람들의 최신 공개 게시물입니다.",
"embed.instructions": "아래의 코드를 복사하여 대화를 원하는 곳으로 공유하세요.",
"embed.preview": "이렇게 표시됩니다:",
"emoji_button.activity": "활동",
diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json
index 0a14ea93a3..8ff390aabb 100644
--- a/app/javascript/mastodon/locales/nn.json
+++ b/app/javascript/mastodon/locales/nn.json
@@ -153,7 +153,7 @@
"compose_form.publish": "Legg ut",
"compose_form.publish_form": "Legg ut",
"compose_form.publish_loud": "{publish}!",
- "compose_form.save_changes": "Gøym",
+ "compose_form.save_changes": "Lagre endringar",
"compose_form.sensitive.hide": "{count, plural, one {Marker mediet som ømtolig} other {Marker media som ømtolige}}",
"compose_form.sensitive.marked": "{count, plural, one {Mediet er markert som ømtolig} other {Media er markerte som ømtolige}}",
"compose_form.sensitive.unmarked": "{count, plural, one {Mediet er ikkje markert som ømtolig} other {Media er ikkje markerte som ømtolige}}",
@@ -171,7 +171,7 @@
"confirmations.delete_list.confirm": "Slett",
"confirmations.delete_list.message": "Er du sikker på at du vil sletta denne lista for alltid?",
"confirmations.discard_edit_media.confirm": "Forkast",
- "confirmations.discard_edit_media.message": "Du har ulagra endringar i mediaskildringa eller førehandsvisinga. Vil du forkaste dei likevel?",
+ "confirmations.discard_edit_media.message": "Du har ulagra endringar i mediaskildringa eller førehandsvisinga. Vil du forkasta dei likevel?",
"confirmations.domain_block.confirm": "Skjul alt frå domenet",
"confirmations.domain_block.message": "Er du heilt, heilt sikker på at du vil skjula heile {domain}? I dei fleste tilfelle er det godt nok og føretrekt med nokre få målretta blokkeringar eller målbindingar. Du kjem ikkje til å sjå innhald frå domenet i fødererte tidsliner eller i varsla dine. Fylgjarane dine frå domenet vert fjerna.",
"confirmations.edit.confirm": "Rediger",
@@ -285,7 +285,7 @@
"footer.privacy_policy": "Personvernsreglar",
"footer.source_code": "Vis kjeldekode",
"footer.status": "Status",
- "generic.saved": "Gøymt",
+ "generic.saved": "Lagra",
"getting_started.heading": "Kom i gang",
"hashtag.column_header.tag_mode.all": "og {additional}",
"hashtag.column_header.tag_mode.any": "eller {additional}",
@@ -314,7 +314,7 @@
"home.pending_critical_update.link": "Sjå oppdateringar",
"home.pending_critical_update.title": "Kritisk sikkerheitsoppdatering er tilgjengeleg!",
"home.show_announcements": "Vis kunngjeringar",
- "interaction_modal.description.favourite": "Med ein konto på Mastodon kan du favorittmerkja dette innlegget for å visa forfattaren at du set pris på det, og for å lagra det til seinare.",
+ "interaction_modal.description.favourite": "Med ein konto på Mastodon kan du favorittmerka dette innlegget for å visa forfattaren at du set pris på det, og for å lagra det til seinare.",
"interaction_modal.description.follow": "Med ein konto på Mastodon kan du fylgja {name} for å sjå innlegga deira i din heimestraum.",
"interaction_modal.description.reblog": "Med ein konto på Mastodon kan du framheva dette innlegget for å dela det med dine eigne fylgjarar.",
"interaction_modal.description.reply": "Med ein konto på Mastodon kan du svara på dette innlegget.",
@@ -673,7 +673,7 @@
"status.unmute_conversation": "Opphev målbinding av samtalen",
"status.unpin": "Løys frå profil",
"subscribed_languages.lead": "Kun innlegg på valde språk vil bli dukke opp i heimestraumen din og i listene dine etter denne endringa. For å motta innlegg på alle språk, la vere å velje nokon.",
- "subscribed_languages.save": "Gøym",
+ "subscribed_languages.save": "Lagre endringar",
"subscribed_languages.target": "Endre abonnerte språk for {target}",
"tabs_bar.home": "Heim",
"tabs_bar.notifications": "Varsel",
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index 130b28bc74..ec2c76e8f9 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -303,7 +303,7 @@
"hashtag.unfollow": "Отпрати хеш ознаку",
"hashtags.and_other": "…и {count, plural, one {још #} few {још #}other {још #}}",
"home.actions.go_to_explore": "Погледате шта је у тренду",
- "home.actions.go_to_suggestions": "Пронађeте људе које бисте пратили",
+ "home.actions.go_to_suggestions": "Пронађете људе које бисте пратили",
"home.column_settings.basic": "Основна",
"home.column_settings.show_reblogs": "Прикажи подржавања",
"home.column_settings.show_replies": "Прикажи одговоре",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index b1cd0ca753..f8dcb6a89f 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -114,7 +114,7 @@
"column.directory": "瀏覽個人檔案",
"column.domain_blocks": "已封鎖網域",
"column.favourites": "最愛",
- "column.firehose": "即時內容",
+ "column.firehose": "即時河道",
"column.follow_requests": "跟隨請求",
"column.home": "首頁",
"column.lists": "列表",
diff --git a/app/javascript/mastodon/utils/react_router.jsx b/app/javascript/mastodon/utils/react_router.jsx
new file mode 100644
index 0000000000..a56883270b
--- /dev/null
+++ b/app/javascript/mastodon/utils/react_router.jsx
@@ -0,0 +1,60 @@
+import PropTypes from "prop-types";
+
+import { __RouterContext } from "react-router";
+
+import hoistStatics from "hoist-non-react-statics";
+
+export const WithRouterPropTypes = {
+ match: PropTypes.object.isRequired,
+ location: PropTypes.object.isRequired,
+ history: PropTypes.object.isRequired,
+};
+
+export const WithOptionalRouterPropTypes = {
+ match: PropTypes.object,
+ location: PropTypes.object,
+ history: PropTypes.object,
+};
+
+// This is copied from https://github.com/remix-run/react-router/blob/v5.3.4/packages/react-router/modules/withRouter.js
+// but does not fail if called outside of a React Router context
+export function withOptionalRouter(Component) {
+ const displayName = `withRouter(${Component.displayName || Component.name})`;
+ const C = props => {
+ const { wrappedComponentRef, ...remainingProps } = props;
+
+ return (
+ <__RouterContext.Consumer>
+ {context => {
+ if(context)
+ return (
+
+ );
+ else
+ return (
+
+ );
+ }}
+
+ );
+ };
+
+ C.displayName = displayName;
+ C.WrappedComponent = Component;
+ C.propTypes = {
+ wrappedComponentRef: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.func,
+ PropTypes.object
+ ])
+ };
+
+ return hoistStatics(C, Component);
+}
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index ad12fc3b3e..67b061e52e 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -2536,6 +2536,7 @@ $ui-header-height: 55px;
.navigation-panel__sign-in-banner,
.navigation-panel__logo,
+ .navigation-panel__banner,
.getting-started__trends {
display: none;
}
diff --git a/app/lib/activitypub/linked_data_signature.rb b/app/lib/activitypub/linked_data_signature.rb
index ea59879f3b..faea63e8f1 100644
--- a/app/lib/activitypub/linked_data_signature.rb
+++ b/app/lib/activitypub/linked_data_signature.rb
@@ -18,8 +18,8 @@ class ActivityPub::LinkedDataSignature
return unless type == 'RsaSignature2017'
- creator = ActivityPub::TagManager.instance.uri_to_actor(creator_uri)
- creator ||= ActivityPub::FetchRemoteKeyService.new.call(creator_uri, id: false)
+ creator = ActivityPub::TagManager.instance.uri_to_actor(creator_uri)
+ creator = ActivityPub::FetchRemoteKeyService.new.call(creator_uri, id: false) if creator&.public_key.blank?
return if creator.nil?
@@ -28,6 +28,8 @@ class ActivityPub::LinkedDataSignature
to_be_verified = options_hash + document_hash
creator if creator.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), Base64.decode64(signature), to_be_verified)
+ rescue OpenSSL::PKey::RSAError
+ false
end
def sign!(creator, sign_with: nil)
diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb
index 6aa5834aca..3412a076cc 100644
--- a/app/models/featured_tag.rb
+++ b/app/models/featured_tag.rb
@@ -51,7 +51,7 @@ class FeaturedTag < ApplicationRecord
private
def strip_name
- self.name = name&.strip&.gsub(/\A#/, '')
+ self.name = name&.strip&.delete_prefix('#')
end
def set_tag
diff --git a/app/serializers/rest/application_serializer.rb b/app/serializers/rest/application_serializer.rb
index ab68219ade..e4806a3c9f 100644
--- a/app/serializers/rest/application_serializer.rb
+++ b/app/serializers/rest/application_serializer.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class REST::ApplicationSerializer < ActiveModel::Serializer
- attributes :id, :name, :website, :redirect_uri,
+ attributes :id, :name, :website, :scopes, :redirect_uri,
:client_id, :client_secret, :vapid_key
def id
diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb
index c7bc93f781..1ea2e93e03 100644
--- a/app/services/activitypub/process_status_update_service.rb
+++ b/app/services/activitypub/process_status_update_service.rb
@@ -101,8 +101,6 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
end
end
- added_media_attachments = @next_media_attachments - previous_media_attachments
-
@status.ordered_media_attachment_ids = @next_media_attachments.map(&:id)
@media_attachments_changed = true if @status.ordered_media_attachment_ids != previous_media_attachments_ids
diff --git a/app/views/admin/disputes/appeals/_appeal.html.haml b/app/views/admin/disputes/appeals/_appeal.html.haml
index 3f6efb856e..d5611211ed 100644
--- a/app/views/admin/disputes/appeals/_appeal.html.haml
+++ b/app/views/admin/disputes/appeals/_appeal.html.haml
@@ -4,7 +4,7 @@
= image_tag appeal.account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar'
.log-entry__content
.log-entry__title
- = t(appeal.strike.action, scope: 'admin.strikes.actions', name: content_tag(:span, appeal.strike.account.username, class: 'username'), target: content_tag(:span, appeal.account.username, class: 'target')).html_safe
+ = strike_action_label(appeal)
.log-entry__timestamp
%time.formatted{ datetime: appeal.strike.created_at.iso8601 }
= l(appeal.strike.created_at)
diff --git a/app/views/admin/trends/links/_preview_card.html.haml b/app/views/admin/trends/links/_preview_card.html.haml
index 1ca3483715..ee3774790c 100644
--- a/app/views/admin/trends/links/_preview_card.html.haml
+++ b/app/views/admin/trends/links/_preview_card.html.haml
@@ -4,7 +4,7 @@
.batch-table__row__content.pending-account
.pending-account__header
- = link_to preview_card.title, preview_card.url
+ = link_to preview_card.title, url_for_preview_card(preview_card)
%br/
diff --git a/config/brakeman.ignore b/config/brakeman.ignore
deleted file mode 100644
index f1ef5ed3b1..0000000000
--- a/config/brakeman.ignore
+++ /dev/null
@@ -1,118 +0,0 @@
-{
- "ignored_warnings": [
- {
- "warning_type": "Cross-Site Scripting",
- "warning_code": 2,
- "fingerprint": "71cf98c8235b5cfa9946b5db8fdc1a2f3a862566abb34e4542be6f3acae78233",
- "check_name": "CrossSiteScripting",
- "message": "Unescaped model attribute",
- "file": "app/views/admin/disputes/appeals/_appeal.html.haml",
- "line": 7,
- "link": "https://brakemanscanner.org/docs/warning_types/cross_site_scripting",
- "code": "t((Unresolved Model).new.strike.action, :scope => \"admin.strikes.actions\", :name => content_tag(:span, (Unresolved Model).new.strike.account.username, :class => \"username\"), :target => content_tag(:span, (Unresolved Model).new.account.username, :class => \"target\"))",
- "render_path": [
- {
- "type": "template",
- "name": "admin/disputes/appeals/index",
- "line": 20,
- "file": "app/views/admin/disputes/appeals/index.html.haml",
- "rendered": {
- "name": "admin/disputes/appeals/_appeal",
- "file": "app/views/admin/disputes/appeals/_appeal.html.haml"
- }
- }
- ],
- "location": {
- "type": "template",
- "template": "admin/disputes/appeals/_appeal"
- },
- "user_input": "(Unresolved Model).new.strike",
- "confidence": "Weak",
- "cwe_id": [
- 79
- ],
- "note": ""
- },
- {
- "warning_type": "Cross-Site Scripting",
- "warning_code": 4,
- "fingerprint": "cd5cfd7f40037fbfa753e494d7129df16e358bfc43ef0da3febafbf4ee1ed3ac",
- "check_name": "LinkToHref",
- "message": "Potentially unsafe model attribute in `link_to` href",
- "file": "app/views/admin/trends/links/_preview_card.html.haml",
- "line": 7,
- "link": "https://brakemanscanner.org/docs/warning_types/link_to_href",
- "code": "link_to((Unresolved Model).new.title, (Unresolved Model).new.url)",
- "render_path": [
- {
- "type": "template",
- "name": "admin/trends/links/index",
- "line": 49,
- "file": "app/views/admin/trends/links/index.html.haml",
- "rendered": {
- "name": "admin/trends/links/_preview_card",
- "file": "app/views/admin/trends/links/_preview_card.html.haml"
- }
- }
- ],
- "location": {
- "type": "template",
- "template": "admin/trends/links/_preview_card"
- },
- "user_input": "(Unresolved Model).new.url",
- "confidence": "Weak",
- "cwe_id": [
- 79
- ],
- "note": ""
- },
- {
- "warning_type": "Mass Assignment",
- "warning_code": 105,
- "fingerprint": "d0511f0287aea4ed9511f5a744f880cb15af77a8ec88f81b7365b00b642cf427",
- "check_name": "PermitAttributes",
- "message": "Potentially dangerous key allowed for mass assignment",
- "file": "app/controllers/api/v1/reports_controller.rb",
- "line": 26,
- "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
- "code": "params.permit(:account_id, :comment, :category, :forward, :forward_to_domains => ([]), :status_ids => ([]), :rule_ids => ([]))",
- "render_path": null,
- "location": {
- "type": "method",
- "class": "Api::V1::ReportsController",
- "method": "report_params"
- },
- "user_input": ":account_id",
- "confidence": "High",
- "cwe_id": [
- 915
- ],
- "note": ""
- },
- {
- "warning_type": "Mass Assignment",
- "warning_code": 105,
- "fingerprint": "dd59382eb5fda8da4d29f5d52dc8261ed9070d3c61cecc12b8332e18448b1c11",
- "check_name": "PermitAttributes",
- "message": "Potentially dangerous key allowed for mass assignment",
- "file": "app/controllers/api/v2/search_controller.rb",
- "line": 42,
- "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
- "code": "params.permit(:type, :offset, :min_id, :max_id, :account_id, :following, :searchability)",
- "render_path": null,
- "location": {
- "type": "method",
- "class": "Api::V2::SearchController",
- "method": "search_params"
- },
- "user_input": ":account_id",
- "confidence": "High",
- "cwe_id": [
- 915
- ],
- "note": ""
- }
- ],
- "updated": "2023-07-30 22:53:30 +0900",
- "brakeman_version": "6.0.1"
-}
diff --git a/config/brakeman.yml b/config/brakeman.yml
index 95ebc13865..9ac95c8006 100644
--- a/config/brakeman.yml
+++ b/config/brakeman.yml
@@ -1,3 +1,5 @@
---
:skip_checks:
- CheckPermitAttributes
+:url_safe_methods:
+ - url_for_preview_card
diff --git a/config/initializers/3_omniauth.rb b/config/initializers/3_omniauth.rb
index 566e7362a5..d316c3b73a 100644
--- a/config/initializers/3_omniauth.rb
+++ b/config/initializers/3_omniauth.rb
@@ -9,9 +9,6 @@ Rails.application.config.middleware.use OmniAuth::Builder do
end
Devise.setup do |config|
- # Devise omniauth strategies
- options = {}
-
# CAS strategy
if ENV['CAS_ENABLED'] == 'true'
cas_options = {}
diff --git a/config/locales/activerecord.sk.yml b/config/locales/activerecord.sk.yml
index 33f53a88ed..d13c416e51 100644
--- a/config/locales/activerecord.sk.yml
+++ b/config/locales/activerecord.sk.yml
@@ -53,3 +53,7 @@ sk:
position:
elevated: nemôže byť vyššia ako vaša súčasná rola
own_role: nie je možné zmeniť s vašou aktuálnou rolou
+ webhook:
+ attributes:
+ events:
+ invalid_permissions: nemožno zahrnúť udalosti, ku ktorým nemáte práva
diff --git a/config/locales/doorkeeper.sk.yml b/config/locales/doorkeeper.sk.yml
index acfd59b3e7..91c9430b30 100644
--- a/config/locales/doorkeeper.sk.yml
+++ b/config/locales/doorkeeper.sk.yml
@@ -129,6 +129,7 @@ sk:
crypto: Šifrovanie End-to-end
favourites: Obľúbené
filters: Filtre
+ follow: Sledovanie, stlmenie a blokovanie
follows: Sledovania
lists: Zoznamy
media: Mediálne prílohy
@@ -148,9 +149,19 @@ sk:
scopes:
admin:read: prezeraj všetky dáta na serveri
admin:read:accounts: prezeraj chúlostivé informácie na všetkých účtoch
+ admin:read:canonical_email_blocks: čítať citlivé informácie všetkých kanonických e-mailových blokov
+ admin:read:domain_allows: čítať citlivé informácie zo všetkých povolených domén
+ admin:read:domain_blocks: čítať citlivé informácie zo všetkých blokov domén
+ admin:read:email_domain_blocks: čítať citlivé informácie zo všetkých blokov emailových domén
+ admin:read:ip_blocks: čítať citlivé informácie zo všetkých blokov IP
admin:read:reports: čítaj chulostivé informácie o všetkých hláseniach a nahlásených účtoch
admin:write: uprav všetky dáta na serveri
admin:write:accounts: urob moderovacie úkony na účtoch
+ admin:write:canonical_email_blocks: vykonať akcie moderácie na kanonických emailových blokoch
+ admin:write:domain_allows: vykonať akcie moderácie na povolených doménach
+ admin:write:domain_blocks: vykonať akcie moderácie na doménových blokoch
+ admin:write:email_domain_blocks: vykonať akcie moderácie na blokoch emailových domén
+ admin:write:ip_blocks: vykonať akcie moderácie na blokoch IP
admin:write:reports: urob moderovacie úkony voči hláseniam
crypto: používať end-to-end šifrovanie
follow: uprav vzťahy svojho účtu
@@ -159,6 +170,7 @@ sk:
read:accounts: prezri si informácie o účte
read:blocks: prezri svoje bloky
read:bookmarks: pozri svoje záložky
+ read:favourites: zobraziť vaše obľúbené
read:filters: prezri svoje filtrovanie
read:follows: prezri si svoje sledovania
read:lists: prezri si svoje zoznamy
diff --git a/config/locales/fi.yml b/config/locales/fi.yml
index cdc5d12678..09d2a68779 100644
--- a/config/locales/fi.yml
+++ b/config/locales/fi.yml
@@ -33,7 +33,7 @@ fi:
accounts:
add_email_domain_block: Estä sähköpostiverkkotunnus
approve: Hyväksy
- approved_msg: Käyttäjän %{username} liittymishakemus hyväksyttiin
+ approved_msg: Käyttäjän %{username} rekisteröitymishakemus hyväksyttiin
are_you_sure: Oletko varma?
avatar: Profiilikuva
by_domain: Verkkotunnus
@@ -364,7 +364,7 @@ fi:
other: "
%{count} odottavaa käyttäjää"
resolved_reports: ratkaistut raportit
software: Ohjelmisto
- sources: Rekisteröitymisen lähteet
+ sources: Rekisteröitymislähteet
space: Tilankäyttö
title: Hallintapaneeli
top_languages: Aktiivisimmat kielet
@@ -440,7 +440,7 @@ fi:
title: Estä uusi sähköpostiverkkotunnus
no_email_domain_block_selected: Sähköpostin verkkotunnuksia ei muutettu, koska yhtään ei ollut valittuna
not_permitted: Ei sallittu
- resolved_dns_records_hint_html: Verkkotunnuksen nimi määräytyy seuraaviin MX-verkkotunnuksiin, jotka ovat viime kädessä vastuussa sähköpostin vastaanottamisesta. MX-verkkotunnuksen estäminen estää kirjautumisen mistä tahansa sähköpostiosoitteesta, joka käyttää samaa MX-verkkotunnusta, vaikka näkyvä verkkotunnuksen nimi olisikin erilainen.
Varo estämästä suuria sähköpostin palveluntarjoajia.
+ resolved_dns_records_hint_html: Verkkotunnuksen nimi määräytyy seuraaviin MX-verkkotunnuksiin, jotka ovat viime kädessä vastuussa sähköpostin vastaanottamisesta. MX-verkkotunnuksen estäminen estää rekisteröitymisen mistä tahansa sähköpostiosoitteesta, joka käyttää samaa MX-verkkotunnusta, vaikka näkyvä verkkotunnuksen nimi olisikin erilainen.
Varo estämästä suuria sähköpostin palveluntarjoajia.
resolved_through_html: Ratkaistu %{domain} kautta
title: Estetyt sähköpostiverkkotunnukset
export_domain_allows:
@@ -1405,7 +1405,7 @@ fi:
migrations:
acct: Muuttanut tunnukselle
cancel: Peruuta uudelleenohjaus
- cancel_explanation: Uudelleenohjauksen peruuttaminen aktivoi uudelleen nykyisen tilisi, mutta ei palauta seuraajia, jotka on siirretty kyseiselle tilille.
+ cancel_explanation: Uudelleenohjauksen peruuttaminen aktivoi nykyisen tilisi uudelleen mutta ei palauta seuraajia, jotka on siirretty kyseiselle tilille.
cancelled_msg: Uudelleenohjaus peruttu onnistuneesti.
errors:
already_moved: on sama tili, jonka olet jo siirtänyt
diff --git a/config/locales/nn.yml b/config/locales/nn.yml
index 9f34e56216..1c187c1de1 100644
--- a/config/locales/nn.yml
+++ b/config/locales/nn.yml
@@ -570,7 +570,7 @@ nn:
enabled: Skrudd på
inbox_url: Overførings-URL
pending: Avventer overgangens godkjenning
- save_and_enable: Lagr og slå på
+ save_and_enable: Lagre og slå på
setup: Sett opp en overgangsforbindelse
signatures_not_enabled: Overgangar fungerer ikkje så lenge sikker- eller kvitlistingsmodus er aktivert
status: Status
@@ -1280,7 +1280,7 @@ nn:
deselect: Vel ingen
none: Ingen
order_by: Sorter etter
- save_changes: Lagr endringar
+ save_changes: Lagre endringar
select_all_matching_items:
one: Vel %{count} element som passar til søket ditt.
other: Vel %{count} element som passar til søket ditt.
diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml
index 6c8875327b..403162b820 100644
--- a/config/locales/simple_form.fi.yml
+++ b/config/locales/simple_form.fi.yml
@@ -59,14 +59,14 @@ fi:
setting_display_media_default: Piilota arkaluonteiseksi merkitty media
setting_display_media_hide_all: Piilota media aina
setting_display_media_show_all: Näytä media aina
- setting_use_blurhash: Liukuvärit perustuvat piilotettujen kuvien väreihin, mutta sumentavat yksityiskohdat
+ setting_use_blurhash: Liukuvärit perustuvat piilotettujen kuvien väreihin mutta sumentavat yksityiskohdat
setting_use_pending_items: Piilota aikajanan päivitykset napsautuksen taakse syötteen automaattisen vierityksen sijaan
username: Voit käyttää kirjaimia, numeroita ja alaviivoja
whole_word: Kun avainsana tai -fraasi on kokonaan aakkosnumeerinen, se on voimassa vain, jos se vastaa koko sanaa
domain_allow:
domain: Tämä verkkotunnus voi noutaa tietoja tältä palvelimelta ja sieltä saapuvat tiedot käsitellään ja tallennetaan
email_domain_block:
- domain: Tämä voi olla se verkkotunnus, joka näkyy sähköpostiosoitteessa tai MX tietueessa jota se käyttää. Ne tarkistetaan rekisteröitymisen yhteydessä.
+ domain: Tämä voi olla verkkotunnus, joka näkyy sähköpostiosoitteessa tai sen käyttämässä MX-tietueessa. Ne tarkistetaan rekisteröitymisen yhteydessä.
with_dns_records: Annetun verkkotunnuksen DNS-tietueet yritetään ratkaista ja tulokset myös estetään
featured_tag:
name: 'Tässä muutamia hiljattain käyttämiäsi aihetunnisteita:'
@@ -112,7 +112,7 @@ fi:
ip: Kirjoita IPv4- tai IPv6-osoite. Voit estää kokonaisia alueita käyttämällä CIDR-syntaksia. Varo, että et lukitse itseäsi ulos!
severities:
no_access: Estä pääsy kaikkiin resursseihin
- sign_up_block: Uudet kirjautumiset eivät ole mahdollisia
+ sign_up_block: Uudet rekisteröitymiset eivät ole mahdollisia
sign_up_requires_approval: Uudet rekisteröitymiset edellyttävät hyväksyntääsi
severity: Valitse, mitä tapahtuu tämän IP-osoitteen pyynnöille
rule:
diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml
index c48c659fa6..4580e772fc 100644
--- a/config/locales/simple_form.zh-TW.yml
+++ b/config/locales/simple_form.zh-TW.yml
@@ -30,7 +30,7 @@ zh-TW:
suspend: 禁止所有對該帳號任何互動,並且刪除其內容。三十天內可以撤銷此動作。關閉所有對此帳號之檢舉報告。
warning_preset_id: 選用。您仍可在預設的結尾新增自訂文字
announcement:
- all_day: 核取後,只會顯示出時間範圍中的日期部分
+ all_day: 當選取時,僅顯示出時間範圍中的日期部分
ends_at: 可選的,公告會於該時間點自動取消發布
scheduled_at: 空白則立即發布公告
starts_at: 可選的,讓公告在特定時間範圍內顯示
@@ -60,7 +60,7 @@ zh-TW:
setting_display_media_hide_all: 總是隱藏所有媒體
setting_display_media_show_all: 總是顯示標為敏感內容的媒體
setting_use_blurhash: 彩色漸層圖樣是基於隱藏媒體內容顏色產生,所有細節將變得模糊
- setting_use_pending_items: 關閉自動捲動更新,時間軸只會於點擊後更新
+ setting_use_pending_items: 關閉自動捲動更新,時間軸僅於點擊後更新
username: 您可以使用字幕、數字與底線
whole_word: 如果關鍵字或詞組僅有字母與數字,則其將只在符合整個單字的時候才會套用
domain_allow:
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index e139742ff1..fd93481509 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -578,7 +578,7 @@ zh-TW:
mark_as_sensitive_description_html: 被檢舉的嘟文中的媒體將會被標記為敏感內容,並將會記錄一次警告,以協助您升級同一帳號未來的違規行為。
other_description_html: 檢視更多控制帳號行為以及自訂檢舉帳號通知之選項。
resolve_description_html: 被檢舉的帳號將不被採取任何行動,不會加以刪除線標記,並且此份報告將被關閉。
- silence_description_html: 此帳號僅會對已跟隨帳號之使用者或手動查詢可見,將大幅度限制觸及範圍。此設定可隨時被還原。關閉所有對此帳號之檢舉報告。
+ silence_description_html: 此帳號僅對已跟隨帳號之使用者或手動查詢可見,將大幅度限制觸及範圍。此設定可隨時被還原。關閉所有對此帳號之檢舉報告。
suspend_description_html: 此帳號及其所有內容將不可被存取並且最終被移除,並且無法與之進行互動。三十天內可以撤銷此動作。關閉所有對此帳號之檢舉報告。
actions_description_html: 決定應對此報告採取何種行動。若您對檢舉之帳號採取懲罰措施,則將對他們發送 e-mail 通知,如非選擇了
垃圾郵件 類別。
actions_description_remote_html: 決定將對此檢舉報告採取何種動作。這將僅作用於
您的伺服器與此遠端帳號及其內容之通訊行為。
diff --git a/db/migrate/.rubocop.yml b/db/migrate/.rubocop.yml
new file mode 100644
index 0000000000..4e23800dd1
--- /dev/null
+++ b/db/migrate/.rubocop.yml
@@ -0,0 +1,4 @@
+inherit_from: ../../.rubocop.yml
+
+Naming/VariableNumber:
+ CheckSymbols: false
diff --git a/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb b/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb
index 7301e960d5..c9f0849557 100644
--- a/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb
+++ b/db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb
@@ -19,7 +19,6 @@ class AddSilencedAtSuspendedAtToAccounts < ActiveRecord::Migration[5.2]
# Record suspend date of blocks and silences for users whose limitations match
# a domain block
DomainBlock.where(severity: [:silence, :suspend]).find_each do |block|
- scope = block.accounts
if block.suspend?
block.accounts.where(suspended: true).in_batches.update_all(suspended_at: block.created_at)
else
diff --git a/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb b/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb
index 615f35cd0d..7788431cd5 100644
--- a/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb
+++ b/db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb
@@ -18,7 +18,6 @@ class RemoveSuspendedSilencedAccountFields < ActiveRecord::Migration[5.2]
# Record suspend date of blocks and silences for users whose limitations match
# a domain block
DomainBlock.where(severity: [:silence, :suspend]).find_each do |block|
- scope = block.accounts
if block.suspend?
block.accounts.where(suspended: true).in_batches.update_all(suspended_at: block.created_at)
else
diff --git a/package.json b/package.json
index 902dd8e878..a75e68414a 100644
--- a/package.json
+++ b/package.json
@@ -79,6 +79,7 @@
"fuzzysort": "^2.0.4",
"glob": "^10.2.6",
"history": "^4.10.1",
+ "hoist-non-react-statics": "^3.3.2",
"http-link-header": "^1.1.1",
"immutable": "^4.3.0",
"imports-loader": "^1.2.0",
@@ -112,8 +113,8 @@
"react-overlays": "^5.2.1",
"react-redux": "^8.0.4",
"react-redux-loading-bar": "^5.0.4",
- "react-router": "^4.3.1",
- "react-router-dom": "^4.1.1",
+ "react-router": "^5.3.4",
+ "react-router-dom": "^5.3.4",
"react-router-scroll-4": "^1.0.0-beta.1",
"react-select": "^5.7.3",
"react-sparklines": "^1.7.0",
@@ -159,6 +160,7 @@
"@types/emoji-mart": "^3.0.9",
"@types/escape-html": "^1.0.2",
"@types/express": "^4.17.17",
+ "@types/hoist-non-react-statics": "^3.3.1",
"@types/http-link-header": "^1.0.3",
"@types/intl": "^1.2.0",
"@types/jest": "^29.5.2",
@@ -175,6 +177,7 @@
"@types/react-immutable-proptypes": "^2.1.0",
"@types/react-motion": "^0.0.35",
"@types/react-overlays": "^3.1.0",
+ "@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
"@types/react-select": "^5.0.1",
"@types/react-sparklines": "^1.7.2",
diff --git a/spec/controllers/admin/disputes/appeals_controller_spec.rb b/spec/controllers/admin/disputes/appeals_controller_spec.rb
index 4afe074921..3f4175a281 100644
--- a/spec/controllers/admin/disputes/appeals_controller_spec.rb
+++ b/spec/controllers/admin/disputes/appeals_controller_spec.rb
@@ -18,10 +18,14 @@ RSpec.describe Admin::Disputes::AppealsController do
describe 'GET #index' do
let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
- it 'lists appeals' do
+ before { appeal }
+
+ it 'returns a page that lists details of appeals' do
get :index
- expect(response).to have_http_status(200)
+ expect(response).to have_http_status(:success)
+ expect(response.body).to include("
#{strike.account.username}")
+ expect(response.body).to include("
#{appeal.account.username}")
end
end
diff --git a/spec/controllers/concerns/account_controller_concern_spec.rb b/spec/controllers/concerns/account_controller_concern_spec.rb
index d080475c32..56ffcfb047 100644
--- a/spec/controllers/concerns/account_controller_concern_spec.rb
+++ b/spec/controllers/concerns/account_controller_concern_spec.rb
@@ -62,7 +62,7 @@ describe AccountControllerConcern do
end
it 'sets link headers' do
- account = Fabricate(:account, username: 'username')
+ Fabricate(:account, username: 'username')
get 'success', params: { account_username: 'username' }
expect(response.headers['Link'].to_s).to eq '
; rel="lrdd"; type="application/jrd+json", ; rel="alternate"; type="application/activity+json"'
end
diff --git a/spec/helpers/admin/disputes_helper_spec.rb b/spec/helpers/admin/disputes_helper_spec.rb
new file mode 100644
index 0000000000..5f9a85df86
--- /dev/null
+++ b/spec/helpers/admin/disputes_helper_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Admin::DisputesHelper do
+ describe 'strike_action_label' do
+ it 'returns html describing the appeal' do
+ adam = Account.new(username: 'Adam')
+ becky = Account.new(username: 'Becky')
+ strike = AccountWarning.new(account: adam, action: :suspend)
+ appeal = Appeal.new(strike: strike, account: becky)
+
+ expected = <<~OUTPUT.strip
+ Adam suspended Becky's account
+ OUTPUT
+ result = helper.strike_action_label(appeal)
+
+ expect(result).to eq(expected)
+ end
+ end
+end
diff --git a/spec/helpers/jsonld_helper_spec.rb b/spec/helpers/jsonld_helper_spec.rb
index 1f1a7fd891..e4c1e0cec4 100644
--- a/spec/helpers/jsonld_helper_spec.rb
+++ b/spec/helpers/jsonld_helper_spec.rb
@@ -179,14 +179,14 @@ describe JsonLdHelper do
it 'deems a safe compacting as such' do
json['object'].delete('convo')
compacted = compact(json)
- deemed_compatible = patch_for_forwarding!(json, compacted)
+ patch_for_forwarding!(json, compacted)
expect(compacted['to']).to eq ['https://www.w3.org/ns/activitystreams#Public']
expect(safe_for_forwarding?(json, compacted)).to be true
end
it 'deems an unsafe compacting as such' do
compacted = compact(json)
- deemed_compatible = patch_for_forwarding!(json, compacted)
+ patch_for_forwarding!(json, compacted)
expect(compacted['to']).to eq ['https://www.w3.org/ns/activitystreams#Public']
expect(safe_for_forwarding?(json, compacted)).to be false
end
diff --git a/spec/lib/activitypub/linked_data_signature_spec.rb b/spec/lib/activitypub/linked_data_signature_spec.rb
index d5b713b347..97268eea6d 100644
--- a/spec/lib/activitypub/linked_data_signature_spec.rb
+++ b/spec/lib/activitypub/linked_data_signature_spec.rb
@@ -34,6 +34,40 @@ RSpec.describe ActivityPub::LinkedDataSignature do
end
end
+ context 'when local account record is missing a public key' do
+ let(:raw_signature) do
+ {
+ 'creator' => 'http://example.com/alice',
+ 'created' => '2017-09-23T20:21:34Z',
+ }
+ end
+
+ let(:signature) { raw_signature.merge('type' => 'RsaSignature2017', 'signatureValue' => sign(sender, raw_signature, raw_json)) }
+
+ let(:service_stub) { instance_double(ActivityPub::FetchRemoteKeyService) }
+
+ before do
+ # Ensure signature is computed with the old key
+ signature
+
+ # Unset key
+ old_key = sender.public_key
+ sender.update!(private_key: '', public_key: '')
+
+ allow(ActivityPub::FetchRemoteKeyService).to receive(:new).and_return(service_stub)
+
+ allow(service_stub).to receive(:call).with('http://example.com/alice', id: false) do
+ sender.update!(public_key: old_key)
+ sender
+ end
+ end
+
+ it 'fetches key and returns creator' do
+ expect(subject.verify_actor!).to eq sender
+ expect(service_stub).to have_received(:call).with('http://example.com/alice', id: false).once
+ end
+ end
+
context 'when signature is missing' do
let(:signature) { nil }
diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb
index 5ecea5ea16..2c8c994712 100644
--- a/spec/lib/mastodon/cli/accounts_spec.rb
+++ b/spec/lib/mastodon/cli/accounts_spec.rb
@@ -1356,4 +1356,254 @@ describe Mastodon::CLI::Accounts do
end
end
end
+
+ describe '#prune' do
+ let!(:local_account) { Fabricate(:account) }
+ let!(:bot_account) { Fabricate(:account, bot: true, domain: 'example.com') }
+ let!(:group_account) { Fabricate(:account, actor_type: 'Group', domain: 'example.com') }
+ let!(:mentioned_account) { Fabricate(:account, domain: 'example.com') }
+ let!(:prunable_accounts) do
+ Fabricate.times(3, :account, domain: 'example.com', bot: false, suspended_at: nil, silenced_at: nil)
+ end
+
+ before do
+ Fabricate(:mention, account: mentioned_account, status: Fabricate(:status, account: Fabricate(:account)))
+ stub_parallelize_with_progress!
+ end
+
+ it 'prunes all remote accounts with no interactions with local users' do
+ cli.prune
+
+ prunable_account_ids = prunable_accounts.pluck(:id)
+
+ expect(Account.where(id: prunable_account_ids).count).to eq(0)
+ end
+
+ it 'displays a successful message' do
+ expect { cli.prune }.to output(
+ a_string_including("OK, pruned #{prunable_accounts.size} accounts")
+ ).to_stdout
+ end
+
+ it 'does not prune local accounts' do
+ cli.prune
+
+ expect(Account.exists?(id: local_account.id)).to be(true)
+ end
+
+ it 'does not prune bot accounts' do
+ cli.prune
+
+ expect(Account.exists?(id: bot_account.id)).to be(true)
+ end
+
+ it 'does not prune group accounts' do
+ cli.prune
+
+ expect(Account.exists?(id: group_account.id)).to be(true)
+ end
+
+ it 'does not prune accounts that have been mentioned' do
+ cli.prune
+
+ expect(Account.exists?(id: mentioned_account.id)).to be true
+ end
+
+ context 'with --dry-run option' do
+ before do
+ cli.options = { dry_run: true }
+ end
+
+ it 'does not prune any account' do
+ cli.prune
+
+ prunable_account_ids = prunable_accounts.pluck(:id)
+
+ expect(Account.where(id: prunable_account_ids).count).to eq(prunable_accounts.size)
+ end
+
+ it 'displays a successful message with (DRY RUN)' do
+ expect { cli.prune }.to output(
+ a_string_including("OK, pruned #{prunable_accounts.size} accounts (DRY RUN)")
+ ).to_stdout
+ end
+ end
+ end
+
+ describe '#migrate' do
+ let!(:source_account) { Fabricate(:account) }
+ let!(:target_account) { Fabricate(:account, domain: 'example.com') }
+ let(:arguments) { [source_account.username] }
+ let(:resolve_account_service) { instance_double(ResolveAccountService, call: nil) }
+ let(:move_service) { instance_double(MoveService, call: nil) }
+
+ before do
+ allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service)
+ allow(MoveService).to receive(:new).and_return(move_service)
+ end
+
+ shared_examples 'a successful migration' do
+ it 'calls the MoveService for the last migration' do
+ cli.invoke(:migrate, arguments, options)
+
+ last_migration = source_account.migrations.last
+
+ expect(move_service).to have_received(:call).with(last_migration).once
+ end
+
+ it 'displays a successful message' do
+ expect { cli.invoke(:migrate, arguments, options) }.to output(
+ a_string_including("OK, migrated #{source_account.acct} to #{target_account.acct}")
+ ).to_stdout
+ end
+ end
+
+ context 'when both --replay and --target options are given' do
+ let(:options) { { replay: true, target: "#{target_account.username}@example.com" } }
+
+ it 'exits with an error message indicating that using both options is not possible' do
+ expect { cli.invoke(:migrate, arguments, options) }.to output(
+ a_string_including('Use --replay or --target, not both')
+ ).to_stdout
+ .and raise_error(SystemExit)
+ end
+ end
+
+ context 'when no option is given' do
+ it 'exits with an error message indicating that at least one option must be used' do
+ expect { cli.invoke(:migrate, arguments, {}) }.to output(
+ a_string_including('Use either --replay or --target')
+ ).to_stdout
+ .and raise_error(SystemExit)
+ end
+ end
+
+ context 'when the given username is not found' do
+ let(:arguments) { ['non_existent_username'] }
+
+ it 'exits with an error message indicating that there is no such account' do
+ expect { cli.invoke(:migrate, arguments, replay: true) }.to output(
+ a_string_including("No such account: #{arguments.first}")
+ ).to_stdout
+ .and raise_error(SystemExit)
+ end
+ end
+
+ context 'with --replay option' do
+ let(:options) { { replay: true } }
+
+ context 'when the specified account has no previous migrations' do
+ it 'exits with an error message indicating that the given account has no previous migrations' do
+ expect { cli.invoke(:migrate, arguments, options) }.to output(
+ a_string_including('The specified account has not performed any migration')
+ ).to_stdout
+ .and raise_error(SystemExit)
+ end
+ end
+
+ context 'when the specified account has a previous migration' do
+ before do
+ allow(resolve_account_service).to receive(:call).with(source_account.acct, any_args).and_return(source_account)
+ allow(resolve_account_service).to receive(:call).with(target_account.acct, any_args).and_return(target_account)
+ target_account.aliases.create!(acct: source_account.acct)
+ source_account.migrations.create!(acct: target_account.acct)
+ source_account.update!(moved_to_account: target_account)
+ end
+
+ it_behaves_like 'a successful migration'
+
+ context 'when the specified account is redirecting to a different target account' do
+ before do
+ source_account.update!(moved_to_account: nil)
+ end
+
+ it 'exits with an error message' do
+ expect { cli.invoke(:migrate, arguments, options) }.to output(
+ a_string_including('The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway')
+ ).to_stdout
+ .and raise_error(SystemExit)
+ end
+ end
+
+ context 'with --force option' do
+ let(:options) { { replay: true, force: true } }
+
+ it_behaves_like 'a successful migration'
+ end
+ end
+ end
+
+ context 'with --target option' do
+ let(:options) { { target: target_account.acct } }
+
+ before do
+ allow(resolve_account_service).to receive(:call).with(source_account.acct, any_args).and_return(source_account)
+ allow(resolve_account_service).to receive(:call).with(target_account.acct, any_args).and_return(target_account)
+ end
+
+ context 'when the specified target account is not found' do
+ before do
+ allow(resolve_account_service).to receive(:call).with(target_account.acct).and_return(nil)
+ end
+
+ it 'exits with an error message indicating that there is no such account' do
+ expect { cli.invoke(:migrate, arguments, options) }.to output(
+ a_string_including("The specified target account could not be found: #{options[:target]}")
+ ).to_stdout
+ .and raise_error(SystemExit)
+ end
+ end
+
+ context 'when the specified target account exists' do
+ before do
+ target_account.aliases.create!(acct: source_account.acct)
+ end
+
+ it 'creates a migration for the specified account with the target account' do
+ cli.invoke(:migrate, arguments, options)
+
+ last_migration = source_account.migrations.last
+
+ expect(last_migration.acct).to eq(target_account.acct)
+ end
+
+ it_behaves_like 'a successful migration'
+ end
+
+ context 'when the migration record is invalid' do
+ it 'exits with an error indicating that the validation failed' do
+ expect { cli.invoke(:migrate, arguments, options) }.to output(
+ a_string_including('Error: Validation failed')
+ ).to_stdout
+ .and raise_error(SystemExit)
+ end
+ end
+
+ context 'when the specified account is redirecting to a different target account' do
+ before do
+ allow(Account).to receive(:find_local).with(source_account.username).and_return(source_account)
+ allow(source_account).to receive(:moved_to_account_id).and_return(-1)
+ end
+
+ it 'exits with an error message' do
+ expect { cli.invoke(:migrate, arguments, options) }.to output(
+ a_string_including('The specified account is redirecting to a different target account. Use --force if you want to change the migration target')
+ ).to_stdout
+ .and raise_error(SystemExit)
+ end
+ end
+
+ context 'with --target and --force options' do
+ let(:options) { { target: target_account.acct, force: true } }
+
+ before do
+ target_account.aliases.create!(acct: source_account.acct)
+ allow(Account).to receive(:find_local).with(source_account.username).and_return(source_account)
+ allow(source_account).to receive(:moved_to_account_id).and_return(-1)
+ end
+
+ it_behaves_like 'a successful migration'
+ end
+ end
+ end
end
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index c13d57c761..96f2fb514c 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -356,7 +356,7 @@ RSpec.describe Account do
end
it 'does not return suspended users' do
- match = Fabricate(
+ Fabricate(
:account,
display_name: 'Display Name',
username: 'username',
@@ -483,7 +483,7 @@ RSpec.describe Account do
end
it 'does not return non-followed accounts' do
- match = Fabricate(
+ Fabricate(
:account,
display_name: 'A & l & i & c & e',
username: 'username',
@@ -495,7 +495,7 @@ RSpec.describe Account do
end
it 'does not return suspended users' do
- match = Fabricate(
+ Fabricate(
:account,
display_name: 'Display Name',
username: 'username',
@@ -535,7 +535,7 @@ RSpec.describe Account do
end
it 'does not return suspended users' do
- match = Fabricate(
+ Fabricate(
:account,
display_name: 'Display Name',
username: 'username',
@@ -719,10 +719,10 @@ RSpec.describe Account do
context 'when is local' do
it 'is invalid if the username is not unique in case-insensitive comparison among local accounts' do
- account_1 = Fabricate(:account, username: 'the_doctor')
- account_2 = Fabricate.build(:account, username: 'the_Doctor')
- account_2.valid?
- expect(account_2).to model_have_error_on_field(:username)
+ _account = Fabricate(:account, username: 'the_doctor')
+ non_unique_account = Fabricate.build(:account, username: 'the_Doctor')
+ non_unique_account.valid?
+ expect(non_unique_account).to model_have_error_on_field(:username)
end
it 'is invalid if the username is reserved' do
@@ -743,9 +743,9 @@ RSpec.describe Account do
end
it 'is valid if we are creating a possibly-conflicting instance actor account' do
- account_1 = Fabricate(:account, username: 'examplecom')
- account_2 = Fabricate.build(:account, id: -99, actor_type: 'Application', locked: true, username: 'example.com')
- expect(account_2.valid?).to be true
+ _account = Fabricate(:account, username: 'examplecom')
+ instance_account = Fabricate.build(:account, id: -99, actor_type: 'Application', locked: true, username: 'example.com')
+ expect(instance_account.valid?).to be true
end
it 'is invalid if the username doesn\'t only contains letters, numbers and underscores' do
@@ -877,17 +877,17 @@ RSpec.describe Account do
describe 'remote' do
it 'returns an array of accounts who have a domain' do
- account_1 = Fabricate(:account, domain: nil)
- account_2 = Fabricate(:account, domain: 'example.com')
- expect(described_class.remote).to contain_exactly(account_2)
+ _account = Fabricate(:account, domain: nil)
+ account_with_domain = Fabricate(:account, domain: 'example.com')
+ expect(described_class.remote).to contain_exactly(account_with_domain)
end
end
describe 'local' do
it 'returns an array of accounts who do not have a domain' do
- account_1 = Fabricate(:account, domain: nil)
- account_2 = Fabricate(:account, domain: 'example.com')
- expect(described_class.where('id > 0').local).to contain_exactly(account_1)
+ local_account = Fabricate(:account, domain: nil)
+ _account_with_domain = Fabricate(:account, domain: 'example.com')
+ expect(described_class.where('id > 0').local).to contain_exactly(local_account)
end
end
@@ -911,17 +911,17 @@ RSpec.describe Account do
describe 'silenced' do
it 'returns an array of accounts who are silenced' do
- account_1 = Fabricate(:account, silenced: true)
- account_2 = Fabricate(:account, silenced: false)
- expect(described_class.silenced).to contain_exactly(account_1)
+ silenced_account = Fabricate(:account, silenced: true)
+ _account = Fabricate(:account, silenced: false)
+ expect(described_class.silenced).to contain_exactly(silenced_account)
end
end
describe 'suspended' do
it 'returns an array of accounts who are suspended' do
- account_1 = Fabricate(:account, suspended: true)
- account_2 = Fabricate(:account, suspended: false)
- expect(described_class.suspended).to contain_exactly(account_1)
+ suspended_account = Fabricate(:account, suspended: true)
+ _account = Fabricate(:account, suspended: false)
+ expect(described_class.suspended).to contain_exactly(suspended_account)
end
end
diff --git a/spec/models/domain_block_spec.rb b/spec/models/domain_block_spec.rb
index 67f53fa785..d595441fd3 100644
--- a/spec/models/domain_block_spec.rb
+++ b/spec/models/domain_block_spec.rb
@@ -11,10 +11,10 @@ RSpec.describe DomainBlock do
end
it 'is invalid if the same normalized domain already exists' do
- domain_block_1 = Fabricate(:domain_block, domain: 'にゃん')
- domain_block_2 = Fabricate.build(:domain_block, domain: 'xn--r9j5b5b')
- domain_block_2.valid?
- expect(domain_block_2).to model_have_error_on_field(:domain)
+ _domain_block = Fabricate(:domain_block, domain: 'にゃん')
+ domain_block_with_normalized_value = Fabricate.build(:domain_block, domain: 'xn--r9j5b5b')
+ domain_block_with_normalized_value.valid?
+ expect(domain_block_with_normalized_value).to model_have_error_on_field(:domain)
end
end
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index 136b301933..153d6db25c 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -341,7 +341,7 @@ RSpec.describe Status do
describe '#replies_count' do
it 'is the number of replies' do
- reply = Fabricate(:status, account: bob, thread: subject)
+ Fabricate(:status, account: bob, thread: subject)
expect(subject.replies_count).to eq 1
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index bb61c02a63..92ce87e369 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -55,17 +55,17 @@ RSpec.describe User do
describe 'scopes' do
describe 'recent' do
it 'returns an array of recent users ordered by id' do
- user_1 = Fabricate(:user)
- user_2 = Fabricate(:user)
- expect(described_class.recent).to eq [user_2, user_1]
+ first_user = Fabricate(:user)
+ second_user = Fabricate(:user)
+ expect(described_class.recent).to eq [second_user, first_user]
end
end
describe 'confirmed' do
it 'returns an array of users who are confirmed' do
- user_1 = Fabricate(:user, confirmed_at: nil)
- user_2 = Fabricate(:user, confirmed_at: Time.zone.now)
- expect(described_class.confirmed).to contain_exactly(user_2)
+ Fabricate(:user, confirmed_at: nil)
+ confirmed_user = Fabricate(:user, confirmed_at: Time.zone.now)
+ expect(described_class.confirmed).to contain_exactly(confirmed_user)
end
end
diff --git a/spec/models/webauthn_credentials_spec.rb b/spec/models/webauthn_credentials_spec.rb
index 4579ebb82e..9631245e11 100644
--- a/spec/models/webauthn_credentials_spec.rb
+++ b/spec/models/webauthn_credentials_spec.rb
@@ -37,7 +37,7 @@ RSpec.describe WebauthnCredential do
end
it 'is invalid if already exist a webauthn credential with the same external id' do
- existing_webauthn_credential = Fabricate(:webauthn_credential, external_id: '_Typ0ygudDnk9YUVWLQayw')
+ Fabricate(:webauthn_credential, external_id: '_Typ0ygudDnk9YUVWLQayw')
new_webauthn_credential = Fabricate.build(:webauthn_credential, external_id: '_Typ0ygudDnk9YUVWLQayw')
new_webauthn_credential.valid?
@@ -47,7 +47,7 @@ RSpec.describe WebauthnCredential do
it 'is invalid if user already registered a webauthn credential with the same nickname' do
user = Fabricate(:user)
- existing_webauthn_credential = Fabricate(:webauthn_credential, user_id: user.id, nickname: 'USB Key')
+ Fabricate(:webauthn_credential, user_id: user.id, nickname: 'USB Key')
new_webauthn_credential = Fabricate.build(:webauthn_credential, user_id: user.id, nickname: 'USB Key')
new_webauthn_credential.valid?
diff --git a/spec/requests/api/v1/apps/credentials_spec.rb b/spec/requests/api/v1/apps/credentials_spec.rb
index 1268b36f8a..e1455fe799 100644
--- a/spec/requests/api/v1/apps/credentials_spec.rb
+++ b/spec/requests/api/v1/apps/credentials_spec.rb
@@ -9,7 +9,8 @@ describe 'Credentials' do
end
context 'with an oauth token' do
- let(:token) { Fabricate(:accessible_access_token, scopes: 'read', application: Fabricate(:application)) }
+ let(:application) { Fabricate(:application, scopes: 'read') }
+ let(:token) { Fabricate(:accessible_access_token, application: application) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
it 'returns the app information correctly', :aggregate_failures do
@@ -21,7 +22,35 @@ describe 'Credentials' do
a_hash_including(
name: token.application.name,
website: token.application.website,
- vapid_key: Rails.configuration.x.vapid_public_key
+ vapid_key: Rails.configuration.x.vapid_public_key,
+ scopes: token.application.scopes.map(&:to_s),
+ client_id: token.application.uid
+ )
+ )
+ end
+ end
+
+ context 'with a non-read scoped oauth token' do
+ let(:application) { Fabricate(:application, scopes: 'admin:write') }
+ let(:token) { Fabricate(:accessible_access_token, application: application) }
+ let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
+
+ it 'returns http success' do
+ subject
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns the app information correctly' do
+ subject
+
+ expect(body_as_json).to match(
+ a_hash_including(
+ name: token.application.name,
+ website: token.application.website,
+ vapid_key: Rails.configuration.x.vapid_public_key,
+ scopes: token.application.scopes.map(&:to_s),
+ client_id: token.application.uid
)
)
end
@@ -36,5 +65,49 @@ describe 'Credentials' do
expect(response).to have_http_status(401)
end
end
+
+ context 'with a revoked oauth token' do
+ let(:application) { Fabricate(:application, scopes: 'read') }
+ let(:token) { Fabricate(:accessible_access_token, application: application, revoked_at: DateTime.now.utc) }
+ let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
+
+ it 'returns http authorization error' do
+ subject
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns the error in the json response' do
+ subject
+
+ expect(body_as_json).to match(
+ a_hash_including(
+ error: 'The access token was revoked'
+ )
+ )
+ end
+ end
+
+ context 'with an invalid oauth token' do
+ let(:application) { Fabricate(:application, scopes: 'read') }
+ let(:token) { Fabricate(:accessible_access_token, application: application) }
+ let(:headers) { { 'Authorization' => "Bearer #{token.token}-invalid" } }
+
+ it 'returns http authorization error' do
+ subject
+
+ expect(response).to have_http_status(401)
+ end
+
+ it 'returns the error in the json response' do
+ subject
+
+ expect(body_as_json).to match(
+ a_hash_including(
+ error: 'The access token is invalid'
+ )
+ )
+ end
+ end
end
end
diff --git a/spec/services/account_search_service_spec.rb b/spec/services/account_search_service_spec.rb
index 1cd036f484..4f89cd220c 100644
--- a/spec/services/account_search_service_spec.rb
+++ b/spec/services/account_search_service_spec.rb
@@ -56,7 +56,7 @@ describe AccountSearchService, type: :service do
service = instance_double(ResolveAccountService, call: nil)
allow(ResolveAccountService).to receive(:new).and_return(service)
- results = subject.call('newuser@remote.com', nil, limit: 10, resolve: true)
+ subject.call('newuser@remote.com', nil, limit: 10, resolve: true)
expect(service).to have_received(:call).with('newuser@remote.com')
end
@@ -64,14 +64,14 @@ describe AccountSearchService, type: :service do
service = instance_double(ResolveAccountService, call: nil)
allow(ResolveAccountService).to receive(:new).and_return(service)
- results = subject.call('newuser@remote.com', nil, limit: 10, resolve: false)
+ subject.call('newuser@remote.com', nil, limit: 10, resolve: false)
expect(service).to_not have_received(:call)
end
end
it 'returns the fuzzy match first, and does not return suspended exacts' do
partial = Fabricate(:account, username: 'exactness')
- exact = Fabricate(:account, username: 'exact', suspended: true)
+ Fabricate(:account, username: 'exact', suspended: true)
results = subject.call('exact', nil, limit: 10)
expect(results.size).to eq 1
@@ -79,7 +79,7 @@ describe AccountSearchService, type: :service do
end
it 'does not return suspended remote accounts' do
- remote = Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e', suspended: true)
+ Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e', suspended: true)
results = subject.call('a@example.com', nil, limit: 2)
expect(results.size).to eq 0
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index adf7e33d8e..f122b5d236 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -307,7 +307,7 @@ RSpec.describe PostStatusService, type: :service do
it 'processes duplicate mentions correctly' do
account = Fabricate(:account)
- mentioned_account = Fabricate(:account, username: 'alice')
+ Fabricate(:account, username: 'alice')
expect do
subject.call(account, text: '@alice @alice @alice hey @alice')
@@ -377,7 +377,7 @@ RSpec.describe PostStatusService, type: :service do
account = Fabricate(:account)
media = Fabricate(:media_attachment, account: Fabricate(:account))
- status = subject.call(
+ subject.call(
account,
text: 'test status update',
media_ids: [media.id]
@@ -458,7 +458,7 @@ RSpec.describe PostStatusService, type: :service do
it 'hit ng words for mention' do
account = Fabricate(:account)
- mentioned = Fabricate(:account, username: 'ohagi', domain: nil)
+ Fabricate(:account, username: 'ohagi', domain: nil)
text = 'ng word test @ohagi'
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '1').save
@@ -467,7 +467,7 @@ RSpec.describe PostStatusService, type: :service do
it 'hit ng words for mention but local posts are not checked' do
account = Fabricate(:account)
- mentioned = Fabricate(:account, username: 'ohagi', domain: nil)
+ Fabricate(:account, username: 'ohagi', domain: nil)
text = 'ng word test @ohagi'
Form::AdminSettings.new(ng_words_for_stranger_mention: 'test', stranger_mention_from_local_ng: '0').save
diff --git a/spec/services/precompute_feed_service_spec.rb b/spec/services/precompute_feed_service_spec.rb
index 54e0d94ee0..663babae8a 100644
--- a/spec/services/precompute_feed_service_spec.rb
+++ b/spec/services/precompute_feed_service_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe PrecomputeFeedService, type: :service do
muted_account = Fabricate(:account)
Fabricate(:mute, account: account, target_account: muted_account)
reblog = Fabricate(:status, account: muted_account)
- status = Fabricate(:status, account: account, reblog: reblog)
+ Fabricate(:status, account: account, reblog: reblog)
subject.call(account)
diff --git a/spec/services/resolve_url_service_spec.rb b/spec/services/resolve_url_service_spec.rb
index 7991aa6ef1..bcfb9dbfb0 100644
--- a/spec/services/resolve_url_service_spec.rb
+++ b/spec/services/resolve_url_service_spec.rb
@@ -7,8 +7,8 @@ describe ResolveURLService, type: :service do
describe '#call' do
it 'returns nil when there is no resource url' do
- url = 'http://example.com/missing-resource'
- known_account = Fabricate(:account, uri: url, domain: 'example.com')
+ url = 'http://example.com/missing-resource'
+ Fabricate(:account, uri: url, domain: 'example.com')
service = instance_double(FetchResourceService)
allow(FetchResourceService).to receive(:new).and_return service
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 95bed8f59b..b81be752ca 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -44,7 +44,7 @@ RSpec.configure do |config|
config.display_try_failure_messages = true
# Use the GitHub Annotations formatter for CI
- if ENV['GITHUB_ACTIONS'] == 'true'
+ if ENV['GITHUB_ACTIONS'] == 'true' && ENV['GITHUB_RSPEC'] == 'true'
require 'rspec/github'
config.add_formatter RSpec::Github::Formatter
end
diff --git a/spec/views/admin/trends/links/_preview_card.html.haml_spec.rb b/spec/views/admin/trends/links/_preview_card.html.haml_spec.rb
new file mode 100644
index 0000000000..82a1dee6d7
--- /dev/null
+++ b/spec/views/admin/trends/links/_preview_card.html.haml_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'admin/trends/links/_preview_card.html.haml' do
+ it 'correctly escapes user supplied url values' do
+ form = instance_double(ActionView::Helpers::FormHelper, check_box: nil)
+ trend = PreviewCardTrend.new(allowed: false)
+ preview_card = Fabricate.build(
+ :preview_card,
+ url: 'https://host.example/path?query=