Merge pull request #3107 from ClearlyClaire/glitch-soc/merge-upstream

Merge upstream changes up to ac039d5f13
This commit is contained in:
Claire
2025-06-22 11:36:37 +02:00
committed by GitHub
14 changed files with 128 additions and 42 deletions

View File

@@ -116,7 +116,7 @@ GEM
base64 (0.3.0)
bcp47_spec (0.2.1)
bcrypt (3.1.20)
benchmark (0.4.0)
benchmark (0.4.1)
better_errors (2.10.1)
erubi (>= 1.0.0)
rack (>= 0.9.0)
@@ -708,7 +708,7 @@ GEM
link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.7.0)
rdf (~> 3.3)
rdoc (6.14.0)
rdoc (6.14.1)
erb
psych (>= 4.0.0)
redcarpet (3.6.1)
@@ -737,17 +737,17 @@ GEM
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.3)
rspec-core (3.13.4)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.4)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-github (3.0.0)
rspec-core (~> 3.0)
rspec-mocks (3.13.4)
rspec-mocks (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (8.0.0)
rspec-rails (8.0.1)
actionpack (>= 7.2)
activesupport (>= 7.2)
railties (>= 7.2)
@@ -760,7 +760,7 @@ GEM
rspec-expectations (~> 3.0)
rspec-mocks (~> 3.0)
sidekiq (>= 5, < 9)
rspec-support (3.13.3)
rspec-support (3.13.4)
rubocop (1.76.2)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
@@ -940,7 +940,7 @@ GEM
xorcist (1.1.3)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.7.2)
zeitwerk (2.7.3)
PLATFORMS
ruby

View File

@@ -15,8 +15,9 @@ class Api::V1::Instances::TermsOfServicesController < Api::V1::Instances::BaseCo
if params[:date].present?
TermsOfService.published.find_by!(effective_date: params[:date])
else
TermsOfService.live.first || TermsOfService.published.first! # For the case when none of the published terms have become effective yet
TermsOfService.current
end
end
not_found if @terms_of_service.nil?
end
end

View File

@@ -377,7 +377,11 @@ class Status extends ImmutablePureComponent {
if (newTab) {
window.open(path, '_blank', 'noopener');
} else {
history.push(path);
if (history.location.pathname.replace('/deck/', '/') === path) {
history.replace(path);
} else {
history.push(path);
}
}
};

View File

@@ -1244,6 +1244,7 @@ body > [data-popper-placement] {
line-height: 20px;
letter-spacing: 0.25px;
display: -webkit-box;
line-clamp: 4;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
padding: 0;
@@ -1322,6 +1323,7 @@ body > [data-popper-placement] {
letter-spacing: 0.25px;
padding-top: 0 !important;
display: -webkit-box;
line-clamp: 4;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
max-height: 4 * 20px;
@@ -2166,6 +2168,7 @@ body > [data-popper-placement] {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
line-clamp: 1;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
margin-top: 10px;
@@ -3189,7 +3192,7 @@ a.account__display-name {
}
}
@media screen and (max-width: $no-gap-breakpoint - 1px) {
@media screen and (max-width: ($no-gap-breakpoint - 1px)) {
$sidebar-width: 285px;
.columns-area__panels__main {
@@ -3389,7 +3392,7 @@ a.account__display-name {
}
}
@media screen and (max-width: $no-gap-breakpoint - 1px) {
@media screen and (max-width: ($no-gap-breakpoint - 1px)) {
.columns-area__panels__pane--compositional {
display: none;
}
@@ -3630,19 +3633,19 @@ a.account__display-name {
}
}
@media screen and (height <= 930px - 56px) {
@media screen and (height <= (930px - 56px)) {
&__portal .trends__item:nth-child(n + 4) {
display: none;
}
}
@media screen and (height <= 930px - (56px * 2)) {
@media screen and (height <= (930px - 56px * 2)) {
&__portal .trends__item:nth-child(n + 3) {
display: none;
}
}
@media screen and (height <= 930px - (56px * 3)) {
@media screen and (height <= (930px - 56px * 3)) {
&__portal {
display: none;
}
@@ -4198,6 +4201,7 @@ a.status-card {
.status-card.expanded .status-card__title {
white-space: normal;
display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
@@ -6390,6 +6394,7 @@ a.status-card {
font-size: 15px;
line-height: 22px;
color: $dark-text-color;
line-clamp: 4;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
max-height: 4 * 22px;
@@ -8021,7 +8026,7 @@ img.modal-warning {
display: flex;
flex-shrink: 0;
@media screen and (max-width: $no-gap-breakpoint - 1px) {
@media screen and (max-width: ($no-gap-breakpoint - 1px)) {
border-right: 0;
border-left: 0;
}
@@ -8429,7 +8434,7 @@ noscript {
}
// Fallback for older browsers with no container queries support
@media screen and (max-width: 372px + 55px) {
@media screen and (max-width: (372px + 55px)) {
display: none;
}
}
@@ -8898,7 +8903,7 @@ noscript {
width: 124px;
flex: 0 0 auto;
@media screen and (max-width: 124px + 300px) {
@media screen and (max-width: (124px + 300px)) {
display: none;
}
}
@@ -8908,7 +8913,7 @@ noscript {
flex: 0 0 auto;
position: relative;
@media screen and (max-width: 124px + 300px) {
@media screen and (max-width: (124px + 300px)) {
width: 100%;
}
}
@@ -9001,7 +9006,6 @@ noscript {
height: 100%;
min-width: auto;
min-height: auto;
vertical-align: bottom;
object-fit: contain;
}
}
@@ -9037,6 +9041,7 @@ noscript {
}
.emoji-picker-dropdown {
display: flex;
margin: 2px;
}
@@ -9268,7 +9273,7 @@ noscript {
.layout-single-column .explore__search-header {
display: none;
@media screen and (max-width: $no-gap-breakpoint - 1px) {
@media screen and (max-width: ($no-gap-breakpoint - 1px)) {
display: flex;
}
}
@@ -9666,7 +9671,7 @@ noscript {
}
}
@media screen and (max-width: $no-gap-breakpoint - 1px) {
@media screen and (max-width: ($no-gap-breakpoint - 1px)) {
&__choices {
flex-direction: column;
@@ -11216,6 +11221,7 @@ noscript {
font-size: 14px;
line-height: 20px;
display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
max-height: 2 * 20px;

View File

@@ -301,7 +301,11 @@ class Status extends ImmutablePureComponent {
if (newTab) {
window.open(path, '_blank', 'noopener');
} else {
history.push(path);
if (history.location.pathname.replace('/deck/', '/') === path) {
history.replace(path);
} else {
history.push(path);
}
}
};

View File

@@ -1185,6 +1185,7 @@ body > [data-popper-placement] {
line-height: 20px;
letter-spacing: 0.25px;
display: -webkit-box;
line-clamp: 4;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
padding: 0;
@@ -1263,6 +1264,7 @@ body > [data-popper-placement] {
letter-spacing: 0.25px;
padding-top: 0 !important;
display: -webkit-box;
line-clamp: 4;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
max-height: 4 * 20px;
@@ -2100,6 +2102,7 @@ body > [data-popper-placement] {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
line-clamp: 1;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
margin-top: 10px;
@@ -3124,7 +3127,7 @@ a.account__display-name {
}
}
@media screen and (max-width: $no-gap-breakpoint - 1px) {
@media screen and (max-width: ($no-gap-breakpoint - 1px)) {
$sidebar-width: 285px;
.columns-area__panels__main {
@@ -3324,7 +3327,7 @@ a.account__display-name {
}
}
@media screen and (max-width: $no-gap-breakpoint - 1px) {
@media screen and (max-width: ($no-gap-breakpoint - 1px)) {
.columns-area__panels__pane--compositional {
display: none;
}
@@ -3565,19 +3568,19 @@ a.account__display-name {
}
}
@media screen and (height <= 930px - 56px) {
@media screen and (height <= (930px - 56px)) {
&__portal .trends__item:nth-child(n + 4) {
display: none;
}
}
@media screen and (height <= 930px - (56px * 2)) {
@media screen and (height <= (930px - 56px * 2)) {
&__portal .trends__item:nth-child(n + 3) {
display: none;
}
}
@media screen and (height <= 930px - (56px * 3)) {
@media screen and (height <= (930px - 56px * 3)) {
&__portal {
display: none;
}
@@ -4110,6 +4113,7 @@ a.status-card {
.status-card.expanded .status-card__title {
white-space: normal;
display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
@@ -6153,6 +6157,7 @@ a.status-card {
font-size: 15px;
line-height: 22px;
color: $dark-text-color;
line-clamp: 4;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
max-height: 4 * 22px;
@@ -7717,7 +7722,7 @@ a.status-card {
display: flex;
flex-shrink: 0;
@media screen and (max-width: $no-gap-breakpoint - 1px) {
@media screen and (max-width: ($no-gap-breakpoint - 1px)) {
border-right: 0;
border-left: 0;
}
@@ -8126,7 +8131,7 @@ noscript {
}
// Fallback for older browsers with no container queries support
@media screen and (max-width: 372px + 55px) {
@media screen and (max-width: (372px + 55px)) {
display: none;
}
}
@@ -8595,7 +8600,7 @@ noscript {
width: 124px;
flex: 0 0 auto;
@media screen and (max-width: 124px + 300px) {
@media screen and (max-width: (124px + 300px)) {
display: none;
}
}
@@ -8605,7 +8610,7 @@ noscript {
flex: 0 0 auto;
position: relative;
@media screen and (max-width: 124px + 300px) {
@media screen and (max-width: (124px + 300px)) {
width: 100%;
}
}
@@ -8698,7 +8703,6 @@ noscript {
height: 100%;
min-width: auto;
min-height: auto;
vertical-align: bottom;
object-fit: contain;
}
}
@@ -8734,6 +8738,7 @@ noscript {
}
.emoji-picker-dropdown {
display: flex;
margin: 2px;
}
@@ -8959,7 +8964,7 @@ noscript {
.layout-single-column .explore__search-header {
display: none;
@media screen and (max-width: $no-gap-breakpoint - 1px) {
@media screen and (max-width: ($no-gap-breakpoint - 1px)) {
display: flex;
}
}
@@ -9357,7 +9362,7 @@ noscript {
}
}
@media screen and (max-width: $no-gap-breakpoint - 1px) {
@media screen and (max-width: ($no-gap-breakpoint - 1px)) {
&__choices {
flex-direction: column;
@@ -10902,6 +10907,7 @@ noscript {
font-size: 14px;
line-height: 20px;
display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
max-height: 2 * 20px;

View File

@@ -16,6 +16,7 @@
class TermsOfService < ApplicationRecord
scope :published, -> { where.not(published_at: nil).order(Arel.sql('coalesce(effective_date, published_at) DESC')) }
scope :live, -> { published.where('effective_date IS NULL OR effective_date < now()').limit(1) }
scope :upcoming, -> { published.reorder(effective_date: :asc).where('effective_date IS NOT NULL AND effective_date > now()').limit(1) }
scope :draft, -> { where(published_at: nil).order(id: :desc).limit(1) }
validates :text, presence: true
@@ -26,6 +27,10 @@ class TermsOfService < ApplicationRecord
NOTIFICATION_ACTIVITY_CUTOFF = 1.year.freeze
def self.current
live.first || upcoming.first # For the case when none of the published terms have become effective yet
end
def published?
published_at.present?
end

View File

@@ -21,5 +21,5 @@ class WebauthnCredential < ApplicationRecord
validates :external_id, uniqueness: true
validates :nickname, uniqueness: { scope: :user_id }
validates :sign_count,
numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: SIGN_COUNT_LIMIT - 1 }
numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than: SIGN_COUNT_LIMIT }
end

View File

@@ -25,12 +25,15 @@ class Webhook < ApplicationRecord
status.updated
).freeze
SECRET_LENGTH_MIN = 12
SECRET_SIZE = 20
attr_writer :current_account
scope :enabled, -> { where(enabled: true) }
validates :url, presence: true, url: true
validates :secret, presence: true, length: { minimum: 12 }
validates :secret, presence: true, length: { minimum: SECRET_LENGTH_MIN }
validates :events, presence: true
validate :events_validation_error, if: :invalid_events?
@@ -41,7 +44,7 @@ class Webhook < ApplicationRecord
before_validation :generate_secret
def rotate_secret!
update!(secret: SecureRandom.hex(20))
update!(secret: random_secret)
end
def enable!
@@ -93,6 +96,10 @@ class Webhook < ApplicationRecord
end
def generate_secret
self.secret = SecureRandom.hex(20) if secret.blank?
self.secret = random_secret if secret.blank?
end
def random_secret
SecureRandom.hex(SECRET_SIZE)
end
end

View File

@@ -127,7 +127,7 @@ class InitialStateSerializer < ActiveModel::Serializer
trends_as_landing_page: Setting.trends_as_landing_page,
trends_enabled: Setting.trends,
version: instance_presenter.version,
terms_of_service_enabled: TermsOfService.live.exists?,
terms_of_service_enabled: TermsOfService.current.present?,
}
end

View File

@@ -61,7 +61,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
status: object.status_page_url,
about: about_url,
privacy_policy: privacy_policy_url,
terms_of_service: TermsOfService.live.exists? ? terms_of_service_url : nil,
terms_of_service: TermsOfService.current.present? ? terms_of_service_url : nil,
},
vapid: {

View File

@@ -24,4 +24,55 @@ RSpec.describe TermsOfService do
expect(subject.pluck(:id)).to match_array(user_before.id)
end
end
describe '::current' do
context 'when no terms exist' do
it 'returns nil' do
expect(described_class.current).to be_nil
end
end
context 'when only unpublished terms exist' do
before do
yesterday = Date.yesterday
travel_to yesterday do
Fabricate(:terms_of_service, published_at: nil, effective_date: yesterday)
end
Fabricate(:terms_of_service, published_at: nil, effective_date: Date.tomorrow)
end
it 'returns nil' do
expect(described_class.current).to be_nil
end
end
context 'when both effective and future terms exist' do
let!(:effective_terms) do
yesterday = Date.yesterday
travel_to yesterday do
Fabricate(:terms_of_service, effective_date: yesterday)
end
end
before do
Fabricate(:terms_of_service, effective_date: Date.tomorrow)
end
it 'returns the effective terms' do
expect(described_class.current).to eq effective_terms
end
end
context 'when only future terms exist' do
let!(:upcoming_terms) { Fabricate(:terms_of_service, effective_date: Date.tomorrow) }
before do
Fabricate(:terms_of_service, effective_date: 10.days.since)
end
it 'returns the terms that are upcoming next' do
expect(described_class.current).to eq upcoming_terms
end
end
end
end

View File

@@ -14,6 +14,6 @@ RSpec.describe WebauthnCredential do
it { is_expected.to validate_uniqueness_of(:external_id) }
it { is_expected.to validate_uniqueness_of(:nickname).scoped_to(:user_id) }
it { is_expected.to validate_numericality_of(:sign_count).only_integer.is_greater_than_or_equal_to(0).is_less_than_or_equal_to(described_class::SIGN_COUNT_LIMIT - 1) }
it { is_expected.to validate_numericality_of(:sign_count).only_integer.is_greater_than_or_equal_to(0).is_less_than(described_class::SIGN_COUNT_LIMIT) }
end
end

View File

@@ -8,6 +8,8 @@ RSpec.describe Webhook do
describe 'Validations' do
subject { Fabricate.build :webhook }
it { is_expected.to validate_length_of(:secret).is_at_least(described_class::SECRET_LENGTH_MIN) }
it { is_expected.to validate_presence_of(:events) }
it { is_expected.to_not allow_values([], %w(account.invalid)).for(:events) }