From e4ea80d8089110790bf11f7d4021dbe7b6a866de Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 21 Jul 2023 13:14:30 +0200 Subject: [PATCH 1/6] Change thread view to scroll to the selected post rather than the post being replied to (#24685) --- .../mastodon/features/status/index.jsx | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index e5f91071e..4f58c7d2f 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -196,8 +196,8 @@ class Status extends ImmutablePureComponent { dispatch: PropTypes.func.isRequired, status: ImmutablePropTypes.map, isLoading: PropTypes.bool, - ancestorsIds: ImmutablePropTypes.list, - descendantsIds: ImmutablePropTypes.list, + ancestorsIds: ImmutablePropTypes.list.isRequired, + descendantsIds: ImmutablePropTypes.list.isRequired, intl: PropTypes.object.isRequired, askReplyConfirmation: PropTypes.bool, multiColumn: PropTypes.bool, @@ -224,14 +224,9 @@ class Status extends ImmutablePureComponent { UNSAFE_componentWillReceiveProps (nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { - this._scrolledIntoView = false; this.props.dispatch(fetchStatus(nextProps.params.statusId)); } - if (nextProps.params.statusId && nextProps.ancestorsIds.size > this.props.ancestorsIds.size) { - this._scrolledIntoView = false; - } - if (nextProps.status && nextProps.status.get('id') !== this.state.loadedStatusId) { this.setState({ showMedia: defaultMediaVisibility(nextProps.status), loadedStatusId: nextProps.status.get('id') }); } @@ -584,20 +579,23 @@ class Status extends ImmutablePureComponent { this.node = c; }; - componentDidUpdate () { - if (this._scrolledIntoView) { - return; - } - - const { status, ancestorsIds } = this.props; - - if (status && ancestorsIds && ancestorsIds.size > 0) { - const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1]; + componentDidUpdate (prevProps) { + const { status, ancestorsIds, multiColumn } = this.props; + if (status && (ancestorsIds.size > prevProps.ancestorsIds.size || prevProps.status?.get('id') !== status.get('id'))) { window.requestAnimationFrame(() => { - element.scrollIntoView(true); + this.node?.querySelector('.detailed-status__wrapper')?.scrollIntoView(true); + + // In the single-column interface, `scrollIntoView` will put the post behind the header, + // so compensate for that. + if (!multiColumn) { + const offset = document.querySelector('.column-header__wrapper')?.getBoundingClientRect()?.bottom; + if (offset) { + const scrollingElement = document.scrollingElement || document.body; + scrollingElement.scrollBy(0, -offset); + } + } }); - this._scrolledIntoView = true; } } From 934c7b33d1d33e3e12a7bb12f654b5d8eed0d51b Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 21 Jul 2023 13:17:43 +0200 Subject: [PATCH 2/6] Change default KeyGenerator digest to SHA1 to fix cookies in rolling upgrades (#26023) --- config/initializers/cookie_rotator.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/config/initializers/cookie_rotator.rb b/config/initializers/cookie_rotator.rb index 349c363f1..b829b1a90 100644 --- a/config/initializers/cookie_rotator.rb +++ b/config/initializers/cookie_rotator.rb @@ -1,5 +1,10 @@ # frozen_string_literal: true +# TODO: Remove after 4.2.0 +Rails.application.configure do + config.active_support.key_generator_hash_digest_class = OpenSSL::Digest::SHA1 +end + Rails.application.config.after_initialize do Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies| authenticated_encrypted_cookie_salt = Rails.application.config.action_dispatch.authenticated_encrypted_cookie_salt @@ -7,8 +12,9 @@ Rails.application.config.after_initialize do secret_key_base = Rails.application.secret_key_base + # TODO: Switch to SHA1 after 4.2.0 key_generator = ActiveSupport::KeyGenerator.new( - secret_key_base, iterations: 1000, hash_digest_class: OpenSSL::Digest::SHA1 + secret_key_base, iterations: 1000, hash_digest_class: OpenSSL::Digest::SHA256 ) key_len = ActiveSupport::MessageEncryptor.key_len From 59478a1b46e934c75ad27f1270d9b9411de3f185 Mon Sep 17 00:00:00 2001 From: Trevor Wolf Date: Fri, 21 Jul 2023 21:20:14 +1000 Subject: [PATCH 3/6] change focus ui for keyboard only input (#25935) --- .../mastodon/components/dropdown_menu.jsx | 2 +- .../compose/components/navigation_bar.jsx | 19 +++-- app/javascript/styles/mastodon/basics.scss | 15 +++- .../styles/mastodon/components.scss | 83 +++++-------------- app/javascript/styles/mastodon/variables.scss | 4 + 5 files changed, 50 insertions(+), 73 deletions(-) diff --git a/app/javascript/mastodon/components/dropdown_menu.jsx b/app/javascript/mastodon/components/dropdown_menu.jsx index 0416df5d4..fd66310e8 100644 --- a/app/javascript/mastodon/components/dropdown_menu.jsx +++ b/app/javascript/mastodon/components/dropdown_menu.jsx @@ -297,7 +297,7 @@ export default class Dropdown extends PureComponent { onKeyPress: this.handleKeyPress, }) : ( - - {this.props.account.get('acct')} + + {username}
- - @{this.props.account.get('acct')} - + + + @{username} + + - + + +
-
diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index ff00c797c..234c703f2 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -161,13 +161,20 @@ body { } } +a { + &:focus { + border-radius: 4px; + outline: $ui-button-icon-focus-outline; + } + + &:focus:not(:focus-visible) { + outline: none; + } +} + button { font-family: inherit; cursor: pointer; - - &:focus { - outline: none; - } } .app-holder { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index beff07daa..d08cb2803 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -74,6 +74,10 @@ background-color: $ui-button-focus-background-color; } + &:focus { + outline: $ui-button-icon-focus-outline; + } + &--destructive { &:active, &:focus, @@ -98,16 +102,6 @@ transition: none; } - &::-moz-focus-inner { - border: 0; - } - - &::-moz-focus-inner, - &:focus, - &:active { - outline: 0 !important; - } - &.button-secondary { color: $ui-button-secondary-color; background: transparent; @@ -197,7 +191,7 @@ border-radius: 4px; background: transparent; cursor: pointer; - transition: all 100ms ease-in; + transition: all 100ms ease-out; transition-property: background-color, color; text-decoration: none; @@ -209,14 +203,12 @@ &:hover, &:active, &:focus { - color: lighten($action-button-color, 7%); - background-color: rgba($action-button-color, 0.15); - transition: all 200ms ease-out; - transition-property: background-color, color; + color: lighten($action-button-color, 20%); + background-color: $ui-button-icon-hover-background-color; } &:focus { - background-color: rgba($action-button-color, 0.3); + outline: $ui-button-icon-focus-outline; } &.disabled { @@ -225,20 +217,6 @@ cursor: default; } - &.active { - color: $highlight-text-color; - } - - &::-moz-focus-inner { - border: 0; - } - - &::-moz-focus-inner, - &:focus, - &:active { - outline: 0 !important; - } - &.inverted { color: $lighter-text-color; @@ -246,11 +224,11 @@ &:active, &:focus { color: darken($lighter-text-color, 7%); - background-color: rgba($lighter-text-color, 0.15); + background-color: $ui-button-icon-hover-background-color; } &:focus { - background-color: rgba($lighter-text-color, 0.3); + outline: $ui-button-icon-focus-outline; } &.disabled { @@ -305,7 +283,6 @@ font-size: 11px; padding: 0 3px; line-height: 27px; - outline: 0; transition: all 100ms ease-in; transition-property: background-color, color; @@ -313,13 +290,13 @@ &:active, &:focus { color: darken($lighter-text-color, 7%); - background-color: rgba($lighter-text-color, 0.15); + background-color: $ui-button-icon-hover-background-color; transition: all 200ms ease-out; transition-property: background-color, color; } &:focus { - background-color: rgba($lighter-text-color, 0.3); + outline: $ui-button-icon-focus-outline; } &.disabled { @@ -331,16 +308,6 @@ &.active { color: $highlight-text-color; } - - &::-moz-focus-inner { - border: 0; - } - - &::-moz-focus-inner, - &:focus, - &:active { - outline: 0 !important; - } } body > [data-popper-placement] { @@ -728,7 +695,6 @@ body > [data-popper-placement] { flex: 0 0 auto; .compose-form__publish-button-wrapper { - overflow: hidden; padding-top: 15px; } } @@ -1929,13 +1895,6 @@ a.account__display-name { .navigation-bar__actions { position: relative; - .icon-button.close { - position: absolute; - pointer-events: none; - transform: scale(0, 1) translate(-100%, 0); - opacity: 0; - } - .compose__action-bar .icon-button { pointer-events: auto; transform: scale(1, 1) translate(0, 0); @@ -1945,19 +1904,21 @@ a.account__display-name { } .navigation-bar__profile { + display: flex; + flex-direction: column; flex: 1 1 auto; line-height: 20px; - overflow: hidden; } .navigation-bar__profile-account { - display: block; + display: inline; font-weight: 500; overflow: hidden; text-overflow: ellipsis; } .navigation-bar__profile-edit { + display: inline; color: inherit; text-decoration: none; } @@ -4740,11 +4701,6 @@ a.status-card.compact:hover { outline: 0; cursor: pointer; - &:active, - &:focus { - outline: 0 !important; - } - img { filter: grayscale(100%); opacity: 0.8; @@ -4760,6 +4716,13 @@ a.status-card.compact:hover { img { opacity: 1; filter: none; + border-radius: 100%; + } + } + + &:focus { + img { + outline: $ui-button-icon-focus-outline; } } } diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss index 68db9d5fc..e89dd5d3a 100644 --- a/app/javascript/styles/mastodon/variables.scss +++ b/app/javascript/styles/mastodon/variables.scss @@ -5,6 +5,7 @@ $red-600: #b7253d !default; // Deep Carmine $red-500: #df405a !default; // Cerise $blurple-600: #563acc; // Iris $blurple-500: #6364ff; // Brand purple +$blurple-400: #7477fd; // Medium slate blue $blurple-300: #858afa; // Faded Blue $grey-600: #4e4c5a; // Trout $grey-100: #dadaf3; // Topaz @@ -56,6 +57,9 @@ $ui-button-tertiary-focus-color: $white !default; $ui-button-destructive-background-color: $red-500 !default; $ui-button-destructive-focus-background-color: $red-600 !default; +$ui-button-icon-focus-outline: solid 2px $blurple-400 !default; +$ui-button-icon-hover-background-color: rgba(140, 141, 255, 40%) !default; + // Variables for texts $primary-text-color: $white !default; $darker-text-color: $ui-primary-color !default; From 14fad603845dedda87300516a90d78b3b69133df Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 21 Jul 2023 13:20:53 +0200 Subject: [PATCH 4/6] Use username as display name for suspended users or users with blank display names (#25276) --- app/serializers/activitypub/actor_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index e6dd8040e..36397857f 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -99,7 +99,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer end def name - object.suspended? ? '' : object.display_name + object.suspended? ? object.username : (object.display_name.presence || object.username) end def summary From e5f1000ad1c2817a62617f78ed21983bd4b38487 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 21 Jul 2023 13:34:15 +0200 Subject: [PATCH 5/6] Fix CSP headers being unintendedly wide (#26105) --- .../initializers/content_security_policy.rb | 2 +- spec/requests/content_security_policy_spec.rb | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 spec/requests/content_security_policy_spec.rb diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb index 4cc9c204d..98c4f541f 100644 --- a/config/initializers/content_security_policy.rb +++ b/config/initializers/content_security_policy.rb @@ -5,7 +5,7 @@ # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy def host_to_url(str) - "http#{Rails.configuration.x.use_https ? 's' : ''}://#{str}".split('/').first if str.present? + "http#{Rails.configuration.x.use_https ? 's' : ''}://#{str.split('/').first}" if str.present? end base_host = Rails.configuration.x.web_domain diff --git a/spec/requests/content_security_policy_spec.rb b/spec/requests/content_security_policy_spec.rb new file mode 100644 index 000000000..7eb27d61d --- /dev/null +++ b/spec/requests/content_security_policy_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Content-Security-Policy' do + it 'sets the expected CSP headers' do + allow(SecureRandom).to receive(:base64).with(16).and_return('ZbA+JmE7+bK8F5qvADZHuQ==') + + get '/' + expect(response.headers['Content-Security-Policy'].split(';').map(&:strip)).to contain_exactly( + "base-uri 'none'", + "default-src 'none'", + "frame-ancestors 'none'", + "font-src 'self' https://cb6e6126.ngrok.io", + "img-src 'self' https: data: blob: https://cb6e6126.ngrok.io", + "style-src 'self' https://cb6e6126.ngrok.io 'nonce-ZbA+JmE7+bK8F5qvADZHuQ=='", + "media-src 'self' https: data: https://cb6e6126.ngrok.io", + "frame-src 'self' https:", + "manifest-src 'self' https://cb6e6126.ngrok.io", + "form-action 'self'", + "child-src 'self' blob: https://cb6e6126.ngrok.io", + "worker-src 'self' blob: https://cb6e6126.ngrok.io", + "connect-src 'self' data: blob: https://cb6e6126.ngrok.io https://cb6e6126.ngrok.io ws://localhost:4000", + "script-src 'self' https://cb6e6126.ngrok.io 'wasm-unsafe-eval'" + ) + end +end From 5b457961fc1189a71599dc6c06b3f159b195a455 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 21 Jul 2023 14:21:10 +0200 Subject: [PATCH 6/6] Fix linting issue (#26106) --- spec/lib/vacuum/applications_vacuum_spec.rb | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/lib/vacuum/applications_vacuum_spec.rb b/spec/lib/vacuum/applications_vacuum_spec.rb index d30311ab1..57a222aaf 100644 --- a/spec/lib/vacuum/applications_vacuum_spec.rb +++ b/spec/lib/vacuum/applications_vacuum_spec.rb @@ -6,43 +6,43 @@ RSpec.describe Vacuum::ApplicationsVacuum do subject { described_class.new } describe '#perform' do - let!(:app1) { Fabricate(:application, created_at: 1.month.ago) } - let!(:app2) { Fabricate(:application, created_at: 1.month.ago) } - let!(:app3) { Fabricate(:application, created_at: 1.month.ago) } - let!(:app4) { Fabricate(:application, created_at: 1.month.ago, owner: Fabricate(:user)) } - let!(:app5) { Fabricate(:application, created_at: 1.month.ago) } - let!(:app6) { Fabricate(:application, created_at: 1.hour.ago) } + let!(:app_with_token) { Fabricate(:application, created_at: 1.month.ago) } + let!(:app_with_grant) { Fabricate(:application, created_at: 1.month.ago) } + let!(:app_with_signup) { Fabricate(:application, created_at: 1.month.ago) } + let!(:app_with_owner) { Fabricate(:application, created_at: 1.month.ago, owner: Fabricate(:user)) } + let!(:unused_app) { Fabricate(:application, created_at: 1.month.ago) } + let!(:recent_app) { Fabricate(:application, created_at: 1.hour.ago) } - let!(:active_access_token) { Fabricate(:access_token, application: app1) } - let!(:active_access_grant) { Fabricate(:access_grant, application: app2) } - let!(:user) { Fabricate(:user, created_by_application: app3) } + let!(:active_access_token) { Fabricate(:access_token, application: app_with_token) } + let!(:active_access_grant) { Fabricate(:access_grant, application: app_with_grant) } + let!(:user) { Fabricate(:user, created_by_application: app_with_signup) } before do subject.perform end it 'does not delete applications with valid access tokens' do - expect { app1.reload }.to_not raise_error + expect { app_with_token.reload }.to_not raise_error end it 'does not delete applications with valid access grants' do - expect { app2.reload }.to_not raise_error + expect { app_with_grant.reload }.to_not raise_error end it 'does not delete applications that were used to create users' do - expect { app3.reload }.to_not raise_error + expect { app_with_signup.reload }.to_not raise_error end it 'does not delete owned applications' do - expect { app4.reload }.to_not raise_error + expect { app_with_owner.reload }.to_not raise_error end it 'does not delete applications registered less than a day ago' do - expect { app6.reload }.to_not raise_error + expect { recent_app.reload }.to_not raise_error end it 'deletes unused applications' do - expect { app5.reload }.to raise_error ActiveRecord::RecordNotFound + expect { unused_app.reload }.to raise_error ActiveRecord::RecordNotFound end end end