From 1b3472bec8daa69aa511a81024fc22a3796357c9 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 04:19:53 -0400 Subject: [PATCH 01/51] Use account display name for pretend blog example in attribution area (#32188) --- app/views/settings/verifications/show.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/settings/verifications/show.html.haml b/app/views/settings/verifications/show.html.haml index 5318b0767ddfb0..af9556d0048704 100644 --- a/app/views/settings/verifications/show.html.haml +++ b/app/views/settings/verifications/show.html.haml @@ -50,13 +50,13 @@ = image_tag frontend_asset_url('images/preview.png'), alt: '', class: 'status-card__image-image' .status-card__content %span.status-card__host - %span= t('author_attribution.s_blog', name: @account.username) + %span= t('author_attribution.s_blog', name: display_name(@account)) · %time.time-ago{ datetime: 1.year.ago.to_date.iso8601 } %strong.status-card__title= t('author_attribution.example_title') .more-from-author = logo_as_symbol(:icon) - = t('author_attribution.more_from_html', name: link_to(root_url, class: 'story__details__shared__author-link') { image_tag(@account.avatar.url, class: 'account__avatar', width: 16, height: 16, alt: '') + content_tag(:bdi, display_name(@account)) }) + = t('author_attribution.more_from_html', name: link_to(root_url, class: 'story__details__shared__author-link') { image_tag(@account.avatar.url, class: 'account__avatar', width: 16, height: 16, alt: '') + tag.bdi(display_name(@account)) }) .actions = f.button :button, t('generic.save_changes'), type: :submit From 8ac00533ff1354a121b66c5148e9cce70e4e73e6 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Tue, 1 Oct 2024 10:22:14 +0200 Subject: [PATCH 02/51] Fix follow notifications from streaming being grouped (#32179) --- .../mastodon/actions/notification_groups.ts | 4 ++ .../mastodon/reducers/notification_groups.ts | 67 ++++++++++--------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/app/javascript/mastodon/actions/notification_groups.ts b/app/javascript/mastodon/actions/notification_groups.ts index b40b04f8ccfd69..a359913e614bb4 100644 --- a/app/javascript/mastodon/actions/notification_groups.ts +++ b/app/javascript/mastodon/actions/notification_groups.ts @@ -70,6 +70,10 @@ function dispatchAssociatedRecords( const supportedGroupedNotificationTypes = ['favourite', 'reblog']; +export function shouldGroupNotificationType(type: string) { + return supportedGroupedNotificationTypes.includes(type); +} + export const fetchNotifications = createDataLoadingThunk( 'notificationGroups/fetch', async (_params, { getState }) => diff --git a/app/javascript/mastodon/reducers/notification_groups.ts b/app/javascript/mastodon/reducers/notification_groups.ts index f3c83ccd8d6e44..375e64387633da 100644 --- a/app/javascript/mastodon/reducers/notification_groups.ts +++ b/app/javascript/mastodon/reducers/notification_groups.ts @@ -21,6 +21,7 @@ import { unmountNotifications, refreshStaleNotificationGroups, pollRecentNotifications, + shouldGroupNotificationType, } from 'mastodon/actions/notification_groups'; import { disconnectTimeline, @@ -205,46 +206,50 @@ function processNewNotification( groups: NotificationGroupsState['groups'], notification: ApiNotificationJSON, ) { - const existingGroupIndex = groups.findIndex( - (group) => - group.type !== 'gap' && group.group_key === notification.group_key, - ); + if (shouldGroupNotificationType(notification.type)) { + const existingGroupIndex = groups.findIndex( + (group) => + group.type !== 'gap' && group.group_key === notification.group_key, + ); - // In any case, we are going to add a group at the top - // If there is currently a gap at the top, now is the time to update it - if (groups.length > 0 && groups[0]?.type === 'gap') { - groups[0].maxId = notification.id; - } + // In any case, we are going to add a group at the top + // If there is currently a gap at the top, now is the time to update it + if (groups.length > 0 && groups[0]?.type === 'gap') { + groups[0].maxId = notification.id; + } - if (existingGroupIndex > -1) { - const existingGroup = groups[existingGroupIndex]; + if (existingGroupIndex > -1) { + const existingGroup = groups[existingGroupIndex]; - if ( - existingGroup && - existingGroup.type !== 'gap' && - !existingGroup.sampleAccountIds.includes(notification.account.id) // This can happen for example if you like, then unlike, then like again the same post - ) { - // Update the existing group if ( - existingGroup.sampleAccountIds.unshift(notification.account.id) > - NOTIFICATIONS_GROUP_MAX_AVATARS - ) - existingGroup.sampleAccountIds.pop(); + existingGroup && + existingGroup.type !== 'gap' && + !existingGroup.sampleAccountIds.includes(notification.account.id) // This can happen for example if you like, then unlike, then like again the same post + ) { + // Update the existing group + if ( + existingGroup.sampleAccountIds.unshift(notification.account.id) > + NOTIFICATIONS_GROUP_MAX_AVATARS + ) + existingGroup.sampleAccountIds.pop(); + + existingGroup.most_recent_notification_id = notification.id; + existingGroup.page_max_id = notification.id; + existingGroup.latest_page_notification_at = notification.created_at; + existingGroup.notifications_count += 1; - existingGroup.most_recent_notification_id = notification.id; - existingGroup.page_max_id = notification.id; - existingGroup.latest_page_notification_at = notification.created_at; - existingGroup.notifications_count += 1; + groups.splice(existingGroupIndex, 1); + mergeGapsAround(groups, existingGroupIndex); - groups.splice(existingGroupIndex, 1); - mergeGapsAround(groups, existingGroupIndex); + groups.unshift(existingGroup); - groups.unshift(existingGroup); + return; + } } - } else { - // Create a new group - groups.unshift(createNotificationGroupFromNotificationJSON(notification)); } + + // We have not found an existing group, create a new one + groups.unshift(createNotificationGroupFromNotificationJSON(notification)); } function trimNotifications(state: NotificationGroupsState) { From 1283c3544c383864314bb9fbc1fa8c964617f690 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 04:23:05 -0400 Subject: [PATCH 03/51] Avoid `id` duplication conflict with main navigation from settings profile link (#32181) --- app/views/settings/shared/_profile_navigation.html.haml | 4 ++-- config/navigation.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/settings/shared/_profile_navigation.html.haml b/app/views/settings/shared/_profile_navigation.html.haml index d490bd75661704..2f81cb5cfdccdf 100644 --- a/app/views/settings/shared/_profile_navigation.html.haml +++ b/app/views/settings/shared/_profile_navigation.html.haml @@ -1,7 +1,7 @@ .content__heading__tabs = render_navigation renderer: :links do |primary| :ruby - primary.item :profile, safe_join([material_symbol('person'), t('settings.edit_profile')]), settings_profile_path - primary.item :privacy, safe_join([material_symbol('lock'), t('privacy.title')]), settings_privacy_path + primary.item :edit_profile, safe_join([material_symbol('person'), t('settings.edit_profile')]), settings_profile_path + primary.item :privacy_reach, safe_join([material_symbol('lock'), t('privacy.title')]), settings_privacy_path primary.item :verification, safe_join([material_symbol('check'), t('verification.verification')]), settings_verification_path primary.item :featured_tags, safe_join([material_symbol('tag'), t('settings.featured_tags')]), settings_featured_tags_path diff --git a/config/navigation.rb b/config/navigation.rb index 33efb78b1eee5f..c79e96835b9b0a 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -35,7 +35,7 @@ s.item :export, safe_join([material_symbol('cloud_download'), t('settings.export')]), settings_export_path end - n.item :invites, safe_join([material_symbol('person_add'), t('invites.title')]), invites_path, if: -> { current_user.can?(:invite_users) && current_user.functional? && !self_destruct } + n.item :user_invites, safe_join([material_symbol('person_add'), t('invites.title')]), invites_path, if: -> { current_user.can?(:invite_users) && current_user.functional? && !self_destruct } n.item :development, safe_join([material_symbol('code'), t('settings.development')]), settings_applications_path, highlights_on: %r{/settings/applications}, if: -> { current_user.functional? && !self_destruct } n.item :trends, safe_join([material_symbol('trending_up'), t('admin.trends.title')]), admin_trends_statuses_path, if: -> { current_user.can?(:manage_taxonomies) && !self_destruct } do |s| From 6398d7b784424eef026aad60440975cd0020e4ab Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 08:28:50 +0000 Subject: [PATCH 04/51] Update peter-evans/create-pull-request action to v7.0.5 (#32164) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/crowdin-download-stable.yml | 2 +- .github/workflows/crowdin-download.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/crowdin-download-stable.yml b/.github/workflows/crowdin-download-stable.yml index c0b402a5b9300e..de21e2e58fcfff 100644 --- a/.github/workflows/crowdin-download-stable.yml +++ b/.github/workflows/crowdin-download-stable.yml @@ -50,7 +50,7 @@ jobs: # Create or update the pull request - name: Create Pull Request - uses: peter-evans/create-pull-request@v7.0.1 + uses: peter-evans/create-pull-request@v7.0.5 with: commit-message: 'New Crowdin translations' title: 'New Crowdin Translations for ${{ github.base_ref || github.ref_name }} (automated)' diff --git a/.github/workflows/crowdin-download.yml b/.github/workflows/crowdin-download.yml index f1817b3e9a1c36..900899dd526474 100644 --- a/.github/workflows/crowdin-download.yml +++ b/.github/workflows/crowdin-download.yml @@ -52,7 +52,7 @@ jobs: # Create or update the pull request - name: Create Pull Request - uses: peter-evans/create-pull-request@v7.0.1 + uses: peter-evans/create-pull-request@v7.0.5 with: commit-message: 'New Crowdin translations' title: 'New Crowdin Translations (automated)' From 6734b6550f6afbf62162c02697201d3e981fc575 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 04:36:41 -0400 Subject: [PATCH 05/51] Extract dashboard partial for admin instance page (#32189) --- .../admin/instances/_dashboard.html.haml | 66 ++++++++++++++++++ app/views/admin/instances/show.html.haml | 67 +------------------ 2 files changed, 67 insertions(+), 66 deletions(-) create mode 100644 app/views/admin/instances/_dashboard.html.haml diff --git a/app/views/admin/instances/_dashboard.html.haml b/app/views/admin/instances/_dashboard.html.haml new file mode 100644 index 00000000000000..ef8500103be462 --- /dev/null +++ b/app/views/admin/instances/_dashboard.html.haml @@ -0,0 +1,66 @@ +-# locals: (instance_domain:, period_end_at:, period_start_at:) +%p + = material_symbol 'info' + = t('admin.instances.totals_time_period_hint_html') + +.dashboard + .dashboard__item + = react_admin_component :counter, + end_at: period_end_at, + href: admin_accounts_path(origin: 'remote', by_domain: instance_domain), + label: t('admin.instances.dashboard.instance_accounts_measure'), + measure: 'instance_accounts', + params: { domain: instance_domain }, + start_at: period_start_at + .dashboard__item + = react_admin_component :counter, + end_at: period_end_at, + label: t('admin.instances.dashboard.instance_statuses_measure'), + measure: 'instance_statuses', + params: { domain: instance_domain }, + start_at: period_start_at + .dashboard__item + = react_admin_component :counter, + end_at: period_end_at, + label: t('admin.instances.dashboard.instance_media_attachments_measure'), + measure: 'instance_media_attachments', + params: { domain: instance_domain }, + start_at: period_start_at + .dashboard__item + = react_admin_component :counter, + end_at: period_end_at, + label: t('admin.instances.dashboard.instance_follows_measure'), + measure: 'instance_follows', + params: { domain: instance_domain }, + start_at: period_start_at + .dashboard__item + = react_admin_component :counter, + end_at: period_end_at, + label: t('admin.instances.dashboard.instance_followers_measure'), + measure: 'instance_followers', + params: { domain: instance_domain }, + start_at: period_start_at + .dashboard__item + = react_admin_component :counter, + end_at: period_end_at, + href: admin_reports_path(by_target_domain: instance_domain), + label: t('admin.instances.dashboard.instance_reports_measure'), + measure: 'instance_reports', + params: { domain: instance_domain }, + start_at: period_start_at + .dashboard__item + = react_admin_component :dimension, + dimension: 'instance_accounts', + end_at: period_end_at, + label: t('admin.instances.dashboard.instance_accounts_dimension'), + limit: 8, + params: { domain: instance_domain }, + start_at: period_start_at + .dashboard__item + = react_admin_component :dimension, + dimension: 'instance_languages', + end_at: period_end_at, + label: t('admin.instances.dashboard.instance_languages_dimension'), + limit: 8, + params: { domain: instance_domain }, + start_at: period_start_at diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml index c55eb89dc94933..dfedbf4cb4fc2c 100644 --- a/app/views/admin/instances/show.html.haml +++ b/app/views/admin/instances/show.html.haml @@ -8,72 +8,7 @@ = l(@time_period.last) - if @instance.persisted? - %p - = material_symbol 'info' - = t('admin.instances.totals_time_period_hint_html') - - .dashboard - .dashboard__item - = react_admin_component :counter, - end_at: @time_period.last, - href: admin_accounts_path(origin: 'remote', by_domain: @instance.domain), - label: t('admin.instances.dashboard.instance_accounts_measure'), - measure: 'instance_accounts', - params: { domain: @instance.domain }, - start_at: @time_period.first - .dashboard__item - = react_admin_component :counter, - end_at: @time_period.last, - label: t('admin.instances.dashboard.instance_statuses_measure'), - measure: 'instance_statuses', - params: { domain: @instance.domain }, - start_at: @time_period.first - .dashboard__item - = react_admin_component :counter, - end_at: @time_period.last, - label: t('admin.instances.dashboard.instance_media_attachments_measure'), - measure: 'instance_media_attachments', - params: { domain: @instance.domain }, - start_at: @time_period.first - .dashboard__item - = react_admin_component :counter, - end_at: @time_period.last, - label: t('admin.instances.dashboard.instance_follows_measure'), - measure: 'instance_follows', - params: { domain: @instance.domain }, - start_at: @time_period.first - .dashboard__item - = react_admin_component :counter, - end_at: @time_period.last, - label: t('admin.instances.dashboard.instance_followers_measure'), - measure: 'instance_followers', - params: { domain: @instance.domain }, - start_at: @time_period.first - .dashboard__item - = react_admin_component :counter, - end_at: @time_period.last, - href: admin_reports_path(by_target_domain: @instance.domain), - label: t('admin.instances.dashboard.instance_reports_measure'), - measure: 'instance_reports', - params: { domain: @instance.domain }, - start_at: @time_period.first - .dashboard__item - = react_admin_component :dimension, - dimension: 'instance_accounts', - end_at: @time_period.last, - label: t('admin.instances.dashboard.instance_accounts_dimension'), - limit: 8, - params: { domain: @instance.domain }, - start_at: @time_period.first - .dashboard__item - = react_admin_component :dimension, - dimension: 'instance_languages', - end_at: @time_period.last, - label: t('admin.instances.dashboard.instance_languages_dimension'), - limit: 8, - params: { domain: @instance.domain }, - start_at: @time_period.first - + = render 'dashboard', instance_domain: @instance.domain, period_end_at: @time_period.last, period_start_at: @time_period.first - else %p = t('admin.instances.unknown_instance') From c828e7731c894b9ac265defe5e35a77cc32fbd4c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 05:08:29 -0400 Subject: [PATCH 06/51] Improve alignment of icons on admin roles list (#32153) --- app/javascript/styles/mastodon/admin.scss | 4 ++++ app/javascript/styles/mastodon/tables.scss | 1 + 2 files changed, 5 insertions(+) diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 120bad27ed9324..da76fa11071d19 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -1054,6 +1054,10 @@ a.name-tag, } } + .icon { + vertical-align: middle; + } + a.announcements-list__item__title { &:hover, &:focus, diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss index af8ccf5b3839ef..0cbf5c1d554921 100644 --- a/app/javascript/styles/mastodon/tables.scss +++ b/app/javascript/styles/mastodon/tables.scss @@ -137,6 +137,7 @@ a.table-action-link { padding: 0 10px; color: $darker-text-color; font-weight: 500; + white-space: nowrap; &:hover { color: $highlight-text-color; From 25e8a6eaebb24c42dc1d91a8b8a3e8b2c4e1c303 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:13:29 +0200 Subject: [PATCH 07/51] Update dependency propshaft to v1.1.0 (#32192) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index b85d97761d2dd3..a06b4407a0d1ee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -601,7 +601,7 @@ GEM actionmailer (>= 3) net-smtp premailer (~> 1.7, >= 1.7.9) - propshaft (1.0.1) + propshaft (1.1.0) actionpack (>= 7.0.0) activesupport (>= 7.0.0) rack From e13453aec4df6eee809d196258501bde31cce0ad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:13:33 +0200 Subject: [PATCH 08/51] Update dependency webmock to v3.24.0 (#32190) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a06b4407a0d1ee..b3c37393f2652c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -698,7 +698,7 @@ GEM responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.3.7) + rexml (3.3.8) rotp (6.3.0) rouge (4.3.0) rpam2 (4.0.2) @@ -884,7 +884,7 @@ GEM webfinger (1.2.0) activesupport httpclient (>= 2.4) - webmock (3.23.1) + webmock (3.24.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) From bdceb1dacf13f49cd1f1bc2b644de4cf3c0646ac Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 05:30:21 -0400 Subject: [PATCH 09/51] Add `date_range` view helper (#32187) --- app/helpers/admin/dashboard_helper.rb | 5 +++++ app/views/admin/dashboard/index.html.haml | 4 +--- app/views/admin/instances/show.html.haml | 4 +--- app/views/admin/tags/show.html.haml | 4 +--- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/helpers/admin/dashboard_helper.rb b/app/helpers/admin/dashboard_helper.rb index 6096ff1381e7bb..4de779b79a80fb 100644 --- a/app/helpers/admin/dashboard_helper.rb +++ b/app/helpers/admin/dashboard_helper.rb @@ -18,6 +18,11 @@ def relevant_account_ip(account, ip_query) end end + def date_range(range) + [l(range.first), l(range.last)] + .join(' - ') + end + def relevant_account_timestamp(account) timestamp, exact = if account.user_current_sign_in_at && account.user_current_sign_in_at < 24.hours.ago [account.user_current_sign_in_at, true] diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 27d8f4790ba8be..2b4d02fa67c0a0 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -2,9 +2,7 @@ = t('admin.dashboard.title') - content_for :heading_actions do - = l(@time_period.first) - = ' - ' - = l(@time_period.last) + = date_range(@time_period) - unless @system_checks.empty? .flash-message-stack diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml index dfedbf4cb4fc2c..812a9c8870b0eb 100644 --- a/app/views/admin/instances/show.html.haml +++ b/app/views/admin/instances/show.html.haml @@ -3,9 +3,7 @@ - if current_user.can?(:view_dashboard) - content_for :heading_actions do - = l(@time_period.first) - = ' - ' - = l(@time_period.last) + = date_range(@time_period) - if @instance.persisted? = render 'dashboard', instance_domain: @instance.domain, period_end_at: @time_period.last, period_start_at: @time_period.first diff --git a/app/views/admin/tags/show.html.haml b/app/views/admin/tags/show.html.haml index 93387843b2ce41..462ca312a07e86 100644 --- a/app/views/admin/tags/show.html.haml +++ b/app/views/admin/tags/show.html.haml @@ -4,9 +4,7 @@ - content_for :heading_actions do - if current_user.can?(:view_dashboard) .time-period - = l(@time_period.first) - = ' - ' - = l(@time_period.last) + = date_range(@time_period) = link_to t('admin.tags.open'), tag_url(@tag), class: 'button', target: '_blank', rel: 'noopener noreferrer' From efa74a6c4400117336731e4e5d4b411a3642d4eb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 09:30:54 +0000 Subject: [PATCH 10/51] Update RuboCop (non-major) to v1.22.1 (#31573) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b3c37393f2652c..4c819ce9b2987c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -748,15 +748,15 @@ GEM parser (>= 3.3.1.0) rubocop-capybara (2.21.0) rubocop (~> 1.41) - rubocop-performance (1.21.1) + rubocop-performance (1.22.1) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rails (2.25.1) + rubocop-rails (2.26.2) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 1.33.0, < 2.0) + rubocop (>= 1.52.0, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rspec (3.0.4) + rubocop-rspec (3.0.5) rubocop (~> 1.61) rubocop-rspec_rails (2.30.0) rubocop (~> 1.61) From ce2481a81b8b781e06561c5a89ba2a3f178b246a Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Tue, 1 Oct 2024 11:38:42 +0200 Subject: [PATCH 11/51] Move OTP secret length to configuration (#32125) --- .../otp_authentication_controller.rb | 2 +- app/models/user.rb | 3 ++- spec/controllers/auth/sessions_controller_spec.rb | 6 +++--- spec/requests/auth/sessions/security_key_options_spec.rb | 2 +- spec/system/oauth_spec.rb | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/controllers/settings/two_factor_authentication/otp_authentication_controller.rb b/app/controllers/settings/two_factor_authentication/otp_authentication_controller.rb index 0bff01ec2793a4..ca8d46afe48199 100644 --- a/app/controllers/settings/two_factor_authentication/otp_authentication_controller.rb +++ b/app/controllers/settings/two_factor_authentication/otp_authentication_controller.rb @@ -15,7 +15,7 @@ def show end def create - session[:new_otp_secret] = User.generate_otp_secret(32) + session[:new_otp_secret] = User.generate_otp_secret redirect_to new_settings_two_factor_authentication_confirmation_path end diff --git a/app/models/user.rb b/app/models/user.rb index fcb0eced72e3b9..c32a575edff71a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -71,7 +71,8 @@ class User < ApplicationRecord ACTIVE_DURATION = ENV.fetch('USER_ACTIVE_DAYS', 7).to_i.days.freeze devise :two_factor_authenticatable, - otp_secret_encryption_key: Rails.configuration.x.otp_secret + otp_secret_encryption_key: Rails.configuration.x.otp_secret, + otp_secret_length: 32 include LegacyOtpSecret # Must be after the above `devise` line in order to override the legacy method diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb index 713ea3ff16c5bf..4a6956cb095e1c 100644 --- a/spec/controllers/auth/sessions_controller_spec.rb +++ b/spec/controllers/auth/sessions_controller_spec.rb @@ -208,7 +208,7 @@ context 'when using two-factor authentication' do context 'with OTP enabled as second factor' do let!(:user) do - Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32)) + Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret) end let!(:recovery_codes) do @@ -230,7 +230,7 @@ context 'when using email and password after an unfinished log-in attempt to a 2FA-protected account' do let!(:other_user) do - Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32)) + Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret) end before do @@ -342,7 +342,7 @@ context 'with WebAuthn and OTP enabled as second factor' do let!(:user) do - Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32)) + Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret) end let!(:webauthn_credential) do diff --git a/spec/requests/auth/sessions/security_key_options_spec.rb b/spec/requests/auth/sessions/security_key_options_spec.rb index 6246e4beb315f7..e53b9802b4c820 100644 --- a/spec/requests/auth/sessions/security_key_options_spec.rb +++ b/spec/requests/auth/sessions/security_key_options_spec.rb @@ -6,7 +6,7 @@ RSpec.describe 'Security Key Options' do describe 'GET /auth/sessions/security_key_options' do let!(:user) do - Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32)) + Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret) end context 'with WebAuthn and OTP enabled as second factor' do diff --git a/spec/system/oauth_spec.rb b/spec/system/oauth_spec.rb index 0f96a59675803b..64ac75879e666d 100644 --- a/spec/system/oauth_spec.rb +++ b/spec/system/oauth_spec.rb @@ -179,7 +179,7 @@ end context 'when the user has set up TOTP' do - let(:user) { Fabricate(:user, email: email, password: password, otp_required_for_login: true, otp_secret: User.generate_otp_secret(32)) } + let(:user) { Fabricate(:user, email: email, password: password, otp_required_for_login: true, otp_secret: User.generate_otp_secret) } it 'when accepting the authorization request' do params = { client_id: client_app.uid, response_type: 'code', redirect_uri: client_app.redirect_uri, scope: 'read' } From 66ef4b9984df3519f6f2f7fb41fc4a27beefefcf Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 05:54:42 -0400 Subject: [PATCH 12/51] Remove `WebfingerHelper` module & move usage inline (#31203) --- app/helpers/webfinger_helper.rb | 7 ------- app/models/remote_follow.rb | 3 +-- app/services/activitypub/fetch_remote_actor_service.rb | 5 ++--- app/services/resolve_account_service.rb | 5 ++--- 4 files changed, 5 insertions(+), 15 deletions(-) delete mode 100644 app/helpers/webfinger_helper.rb diff --git a/app/helpers/webfinger_helper.rb b/app/helpers/webfinger_helper.rb deleted file mode 100644 index 482f4e19eabef0..00000000000000 --- a/app/helpers/webfinger_helper.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -module WebfingerHelper - def webfinger!(uri) - Webfinger.new(uri).perform - end -end diff --git a/app/models/remote_follow.rb b/app/models/remote_follow.rb index 10715ac97da425..fa0586f57eb4c9 100644 --- a/app/models/remote_follow.rb +++ b/app/models/remote_follow.rb @@ -3,7 +3,6 @@ class RemoteFollow include ActiveModel::Validations include RoutingHelper - include WebfingerHelper attr_accessor :acct, :addressable_template @@ -66,7 +65,7 @@ def redirect_uri_template end def acct_resource - @acct_resource ||= webfinger!("acct:#{acct}") + @acct_resource ||= Webfinger.new("acct:#{acct}").perform rescue Webfinger::Error, HTTP::ConnectionError nil end diff --git a/app/services/activitypub/fetch_remote_actor_service.rb b/app/services/activitypub/fetch_remote_actor_service.rb index 2c372c2ec3fd84..560cf424e1539f 100644 --- a/app/services/activitypub/fetch_remote_actor_service.rb +++ b/app/services/activitypub/fetch_remote_actor_service.rb @@ -3,7 +3,6 @@ class ActivityPub::FetchRemoteActorService < BaseService include JsonLdHelper include DomainControlHelper - include WebfingerHelper class Error < StandardError; end @@ -45,7 +44,7 @@ def call(uri, prefetched_body: nil, break_on_redirect: false, only_key: false, s private def check_webfinger! - webfinger = webfinger!("acct:#{@username}@#{@domain}") + webfinger = Webfinger.new("acct:#{@username}@#{@domain}").perform confirmed_username, confirmed_domain = split_acct(webfinger.subject) if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero? @@ -54,7 +53,7 @@ def check_webfinger! return end - webfinger = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}") + webfinger = Webfinger.new("acct:#{confirmed_username}@#{confirmed_domain}").perform @username, @domain = split_acct(webfinger.subject) raise Webfinger::RedirectError, "Too many webfinger redirects for URI #{@uri} (stopped at #{@username}@#{@domain})" unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero? diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb index 8a5863baba6c7f..cd96b55c740e23 100644 --- a/app/services/resolve_account_service.rb +++ b/app/services/resolve_account_service.rb @@ -2,7 +2,6 @@ class ResolveAccountService < BaseService include DomainControlHelper - include WebfingerHelper include Redisable include Lockable @@ -81,7 +80,7 @@ def process_options!(uri, options) end def process_webfinger!(uri) - @webfinger = webfinger!("acct:#{uri}") + @webfinger = Webfinger.new("acct:#{uri}").perform confirmed_username, confirmed_domain = split_acct(@webfinger.subject) if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero? @@ -91,7 +90,7 @@ def process_webfinger!(uri) end # Account doesn't match, so it may have been redirected - @webfinger = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}") + @webfinger = Webfinger.new("acct:#{confirmed_username}@#{confirmed_domain}").perform @username, @domain = split_acct(@webfinger.subject) raise Webfinger::RedirectError, "Too many webfinger redirects for URI #{uri} (stopped at #{@username}@#{@domain})" unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero? From 4ad1e955eb97f7169bbf87990e72dc8b76019a85 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 06:02:58 -0400 Subject: [PATCH 13/51] Use `module: :users` in routes/admin section (#30767) --- config/routes/admin.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 50c4c10594a010..3dba6fa5b8eb6d 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -144,8 +144,10 @@ end resources :users, only: [] do - resource :two_factor_authentication, only: [:destroy], controller: 'users/two_factor_authentications' - resource :role, only: [:show, :update], controller: 'users/roles' + scope module: :users do + resource :two_factor_authentication, only: [:destroy] + resource :role, only: [:show, :update] + end end resources :custom_emojis, only: [:index, :new, :create] do From a473988969d539d102992703741ba3bfcf583de6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:04:12 +0000 Subject: [PATCH 14/51] Update dependency postcss-preset-env to v10.0.5 (#32019) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 38402229bdfcc9..8970981d88b1b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1763,9 +1763,9 @@ __metadata: languageName: node linkType: hard -"@csstools/postcss-light-dark-function@npm:^2.0.2": - version: 2.0.2 - resolution: "@csstools/postcss-light-dark-function@npm:2.0.2" +"@csstools/postcss-light-dark-function@npm:^2.0.4": + version: 2.0.4 + resolution: "@csstools/postcss-light-dark-function@npm:2.0.4" dependencies: "@csstools/css-parser-algorithms": "npm:^3.0.1" "@csstools/css-tokenizer": "npm:^3.0.1" @@ -1773,7 +1773,7 @@ __metadata: "@csstools/utilities": "npm:^2.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/f8973c435868998e5d6af1fc0c35b27bbf65fa9d0c35f5055c689b8ee2807a16802044e296f7def39a7253ae544fb49559e8273ee22eb4e21845aa980a1bc82b + checksum: 10c0/0176422ad9747953964b1ceff002df1ecb1952ebc481db6192070d68777135b582ea6fd32ae819b9c64c96cb9170bd6907c647c85b48daa4984b7ed3d7f9bccb languageName: node linkType: hard @@ -13921,8 +13921,8 @@ __metadata: linkType: hard "postcss-preset-env@npm:^10.0.0": - version: 10.0.3 - resolution: "postcss-preset-env@npm:10.0.3" + version: 10.0.5 + resolution: "postcss-preset-env@npm:10.0.5" dependencies: "@csstools/postcss-cascade-layers": "npm:^5.0.0" "@csstools/postcss-color-function": "npm:^4.0.2" @@ -13936,7 +13936,7 @@ __metadata: "@csstools/postcss-ic-unit": "npm:^4.0.0" "@csstools/postcss-initial": "npm:^2.0.0" "@csstools/postcss-is-pseudo-class": "npm:^5.0.0" - "@csstools/postcss-light-dark-function": "npm:^2.0.2" + "@csstools/postcss-light-dark-function": "npm:^2.0.4" "@csstools/postcss-logical-float-and-clear": "npm:^3.0.0" "@csstools/postcss-logical-overflow": "npm:^2.0.0" "@csstools/postcss-logical-overscroll-behavior": "npm:^2.0.0" @@ -13987,7 +13987,7 @@ __metadata: postcss-selector-not: "npm:^8.0.0" peerDependencies: postcss: ^8.4 - checksum: 10c0/da42caa2aab4d825fddfde00ebe2416d338c7b9a6f79a68840297888a8384f85991991c3fa10cf2d359fb230c885375f5cebd7bd63972725cd2a596d218f8b6a + checksum: 10c0/db5eb1175cb26bed3f1a4c47acc67935ffc784520321470520e59de366ac6f91be1e609fe36056af707ed20f7910721287cff0fae416c437dd3e944de13ffd05 languageName: node linkType: hard From 53624b1b5462037ebc1c002682f1469b81e98048 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 06:34:05 -0400 Subject: [PATCH 15/51] Remove explicit `put` action in settings forms (#32176) --- app/views/settings/applications/show.html.haml | 2 +- app/views/settings/preferences/appearance/show.html.haml | 2 +- app/views/settings/preferences/notifications/show.html.haml | 2 +- app/views/settings/preferences/other/show.html.haml | 2 +- app/views/settings/privacy/show.html.haml | 2 +- app/views/settings/profiles/show.html.haml | 2 +- app/views/settings/verifications/show.html.haml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/settings/applications/show.html.haml b/app/views/settings/applications/show.html.haml index 19630cf49b8ccb..099e0d96a8bcf5 100644 --- a/app/views/settings/applications/show.html.haml +++ b/app/views/settings/applications/show.html.haml @@ -23,7 +23,7 @@ %hr/ -= simple_form_for @application, url: settings_application_path(@application), method: :put do |form| += simple_form_for @application, url: settings_application_path(@application) do |form| = render form .actions diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index f34ce4a6aa5e61..22f88c7cdd7f55 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -4,7 +4,7 @@ - content_for :heading_actions do = button_tag t('generic.save_changes'), class: 'button', form: 'edit_user' -= simple_form_for current_user, url: settings_preferences_appearance_path, html: { method: :put, id: 'edit_user' } do |f| += simple_form_for current_user, url: settings_preferences_appearance_path, html: { id: :edit_user } do |f| .fields-row .fields-group.fields-row__column.fields-row__column-6 = f.input :locale, diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml index de318dda5437c1..a8e179d01914ec 100644 --- a/app/views/settings/preferences/notifications/show.html.haml +++ b/app/views/settings/preferences/notifications/show.html.haml @@ -4,7 +4,7 @@ - content_for :heading_actions do = button_tag t('generic.save_changes'), class: 'button', form: 'edit_notification' -= simple_form_for current_user, url: settings_preferences_notifications_path, html: { method: :put, id: 'edit_notification' } do |f| += simple_form_for current_user, url: settings_preferences_notifications_path, html: { id: :edit_notification } do |f| = render 'shared/error_messages', object: current_user %h4= t 'notifications.email_events' diff --git a/app/views/settings/preferences/other/show.html.haml b/app/views/settings/preferences/other/show.html.haml index e2888f7212935f..92df8e5213f209 100644 --- a/app/views/settings/preferences/other/show.html.haml +++ b/app/views/settings/preferences/other/show.html.haml @@ -4,7 +4,7 @@ - content_for :heading_actions do = button_tag t('generic.save_changes'), class: 'button', form: 'edit_preferences' -= simple_form_for current_user, url: settings_preferences_other_path, html: { method: :put, id: 'edit_preferences' } do |f| += simple_form_for current_user, url: settings_preferences_other_path, html: { id: :edit_preferences } do |f| = render 'shared/error_messages', object: current_user = f.simple_fields_for :settings, current_user.settings do |ff| diff --git a/app/views/settings/privacy/show.html.haml b/app/views/settings/privacy/show.html.haml index 3c14382587f593..f7241cfb2469f0 100644 --- a/app/views/settings/privacy/show.html.haml +++ b/app/views/settings/privacy/show.html.haml @@ -5,7 +5,7 @@ %h2= t('settings.profile') = render partial: 'settings/shared/profile_navigation' -= simple_form_for @account, url: settings_privacy_path, html: { method: :put } do |f| += simple_form_for @account, url: settings_privacy_path do |f| = render 'shared/error_messages', object: @account %p.lead= t('privacy.hint_html') diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml index 8fb21325197e3c..cad5aee669c88c 100644 --- a/app/views/settings/profiles/show.html.haml +++ b/app/views/settings/profiles/show.html.haml @@ -5,7 +5,7 @@ %h2= t('settings.profile') = render partial: 'settings/shared/profile_navigation' -= simple_form_for @account, url: settings_profile_path, html: { method: :put, id: 'edit_profile' } do |f| += simple_form_for @account, url: settings_profile_path, html: { id: :edit_profile } do |f| = render 'shared/error_messages', object: @account %p.lead= t('edit_profile.hint_html') diff --git a/app/views/settings/verifications/show.html.haml b/app/views/settings/verifications/show.html.haml index af9556d0048704..00491866c6592c 100644 --- a/app/views/settings/verifications/show.html.haml +++ b/app/views/settings/verifications/show.html.haml @@ -31,7 +31,7 @@ = material_symbol 'check', class: 'verified-badge__mark' %span= field.value -= simple_form_for @account, url: settings_verification_path, html: { method: :put, class: 'form-section' } do |f| += simple_form_for @account, url: settings_verification_path, html: { class: 'form-section' } do |f| = render 'shared/error_messages', object: @account %h3= t('author_attribution.title') From f517f0dbef10e02e7ef468ce23733b8eb53a7e46 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 06:48:27 -0400 Subject: [PATCH 16/51] Fix nav item active highlight for some paths (#32159) --- config/navigation.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/config/navigation.rb b/config/navigation.rb index c79e96835b9b0a..09a7b55b5e1382 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -25,13 +25,13 @@ n.item :statuses_cleanup, safe_join([material_symbol('history'), t('settings.statuses_cleanup')]), statuses_cleanup_path, if: -> { current_user.functional_or_moved? && !self_destruct } n.item :security, safe_join([material_symbol('lock'), t('settings.account')]), edit_user_registration_path do |s| - s.item :password, safe_join([material_symbol('lock'), t('settings.account_settings')]), edit_user_registration_path, highlights_on: %r{/auth/edit|/settings/delete|/settings/migration|/settings/aliases|/settings/login_activities|^/disputes} + s.item :password, safe_join([material_symbol('lock'), t('settings.account_settings')]), edit_user_registration_path, highlights_on: %r{^/auth|/settings/delete|/settings/migration|/settings/aliases|/settings/login_activities|^/disputes} s.item :two_factor_authentication, safe_join([material_symbol('safety_check'), t('settings.two_factor_authentication')]), settings_two_factor_authentication_methods_path, highlights_on: %r{/settings/two_factor_authentication|/settings/otp_authentication|/settings/security_keys} s.item :authorized_apps, safe_join([material_symbol('list_alt'), t('settings.authorized_apps')]), oauth_authorized_applications_path, if: -> { !self_destruct } end n.item :data, safe_join([material_symbol('cloud_sync'), t('settings.import_and_export')]), settings_export_path do |s| - s.item :import, safe_join([material_symbol('cloud_upload'), t('settings.import')]), settings_imports_path, if: -> { current_user.functional? && !self_destruct } + s.item :import, safe_join([material_symbol('cloud_upload'), t('settings.import')]), settings_imports_path, highlights_on: %r{/settings/imports}, if: -> { current_user.functional? && !self_destruct } s.item :export, safe_join([material_symbol('cloud_download'), t('settings.export')]), settings_export_path end @@ -51,7 +51,9 @@ s.item :accounts, safe_join([material_symbol('groups'), t('admin.accounts.title')]), admin_accounts_path(origin: 'local'), highlights_on: %r{/admin/accounts|admin/account_moderation_notes|/admin/pending_accounts|/admin/users}, if: -> { current_user.can?(:manage_users) } s.item :tags, safe_join([material_symbol('tag'), t('admin.tags.title')]), admin_tags_path, highlights_on: %r{/admin/tags}, if: -> { current_user.can?(:manage_taxonomies) } s.item :invites, safe_join([material_symbol('person_add'), t('admin.invites.title')]), admin_invites_path, if: -> { current_user.can?(:manage_invites) } - s.item :instances, safe_join([material_symbol('cloud'), t('admin.instances.title')]), admin_instances_path(limited: limited_federation_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.can?(:manage_federation) } + s.item :instances, safe_join([material_symbol('cloud'), t('admin.instances.title')]), admin_instances_path(limited: limited_federation_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows|/admin/export_domain_blocks}, if: lambda { + current_user.can?(:manage_federation) + } s.item :email_domain_blocks, safe_join([material_symbol('mail'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_path, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.can?(:manage_blocks) } s.item :ip_blocks, safe_join([material_symbol('hide_source'), t('admin.ip_blocks.title')]), admin_ip_blocks_path, highlights_on: %r{/admin/ip_blocks}, if: -> { current_user.can?(:manage_blocks) } s.item :action_logs, safe_join([material_symbol('list'), t('admin.action_logs.title')]), admin_action_logs_path, if: -> { current_user.can?(:view_audit_log) } From 4e6f13a0fb08b25b915e7a9d3f2e597ff0d3a2b9 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 07:03:13 -0400 Subject: [PATCH 17/51] Only show email domain blocks MX table when some found (#32155) --- app/views/admin/email_domain_blocks/new.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/email_domain_blocks/new.html.haml b/app/views/admin/email_domain_blocks/new.html.haml index dd4b83ee3f2832..2dfdca93763dda 100644 --- a/app/views/admin/email_domain_blocks/new.html.haml +++ b/app/views/admin/email_domain_blocks/new.html.haml @@ -16,7 +16,7 @@ label: I18n.t('admin.email_domain_blocks.allow_registrations_with_approval'), wrapper: :with_label - - if defined?(@resolved_records) + - if defined?(@resolved_records) && @resolved_records.any? %p.hint= t('admin.email_domain_blocks.resolved_dns_records_hint_html') .batch-table From 1e192421342e3290f7c4133c32156b85a25bedc9 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 07:36:25 -0400 Subject: [PATCH 18/51] Extract constants for header and avatar geometry (#32151) --- app/models/concerns/account/avatar.rb | 7 +++++-- app/models/concerns/account/header.rb | 5 ++++- app/views/settings/profiles/show.html.haml | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/models/concerns/account/avatar.rb b/app/models/concerns/account/avatar.rb index 39f599db182275..5ca8fa862f9495 100644 --- a/app/models/concerns/account/avatar.rb +++ b/app/models/concerns/account/avatar.rb @@ -6,10 +6,13 @@ module Account::Avatar IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze LIMIT = 2.megabytes + AVATAR_DIMENSIONS = [400, 400].freeze + AVATAR_GEOMETRY = [AVATAR_DIMENSIONS.first, AVATAR_DIMENSIONS.last].join('x') + class_methods do def avatar_styles(file) - styles = { original: { geometry: '400x400#', file_geometry_parser: FastGeometryParser } } - styles[:static] = { geometry: '400x400#', format: 'png', convert_options: '-coalesce', file_geometry_parser: FastGeometryParser } if file.content_type == 'image/gif' + styles = { original: { geometry: "#{AVATAR_GEOMETRY}#", file_geometry_parser: FastGeometryParser } } + styles[:static] = { geometry: "#{AVATAR_GEOMETRY}#", format: 'png', convert_options: '-coalesce', file_geometry_parser: FastGeometryParser } if file.content_type == 'image/gif' styles end diff --git a/app/models/concerns/account/header.rb b/app/models/concerns/account/header.rb index 44ae774e94d034..2a47097fcfd038 100644 --- a/app/models/concerns/account/header.rb +++ b/app/models/concerns/account/header.rb @@ -5,7 +5,10 @@ module Account::Header IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze LIMIT = 2.megabytes - MAX_PIXELS = 750_000 # 1500x500px + + HEADER_DIMENSIONS = [1500, 500].freeze + HEADER_GEOMETRY = [HEADER_DIMENSIONS.first, HEADER_DIMENSIONS.last].join('x') + MAX_PIXELS = HEADER_DIMENSIONS.first * HEADER_DIMENSIONS.last class_methods do def header_styles(file) diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml index cad5aee669c88c..427a4fa95ac884 100644 --- a/app/views/settings/profiles/show.html.haml +++ b/app/views/settings/profiles/show.html.haml @@ -34,7 +34,7 @@ .fields-row__column.fields-row__column-6 .fields-group = f.input :avatar, - hint: t('simple_form.hints.defaults.avatar', dimensions: '400x400', size: number_to_human_size(Account::Avatar::LIMIT)), + hint: t('simple_form.hints.defaults.avatar', dimensions: Account::Avatar::AVATAR_GEOMETRY, size: number_to_human_size(Account::Avatar::LIMIT)), input_html: { accept: Account::Avatar::IMAGE_MIME_TYPES.join(',') }, wrapper: :with_block_label @@ -50,7 +50,7 @@ .fields-row__column.fields-row__column-6 .fields-group = f.input :header, - hint: t('simple_form.hints.defaults.header', dimensions: '1500x500', size: number_to_human_size(Account::Header::LIMIT)), + hint: t('simple_form.hints.defaults.header', dimensions: Account::Header::HEADER_GEOMETRY, size: number_to_human_size(Account::Header::LIMIT)), input_html: { accept: Account::Header::IMAGE_MIME_TYPES.join(',') }, wrapper: :with_block_label From 97db4bd4ddb62f7300d8fc0c6e09c9699b569981 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 08:45:58 -0400 Subject: [PATCH 19/51] Wrap datetime in `time` element with attrs (#32177) --- app/views/settings/exports/show.html.haml | 3 ++- app/views/settings/imports/index.html.haml | 5 ++++- app/views/severed_relationships/index.html.haml | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/views/settings/exports/show.html.haml b/app/views/settings/exports/show.html.haml index 320bb0c7cebcbb..273c5a4ba6feac 100644 --- a/app/views/settings/exports/show.html.haml +++ b/app/views/settings/exports/show.html.haml @@ -61,7 +61,8 @@ %tbody - @backups.each do |backup| %tr - %td= l backup.created_at + %td + %time.formatted{ datetime: backup.created_at.iso8601, title: l(backup.created_at) }= l backup.created_at - if backup.processed? %td= number_to_human_size backup.dump_file_size %td= table_link_to 'download', t('exports.archive_takeout.download'), download_backup_url(backup) diff --git a/app/views/settings/imports/index.html.haml b/app/views/settings/imports/index.html.haml index 634631b5aaa4cc..55421991e13173 100644 --- a/app/views/settings/imports/index.html.haml +++ b/app/views/settings/imports/index.html.haml @@ -55,7 +55,10 @@ = t("imports.states.#{import.state}") %td #{import.imported_items} / #{import.total_items} - %td= l(import.created_at) + %td + %time.formatted{ datetime: import.created_at.iso8601, title: l(import.created_at) } + = l(import.created_at) + %td - num_failed = import.processed_items - import.imported_items - if num_failed.positive? diff --git a/app/views/severed_relationships/index.html.haml b/app/views/severed_relationships/index.html.haml index 7c599e9c0e7e55..cc9439b4681eed 100644 --- a/app/views/severed_relationships/index.html.haml +++ b/app/views/severed_relationships/index.html.haml @@ -15,7 +15,9 @@ %tbody - @events.each do |event| %tr - %td= l event.created_at + %td + %time.formatted{ datetime: event.created_at.iso8601, title: l(event.created_at) } + = l(event.created_at) %td= t("severed_relationships.event_type.#{event.type}", target_name: event.target_name) - if event.purged? %td{ rowspan: 2 }= t('severed_relationships.purged') From f397550311232ba23a70b52e3d557d3399766522 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Tue, 1 Oct 2024 14:49:04 +0200 Subject: [PATCH 20/51] Add detection and download of material_symbol icons in config/navigation.rb (#31366) --- .../material-icons/400-24px/breaking_news-fill.svg | 1 + .../material-icons/400-24px/breaking_news.svg | 2 +- .../400-24px/captive_portal-fill.svg | 1 + .../material-icons/400-24px/captive_portal.svg | 2 +- .../material-icons/400-24px/chat_bubble-fill.svg | 1 + .../material-icons/400-24px/chat_bubble.svg | 2 +- .../material-icons/400-24px/cloud-fill.svg | 1 + app/javascript/material-icons/400-24px/cloud.svg | 2 +- .../400-24px/cloud_download-fill.svg | 1 + .../material-icons/400-24px/cloud_download.svg | 2 +- .../material-icons/400-24px/cloud_sync-fill.svg | 1 + .../material-icons/400-24px/cloud_sync.svg | 2 +- .../material-icons/400-24px/cloud_upload-fill.svg | 1 + .../material-icons/400-24px/cloud_upload.svg | 2 +- .../material-icons/400-24px/code-fill.svg | 1 + app/javascript/material-icons/400-24px/code.svg | 2 +- .../material-icons/400-24px/computer-fill.svg | 1 + .../material-icons/400-24px/computer.svg | 2 +- .../material-icons/400-24px/contact_mail-fill.svg | 1 + .../material-icons/400-24px/contact_mail.svg | 2 +- .../material-icons/400-24px/database-fill.svg | 1 + .../material-icons/400-24px/database.svg | 2 +- .../material-icons/400-24px/diamond-fill.svg | 1 + app/javascript/material-icons/400-24px/diamond.svg | 2 +- .../material-icons/400-24px/filter_alt-fill.svg | 1 + .../material-icons/400-24px/filter_alt.svg | 2 +- .../material-icons/400-24px/groups-fill.svg | 1 + app/javascript/material-icons/400-24px/groups.svg | 2 +- .../material-icons/400-24px/hide_source-fill.svg | 1 + .../material-icons/400-24px/hide_source.svg | 2 +- .../material-icons/400-24px/inbox-fill.svg | 1 + app/javascript/material-icons/400-24px/inbox.svg | 2 +- .../material-icons/400-24px/list-fill.svg | 1 + app/javascript/material-icons/400-24px/list.svg | 2 +- .../material-icons/400-24px/mail-fill.svg | 1 + app/javascript/material-icons/400-24px/mail.svg | 2 +- .../material-icons/400-24px/mood-fill.svg | 1 + app/javascript/material-icons/400-24px/mood.svg | 2 +- .../material-icons/400-24px/report-fill.svg | 1 + app/javascript/material-icons/400-24px/report.svg | 2 +- .../material-icons/400-24px/safety_check-fill.svg | 1 + .../material-icons/400-24px/safety_check.svg | 2 +- .../material-icons/400-24px/speed-fill.svg | 1 + app/javascript/material-icons/400-24px/speed.svg | 2 +- .../material-icons/400-24px/trending_up-fill.svg | 1 + .../material-icons/400-24px/trending_up.svg | 2 +- lib/tasks/icons.rake | 14 ++++++++++++++ 47 files changed, 60 insertions(+), 23 deletions(-) create mode 100644 app/javascript/material-icons/400-24px/breaking_news-fill.svg create mode 100644 app/javascript/material-icons/400-24px/captive_portal-fill.svg create mode 100644 app/javascript/material-icons/400-24px/chat_bubble-fill.svg create mode 100644 app/javascript/material-icons/400-24px/cloud-fill.svg create mode 100644 app/javascript/material-icons/400-24px/cloud_download-fill.svg create mode 100644 app/javascript/material-icons/400-24px/cloud_sync-fill.svg create mode 100644 app/javascript/material-icons/400-24px/cloud_upload-fill.svg create mode 100644 app/javascript/material-icons/400-24px/code-fill.svg create mode 100644 app/javascript/material-icons/400-24px/computer-fill.svg create mode 100644 app/javascript/material-icons/400-24px/contact_mail-fill.svg create mode 100644 app/javascript/material-icons/400-24px/database-fill.svg create mode 100644 app/javascript/material-icons/400-24px/diamond-fill.svg create mode 100644 app/javascript/material-icons/400-24px/filter_alt-fill.svg create mode 100644 app/javascript/material-icons/400-24px/groups-fill.svg create mode 100644 app/javascript/material-icons/400-24px/hide_source-fill.svg create mode 100644 app/javascript/material-icons/400-24px/inbox-fill.svg create mode 100644 app/javascript/material-icons/400-24px/list-fill.svg create mode 100644 app/javascript/material-icons/400-24px/mail-fill.svg create mode 100644 app/javascript/material-icons/400-24px/mood-fill.svg create mode 100644 app/javascript/material-icons/400-24px/report-fill.svg create mode 100644 app/javascript/material-icons/400-24px/safety_check-fill.svg create mode 100644 app/javascript/material-icons/400-24px/speed-fill.svg create mode 100644 app/javascript/material-icons/400-24px/trending_up-fill.svg diff --git a/app/javascript/material-icons/400-24px/breaking_news-fill.svg b/app/javascript/material-icons/400-24px/breaking_news-fill.svg new file mode 100644 index 00000000000000..633ca48d57d6b4 --- /dev/null +++ b/app/javascript/material-icons/400-24px/breaking_news-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/breaking_news.svg b/app/javascript/material-icons/400-24px/breaking_news.svg index d7dd0c12f47fec..c043f11a8b0207 100644 --- a/app/javascript/material-icons/400-24px/breaking_news.svg +++ b/app/javascript/material-icons/400-24px/breaking_news.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/captive_portal-fill.svg b/app/javascript/material-icons/400-24px/captive_portal-fill.svg new file mode 100644 index 00000000000000..5c0b26fb647b18 --- /dev/null +++ b/app/javascript/material-icons/400-24px/captive_portal-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/captive_portal.svg b/app/javascript/material-icons/400-24px/captive_portal.svg index 1f0f09c7734912..5c0b26fb647b18 100644 --- a/app/javascript/material-icons/400-24px/captive_portal.svg +++ b/app/javascript/material-icons/400-24px/captive_portal.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/chat_bubble-fill.svg b/app/javascript/material-icons/400-24px/chat_bubble-fill.svg new file mode 100644 index 00000000000000..b47338a6c9c9ab --- /dev/null +++ b/app/javascript/material-icons/400-24px/chat_bubble-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/chat_bubble.svg b/app/javascript/material-icons/400-24px/chat_bubble.svg index 7d210b460865ee..05d976d242d866 100644 --- a/app/javascript/material-icons/400-24px/chat_bubble.svg +++ b/app/javascript/material-icons/400-24px/chat_bubble.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/cloud-fill.svg b/app/javascript/material-icons/400-24px/cloud-fill.svg new file mode 100644 index 00000000000000..d049a74c0191e3 --- /dev/null +++ b/app/javascript/material-icons/400-24px/cloud-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/cloud.svg b/app/javascript/material-icons/400-24px/cloud.svg index 75b4e957fc416a..a36bddda918e0f 100644 --- a/app/javascript/material-icons/400-24px/cloud.svg +++ b/app/javascript/material-icons/400-24px/cloud.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/cloud_download-fill.svg b/app/javascript/material-icons/400-24px/cloud_download-fill.svg new file mode 100644 index 00000000000000..c55d49f7e5c0dd --- /dev/null +++ b/app/javascript/material-icons/400-24px/cloud_download-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/cloud_download.svg b/app/javascript/material-icons/400-24px/cloud_download.svg index 2fc3717ff9e35e..8e9314800cca76 100644 --- a/app/javascript/material-icons/400-24px/cloud_download.svg +++ b/app/javascript/material-icons/400-24px/cloud_download.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/cloud_sync-fill.svg b/app/javascript/material-icons/400-24px/cloud_sync-fill.svg new file mode 100644 index 00000000000000..0c648e19e4f36b --- /dev/null +++ b/app/javascript/material-icons/400-24px/cloud_sync-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/cloud_sync.svg b/app/javascript/material-icons/400-24px/cloud_sync.svg index dbf6adc0001ada..461796e323ff9a 100644 --- a/app/javascript/material-icons/400-24px/cloud_sync.svg +++ b/app/javascript/material-icons/400-24px/cloud_sync.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/cloud_upload-fill.svg b/app/javascript/material-icons/400-24px/cloud_upload-fill.svg new file mode 100644 index 00000000000000..66a7bb22d3511e --- /dev/null +++ b/app/javascript/material-icons/400-24px/cloud_upload-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/cloud_upload.svg b/app/javascript/material-icons/400-24px/cloud_upload.svg index 5e1a4b9aef684f..94968cb94713bf 100644 --- a/app/javascript/material-icons/400-24px/cloud_upload.svg +++ b/app/javascript/material-icons/400-24px/cloud_upload.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/code-fill.svg b/app/javascript/material-icons/400-24px/code-fill.svg new file mode 100644 index 00000000000000..8ef5c55cd48598 --- /dev/null +++ b/app/javascript/material-icons/400-24px/code-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/code.svg b/app/javascript/material-icons/400-24px/code.svg index 5bdc338f7f5bc6..8ef5c55cd48598 100644 --- a/app/javascript/material-icons/400-24px/code.svg +++ b/app/javascript/material-icons/400-24px/code.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/computer-fill.svg b/app/javascript/material-icons/400-24px/computer-fill.svg new file mode 100644 index 00000000000000..91295d68463510 --- /dev/null +++ b/app/javascript/material-icons/400-24px/computer-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/computer.svg b/app/javascript/material-icons/400-24px/computer.svg index 8c5bd9110e1d23..b8af5d46447944 100644 --- a/app/javascript/material-icons/400-24px/computer.svg +++ b/app/javascript/material-icons/400-24px/computer.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/contact_mail-fill.svg b/app/javascript/material-icons/400-24px/contact_mail-fill.svg new file mode 100644 index 00000000000000..c42c799955cc9a --- /dev/null +++ b/app/javascript/material-icons/400-24px/contact_mail-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/contact_mail.svg b/app/javascript/material-icons/400-24px/contact_mail.svg index 1ae26cc4d157ed..4547c48ec5b7b4 100644 --- a/app/javascript/material-icons/400-24px/contact_mail.svg +++ b/app/javascript/material-icons/400-24px/contact_mail.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/database-fill.svg b/app/javascript/material-icons/400-24px/database-fill.svg new file mode 100644 index 00000000000000..3520f6961446e6 --- /dev/null +++ b/app/javascript/material-icons/400-24px/database-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/database.svg b/app/javascript/material-icons/400-24px/database.svg index 54ca2f4e560b2e..a3bc2bfbc24474 100644 --- a/app/javascript/material-icons/400-24px/database.svg +++ b/app/javascript/material-icons/400-24px/database.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/diamond-fill.svg b/app/javascript/material-icons/400-24px/diamond-fill.svg new file mode 100644 index 00000000000000..474968ad6f8cdf --- /dev/null +++ b/app/javascript/material-icons/400-24px/diamond-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/diamond.svg b/app/javascript/material-icons/400-24px/diamond.svg index 26f4814b443b95..b604492fa8bf7c 100644 --- a/app/javascript/material-icons/400-24px/diamond.svg +++ b/app/javascript/material-icons/400-24px/diamond.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/filter_alt-fill.svg b/app/javascript/material-icons/400-24px/filter_alt-fill.svg new file mode 100644 index 00000000000000..ec1d90bba6bbd5 --- /dev/null +++ b/app/javascript/material-icons/400-24px/filter_alt-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/filter_alt.svg b/app/javascript/material-icons/400-24px/filter_alt.svg index 0294cf1da53ac0..e4af9efd5d4d67 100644 --- a/app/javascript/material-icons/400-24px/filter_alt.svg +++ b/app/javascript/material-icons/400-24px/filter_alt.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/groups-fill.svg b/app/javascript/material-icons/400-24px/groups-fill.svg new file mode 100644 index 00000000000000..754eb0946c291c --- /dev/null +++ b/app/javascript/material-icons/400-24px/groups-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/groups.svg b/app/javascript/material-icons/400-24px/groups.svg index 0e795eb301d89d..998ff03729bd58 100644 --- a/app/javascript/material-icons/400-24px/groups.svg +++ b/app/javascript/material-icons/400-24px/groups.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/hide_source-fill.svg b/app/javascript/material-icons/400-24px/hide_source-fill.svg new file mode 100644 index 00000000000000..959631bc1aa150 --- /dev/null +++ b/app/javascript/material-icons/400-24px/hide_source-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/hide_source.svg b/app/javascript/material-icons/400-24px/hide_source.svg index d103ed770a5d6b..09633cef8c83b6 100644 --- a/app/javascript/material-icons/400-24px/hide_source.svg +++ b/app/javascript/material-icons/400-24px/hide_source.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/inbox-fill.svg b/app/javascript/material-icons/400-24px/inbox-fill.svg new file mode 100644 index 00000000000000..15ae2d8f3c4068 --- /dev/null +++ b/app/javascript/material-icons/400-24px/inbox-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/inbox.svg b/app/javascript/material-icons/400-24px/inbox.svg index 427817958c169a..32c727e8104990 100644 --- a/app/javascript/material-icons/400-24px/inbox.svg +++ b/app/javascript/material-icons/400-24px/inbox.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/list-fill.svg b/app/javascript/material-icons/400-24px/list-fill.svg new file mode 100644 index 00000000000000..c9cbe35eb52fa9 --- /dev/null +++ b/app/javascript/material-icons/400-24px/list-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/list.svg b/app/javascript/material-icons/400-24px/list.svg index 457a820ab1781b..c9cbe35eb52fa9 100644 --- a/app/javascript/material-icons/400-24px/list.svg +++ b/app/javascript/material-icons/400-24px/list.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/mail-fill.svg b/app/javascript/material-icons/400-24px/mail-fill.svg new file mode 100644 index 00000000000000..5e7e4a2fb246b3 --- /dev/null +++ b/app/javascript/material-icons/400-24px/mail-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/mail.svg b/app/javascript/material-icons/400-24px/mail.svg index a92ea7b198a82f..15e1d12d4e1a21 100644 --- a/app/javascript/material-icons/400-24px/mail.svg +++ b/app/javascript/material-icons/400-24px/mail.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/mood-fill.svg b/app/javascript/material-icons/400-24px/mood-fill.svg new file mode 100644 index 00000000000000..9480d0fb92aaa3 --- /dev/null +++ b/app/javascript/material-icons/400-24px/mood-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/mood.svg b/app/javascript/material-icons/400-24px/mood.svg index 27b3534244888c..46cafa76808008 100644 --- a/app/javascript/material-icons/400-24px/mood.svg +++ b/app/javascript/material-icons/400-24px/mood.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/report-fill.svg b/app/javascript/material-icons/400-24px/report-fill.svg new file mode 100644 index 00000000000000..50c638869d69cd --- /dev/null +++ b/app/javascript/material-icons/400-24px/report-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/report.svg b/app/javascript/material-icons/400-24px/report.svg index f281f0e1fa8f01..b08b5a1c987d5a 100644 --- a/app/javascript/material-icons/400-24px/report.svg +++ b/app/javascript/material-icons/400-24px/report.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/safety_check-fill.svg b/app/javascript/material-icons/400-24px/safety_check-fill.svg new file mode 100644 index 00000000000000..b38091a8ec4f2a --- /dev/null +++ b/app/javascript/material-icons/400-24px/safety_check-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/safety_check.svg b/app/javascript/material-icons/400-24px/safety_check.svg index f4eab46fb75b82..87bdba21fe3444 100644 --- a/app/javascript/material-icons/400-24px/safety_check.svg +++ b/app/javascript/material-icons/400-24px/safety_check.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/speed-fill.svg b/app/javascript/material-icons/400-24px/speed-fill.svg new file mode 100644 index 00000000000000..dca22ac521e3f0 --- /dev/null +++ b/app/javascript/material-icons/400-24px/speed-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/speed.svg b/app/javascript/material-icons/400-24px/speed.svg index ceb855c684f8c3..0837877f42f2f8 100644 --- a/app/javascript/material-icons/400-24px/speed.svg +++ b/app/javascript/material-icons/400-24px/speed.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/trending_up-fill.svg b/app/javascript/material-icons/400-24px/trending_up-fill.svg new file mode 100644 index 00000000000000..cd0e368964ec68 --- /dev/null +++ b/app/javascript/material-icons/400-24px/trending_up-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/trending_up.svg b/app/javascript/material-icons/400-24px/trending_up.svg index 06f9ba20639860..cd0e368964ec68 100644 --- a/app/javascript/material-icons/400-24px/trending_up.svg +++ b/app/javascript/material-icons/400-24px/trending_up.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/lib/tasks/icons.rake b/lib/tasks/icons.rake index 96e0a143150584..9a05cf349914d2 100644 --- a/lib/tasks/icons.rake +++ b/lib/tasks/icons.rake @@ -38,6 +38,20 @@ def find_used_icons end end + Rails.root.join('config', 'navigation.rb').open('r') do |file| + pattern = /material_symbol\('(?[^']*)'\)/ + file.each_line do |line| + match = pattern.match(line) + next if match.blank? + + # navigation.rb only uses 400x24 icons, per material_symbol() in + # app/helpers/application_helper.rb + icons_by_weight_and_size[400] ||= {} + icons_by_weight_and_size[400][24] ||= Set.new + icons_by_weight_and_size[400][24] << match['icon'] + end + end + icons_by_weight_and_size end From 754b03d8cb9d3eceede9f46741bec222d372082e Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 1 Oct 2024 14:52:13 +0200 Subject: [PATCH 21/51] Fix unneeded requests to blocked domains when receiving relayed signed activities from them (#31161) --- app/services/activitypub/process_collection_service.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/services/activitypub/process_collection_service.rb b/app/services/activitypub/process_collection_service.rb index 4f049a5ae940b0..cadc7d2d10e300 100644 --- a/app/services/activitypub/process_collection_service.rb +++ b/app/services/activitypub/process_collection_service.rb @@ -2,6 +2,7 @@ class ActivityPub::ProcessCollectionService < BaseService include JsonLdHelper + include DomainControlHelper def call(body, actor, **options) @account = actor @@ -69,6 +70,9 @@ def process_item(item) end def verify_account! + return unless @json['signature'].is_a?(Hash) + return if domain_not_allowed?(@json['signature']['creator']) + @options[:relayed_through_actor] = @account @account = ActivityPub::LinkedDataSignature.new(@json).verify_actor! @account = nil unless @account.is_a?(Account) From 784d1bfb295905aca44b696be80ad085fd396cf4 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 09:38:29 -0400 Subject: [PATCH 22/51] Fix broken border on applications list (#32147) --- app/views/settings/applications/index.html.haml | 4 +++- app/views/settings/applications/show.html.haml | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/views/settings/applications/index.html.haml b/app/views/settings/applications/index.html.haml index 80eaed5c3994f8..e3011947a64f59 100644 --- a/app/views/settings/applications/index.html.haml +++ b/app/views/settings/applications/index.html.haml @@ -18,7 +18,9 @@ - @applications.each do |application| %tr %td= link_to application.name, settings_application_path(application) - %th= application.scopes + %th + - application.scopes.to_s.split.each do |scope| + = tag.samp(scope, class: 'information-badge', title: t("doorkeeper.scopes.#{scope}")) %td = table_link_to 'close', t('doorkeeper.applications.index.delete'), settings_application_path(application), method: :delete, data: { confirm: t('doorkeeper.applications.confirmations.destroy') } diff --git a/app/views/settings/applications/show.html.haml b/app/views/settings/applications/show.html.haml index 099e0d96a8bcf5..bfde27daa96440 100644 --- a/app/views/settings/applications/show.html.haml +++ b/app/views/settings/applications/show.html.haml @@ -15,10 +15,11 @@ %td %code= @application.secret %tr - %th{ rowspan: 2 }= t('applications.your_token') + %th= t('applications.your_token') %td %code= current_user.token_for_app(@application).token %tr + %th %td= table_link_to 'refresh', t('applications.regenerate_token'), regenerate_settings_application_path(@application), method: :post %hr/ From 09cf617d7f8b633eb0f5ce84bf093e272943fede Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 09:41:25 -0400 Subject: [PATCH 23/51] Adjust spacing on setting sub-nav items when below mobile size (#32137) --- app/javascript/styles/mastodon/admin.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index da76fa11071d19..0712a0d3f4934e 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -226,6 +226,10 @@ $content-width: 840px; gap: 5px; white-space: nowrap; + @media screen and (max-width: $mobile-breakpoint) { + flex: 1 0 50%; + } + &:hover, &:focus, &:active { From 19d1392b332d63450f0c18dd756e89f7101ce388 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 09:50:20 -0400 Subject: [PATCH 24/51] Avoid repeated icon stack in settings sidebar (#32201) --- config/navigation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/navigation.rb b/config/navigation.rb index 09a7b55b5e1382..7ec7ecb7e72794 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -24,7 +24,7 @@ n.item :filters, safe_join([material_symbol('filter_alt'), t('filters.index.title')]), filters_path, highlights_on: %r{/filters}, if: -> { current_user.functional? && !self_destruct } n.item :statuses_cleanup, safe_join([material_symbol('history'), t('settings.statuses_cleanup')]), statuses_cleanup_path, if: -> { current_user.functional_or_moved? && !self_destruct } - n.item :security, safe_join([material_symbol('lock'), t('settings.account')]), edit_user_registration_path do |s| + n.item :security, safe_join([material_symbol('account_circle'), t('settings.account')]), edit_user_registration_path do |s| s.item :password, safe_join([material_symbol('lock'), t('settings.account_settings')]), edit_user_registration_path, highlights_on: %r{^/auth|/settings/delete|/settings/migration|/settings/aliases|/settings/login_activities|^/disputes} s.item :two_factor_authentication, safe_join([material_symbol('safety_check'), t('settings.two_factor_authentication')]), settings_two_factor_authentication_methods_path, highlights_on: %r{/settings/two_factor_authentication|/settings/otp_authentication|/settings/security_keys} s.item :authorized_apps, safe_join([material_symbol('list_alt'), t('settings.authorized_apps')]), oauth_authorized_applications_path, if: -> { !self_destruct } From b2ce9bb4c71c9cd19fc5371020ac93a38fbbce28 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Tue, 1 Oct 2024 22:54:28 +0900 Subject: [PATCH 25/51] Show timestamp when the user deletes their account on admin dashboard (#25640) Co-authored-by: Claire --- app/helpers/admin/dashboard_helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/helpers/admin/dashboard_helper.rb b/app/helpers/admin/dashboard_helper.rb index 4de779b79a80fb..f87fdad70832de 100644 --- a/app/helpers/admin/dashboard_helper.rb +++ b/app/helpers/admin/dashboard_helper.rb @@ -30,6 +30,8 @@ def relevant_account_timestamp(account) [account.user_current_sign_in_at, false] elsif account.user_pending? [account.user_created_at, true] + elsif account.suspended_at.present? && account.local? && account.user.nil? + [account.suspended_at, true] elsif account.last_status_at.present? [account.last_status_at, true] else From c91e06bcada30ad5b688a8f03ddb0095e3cb7c40 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 09:56:43 -0400 Subject: [PATCH 26/51] Fix `Rails/CreateTableWithTimestamps` cop (#30836) --- db/migrate/.rubocop.yml | 5 +++++ db/migrate/20170508230434_create_conversation_mutes.rb | 2 +- db/migrate/20170823162448_create_status_pins.rb | 2 +- db/migrate/20171116161857_create_list_accounts.rb | 2 +- db/migrate/20180929222014_create_account_conversations.rb | 2 +- db/migrate/20181007025445_create_pghero_space_stats.rb | 2 +- db/migrate/20190103124649_create_scheduled_statuses.rb | 2 +- db/migrate/20220824233535_create_status_trends.rb | 2 +- db/migrate/20221006061337_create_preview_card_trends.rb | 2 +- 9 files changed, 13 insertions(+), 8 deletions(-) diff --git a/db/migrate/.rubocop.yml b/db/migrate/.rubocop.yml index 4e23800dd14965..6f8b6cc60da42e 100644 --- a/db/migrate/.rubocop.yml +++ b/db/migrate/.rubocop.yml @@ -2,3 +2,8 @@ inherit_from: ../../.rubocop.yml Naming/VariableNumber: CheckSymbols: false + +# Enabled here as workaround for https://docs.rubocop.org/rubocop/configuration.html#path-relativity +Rails/CreateTableWithTimestamps: + Include: + - '*.rb' diff --git a/db/migrate/20170508230434_create_conversation_mutes.rb b/db/migrate/20170508230434_create_conversation_mutes.rb index 01122c45166a92..4beba9dfa32009 100644 --- a/db/migrate/20170508230434_create_conversation_mutes.rb +++ b/db/migrate/20170508230434_create_conversation_mutes.rb @@ -2,7 +2,7 @@ class CreateConversationMutes < ActiveRecord::Migration[5.0] def change - create_table :conversation_mutes do |t| + create_table :conversation_mutes do |t| # rubocop:disable Rails/CreateTableWithTimestamps t.integer :account_id, null: false t.bigint :conversation_id, null: false end diff --git a/db/migrate/20170823162448_create_status_pins.rb b/db/migrate/20170823162448_create_status_pins.rb index c8d3fab3a5128e..2cf3a85ca9de6b 100644 --- a/db/migrate/20170823162448_create_status_pins.rb +++ b/db/migrate/20170823162448_create_status_pins.rb @@ -2,7 +2,7 @@ class CreateStatusPins < ActiveRecord::Migration[5.1] def change - create_table :status_pins do |t| + create_table :status_pins do |t| # rubocop:disable Rails/CreateTableWithTimestamps t.belongs_to :account, foreign_key: { on_delete: :cascade }, null: false t.belongs_to :status, foreign_key: { on_delete: :cascade }, null: false end diff --git a/db/migrate/20171116161857_create_list_accounts.rb b/db/migrate/20171116161857_create_list_accounts.rb index ff9ab3faad89bc..b0371e4c884771 100644 --- a/db/migrate/20171116161857_create_list_accounts.rb +++ b/db/migrate/20171116161857_create_list_accounts.rb @@ -2,7 +2,7 @@ class CreateListAccounts < ActiveRecord::Migration[5.2] def change - create_table :list_accounts do |t| + create_table :list_accounts do |t| # rubocop:disable Rails/CreateTableWithTimestamps t.belongs_to :list, foreign_key: { on_delete: :cascade }, null: false t.belongs_to :account, foreign_key: { on_delete: :cascade }, null: false t.belongs_to :follow, foreign_key: { on_delete: :cascade }, null: false diff --git a/db/migrate/20180929222014_create_account_conversations.rb b/db/migrate/20180929222014_create_account_conversations.rb index 9386b86e7c9181..4e85e68d4771f5 100644 --- a/db/migrate/20180929222014_create_account_conversations.rb +++ b/db/migrate/20180929222014_create_account_conversations.rb @@ -2,7 +2,7 @@ class CreateAccountConversations < ActiveRecord::Migration[5.2] def change - create_table :account_conversations do |t| + create_table :account_conversations do |t| # rubocop:disable Rails/CreateTableWithTimestamps t.belongs_to :account, foreign_key: { on_delete: :cascade } t.belongs_to :conversation, foreign_key: { on_delete: :cascade } t.bigint :participant_account_ids, array: true, null: false, default: [] diff --git a/db/migrate/20181007025445_create_pghero_space_stats.rb b/db/migrate/20181007025445_create_pghero_space_stats.rb index ddaf4aef31c6f0..696b53d8d77518 100644 --- a/db/migrate/20181007025445_create_pghero_space_stats.rb +++ b/db/migrate/20181007025445_create_pghero_space_stats.rb @@ -2,7 +2,7 @@ class CreatePgheroSpaceStats < ActiveRecord::Migration[5.2] def change - create_table :pghero_space_stats do |t| + create_table :pghero_space_stats do |t| # rubocop:disable Rails/CreateTableWithTimestamps t.text :database t.text :schema t.text :relation diff --git a/db/migrate/20190103124649_create_scheduled_statuses.rb b/db/migrate/20190103124649_create_scheduled_statuses.rb index a66546187eed57..02b4916be86281 100644 --- a/db/migrate/20190103124649_create_scheduled_statuses.rb +++ b/db/migrate/20190103124649_create_scheduled_statuses.rb @@ -2,7 +2,7 @@ class CreateScheduledStatuses < ActiveRecord::Migration[5.2] def change - create_table :scheduled_statuses do |t| + create_table :scheduled_statuses do |t| # rubocop:disable Rails/CreateTableWithTimestamps t.belongs_to :account, foreign_key: { on_delete: :cascade } t.datetime :scheduled_at, index: true t.jsonb :params diff --git a/db/migrate/20220824233535_create_status_trends.rb b/db/migrate/20220824233535_create_status_trends.rb index 52dcbf8f38e9e5..e68e5b7c11aa5d 100644 --- a/db/migrate/20220824233535_create_status_trends.rb +++ b/db/migrate/20220824233535_create_status_trends.rb @@ -2,7 +2,7 @@ class CreateStatusTrends < ActiveRecord::Migration[6.1] def change - create_table :status_trends do |t| + create_table :status_trends do |t| # rubocop:disable Rails/CreateTableWithTimestamps t.references :status, null: false, foreign_key: { on_delete: :cascade }, index: { unique: true } t.references :account, null: false, foreign_key: { on_delete: :cascade } t.float :score, null: false, default: 0 diff --git a/db/migrate/20221006061337_create_preview_card_trends.rb b/db/migrate/20221006061337_create_preview_card_trends.rb index 934a06e24d1728..266f6440239751 100644 --- a/db/migrate/20221006061337_create_preview_card_trends.rb +++ b/db/migrate/20221006061337_create_preview_card_trends.rb @@ -2,7 +2,7 @@ class CreatePreviewCardTrends < ActiveRecord::Migration[6.1] def change - create_table :preview_card_trends do |t| + create_table :preview_card_trends do |t| # rubocop:disable Rails/CreateTableWithTimestamps t.references :preview_card, null: false, foreign_key: { on_delete: :cascade }, index: { unique: true } t.float :score, null: false, default: 0 t.integer :rank, null: false, default: 0 From b8fdffe8246f271b4e49c8599229bcd576aeb6cf Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Wed, 2 Oct 2024 17:08:02 +0900 Subject: [PATCH 27/51] Ignore error if mentioned account was not processable (#29215) Co-authored-by: Claire --- app/lib/activitypub/activity/create.rb | 10 ++++++ app/workers/mention_resolve_worker.rb | 37 +++++++++++++++++++ spec/lib/activitypub/activity/create_spec.rb | 37 +++++++++++++++++++ spec/workers/mention_resolve_worker_spec.rb | 38 ++++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 app/workers/mention_resolve_worker.rb create mode 100644 spec/workers/mention_resolve_worker_spec.rb diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 09a8caf1fcce27..928803cd645655 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -42,6 +42,7 @@ def audience_cc def process_status @tags = [] @mentions = [] + @unresolved_mentions = [] @silenced_account_ids = [] @params = {} @@ -55,6 +56,7 @@ def process_status end resolve_thread(@status) + resolve_unresolved_mentions(@status) fetch_replies(@status) distribute forward_for_reply @@ -197,6 +199,8 @@ def process_mention(tag) return if account.nil? @mentions << Mention.new(account: account, silent: false) + rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError + @unresolved_mentions << tag['href'] end def process_emoji(tag) @@ -301,6 +305,12 @@ def resolve_thread(status) ThreadResolveWorker.perform_async(status.id, in_reply_to_uri, { 'request_id' => @options[:request_id] }) end + def resolve_unresolved_mentions(status) + @unresolved_mentions.uniq.each do |uri| + MentionResolveWorker.perform_in(rand(30...600).seconds, status.id, uri, { 'request_id' => @options[:request_id] }) + end + end + def fetch_replies(status) collection = @object['replies'] return if collection.blank? diff --git a/app/workers/mention_resolve_worker.rb b/app/workers/mention_resolve_worker.rb new file mode 100644 index 00000000000000..72dcd9633f32fe --- /dev/null +++ b/app/workers/mention_resolve_worker.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class MentionResolveWorker + include Sidekiq::Worker + include ExponentialBackoff + include JsonLdHelper + + sidekiq_options queue: 'pull', retry: 7 + + def perform(status_id, uri, options = {}) + status = Status.find_by(id: status_id) + return if status.nil? + + account = account_from_uri(uri) + account = ActivityPub::FetchRemoteAccountService.new.call(uri, request_id: options[:request_id]) if account.nil? + + return if account.nil? + + status.mentions.create!(account: account, silent: false) + rescue ActiveRecord::RecordNotFound + # Do nothing + rescue Mastodon::UnexpectedResponseError => e + response = e.response + + if response_error_unsalvageable?(response) + # Give up + else + raise e + end + end + + private + + def account_from_uri(uri) + ActivityPub::TagManager.instance.uri_to_resource(uri, Account) + end +end diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index 0986ae17151e0f..bdc8fd9d519080 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -63,6 +63,24 @@ } end + let(:invalid_mention_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), 'post2'].join('/'), + type: 'Note', + to: [ + 'https://www.w3.org/ns/activitystreams#Public', + ActivityPub::TagManager.instance.uri_for(follower), + ], + content: '@bob lorem ipsum', + published: 1.hour.ago.utc.iso8601, + updated: 1.hour.ago.utc.iso8601, + tag: { + type: 'Mention', + href: 'http://notexisting.dontexistingtld/actor', + }, + } + end + def activity_for_object(json) { '@context': 'https://www.w3.org/ns/activitystreams', @@ -117,6 +135,25 @@ def activity_for_object(json) # Creates two notifications expect(Notification.count).to eq 2 end + + it 'ignores unprocessable mention', :aggregate_failures do + stub_request(:get, invalid_mention_json[:tag][:href]).to_raise(HTTP::ConnectionError) + # When receiving the post that contains an invalid mention… + described_class.new(activity_for_object(invalid_mention_json), sender, delivery: true).perform + + # NOTE: Refering explicitly to the workers is a bit awkward + DistributionWorker.drain + FeedInsertWorker.drain + + # …it creates a status + status = Status.find_by(uri: invalid_mention_json[:id]) + + # Check the process did not crash + expect(status.nil?).to be false + + # It has queued a mention resolve job + expect(MentionResolveWorker).to have_enqueued_sidekiq_job(status.id, invalid_mention_json[:tag][:href], anything) + end end describe '#perform' do diff --git a/spec/workers/mention_resolve_worker_spec.rb b/spec/workers/mention_resolve_worker_spec.rb new file mode 100644 index 00000000000000..5e23876b4ae4f8 --- /dev/null +++ b/spec/workers/mention_resolve_worker_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe MentionResolveWorker do + let(:status_id) { -42 } + let(:uri) { 'https://example.com/users/unknown' } + + describe '#perform' do + subject { described_class.new.perform(status_id, uri, {}) } + + context 'with a non-existent status' do + it 'returns nil' do + expect(subject).to be_nil + end + end + + context 'with a valid user' do + let(:status) { Fabricate(:status) } + let(:status_id) { status.id } + + let(:service_double) { instance_double(ActivityPub::FetchRemoteAccountService) } + + before do + allow(ActivityPub::FetchRemoteAccountService).to receive(:new).and_return(service_double) + + allow(service_double).to receive(:call).with(uri, anything) { Fabricate(:account, domain: 'example.com', uri: uri) } + end + + it 'resolves the account and adds a new mention', :aggregate_failures do + expect { subject } + .to change { status.reload.mentions }.from([]).to(a_collection_including(having_attributes(account: having_attributes(uri: uri), silent: false))) + + expect(service_double).to have_received(:call).once + end + end + end +end From cbf134937054dadb037350cb390b9612450c8062 Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Wed, 2 Oct 2024 11:23:44 +0200 Subject: [PATCH 28/51] Support /.well-known/host-meta.json (#32206) --- .../well_known/host_meta_controller.rb | 18 ++++++++- config/routes.rb | 2 +- spec/requests/well_known/host_meta_spec.rb | 40 ++++++++++++++----- spec/routing/well_known_routes_spec.rb | 9 ++++- 4 files changed, 55 insertions(+), 14 deletions(-) diff --git a/app/controllers/well_known/host_meta_controller.rb b/app/controllers/well_known/host_meta_controller.rb index 201da9fbc3b440..6dee587baf4fc8 100644 --- a/app/controllers/well_known/host_meta_controller.rb +++ b/app/controllers/well_known/host_meta_controller.rb @@ -7,7 +7,23 @@ class HostMetaController < ActionController::Base # rubocop:disable Rails/Applic def show @webfinger_template = "#{webfinger_url}?resource={uri}" expires_in 3.days, public: true - render content_type: 'application/xrd+xml', formats: [:xml] + + respond_to do |format| + format.any do + render content_type: 'application/xrd+xml', formats: [:xml] + end + + format.json do + render json: { + links: [ + { + rel: 'lrdd', + template: @webfinger_template, + }, + ], + } + end + end end end end diff --git a/config/routes.rb b/config/routes.rb index 890102955c67ac..83170fba0f3180 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -67,7 +67,7 @@ def redirect_with_vary(path) scope path: '.well-known' do scope module: :well_known do get 'oauth-authorization-server', to: 'oauth_metadata#show', as: :oauth_metadata, defaults: { format: 'json' } - get 'host-meta', to: 'host_meta#show', as: :host_meta, defaults: { format: 'xml' } + get 'host-meta', to: 'host_meta#show', as: :host_meta get 'nodeinfo', to: 'node_info#index', as: :nodeinfo, defaults: { format: 'json' } get 'webfinger', to: 'webfinger#show', as: :webfinger end diff --git a/spec/requests/well_known/host_meta_spec.rb b/spec/requests/well_known/host_meta_spec.rb index 09f17baa894d64..726911dda1964f 100644 --- a/spec/requests/well_known/host_meta_spec.rb +++ b/spec/requests/well_known/host_meta_spec.rb @@ -9,19 +9,39 @@ expect(response) .to have_http_status(200) .and have_attributes( - media_type: 'application/xrd+xml', - body: host_meta_xml_template + media_type: 'application/xrd+xml' + ) + + doc = Nokogiri::XML(response.parsed_body) + expect(doc.at_xpath('/xrd:XRD/xrd:Link[@rel="lrdd"]/@template', 'xrd' => 'http://docs.oasis-open.org/ns/xri/xrd-1.0').value) + .to eq 'https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}' + end + + it 'returns http success with valid JSON response with .json extension' do + get '/.well-known/host-meta.json' + + expect(response) + .to have_http_status(200) + .and have_attributes( + media_type: 'application/json' + ) + + expect(response.parsed_body) + .to include( + links: [ + 'rel' => 'lrdd', + 'template' => 'https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}', + ] ) end - private + it 'returns http success with valid JSON response with Accept header' do + get '/.well-known/host-meta', headers: { 'Accept' => 'application/json' } - def host_meta_xml_template - <<~XML - - - - - XML + expect(response) + .to have_http_status(200) + .and have_attributes( + media_type: 'application/json' + ) end end diff --git a/spec/routing/well_known_routes_spec.rb b/spec/routing/well_known_routes_spec.rb index 6578e939ae1e14..84081059bba75e 100644 --- a/spec/routing/well_known_routes_spec.rb +++ b/spec/routing/well_known_routes_spec.rb @@ -4,9 +4,14 @@ RSpec.describe 'Well Known routes' do describe 'the host-meta route' do - it 'routes to correct place with xml format' do + it 'routes to correct place' do expect(get('/.well-known/host-meta')) - .to route_to('well_known/host_meta#show', format: 'xml') + .to route_to('well_known/host_meta#show') + end + + it 'routes to correct place with json format' do + expect(get('/.well-known/host-meta.json')) + .to route_to('well_known/host_meta#show', format: 'json') end end From 243a85ec8dbaad7db7f71324699db3b06140c0ad Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 2 Oct 2024 05:43:04 -0400 Subject: [PATCH 29/51] Expand coverage for `Export` utility class (#32212) --- .../account_domain_block_fabricator.rb | 2 +- spec/models/export_spec.rb | 169 ++++++++++++++---- 2 files changed, 140 insertions(+), 31 deletions(-) diff --git a/spec/fabricators/account_domain_block_fabricator.rb b/spec/fabricators/account_domain_block_fabricator.rb index 83df509da2cc85..a211b7c6667031 100644 --- a/spec/fabricators/account_domain_block_fabricator.rb +++ b/spec/fabricators/account_domain_block_fabricator.rb @@ -2,5 +2,5 @@ Fabricator(:account_domain_block) do account { Fabricate.build(:account) } - domain 'example.com' + domain { sequence { |n| "host-#{n}.example" } } end diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb index 06bf07ed78a314..48e78830dd15dd 100644 --- a/spec/models/export_spec.rb +++ b/spec/models/export_spec.rb @@ -3,66 +3,175 @@ require 'rails_helper' RSpec.describe Export do + subject { described_class.new(account) } + let(:account) { Fabricate(:account) } let(:target_accounts) do - [{}, { username: 'one', domain: 'local.host' }].map(&method(:Fabricate).curry(2).call(:account)) + [ + Fabricate(:account), + Fabricate(:account, username: 'one', domain: 'local.host'), + ] end - describe 'to_csv' do - it 'returns a csv of the blocked accounts' do - target_accounts.each { |target_account| account.block!(target_account) } + describe '#to_bookmarks_csv' do + before { Fabricate.times(2, :bookmark, account: account) } - export = described_class.new(account).to_blocked_accounts_csv - results = export.strip.split + let(:export) { CSV.parse(subject.to_bookmarks_csv) } - expect(results.size).to eq 2 - expect(results.first).to eq 'one@local.host' + it 'returns a csv of bookmarks' do + expect(export) + .to contain_exactly( + include(/statuses/), + include(/statuses/) + ) end + end - it 'returns a csv of the muted accounts' do - target_accounts.each { |target_account| account.mute!(target_account) } + describe '#to_blocked_accounts_csv' do + before { target_accounts.each { |target_account| account.block!(target_account) } } + + let(:export) { CSV.parse(subject.to_blocked_accounts_csv) } + + it 'returns a csv of the blocked accounts' do + expect(export) + .to contain_exactly( + include('one@local.host'), + include(be_present) + ) + end + end - export = described_class.new(account).to_muted_accounts_csv - results = export.strip.split("\n") + describe '#to_muted_accounts_csv' do + before { target_accounts.each { |target_account| account.mute!(target_account) } } - expect(results.size).to eq 3 - expect(results.first).to eq 'Account address,Hide notifications' - expect(results.second).to eq 'one@local.host,true' + let(:export) { CSV.parse(subject.to_muted_accounts_csv) } + + it 'returns a csv of the muted accounts' do + expect(export) + .to contain_exactly( + contain_exactly('Account address', 'Hide notifications'), + include('one@local.host', 'true'), + include(be_present) + ) end + end + + describe '#to_following_accounts_csv' do + before { target_accounts.each { |target_account| account.follow!(target_account) } } + + let(:export) { CSV.parse(subject.to_following_accounts_csv) } it 'returns a csv of the following accounts' do - target_accounts.each { |target_account| account.follow!(target_account) } + expect(export) + .to contain_exactly( + contain_exactly('Account address', 'Show boosts', 'Notify on new posts', 'Languages'), + include('one@local.host', 'true', 'false', be_blank), + include(be_present) + ) + end + end + + describe '#to_lists_csv' do + before do + target_accounts.each do |target_account| + account.follow!(target_account) + Fabricate(:list, account: account).accounts << target_account + end + end - export = described_class.new(account).to_following_accounts_csv - results = export.strip.split("\n") + let(:export) { CSV.parse(subject.to_lists_csv) } - expect(results.size).to eq 3 - expect(results.first).to eq 'Account address,Show boosts,Notify on new posts,Languages' - expect(results.second).to eq 'one@local.host,true,false,' + it 'returns a csv of the lists' do + expect(export) + .to contain_exactly( + include('one@local.host'), + include(be_present) + ) end end - describe 'total_storage' do + describe '#to_blocked_domains_csv' do + before { Fabricate.times(2, :account_domain_block, account: account) } + + let(:export) { CSV.parse(subject.to_blocked_domains_csv) } + + it 'returns a csv of the blocked domains' do + expect(export) + .to contain_exactly( + include(/example/), + include(/example/) + ) + end + end + + describe '#total_storage' do it 'returns the total size of the media attachments' do media_attachment = Fabricate(:media_attachment, account: account) - expect(described_class.new(account).total_storage).to eq media_attachment.file_file_size || 0 + expect(subject.total_storage).to eq media_attachment.file_file_size || 0 + end + end + + describe '#total_statuses' do + before { Fabricate.times(2, :status, account: account) } + + it 'returns the total number of statuses' do + expect(subject.total_statuses).to eq(2) + end + end + + describe '#total_bookmarks' do + before { Fabricate.times(2, :bookmark, account: account) } + + it 'returns the total number of bookmarks' do + expect(subject.total_bookmarks).to eq(2) end end - describe 'total_follows' do + describe '#total_follows' do + before { target_accounts.each { |target_account| account.follow!(target_account) } } + it 'returns the total number of the followed accounts' do - target_accounts.each { |target_account| account.follow!(target_account) } - expect(described_class.new(account.reload).total_follows).to eq 2 + expect(subject.total_follows).to eq(2) + end + end + + describe '#total_lists' do + before { Fabricate.times(2, :list, account: account) } + + it 'returns the total number of lists' do + expect(subject.total_lists).to eq(2) end + end + + describe '#total_followers' do + before { target_accounts.each { |target_account| target_account.follow!(account) } } + + it 'returns the total number of the follower accounts' do + expect(subject.total_followers).to eq(2) + end + end + + describe '#total_blocks' do + before { target_accounts.each { |target_account| account.block!(target_account) } } it 'returns the total number of the blocked accounts' do - target_accounts.each { |target_account| account.block!(target_account) } - expect(described_class.new(account.reload).total_blocks).to eq 2 + expect(subject.total_blocks).to eq(2) end + end + + describe '#total_mutes' do + before { target_accounts.each { |target_account| account.mute!(target_account) } } it 'returns the total number of the muted accounts' do - target_accounts.each { |target_account| account.mute!(target_account) } - expect(described_class.new(account.reload).total_mutes).to eq 2 + expect(subject.total_mutes).to eq(2) + end + end + + describe '#total_domain_blocks' do + before { Fabricate.times(2, :account_domain_block, account: account) } + + it 'returns the total number of account domain blocks' do + expect(subject.total_domain_blocks).to eq(2) end end end From 931553844d851b7209039cd35629ae22818476cf Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 2 Oct 2024 12:03:04 +0200 Subject: [PATCH 30/51] Fix incorrect `'navigator'` check (#32219) --- app/javascript/mastodon/actions/markers.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/javascript/mastodon/actions/markers.ts b/app/javascript/mastodon/actions/markers.ts index 0b3280c2128dea..251546cb9a35b3 100644 --- a/app/javascript/mastodon/actions/markers.ts +++ b/app/javascript/mastodon/actions/markers.ts @@ -37,8 +37,7 @@ export const synchronouslySubmitMarkers = createAppAsyncThunk( }); return; - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - } else if ('navigator' && 'sendBeacon' in navigator) { + } else if ('sendBeacon' in navigator) { // Failing that, we can use sendBeacon, but we have to encode the data as // FormData for DoorKeeper to recognize the token. const formData = new FormData(); From f07707a9bb990de3e9be85adc28cdd620451b3c5 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 2 Oct 2024 07:11:52 -0400 Subject: [PATCH 31/51] Extract `WebPushRequest` from push notification worker and subscription (#32208) --- app/lib/web_push_request.rb | 72 +++++++++++++++++++ app/models/web/push_subscription.rb | 28 -------- app/workers/web/push_notification_worker.rb | 36 ++++++---- .../web/push_notification_worker_spec.rb | 45 ++++++++---- 4 files changed, 128 insertions(+), 53 deletions(-) create mode 100644 app/lib/web_push_request.rb diff --git a/app/lib/web_push_request.rb b/app/lib/web_push_request.rb new file mode 100644 index 00000000000000..a43e22480e12a2 --- /dev/null +++ b/app/lib/web_push_request.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +class WebPushRequest + SIGNATURE_ALGORITHM = 'p256ecdsa' + AUTH_HEADER = 'WebPush' + PAYLOAD_EXPIRATION = 24.hours + JWT_ALGORITHM = 'ES256' + JWT_TYPE = 'JWT' + + attr_reader :web_push_subscription + + delegate( + :endpoint, + :key_auth, + :key_p256dh, + to: :web_push_subscription + ) + + def initialize(web_push_subscription) + @web_push_subscription = web_push_subscription + end + + def audience + @audience ||= Addressable::URI.parse(endpoint).normalized_site + end + + def authorization_header + [AUTH_HEADER, encoded_json_web_token].join(' ') + end + + def crypto_key_header + [SIGNATURE_ALGORITHM, vapid_key.public_key_for_push_header].join('=') + end + + def encrypt(payload) + Webpush::Encryption.encrypt(payload, key_p256dh, key_auth) + end + + private + + def encoded_json_web_token + JWT.encode( + web_token_payload, + vapid_key.curve, + JWT_ALGORITHM, + typ: JWT_TYPE + ) + end + + def web_token_payload + { + aud: audience, + exp: PAYLOAD_EXPIRATION.from_now.to_i, + sub: payload_subject, + } + end + + def payload_subject + [:mailto, contact_email].join(':') + end + + def vapid_key + @vapid_key ||= Webpush::VapidKey.from_keys( + Rails.configuration.x.vapid_public_key, + Rails.configuration.x.vapid_private_key + ) + end + + def contact_email + @contact_email ||= ::Setting.site_contact_email + end +end diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb index ddfd08146e76ae..9d30881bf38238 100644 --- a/app/models/web/push_subscription.rb +++ b/app/models/web/push_subscription.rb @@ -29,26 +29,6 @@ class Web::PushSubscription < ApplicationRecord delegate :locale, to: :associated_user - def encrypt(payload) - Webpush::Encryption.encrypt(payload, key_p256dh, key_auth) - end - - def audience - @audience ||= Addressable::URI.parse(endpoint).normalized_site - end - - def crypto_key_header - p256ecdsa = vapid_key.public_key_for_push_header - - "p256ecdsa=#{p256ecdsa}" - end - - def authorization_header - jwt = JWT.encode({ aud: audience, exp: 24.hours.from_now.to_i, sub: "mailto:#{contact_email}" }, vapid_key.curve, 'ES256', typ: 'JWT') - - "WebPush #{jwt}" - end - def pushable?(notification) policy_allows_notification?(notification) && alert_enabled_for_notification_type?(notification) end @@ -92,14 +72,6 @@ def find_or_create_access_token ) end - def vapid_key - @vapid_key ||= Webpush::VapidKey.from_keys(Rails.configuration.x.vapid_public_key, Rails.configuration.x.vapid_private_key) - end - - def contact_email - @contact_email ||= ::Setting.site_contact_email - end - def alert_enabled_for_notification_type?(notification) truthy?(data&.dig('alerts', notification.type.to_s)) end diff --git a/app/workers/web/push_notification_worker.rb b/app/workers/web/push_notification_worker.rb index 7e9691aaba37cd..104503f130fc2d 100644 --- a/app/workers/web/push_notification_worker.rb +++ b/app/workers/web/push_notification_worker.rb @@ -16,10 +16,10 @@ def perform(subscription_id, notification_id) # in the meantime, so we have to double-check before proceeding return unless @notification.activity.present? && @subscription.pushable?(@notification) - payload = @subscription.encrypt(push_notification_json) + payload = web_push_request.encrypt(push_notification_json) - request_pool.with(@subscription.audience) do |http_client| - request = Request.new(:post, @subscription.endpoint, body: payload.fetch(:ciphertext), http_client: http_client) + request_pool.with(web_push_request.audience) do |http_client| + request = Request.new(:post, web_push_request.endpoint, body: payload.fetch(:ciphertext), http_client: http_client) request.add_headers( 'Content-Type' => 'application/octet-stream', @@ -27,8 +27,8 @@ def perform(subscription_id, notification_id) 'Urgency' => URGENCY, 'Content-Encoding' => 'aesgcm', 'Encryption' => "salt=#{Webpush.encode64(payload.fetch(:salt)).delete('=')}", - 'Crypto-Key' => "dh=#{Webpush.encode64(payload.fetch(:server_public_key)).delete('=')};#{@subscription.crypto_key_header}", - 'Authorization' => @subscription.authorization_header + 'Crypto-Key' => "dh=#{Webpush.encode64(payload.fetch(:server_public_key)).delete('=')};#{web_push_request.crypto_key_header}", + 'Authorization' => web_push_request.authorization_header ) request.perform do |response| @@ -50,17 +50,27 @@ def perform(subscription_id, notification_id) private + def web_push_request + @web_push_request || WebPushRequest.new(@subscription) + end + def push_notification_json - json = I18n.with_locale(@subscription.locale.presence || I18n.default_locale) do - ActiveModelSerializers::SerializableResource.new( - @notification, - serializer: Web::NotificationSerializer, - scope: @subscription, - scope_name: :current_push_subscription - ).as_json + Oj.dump(serialized_notification_in_subscription_locale.as_json) + end + + def serialized_notification_in_subscription_locale + I18n.with_locale(@subscription.locale.presence || I18n.default_locale) do + serialized_notification end + end - Oj.dump(json) + def serialized_notification + ActiveModelSerializers::SerializableResource.new( + @notification, + serializer: Web::NotificationSerializer, + scope: @subscription, + scope_name: :current_push_subscription + ) end def request_pool diff --git a/spec/workers/web/push_notification_worker_spec.rb b/spec/workers/web/push_notification_worker_spec.rb index ced21d5bf799b4..7f836d99e431f9 100644 --- a/spec/workers/web/push_notification_worker_spec.rb +++ b/spec/workers/web/push_notification_worker_spec.rb @@ -22,27 +22,48 @@ let(:payload) { { ciphertext: ciphertext, salt: salt, server_public_key: server_public_key, shared_secret: shared_secret } } describe 'perform' do + around do |example| + original_private = Rails.configuration.x.vapid_private_key + original_public = Rails.configuration.x.vapid_public_key + Rails.configuration.x.vapid_private_key = vapid_private_key + Rails.configuration.x.vapid_public_key = vapid_public_key + example.run + Rails.configuration.x.vapid_private_key = original_private + Rails.configuration.x.vapid_public_key = original_public + end + before do - allow(subscription).to receive_messages(contact_email: contact_email, vapid_key: vapid_key) - allow(Web::PushSubscription).to receive(:find).with(subscription.id).and_return(subscription) + Setting.site_contact_email = contact_email + allow(Webpush::Encryption).to receive(:encrypt).and_return(payload) allow(JWT).to receive(:encode).and_return('jwt.encoded.payload') stub_request(:post, endpoint).to_return(status: 201, body: '') + end + it 'calls the relevant service with the correct headers' do subject.perform(subscription.id, notification.id) + + expect(web_push_endpoint_request) + .to have_been_made end - it 'calls the relevant service with the correct headers' do - expect(a_request(:post, endpoint).with(headers: { - 'Content-Encoding' => 'aesgcm', - 'Content-Type' => 'application/octet-stream', - 'Crypto-Key' => "dh=BAgtUks5d90kFmxGevk9tH7GEmvz9DB0qcEMUsOBgKwMf-TMjsKIIG6LQvGcFAf6jcmAod15VVwmYwGIIxE4VWE;p256ecdsa=#{vapid_public_key.delete('=')}", - 'Encryption' => 'salt=WJeVM-RY-F9351SVxTFx_g', - 'Ttl' => '172800', - 'Urgency' => 'normal', - 'Authorization' => 'WebPush jwt.encoded.payload', - }, body: "+\xB8\xDBT}\u0013\xB6\xDD.\xF9\xB0\xA7\xC8Ҁ\xFD\x99#\xF7\xAC\x83\xA4\xDB,\u001F\xB5\xB9w\x85>\xF7\xADr")).to have_been_made + def web_push_endpoint_request + a_request( + :post, + endpoint + ).with( + headers: { + 'Content-Encoding' => 'aesgcm', + 'Content-Type' => 'application/octet-stream', + 'Crypto-Key' => "dh=BAgtUks5d90kFmxGevk9tH7GEmvz9DB0qcEMUsOBgKwMf-TMjsKIIG6LQvGcFAf6jcmAod15VVwmYwGIIxE4VWE;p256ecdsa=#{vapid_public_key.delete('=')}", + 'Encryption' => 'salt=WJeVM-RY-F9351SVxTFx_g', + 'Ttl' => '172800', + 'Urgency' => 'normal', + 'Authorization' => 'WebPush jwt.encoded.payload', + }, + body: "+\xB8\xDBT}\u0013\xB6\xDD.\xF9\xB0\xA7\xC8Ҁ\xFD\x99#\xF7\xAC\x83\xA4\xDB,\u001F\xB5\xB9w\x85>\xF7\xADr" + ) end end end From 74291dfb77ad4536fbe3d287b9c7051d7329ecef Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 2 Oct 2024 08:26:16 -0400 Subject: [PATCH 32/51] Remove unneeded `reorder(nil)` conditions (#32200) --- app/lib/vacuum/imports_vacuum.rb | 4 ++-- app/models/account_filter.rb | 2 +- app/models/admin/tag_filter.rb | 2 +- app/services/purge_domain_service.rb | 4 ++-- app/workers/filtered_notification_cleanup_worker.rb | 2 +- app/workers/scheduler/user_cleanup_scheduler.rb | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/lib/vacuum/imports_vacuum.rb b/app/lib/vacuum/imports_vacuum.rb index 700bd81847f4ac..b67865194f39a9 100644 --- a/app/lib/vacuum/imports_vacuum.rb +++ b/app/lib/vacuum/imports_vacuum.rb @@ -9,10 +9,10 @@ def perform private def clean_unconfirmed_imports! - BulkImport.state_unconfirmed.where(created_at: ..10.minutes.ago).reorder(nil).in_batches.delete_all + BulkImport.state_unconfirmed.where(created_at: ..10.minutes.ago).in_batches.delete_all end def clean_old_imports! - BulkImport.where(created_at: ..1.week.ago).reorder(nil).in_batches.delete_all + BulkImport.where(created_at: ..1.week.ago).in_batches.delete_all end end diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb index 42b1c49538b0a5..e2f359a8c32ef6 100644 --- a/app/models/account_filter.rb +++ b/app/models/account_filter.rb @@ -21,7 +21,7 @@ def initialize(params) end def results - scope = Account.includes(:account_stat, user: [:ips, :invite_request]).without_instance_actor.reorder(nil) + scope = Account.includes(:account_stat, user: [:ips, :invite_request]).without_instance_actor relevant_params.each do |key, value| next if key.to_s == 'page' diff --git a/app/models/admin/tag_filter.rb b/app/models/admin/tag_filter.rb index 6149c52175884b..5e75757b2374a7 100644 --- a/app/models/admin/tag_filter.rb +++ b/app/models/admin/tag_filter.rb @@ -14,7 +14,7 @@ def initialize(params) end def results - scope = Tag.reorder(nil) + scope = Tag.all params.each do |key, value| next if key == :page diff --git a/app/services/purge_domain_service.rb b/app/services/purge_domain_service.rb index ca0f0d441fe5bb..feab8aa1dd5da2 100644 --- a/app/services/purge_domain_service.rb +++ b/app/services/purge_domain_service.rb @@ -16,12 +16,12 @@ def purge_relationship_severance_events! end def purge_accounts! - Account.remote.where(domain: @domain).reorder(nil).find_each do |account| + Account.remote.where(domain: @domain).find_each do |account| DeleteAccountService.new.call(account, reserve_username: false, skip_side_effects: true) end end def purge_emojis! - CustomEmoji.remote.where(domain: @domain).reorder(nil).find_each(&:destroy) + CustomEmoji.remote.where(domain: @domain).find_each(&:destroy) end end diff --git a/app/workers/filtered_notification_cleanup_worker.rb b/app/workers/filtered_notification_cleanup_worker.rb index 2b955da3c0a1ac..87ff6a9eb5e928 100644 --- a/app/workers/filtered_notification_cleanup_worker.rb +++ b/app/workers/filtered_notification_cleanup_worker.rb @@ -4,6 +4,6 @@ class FilteredNotificationCleanupWorker include Sidekiq::Worker def perform(account_id, from_account_id) - Notification.where(account_id: account_id, from_account_id: from_account_id, filtered: true).reorder(nil).in_batches(order: :desc).delete_all + Notification.where(account_id: account_id, from_account_id: from_account_id, filtered: true).in_batches(order: :desc).delete_all end end diff --git a/app/workers/scheduler/user_cleanup_scheduler.rb b/app/workers/scheduler/user_cleanup_scheduler.rb index 9f58d9225b3a14..f7551283320dce 100644 --- a/app/workers/scheduler/user_cleanup_scheduler.rb +++ b/app/workers/scheduler/user_cleanup_scheduler.rb @@ -16,7 +16,7 @@ def perform private def clean_unconfirmed_accounts! - User.unconfirmed.where(confirmation_sent_at: ..UNCONFIRMED_ACCOUNTS_MAX_AGE_DAYS.days.ago).reorder(nil).find_in_batches do |batch| + User.unconfirmed.where(confirmation_sent_at: ..UNCONFIRMED_ACCOUNTS_MAX_AGE_DAYS.days.ago).find_in_batches do |batch| # We have to do it separately because of missing database constraints AccountModerationNote.where(target_account_id: batch.map(&:account_id)).delete_all Account.where(id: batch.map(&:account_id)).delete_all From 7de8d5ffca8550d263b5a925b8a0c02f163a86ce Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 2 Oct 2024 09:24:40 -0400 Subject: [PATCH 33/51] Add `relevant_params` to ReportFilter (matches account filter) (#32136) --- app/models/report_filter.rb | 14 +++++++++++++- spec/models/report_filter_spec.rb | 13 +++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/models/report_filter.rb b/app/models/report_filter.rb index fd0e23cb81705d..9d2b0fb37420f5 100644 --- a/app/models/report_filter.rb +++ b/app/models/report_filter.rb @@ -18,13 +18,25 @@ def initialize(params) def results scope = Report.unresolved - params.each do |key, value| + relevant_params.each do |key, value| scope = scope.merge scope_for(key, value) end scope end + private + + def relevant_params + params.tap do |args| + args.delete(:target_origin) if origin_is_remote_and_domain_present? + end + end + + def origin_is_remote_and_domain_present? + params[:target_origin] == 'remote' && params[:by_target_domain].present? + end + def scope_for(key, value) case key.to_sym when :by_target_domain diff --git a/spec/models/report_filter_spec.rb b/spec/models/report_filter_spec.rb index 8668eb3d1085e8..51933e475adb70 100644 --- a/spec/models/report_filter_spec.rb +++ b/spec/models/report_filter_spec.rb @@ -30,4 +30,17 @@ expect(Report).to have_received(:resolved) end end + + context 'when given remote target_origin and also by_target_domain' do + let!(:matching_report) { Fabricate :report, target_account: Fabricate(:account, domain: 'match.example') } + let!(:non_matching_report) { Fabricate :report, target_account: Fabricate(:account, domain: 'other.example') } + + it 'preserves the domain value' do + filter = described_class.new(by_target_domain: 'match.example', target_origin: 'remote') + + expect(filter.results) + .to include(matching_report) + .and not_include(non_matching_report) + end + end end From ceba0f082e46ee53eb84dc2b02ebb59279c569cb Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 2 Oct 2024 09:26:46 -0400 Subject: [PATCH 34/51] Provide `use_path` to qr generator for svg data size reduction (#32127) --- .../two_factor_authentication/confirmations/new.html.haml | 2 +- .../two_factor_authentication/confirmations_controller_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/settings/two_factor_authentication/confirmations/new.html.haml b/app/views/settings/two_factor_authentication/confirmations/new.html.haml index 0b8278a104eaea..a35479b84e6850 100644 --- a/app/views/settings/two_factor_authentication/confirmations/new.html.haml +++ b/app/views/settings/two_factor_authentication/confirmations/new.html.haml @@ -5,7 +5,7 @@ %p.hint= t('otp_authentication.instructions_html') .qr-wrapper - .qr-code!= @qrcode.as_svg(padding: 0, module_size: 4) + .qr-code!= @qrcode.as_svg(padding: 0, module_size: 4, use_path: true) .qr-alternative %p.hint= t('otp_authentication.manual_instructions') diff --git a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb index 34eaacdf498f96..224310b7ef3043 100644 --- a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb +++ b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb @@ -18,7 +18,7 @@ def qr_code_markup RQRCode::QRCode.new( 'otpauth://totp/cb6e6126.ngrok.io:local-part%40domain?secret=thisisasecretforthespecofnewview&issuer=cb6e6126.ngrok.io' - ).as_svg(padding: 0, module_size: 4) + ).as_svg(padding: 0, module_size: 4, use_path: true) end end From 4a2d3929c5987fafde31d7ddd0a0d4842ae40eee Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 2 Oct 2024 15:28:36 +0200 Subject: [PATCH 35/51] Fix media uploads in composer appearing over search results in advanced interface (#32217) --- app/javascript/styles/mastodon/components.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 14de6e6818bdd3..5f410ead931066 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3611,6 +3611,7 @@ $ui-header-logo-wordmark-width: 99px; overflow-y: auto; width: 100%; height: 100%; + z-index: 0; } .drawer__inner__mastodon { From 404f467fcf6db5c4510bc6dd788858f2440306ff Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 2 Oct 2024 15:29:23 +0200 Subject: [PATCH 36/51] Fix editing description of media uploads with custom thumbnails (#32221) --- app/javascript/mastodon/reducers/compose.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 9f66c09631d770..4e16c5b351aeb0 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -402,7 +402,7 @@ export default function compose(state = initialState, action) { .set('isUploadingThumbnail', false) .update('media_attachments', list => list.map(item => { if (item.get('id') === action.media.id) { - return fromJS(action.media); + return fromJS(action.media).set('unattached', item.get('unattached')); } return item; From aa46348c03579147114e894954ee2e29a62c8e02 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 2 Oct 2024 09:56:26 -0400 Subject: [PATCH 37/51] Enable hostname config for all system specs (#32109) --- spec/rails_helper.rb | 5 +++++ spec/system/invites_spec.rb | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index ee03b49bc6aa16..84cee0974f1a42 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -161,6 +161,11 @@ def sign_in(resource, _deprecated = nil, scope: nil) host! Rails.configuration.x.local_domain end + config.before :each, type: :system do + # Align with capybara config so that rails helpers called from rspec use matching host + host! 'localhost:3000' + end + config.after do Rails.cache.clear redis.del(redis.keys) diff --git a/spec/system/invites_spec.rb b/spec/system/invites_spec.rb index c57de871cc6e1a..fc60ce5913bc03 100644 --- a/spec/system/invites_spec.rb +++ b/spec/system/invites_spec.rb @@ -7,10 +7,7 @@ let(:user) { Fabricate :user } - before do - host! 'localhost:3000' # TODO: Move into before for all system specs? - sign_in user - end + before { sign_in user } describe 'Viewing invites' do it 'Lists existing user invites' do From 5c72b46a4e3edc070b465f494fdf7a6c3679df7c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 2 Oct 2024 10:45:12 -0400 Subject: [PATCH 38/51] Clean up labels on development application form (#32116) --- app/helpers/application_helper.rb | 13 +++++-------- app/models/session_activation.rb | 4 +++- app/views/settings/applications/_form.html.haml | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de00f76d3623d5..a40580fbaec1ff 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,12 +1,6 @@ # frozen_string_literal: true module ApplicationHelper - DANGEROUS_SCOPES = %w( - read - write - follow - ).freeze - RTL_LOCALES = %i( ar ckb @@ -95,8 +89,11 @@ def title Rails.env.production? ? site_title : "#{site_title} (Dev)" end - def class_for_scope(scope) - 'scope-danger' if DANGEROUS_SCOPES.include?(scope.to_s) + def label_for_scope(scope) + safe_join [ + tag.samp(scope, class: { 'scope-danger' => SessionActivation::DEFAULT_SCOPES.include?(scope.to_s) }), + tag.span(t("doorkeeper.scopes.#{scope}"), class: :hint), + ] end def can?(action, record) diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb index d0a77daf0a666c..8b8e533d30c980 100644 --- a/app/models/session_activation.rb +++ b/app/models/session_activation.rb @@ -28,6 +28,8 @@ class SessionActivation < ApplicationRecord before_create :assign_access_token + DEFAULT_SCOPES = %w(read write follow).freeze + class << self def active?(id) id && exists?(session_id: id) @@ -64,7 +66,7 @@ def access_token_attributes { application_id: Doorkeeper::Application.find_by(superapp: true)&.id, resource_owner_id: user_id, - scopes: 'read write follow', + scopes: DEFAULT_SCOPES.join(' '), expires_in: Doorkeeper.configuration.access_token_expires_in, use_refresh_token: Doorkeeper.configuration.refresh_token_enabled?, } diff --git a/app/views/settings/applications/_form.html.haml b/app/views/settings/applications/_form.html.haml index 66ea8bc12bf00f..85fdefa0fcdf2e 100644 --- a/app/views/settings/applications/_form.html.haml +++ b/app/views/settings/applications/_form.html.haml @@ -18,7 +18,7 @@ .field-group .input.with_block_label - %label= t('activerecord.attributes.doorkeeper/application.scopes') + = form.label t('activerecord.attributes.doorkeeper/application.scopes'), required: true %span.hint= t('simple_form.hints.defaults.scopes') - Doorkeeper.configuration.scopes.group_by { |s| s.split(':').first }.each_value do |value| @@ -29,7 +29,7 @@ hint: false, include_blank: false, item_wrapper_tag: 'li', - label_method: ->(scope) { safe_join([content_tag(:samp, scope, class: class_for_scope(scope)), content_tag(:span, t("doorkeeper.scopes.#{scope}"), class: 'hint')]) }, + label_method: ->(scope) { label_for_scope(scope) }, label: false, required: false, selected: form.object.scopes.all, From d82ffdccbb6ec10d6cc444824c40d1b142dc0adf Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 2 Oct 2024 10:45:54 -0400 Subject: [PATCH 39/51] Add `copyable_input` helper method to wrap shared options (#32119) --- app/helpers/application_helper.rb | 4 ++++ app/views/admin/invites/_invite.html.haml | 2 +- app/views/invites/_invite.html.haml | 2 +- app/views/oauth/authorizations/show.html.haml | 2 +- app/views/settings/verifications/show.html.haml | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index a40580fbaec1ff..8f9a433d82d226 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -240,6 +240,10 @@ def mascot_url full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg')) end + def copyable_input(options = {}) + tag.input(type: :text, maxlength: 999, spellcheck: false, readonly: true, **options) + end + private def storage_host_var diff --git a/app/views/admin/invites/_invite.html.haml b/app/views/admin/invites/_invite.html.haml index 8bd5f10feefdc6..53eac1d0cddf89 100644 --- a/app/views/admin/invites/_invite.html.haml +++ b/app/views/admin/invites/_invite.html.haml @@ -2,7 +2,7 @@ %td .input-copy .input-copy__wrapper - %input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: public_invite_url(invite_code: invite.code) } + = copyable_input value: public_invite_url(invite_code: invite.code) %button{ type: :button }= t('generic.copy') %td diff --git a/app/views/invites/_invite.html.haml b/app/views/invites/_invite.html.haml index 892fdc5a0e38af..7c94062de450bb 100644 --- a/app/views/invites/_invite.html.haml +++ b/app/views/invites/_invite.html.haml @@ -2,7 +2,7 @@ %td .input-copy .input-copy__wrapper - %input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: public_invite_url(invite_code: invite.code) } + = copyable_input value: public_invite_url(invite_code: invite.code) %button{ type: :button }= t('generic.copy') - if invite.valid_for_use? diff --git a/app/views/oauth/authorizations/show.html.haml b/app/views/oauth/authorizations/show.html.haml index a5122a87fc39d7..bdff3363682c07 100644 --- a/app/views/oauth/authorizations/show.html.haml +++ b/app/views/oauth/authorizations/show.html.haml @@ -3,5 +3,5 @@ %p= t('doorkeeper.authorizations.show.title') .input-copy .input-copy__wrapper - %input.oauth-code{ type: 'text', spellcheck: 'false', readonly: true, value: params[:code] } + = copyable_input value: params[:code], class: 'oauth-code' %button{ type: :button }= t('generic.copy') diff --git a/app/views/settings/verifications/show.html.haml b/app/views/settings/verifications/show.html.haml index 00491866c6592c..560807f27ca7fb 100644 --- a/app/views/settings/verifications/show.html.haml +++ b/app/views/settings/verifications/show.html.haml @@ -16,7 +16,7 @@ .input-copy.lead .input-copy__wrapper - %input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: link_to('Mastodon', ActivityPub::TagManager.instance.url_for(@account), rel: 'me').to_str } + = copyable_input value: link_to('Mastodon', ActivityPub::TagManager.instance.url_for(@account), rel: :me) %button{ type: :button }= t('generic.copy') %p.lead= t('verification.extra_instructions_html') From 2e8b752c556c72231c7a008d2fab837e508f5101 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 2 Oct 2024 10:47:00 -0400 Subject: [PATCH 40/51] Move admin action log type list generation to helper (#32178) --- app/helpers/admin/action_logs_helper.rb | 7 +++++++ app/views/admin/action_logs/index.html.haml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index e8d56341262cc5..51e28d8b4e9234 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -35,4 +35,11 @@ def log_target(log) end end end + + def sorted_action_log_types + Admin::ActionLogFilter::ACTION_TYPE_MAP + .keys + .map { |key| [I18n.t("admin.action_logs.action_types.#{key}"), key] } + .sort_by(&:first) + end end diff --git a/app/views/admin/action_logs/index.html.haml b/app/views/admin/action_logs/index.html.haml index c02c8f0ad41fae..a5d41882943cf2 100644 --- a/app/views/admin/action_logs/index.html.haml +++ b/app/views/admin/action_logs/index.html.haml @@ -16,7 +16,7 @@ %strong= t('admin.action_logs.filter_by_action') .input.select.optional = form.select :action_type, - options_for_select(Admin::ActionLogFilter::ACTION_TYPE_MAP.keys.map { |key| [I18n.t("admin.action_logs.action_types.#{key}"), key] }, params[:action_type]), + options_for_select(sorted_action_log_types, params[:action_type]), prompt: I18n.t('admin.accounts.moderation.all') - if @action_logs.empty? From 55b5364534a97c66b8612e6611ef0bba19b349d2 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 2 Oct 2024 17:51:05 +0200 Subject: [PATCH 41/51] Hide badges in media gallery when media are hidden (#32224) --- app/javascript/mastodon/components/media_gallery.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/components/media_gallery.jsx b/app/javascript/mastodon/components/media_gallery.jsx index 84cb4e04dcb5db..1380d244ad9a92 100644 --- a/app/javascript/mastodon/components/media_gallery.jsx +++ b/app/javascript/mastodon/components/media_gallery.jsx @@ -196,7 +196,7 @@ class Item extends PureComponent { {visible && thumbnail} - {badges && ( + {visible && badges && (
{badges}
From 81cd4892089f1d6d1620f5f80e917828454d2554 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 4 Oct 2024 10:50:36 +0200 Subject: [PATCH 42/51] Fix Content-Security-Policy when using sso-redirect (#32241) --- app/controllers/concerns/web_app_controller_concern.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/concerns/web_app_controller_concern.rb b/app/controllers/concerns/web_app_controller_concern.rb index ebbdba59af0293..9485ecda4941cc 100644 --- a/app/controllers/concerns/web_app_controller_concern.rb +++ b/app/controllers/concerns/web_app_controller_concern.rb @@ -13,7 +13,7 @@ module WebAppControllerConcern policy = ContentSecurityPolicy.new if policy.sso_host.present? - p.form_action policy.sso_host + p.form_action policy.sso_host, -> { "https://#{request.host}/auth/auth/" } else p.form_action :none end From 6d5aa58f88afbece2810c1321c3879d250ed11dc Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 4 Oct 2024 14:23:30 +0200 Subject: [PATCH 43/51] Fix unsupported grouped notifications from streaming causing duplicate IDs (#32243) --- .../mastodon/reducers/notification_groups.ts | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/app/javascript/mastodon/reducers/notification_groups.ts b/app/javascript/mastodon/reducers/notification_groups.ts index 375e64387633da..8b033f0fc78b7c 100644 --- a/app/javascript/mastodon/reducers/notification_groups.ts +++ b/app/javascript/mastodon/reducers/notification_groups.ts @@ -206,50 +206,53 @@ function processNewNotification( groups: NotificationGroupsState['groups'], notification: ApiNotificationJSON, ) { - if (shouldGroupNotificationType(notification.type)) { - const existingGroupIndex = groups.findIndex( - (group) => - group.type !== 'gap' && group.group_key === notification.group_key, - ); + if (!shouldGroupNotificationType(notification.type)) { + notification = { + ...notification, + group_key: `ungrouped-${notification.id}`, + }; + } - // In any case, we are going to add a group at the top - // If there is currently a gap at the top, now is the time to update it - if (groups.length > 0 && groups[0]?.type === 'gap') { - groups[0].maxId = notification.id; - } + const existingGroupIndex = groups.findIndex( + (group) => + group.type !== 'gap' && group.group_key === notification.group_key, + ); - if (existingGroupIndex > -1) { - const existingGroup = groups[existingGroupIndex]; + // In any case, we are going to add a group at the top + // If there is currently a gap at the top, now is the time to update it + if (groups.length > 0 && groups[0]?.type === 'gap') { + groups[0].maxId = notification.id; + } - if ( - existingGroup && - existingGroup.type !== 'gap' && - !existingGroup.sampleAccountIds.includes(notification.account.id) // This can happen for example if you like, then unlike, then like again the same post - ) { - // Update the existing group - if ( - existingGroup.sampleAccountIds.unshift(notification.account.id) > - NOTIFICATIONS_GROUP_MAX_AVATARS - ) - existingGroup.sampleAccountIds.pop(); + if (existingGroupIndex > -1) { + const existingGroup = groups[existingGroupIndex]; - existingGroup.most_recent_notification_id = notification.id; - existingGroup.page_max_id = notification.id; - existingGroup.latest_page_notification_at = notification.created_at; - existingGroup.notifications_count += 1; + if ( + existingGroup && + existingGroup.type !== 'gap' && + !existingGroup.sampleAccountIds.includes(notification.account.id) // This can happen for example if you like, then unlike, then like again the same post + ) { + // Update the existing group + if ( + existingGroup.sampleAccountIds.unshift(notification.account.id) > + NOTIFICATIONS_GROUP_MAX_AVATARS + ) + existingGroup.sampleAccountIds.pop(); - groups.splice(existingGroupIndex, 1); - mergeGapsAround(groups, existingGroupIndex); + existingGroup.most_recent_notification_id = notification.id; + existingGroup.page_max_id = notification.id; + existingGroup.latest_page_notification_at = notification.created_at; + existingGroup.notifications_count += 1; - groups.unshift(existingGroup); + groups.splice(existingGroupIndex, 1); + mergeGapsAround(groups, existingGroupIndex); - return; - } + groups.unshift(existingGroup); } + } else { + // We have not found an existing group, create a new one + groups.unshift(createNotificationGroupFromNotificationJSON(notification)); } - - // We have not found an existing group, create a new one - groups.unshift(createNotificationGroupFromNotificationJSON(notification)); } function trimNotifications(state: NotificationGroupsState) { From 1b6bd585abe6a528c4784e527ad27389a59c22bf Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Tue, 1 Oct 2024 10:22:14 +0200 Subject: [PATCH 44/51] [Glitch] Fix follow notifications from streaming being grouped Port 8ac00533ff1354a121b66c5148e9cce70e4e73e6 to glitch-soc Signed-off-by: Claire --- .../glitch/actions/notification_groups.ts | 4 ++ .../glitch/reducers/notification_groups.ts | 67 ++++++++++--------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/app/javascript/flavours/glitch/actions/notification_groups.ts b/app/javascript/flavours/glitch/actions/notification_groups.ts index eebf6ce7a2b6b3..e39c9d6846b3da 100644 --- a/app/javascript/flavours/glitch/actions/notification_groups.ts +++ b/app/javascript/flavours/glitch/actions/notification_groups.ts @@ -70,6 +70,10 @@ function dispatchAssociatedRecords( const supportedGroupedNotificationTypes = ['favourite', 'reblog']; +export function shouldGroupNotificationType(type: string) { + return supportedGroupedNotificationTypes.includes(type); +} + export const fetchNotifications = createDataLoadingThunk( 'notificationGroups/fetch', async (_params, { getState }) => diff --git a/app/javascript/flavours/glitch/reducers/notification_groups.ts b/app/javascript/flavours/glitch/reducers/notification_groups.ts index 30f7b58737124c..a2ba9a4f1793c3 100644 --- a/app/javascript/flavours/glitch/reducers/notification_groups.ts +++ b/app/javascript/flavours/glitch/reducers/notification_groups.ts @@ -21,6 +21,7 @@ import { unmountNotifications, refreshStaleNotificationGroups, pollRecentNotifications, + shouldGroupNotificationType, } from 'flavours/glitch/actions/notification_groups'; import { disconnectTimeline, @@ -205,46 +206,50 @@ function processNewNotification( groups: NotificationGroupsState['groups'], notification: ApiNotificationJSON, ) { - const existingGroupIndex = groups.findIndex( - (group) => - group.type !== 'gap' && group.group_key === notification.group_key, - ); + if (shouldGroupNotificationType(notification.type)) { + const existingGroupIndex = groups.findIndex( + (group) => + group.type !== 'gap' && group.group_key === notification.group_key, + ); - // In any case, we are going to add a group at the top - // If there is currently a gap at the top, now is the time to update it - if (groups.length > 0 && groups[0]?.type === 'gap') { - groups[0].maxId = notification.id; - } + // In any case, we are going to add a group at the top + // If there is currently a gap at the top, now is the time to update it + if (groups.length > 0 && groups[0]?.type === 'gap') { + groups[0].maxId = notification.id; + } - if (existingGroupIndex > -1) { - const existingGroup = groups[existingGroupIndex]; + if (existingGroupIndex > -1) { + const existingGroup = groups[existingGroupIndex]; - if ( - existingGroup && - existingGroup.type !== 'gap' && - !existingGroup.sampleAccountIds.includes(notification.account.id) // This can happen for example if you like, then unlike, then like again the same post - ) { - // Update the existing group if ( - existingGroup.sampleAccountIds.unshift(notification.account.id) > - NOTIFICATIONS_GROUP_MAX_AVATARS - ) - existingGroup.sampleAccountIds.pop(); + existingGroup && + existingGroup.type !== 'gap' && + !existingGroup.sampleAccountIds.includes(notification.account.id) // This can happen for example if you like, then unlike, then like again the same post + ) { + // Update the existing group + if ( + existingGroup.sampleAccountIds.unshift(notification.account.id) > + NOTIFICATIONS_GROUP_MAX_AVATARS + ) + existingGroup.sampleAccountIds.pop(); + + existingGroup.most_recent_notification_id = notification.id; + existingGroup.page_max_id = notification.id; + existingGroup.latest_page_notification_at = notification.created_at; + existingGroup.notifications_count += 1; - existingGroup.most_recent_notification_id = notification.id; - existingGroup.page_max_id = notification.id; - existingGroup.latest_page_notification_at = notification.created_at; - existingGroup.notifications_count += 1; + groups.splice(existingGroupIndex, 1); + mergeGapsAround(groups, existingGroupIndex); - groups.splice(existingGroupIndex, 1); - mergeGapsAround(groups, existingGroupIndex); + groups.unshift(existingGroup); - groups.unshift(existingGroup); + return; + } } - } else { - // Create a new group - groups.unshift(createNotificationGroupFromNotificationJSON(notification)); } + + // We have not found an existing group, create a new one + groups.unshift(createNotificationGroupFromNotificationJSON(notification)); } function trimNotifications(state: NotificationGroupsState) { From 69c76fd94aa407d8496b8b23aa19d9a39c596ffe Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 05:08:29 -0400 Subject: [PATCH 45/51] [Glitch] Improve alignment of icons on admin roles list Port c828e7731c894b9ac265defe5e35a77cc32fbd4c to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/styles/admin.scss | 4 ++++ app/javascript/flavours/glitch/styles/tables.scss | 1 + 2 files changed, 5 insertions(+) diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss index c4a16247b8dad0..0cd3789fb88fd7 100644 --- a/app/javascript/flavours/glitch/styles/admin.scss +++ b/app/javascript/flavours/glitch/styles/admin.scss @@ -1075,6 +1075,10 @@ a.name-tag, } } + .icon { + vertical-align: middle; + } + a.announcements-list__item__title { &:hover, &:focus, diff --git a/app/javascript/flavours/glitch/styles/tables.scss b/app/javascript/flavours/glitch/styles/tables.scss index c84f672a6f345e..75fc29aed5ea10 100644 --- a/app/javascript/flavours/glitch/styles/tables.scss +++ b/app/javascript/flavours/glitch/styles/tables.scss @@ -137,6 +137,7 @@ a.table-action-link { padding: 0 10px; color: $darker-text-color; font-weight: 500; + white-space: nowrap; &:hover { color: $highlight-text-color; From e58d99a771d313fa037b1b058f88cc1baff4c08f Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 1 Oct 2024 09:41:25 -0400 Subject: [PATCH 46/51] [Glitch] Adjust spacing on setting sub-nav items when below mobile size Port 09cf617d7f8b633eb0f5ce84bf093e272943fede to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/styles/admin.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss index 0cd3789fb88fd7..9bc3c7758ea741 100644 --- a/app/javascript/flavours/glitch/styles/admin.scss +++ b/app/javascript/flavours/glitch/styles/admin.scss @@ -226,6 +226,10 @@ $content-width: 840px; gap: 5px; white-space: nowrap; + @media screen and (max-width: $mobile-breakpoint) { + flex: 1 0 50%; + } + &:hover, &:focus, &:active { From 3cad5095c9da1403f219dacedf4dba12070c6d78 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 2 Oct 2024 12:03:04 +0200 Subject: [PATCH 47/51] [Glitch] Fix incorrect `'navigator'` check Port 931553844d851b7209039cd35629ae22818476cf to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/actions/markers.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/javascript/flavours/glitch/actions/markers.ts b/app/javascript/flavours/glitch/actions/markers.ts index 37067dbcf7b56c..ea325109c5ea3b 100644 --- a/app/javascript/flavours/glitch/actions/markers.ts +++ b/app/javascript/flavours/glitch/actions/markers.ts @@ -37,8 +37,7 @@ export const synchronouslySubmitMarkers = createAppAsyncThunk( }); return; - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - } else if ('navigator' && 'sendBeacon' in navigator) { + } else if ('sendBeacon' in navigator) { // Failing that, we can use sendBeacon, but we have to encode the data as // FormData for DoorKeeper to recognize the token. const formData = new FormData(); From 59a8066045e427ff51d452119ca3acf97f10ff36 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 2 Oct 2024 15:28:36 +0200 Subject: [PATCH 48/51] [Glitch] Fix media uploads in composer appearing over search results in advanced interface Port 4a2d3929c5987fafde31d7ddd0a0d4842ae40eee to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/styles/components.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/flavours/glitch/styles/components.scss b/app/javascript/flavours/glitch/styles/components.scss index 72613cbbc4874b..a17591293fd50c 100644 --- a/app/javascript/flavours/glitch/styles/components.scss +++ b/app/javascript/flavours/glitch/styles/components.scss @@ -3793,6 +3793,7 @@ $ui-header-logo-wordmark-width: 99px; overflow-y: auto; width: 100%; height: 100%; + z-index: 0; } .drawer__inner__mastodon { From a09a26da49ef60058c816942d00cfbc400085822 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 2 Oct 2024 15:29:23 +0200 Subject: [PATCH 49/51] [Glitch] Fix editing description of media uploads with custom thumbnails Port 404f467fcf6db5c4510bc6dd788858f2440306ff to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/reducers/compose.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js index 211402ab2a5ef5..b797c2aae28e1f 100644 --- a/app/javascript/flavours/glitch/reducers/compose.js +++ b/app/javascript/flavours/glitch/reducers/compose.js @@ -507,7 +507,7 @@ export default function compose(state = initialState, action) { .set('isUploadingThumbnail', false) .update('media_attachments', list => list.map(item => { if (item.get('id') === action.media.id) { - return fromJS(action.media); + return fromJS(action.media).set('unattached', item.get('unattached')); } return item; From 4d611e94ee539687d28e1e5c8209675de3741a27 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 2 Oct 2024 17:51:05 +0200 Subject: [PATCH 50/51] [Glitch] Hide badges in media gallery when media are hidden Port 55b5364534a97c66b8612e6611ef0bba19b349d2 to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/components/media_gallery.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/flavours/glitch/components/media_gallery.jsx b/app/javascript/flavours/glitch/components/media_gallery.jsx index 4d51bdcdc2b1ff..5b1e56185e22bf 100644 --- a/app/javascript/flavours/glitch/components/media_gallery.jsx +++ b/app/javascript/flavours/glitch/components/media_gallery.jsx @@ -198,7 +198,7 @@ class Item extends PureComponent { {visible && thumbnail} - {badges && ( + {visible && badges && (
{badges}
From 354f54907daf11eabb18c8f8d48e8e5d470ba467 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 4 Oct 2024 14:23:30 +0200 Subject: [PATCH 51/51] [Glitch] Fix unsupported grouped notifications from streaming causing duplicate IDs Port 6d5aa58f88afbece2810c1321c3879d250ed11dc to glitch-soc Signed-off-by: Claire --- .../glitch/reducers/notification_groups.ts | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/app/javascript/flavours/glitch/reducers/notification_groups.ts b/app/javascript/flavours/glitch/reducers/notification_groups.ts index a2ba9a4f1793c3..3eece4b1d09a0d 100644 --- a/app/javascript/flavours/glitch/reducers/notification_groups.ts +++ b/app/javascript/flavours/glitch/reducers/notification_groups.ts @@ -206,50 +206,53 @@ function processNewNotification( groups: NotificationGroupsState['groups'], notification: ApiNotificationJSON, ) { - if (shouldGroupNotificationType(notification.type)) { - const existingGroupIndex = groups.findIndex( - (group) => - group.type !== 'gap' && group.group_key === notification.group_key, - ); + if (!shouldGroupNotificationType(notification.type)) { + notification = { + ...notification, + group_key: `ungrouped-${notification.id}`, + }; + } - // In any case, we are going to add a group at the top - // If there is currently a gap at the top, now is the time to update it - if (groups.length > 0 && groups[0]?.type === 'gap') { - groups[0].maxId = notification.id; - } + const existingGroupIndex = groups.findIndex( + (group) => + group.type !== 'gap' && group.group_key === notification.group_key, + ); - if (existingGroupIndex > -1) { - const existingGroup = groups[existingGroupIndex]; + // In any case, we are going to add a group at the top + // If there is currently a gap at the top, now is the time to update it + if (groups.length > 0 && groups[0]?.type === 'gap') { + groups[0].maxId = notification.id; + } - if ( - existingGroup && - existingGroup.type !== 'gap' && - !existingGroup.sampleAccountIds.includes(notification.account.id) // This can happen for example if you like, then unlike, then like again the same post - ) { - // Update the existing group - if ( - existingGroup.sampleAccountIds.unshift(notification.account.id) > - NOTIFICATIONS_GROUP_MAX_AVATARS - ) - existingGroup.sampleAccountIds.pop(); + if (existingGroupIndex > -1) { + const existingGroup = groups[existingGroupIndex]; - existingGroup.most_recent_notification_id = notification.id; - existingGroup.page_max_id = notification.id; - existingGroup.latest_page_notification_at = notification.created_at; - existingGroup.notifications_count += 1; + if ( + existingGroup && + existingGroup.type !== 'gap' && + !existingGroup.sampleAccountIds.includes(notification.account.id) // This can happen for example if you like, then unlike, then like again the same post + ) { + // Update the existing group + if ( + existingGroup.sampleAccountIds.unshift(notification.account.id) > + NOTIFICATIONS_GROUP_MAX_AVATARS + ) + existingGroup.sampleAccountIds.pop(); - groups.splice(existingGroupIndex, 1); - mergeGapsAround(groups, existingGroupIndex); + existingGroup.most_recent_notification_id = notification.id; + existingGroup.page_max_id = notification.id; + existingGroup.latest_page_notification_at = notification.created_at; + existingGroup.notifications_count += 1; - groups.unshift(existingGroup); + groups.splice(existingGroupIndex, 1); + mergeGapsAround(groups, existingGroupIndex); - return; - } + groups.unshift(existingGroup); } + } else { + // We have not found an existing group, create a new one + groups.unshift(createNotificationGroupFromNotificationJSON(notification)); } - - // We have not found an existing group, create a new one - groups.unshift(createNotificationGroupFromNotificationJSON(notification)); } function trimNotifications(state: NotificationGroupsState) {