From acc419b81be647336059ed8048dd88c1c1c1e95a Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Fri, 2 Jun 2023 09:40:23 -0400 Subject: [PATCH 01/17] Fix spacing of middle dots in the detailed status meta section (#25247) --- .../mastodon/features/status/components/detailed_status.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx index 83a566710d9..ddda6eaac66 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.jsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx @@ -217,7 +217,7 @@ class DetailedStatus extends ImmutablePureComponent { } else if (this.context.router) { reblogLink = ( <> - · + {' · '} @@ -229,7 +229,7 @@ class DetailedStatus extends ImmutablePureComponent { } else { reblogLink = ( <> - · + {' · '} @@ -263,7 +263,7 @@ class DetailedStatus extends ImmutablePureComponent { if (status.get('edited_at')) { edited = ( <> - · + {' · '} ); From 94329f28e1d6edace2667daeaf0097f895e4940c Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 2 Jun 2023 18:09:08 +0200 Subject: [PATCH 02/17] =?UTF-8?q?Change=20wording=20of=20=E2=80=9CContent?= =?UTF-8?q?=20cache=20retention=20period=E2=80=9D=20setting=20to=20highlig?= =?UTF-8?q?ht=20destructive=20implications=20(#23261)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/settings/content_retention/show.html.haml | 2 +- config/initializers/simple_form.rb | 10 ++++++++++ config/locales/simple_form.en.yml | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/views/admin/settings/content_retention/show.html.haml b/app/views/admin/settings/content_retention/show.html.haml index 36856127f2c..de34b5ee3c0 100644 --- a/app/views/admin/settings/content_retention/show.html.haml +++ b/app/views/admin/settings/content_retention/show.html.haml @@ -15,7 +15,7 @@ .fields-group = f.input :media_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } - = f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } + = f.input :content_cache_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' }, hint: false, warning_hint: t('simple_form.hints.form_admin_settings.content_cache_retention_period') = f.input :backups_retention_period, wrapper: :with_block_label, input_html: { pattern: '[0-9]+' } .actions diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb index 92cffc5a2a4..74034f36fd0 100644 --- a/config/initializers/simple_form.rb +++ b/config/initializers/simple_form.rb @@ -19,8 +19,17 @@ module RecommendedComponent end end +module WarningHintComponent + def warning_hint(_wrapper_options = nil) + @warning_hint ||= begin + options[:warning_hint].to_s.html_safe if options[:warning_hint].present? + end + end +end + SimpleForm.include_component(AppendComponent) SimpleForm.include_component(RecommendedComponent) +SimpleForm.include_component(WarningHintComponent) SimpleForm.setup do |config| # Wrappers are used by the form builder to generate a @@ -101,6 +110,7 @@ SimpleForm.setup do |config| b.use :html5 b.use :label b.use :hint, wrap_with: { tag: :span, class: :hint } + b.use :warning_hint, wrap_with: { tag: :span, class: [:hint, 'warning-hint'] } b.use :input, wrap_with: { tag: :div, class: :label_input } b.use :error, wrap_with: { tag: :span, class: :error } end diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index b646a15e26e..9c747e595de 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -78,7 +78,7 @@ en: backups_retention_period: Keep generated user archives for the specified number of days. bootstrap_timeline_accounts: These accounts will be pinned to the top of new users' follow recommendations. closed_registrations_message: Displayed when sign-ups are closed - content_cache_retention_period: Posts from other servers will be deleted after the specified number of days when set to a positive value. This may be irreversible. + content_cache_retention_period: All posts and boosts from other servers will be deleted after the specified number of days. Some posts may not be recoverable. All related bookmarks, favourites and boosts will also be lost and impossible to undo. custom_css: You can apply custom styles on the web version of Mastodon. mascot: Overrides the illustration in the advanced web interface. media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand. From 0766c9a631e45ff66603ff10fa69808b8452d0b3 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 2 Jun 2023 18:35:37 +0200 Subject: [PATCH 03/17] Add card with who invited you to join when displaying rules on sign-up (#23475) --- app/javascript/styles/mastodon/accounts.scss | 14 ++------------ app/javascript/styles/mastodon/forms.scss | 4 ++++ app/views/application/_card.html.haml | 6 ++++-- app/views/auth/registrations/rules.html.haml | 10 ++++++++-- config/locales/en.yml | 3 +++ 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index 8b7b634071a..b50306deda8 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -3,11 +3,8 @@ display: block; text-decoration: none; color: inherit; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - - @media screen and (max-width: $no-gap-breakpoint) { - box-shadow: none; - } + overflow: hidden; + border-radius: 4px; &:hover, &:active, @@ -22,7 +19,6 @@ height: 130px; position: relative; background: darken($ui-base-color, 12%); - border-radius: 4px 4px 0 0; img { display: block; @@ -30,7 +26,6 @@ height: 100%; margin: 0; object-fit: cover; - border-radius: 4px 4px 0 0; } @media screen and (width <= 600px) { @@ -45,11 +40,6 @@ justify-content: flex-start; align-items: center; background: lighten($ui-base-color, 4%); - border-radius: 0 0 4px 4px; - - @media screen and (max-width: $no-gap-breakpoint) { - border-radius: 0; - } .avatar { flex: 0 0 auto; diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 57f077c4e80..d63a42557f2 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -137,6 +137,10 @@ code { color: $secondary-text-color; margin-bottom: 30px; + &.invited-by { + margin-bottom: 15px; + } + a { color: $highlight-text-color; } diff --git a/app/views/application/_card.html.haml b/app/views/application/_card.html.haml index 719856d4958..1b3dd889c19 100644 --- a/app/views/application/_card.html.haml +++ b/app/views/application/_card.html.haml @@ -1,9 +1,11 @@ - account_url = local_assigns[:admin] ? admin_account_path(account.id) : ActivityPub::TagManager.instance.url_for(account) +- compact ||= false .card.h-card = link_to account_url, target: '_blank', rel: 'noopener noreferrer' do - .card__img - = image_tag account.header.url, alt: '' + - unless compact + .card__img + = image_tag account.header.url, alt: '' .card__bar .avatar = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo' diff --git a/app/views/auth/registrations/rules.html.haml b/app/views/auth/registrations/rules.html.haml index ab3fa864abd..234f4a601da 100644 --- a/app/views/auth/registrations/rules.html.haml +++ b/app/views/auth/registrations/rules.html.haml @@ -7,8 +7,14 @@ .simple_form = render 'auth/shared/progress', stage: 'rules' - %h1.title= t('auth.rules.title') - %p.lead= t('auth.rules.preamble', domain: site_hostname) + - if @invite.present? && @invite.autofollow? + %h1.title= t('auth.rules.title_invited') + %p.lead.invited-by= t('auth.rules.invited_by', domain: site_hostname) + = render 'application/card', account: @invite.user.account, compact: true + %p.lead= t('auth.rules.preamble_invited', domain: site_hostname) + - else + %h1.title= t('auth.rules.title') + %p.lead= t('auth.rules.preamble', domain: site_hostname) %ol.rules-list - @rules.each do |rule| diff --git a/config/locales/en.yml b/config/locales/en.yml index 6a8da6e60d9..2c292c42d44 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1031,8 +1031,11 @@ en: rules: accept: Accept back: Back + invited_by: 'You can join %{domain} thanks to the invitation you have received from:' preamble: These are set and enforced by the %{domain} moderators. + preamble_invited: Before you proceed, please consider the ground rules set by the moderators of %{domain}. title: Some ground rules. + title_invited: You've been invited. security: Security set_new_password: Set new password setup: From 768b00c4d0c05c35c2c6c9bc8b4a821f1bde119d Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Fri, 2 Jun 2023 13:58:18 -0400 Subject: [PATCH 04/17] =?UTF-8?q?Consistently=20use=20middle=20dot=20(?= =?UTF-8?q?=C2=B7)=20instead=20of=20bullet=20(=E2=80=A2)=20to=20separate?= =?UTF-8?q?=20items=20(#25248)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.js | 9 ++++++ .haml-lint.yml | 5 +++ .rubocop.yml | 4 +++ .../_email_domain_block.html.haml | 2 +- .../_domain_block.html.haml | 6 ++-- app/views/admin/instances/_instance.html.haml | 2 +- app/views/admin/instances/show.html.haml | 2 +- app/views/admin/ip_blocks/_ip_block.html.haml | 2 +- app/views/admin/roles/_role.html.haml | 2 +- .../trends/links/_preview_card.html.haml | 10 +++--- .../admin/trends/statuses/_status.html.haml | 10 +++--- app/views/admin/trends/tags/_tag.html.haml | 6 ++-- app/views/admin/webhooks/_webhook.html.haml | 2 +- .../admin_mailer/_new_trending_links.text.erb | 4 +-- .../_new_trending_statuses.text.erb | 2 +- .../admin_mailer/_new_trending_tags.text.erb | 2 +- .../authorized_applications/index.html.haml | 2 +- lib/linter/haml_middle_dot.rb | 26 ++++++++++++++++ lib/linter/rubocop_middle_dot.rb | 31 +++++++++++++++++++ 19 files changed, 102 insertions(+), 27 deletions(-) create mode 100644 lib/linter/haml_middle_dot.rb create mode 100644 lib/linter/rubocop_middle_dot.rb diff --git a/.eslintrc.js b/.eslintrc.js index 24961cdd9d4..91dcd8e60c5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -81,6 +81,15 @@ module.exports = { { property: 'substring', message: 'Use .slice instead of .substring.' }, { property: 'substr', message: 'Use .slice instead of .substr.' }, ], + 'no-restricted-syntax': [ + 'error', + { + // eslint-disable-next-line no-restricted-syntax + selector: 'Literal[value=/•/], JSXText[value=/•/]', + // eslint-disable-next-line no-restricted-syntax + message: "Use '·' (middle dot) instead of '•' (bullet)", + }, + ], 'no-self-assign': 'off', 'no-unused-expressions': 'error', 'no-unused-vars': 'off', diff --git a/.haml-lint.yml b/.haml-lint.yml index 12ca4634225..d1ed30b260c 100644 --- a/.haml-lint.yml +++ b/.haml-lint.yml @@ -4,6 +4,11 @@ exclude: - 'vendor/**/*' - lib/templates/haml/scaffold/_form.html.haml +require: + - ./lib/linter/haml_middle_dot.rb + linters: AltText: enabled: true + MiddleDot: + enabled: true diff --git a/.rubocop.yml b/.rubocop.yml index bd561df1d2e..eff89bdaee7 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -11,6 +11,7 @@ require: - rubocop-rspec - rubocop-performance - rubocop-capybara + - ./lib/linter/rubocop_middle_dot AllCops: TargetRubyVersion: 3.0 # Set to minimum supported version of CI @@ -205,3 +206,6 @@ Style/TrailingCommaInArrayLiteral: # https://docs.rubocop.org/rubocop/cops_style.html#styletrailingcommainhashliteral Style/TrailingCommaInHashLiteral: EnforcedStyleForMultiline: 'comma' + +Style/MiddleDot: + Enabled: true diff --git a/app/views/admin/email_domain_blocks/_email_domain_block.html.haml b/app/views/admin/email_domain_blocks/_email_domain_block.html.haml index c5a55bc27c3..7cb973c4b4e 100644 --- a/app/views/admin/email_domain_blocks/_email_domain_block.html.haml +++ b/app/views/admin/email_domain_blocks/_email_domain_block.html.haml @@ -9,6 +9,6 @@ - if email_domain_block.parent.present? = t('admin.email_domain_blocks.resolved_through_html', domain: content_tag(:samp, email_domain_block.parent.domain)) - • + · = t('admin.email_domain_blocks.attempts_over_week', count: email_domain_block.history.reduce(0) { |sum, day| sum + day.accounts }) diff --git a/app/views/admin/export_domain_blocks/_domain_block.html.haml b/app/views/admin/export_domain_blocks/_domain_block.html.haml index 5d4b6c4d0d2..cdce4fd28a9 100644 --- a/app/views/admin/export_domain_blocks/_domain_block.html.haml +++ b/app/views/admin/export_domain_blocks/_domain_block.html.haml @@ -17,11 +17,11 @@ %br/ - = f.object.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' • ') + = f.object.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ') - if f.object.public_comment.present? - • + · = f.object.public_comment - if existing_relationships - • + · = fa_icon 'warning fw' = t('admin.export_domain_blocks.import.existing_relationships_warning') diff --git a/app/views/admin/instances/_instance.html.haml b/app/views/admin/instances/_instance.html.haml index 93f9bd41816..65cf789ce31 100644 --- a/app/views/admin/instances/_instance.html.haml +++ b/app/views/admin/instances/_instance.html.haml @@ -6,7 +6,7 @@ %small - if instance.domain_block - = instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' • ') + = instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ') - elsif instance.domain_allow = t('admin.accounts.whitelisted') - else diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml index ab290912e21..6d67d389d25 100644 --- a/app/views/admin/instances/show.html.haml +++ b/app/views/admin/instances/show.html.haml @@ -58,7 +58,7 @@ %td= @instance.domain_block.public_comment %tr %th= t('admin.instances.content_policies.policy') - %td= @instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' • ') + %td= @instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ') = link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@instance.domain_block), class: 'button' = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: 'button', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete } diff --git a/app/views/admin/ip_blocks/_ip_block.html.haml b/app/views/admin/ip_blocks/_ip_block.html.haml index b8d3ac0e864..3dc6f8f8e57 100644 --- a/app/views/admin/ip_blocks/_ip_block.html.haml +++ b/app/views/admin/ip_blocks/_ip_block.html.haml @@ -5,7 +5,7 @@ .pending-account__header %samp= link_to "#{ip_block.ip}/#{ip_block.ip.prefix}", admin_accounts_path(ip: "#{ip_block.ip}/#{ip_block.ip.prefix}") - if ip_block.comment.present? - • + · = ip_block.comment %br/ = t("simple_form.labels.ip_block.severities.#{ip_block.severity}") diff --git a/app/views/admin/roles/_role.html.haml b/app/views/admin/roles/_role.html.haml index 798d8d8b4fc..d6c6b62c81b 100644 --- a/app/views/admin/roles/_role.html.haml +++ b/app/views/admin/roles/_role.html.haml @@ -24,7 +24,7 @@ = t('admin.roles.everyone_full_description_html') - else = link_to t('admin.roles.assigned_users', count: role.users.count), admin_accounts_path(role_ids: role.id) - • + · %abbr{ title: role.permissions_as_keys.map { |privilege| I18n.t("admin.roles.privileges.#{privilege}") }.join(', ') }= t('admin.roles.permissions_count', count: role.permissions_as_keys.size) %div = table_link_to 'pencil', t('admin.accounts.edit'), edit_admin_role_path(role) if can?(:update, role) diff --git a/app/views/admin/trends/links/_preview_card.html.haml b/app/views/admin/trends/links/_preview_card.html.haml index 8812feb3163..1ca34837151 100644 --- a/app/views/admin/trends/links/_preview_card.html.haml +++ b/app/views/admin/trends/links/_preview_card.html.haml @@ -10,21 +10,21 @@ - if preview_card.provider_name.present? = preview_card.provider_name - • + · - if preview_card.language.present? = standard_locale_name(preview_card.language) - • + · = t('admin.trends.links.shared_by_over_week', count: preview_card.history.reduce(0) { |sum, day| sum + day.accounts }) - if preview_card.trend.allowed? - • + · %abbr{ title: t('admin.trends.tags.current_score', score: preview_card.trend.score) }= t('admin.trends.tags.trending_rank', rank: preview_card.trend.rank) - if preview_card.decaying? - • + · = t('admin.trends.tags.peaked_on_and_decaying', date: l(preview_card.max_score_at.to_date, format: :short)) - elsif preview_card.requires_review? - • + · = t('admin.trends.pending_review') diff --git a/app/views/admin/trends/statuses/_status.html.haml b/app/views/admin/trends/statuses/_status.html.haml index f35e13d1288..98f2e770908 100644 --- a/app/views/admin/trends/statuses/_status.html.haml +++ b/app/views/admin/trends/statuses/_status.html.haml @@ -17,17 +17,17 @@ = t('admin.trends.statuses.shared_by', count: status.reblogs_count + status.favourites_count, friendly_count: friendly_number_to_human(status.reblogs_count + status.favourites_count)) - if status.account.domain.present? - • + · = status.account.domain - if status.language.present? - • + · = standard_locale_name(status.language) - if status.trendable? && !status.account.discoverable? - • + · = t('admin.trends.statuses.not_discoverable') - if status.trend.allowed? - • + · %abbr{ title: t('admin.trends.tags.current_score', score: status.trend.score) }= t('admin.trends.tags.trending_rank', rank: status.trend.rank) - elsif status.requires_review? - • + · = t('admin.trends.pending_review') diff --git a/app/views/admin/trends/tags/_tag.html.haml b/app/views/admin/trends/tags/_tag.html.haml index a30666a08b1..3bbdd08db83 100644 --- a/app/views/admin/trends/tags/_tag.html.haml +++ b/app/views/admin/trends/tags/_tag.html.haml @@ -13,12 +13,12 @@ = t('admin.trends.tags.used_by_over_week', count: tag.history.reduce(0) { |sum, day| sum + day.accounts }) - if tag.trendable? && (rank = Trends.tags.rank(tag.id)) - • + · %abbr{ title: t('admin.trends.tags.current_score', score: Trends.tags.score(tag.id)) }= t('admin.trends.tags.trending_rank', rank: rank + 1) - if tag.decaying? - • + · = t('admin.trends.tags.peaked_on_and_decaying', date: l(tag.max_score_at.to_date, format: :short)) - elsif tag.requires_review? - • + · = t('admin.trends.pending_review') diff --git a/app/views/admin/webhooks/_webhook.html.haml b/app/views/admin/webhooks/_webhook.html.haml index d94a41eb3d8..6b3e49eba0a 100644 --- a/app/views/admin/webhooks/_webhook.html.haml +++ b/app/views/admin/webhooks/_webhook.html.haml @@ -10,7 +10,7 @@ - else %span.negative-hint= t('admin.webhooks.disabled') - • + · %abbr{ title: webhook.events.join(', ') }= t('admin.webhooks.enabled_events', count: webhook.events.size) diff --git a/app/views/admin_mailer/_new_trending_links.text.erb b/app/views/admin_mailer/_new_trending_links.text.erb index 602e12793ec..85f3f8039d5 100644 --- a/app/views/admin_mailer/_new_trending_links.text.erb +++ b/app/views/admin_mailer/_new_trending_links.text.erb @@ -1,8 +1,8 @@ <%= raw t('admin_mailer.new_trends.new_trending_links.title') %> <% @links.each do |link| %> -- <%= link.title %> • <%= link.url %> - <%= standard_locale_name(link.language) %> • <%= raw t('admin.trends.links.usage_comparison', today: link.history.get(Time.now.utc).accounts, yesterday: link.history.get(Time.now.utc - 1.day).accounts) %> • <%= t('admin.trends.tags.current_score', score: link.trend.score.round(2)) %> +- <%= link.title %> · <%= link.url %> + <%= standard_locale_name(link.language) %> · <%= raw t('admin.trends.links.usage_comparison', today: link.history.get(Time.now.utc).accounts, yesterday: link.history.get(Time.now.utc - 1.day).accounts) %> · <%= t('admin.trends.tags.current_score', score: link.trend.score.round(2)) %> <% end %> <%= raw t('application_mailer.view')%> <%= admin_trends_links_url %> diff --git a/app/views/admin_mailer/_new_trending_statuses.text.erb b/app/views/admin_mailer/_new_trending_statuses.text.erb index 1ed3ae85730..eedbfff9d91 100644 --- a/app/views/admin_mailer/_new_trending_statuses.text.erb +++ b/app/views/admin_mailer/_new_trending_statuses.text.erb @@ -2,7 +2,7 @@ <% @statuses.each do |status| %> - <%= ActivityPub::TagManager.instance.url_for(status) %> - <%= standard_locale_name(status.language) %> • <%= raw t('admin.trends.tags.current_score', score: status.trend.score.round(2)) %> + <%= standard_locale_name(status.language) %> · <%= raw t('admin.trends.tags.current_score', score: status.trend.score.round(2)) %> <% end %> <%= raw t('application_mailer.view')%> <%= admin_trends_statuses_url %> diff --git a/app/views/admin_mailer/_new_trending_tags.text.erb b/app/views/admin_mailer/_new_trending_tags.text.erb index 363df369d56..d528ab8eb7c 100644 --- a/app/views/admin_mailer/_new_trending_tags.text.erb +++ b/app/views/admin_mailer/_new_trending_tags.text.erb @@ -2,7 +2,7 @@ <% @tags.each do |tag| %> - #<%= tag.display_name %> - <%= raw t('admin.trends.tags.usage_comparison', today: tag.history.get(Time.now.utc).accounts, yesterday: tag.history.get(Time.now.utc - 1.day).accounts) %> • <%= t('admin.trends.tags.current_score', score: Trends.tags.score(tag.id).round(2)) %> + <%= raw t('admin.trends.tags.usage_comparison', today: tag.history.get(Time.now.utc).accounts, yesterday: tag.history.get(Time.now.utc - 1.day).accounts) %> · <%= t('admin.trends.tags.current_score', score: Trends.tags.score(tag.id).round(2)) %> <% end %> <% if @lowest_trending_tag %> diff --git a/app/views/oauth/authorized_applications/index.html.haml b/app/views/oauth/authorized_applications/index.html.haml index 55d8524dbe9..689f051029f 100644 --- a/app/views/oauth/authorized_applications/index.html.haml +++ b/app/views/oauth/authorized_applications/index.html.haml @@ -23,7 +23,7 @@ - else = t('doorkeeper.authorized_applications.index.never_used') - • + · = t('doorkeeper.authorized_applications.index.authorized_at', date: l(application.created_at.to_date)) diff --git a/lib/linter/haml_middle_dot.rb b/lib/linter/haml_middle_dot.rb new file mode 100644 index 00000000000..3b27711521e --- /dev/null +++ b/lib/linter/haml_middle_dot.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module HamlLint + # Bans the usage of “•” (bullet) in HTML/HAML in favor of “·” (middle dot) in anything that will end up as a text node. (including string literals in Ruby code) + class Linter::MiddleDot < Linter + include LinterRegistry + + # rubocop:disable Style/MiddleDot + BULLET = '•' + # rubocop:enable Style/MiddleDot + MIDDLE_DOT = '·' + MESSAGE = "Use '#{MIDDLE_DOT}' (middle dot) instead of '#{BULLET}' (bullet)".freeze + + def visit_plain(node) + return unless node.text.include?(BULLET) + + record_lint(node, MESSAGE) + end + + def visit_script(node) + return unless node.script.include?(BULLET) + + record_lint(node, MESSAGE) + end + end +end diff --git a/lib/linter/rubocop_middle_dot.rb b/lib/linter/rubocop_middle_dot.rb new file mode 100644 index 00000000000..3a1d97c0c98 --- /dev/null +++ b/lib/linter/rubocop_middle_dot.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Style + # Bans the usage of “•” (bullet) in HTML/HAML in favor of “·” (middle dot) in string literals + class MiddleDot < Base + extend AutoCorrector + extend Util + + # rubocop:disable Style/MiddleDot + BULLET = '•' + # rubocop:enable Style/MiddleDot + MIDDLE_DOT = '·' + MESSAGE = "Use '#{MIDDLE_DOT}' (middle dot) instead of '#{BULLET}' (bullet)".freeze + + def on_str(node) + # Constants like __FILE__ are handled as strings, + # but don't respond to begin. + return unless node.loc.respond_to?(:begin) && node.loc.begin + + return unless node.value.include?(BULLET) + + add_offense(node, message: MESSAGE) do |corrector| + corrector.replace(node, node.source.gsub(BULLET, MIDDLE_DOT)) + end + end + end + end + end +end From aea67d448bc7974aa4c342f5c1987ec4ee2681ea Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Fri, 2 Jun 2023 14:01:36 -0400 Subject: [PATCH 05/17] Cleanup old translationRunner (#25241) --- config/webpack/translationRunner.js | 3 --- package.json | 1 - 2 files changed, 4 deletions(-) delete mode 100644 config/webpack/translationRunner.js diff --git a/config/webpack/translationRunner.js b/config/webpack/translationRunner.js deleted file mode 100644 index 77534c9de30..00000000000 --- a/config/webpack/translationRunner.js +++ /dev/null @@ -1,3 +0,0 @@ -console.error("The localisation functionality has been refactored, please see the Localisation section in the development documentation (https://docs.joinmastodon.org/dev/code/#localizations)"); - -process.exit(1); diff --git a/package.json b/package.json index a08b485fd75..49e9c7f743d 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "lint:sass": "stylelint \"**/*.{css,scss}\" && prettier --check \"**/*.{css,scss}\"", "lint:yml": "prettier --check \"**/*.{yaml,yml}\"", "lint": "yarn lint:js && yarn lint:json && yarn lint:sass && yarn lint:yml", - "manage:translations": "node ./config/webpack/translationRunner.js", "postversion": "git push --tags", "prepare": "husky install", "start": "node ./streaming/index.js", From 4a5464f36051ea9cbc0c7afa8c885f4f2bdd9245 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 5 Jun 2023 01:42:17 +0200 Subject: [PATCH 06/17] Change "Follow 7 people" to "Find at least 7 people to follow" in web UI (#24954) --- app/javascript/mastodon/features/onboarding/index.jsx | 2 +- app/javascript/mastodon/locales/en.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/features/onboarding/index.jsx b/app/javascript/mastodon/features/onboarding/index.jsx index ecebdb6965e..79291b3d087 100644 --- a/app/javascript/mastodon/features/onboarding/index.jsx +++ b/app/javascript/mastodon/features/onboarding/index.jsx @@ -120,7 +120,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/en.json b/app/javascript/mastodon/locales/en.json index 5ed793cdba7..f6d6daa3e52 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -460,8 +460,8 @@ "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:", "onboarding.start.skip": "Want to skip right ahead?", "onboarding.start.title": "You've made it!", - "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.", - "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}", + "onboarding.steps.follow_people.body": "You curate your own home feed. Let's fill it with interesting people.", + "onboarding.steps.follow_people.title": "Find at least {count, plural, one {one person} other {# people}} to follow", "onboarding.steps.publish_status.body": "Say hello to the world.", "onboarding.steps.publish_status.title": "Make your first post", "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.", From e49819142f98a94ba44e24aa093815bf74afab05 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Sun, 4 Jun 2023 19:57:05 -0400 Subject: [PATCH 07/17] Remove unmaintained `nsa` gem (#25265) --- Gemfile | 1 - Gemfile.lock | 7 ------- config/initializers/statsd.rb | 15 --------------- 3 files changed, 23 deletions(-) delete mode 100644 config/initializers/statsd.rb diff --git a/Gemfile b/Gemfile index 62e45a5f30e..cff8cb1f88a 100644 --- a/Gemfile +++ b/Gemfile @@ -60,7 +60,6 @@ gem 'kaminari', '~> 1.2' gem 'link_header', '~> 0.0' gem 'mime-types', '~> 3.4.1', require: 'mime/types/columnar' gem 'nokogiri', '~> 1.15' -gem 'nsa', '~> 0.2' gem 'oj', '~> 3.14' gem 'ox', '~> 2.14' gem 'parslet' diff --git a/Gemfile.lock b/Gemfile.lock index 7d04d875c5f..bb209b38417 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -442,11 +442,6 @@ GEM nokogiri (1.15.2) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nsa (0.2.8) - activesupport (>= 4.2, < 7) - concurrent-ruby (~> 1.0, >= 1.0.2) - sidekiq (>= 3.5) - statsd-ruby (~> 1.4, >= 1.4.0) oj (3.14.3) omniauth (1.9.2) hashie (>= 3.4.6) @@ -682,7 +677,6 @@ GEM net-scp (>= 1.1.2) net-ssh (>= 2.8.0) stackprof (0.2.25) - statsd-ruby (1.5.0) stoplight (3.0.1) redlock (~> 1.0) strong_migrations (0.8.0) @@ -831,7 +825,6 @@ DEPENDENCIES net-http (~> 0.3.2) net-ldap (~> 0.18) nokogiri (~> 1.15) - nsa (~> 0.2) oj (~> 3.14) omniauth (~> 1.9) omniauth-cas (~> 2.0) diff --git a/config/initializers/statsd.rb b/config/initializers/statsd.rb deleted file mode 100644 index 93ea1d1e4a7..00000000000 --- a/config/initializers/statsd.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -if ENV['STATSD_ADDR'].present? - host, port = ENV['STATSD_ADDR'].split(':') - - $statsd = ::Statsd.new(host, port) - $statsd.namespace = ENV.fetch('STATSD_NAMESPACE') { ['Mastodon', Rails.env].join('.') } - - ::NSA.inform_statsd($statsd) do |informant| - informant.collect(:action_controller, :web) - informant.collect(:active_record, :db) - informant.collect(:active_support_cache, :cache) - informant.collect(:sidekiq, :sidekiq) - end -end From 03a707f6a6c8bbefec630c04d183fc9f22b23d9d Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 5 Jun 2023 03:16:12 -0300 Subject: [PATCH 08/17] Add test coverage for `Mastodon::CLI::Accounts#merge` (#25199) --- spec/lib/mastodon/cli/accounts_spec.rb | 111 +++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb index ba49e480ad7..50066572c69 100644 --- a/spec/lib/mastodon/cli/accounts_spec.rb +++ b/spec/lib/mastodon/cli/accounts_spec.rb @@ -998,4 +998,115 @@ describe Mastodon::CLI::Accounts do end end end + + describe '#merge' do + shared_examples 'an account not found' do |acct| + it 'exits with an error message indicating that there is no such account' do + expect { cli.invoke(:merge, arguments) }.to output( + a_string_including("No such account (#{acct})") + ).to_stdout + .and raise_error(SystemExit) + end + end + + context 'when "from_account" is not found' do + let(:to_account) { Fabricate(:account, domain: 'example.com') } + let(:arguments) { ['non_existent_username@domain.com', "#{to_account.username}@#{to_account.domain}"] } + + it_behaves_like 'an account not found', 'non_existent_username@domain.com' + end + + context 'when "from_account" is a local account' do + let(:from_account) { Fabricate(:account, domain: nil, username: 'bob') } + let(:to_account) { Fabricate(:account, domain: 'example.com') } + let(:arguments) { [from_account.username, "#{to_account.username}@#{to_account.domain}"] } + + it_behaves_like 'an account not found', 'bob' + end + + context 'when "to_account" is not found' do + let(:from_account) { Fabricate(:account, domain: 'example.com') } + let(:arguments) { ["#{from_account.username}@#{from_account.domain}", 'non_existent_username'] } + + it_behaves_like 'an account not found', 'non_existent_username' + end + + context 'when "to_account" is local' do + let(:from_account) { Fabricate(:account, domain: 'example.com') } + let(:to_account) { Fabricate(:account, domain: nil, username: 'bob') } + let(:arguments) do + ["#{from_account.username}@#{from_account.domain}", "#{to_account.username}@#{to_account.domain}"] + end + + it_behaves_like 'an account not found', 'bob@' + end + + context 'when "from_account" and "to_account" public keys do not match' do + let(:from_account) { instance_double(Account, username: 'bob', domain: 'example1.com', local?: false, public_key: 'from_account') } + let(:to_account) { instance_double(Account, username: 'bob', domain: 'example2.com', local?: false, public_key: 'to_account') } + let(:arguments) do + ["#{from_account.username}@#{from_account.domain}", "#{to_account.username}@#{to_account.domain}"] + end + + before do + allow(Account).to receive(:find_remote).with(from_account.username, from_account.domain).and_return(from_account) + allow(Account).to receive(:find_remote).with(to_account.username, to_account.domain).and_return(to_account) + end + + it 'exits with an error message indicating that the accounts do not have the same pub key' do + expect { cli.invoke(:merge, arguments) }.to output( + a_string_including("Accounts don't have the same public key, might not be duplicates!\nOverride with --force") + ).to_stdout + .and raise_error(SystemExit) + end + + context 'with --force option' do + let(:options) { { force: true } } + + before do + allow(to_account).to receive(:merge_with!) + allow(from_account).to receive(:destroy) + end + + it 'merges "from_account" into "to_account"' do + cli.invoke(:merge, arguments, options) + + expect(to_account).to have_received(:merge_with!).with(from_account).once + end + + it 'deletes "from_account"' do + cli.invoke(:merge, arguments, options) + + expect(from_account).to have_received(:destroy).once + end + end + end + + context 'when "from_account" and "to_account" public keys match' do + let(:from_account) { instance_double(Account, username: 'bob', domain: 'example1.com', local?: false, public_key: 'pub_key') } + let(:to_account) { instance_double(Account, username: 'bob', domain: 'example2.com', local?: false, public_key: 'pub_key') } + let(:arguments) do + ["#{from_account.username}@#{from_account.domain}", "#{to_account.username}@#{to_account.domain}"] + end + + before do + allow(Account).to receive(:find_remote).with(from_account.username, from_account.domain).and_return(from_account) + allow(Account).to receive(:find_remote).with(to_account.username, to_account.domain).and_return(to_account) + allow(to_account).to receive(:merge_with!) + allow(from_account).to receive(:destroy) + end + + it 'merges "from_account" into "to_account"' do + cli.invoke(:merge, arguments) + + expect(to_account).to have_received(:merge_with!).with(from_account).once + end + + it 'deletes "from_account"' do + cli.invoke(:merge, arguments) + + expect(from_account).to have_received(:destroy) + end + end + end end From 5c87c6650424c0ec759257b0c8e71fa2d51eedc9 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 5 Jun 2023 02:20:18 -0400 Subject: [PATCH 09/17] Add coverage for CLI::CanonicalEmailBlocks command (#25239) --- .../cli/canonical_email_blocks_spec.rb | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb b/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb index fb481e8a821..eb57a3cd15a 100644 --- a/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb +++ b/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb @@ -4,9 +4,57 @@ require 'rails_helper' require 'mastodon/cli/canonical_email_blocks' describe Mastodon::CLI::CanonicalEmailBlocks do + let(:cli) { described_class.new } + describe '.exit_on_failure?' do it 'returns true' do expect(described_class.exit_on_failure?).to be true end end + + describe '#find' do + let(:arguments) { ['user@example.com'] } + + context 'when a block is present' do + before { Fabricate(:canonical_email_block, email: 'user@example.com') } + + it 'announces the presence of the block' do + expect { cli.invoke(:find, arguments) }.to output( + a_string_including('user@example.com is blocked') + ).to_stdout + end + end + + context 'when a block is not present' do + it 'announces the absence of the block' do + expect { cli.invoke(:find, arguments) }.to output( + a_string_including('user@example.com is not blocked') + ).to_stdout + end + end + end + + describe '#remove' do + let(:arguments) { ['user@example.com'] } + + context 'when a block is present' do + before { Fabricate(:canonical_email_block, email: 'user@example.com') } + + it 'removes the block' do + expect { cli.invoke(:remove, arguments) }.to output( + a_string_including('Unblocked user@example.com') + ).to_stdout + + expect(CanonicalEmailBlock.matching_email('user@example.com')).to be_empty + end + end + + context 'when a block is not present' do + it 'announces the absence of the block' do + expect { cli.invoke(:remove, arguments) }.to output( + a_string_including('user@example.com is not blocked') + ).to_stdout + end + end + end end From 0daf78f903ba81a2fd1566fd9845cea623e8324c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 5 Jun 2023 02:22:03 -0400 Subject: [PATCH 10/17] Add `allow_other_host: true` to backups controller (#25266) --- app/controllers/backups_controller.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/backups_controller.rb b/app/controllers/backups_controller.rb index 5891da6f6d6..205df48d442 100644 --- a/app/controllers/backups_controller.rb +++ b/app/controllers/backups_controller.rb @@ -11,15 +11,15 @@ class BackupsController < ApplicationController def download case Paperclip::Attachment.default_options[:storage] when :s3 - redirect_to @backup.dump.expiring_url(10) + redirect_to @backup.dump.expiring_url(10), allow_other_host: true when :fog if Paperclip::Attachment.default_options.dig(:fog_credentials, :openstack_temp_url_key).present? - redirect_to @backup.dump.expiring_url(Time.now.utc + 10) + redirect_to @backup.dump.expiring_url(Time.now.utc + 10), allow_other_host: true else - redirect_to full_asset_url(@backup.dump.url) + redirect_to full_asset_url(@backup.dump.url), allow_other_host: true end when :filesystem - redirect_to full_asset_url(@backup.dump.url) + redirect_to full_asset_url(@backup.dump.url), allow_other_host: true end end From bacb6749217767de83120ad1dea6a59f69ee66d9 Mon Sep 17 00:00:00 2001 From: Darius Kazemi Date: Mon, 5 Jun 2023 00:37:02 -0700 Subject: [PATCH 11/17] Add exclusive lists (#22048) Co-authored-by: Liam Cooke Co-authored-by: John Holdun Co-authored-by: Effy Elden Co-authored-by: Lina Reyne Co-authored-by: Lina <20880695+necropolina@users.noreply.github.com> Co-authored-by: Claire --- app/controllers/api/v1/lists_controller.rb | 2 +- app/javascript/mastodon/actions/lists.js | 4 +- .../mastodon/features/list_timeline/index.jsx | 18 ++++++++- app/javascript/mastodon/locales/en.json | 1 + .../mastodon/reducers/list_editor.js | 2 + app/lib/feed_manager.rb | 26 +++++++------ app/models/list.rb | 1 + app/serializers/rest/list_serializer.rb | 2 +- .../20230605085710_add_exclusive_to_lists.rb | 7 ++++ db/schema.rb | 3 +- spec/lib/feed_manager_spec.rb | 37 +++++++++++++++++++ 11 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 db/migrate/20230605085710_add_exclusive_to_lists.rb diff --git a/app/controllers/api/v1/lists_controller.rb b/app/controllers/api/v1/lists_controller.rb index 843ca2ec2b6..4bbbed26735 100644 --- a/app/controllers/api/v1/lists_controller.rb +++ b/app/controllers/api/v1/lists_controller.rb @@ -42,6 +42,6 @@ class Api::V1::ListsController < Api::BaseController end def list_params - params.permit(:title, :replies_policy) + params.permit(:title, :replies_policy, :exclusive) end end diff --git a/app/javascript/mastodon/actions/lists.js b/app/javascript/mastodon/actions/lists.js index 2faa54b955f..b0789cd4264 100644 --- a/app/javascript/mastodon/actions/lists.js +++ b/app/javascript/mastodon/actions/lists.js @@ -151,10 +151,10 @@ export const createListFail = error => ({ error, }); -export const updateList = (id, title, shouldReset, replies_policy) => (dispatch, getState) => { +export const updateList = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch, getState) => { dispatch(updateListRequest(id)); - api(getState).put(`/api/v1/lists/${id}`, { title, replies_policy }).then(({ data }) => { + api(getState).put(`/api/v1/lists/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => { dispatch(updateListSuccess(data)); if (shouldReset) { diff --git a/app/javascript/mastodon/features/list_timeline/index.jsx b/app/javascript/mastodon/features/list_timeline/index.jsx index f41e8e6f234..f9f3a7c3159 100644 --- a/app/javascript/mastodon/features/list_timeline/index.jsx +++ b/app/javascript/mastodon/features/list_timeline/index.jsx @@ -8,6 +8,8 @@ import { Helmet } from 'react-helmet'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; +import Toggle from 'react-toggle'; + import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; import { fetchList, deleteList, updateList } from 'mastodon/actions/lists'; import { openModal } from 'mastodon/actions/modal'; @@ -145,7 +147,13 @@ class ListTimeline extends PureComponent { handleRepliesPolicyChange = ({ target }) => { const { dispatch } = this.props; const { id } = this.props.params; - dispatch(updateList(id, undefined, false, target.value)); + dispatch(updateList(id, undefined, false, undefined, target.value)); + }; + + onExclusiveToggle = ({ target }) => { + const { dispatch } = this.props; + const { id } = this.props.params; + dispatch(updateList(id, undefined, false, target.checked, undefined)); }; render () { @@ -154,6 +162,7 @@ class ListTimeline extends PureComponent { const pinned = !!columnId; const title = list ? list.get('title') : id; const replies_policy = list ? list.get('replies_policy') : undefined; + const isExclusive = list ? list.get('exclusive') : undefined; if (typeof list === 'undefined') { return ( @@ -191,6 +200,13 @@ class ListTimeline extends PureComponent { +
+ + +
+ { replies_policy !== undefined && (
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index f6d6daa3e52..09282de7c89 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -356,6 +356,7 @@ "lists.delete": "Delete list", "lists.edit": "Edit list", "lists.edit.submit": "Change title", + "lists.exclusive": "Hide these posts from home", "lists.new.create": "Add list", "lists.new.title_placeholder": "New list title", "lists.replies_policy.followed": "Any followed user", diff --git a/app/javascript/mastodon/reducers/list_editor.js b/app/javascript/mastodon/reducers/list_editor.js index ceceb27c7ab..d3fd62adecb 100644 --- a/app/javascript/mastodon/reducers/list_editor.js +++ b/app/javascript/mastodon/reducers/list_editor.js @@ -25,6 +25,7 @@ const initialState = ImmutableMap({ isSubmitting: false, isChanged: false, title: '', + isExclusive: false, accounts: ImmutableMap({ items: ImmutableList(), @@ -46,6 +47,7 @@ export default function listEditorReducer(state = initialState, action) { return state.withMutations(map => { map.set('listId', action.list.get('id')); map.set('title', action.list.get('title')); + map.set('isExclusive', action.list.get('is_exclusive')); map.set('isSubmitting', false); }); case LIST_EDITOR_TITLE_CHANGE: diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 643e6828d2c..7423d2d092e 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -40,9 +40,9 @@ class FeedManager def filter?(timeline_type, status, receiver) case timeline_type when :home - filter_from_home?(status, receiver.id, build_crutches(receiver.id, [status])) + filter_from_home?(status, receiver.id, build_crutches(receiver.id, [status]), :home) when :list - filter_from_list?(status, receiver) || filter_from_home?(status, receiver.account_id, build_crutches(receiver.account_id, [status])) + filter_from_list?(status, receiver) || filter_from_home?(status, receiver.account_id, build_crutches(receiver.account_id, [status]), :list) when :mentions filter_from_mentions?(status, receiver.id) when :tags @@ -351,10 +351,11 @@ class FeedManager # @param [Integer] receiver_id # @param [Hash] crutches # @return [Boolean] - def filter_from_home?(status, receiver_id, crutches) + def filter_from_home?(status, receiver_id, crutches, timeline_type = :home) return false if receiver_id == status.account_id return true if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?) - return true if crutches[:languages][status.account_id].present? && status.language.present? && !crutches[:languages][status.account_id].include?(status.language) + return true if timeline_type != :list && crutches[:exclusive_list_users][status.account_id].present? + return true if crutches[:languages][status.account_id].present? && status.language.present? && !crutches[:languages][status.account_id].include?(status.language) check_for_blocks = crutches[:active_mentions][status.id] || [] check_for_blocks.push(status.account_id) @@ -543,13 +544,16 @@ class FeedManager arr end - 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.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) - crutches[:blocked_by] = Block.where(target_account_id: receiver_id, account_id: statuses.map { |s| [s.account_id, s.reblog&.account_id] }.flatten.compact).pluck(:account_id).index_with(true) + lists = List.where(account_id: receiver_id, exclusive: 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.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) + crutches[:blocked_by] = Block.where(target_account_id: receiver_id, account_id: statuses.map { |s| [s.account_id, s.reblog&.account_id] }.flatten.compact).pluck(:account_id).index_with(true) + crutches[:exclusive_list_users] = ListAccount.where(list: lists, account_id: statuses.map(&:account_id)).pluck(:account_id).index_with(true) crutches end diff --git a/app/models/list.rb b/app/models/list.rb index bd1bdbd24de..7dc96f01b3c 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -10,6 +10,7 @@ # created_at :datetime not null # updated_at :datetime not null # replies_policy :integer default("list"), not null +# exclusive :boolean default(FALSE) # class List < ApplicationRecord diff --git a/app/serializers/rest/list_serializer.rb b/app/serializers/rest/list_serializer.rb index 3e87f711961..6a1b6ea3ebd 100644 --- a/app/serializers/rest/list_serializer.rb +++ b/app/serializers/rest/list_serializer.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class REST::ListSerializer < ActiveModel::Serializer - attributes :id, :title, :replies_policy + attributes :id, :title, :replies_policy, :exclusive def id object.id.to_s diff --git a/db/migrate/20230605085710_add_exclusive_to_lists.rb b/db/migrate/20230605085710_add_exclusive_to_lists.rb new file mode 100644 index 00000000000..cc21a3e315c --- /dev/null +++ b/db/migrate/20230605085710_add_exclusive_to_lists.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddExclusiveToLists < ActiveRecord::Migration[6.1] + def change + add_column :lists, :exclusive, :boolean, null: false, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 98fa5d6004f..35fbb8d2efe 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_05_31_154811) do +ActiveRecord::Schema.define(version: 2023_06_05_085710) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -567,6 +567,7 @@ ActiveRecord::Schema.define(version: 2023_05_31_154811) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "replies_policy", default: 0, null: false + t.boolean "exclusive", default: false t.index ["account_id"], name: "index_lists_on_account_id" end diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb index 79d1f5249e4..31b53fd879a 100644 --- a/spec/lib/feed_manager_spec.rb +++ b/spec/lib/feed_manager_spec.rb @@ -26,6 +26,7 @@ RSpec.describe FeedManager do let(:alice) { Fabricate(:account, username: 'alice') } let(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com') } let(:jeff) { Fabricate(:account, username: 'jeff') } + let(:list) { Fabricate(:list, account: alice) } context 'with home feed' do it 'returns false for followee\'s status' do @@ -153,6 +154,42 @@ RSpec.describe FeedManager do status = Fabricate(:status, text: 'Hallo Welt', account: bob, language: 'de') expect(FeedManager.instance.filter?(:home, status, alice)).to be false end + + it 'returns true for post from followee on exclusive list' do + list.exclusive = true + alice.follow!(bob) + list.accounts << bob + allow(List).to receive(:where).and_return(list) + status = Fabricate(:status, text: 'I post a lot', account: bob) + expect(FeedManager.instance.filter?(:home, status, alice)).to be true + end + + it 'returns true for reblog from followee on exclusive list' do + list.exclusive = true + alice.follow!(jeff) + list.accounts << jeff + allow(List).to receive(:where).and_return(list) + status = Fabricate(:status, text: 'I post a lot', account: bob) + reblog = Fabricate(:status, reblog: status, account: jeff) + expect(FeedManager.instance.filter?(:home, reblog, alice)).to be true + end + + it 'returns false for post from followee on non-exclusive list' do + list.exclusive = false + alice.follow!(bob) + list.accounts << bob + status = Fabricate(:status, text: 'I post a lot', account: bob) + expect(FeedManager.instance.filter?(:home, status, alice)).to be false + end + + it 'returns false for reblog from followee on non-exclusive list' do + list.exclusive = false + alice.follow!(jeff) + list.accounts << jeff + status = Fabricate(:status, text: 'I post a lot', account: bob) + reblog = Fabricate(:status, reblog: status, account: jeff) + expect(FeedManager.instance.filter?(:home, reblog, alice)).to be false + end end context 'with mentions feed' do From c2c396157fa76160b2bb28fd224b1eac40c9c220 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 5 Jun 2023 09:52:36 +0200 Subject: [PATCH 12/17] Fix design issues with recent react-intl upgrade (#25272) --- app/javascript/mastodon/locales/intl_provider.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/mastodon/locales/intl_provider.tsx b/app/javascript/mastodon/locales/intl_provider.tsx index 1ea77c798e1..4fa8b2247c7 100644 --- a/app/javascript/mastodon/locales/intl_provider.tsx +++ b/app/javascript/mastodon/locales/intl_provider.tsx @@ -48,6 +48,7 @@ export const IntlProvider: React.FC< locale={locale} messages={messages} onError={onProviderError} + textComponent='span' {...props} > {children} From a8310b15ed95dc262084940d79edb2f456f96742 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Mon, 5 Jun 2023 08:40:35 -0400 Subject: [PATCH 13/17] Update kt-paperclip 7.2 from sha (#25274) --- Gemfile | 2 +- Gemfile.lock | 20 +++++++------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/Gemfile b/Gemfile index cff8cb1f88a..ad164af1e49 100644 --- a/Gemfile +++ b/Gemfile @@ -20,7 +20,7 @@ gem 'dotenv-rails', '~> 2.8' gem 'aws-sdk-s3', '~> 1.123', 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' +gem 'kt-paperclip', '~> 7.2' gem 'blurhash', '~> 0.1' gem 'active_model_serializers', '~> 0.10' diff --git a/Gemfile.lock b/Gemfile.lock index bb209b38417..a9919bd3a28 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,18 +7,6 @@ GIT hkdf (~> 0.2) jwt (~> 2.0) -GIT - remote: https://github.com/kreeti/kt-paperclip.git - revision: 11abf222dc31bff71160a1d138b445214f434b2b - ref: 11abf222dc31bff71160a1d138b445214f434b2b - specs: - kt-paperclip (7.1.1) - activemodel (>= 4.2.0) - activesupport (>= 4.2.0) - marcel (~> 1.0.1) - mime-types - terrapin (~> 0.6.0) - GIT remote: https://github.com/mastodon/rails-settings-cached.git revision: 86328ef0bd04ce21cc0504ff5e334591e8c2ccab @@ -380,6 +368,12 @@ GEM activerecord kaminari-core (= 1.2.2) kaminari-core (1.2.2) + kt-paperclip (7.2.0) + activemodel (>= 4.2.0) + activesupport (>= 4.2.0) + marcel (~> 1.0.1) + mime-types + terrapin (~> 0.6.0) launchy (2.5.2) addressable (~> 2.8) letter_opener (1.8.1) @@ -813,7 +807,7 @@ DEPENDENCIES json-ld-preloaded (~> 3.2) json-schema (~> 4.0) kaminari (~> 1.2) - kt-paperclip (~> 7.1)! + kt-paperclip (~> 7.2) letter_opener (~> 1.8) letter_opener_web (~> 2.0) link_header (~> 0.0) From a6c898f50160c322eb53252a9a2fff8b7b93a0fb Mon Sep 17 00:00:00 2001 From: "S.H" Date: Mon, 5 Jun 2023 21:49:51 +0900 Subject: [PATCH 14/17] Fix not shown announcements in hometimeline. (#25251) --- app/javascript/mastodon/actions/importer/normalizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index 3232e12a2b4..9ed6b583b5f 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -138,7 +138,7 @@ export function normalizePollOptionTranslation(translation, poll) { export function normalizeAnnouncement(announcement) { const normalAnnouncement = { ...announcement }; - const emojiMap = makeEmojiMap.emojis(normalAnnouncement); + const emojiMap = makeEmojiMap(normalAnnouncement.emojis); normalAnnouncement.contentHtml = emojify(normalAnnouncement.content, emojiMap); From 70cd2d600029bb43e98994886b4e528c565fafd6 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 5 Jun 2023 09:51:25 -0300 Subject: [PATCH 15/17] Add test coverage for `Mastodon::CLI::Accounts#cull` (#25250) --- spec/lib/mastodon/cli/accounts_spec.rb | 139 +++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb index 50066572c69..cf1d612f3d1 100644 --- a/spec/lib/mastodon/cli/accounts_spec.rb +++ b/spec/lib/mastodon/cli/accounts_spec.rb @@ -1109,4 +1109,143 @@ describe Mastodon::CLI::Accounts do end end end + + describe '#cull' do + let(:delete_account_service) { instance_double(DeleteAccountService, call: nil) } + let!(:tom) { Fabricate(:account, updated_at: 30.days.ago, username: 'tom', uri: 'https://example.com/users/tom', domain: 'example.com') } + let!(:bob) { Fabricate(:account, updated_at: 30.days.ago, last_webfingered_at: nil, username: 'bob', uri: 'https://example.org/users/bob', domain: 'example.org') } + let!(:gon) { Fabricate(:account, updated_at: 15.days.ago, last_webfingered_at: 15.days.ago, username: 'gon', uri: 'https://example.net/users/gon', domain: 'example.net') } + let!(:ana) { Fabricate(:account, username: 'ana', uri: 'https://example.com/users/ana', domain: 'example.com') } + let!(:tales) { Fabricate(:account, updated_at: 10.days.ago, last_webfingered_at: nil, username: 'tales', uri: 'https://example.net/users/tales', domain: 'example.net') } + + before do + allow(DeleteAccountService).to receive(:new).and_return(delete_account_service) + end + + context 'when no domain is specified' do + let(:scope) { Account.remote.where(protocol: :activitypub).partitioned } + + before do + allow(cli).to receive(:parallelize_with_progress).and_yield(tom) + .and_yield(bob) + .and_yield(gon) + .and_yield(ana) + .and_yield(tales) + .and_return([5, 3]) + stub_request(:head, 'https://example.org/users/bob').to_return(status: 404) + stub_request(:head, 'https://example.net/users/gon').to_return(status: 410) + stub_request(:head, 'https://example.net/users/tales').to_return(status: 200) + end + + it 'deletes all inactive remote accounts that longer exist in the origin server' do + cli.cull + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(delete_account_service).to have_received(:call).with(bob, reserve_username: false).once + expect(delete_account_service).to have_received(:call).with(gon, reserve_username: false).once + end + + it 'does not delete any active remote account that still exists in the origin server' do + cli.cull + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(delete_account_service).to_not have_received(:call).with(tom, reserve_username: false) + expect(delete_account_service).to_not have_received(:call).with(ana, reserve_username: false) + expect(delete_account_service).to_not have_received(:call).with(tales, reserve_username: false) + end + + it 'touches inactive remote accounts that have not been deleted' do + allow(tales).to receive(:touch) + + cli.cull + + expect(tales).to have_received(:touch).once + end + + it 'displays the summary correctly' do + expect { cli.cull }.to output( + a_string_including('Visited 5 accounts, removed 3') + ).to_stdout + end + end + + context 'when a domain is specified' do + let(:domain) { 'example.net' } + let(:scope) { Account.remote.where(protocol: :activitypub, domain: domain).partitioned } + + before do + allow(cli).to receive(:parallelize_with_progress).and_yield(gon) + .and_yield(tales) + .and_return([2, 2]) + stub_request(:head, 'https://example.net/users/gon').to_return(status: 410) + stub_request(:head, 'https://example.net/users/tales').to_return(status: 404) + end + + it 'deletes inactive remote accounts that longer exist in the specified domain' do + cli.cull(domain) + + expect(cli).to have_received(:parallelize_with_progress).with(scope).once + expect(delete_account_service).to have_received(:call).with(gon, reserve_username: false).once + expect(delete_account_service).to have_received(:call).with(tales, reserve_username: false).once + end + + it 'displays the summary correctly' do + expect { cli.cull }.to output( + a_string_including('Visited 2 accounts, removed 2') + ).to_stdout + end + end + + context 'when a domain is unavailable' do + shared_examples 'an unavailable domain' do + before do + allow(cli).to receive(:parallelize_with_progress).and_yield(tales).and_return([1, 0]) + end + + it 'skips accounts from the unavailable domain' do + cli.cull + + expect(delete_account_service).to_not have_received(:call).with(tales, reserve_username: false) + end + + it 'displays the summary correctly' do + expect { cli.cull }.to output( + a_string_including("Visited 1 accounts, removed 0\nThe following domains were not available during the check:\n example.net") + ).to_stdout + end + end + + context 'when a connection timeout occurs' do + before do + stub_request(:head, 'https://example.net/users/tales').to_timeout + end + + it_behaves_like 'an unavailable domain' + end + + context 'when a connection error occurs' do + before do + stub_request(:head, 'https://example.net/users/tales').to_raise(HTTP::ConnectionError) + end + + it_behaves_like 'an unavailable domain' + end + + context 'when an ssl error occurs' do + before do + stub_request(:head, 'https://example.net/users/tales').to_raise(OpenSSL::SSL::SSLError) + end + + it_behaves_like 'an unavailable domain' + end + + context 'when a private network address error occurs' do + before do + stub_request(:head, 'https://example.net/users/tales').to_raise(Mastodon::PrivateNetworkAddressError) + end + + it_behaves_like 'an unavailable domain' + end + end + end end From 3b21c13dcc80bad4d8d1ec7c7c52470c5d3942aa Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 5 Jun 2023 10:52:33 -0400 Subject: [PATCH 16/17] Rails 7 compatibility fix for `Admin::Metrics::Dimension` classes (#25277) --- .../dimension/instance_accounts_dimension.rb | 19 +++++++----- .../dimension/instance_languages_dimension.rb | 25 ++++++++++++---- .../metrics/dimension/languages_dimension.rb | 19 +++++++----- .../admin/metrics/dimension/query_helper.rb | 13 ++++++++ .../metrics/dimension/servers_dimension.rb | 24 +++++++++++---- .../metrics/dimension/sources_dimension.rb | 20 ++++++++----- .../dimension/tag_languages_dimension.rb | 29 ++++++++++++++---- .../dimension/tag_servers_dimension.rb | 30 +++++++++++++++---- .../instance_accounts_dimension_spec.rb | 18 +++++++++++ .../instance_languages_dimension_spec.rb | 18 +++++++++++ .../dimension/languages_dimension_spec.rb | 18 +++++++++++ .../dimension/servers_dimension_spec.rb | 18 +++++++++++ .../software_versions_dimension_spec.rb | 18 +++++++++++ .../dimension/sources_dimension_spec.rb | 18 +++++++++++ .../dimension/space_usage_dimension_spec.rb | 18 +++++++++++ .../dimension/tag_languages_dimension_spec.rb | 18 +++++++++++ .../dimension/tag_servers_dimension_spec.rb | 18 +++++++++++ 17 files changed, 297 insertions(+), 44 deletions(-) create mode 100644 app/lib/admin/metrics/dimension/query_helper.rb create mode 100644 spec/lib/admin/metrics/dimension/instance_accounts_dimension_spec.rb create mode 100644 spec/lib/admin/metrics/dimension/instance_languages_dimension_spec.rb create mode 100644 spec/lib/admin/metrics/dimension/languages_dimension_spec.rb create mode 100644 spec/lib/admin/metrics/dimension/servers_dimension_spec.rb create mode 100644 spec/lib/admin/metrics/dimension/software_versions_dimension_spec.rb create mode 100644 spec/lib/admin/metrics/dimension/sources_dimension_spec.rb create mode 100644 spec/lib/admin/metrics/dimension/space_usage_dimension_spec.rb create mode 100644 spec/lib/admin/metrics/dimension/tag_languages_dimension_spec.rb create mode 100644 spec/lib/admin/metrics/dimension/tag_servers_dimension_spec.rb diff --git a/app/lib/admin/metrics/dimension/instance_accounts_dimension.rb b/app/lib/admin/metrics/dimension/instance_accounts_dimension.rb index 4eac8e611ea..f8eb9d7bfb8 100644 --- a/app/lib/admin/metrics/dimension/instance_accounts_dimension.rb +++ b/app/lib/admin/metrics/dimension/instance_accounts_dimension.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Admin::Metrics::Dimension::InstanceAccountsDimension < Admin::Metrics::Dimension::BaseDimension + include Admin::Metrics::Dimension::QueryHelper include LanguagesHelper def self.with_params? @@ -14,19 +15,23 @@ class Admin::Metrics::Dimension::InstanceAccountsDimension < Admin::Metrics::Dim protected def perform_query - sql = <<-SQL.squish + dimension_data_rows.map { |row| { key: row['username'], human_key: row['username'], value: row['value'].to_s } } + end + + def sql_array + [sql_query_string, { domain: params[:domain], limit: @limit }] + end + + def sql_query_string + <<~SQL.squish SELECT accounts.username, count(follows.*) AS value FROM accounts LEFT JOIN follows ON follows.target_account_id = accounts.id - WHERE accounts.domain = $1 + WHERE accounts.domain = :domain GROUP BY accounts.id, follows.target_account_id ORDER BY value DESC - LIMIT $2 + LIMIT :limit SQL - - rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, params[:domain]], [nil, @limit]]) - - rows.map { |row| { key: row['username'], human_key: row['username'], value: row['value'].to_s } } end def params diff --git a/app/lib/admin/metrics/dimension/instance_languages_dimension.rb b/app/lib/admin/metrics/dimension/instance_languages_dimension.rb index 1ede1a56e4b..b478213808e 100644 --- a/app/lib/admin/metrics/dimension/instance_languages_dimension.rb +++ b/app/lib/admin/metrics/dimension/instance_languages_dimension.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Admin::Metrics::Dimension::InstanceLanguagesDimension < Admin::Metrics::Dimension::BaseDimension + include Admin::Metrics::Dimension::QueryHelper include LanguagesHelper def self.with_params? @@ -14,21 +15,33 @@ class Admin::Metrics::Dimension::InstanceLanguagesDimension < Admin::Metrics::Di protected def perform_query - sql = <<-SQL.squish + dimension_data_rows.map { |row| { key: row['language'], human_key: standard_locale_name(row['language']), value: row['value'].to_s } } + end + + def sql_array + [sql_query_string, { domain: params[:domain], earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }] + end + + def sql_query_string + <<~SQL.squish SELECT COALESCE(statuses.language, 'und') AS language, count(*) AS value FROM statuses INNER JOIN accounts ON accounts.id = statuses.account_id - WHERE accounts.domain = $1 - AND statuses.id BETWEEN $2 AND $3 + WHERE accounts.domain = :domain + AND statuses.id BETWEEN :earliest_status_id AND :latest_status_id AND statuses.reblog_of_id IS NULL GROUP BY COALESCE(statuses.language, 'und') ORDER BY count(*) DESC - LIMIT $4 + LIMIT :limit SQL + end - rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, params[:domain]], [nil, Mastodon::Snowflake.id_at(@start_at, with_random: false)], [nil, Mastodon::Snowflake.id_at(@end_at, with_random: false)], [nil, @limit]]) + def earliest_status_id + Mastodon::Snowflake.id_at(@start_at, with_random: false) + end - rows.map { |row| { key: row['language'], human_key: standard_locale_name(row['language']), value: row['value'].to_s } } + def latest_status_id + Mastodon::Snowflake.id_at(@end_at, with_random: false) end def params diff --git a/app/lib/admin/metrics/dimension/languages_dimension.rb b/app/lib/admin/metrics/dimension/languages_dimension.rb index f1cf82cf276..100692a17b2 100644 --- a/app/lib/admin/metrics/dimension/languages_dimension.rb +++ b/app/lib/admin/metrics/dimension/languages_dimension.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Admin::Metrics::Dimension::LanguagesDimension < Admin::Metrics::Dimension::BaseDimension + include Admin::Metrics::Dimension::QueryHelper include LanguagesHelper def key @@ -10,18 +11,22 @@ class Admin::Metrics::Dimension::LanguagesDimension < Admin::Metrics::Dimension: protected def perform_query - sql = <<-SQL.squish + dimension_data_rows.map { |row| { key: row['locale'], human_key: standard_locale_name(row['locale']), value: row['value'].to_s } } + end + + def sql_array + [sql_query_string, { start_at: @start_at, end_at: @end_at, limit: @limit }] + end + + def sql_query_string + <<~SQL.squish SELECT locale, count(*) AS value FROM users - WHERE current_sign_in_at BETWEEN $1 AND $2 + WHERE current_sign_in_at BETWEEN :start_at AND :end_at AND locale IS NOT NULL GROUP BY locale ORDER BY count(*) DESC - LIMIT $3 + LIMIT :limit SQL - - rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, @limit]]) - - rows.map { |row| { key: row['locale'], human_key: standard_locale_name(row['locale']), value: row['value'].to_s } } end end diff --git a/app/lib/admin/metrics/dimension/query_helper.rb b/app/lib/admin/metrics/dimension/query_helper.rb new file mode 100644 index 00000000000..9fc953cb3e4 --- /dev/null +++ b/app/lib/admin/metrics/dimension/query_helper.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Admin::Metrics::Dimension::QueryHelper + protected + + def dimension_data_rows + ActiveRecord::Base.connection.select_all(sanitized_sql_string) + end + + def sanitized_sql_string + ActiveRecord::Base.sanitize_sql_array(sql_array) + end +end diff --git a/app/lib/admin/metrics/dimension/servers_dimension.rb b/app/lib/admin/metrics/dimension/servers_dimension.rb index 91bcce65510..42aba8e2132 100644 --- a/app/lib/admin/metrics/dimension/servers_dimension.rb +++ b/app/lib/admin/metrics/dimension/servers_dimension.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Admin::Metrics::Dimension::ServersDimension < Admin::Metrics::Dimension::BaseDimension + include Admin::Metrics::Dimension::QueryHelper + def key 'servers' end @@ -8,18 +10,30 @@ class Admin::Metrics::Dimension::ServersDimension < Admin::Metrics::Dimension::B protected def perform_query - sql = <<-SQL.squish + dimension_data_rows.map { |row| { key: row['domain'] || Rails.configuration.x.local_domain, human_key: row['domain'] || Rails.configuration.x.local_domain, value: row['value'].to_s } } + end + + def sql_array + [sql_query_string, { earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }] + end + + def sql_query_string + <<~SQL.squish SELECT accounts.domain, count(*) AS value FROM statuses INNER JOIN accounts ON accounts.id = statuses.account_id - WHERE statuses.id BETWEEN $1 AND $2 + WHERE statuses.id BETWEEN :earliest_status_id AND :latest_status_id GROUP BY accounts.domain ORDER BY count(*) DESC - LIMIT $3 + LIMIT :limit SQL + end - rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, Mastodon::Snowflake.id_at(@start_at)], [nil, Mastodon::Snowflake.id_at(@end_at)], [nil, @limit]]) + def earliest_status_id + Mastodon::Snowflake.id_at(@start_at) + end - rows.map { |row| { key: row['domain'] || Rails.configuration.x.local_domain, human_key: row['domain'] || Rails.configuration.x.local_domain, value: row['value'].to_s } } + def latest_status_id + Mastodon::Snowflake.id_at(@end_at) end end diff --git a/app/lib/admin/metrics/dimension/sources_dimension.rb b/app/lib/admin/metrics/dimension/sources_dimension.rb index 122807cdcdb..a14c3e7c162 100644 --- a/app/lib/admin/metrics/dimension/sources_dimension.rb +++ b/app/lib/admin/metrics/dimension/sources_dimension.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Admin::Metrics::Dimension::SourcesDimension < Admin::Metrics::Dimension::BaseDimension + include Admin::Metrics::Dimension::QueryHelper + def key 'sources' end @@ -8,18 +10,22 @@ class Admin::Metrics::Dimension::SourcesDimension < Admin::Metrics::Dimension::B protected def perform_query - sql = <<-SQL.squish + dimension_data_rows.map { |row| { key: row['name'] || 'web', human_key: row['name'] || I18n.t('admin.dashboard.website'), value: row['value'].to_s } } + end + + def sql_array + [sql_query_string, { start_at: @start_at, end_at: @end_at, limit: @limit }] + end + + def sql_query_string + <<~SQL.squish SELECT oauth_applications.name, count(*) AS value FROM users LEFT JOIN oauth_applications ON oauth_applications.id = users.created_by_application_id - WHERE users.created_at BETWEEN $1 AND $2 + WHERE users.created_at BETWEEN :start_at AND :end_at GROUP BY oauth_applications.name ORDER BY count(*) DESC - LIMIT $3 + LIMIT :limit SQL - - rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, @start_at], [nil, @end_at], [nil, @limit]]) - - rows.map { |row| { key: row['name'] || 'web', human_key: row['name'] || I18n.t('admin.dashboard.website'), value: row['value'].to_s } } end end diff --git a/app/lib/admin/metrics/dimension/tag_languages_dimension.rb b/app/lib/admin/metrics/dimension/tag_languages_dimension.rb index e1349c22943..cd077ff863d 100644 --- a/app/lib/admin/metrics/dimension/tag_languages_dimension.rb +++ b/app/lib/admin/metrics/dimension/tag_languages_dimension.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Admin::Metrics::Dimension::TagLanguagesDimension < Admin::Metrics::Dimension::BaseDimension + include Admin::Metrics::Dimension::QueryHelper include LanguagesHelper def self.with_params? @@ -14,20 +15,36 @@ class Admin::Metrics::Dimension::TagLanguagesDimension < Admin::Metrics::Dimensi protected def perform_query - sql = <<-SQL.squish + dimension_data_rows.map { |row| { key: row['language'], human_key: standard_locale_name(row['language']), value: row['value'].to_s } } + end + + def sql_array + [sql_query_string, { tag_id: tag_id, earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }] + end + + def sql_query_string + <<~SQL.squish SELECT COALESCE(statuses.language, 'und') AS language, count(*) AS value FROM statuses INNER JOIN statuses_tags ON statuses_tags.status_id = statuses.id - WHERE statuses_tags.tag_id = $1 - AND statuses.id BETWEEN $2 AND $3 + WHERE statuses_tags.tag_id = :tag_id + AND statuses.id BETWEEN :earliest_status_id AND :latest_status_id GROUP BY COALESCE(statuses.language, 'und') ORDER BY count(*) DESC - LIMIT $4 + LIMIT :limit SQL + end - rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, params[:id]], [nil, Mastodon::Snowflake.id_at(@start_at, with_random: false)], [nil, Mastodon::Snowflake.id_at(@end_at, with_random: false)], [nil, @limit]]) + def tag_id + params[:id] + end - rows.map { |row| { key: row['language'], human_key: standard_locale_name(row['language']), value: row['value'].to_s } } + def earliest_status_id + Mastodon::Snowflake.id_at(@start_at, with_random: false) + end + + def latest_status_id + Mastodon::Snowflake.id_at(@end_at, with_random: false) end def params diff --git a/app/lib/admin/metrics/dimension/tag_servers_dimension.rb b/app/lib/admin/metrics/dimension/tag_servers_dimension.rb index 7ddf3378cd4..fc5e49a966a 100644 --- a/app/lib/admin/metrics/dimension/tag_servers_dimension.rb +++ b/app/lib/admin/metrics/dimension/tag_servers_dimension.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Admin::Metrics::Dimension::TagServersDimension < Admin::Metrics::Dimension::BaseDimension + include Admin::Metrics::Dimension::QueryHelper + def self.with_params? true end @@ -12,21 +14,37 @@ class Admin::Metrics::Dimension::TagServersDimension < Admin::Metrics::Dimension protected def perform_query - sql = <<-SQL.squish + dimension_data_rows.map { |row| { key: row['domain'] || Rails.configuration.x.local_domain, human_key: row['domain'] || Rails.configuration.x.local_domain, value: row['value'].to_s } } + end + + def sql_array + [sql_query_string, { tag_id: tag_id, earliest_status_id: earliest_status_id, latest_status_id: latest_status_id, limit: @limit }] + end + + def sql_query_string + <<-SQL.squish SELECT accounts.domain, count(*) AS value FROM statuses INNER JOIN accounts ON accounts.id = statuses.account_id INNER JOIN statuses_tags ON statuses_tags.status_id = statuses.id - WHERE statuses_tags.tag_id = $1 - AND statuses.id BETWEEN $2 AND $3 + WHERE statuses_tags.tag_id = :tag_id + AND statuses.id BETWEEN :earliest_status_id AND :latest_status_id GROUP BY accounts.domain ORDER BY count(*) DESC - LIMIT $4 + LIMIT :limit SQL + end - rows = ActiveRecord::Base.connection.select_all(sql, nil, [[nil, params[:id]], [nil, Mastodon::Snowflake.id_at(@start_at, with_random: false)], [nil, Mastodon::Snowflake.id_at(@end_at, with_random: false)], [nil, @limit]]) + def tag_id + params[:id] + end - rows.map { |row| { key: row['domain'] || Rails.configuration.x.local_domain, human_key: row['domain'] || Rails.configuration.x.local_domain, value: row['value'].to_s } } + def earliest_status_id + Mastodon::Snowflake.id_at(@start_at, with_random: false) + end + + def latest_status_id + Mastodon::Snowflake.id_at(@end_at, with_random: false) end def params diff --git a/spec/lib/admin/metrics/dimension/instance_accounts_dimension_spec.rb b/spec/lib/admin/metrics/dimension/instance_accounts_dimension_spec.rb new file mode 100644 index 00000000000..106717f97b9 --- /dev/null +++ b/spec/lib/admin/metrics/dimension/instance_accounts_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::InstanceAccountsDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/instance_languages_dimension_spec.rb b/spec/lib/admin/metrics/dimension/instance_languages_dimension_spec.rb new file mode 100644 index 00000000000..f9f6430ca0d --- /dev/null +++ b/spec/lib/admin/metrics/dimension/instance_languages_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::InstanceLanguagesDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/languages_dimension_spec.rb b/spec/lib/admin/metrics/dimension/languages_dimension_spec.rb new file mode 100644 index 00000000000..1722c4c6166 --- /dev/null +++ b/spec/lib/admin/metrics/dimension/languages_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::LanguagesDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/servers_dimension_spec.rb b/spec/lib/admin/metrics/dimension/servers_dimension_spec.rb new file mode 100644 index 00000000000..7e2bb9ac0b7 --- /dev/null +++ b/spec/lib/admin/metrics/dimension/servers_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::ServersDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/software_versions_dimension_spec.rb b/spec/lib/admin/metrics/dimension/software_versions_dimension_spec.rb new file mode 100644 index 00000000000..ee14917330f --- /dev/null +++ b/spec/lib/admin/metrics/dimension/software_versions_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::SoftwareVersionsDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/sources_dimension_spec.rb b/spec/lib/admin/metrics/dimension/sources_dimension_spec.rb new file mode 100644 index 00000000000..d6b581a9bbe --- /dev/null +++ b/spec/lib/admin/metrics/dimension/sources_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::SourcesDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/space_usage_dimension_spec.rb b/spec/lib/admin/metrics/dimension/space_usage_dimension_spec.rb new file mode 100644 index 00000000000..65d04cfedd8 --- /dev/null +++ b/spec/lib/admin/metrics/dimension/space_usage_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::SpaceUsageDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/tag_languages_dimension_spec.rb b/spec/lib/admin/metrics/dimension/tag_languages_dimension_spec.rb new file mode 100644 index 00000000000..721d24fa188 --- /dev/null +++ b/spec/lib/admin/metrics/dimension/tag_languages_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::TagLanguagesDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end diff --git a/spec/lib/admin/metrics/dimension/tag_servers_dimension_spec.rb b/spec/lib/admin/metrics/dimension/tag_servers_dimension_spec.rb new file mode 100644 index 00000000000..30547168161 --- /dev/null +++ b/spec/lib/admin/metrics/dimension/tag_servers_dimension_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Admin::Metrics::Dimension::TagServersDimension do + subject(:dimension) { described_class.new(start_at, end_at, limit, params) } + + let(:start_at) { 2.days.ago } + let(:end_at) { Time.now.utc } + let(:limit) { 10 } + let(:params) { ActionController::Parameters.new } + + describe '#data' do + it 'runs data query without error' do + expect { dimension.data }.to_not raise_error + end + end +end From 1483a3ddfe74e4fb81d87447a1781943eab86c60 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 5 Jun 2023 17:32:24 +0200 Subject: [PATCH 17/17] Add data-nosnippet so Google doesn't use trending posts in snippets for / (#25279) --- app/javascript/mastodon/features/explore/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/explore/index.jsx b/app/javascript/mastodon/features/explore/index.jsx index dbc0400e8e5..185db0732a6 100644 --- a/app/javascript/mastodon/features/explore/index.jsx +++ b/app/javascript/mastodon/features/explore/index.jsx @@ -67,7 +67,7 @@ class Explore extends PureComponent {
-
+
{isSearching ? ( ) : (