Compare commits

...

51 Commits

Author SHA1 Message Date
anna 60d54db810
merge with catstodon/main 3 months ago
Jeremy Kescher e995f70ed3
Fix certain jsx files being in an old state in relation to the emoji reactions PR 3 months ago
Jeremy Kescher 55aef6a70c
Fix scss styles being in an old state 3 months ago
Jeremy Kescher 6d6a5561f8
Catstodon rev reset due to upstream version bump 3 months ago
Jeremy Kescher ce878a78bb
Merge branch 'glitch-soc' into develop 3 months ago
Claire d7d477047e Bump version to v4.3.0-alpha.3 (#29241) 3 months ago
Claire 6c1b6194f7 Merge pull request from GHSA-jhrq-qvrm-qr36
* Fix insufficient Content-Type checking of fetched ActivityStreams objects

* Allow JSON-LD documents with multiple profiles
3 months ago
renovate[bot] 63080e10a9 Update dependency pg to v1.5.5 (#29230)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 months ago
Claire e7ca82762d Fix user creation failure handling in OAuth paths (#29207) 3 months ago
Jeremy Kescher f044841a6e
Catstodon rev +1.0.18 3 months ago
Jeremy Kescher 1c9291adcd
Merge branch 'glitch-soc' into develop
# Conflicts:
#	app/javascript/flavours/glitch/styles/components/accounts.scss
#	app/javascript/flavours/glitch/styles/components/status.scss
3 months ago
Claire 486e4bc7d3 Fix OmniAuth tests (#29201) 3 months ago
Claire 40439399c6 Bump version to v4.3.0-alpha.2 (#29200) 3 months ago
Claire 53b73ed6a2 Merge pull request from GHSA-vm39-j3vx-pch3
* Prevent different identities from a same SSO provider from accessing a same account

* Lock auth provider changes behind `ALLOW_UNSAFE_AUTH_PROVIDER_REATTACH=true`

* Rename methods to avoid confusion between OAuth and OmniAuth
3 months ago
Emelia Smith 436419cc2f Merge pull request from GHSA-7w3c-p9j8-mq3x
* Ensure destruction of OAuth Applications notifies streaming

Due to doorkeeper using a dependent: delete_all relationship, the destroy of an OAuth Application bypassed the existing AccessTokenExtension callbacks for announcing destructing of access tokens.

* Ensure password resets revoke access to Streaming API

* Improve performance of deleting OAuth tokens

---------

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
3 months ago
Claire 3c3ef8b9d3 Add `sidekiq_unique_jobs:delete_all_locks` task and disable `sidekiq-unique-jobs` UI by default (#29199) 3 months ago
Emelia Smith eae436c5a8 Disable administrative doorkeeper routes (#29187) 3 months ago
Emelia Smith b83076647e Ignore legacy moderator and admin columns on User model (#29188) 3 months ago
renovate[bot] 7100106f3f Update dependency sidekiq-unique-jobs to v7.1.33 (#29175)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 months ago
renovate[bot] 04da8f887b Update dependency pghero to v3.4.1 (#29144)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 months ago
renovate[bot] d9d0a6ef33 Update dependency irb to v1.11.2 (#29135)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 months ago
renovate[bot] 93b365bfc0 Update dependency webmock to v3.20.0 (#29120)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 months ago
Claire e3c4e90646
Fix new list button being blank (#2616) 3 months ago
Claire 084d051e6c
Merge pull request #2608 from ClearlyClaire/glitch-soc/refactor/monolithic-components-css
Merge SCSS in a single `components.scss` file
3 months ago
Claire c249c10f74 Fix account suggestion size inconsistency with upstream 3 months ago
Jeremy Kescher 924912bf9b
Catstodon rev +1.0.17 3 months ago
Essem 4cbaf46950
Hydrate reactions on streaming API 3 months ago
Essem d61b323c84
Purge status reactions on account delete 3 months ago
Claire d391e01b59 Revert notification bar color for now 3 months ago
Claire 3f89cec3c9 Further reduce differences with upstream 3 months ago
Claire f6f62002c7 Remove leftovers from glitch-soc's layout option 3 months ago
Claire 189b70cd6a Merge SCSS in a single `components.scss` file 3 months ago
Jeremy Kescher e4a333c6ae
Adjust initial_state_serializer to state of updated emoji reactions PR 3 months ago
Jeremy Kescher 40ef7b3849
Merge branch 'glitch-soc' into develop
# Conflicts:
#	app/javascript/flavours/glitch/initial_state.js
#	app/serializers/initial_state_serializer.rb
3 months ago
Claire 8c76a208ed
Merge pull request #2607 from ClearlyClaire/glitch-soc/cherry-pick-upstream
Cherry-pick upstream changes
3 months ago
Eugen Rochko 65eb943b9d Fix confirmation e-mails when signing up through an app (#29064) 3 months ago
Claire 19f1ffe287 Fix self-destruct schedule not actually replacing initial schedule (#29049) 3 months ago
renovate[bot] 5ea36a3606 Update dependency brakeman to v6.1.2 (#29062)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 months ago
renovate[bot] b27cad27c2 Update dependency bootsnap to '~> 1.18.0' (#29019)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 months ago
renovate[bot] 63f1f1465a Update dependency tzinfo-data to v1.2024.1 (#29052)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 months ago
Matt Jankowski 4cd00c0dd9 Update `nsa` gem to version 0.3.0 (#29065) 3 months ago
renovate[bot] c6f7e3a1fb Update dependency haml_lint to v0.56.0 (#29082)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 months ago
renovate[bot] 93aa783aa1 Update dependency nokogiri to v1.16.2 [SECURITY] (#29106)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 months ago
renovate[bot] 868333c86f Update dependency capybara to v3.40.0 (#28966)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 months ago
renovate[bot] ac49514bd6 Update dependency chewy to v7.5.1 (#29018)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
3 months ago
github-actions[bot] a2611d782c
New Crowdin Translations (automated) (#2588)
* New Crowdin translations

* Fix bogus translation files

---------

Co-authored-by: GitHub Actions <noreply@github.com>
Co-authored-by: Claire <claire.github-309c@sitedethib.com>
3 months ago
JS Moore 541cbdd963
Add env variable support for number of followable hashtags in feed column (#2500)
* Add env variable support for number of followable hashtags in feed column.

* Add a note about performance concerns for higher values.

See discussion in https://github.com/glitch-soc/mastodon/pull/2500

* Update .devcontainer/docker-compose.yml

---------

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
3 months ago
Essem 08e511cecb
Fix doodle modal icons (#2597) 3 months ago
Jeremy Kescher a08e32d1e1
Merge branch 'glitch-soc' into develop 3 months ago
Claire 5bc39b3196 Fix build-security docker tags 3 months ago
Claire c3936cbbe3 Temporary hack to correctly tag the security docker image… 3 months ago

@ -251,6 +251,11 @@ SMTP_FROM_ADDRESS=notifications@example.com
# Maximum allowed character count
MAX_TOOT_CHARS=500
# Maximum allowed hashtags to follow in a feed column
# Note that setting this value higher may cause significant
# database load
MAX_FEED_HASHTAGS=4
# Maximum number of pinned posts
MAX_PINNED_TOOTS=5

@ -36,7 +36,7 @@ jobs:
tags: |
type=raw,value=edge
type=raw,value=nightly
type=schedule,pattern=${{ needs.compute-suffix.outputs.prerelease }}
type=raw,value=${{ needs.compute-suffix.outputs.prerelease }}
secrets: inherit
build-image-streaming:
@ -57,5 +57,5 @@ jobs:
tags: |
type=raw,value=edge
type=raw,value=nightly
type=schedule,pattern=${{ needs.compute-suffix.outputs.prerelease }}
type=raw,value=${{ needs.compute-suffix.outputs.prerelease }}
secrets: inherit

@ -2,6 +2,101 @@
All notable changes to this project will be documented in this file.
## [4.2.7] - 2024-02-16
### Fixed
- Fix OmniAuth tests and edge cases in error handling ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29201), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/29207))
- Fix new installs by upgrading to the latest release of the `nsa` gem, instead of a no longer existing commit ([mjankowski](https://github.com/mastodon/mastodon/pull/29065))
### Security
- Fix insufficient checking of remote posts ([GHSA-jhrq-qvrm-qr36](https://github.com/mastodon/mastodon/security/advisories/GHSA-jhrq-qvrm-qr36))
## [4.2.6] - 2024-02-14
### Security
- Update the `sidekiq-unique-jobs` dependency (see [GHSA-cmh9-rx85-xj38](https://github.com/mhenrixon/sidekiq-unique-jobs/security/advisories/GHSA-cmh9-rx85-xj38))
In addition, we have disabled the web interface for `sidekiq-unique-jobs` out of caution.
If you need it, you can re-enable it by setting `ENABLE_SIDEKIQ_UNIQUE_JOBS_UI=true`.
If you only need to clear all locks, you can now use `bundle exec rake sidekiq_unique_jobs:delete_all_locks`.
- Update the `nokogiri` dependency (see [GHSA-xc9x-jj77-9p9j](https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-xc9x-jj77-9p9j))
- Disable administrative Doorkeeper routes ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/29187))
- Fix ongoing streaming sessions not being invalidated when applications get deleted in some cases ([GHSA-7w3c-p9j8-mq3x](https://github.com/mastodon/mastodon/security/advisories/GHSA-7w3c-p9j8-mq3x))
In some rare cases, the streaming server was not notified of access tokens revocation on application deletion.
- Change external authentication behavior to never reattach a new identity to an existing user by default ([GHSA-vm39-j3vx-pch3](https://github.com/mastodon/mastodon/security/advisories/GHSA-vm39-j3vx-pch3))
Up until now, Mastodon has allowed new identities from external authentication providers to attach to an existing local user based on their verified e-mail address.
This allowed upgrading users from a database-stored password to an external authentication provider, or move from one authentication provider to another.
However, this behavior may be unexpected, and means that when multiple authentication providers are configured, the overall security would be that of the least secure authentication provider.
For these reasons, this behavior is now locked under the `ALLOW_UNSAFE_AUTH_PROVIDER_REATTACH` environment variable.
In addition, regardless of this environment variable, Mastodon will refuse to attach two identities from the same authentication provider to the same account.
## [4.2.5] - 2024-02-01
### Security
- Fix insufficient origin validation (CVE-2024-23832, [GHSA-3fjr-858r-92rw](https://github.com/mastodon/mastodon/security/advisories/GHSA-3fjr-858r-92rw))
## [4.2.4] - 2024-01-24
### Fixed
- Fix error when processing remote files with unusually long names ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28823))
- Fix processing of compacted single-item JSON-LD collections ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28816))
- Retry 401 errors on replies fetching ([ShadowJonathan](https://github.com/mastodon/mastodon/pull/28788))
- Fix `RecordNotUnique` errors in LinkCrawlWorker ([tribela](https://github.com/mastodon/mastodon/pull/28748))
- Fix Mastodon not correctly processing HTTP Signatures with query strings ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28443), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/28476))
- Fix potential redirection loop of streaming endpoint ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28665))
- Fix streaming API redirection ignoring the port of `streaming_api_base_url` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28558))
- Fix error when processing link preview with an array as `inLanguage` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28252))
- Fix unsupported time zone or locale preventing sign-up ([Gargron](https://github.com/mastodon/mastodon/pull/28035))
- Fix "Hide these posts from home" list setting not refreshing when switching lists ([brianholley](https://github.com/mastodon/mastodon/pull/27763))
- Fix missing background behind dismissable banner in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/27479))
- Fix line wrapping of language selection button with long locale codes ([gunchleoc](https://github.com/mastodon/mastodon/pull/27100), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27127))
- Fix `Undo Announce` activity not being sent to non-follower authors ([MitarashiDango](https://github.com/mastodon/mastodon/pull/18482))
- Fix N+1s because of association preloaders not actually getting called ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28339))
- Fix empty column explainer getting cropped under certain conditions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28337))
- Fix `LinkCrawlWorker` error when encountering empty OEmbed response ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28268))
- Fix call to inefficient `delete_matched` cache method in domain blocks ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28367))
### Security
- Add rate-limit of TOTP authentication attempts at controller level ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28801))
## [4.2.3] - 2023-12-05
### Fixed
- Fix dependency on `json-canonicalization` version that has been made unavailable since last release
## [4.2.2] - 2023-12-04
### Changed
- Change dismissed banners to be stored server-side ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27055))
- Change GIF max matrix size error to explicitly mention GIF files ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27927))
- Change `Follow` activities delivery to bypass availability check ([ShadowJonathan](https://github.com/mastodon/mastodon/pull/27586))
- Change single-column navigation notice to be displayed outside of the logo container ([renchap](https://github.com/mastodon/mastodon/pull/27462), [renchap](https://github.com/mastodon/mastodon/pull/27476))
- Change Content-Security-Policy to be tighter on media paths ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26889))
- Change post language code to include country code when relevant ([gunchleoc](https://github.com/mastodon/mastodon/pull/27099), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27207))
### Fixed
- Fix upper border radius of onboarding columns ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27890))
- Fix incoming status creation date not being restricted to standard ISO8601 ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27655), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/28081))
- Fix some posts from threads received out-of-order sometimes not being inserted into timelines ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27653))
- Fix posts from force-sensitized accounts being able to trend ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27620))
- Fix error when trying to delete already-deleted file with OpenStack Swift ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27569))
- Fix batch attachment deletion when using OpenStack Swift ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27554))
- Fix processing LDSigned activities from actors with unknown public keys ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27474))
- Fix error and incorrect URLs in `/api/v1/accounts/:id/featured_tags` for remote accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27459))
- Fix report processing notice not mentioning the report number when performing a custom action ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27442))
- Fix handling of `inLanguage` attribute in preview card processing ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27423))
- Fix own posts being removed from home timeline when unfollowing a used hashtag ([kmycode](https://github.com/mastodon/mastodon/pull/27391))
- Fix some link anchors being recognized as hashtags ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27271), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27584))
- Fix format-dependent redirects being cached regardless of requested format ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27634))
## [4.2.1] - 2023-10-10
### Added

@ -26,7 +26,7 @@ gem 'blurhash', '~> 0.1'
gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.8'
gem 'bootsnap', '~> 1.17.0', require: false
gem 'bootsnap', '~> 1.18.0', require: false
gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'chewy', '~> 7.3'
@ -63,7 +63,7 @@ gem 'kaminari', '~> 1.2'
gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.5.0', require: 'mime/types/columnar'
gem 'nokogiri', '~> 1.15'
gem 'nsa', github: 'jhawthorn/nsa', ref: 'e020fcc3a54d993ab45b7194d89ab720296c111b'
gem 'nsa'
gem 'oj', '~> 3.14'
gem 'ox', '~> 2.14'
gem 'parslet'

@ -7,17 +7,6 @@ GIT
hkdf (~> 0.2)
jwt (~> 2.0)
GIT
remote: https://github.com/jhawthorn/nsa.git
revision: e020fcc3a54d993ab45b7194d89ab720296c111b
ref: e020fcc3a54d993ab45b7194d89ab720296c111b
specs:
nsa (0.2.8)
activesupport (>= 4.2, < 7.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0)
GEM
remote: https://rubygems.org/
specs:
@ -155,9 +144,9 @@ GEM
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
blurhash (0.1.7)
bootsnap (1.17.1)
bootsnap (1.18.3)
msgpack (~> 1.2)
brakeman (6.1.1)
brakeman (6.1.2)
racc
browser (5.3.1)
brpoplpush-redis_script (0.1.3)
@ -167,11 +156,11 @@ GEM
bundler-audit (0.9.1)
bundler (>= 1.2.0, < 3)
thor (~> 1.0)
capybara (3.39.2)
capybara (3.40.0)
addressable
matrix
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
nokogiri (~> 1.11)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
@ -180,7 +169,7 @@ GEM
activesupport
cbor (0.5.9.6)
charlock_holmes (0.7.7)
chewy (7.5.0)
chewy (7.5.1)
activesupport (>= 5.2)
elasticsearch (>= 7.12.0, < 7.14.0)
elasticsearch-dsl
@ -193,7 +182,8 @@ GEM
cose (1.3.0)
cbor (~> 0.5.9)
openssl-signature_algorithm (~> 1.0)
crack (0.4.5)
crack (0.4.6)
bigdecimal
rexml
crass (1.0.6)
css_parser (1.14.0)
@ -319,13 +309,13 @@ GEM
activesupport (>= 5.1)
haml (>= 4.0.6)
railties (>= 5.1)
haml_lint (0.55.0)
haml_lint (0.56.0)
haml (>= 5.0)
parallel (~> 1.10)
rainbow
rubocop (>= 1.0)
sysexits (~> 1.1)
hashdiff (1.0.1)
hashdiff (1.1.0)
hashie (5.0.0)
hcaptcha (7.1.0)
json
@ -361,7 +351,7 @@ GEM
terminal-table (>= 1.5.1)
idn-ruby (0.1.5)
io-console (0.7.2)
irb (1.11.1)
irb (1.11.2)
rdoc
reline (>= 0.4.2)
jmespath (1.6.2)
@ -465,9 +455,14 @@ GEM
net-smtp (0.4.0.1)
net-protocol
nio4r (2.5.9)
nokogiri (1.16.0)
nokogiri (1.16.2)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nsa (0.3.0)
activesupport (>= 4.2, < 7.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0)
oj (3.16.3)
bigdecimal (>= 3.0)
omniauth (2.1.1)
@ -510,8 +505,8 @@ GEM
parslet (2.0.0)
pastel (0.8.0)
tty-color (~> 0.5)
pg (1.5.4)
pghero (3.4.0)
pg (1.5.5)
pghero (3.4.1)
activerecord (>= 6)
posix-spawn (0.3.15)
premailer (1.21.0)
@ -712,7 +707,7 @@ GEM
rufus-scheduler (~> 3.2)
sidekiq (>= 6, < 8)
tilt (>= 1.4.0)
sidekiq-unique-jobs (7.1.31)
sidekiq-unique-jobs (7.1.33)
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
concurrent-ruby (~> 1.0, >= 1.0.5)
redis (< 5.0)
@ -771,7 +766,7 @@ GEM
unf (~> 0.1.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2023.4)
tzinfo-data (1.2024.1)
tzinfo (>= 1.0.0)
unf (0.1.4)
unf_ext
@ -798,7 +793,7 @@ GEM
webfinger (1.2.0)
activesupport
httpclient (>= 2.4)
webmock (3.19.1)
webmock (3.20.0)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
@ -829,7 +824,7 @@ DEPENDENCIES
better_errors (~> 2.9)
binding_of_caller (~> 1.0)
blurhash (~> 0.1)
bootsnap (~> 1.17.0)
bootsnap (~> 1.18.0)
brakeman (~> 6.0)
browser
bundler-audit (~> 0.9)
@ -886,7 +881,7 @@ DEPENDENCIES
net-http (~> 0.4.0)
net-ldap (~> 0.18)
nokogiri (~> 1.15)
nsa!
nsa
oj (~> 3.14)
omniauth (~> 2.0)
omniauth-cas (~> 3.0.0.beta.1)

@ -7,7 +7,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def self.provides_callback_for(provider)
define_method provider do
@provider = provider
@user = User.find_for_oauth(request.env['omniauth.auth'], current_user)
@user = User.find_for_omniauth(request.env['omniauth.auth'], current_user)
if @user.persisted?
record_login_activity
@ -17,6 +17,9 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
session["devise.#{provider}_data"] = request.env['omniauth.auth']
redirect_to new_user_registration_url
end
rescue ActiveRecord::RecordInvalid
flash[:alert] = I18n.t('devise.failure.omniauth_user_creation_failure') if is_navigational_format?
redirect_to new_user_session_url
end
end

@ -174,7 +174,19 @@ module JsonLdHelper
build_request(uri, on_behalf_of, options: request_options).perform do |response|
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
body_to_json(response.body_with_limit) if response.code == 200
body_to_json(response.body_with_limit) if response.code == 200 && valid_activitypub_content_type?(response)
end
end
def valid_activitypub_content_type?(response)
return true if response.mime_type == 'application/activity+json'
# When the mime type is `application/ld+json`, we need to check the profile,
# but `http.rb` does not parse it for us.
return false unless response.mime_type == 'application/ld+json'
response.headers[HTTP::Headers::CONTENT_TYPE]&.split(';')&.map(&:strip)&.any? do |str|
str.start_with?('profile="') && str[9...-1].split.include?('https://www.w3.org/ns/activitystreams')
end
end

@ -338,7 +338,7 @@ class StatusActionBar extends ImmutablePureComponent {
onClick={this.handleNoOp} // EmojiPickerDropdown handles that
title={intl.formatMessage(messages.react)}
disabled={!canReact}
icon='add-reaction'
icon='add_reaction'
iconComponent={AddReactionIcon}
/>
);

@ -131,7 +131,7 @@ export default class StatusPrepend extends PureComponent {
iconComponent = StarIcon;
break;
case 'reaction':
iconId = 'add-reaction';
iconId = 'add_reaction';
iconComponent = AddReactionIcon;
break;
case 'featured':

@ -15,7 +15,7 @@ export default class AutosuggestAccount extends ImmutablePureComponent {
return (
<div className='autosuggest-account' title={account.get('acct')}>
<div className='autosuggest-account-icon'><Avatar account={account} size={24} /></div>
<div className='autosuggest-account-icon'><Avatar account={account} size={18} /></div>
<DisplayName account={account} inline />
</div>
);

@ -70,7 +70,6 @@ class ComposeForm extends ImmutablePureComponent {
singleColumn: PropTypes.bool,
lang: PropTypes.string,
advancedOptions: ImmutablePropTypes.map,
layout: PropTypes.string,
media: ImmutablePropTypes.list,
sideArm: PropTypes.string,
sensitive: PropTypes.bool,
@ -259,7 +258,6 @@ class ComposeForm extends ImmutablePureComponent {
intl,
advancedOptions,
isSubmitting,
layout,
onChangeSpoilerness,
onPaste,
privacy,
@ -313,7 +311,7 @@ class ComposeForm extends ImmutablePureComponent {
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
onPaste={onPaste}
autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
autoFocus={!showSearch && !isMobile(window.innerWidth)}
lang={this.props.lang}
>
<TextareaIcons advancedOptions={advancedOptions} />

@ -38,8 +38,8 @@ export default class NavigationBar extends ImmutablePureComponent {
{ profileLink !== undefined && (
<a
className='edit'
href={profileLink}
className='navigation-bar__profile-edit'
><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
)}
</div>

@ -76,7 +76,7 @@ class SearchResults extends ImmutablePureComponent {
return (
<div className='drawer--results'>
<div className='search-results'>
<header className='search-results__header'>
<Icon id='search' icon={SearchIcon} />
<FormattedMessage id='explore.search_results' defaultMessage='Search results' />

@ -71,7 +71,6 @@ const mapStateToProps = state => ({
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
lang: state.getIn(['compose', 'language']),
advancedOptions: state.getIn(['compose', 'advanced_options']),
layout: state.getIn(['local_settings', 'layout']),
media: state.getIn(['compose', 'media_attachments']),
sideArm: sideArmPrivacy(state),
sensitive: state.getIn(['compose', 'sensitive']),

@ -9,6 +9,8 @@ import { NonceProvider } from 'react-select';
import AsyncSelect from 'react-select/async';
import Toggle from 'react-toggle';
import { maxFeedHashtags } from 'flavours/glitch/initial_state';
import SettingToggle from '../../notifications/components/setting_toggle';
const messages = defineMessages({
@ -46,9 +48,9 @@ class ColumnSettings extends PureComponent {
onSelect = mode => value => {
const oldValue = this.tags(mode);
// Prevent changes that add more than 4 tags, but allow removing
// tags that were already added before
if ((value.length > 4) && !(value < oldValue)) {
// Prevent changes that add more than the number of configured
// tags, but allow removing tags that were already added before
if ((value.length > maxFeedHashtags) && !(value < oldValue)) {
return;
}

@ -68,7 +68,7 @@ class NewListForm extends PureComponent {
<Button
disabled={disabled || !value}
title={title}
text={title}
onClick={this.handleClick}
/>
</form>

@ -89,7 +89,7 @@ class FilterBar extends PureComponent {
onClick={this.onClick('reaction')}
title={intl.formatMessage(tooltips.reactions)}
>
<Icon id='add-reaction' icon={AddReactionIcon} />
<Icon id='add_reaction' icon={AddReactionIcon} />
</button>
<button
className={selectedFilter === 'reblog' ? 'active' : ''}

@ -244,11 +244,11 @@ class ActionBar extends PureComponent {
const canReact = signedIn && status.get('reactions').filter(r => r.get('count') > 0 && r.get('me')).size < maxReactions;
const reactButton = (
<IconButton
className='add-reaction'
className='add-reaction-icon'
onClick={this.handleNoOp} // EmojiPickerDropdown handles that
title={intl.formatMessage(messages.react)}
disabled={!canReact}
icon='add-reaction'
icon='add_reaction'
iconComponent={AddReactionIcon}
/>
);

@ -9,6 +9,10 @@ import { connect } from 'react-redux';
import Atrament from 'atrament'; // the doodling library
import { debounce, mapValues } from 'lodash';
import ColorsIcon from '@/material-icons/400-24px/colors.svg?react';
import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
import UndoIcon from '@/material-icons/400-24px/undo.svg?react';
import { doodleSet, uploadCompose } from 'flavours/glitch/actions/compose';
import { Button } from 'flavours/glitch/components/button';
import { IconButton } from 'flavours/glitch/components/icon_button';
@ -584,10 +588,10 @@ class DoodleModal extends ImmutablePureComponent {
</div>
</div>
<div className='doodle-toolbar'>
<IconButton icon='pencil' title='Draw' label='Draw' onClick={this.setModeDraw} size={18} active={this.mode === 'draw'} inverted />
<IconButton icon='bath' title='Fill' label='Fill' onClick={this.setModeFill} size={18} active={this.mode === 'fill'} inverted />
<IconButton icon='undo' title='Undo' label='Undo' onClick={this.undo} size={18} inverted />
<IconButton icon='trash' title='Clear' label='Clear' onClick={this.handleClearBtn} size={18} inverted />
<IconButton icon='pencil' iconComponent={EditIcon} title='Draw' label='Draw' onClick={this.setModeDraw} size={18} active={this.mode === 'draw'} inverted />
<IconButton icon='bath' iconComponent={ColorsIcon} title='Fill' label='Fill' onClick={this.setModeFill} size={18} active={this.mode === 'fill'} inverted />
<IconButton icon='undo' iconComponent={UndoIcon} title='Undo' label='Undo' onClick={this.undo} size={18} inverted />
<IconButton icon='trash' iconComponent={DeleteIcon} title='Clear' label='Clear' onClick={this.handleClearBtn} size={18} inverted />
</div>
<div className='doodle-palette'>
{

@ -602,18 +602,7 @@ class UI extends PureComponent {
const { draggingOver } = this.state;
const { children, isWide, location, dropdownMenuIsOpen, layout, moved } = this.props;
const columnsClass = layout => {
switch (layout) {
case 'single':
return 'single-column';
case 'multiple':
return 'multi-columns';
default:
return 'auto-columns';
}
};
const className = classNames('ui', columnsClass(layout), {
const className = classNames('ui', {
'wide': isWide,
'system-font': this.props.systemFontUi,
'hicolor-privacy-icons': this.props.hicolorPrivacyIcons,

@ -78,6 +78,7 @@ export const hasMultiColumnPath = initialPath === '/'
* @property {InitialStateMeta} meta
* @property {object} local_settings
* @property {number} max_toot_chars
* @property {number} max_feed_hashtags
* @property {PollLimits} poll_limits
* @property {number} max_reactions
*/
@ -144,6 +145,7 @@ export const sso_redirect = getMeta('sso_redirect');
// Glitch-soc-specific settings
export const maxChars = (initialState && initialState.max_toot_chars) || 500;
export const maxFeedHashtags = (initialState && initialState.max_feed_hashtags) || 4;
export const favouriteModal = getMeta('favourite_modal');
export const pollLimits = (initialState && initialState.poll_limits);
export const defaultContentType = getMeta('default_content_type');

@ -1,4 +1,51 @@
{
"about.fork_disclaimer": "Glitch-soc adalah perangkat lunak sumber terbuka yang merupakan fork dari Mastodon.",
"account.disclaimer_full": "Informasi di bawah ini mungkin tidak mencerminkan profil pengguna secara lengkap.",
"account.follows": "Mengikuti",
"account.joined": "Bergabung {date}",
"account.suspended_disclaimer_full": "Pengguna ini telah ditangguhkan oleh moderator.",
"account.view_full_profile": "Tampilkan profil lengkap",
"advanced_options.icon_title": "Opsi lanjutan",
"advanced_options.local-only.long": "Jangan mengunggah ke instance lain",
"advanced_options.local-only.short": "Hanya lokal",
"advanced_options.local-only.tooltip": "Postingan ini hanya untuk lokal",
"advanced_options.threaded_mode.long": "Secara otomatis membuka balasan pada postingan",
"advanced_options.threaded_mode.short": "Mode Utasan",
"advanced_options.threaded_mode.tooltip": "Mode utasan dinyalakan",
"boost_modal.missing_description": "Toot ini berisi beberapa media tanpa deskripsi",
"column.favourited_by": "Disukai oleh",
"column.heading": "Lainnya",
"column.reblogged_by": "Dibagikan oleh",
"column.subheading": "Opsi lain-lain",
"column_header.profile": "Profil",
"column_subheading.lists": "Daftar",
"column_subheading.navigation": "Penelusuran",
"community.column_settings.allow_local_only": "Tampilkan toot lokal saja",
"compose.attach": "Lampirkan...",
"compose.attach.doodle": "Gambar sesuatu",
"compose.attach.upload": "Unggah file",
"compose.content-type.html": "HTML",
"compose.content-type.markdown": "Bahasa Markdown",
"compose.content-type.plain": "Teks biasa",
"compose_form.poll.multiple_choices": "Izinkan beberapa pilihan",
"compose_form.poll.single_choice": "Izinkan hanya satu pilihan",
"compose_form.spoiler": "Sembunyikan teks di balik peringatan",
"confirmation_modal.do_not_ask_again": "Jangan minta konfirmasi lagi",
"confirmations.deprecated_settings.confirm": "Gunakan preferensi Mastodon",
"confirmations.deprecated_settings.message": "Beberapa {app_settings} khusus perangkat Glitch-soc yang Anda gunakan telah digantikan oleh {preferences} Mastodon dan akan diganti:",
"confirmations.missing_media_description.confirm": "Tetap kirim",
"confirmations.missing_media_description.edit": "Sunting media",
"confirmations.missing_media_description.message": "Setidaknya satu lampiran media tidak memiliki deskripsi. Pertimbangkan untuk mendeskripsikan semua lampiran media untuk pengguna tunanetra sebelum mengirim toot Anda.",
"confirmations.unfilter.author": "Penulis",
"confirmations.unfilter.confirm": "Tampilkan",
"confirmations.unfilter.edit_filter": "Ubah saringan",
"content-type.change": "Jenis konten",
"direct.group_by_conversations": "Grupkan berdasarkan percakapan",
"endorsed_accounts_editor.endorsed_accounts": "Akun pilihan",
"favourite_modal.combo": "Anda dapat menekan {combo} untuk melewati ini lain kali",
"firehose.column_settings.allow_local_only": "Tampilkan postingan khusus lokal di \"Semua\"",
"home.column_settings.advanced": "Lanjutan",
"home.column_settings.filter_regex": "Saring dengan ekspresi reguler",
"settings.content_warnings": "Content warnings",
"settings.preferences": "Preferences"
}

@ -1,4 +1,52 @@
{
"account.follows": "Följer",
"account.joined": "Gick med {date}",
"account.suspended_disclaimer_full": "Denna användare har stängts av av en moderator.",
"account.view_full_profile": "Visa full profil",
"advanced_options.icon_title": "Avancerade inställningar",
"advanced_options.local-only.long": "Lägg inte ut på andra instanser",
"advanced_options.local-only.short": "Endast lokalt",
"advanced_options.local-only.tooltip": "Detta inlägg är endast tillgängligt lokalt",
"advanced_options.threaded_mode.long": "Öppnar automatiskt ett svar vid publicering",
"advanced_options.threaded_mode.short": "Tråd-läge",
"advanced_options.threaded_mode.tooltip": "Tråd-läge på",
"boost_modal.missing_description": "Denna toot innehåller viss media utan beskrivning",
"column.favourited_by": "Favoritmarkerad av",
"column.heading": "Övrigt",
"column.reblogged_by": "Boostad av",
"column.subheading": "Övriga val",
"column_header.profile": "Profil",
"column_subheading.lists": "Listor",
"column_subheading.navigation": "Navigering",
"community.column_settings.allow_local_only": "Visa endast lokala toots",
"compose.attach": "Bifoga...",
"compose.attach.doodle": "Rita något",
"compose.attach.upload": "Ladda upp en fil",
"compose.content-type.html": "HTML",
"compose.content-type.markdown": "Markdown",
"compose.content-type.plain": "Klartext",
"compose_form.poll.multiple_choices": "Tillåt flera val",
"compose_form.poll.single_choice": "Tillåt ett val",
"compose_form.spoiler": "Göm text bakom varning",
"confirmation_modal.do_not_ask_again": "Fråga mig inte igen",
"confirmations.deprecated_settings.confirm": "Använd Mastodon-preferenser",
"confirmations.deprecated_settings.message": "Några av de glitch-soc-enhetsspecifika {app_settings} som du använder har ersatts av Mastodon-{preferences} och kommer att åsidosättas:",
"confirmations.missing_media_description.confirm": "Lägg ut ändå",
"confirmations.missing_media_description.edit": "Redigera media",
"confirmations.missing_media_description.message": "Minst en mediebilaga saknar beskrivning. Överväg att beskriva all media för synskadade innan du skickar din toot.",
"confirmations.unfilter.author": "Användare",
"confirmations.unfilter.confirm": "Visa",
"confirmations.unfilter.edit_filter": "Redigera filter",
"confirmations.unfilter.filters": "Matchande {count, plural, one {filter} other {filters}}",
"content-type.change": "Innehållstyp",
"direct.group_by_conversations": "Sortera efter konversation",
"endorsed_accounts_editor.endorsed_accounts": "Utvalda konton",
"favourite_modal.combo": "Du kan trycka på {combo} för att skippa detta nästa gång",
"firehose.column_settings.allow_local_only": "Visa endast lokala inlägg i \"Alla\"",
"home.column_settings.advanced": "Avancerat",
"home.column_settings.filter_regex": "Filtrera bort med reguljära uttryck",
"home.column_settings.show_direct": "Visa privata omnämningar",
"home.settings": "Kolumninställningar",
"settings.content_warnings": "Content warnings",
"settings.preferences": "Preferences"
}

@ -14,9 +14,7 @@ const initialState = ImmutableMap({
export default function meta(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE:
return state.merge(action.state.get('meta'))
.set('permissions', action.state.getIn(['role', 'permissions']))
.set('layout', layoutFromWindow(action.state.getIn(['local_settings', 'layout'])));
return state.merge(action.state.get('meta')).set('permissions', action.state.getIn(['role', 'permissions']));
case changeLayout.type:
return state.set('layout', action.payload.layout);
default:

@ -10,37 +10,6 @@
background-size: $size $size;
}
@mixin single-column($media, $parent: '&') {
.auto-columns #{$parent} {
@media #{$media} {
@content;
}
}
.single-column #{$parent} {
@content;
}
}
@mixin limited-single-column($media, $parent: '&') {
.auto-columns #{$parent},
.single-column #{$parent} {
@media #{$media} {
@content;
}
}
}
@mixin multi-columns($media, $parent: '&') {
.auto-columns #{$parent} {
@media #{$media} {
@content;
}
}
.multi-columns #{$parent} {
@content;
}
}
@mixin fullwidth-gallery {
&.full-width {
margin-left: -14px;

File diff suppressed because it is too large Load Diff

@ -1,298 +0,0 @@
.image {
position: relative;
overflow: hidden;
&__preview {
position: absolute;
top: 0;
inset-inline-start: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
&.loaded &__preview {
display: none;
}
img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
border: 0;
background: transparent;
opacity: 0;
}
&.loaded img {
opacity: 1;
}
}
.link-footer {
flex: 0 0 auto;
padding: 10px;
padding-top: 20px;
z-index: 1;
font-size: 13px;
p {
color: $dark-text-color;
margin-bottom: 20px;
.version {
white-space: nowrap;
}
strong {
font-weight: 500;
}
a {
color: $dark-text-color;
text-decoration: underline;
&:hover,
&:focus,
&:active {
text-decoration: none;
}
}
}
}
.about {
padding: 20px;
@media screen and (min-width: $no-gap-breakpoint) {
border-radius: 4px;
}
&__footer {
color: $dark-text-color;
text-align: center;
font-size: 15px;
line-height: 22px;
margin-top: 20px;
}
&__header {
margin-bottom: 30px;
&__hero {
width: 100%;
height: auto;
aspect-ratio: 1.9;
background: lighten($ui-base-color, 4%);
border-radius: 8px;
margin-bottom: 30px;
}
h1,
p {
text-align: center;
}
h1 {
font-size: 24px;
line-height: 1.5;
font-weight: 700;
margin-bottom: 10px;
}
p {
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: $darker-text-color;
}
}
&__meta {
background: lighten($ui-base-color, 4%);
border-radius: 4px;
display: flex;
margin-bottom: 30px;
font-size: 15px;
&__column {
box-sizing: border-box;
width: 50%;
padding: 20px;
}
&__divider {
width: 0;
border: 0;
border-style: solid;
border-color: lighten($ui-base-color, 8%);
border-left-width: 1px;
min-height: calc(100% - 60px);
flex: 0 0 auto;
}
h4 {
font-size: 15px;
text-transform: uppercase;
color: $darker-text-color;
font-weight: 500;
margin-bottom: 20px;
}
@media screen and (width <= 600px) {
display: block;
h4 {
text-align: center;
}
&__column {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
&__divider {
min-height: 0;
width: 100%;
border-left-width: 0;
border-top-width: 1px;
}
}
.layout-multiple-columns & {
display: block;
h4 {
text-align: center;
}
&__column {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
&__divider {
min-height: 0;
width: 100%;
border-left-width: 0;
border-top-width: 1px;
}
}
}
&__mail {
color: $primary-text-color;
text-decoration: none;
font-weight: 500;
&:hover,
&:focus,
&:active {
text-decoration: underline;
}
}
.link-footer {
padding: 0;
margin-top: 60px;
text-align: center;
font-size: 15px;
line-height: 22px;
@media screen and (min-width: $no-gap-breakpoint) {
display: none;
}
}
.account {
padding: 0;
border: 0;
}
.account__avatar-wrapper {
margin-inline-start: 0;
}
.account__relationship {
display: none;
}
&__section {
margin-bottom: 10px;
&__title {
display: flex;
align-items: center;
gap: 6px;
font-size: 17px;
font-weight: 600;
line-height: 22px;
padding: 20px;
border-radius: 4px;
background: lighten($ui-base-color, 4%);
color: $highlight-text-color;
cursor: pointer;
}
&.active &__title {
border-radius: 4px 4px 0 0;
}
&__body {
border: 1px solid lighten($ui-base-color, 4%);
border-top: 0;
padding: 20px;
font-size: 15px;
line-height: 22px;
}
}
&__domain-blocks {
margin-top: 30px;
background: darken($ui-base-color, 4%);
border: 1px solid lighten($ui-base-color, 4%);
border-radius: 4px;
&__domain {
border-bottom: 1px solid lighten($ui-base-color, 4%);
padding: 10px;
font-size: 15px;
color: $darker-text-color;
&:nth-child(2n) {
background: darken($ui-base-color, 2%);
}
&:last-child {
border-bottom: 0;
}
&__header {
display: flex;
gap: 10px;
justify-content: space-between;
font-weight: 500;
margin-bottom: 4px;
}
h6 {
color: $secondary-text-color;
font-size: inherit;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
p {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}

@ -1,845 +0,0 @@
.account {
padding: 10px;
border-bottom: 1px solid lighten($ui-base-color, 8%);
color: inherit;
text-decoration: none;
.account__display-name {
flex: 1 1 auto;
display: flex;
align-items: center;
gap: 10px;
color: $darker-text-color;
overflow: hidden;
text-decoration: none;
font-size: 14px;
.display-name {
margin-bottom: 4px;
}
.display-name strong {
display: inline;
}
}
&--minimal {
.account__display-name {
.display-name {
margin-bottom: 0;
}
.display-name strong {
display: block;
}
}
}
&__note {
font-size: 14px;
font-weight: 400;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
margin-top: 10px;
color: $darker-text-color;
&--missing {
color: $dark-text-color;
}
p {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
a {
color: inherit;
&:hover,
&:focus,
&:active {
text-decoration: none;
}
}
}
}
.account__wrapper {
display: flex;
gap: 10px;
align-items: center;
}
.account__avatar-wrapper {
float: left;
}
.account__avatar {
@include avatar-radius;
display: block;
position: relative;
overflow: hidden;
img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
&-inline {
display: inline-block;
vertical-align: middle;
margin-inline-end: 5px;
}
&-composite {
@include avatar-radius;
overflow: hidden;
position: relative;
& > div {
@include avatar-radius;
float: left;
position: relative;
box-sizing: border-box;
}
.account__avatar {
width: 100% !important;
height: 100% !important;
}
&__label {
display: block;
position: absolute;
top: 50%;
inset-inline-start: 50%;
transform: translate(-50%, -50%);
color: $primary-text-color;
text-shadow: 1px 1px 2px $base-shadow-color;
font-weight: 700;
font-size: 15px;
}
}
}
.account__avatar-overlay {
position: relative;
&-overlay {
position: absolute;
bottom: 0;
inset-inline-end: 0;
z-index: 1;
}
}
.account__relationship {
white-space: nowrap;
display: flex;
align-items: center;
gap: 4px;
}
.account__header__wrapper {
flex: 0 0 auto;
background: lighten($ui-base-color, 4%);
}
.account__disclaimer {
display: flex;
padding: 10px;
gap: 5px;
color: $dark-text-color;
align-items: center;
strong {
font-weight: 500;
@each $lang in $cjk-langs {
&:lang(#{$lang}) {
font-weight: 700;
}
}
}
a {
font-weight: 500;
color: inherit;
text-decoration: underline;
&:hover,
&:focus,
&:active {
text-decoration: none;
}
}
}
.account__action-bar {
border-top: 1px solid lighten($ui-base-color, 8%);
border-bottom: 1px solid lighten($ui-base-color, 8%);
line-height: 36px;
overflow: hidden;
flex: 0 0 auto;
display: flex;
}
.account__action-bar-links {
display: flex;
flex: 1 1 auto;
line-height: 18px;
text-align: center;
}
.account__action-bar__tab {
text-decoration: none;
overflow: hidden;
flex: 0 1 100%;
border-inline-start: 1px solid lighten($ui-base-color, 8%);
padding: 10px 0;
border-bottom: 4px solid transparent;
&:first-child {
border-inline-start: 0;
}
&.active {
border-bottom: 4px solid $ui-highlight-color;
}
& > span {
display: block;
text-transform: uppercase;
font-size: 11px;
color: $darker-text-color;
}
strong {
display: block;
font-size: 15px;
font-weight: 500;
color: $primary-text-color;
@each $lang in $cjk-langs {
&:lang(#{$lang}) {
font-weight: 700;
}
}
}
abbr {
color: $highlight-text-color;
}
}
.account-authorize {
padding: 14px 10px;
.detailed-status__display-name {
display: block;
margin-bottom: 15px;
overflow: hidden;
}
}
.account-authorize__avatar {
float: left;
margin-inline-end: 10px;
}
.notification__report {
padding: 8px 10px;
padding-inline-start: 68px;
position: relative;
border-bottom: 1px solid lighten($ui-base-color, 8%);
min-height: 54px;
&__details {
display: flex;
justify-content: space-between;
align-items: center;
color: $darker-text-color;
font-size: 15px;
line-height: 22px;
strong {
font-weight: 500;
}
}
&__avatar {
position: absolute;
inset-inline-start: 10px;
top: 10px;
}
}
.notification__message {
margin-inline-start: 42px;
padding-top: 8px;
padding-inline-start: 26px;
cursor: default;
color: $darker-text-color;
font-size: 15px;
position: relative;
align-items: center;
.icon {
color: $highlight-text-color;
width: 18px;
height: 18px;
}
.icon-star {
color: $gold-star;
}
> span {
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
}
.account--panel {
background: lighten($ui-base-color, 4%);
border-top: 1px solid lighten($ui-base-color, 8%);
border-bottom: 1px solid lighten($ui-base-color, 8%);
display: flex;
flex-direction: row;
padding: 10px 0;
}
.account--panel__button,
.detailed-status__button {
flex: 1 1 auto;
text-align: center;
}
.detailed-status__button .emoji-button {
padding: 0;
}
.relationship-tag {
color: $white;
margin-bottom: 4px;
display: block;
background-color: rgba($black, 0.45);
text-transform: uppercase;
font-size: 11px;
font-weight: 500;
padding: 4px;
border-radius: 4px;
opacity: 0.7;
&:hover {
opacity: 1;
}
}
.account-gallery__container {
display: flex;
flex-wrap: wrap;
padding: 4px 2px;
}
.account-gallery__item {
border: 0;
box-sizing: border-box;
display: block;
position: relative;
border-radius: 4px;
overflow: hidden;
margin: 2px;
&__icons {
position: absolute;
top: 50%;
inset-inline-start: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
}
}
.notification__filter-bar,
.account__section-headline {
background: darken($ui-base-color, 4%);
border-bottom: 1px solid lighten($ui-base-color, 8%);
cursor: default;
display: flex;
flex-shrink: 0;
button {
background: transparent;
border: 0;
margin: 0;
}
button,
a {
display: block;
flex: 1 1 auto;
color: $darker-text-color;
padding: 15px 0;
font-size: 14px;
font-weight: 500;
text-align: center;
text-decoration: none;
position: relative;
&.active {
color: $primary-text-color;
&::before {
display: block;
content: '';
position: absolute;
bottom: -1px;
left: 0;
width: 100%;
height: 3px;
border-radius: 4px;
background: $highlight-text-color;
}
}
}
&.directory__section-headline {
background: darken($ui-base-color, 2%);
border-bottom-color: transparent;
a,
button {
&.active {
&::before {
display: none;
}
&::after {
border-color: transparent transparent darken($ui-base-color, 7%);
}
}
}
}
}
.account__moved-note {
padding: 14px 10px;
padding-bottom: 16px;
background: lighten($ui-base-color, 4%);
border-top: 1px solid lighten($ui-base-color, 8%);
border-bottom: 1px solid lighten($ui-base-color, 8%);
&__message {
position: relative;
margin-inline-start: 58px;
color: $dark-text-color;
padding: 8px 0;
padding-top: 0;
padding-bottom: 4px;
font-size: 14px;
> span {
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
}
&__icon-wrapper {
inset-inline-start: -26px;
position: absolute;
}
.detailed-status__display-avatar {
position: relative;
}
.detailed-status__display-name {
margin-bottom: 0;
}
}
.account__header__content {
color: $darker-text-color;
font-size: 14px;
font-weight: 400;
overflow: hidden;
word-break: normal;
word-wrap: break-word;
p {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
a {
color: inherit;
text-decoration: underline;
&:hover {
text-decoration: none;
}
}
}
.account__header {
overflow: hidden;
&.inactive {
opacity: 0.5;
.account__header__image,
.account__avatar {
filter: grayscale(100%);
}
}
&__info {
position: absolute;
top: 10px;
inset-inline-start: 10px;
}
&__image {
overflow: hidden;
height: 145px;
position: relative;
background: darken($ui-base-color, 4%);
img {
object-fit: cover;
display: block;
width: 100%;
height: 100%;
margin: 0;
}
}
&__bar {
position: relative;
background: lighten($ui-base-color, 4%);
padding: 5px;
border-bottom: 1px solid lighten($ui-base-color, 12%);
.avatar {
display: block;
flex: 0 0 auto;
width: 94px;
.account__avatar {
background: darken($ui-base-color, 8%);
border: 2px solid lighten($ui-base-color, 4%);
}
}
}
&__badges {
display: flex;
flex-wrap: wrap;
gap: 8px;
.account-role {
line-height: unset;
}
}
&__tabs {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: 7px 10px;
margin-top: -55px;
gap: 8px;
overflow: hidden;
margin-inline-start: -2px; // aligns the pfp with content below
&__buttons {
display: flex;
align-items: center;
gap: 8px;
padding-top: 55px;
overflow: hidden;
.button {
flex-shrink: 1;
white-space: nowrap;
@media screen and (max-width: $no-gap-breakpoint) {
min-width: 0;
}
}
.icon-button {
border: 1px solid lighten($ui-base-color, 12%);
border-radius: 4px;
box-sizing: content-box;
padding: 5px;
.icon {
width: 24px;
height: 24px;
}
}
}
&__name {
padding: 5px 10px;
.emojione {
width: 22px;
height: 22px;
}
h1 {
font-size: 16px;
line-height: 24px;
color: $primary-text-color;
font-weight: 500;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
small {
display: block;
font-size: 14px;
color: $darker-text-color;
font-weight: 400;
overflow: hidden;
text-overflow: ellipsis;
span {
user-select: all;
}
.icon-lock {
height: 16px;
width: 16px;
position: relative;
top: 3px;
}
}
}
}
.spacer {
flex: 1 1 auto;
}
}
&__bio {
overflow: hidden;
margin: 0 -5px;
.account__header__content {
padding: 20px 15px;
padding-bottom: 5px;
color: $primary-text-color;
}
.account__header__joined {
font-size: 14px;
padding: 5px 15px;
color: $darker-text-color;
.columns-area--mobile & {
padding-inline-start: 20px;
padding-inline-end: 20px;
}
}
.account__header__fields {
margin: 0;
border-top: 1px solid lighten($ui-base-color, 12%);
a {
color: lighten($ui-highlight-color, 8%);
}
dl:first-child .verified {
border-radius: 0 4px 0 0;
}
.icon {
width: 18px;
height: 18px;
vertical-align: middle;
}
dd {
display: flex;
align-items: center;
gap: 4px;
}
.verified a {
color: $valid-value-color;
}
}
}
&__extra {
margin-top: 4px;
&__links {
font-size: 14px;
color: $darker-text-color;
padding: 10px 0;
a {
display: inline-block;
color: $darker-text-color;
text-decoration: none;
padding: 5px 10px;
font-weight: 500;
strong {
font-weight: 700;
color: $primary-text-color;
}
}
}
}
&__account-note {
margin: 0 -5px;
padding: 10px 15px;
display: flex;
flex-direction: column;
font-size: 14px;
font-weight: 400;
border-top: 1px solid lighten($ui-base-color, 12%);
border-bottom: 1px solid lighten($ui-base-color, 12%);
label {
display: block;
font-size: 12px;
font-weight: 500;
color: $darker-text-color;
text-transform: uppercase;
margin-bottom: 5px;
}
&__content {
white-space: pre-wrap;
padding: 10px 0;
}
strong {
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
}
textarea {
display: block;
box-sizing: border-box;
width: calc(100% + 20px);
color: $secondary-text-color;
background: $ui-base-color;
padding: 10px;
margin: 0 -10px;
font-family: inherit;
font-size: 14px;
resize: none;
border: 0;
outline: 0;
border-radius: 4px;
&::placeholder {
color: $dark-text-color;
opacity: 1;
}
}
}
}
.account__contents {
overflow: hidden;
}
.account__details {
display: flex;
flex-wrap: wrap;
column-gap: 1em;
}
.verified-badge {
display: inline-flex;
align-items: center;
color: $valid-value-color;
gap: 4px;
overflow: hidden;
white-space: nowrap;
> span {
overflow: hidden;
text-overflow: ellipsis;
}
a {
color: inherit;
font-weight: 500;
text-decoration: none;
}
.icon {
width: 16px;
height: 16px;
}
}
.moved-account-banner,
.follow-request-banner,
.account-memorial-banner {
padding: 20px;
background: lighten($ui-base-color, 4%);
display: flex;
align-items: center;
flex-direction: column;
&__message {
color: $darker-text-color;
padding: 8px 0;
padding-top: 0;
padding-bottom: 4px;
font-size: 14px;
font-weight: 500;
text-align: center;
margin-bottom: 16px;
}
&__action {
display: flex;
justify-content: space-between;
align-items: center;
gap: 15px;
width: 100%;
}
.detailed-status__display-name {
margin-bottom: 0;
}
}
.follow-request-banner .button {
width: 100%;
}
.account-memorial-banner__message {
margin-bottom: 0;
}

@ -1,233 +0,0 @@
.announcements__item__content {
word-wrap: break-word;
overflow-y: auto;
.emojione {
width: 20px;
height: 20px;
margin: -3px 0 0;
}
p {
margin-bottom: 10px;
white-space: pre-wrap;
&:last-child {
margin-bottom: 0;
}
}
a {
color: $secondary-text-color;
text-decoration: none;
&:hover {
text-decoration: underline;
}
&.mention {
&:hover {
text-decoration: none;
span {
text-decoration: underline;
}
}
}
&.unhandled-link {
color: $highlight-text-color;
}
}
}
.announcements {
background: lighten($ui-base-color, 8%);
font-size: 13px;
display: flex;
align-items: flex-end;
&__mastodon {
width: 124px;
flex: 0 0 auto;
@media screen and (max-width: 124px + 300px) {
display: none;
}
}
&__container {
width: calc(100% - 124px);
flex: 0 0 auto;
position: relative;
@media screen and (max-width: 124px + 300px) {
width: 100%;
}
}
&__item {
box-sizing: border-box;
width: 100%;
padding: 15px;
position: relative;
font-size: 15px;
line-height: 20px;
word-wrap: break-word;
font-weight: 400;
max-height: 50vh;
overflow: hidden;
display: flex;
flex-direction: column;
&__range {
display: block;
font-weight: 500;
margin-bottom: 10px;
padding-inline-end: 18px;
}
&__unread {
position: absolute;
top: 19px;
inset-inline-end: 19px;
display: block;
background: $highlight-text-color;
border-radius: 50%;
width: 0.625rem;
height: 0.625rem;
}
}
&__pagination {
padding: 15px;
color: $darker-text-color;
position: absolute;
bottom: 3px;
inset-inline-end: 0;
}
}
.layout-multiple-columns .announcements__mastodon {
display: none;
}
.layout-multiple-columns .announcements__container {
width: 100%;
}
.reactions-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
margin-top: 15px;
margin-inline-start: -2px;
width: calc(100% - (90px - 33px));
&__item {
flex-shrink: 0;
background: lighten($ui-base-color, 12%);
border: 0;
border-radius: 3px;
margin: 2px;
cursor: pointer;
user-select: none;
padding: 0 6px;
display: flex;
align-items: center;
transition: all 100ms ease-in;
transition-property: background-color, color;
&__emoji {
display: block;
margin: 3px 0;
width: 16px;
height: 16px;
img {
display: block;
margin: 0;
width: 100%;
height: 100%;
min-width: auto;
min-height: auto;
vertical-align: bottom;
object-fit: contain;
}
}
&__count {
display: block;
min-width: 9px;
font-size: 13px;
font-weight: 500;
text-align: center;
margin-inline-start: 6px;
color: $darker-text-color;
}
&:hover,
&:focus,
&:active {
background: lighten($ui-base-color, 16%);
transition: all 200ms ease-out;
transition-property: background-color, color;
&__count {
color: lighten($darker-text-color, 4%);
}
}
&.active {
transition: all 100ms ease-in;
transition-property: background-color, color;
background-color: mix(
lighten($ui-base-color, 12%),
$ui-highlight-color,
80%
);
.reactions-bar__item__count {
color: lighten($highlight-text-color, 8%);
}
}
}
.emoji-picker-dropdown {
margin: 2px;
}
&:hover .emoji-button {
opacity: 0.85;
}
.emoji-button {
color: $darker-text-color;
margin: 0;
font-size: 16px;
width: auto;
flex-shrink: 0;
padding: 0 6px;
height: 22px;
display: flex;
align-items: center;
opacity: 0.5;
transition: all 100ms ease-in;
transition-property: background-color, color;
&:hover,
&:active,
&:focus {
opacity: 1;
color: lighten($darker-text-color, 4%);
transition: all 200ms ease-out;
transition-property: background-color, color;
}
}
&--empty {
.emoji-button {
padding: 0;
}
}
}

@ -1,668 +0,0 @@
.compose-form {
padding: 10px;
.emoji-picker-dropdown {
position: absolute;
top: 0;
inset-inline-end: 0;
::-webkit-scrollbar-track:hover,
::-webkit-scrollbar-track:active {
background-color: rgba($base-overlay-background, 0.3);
}
}
}
.character-counter {
cursor: default;
font-family: $font-sans-serif, sans-serif;
font-size: 14px;
font-weight: 600;
color: $lighter-text-color;
&.character-counter--over {
color: $warning-red;
}
}
.no-reduce-motion .spoiler-input {
transition:
height 0.4s ease,
opacity 0.4s ease;
}
.spoiler-input {
height: 0;
transform-origin: bottom;
opacity: 0;
&.spoiler-input--visible {
height: 36px;
margin-bottom: 11px;
opacity: 1;
}
input {
display: block;
box-sizing: border-box;
margin: 0;
border: 0;
border-radius: 4px;
padding: 10px;
width: 100%;
outline: 0;
color: $inverted-text-color;
background: $simple-background-color;
font-size: 14px;
font-family: inherit;
resize: vertical;
&::placeholder {
color: $dark-text-color;
}
&:focus {
outline: 0;
}
@include single-column('screen and (max-width: 630px)') {
font-size: 16px;
}
}
}
.compose-form__warning {
color: $inverted-text-color;
margin-bottom: 15px;
background: $ui-primary-color;
box-shadow: 0 2px 6px rgba($base-shadow-color, 0.3);
padding: 8px 10px;
border-radius: 4px;
font-size: 13px;
font-weight: 400;
a {
color: $lighter-text-color;
font-weight: 500;
text-decoration: underline;
&:active,
&:focus,
&:hover {
text-decoration: none;
}
}
}
.compose-form__sensitive-button {
padding: 10px;
padding-top: 0;
font-size: 14px;
font-weight: 500;
&.active {
color: $highlight-text-color;
}
input[type='checkbox'] {
appearance: none;
display: inline-block;
position: relative;
border: 1px solid $ui-primary-color;
box-sizing: border-box;
width: 18px;
height: 18px;
flex: 0 0 auto;
margin-inline-start: 5px;
margin-inline-end: 10px;
top: -1px;
border-radius: 4px;
vertical-align: middle;
cursor: inherit;
&:checked {
border-color: $highlight-text-color;
background: $highlight-text-color
url("data:image/svg+xml;utf8,<svg width='18' height='18' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M4.5 8.5L8 12l6-6' stroke='white' stroke-width='1.5'/></svg>")
center center no-repeat;
}
}
}
.reply-indicator {
margin: 0 0 10px;
border-radius: 4px;
padding: 10px;
background: $ui-primary-color;
min-height: 23px;
overflow-y: auto;
flex: 0 2 auto;
}
.reply-indicator__header {
margin-bottom: 5px;
overflow: hidden;
}
.reply-indicator__cancel {
float: right;
line-height: 24px;
}
.reply-indicator__display-name {
color: $inverted-text-color;
display: block;
max-width: 100%;
line-height: 24px;
overflow: hidden;
text-decoration: none;
& > .display-name {
line-height: unset;
height: unset;
}
}
.reply-indicator__display-avatar {
float: left;
margin-inline-end: 5px;
}
.reply-indicator__content {
position: relative;
font-size: 14px;
line-height: 20px;
word-wrap: break-word;
font-weight: 400;
overflow: hidden;
padding-top: 5px;
color: $inverted-text-color;
white-space: pre-wrap;
p,
pre {
margin-bottom: 20px;
white-space: pre-wrap;
&:last-child {
margin-bottom: 0;
}
}
a {
color: $lighter-text-color;
text-decoration: none;
&:hover {
text-decoration: underline;
}
&.mention {
&:hover {
text-decoration: none;
span {
text-decoration: underline;
}
}
}
}
.emojione {
width: 20px;
height: 20px;
margin: -5px 0 0;
}
}
.compose-form .compose-form__autosuggest-wrapper {
position: relative;
}
.compose-form .autosuggest-textarea,
.compose-form .autosuggest-input {
position: relative;
width: 100%;
label {
.autosuggest-textarea__textarea {
display: block;
box-sizing: border-box;
margin: 0;
border: 0;
border-radius: 4px 4px 0 0;
padding: 10px 32px 0 10px;
width: 100%;
min-height: 100px;
outline: 0;
color: $inverted-text-color;
background: $simple-background-color;
font-size: 14px;
font-family: inherit;
resize: none;
scrollbar-color: initial;
&::placeholder {
color: $dark-text-color;
}
&::-webkit-scrollbar {
all: unset;
}
&:focus {
outline: 0;
}
@include single-column('screen and (max-width: 630px)') {
font-size: 16px;
}
@include limited-single-column('screen and (max-width: 600px)') {
height: 100px !important; // prevent auto-resize textarea
resize: vertical;
}
}
}
}
.compose-form__textarea-icons {
display: block;
position: absolute;
top: 29px;
inset-inline-end: 5px;
bottom: 5px;
overflow: hidden;
& > .textarea_icon {
display: block;
margin-top: 2px;
margin-inline-start: 2px;
width: 24px;
height: 24px;
color: $lighter-text-color;
font-size: 18px;
line-height: 24px;
text-align: center;
opacity: 0.8;
}
}
.autosuggest-textarea__suggestions-wrapper {
position: relative;
height: 0;
}
.autosuggest-textarea__suggestions {
box-sizing: border-box;
display: none;
position: absolute;
top: 100%;
width: 100%;
z-index: 99;
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
background: $ui-secondary-color;
border-radius: 0 0 4px 4px;
color: $inverted-text-color;
font-size: 14px;
padding: 6px;
}
.autosuggest-textarea__suggestions--visible {
display: block;
}
.autosuggest-textarea__suggestions__item {
padding: 10px;
cursor: pointer;
border-radius: 4px;
&:hover,
&:focus,
&:active,
&.selected {
background: darken($ui-secondary-color, 10%);
}
.autosuggest-account,
.autosuggest-emoji,
.autosuggest-hashtag {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
line-height: 18px;
font-size: 14px;
}
.autosuggest-hashtag {
justify-content: space-between;
&__name {
flex: 1 1 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
strong {
font-weight: 500;
}
&__uses {
flex: 0 0 auto;
text-align: end;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.autosuggest-account-icon,
.autosuggest-emoji img {
margin-inline-end: 8px;
}
.autosuggest-account .display-name > span {
color: $lighter-text-color;
}
}
.compose-form__upload-wrapper {
overflow: hidden;
}
.compose-form__uploads-wrapper {
display: flex;
flex-direction: row;
flex-wrap: wrap;
font-family: inherit;
padding: 5px;
overflow: hidden;
}
.compose-form__upload {
flex: 1 1 0;
margin: 5px;
min-width: 40%;
.compose-form__upload-thumbnail {
position: relative;
border-radius: 4px;
height: 140px;
width: 100%;
background-color: $base-shadow-color;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
overflow: hidden;
& > .close {
mix-blend-mode: difference;
}
}
.icon-button {
flex: 0 1 auto;
color: $secondary-text-color;
font-size: 14px;
font-weight: 500;
padding: 10px;
font-family: inherit;
&:hover,
&:focus,
&:active {
color: lighten($secondary-text-color, 7%);
}
}
&__warning {
position: absolute;
z-index: 2;
bottom: 0;
inset-inline-start: 0;
inset-inline-end: 0;
box-sizing: border-box;
background: linear-gradient(
0deg,
rgba($base-shadow-color, 0.8) 0,
rgba($base-shadow-color, 0.35) 80%,
transparent
);
}
}
.compose-form__upload__actions {
background: linear-gradient(
180deg,
rgba($base-shadow-color, 0.8) 0,
rgba($base-shadow-color, 0.35) 80%,
transparent
);
display: flex;
align-items: flex-start;
justify-content: space-between;
}
.upload-progress {
display: flex;
padding: 10px;
color: $darker-text-color;
overflow: hidden;
gap: 10px;
.icon {
width: 24px;
height: 24px;
}
span {
display: block;
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
}
}
.upload-progress__message {
flex: 1 1 auto;
}
.upload-progress__backdrop {
position: relative;
margin-top: 5px;
border-radius: 6px;
width: 100%;
height: 6px;
background: darken($simple-background-color, 8%);
}
.upload-progress__tracker {
position: absolute;
top: 0;
inset-inline-start: 0;
height: 6px;
border-radius: 6px;
background: $ui-highlight-color;
}
.compose-form__modifiers {
color: $inverted-text-color;
font-family: inherit;
font-size: 14px;
background: $simple-background-color;
}
.compose-form__buttons-wrapper {
padding: 10px;
background: darken($simple-background-color, 8%);
border-radius: 0 0 4px 4px;
height: 27px;
display: flex;
justify-content: space-between;
flex: 0 0 auto;
}
.compose-form__buttons {
display: flex;
flex: 0 0 auto;
gap: 2px;
.icon-button {
height: 100%;
}
& .icon-button,
& .text-icon-button {
box-sizing: content-box;
padding: 0 3px;
}
}
.character-counter__wrapper {
align-self: center;
margin-inline-end: 4px;
}
.privacy-dropdown__dropdown {
border-radius: 4px;
box-shadow: var(--dropdown-shadow);
background: $simple-background-color;
overflow: hidden;
transform-origin: 50% 0;
}
.privacy-dropdown__option {
display: flex;
align-items: center;
padding: 10px;
color: $inverted-text-color;
cursor: pointer;
.privacy-dropdown__option__content {
flex: 1 1 auto;
color: $lighter-text-color;
&:not(:first-child) {
margin-inline-start: 10px;
}
strong {
display: block;
color: $inverted-text-color;
font-weight: 500;
}
}
&:hover,
&.active {
background: $ui-highlight-color;
color: $primary-text-color;
.privacy-dropdown__option__content {
color: $primary-text-color;
strong {
color: $primary-text-color;
}
}
}
&.active:hover {
background: lighten($ui-highlight-color, 4%);
}
}
.compose-form__publish {
display: flex;
justify-content: flex-end;
min-width: 0;
flex: 0 0 auto;
column-gap: 5px;
.compose-form__publish-button-wrapper {
padding-top: 10px;
button {
padding: 7px 10px;
text-align: center;
& > span {
display: flex;
gap: 5px;
align-items: center;
}
}
.side_arm {
height: 100%;
}
}
}
.language-dropdown {
&__dropdown {
background: $simple-background-color;
box-shadow: var(--dropdown-shadow);
border-radius: 4px;
overflow: hidden;
z-index: 2;
&.top {
transform-origin: 50% 100%;
}
&.bottom {
transform-origin: 50% 0;
}
.emoji-mart-search {
padding-inline-end: 10px;
}
.emoji-mart-search-icon {
inset-inline-end: 10px + 5px;
}
.emoji-mart-scroll {
padding: 0 10px 10px;
}
&__results {
&__item {
cursor: pointer;
color: $inverted-text-color;
font-weight: 500;
padding: 10px;
border-radius: 4px;
display: flex;
gap: 6px;
align-items: center;
&:focus,
&:active,
&:hover {
background: $ui-secondary-color;
}
&__common-name {
color: $darker-text-color;
}
&.active {
background: $ui-highlight-color;
color: $primary-text-color;
outline: 0;
.language-dropdown__dropdown__results__item__common-name {
color: $secondary-text-color;
}
&:hover {
background: lighten($ui-highlight-color, 4%);
}
}
}
}
}
}

@ -1,68 +0,0 @@
.scrollable .account-card {
margin: 10px;
background: lighten($ui-base-color, 8%);
}
.scrollable .account-card__title__avatar {
img,
.account__avatar {
border-color: lighten($ui-base-color, 8%);
}
}
.scrollable .account-card__bio::after {
background: linear-gradient(
to left,
lighten($ui-base-color, 8%),
transparent
);
}
.filter-form {
background: $ui-base-color;
&__column {
padding: 10px 15px;
padding-bottom: 0;
}
.radio-button {
display: block;
}
}
.radio-button {
font-size: 14px;
position: relative;
display: inline-block;
padding: 6px 0;
line-height: 18px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
input[type='radio'],
input[type='checkbox'] {
display: none;
}
&__input {
display: inline-block;
position: relative;
border: 1px solid $ui-primary-color;
box-sizing: border-box;
width: 18px;
height: 18px;
flex: 0 0 auto;
margin-inline-end: 10px;
top: -1px;
border-radius: 50%;
vertical-align: middle;
&.checked {
border-color: lighten($ui-highlight-color, 4%);
background: lighten($ui-highlight-color, 4%);
}
}
}

@ -1,23 +0,0 @@
.domain {
padding: 10px;
border-bottom: 1px solid lighten($ui-base-color, 8%);
.domain__domain-name {
flex: 1 1 auto;
display: block;
color: $primary-text-color;
text-decoration: none;
font-size: 14px;
font-weight: 500;
}
}
.domain__wrapper {
display: flex;
}
.domain_buttons {
height: 18px;
padding: 10px;
white-space: nowrap;
}

@ -1,309 +0,0 @@
.drawer {
width: 300px;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow-y: hidden;
padding: 10px 5px;
flex: none;
&:first-child {
padding-inline-start: 10px;
}
&:last-child {
padding-inline-end: 10px;
}
@include single-column('screen and (max-width: 630px)') {
flex: auto;
}
@include limited-single-column('screen and (max-width: 630px)') {
&,
&:first-child,
&:last-child {
padding: 0;
}
}
.wide & {
min-width: 300px;
max-width: 400px;
flex: 1 1 200px;
}
@include single-column('screen and (max-width: 630px)') {
:root & {
// Overrides `.wide` for single-column view
flex: auto;
width: 100%;
min-width: 0;
max-width: none;
padding: 0;
}
}
.react-swipeable-view-container & {
height: 100%;
}
}
.drawer__tab {
display: flex;
flex: 1 1 auto;
padding: 13px 3px 11px;
color: $darker-text-color;
text-decoration: none;
text-align: center;
font-size: 16px;
align-items: center;
justify-content: center;
border-bottom: 2px solid transparent;
}
.drawer__header {
flex: none;
font-size: 16px;
background: $ui-base-color;
margin-bottom: 10px;
display: flex;
flex-direction: row;
border-radius: 4px;
overflow: hidden;
a:hover {
background: lighten($ui-base-color, 3%);
}
}
.search {
position: relative;
margin-bottom: 10px;
flex: none;
@include limited-single-column(
'screen and (max-width: #{$no-gap-breakpoint})'
) {
margin-bottom: 0;
}
@include single-column('screen and (max-width: 630px)') {
font-size: 16px;
}
}
.navigation-bar {
padding: 10px;
color: $darker-text-color;
display: flex;
align-items: center;
a {
color: inherit;
text-decoration: none;
}
.acct {
display: block;
color: $secondary-text-color;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.navigation-bar__actions {
position: relative;
.compose__action-bar .icon-button {
pointer-events: auto;
transform: scale(1, 1) translate(0, 0);
opacity: 1;
.icon {
width: 24px;
height: 24px;
}
}
}
}
.navigation-bar__profile {
display: flex;
flex-direction: column;
flex: 1 1 auto;
margin-inline-start: 8px;
}
.drawer--results {
overflow-x: hidden;
overflow-y: scroll;
}
.search-results__section {
border-bottom: 1px solid lighten($ui-base-color, 8%);
&:last-child {
border-bottom: 0;
}
&__header {
background: darken($ui-base-color, 4%);
border-bottom: 1px solid lighten($ui-base-color, 8%);
padding: 15px;
font-weight: 500;
font-size: 14px;
color: $darker-text-color;
display: flex;
justify-content: space-between;
h3 {
display: flex;
align-items: center;
gap: 5px;
}
button {
color: $highlight-text-color;
padding: 0;
border: 0;
background: 0;
font: inherit;
&:hover,
&:active,
&:focus {
text-decoration: underline;
}
}
}
.account:last-child,
& > div:last-child .status {
border-bottom: 0;
}
& > .hashtag {
display: block;
padding: 10px;
color: $secondary-text-color;
text-decoration: none;
&:hover,
&:active,
&:focus {
color: lighten($secondary-text-color, 4%);
text-decoration: underline;
}
}
}
.drawer__pager {
box-sizing: border-box;
padding: 0;
flex-grow: 1;
position: relative;
overflow: hidden;
display: flex;
border-radius: 4px;
}
.drawer__inner {
position: absolute;
top: 0;
inset-inline-start: 0;
background: $ui-base-color;
box-sizing: border-box;
padding: 0;
display: flex;
flex-direction: column;
overflow: hidden;
overflow-y: auto;
width: 100%;
height: 100%;
&.darker {
background: $ui-base-color;
}
}
.drawer__inner__mastodon {
background: $ui-base-color
url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>')
no-repeat bottom / 100% auto;
flex: 1;
min-height: 47px;
display: none;
> img {
display: block;
object-fit: contain;
object-position: bottom left;
width: 85%;
height: 100%;
pointer-events: none;
user-select: none;
}
> .mastodon {
display: block;
width: 100%;
height: 100%;
border: 0;
cursor: inherit;
}
@media screen and (height >= 640px) {
display: block;
}
}
.pseudo-drawer {
background: lighten($ui-base-color, 13%);
font-size: 13px;
text-align: start;
}
.drawer__backdrop {
cursor: pointer;
position: absolute;
top: 0;
inset-inline-start: 0;
width: 100%;
height: 100%;
background: rgba($base-overlay-background, 0.5);
}
@for $i from 0 through 3 {
.mbstobon-#{$i} .drawer__inner__mastodon {
@if $i == 3 {
background:
url('~flavours/glitch/images/wave-drawer.png')
no-repeat
bottom /
100%
auto,
$ui-base-color;
} @else {
background:
url('~flavours/glitch/images/wave-drawer-glitched.png')
no-repeat
bottom /
100%
auto,
$ui-base-color;
}
& > .mastodon {
background: url('~flavours/glitch/images/mbstobon-ui-#{$i}.png')
no-repeat
left
bottom /
contain;
@if $i != 3 {
filter: contrast(50%) brightness(50%);
}
}
}
}

@ -1,106 +0,0 @@
.emojione {
font-size: inherit;
vertical-align: middle;
object-fit: contain;
margin: -0.2ex 0.15em 0.2ex;
width: 16px;
height: 16px;
img {
width: auto;
}
}
.emoji-picker-dropdown__menu {
background: $simple-background-color;
position: relative;
box-shadow: var(--dropdown-shadow);
border-radius: 4px;
margin-top: 5px;
z-index: 2;
.emoji-mart-scroll {
transition: opacity 200ms ease;
}
&.selecting .emoji-mart-scroll {
opacity: 0.5;
}
}
.emoji-picker-dropdown__modifiers {
position: absolute;
top: 60px;
inset-inline-end: 11px;
cursor: pointer;
}
.emoji-picker-dropdown__modifiers__menu {
position: absolute;
z-index: 4;
top: -4px;
inset-inline-start: -8px;
background: $simple-background-color;
border-radius: 4px;
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
overflow: hidden;
button {
display: block;
cursor: pointer;
border: 0;
padding: 4px 8px;
background: transparent;
&:hover,
&:focus,
&:active {
background: rgba($ui-secondary-color, 0.4);
}
}
.emoji-mart-emoji {
height: 22px;
}
}
.emoji-mart-emoji {
span {
background-repeat: no-repeat;
}
}
.emoji-button {
display: block;
padding-top: 5px;
padding-bottom: 2px;
padding-inline-start: 2px;
padding-inline-end: 5px;
outline: 0;
cursor: pointer;
img {
filter: grayscale(100%);
opacity: 0.8;
display: block;
margin: 0;
width: 22px;
height: 22px;
}
&:hover,
&:active,
&:focus {
img {
opacity: 1;
filter: none;
border-radius: 100%;
}
}
&:focus-visible {
img {
outline: $ui-button-icon-focus-outline;
}
}
}

@ -1,143 +0,0 @@
.account-card__header {
position: relative;
}
.explore__search-header {
background: darken($ui-base-color, 4%);
justify-content: center;
align-items: center;
padding: 15px;
.search {
width: 100%;
margin-bottom: 0;
}
.search__input {
border: 1px solid lighten($ui-base-color, 8%);
padding: 10px;
}
.search__popout {
border: 1px solid lighten($ui-base-color, 8%);
}
.search .icon {
top: 9px;
inset-inline-end: 10px;
color: $dark-text-color;
}
}
.explore__search-results {
flex: 1 1 auto;
display: flex;
flex-direction: column;
background: $ui-base-color;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
.story {
display: flex;
align-items: center;
color: $primary-text-color;
text-decoration: none;
padding: 15px;
border-bottom: 1px solid lighten($ui-base-color, 8%);
gap: 15px;
&:last-child {
border-bottom: 0;
}
&:hover,
&:active,
&:focus {
color: $highlight-text-color;
.story__details__publisher,
.story__details__shared {
color: $highlight-text-color;
}
}
&__details {
flex: 1 1 auto;
&__publisher {
color: $darker-text-color;
margin-bottom: 8px;
}
&__title {
font-size: 19px;
line-height: 24px;
font-weight: 500;
margin-bottom: 8px;
}
&__shared {
color: $darker-text-color;
}
strong {
font-weight: 500;
}
}
&__thumbnail {
flex: 0 0 auto;
position: relative;
width: 120px;
height: 120px;
.skeleton {
width: 100%;
height: 100%;
}
img {
border-radius: 8px;
display: block;
margin: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
&__preview {
border-radius: 8px;
display: block;
margin: 0;
width: 100%;
height: 100%;
object-fit: fill;
position: absolute;
top: 0;
inset-inline-start: 0;
z-index: 0;
&--hidden {
display: none;
}
}
}
&.expanded {
flex-direction: column;
.story__thumbnail {
order: 1;
width: 100%;
height: auto;
aspect-ratio: 1.91 / 1;
}
.story__details {
order: 2;
width: 100%;
flex: 0 0 auto;
}
}
}

@ -1,24 +0,0 @@
@import 'misc';
@import 'accounts';
@import 'domains';
@import 'status';
@import 'modal';
@import 'compose_form';
@import 'columns';
@import 'regeneration_indicator';
@import 'directory';
@import 'search';
@import 'emoji';
@import 'doodle';
@import 'drawer';
@import 'media';
@import 'sensitive';
@import 'lists';
@import 'emoji_picker';
@import 'local_settings';
@import 'single_column';
@import 'announcements';
@import 'explore';
@import 'signed_out';
@import 'privacy_policy';
@import 'about';

@ -1,94 +0,0 @@
.list-editor {
background: $ui-base-color;
flex-direction: column;
border-radius: 8px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
width: 380px;
overflow: hidden;
@media screen and (width <= 420px) {
width: 90%;
}
h4 {
padding: 15px 0;
background: lighten($ui-base-color, 13%);
font-weight: 500;
font-size: 16px;
text-align: center;
border-radius: 8px 8px 0 0;
}
.drawer__pager {
height: 50vh;
}
.drawer__inner {
border-radius: 0 0 8px 8px;
&.backdrop {
width: calc(100% - 60px);
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
border-radius: 0 0 0 8px;
}
}
&__accounts {
overflow-y: auto;
}
.account__display-name {
&:hover strong {
text-decoration: none;
}
}
.account__avatar {
cursor: default;
}
.search {
margin-bottom: 0;
}
}
.list-adder {
background: $ui-base-color;
flex-direction: column;
border-radius: 8px;
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
width: 380px;
overflow: hidden;
@media screen and (width <= 420px) {
width: 90%;
}
&__account {
background: lighten($ui-base-color, 13%);
}
&__lists {
background: lighten($ui-base-color, 13%);
height: 50vh;
border-radius: 0 0 8px 8px;
overflow-y: auto;
}
.list {
padding: 10px;
border-bottom: 1px solid lighten($ui-base-color, 8%);
}
.list__wrapper {
display: flex;
}
.list__display-name {
flex: 1 1 auto;
overflow: hidden;
text-decoration: none;
font-size: 16px;
padding: 10px;
}
}

@ -1,802 +0,0 @@
.video-error-cover {
align-items: center;
background: $base-overlay-background;
color: $primary-text-color;
cursor: pointer;
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
margin-top: 8px;
position: relative;
text-align: center;
z-index: 100;
}
.media-spoiler {
background: $base-overlay-background;
color: $darker-text-color;
border: 0;
width: 100%;
height: 100%;
&:hover,
&:active,
&:focus {
color: lighten($darker-text-color, 8%);
}
.status__content > & {
margin-top: 15px; // Add margin when used bare for NSFW video player
}
@include fullwidth-gallery;
}
.media-spoiler__warning {
display: block;
font-size: 14px;
}
.media-spoiler__trigger {
display: block;
font-size: 11px;
font-weight: 500;
}
.media-gallery__item__badges {
position: absolute;
bottom: 6px;
inset-inline-start: 6px;
display: flex;
gap: 2px;
}
.media-gallery__alt__label,
.media-gallery__gifv__label {
display: flex;
align-items: center;
justify-content: center;
color: $white;
background: rgba($black, 0.65);
padding: 2px 6px;
border-radius: 4px;
font-size: 11px;
font-weight: 700;
z-index: 1;
pointer-events: none;
line-height: 18px;
.icon {
width: 15px;
height: 15px;
}
}
.media-gallery {
box-sizing: border-box;
margin-top: 8px;
overflow: hidden;
border-radius: 4px;
position: relative;
width: 100%;
min-height: 64px;
display: grid;
grid-template-columns: 50% 50%;
grid-template-rows: 50% 50%;
gap: 2px;
@include fullwidth-gallery;
}
.media-gallery__item {
border: 0;
box-sizing: border-box;
display: block;
position: relative;
border-radius: 4px;
overflow: hidden;
&--tall {
grid-row: span 2;
}
&--wide {
grid-column: span 2;
}
.full-width & {
border-radius: 0;
}
&.letterbox {
background: $base-shadow-color;
}
}
.media-gallery__item-thumbnail {
cursor: zoom-in;
display: block;
text-decoration: none;
color: $secondary-text-color;
position: relative;
z-index: 1;
&,
img {
height: 100%;
width: 100%;
object-fit: contain;
&:not(.letterbox) {
height: 100%;
object-fit: cover;
}
}
}
.media-gallery__preview {
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
top: 0;
inset-inline-start: 0;
z-index: 0;
background: $base-overlay-background;
&--hidden {
display: none;
}
}
.media-gallery__gifv {
height: 100%;
overflow: hidden;
position: relative;
width: 100%;
display: flex;
justify-content: center;
}
.media-gallery__item-gifv-thumbnail {
cursor: zoom-in;
height: 100%;
width: 100%;
object-fit: contain;
user-select: none;
&:not(.letterbox) {
height: 100%;
object-fit: cover;
}
}
.media-gallery__item-thumbnail-label {
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
overflow: hidden;
position: absolute;
}
.video-modal__container {
max-width: 100vw;
max-height: 100vh;
}
.audio-modal__container {
width: 50vw;
}
.media-modal {
width: 100%;
height: 100%;
position: relative;
&__close,
&__zoom-button {
color: rgba($white, 0.7);
&:hover,
&:focus,
&:active {
color: $white;
background-color: rgba($white, 0.15);
}
&:focus {
background-color: rgba($white, 0.3);
}
}
}
.media-modal__closer {
position: absolute;
top: 0;
inset-inline-start: 0;
inset-inline-end: 0;
bottom: 0;
}
.media-modal__navigation {
position: absolute;
top: 0;
inset-inline-start: 0;
inset-inline-end: 0;
bottom: 0;
pointer-events: none;
transition: opacity 0.3s linear;
will-change: opacity;
* {
pointer-events: auto;
}
&.media-modal__navigation--hidden {
opacity: 0;
* {
pointer-events: none;
}
}
}
.media-modal__nav {
background: transparent;
box-sizing: border-box;
border: 0;
color: rgba($white, 0.7);
cursor: pointer;
display: flex;
align-items: center;
font-size: 24px;
height: 20vmax;
margin: auto 0;
padding: 30px 15px;
position: absolute;
top: 0;
bottom: 0;
&:hover,
&:focus,
&:active {
color: $white;
}
}
.media-modal__nav--left {
inset-inline-start: 0;
}
.media-modal__nav--right {
inset-inline-end: 0;
}
.media-modal__overlay {
max-width: 600px;
position: absolute;
inset-inline-start: 0;
inset-inline-end: 0;
bottom: 0;
margin: 0 auto;
.picture-in-picture__footer {
border-radius: 0;
background: transparent;
padding: 20px 0;
.icon-button {
color: $white;
&:hover,
&:focus,
&:active {
color: $white;
background-color: rgba($white, 0.15);
}
&:focus {
background-color: rgba($white, 0.3);
}
&.active {
color: $highlight-text-color;
&:hover,
&:focus,
&:active {
background: rgba($highlight-text-color, 0.15);
}
&:focus {
background: rgba($highlight-text-color, 0.3);
}
}
&.star-icon.active {
color: $gold-star;
&:hover,
&:focus,
&:active {
background: rgba($gold-star, 0.15);
}
&:focus {
background: rgba($gold-star, 0.3);
}
}
&.disabled {
color: $white;
background-color: transparent;
cursor: default;
opacity: 0.4;
}
}
}
}
.media-modal__pagination {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
.media-modal__page-dot {
flex: 0 0 auto;
background-color: $white;
opacity: 0.4;
height: 6px;
width: 6px;
border-radius: 50%;
margin: 0 4px;
padding: 0;
border: 0;
font-size: 0;
transition: opacity 0.2s ease-in-out;
&.active {
opacity: 1;
}
}
.media-modal__close {
position: absolute;
inset-inline-end: 8px;
top: 8px;
z-index: 100;
}
.detailed,
.fullscreen {
.video-player__volume__current,
.video-player__volume::before {
bottom: 27px;
}
.video-player__volume__handle {
bottom: 23px;
}
}
.audio-player {
overflow: hidden;
box-sizing: border-box;
position: relative;
background: darken($ui-base-color, 8%);
border-radius: 4px;
padding-bottom: 44px;
width: 100%;
&.editable {
border-radius: 0;
height: 100%;
}
&.inactive {
audio,
.video-player__controls {
visibility: hidden;
}
}
.video-player__volume::before,
.video-player__seek::before {
background: currentColor;
opacity: 0.15;
}
.video-player__seek__buffer {
background: currentColor;
opacity: 0.2;
}
.video-player__buttons button,
.video-player__buttons a {
color: currentColor;
opacity: 0.75;
&:active,
&:hover,
&:focus {
color: currentColor;
opacity: 1;
}
}
.video-player__time-sep,
.video-player__time-total,
.video-player__time-current {
color: currentColor;
}
.video-player__seek::before,
.video-player__seek__buffer,
.video-player__seek__progress {
top: 0;
}
.video-player__seek__handle {
top: -4px;
}
.video-player__controls {
padding-top: 10px;
background: transparent;
}
}
.video-player {
overflow: hidden;
position: relative;
background: $base-shadow-color;
max-width: 100%;
border-radius: 4px;
box-sizing: border-box;
color: $white;
display: flex;
align-items: center;
&.editable {
border-radius: 0;
height: 100% !important;
}
&:focus {
outline: 0;
}
.detailed-status & {
width: 100%;
height: 100%;
}
@include fullwidth-gallery;
video {
display: block;
max-width: 100vw;
max-height: 80vh;
z-index: 1;
position: relative;
}
&.fullscreen {
width: 100% !important;
height: 100% !important;
margin: 0;
video {
max-width: 100% !important;
max-height: 100% !important;
width: 100% !important;
height: 100% !important;
outline: 0;
}
}
&.inline {
video {
object-fit: contain;
}
}
&__controls {
position: absolute;
direction: ltr;
z-index: 2;
bottom: 0;
inset-inline-start: 0;
inset-inline-end: 0;
box-sizing: border-box;
background: linear-gradient(
0deg,
rgba($base-shadow-color, 0.85) 0,
rgba($base-shadow-color, 0.45) 60%,
transparent
);
padding: 0 15px;
opacity: 0;
transition: opacity 0.1s ease;
&.active {
opacity: 1;
}
}
&.inactive {
video,
.video-player__controls {
visibility: hidden;
}
}
&__spoiler {
display: none;
position: absolute;
top: 0;
inset-inline-start: 0;
width: 100%;
height: 100%;
z-index: 4;
border: 0;
background: $base-shadow-color;
color: $darker-text-color;
transition: none;
pointer-events: none;
&.active {
display: block;
pointer-events: auto;
&:hover,
&:active,
&:focus {
color: lighten($darker-text-color, 7%);
}
}
&__title {
display: block;
font-size: 14px;
}
&__subtitle {
display: block;
font-size: 11px;
font-weight: 500;
}
}
&__buttons-bar {
display: flex;
justify-content: space-between;
padding-bottom: 8px;
margin: 0 -5px;
.video-player__download__icon {
color: inherit;
.fa,
&:active .fa,
&:hover .fa,
&:focus .fa {
color: inherit;
}
}
}
&__buttons {
display: flex;
flex: 0 1 auto;
min-width: 30px;
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
gap: 5px;
.player-button {
display: inline-block;
outline: 0;
padding: 5px;
flex: 0 0 auto;
background: transparent;
border: 0;
color: rgba($white, 0.75);
&:active,
&:hover,
&:focus {
color: $white;
}
}
}
&__time {
display: inline;
flex: 0 1 auto;
overflow: hidden;
text-overflow: ellipsis;
margin: 0 5px;
}
&__time-sep,
&__time-total,
&__time-current {
font-size: 14px;
font-weight: 500;
}
&__time-current {
color: $white;
}
&__time-sep {
display: inline-block;
margin: 0 6px;
}
&__time-sep,
&__time-total {
color: $white;
}
&__volume {
flex: 0 0 auto;
display: inline-flex;
cursor: pointer;
height: 24px;
position: relative;
overflow: hidden;
.no-reduce-motion & {
transition: all 100ms linear;
}
&.active {
overflow: visible;
width: 50px;
margin-inline-end: 16px;
}
&::before {
content: '';
width: 50px;
background: rgba($white, 0.35);
border-radius: 4px;
display: block;
position: absolute;
height: 4px;
inset-inline-start: 0;
top: 50%;
transform: translate(0, -50%);
}
&__current {
display: block;
position: absolute;
height: 4px;
border-radius: 4px;
inset-inline-start: 0;
top: 50%;
transform: translate(0, -50%);
background: lighten($ui-highlight-color, 8%);
}
&__handle {
position: absolute;
z-index: 3;
border-radius: 50%;
width: 12px;
height: 12px;
top: 50%;
inset-inline-start: 0;
margin-inline-start: -6px;
transform: translate(0, -50%);
background: lighten($ui-highlight-color, 8%);
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
opacity: 0;
.no-reduce-motion & {
transition: opacity 100ms linear;
}
}
&.active &__handle {
opacity: 1;
}
}
&__link {
padding: 2px 10px;
a {
text-decoration: none;
font-size: 14px;
font-weight: 500;
color: $white;
&:hover,
&:active,
&:focus {
text-decoration: underline;
}
}
}
&__seek {
cursor: pointer;
height: 24px;
position: relative;
&::before {
content: '';
width: 100%;
background: rgba($white, 0.35);
border-radius: 4px;
display: block;
position: absolute;
height: 4px;
top: 14px;
}
&__progress,
&__buffer {
display: block;
position: absolute;
height: 4px;
border-radius: 4px;
top: 14px;
background: lighten($ui-highlight-color, 8%);
}
&__buffer {
background: rgba($white, 0.2);
}
&__handle {
position: absolute;
z-index: 3;
opacity: 0;
border-radius: 50%;
width: 12px;
height: 12px;
top: 10px;
margin-inline-start: -6px;
background: lighten($ui-highlight-color, 8%);
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
.no-reduce-motion & {
transition: opacity 0.1s ease;
}
&.active {
opacity: 1;
}
}
&:hover {
.video-player__seek__handle {
opacity: 1;
}
}
}
&.detailed,
&.fullscreen {
.video-player__buttons {
.player-button {
padding-top: 10px;
padding-bottom: 10px;
}
}
}
}
.gifv {
video {
max-width: 100vw;
max-height: 80vh;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,209 +0,0 @@
.privacy-policy {
background: $ui-base-color;
padding: 20px;
@media screen and (min-width: $no-gap-breakpoint) {
border-radius: 4px;
}
&__body {
margin-top: 20px;
}
}
.prose {
color: $secondary-text-color;
font-size: 15px;
line-height: 22px;
p,
ul,
ol {
margin-top: 1.25em;
margin-bottom: 1.25em;
}
img {
margin-top: 2em;
margin-bottom: 2em;
max-width: 100%;
}
video {
margin-top: 2em;
margin-bottom: 2em;
max-width: 100%;
}
figure {
margin-top: 2em;
margin-bottom: 2em;
figcaption {
font-size: 0.875em;
line-height: 1.4285714;
margin-top: 0.8571429em;
}
}
figure > * {
margin-top: 0;
margin-bottom: 0;
}
h1 {
font-size: 1.5em;
margin-top: 0;
margin-bottom: 1em;
line-height: 1.33;
}
h2 {
font-size: 1.25em;
margin-top: 1.6em;
margin-bottom: 0.6em;
line-height: 1.6;
}
h3,
h4,
h5,
h6 {
margin-top: 1.5em;
margin-bottom: 0.5em;
line-height: 1.5;
}
ol {
counter-reset: list-counter;
}
li {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
ol > li {
counter-increment: list-counter;
&::before {
content: counter(list-counter) '.';
position: absolute;
inset-inline-start: 0;
}
}
ul > li::before {
content: '';
position: absolute;
background-color: $darker-text-color;
border-radius: 50%;
width: 0.375em;
height: 0.375em;
top: 0.5em;
inset-inline-start: 0.25em;
}
ul > li,
ol > li {
position: relative;
padding-inline-start: 1.75em;
}
& > ul > li p {
margin-top: 0.75em;
margin-bottom: 0.75em;
}
& > ul > li > *:first-child {
margin-top: 1.25em;
}
& > ul > li > *:last-child {
margin-bottom: 1.25em;
}
& > ol > li > *:first-child {
margin-top: 1.25em;
}
& > ol > li > *:last-child {
margin-bottom: 1.25em;
}
ul ul,
ul ol,
ol ul,
ol ol {
margin-top: 0.75em;
margin-bottom: 0.75em;
}
h1,
h2,
h3,
h4,
h5,
h6,
strong,
b {
color: $primary-text-color;
font-weight: 700;
}
em,
i {
font-style: italic;
}
a {
color: $highlight-text-color;
text-decoration: underline;
&:focus,
&:hover,
&:active {
text-decoration: none;
}
}
code {
font-size: 0.875em;
background: darken($ui-base-color, 8%);
border-radius: 4px;
padding: 0.2em 0.3em;
}
hr {
border: 0;
border-top: 1px solid lighten($ui-base-color, 4%);
margin-top: 3em;
margin-bottom: 3em;
}
hr + * {
margin-top: 0;
}
h2 + * {
margin-top: 0;
}
h3 + * {
margin-top: 0;
}
h4 + *,
h5 + *,
h6 + * {
margin-top: 0;
}
& > :first-child {
margin-top: 0;
}
& > :last-child {
margin-bottom: 0;
}
}

@ -1,43 +0,0 @@
.regeneration-indicator {
text-align: center;
font-size: 16px;
font-weight: 500;
color: $dark-text-color;
background: $ui-base-color;
cursor: default;
display: flex;
flex: 1 1 auto;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
&__figure {
&,
img {
display: block;
width: auto;
height: 160px;
margin: 0;
}
}
&--without-header {
padding-top: 20px + 48px;
}
&__label {
margin-top: 30px;
strong {
display: block;
margin-bottom: 10px;
color: $dark-text-color;
}
span {
font-size: 15px;
font-weight: 400;
}
}
}

@ -1,329 +0,0 @@
.search {
margin-bottom: 10px;
position: relative;
&__popout {
box-sizing: border-box;
display: none;
position: absolute;
inset-inline-start: 0;
margin-top: -2px;
width: 100%;
background: $ui-base-color;
border-radius: 0 0 4px 4px;
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
z-index: 99;
font-size: 13px;
padding: 15px 5px;
h4 {
text-transform: uppercase;
color: $dark-text-color;
font-weight: 500;
padding: 0 10px;
margin-bottom: 10px;
}
.icon-button {
padding: 0;
}
.icon {
width: 18px;
height: 18px;
}
&__menu {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
&__message {
color: $dark-text-color;
padding: 0 10px;
}
&__item {
display: block;
box-sizing: border-box;
width: 100%;
border: 0;
font: inherit;
background: transparent;
color: $darker-text-color;
padding: 10px;
cursor: pointer;
border-radius: 4px;
text-align: start;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
&--flex {
display: flex;
justify-content: space-between;
}
.icon-button {
transition: none;
}
&:hover,
&:focus,
&:active,
&.selected {
background: $ui-highlight-color;
color: $primary-text-color;
.icon-button {
color: $primary-text-color;
}
}
mark {
background: transparent;
font-weight: 700;
color: $primary-text-color;
}
span {
overflow: inherit;
text-overflow: inherit;
}
}
}
}
&.active {
.search__popout {
display: block;
}
}
}
.search__input {
@include search-input;
display: block;
padding: 15px;
padding-inline-end: 30px;
line-height: 18px;
font-size: 16px;
&::placeholder {
color: lighten($darker-text-color, 4%);
}
&::-moz-focus-inner {
border: 0;
}
&::-moz-focus-inner,
&:focus,
&:active {
outline: 0 !important;
}
&:focus {
background: lighten($ui-base-color, 4%);
}
}
.search__icon {
&::-moz-focus-inner {
border: 0;
}
&::-moz-focus-inner,
&:focus {
outline: 0 !important;
}
.icon {
position: absolute;
top: 13px;
inset-inline-end: 10px;
display: inline-block;
opacity: 0;
transition: all 100ms linear;
transition-property: color, transform, opacity;
font-size: 18px;
width: 24px;
height: 24px;
color: $secondary-text-color;
cursor: default;
pointer-events: none;
&.active {
pointer-events: auto;
opacity: 0.3;
}
}
.icon-search {
transform: rotate(0deg);
&.active {
pointer-events: auto;
opacity: 0.3;
}
}
.icon-times-circle {
top: 17px;
transform: rotate(0deg);
color: $action-button-color;
cursor: pointer;
&.active {
transform: rotate(90deg);
opacity: 1;
}
&:hover {
color: lighten($action-button-color, 7%);
}
}
}
.search-results__header {
color: $dark-text-color;
background: lighten($ui-base-color, 2%);
padding: 15px;
font-weight: 500;
font-size: 16px;
cursor: default;
display: flex;
align-items: center;
gap: 5px;
}
.search-results__info {
padding: 20px;
color: $darker-text-color;
text-align: center;
}
.trends {
&__item {
display: flex;
align-items: center;
padding: 15px;
border-bottom: 1px solid lighten($ui-base-color, 8%);
gap: 15px;
&:last-child {
border-bottom: 0;
}
&__name {
flex: 1 1 auto;
color: $dark-text-color;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
strong {
font-weight: 500;
}
a {
color: $darker-text-color;
text-decoration: none;
font-size: 14px;
font-weight: 500;
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&:hover,
&:focus,
&:active {
span {
text-decoration: underline;
}
}
}
}
&__current {
flex: 0 0 auto;
font-size: 24px;
font-weight: 500;
text-align: end;
color: $secondary-text-color;
text-decoration: none;
}
&__sparkline {
flex: 0 0 auto;
width: 50px;
path:first-child {
fill: rgba($highlight-text-color, 0.25) !important;
fill-opacity: 1 !important;
}
path:last-child {
stroke: lighten($highlight-text-color, 6%) !important;
fill: none !important;
}
}
&--requires-review {
.trends__item__name {
color: $gold-star;
a {
color: $gold-star;
}
}
.trends__item__current {
color: $gold-star;
}
.trends__item__sparkline {
path:first-child {
fill: rgba($gold-star, 0.25) !important;
}
path:last-child {
stroke: lighten($gold-star, 6%) !important;
}
}
}
&--disabled {
.trends__item__name {
color: lighten($ui-base-color, 12%);
a {
color: lighten($ui-base-color, 12%);
}
}
.trends__item__current {
color: lighten($ui-base-color, 12%);
}
.trends__item__sparkline {
path:first-child {
fill: rgba(lighten($ui-base-color, 12%), 0.25) !important;
}
path:last-child {
stroke: lighten(lighten($ui-base-color, 12%), 6%) !important;
}
}
}
}
&--compact &__item {
padding: 10px;
padding-inline-end: 28px;
}
}

@ -1,26 +0,0 @@
.sensitive-info {
display: flex;
flex-direction: row;
align-items: center;
position: absolute;
top: 4px;
inset-inline-start: 4px;
z-index: 100;
}
.sensitive-marker {
margin: 0 3px;
border-radius: 2px;
padding: 2px 6px;
color: rgba($primary-text-color, 0.8);
background: rgba($base-overlay-background, 0.5);
font-size: 12px;
line-height: 18px;
text-transform: uppercase;
opacity: 0.9;
transition: opacity 0.1s ease;
.media-gallery:hover & {
opacity: 1;
}
}

@ -1,106 +0,0 @@
.sign-in-banner {
padding: 10px;
p {
color: $darker-text-color;
margin-bottom: 20px;
a {
color: $secondary-text-color;
text-decoration: none;
unicode-bidi: isolate;
&:hover {
text-decoration: underline;
}
}
}
.button {
margin-bottom: 10px;
}
}
.server-banner {
padding: 20px 0;
&__introduction {
color: $darker-text-color;
margin-bottom: 20px;
strong {
font-weight: 600;
}
a {
color: inherit;
text-decoration: underline;
&:hover,
&:active,
&:focus {
text-decoration: none;
}
}
}
&__hero {
display: block;
border-radius: 4px;
width: 100%;
height: auto;
margin-bottom: 20px;
aspect-ratio: 1.9;
border: 0;
background: $ui-base-color;
object-fit: cover;
}
&__description {
margin-bottom: 20px;
}
&__meta {
display: flex;
gap: 10px;
max-width: 100%;
&__column {
flex: 0 0 auto;
width: calc(50% - 5px);
overflow: hidden;
}
}
&__number {
font-weight: 600;
color: $primary-text-color;
font-size: 14px;
}
&__number-label {
color: $darker-text-color;
font-weight: 500;
font-size: 14px;
}
h4 {
text-transform: uppercase;
color: $darker-text-color;
margin-bottom: 10px;
font-weight: 600;
}
.account {
padding: 0;
border: 0;
}
.account__avatar-wrapper {
margin-inline-start: 0;
}
.spacer {
margin: 10px 0;
}
}

@ -1,319 +0,0 @@
.compose-panel {
width: 285px;
margin-top: 10px;
display: flex;
flex-direction: column;
height: calc(100% - 10px);
overflow-y: hidden;
.hero-widget {
box-shadow: none;
&__text,
&__img,
&__img img {
border-radius: 0;
}
&__text {
padding: 15px;
color: $secondary-text-color;
strong {
font-weight: 700;
color: $primary-text-color;
}
}
}
.search__input {
line-height: 18px;
font-size: 16px;
padding: 15px;
padding-inline-end: 30px;
}
.search__icon .fa {
top: 15px;
}
.navigation-bar {
flex: 0 1 48px;
}
.compose-form {
flex: 1;
display: flex;
flex-direction: column;
min-height: 310px;
}
.compose-form__autosuggest-wrapper {
overflow-y: auto;
background-color: $white;
border-radius: 4px 4px 0 0;
flex: 0 1 auto;
}
.autosuggest-textarea__textarea {
overflow-y: hidden;
}
}
.navigation-panel {
margin-top: 10px;
margin-bottom: 10px;
height: calc(100% - 20px);
overflow-y: auto;
display: flex;
flex-direction: column;
& > a {
flex: 0 0 auto;
}
.logo {
height: 30px;
width: auto;
}
}
.navigation-panel,
.compose-panel {
hr {
flex: 0 0 auto;
border: 0;
background: transparent;
border-top: 1px solid lighten($ui-base-color, 4%);
margin: 10px 0;
}
.flex-spacer {
background: transparent;
}
}
.columns-area--mobile {
flex-direction: column;
width: 100%;
margin: 0 auto;
.column,
.drawer {
width: 100%;
height: 100%;
padding: 0;
}
.account-card {
margin-bottom: 0;
}
.filter-form {
display: flex;
flex-wrap: wrap;
}
.autosuggest-textarea__textarea {
font-size: 16px;
}
.search__input {
line-height: 18px;
font-size: 16px;
padding: 15px;
padding-inline-end: 30px;
}
.search__icon .fa {
top: 15px;
}
.scrollable {
overflow: visible;
@supports (display: grid) {
contain: content;
}
}
@media screen and (min-width: $no-gap-breakpoint) {
padding: 10px 0;
padding-top: 0;
}
.detailed-status {
padding: 15px;
.media-gallery,
.video-player,
.audio-player {
margin-top: 15px;
}
}
.account__header__bar {
padding: 5px 10px;
}
.navigation-bar,
.compose-form {
padding: 15px;
}
.compose-form .compose-form__publish .compose-form__publish-button-wrapper {
padding-top: 15px;
}
.notification__report {
padding: 15px;
padding-inline-start: (48px + 15px * 2);
min-height: 48px + 2px;
&__avatar {
inset-inline-start: 15px;
top: 17px;
}
}
.status {
padding: 15px;
min-height: 48px + 2px;
.media-gallery,
&__action-bar,
.video-player,
.audio-player {
margin-top: 10px;
}
}
.account {
padding: 15px 10px;
&__header__bio {
margin: 0 -10px;
}
}
.notification {
&__message {
padding-top: 15px;
}
.status {
padding-top: 8px;
}
.account {
padding-top: 8px;
}
}
}
@media screen and (min-width: $no-gap-breakpoint) {
.react-swipeable-view-container .columns-area--mobile {
height: calc(100% - 10px) !important;
}
.getting-started__wrapper {
margin-bottom: 10px;
}
.search-page .search {
display: none;
}
.navigation-panel__legal {
display: none;
}
}
@media screen and (max-width: $no-gap-breakpoint - 1px) {
$sidebar-width: 285px;
.columns-area__panels__main {
width: calc(100% - $sidebar-width);
}
.columns-area__panels {
min-height: calc(100vh - $ui-header-height);
}
.columns-area__panels__pane--navigational {
min-width: $sidebar-width;
.columns-area__panels__pane__inner {
width: $sidebar-width;
}
.navigation-panel {
margin: 0;
background: $ui-base-color;
border-inline-start: 1px solid lighten($ui-base-color, 8%);
height: 100vh;
}
.navigation-panel__sign-in-banner,
.navigation-panel__logo,
.navigation-panel__banner,
.getting-started__trends {
display: none;
}
.column-link__icon {
font-size: 18px;
}
}
.layout-single-column .ui__header {
display: flex;
background: $ui-base-color;
border-bottom: 1px solid lighten($ui-base-color, 8%);
}
.column-header,
.column-back-button,
.scrollable,
.error-column {
border-radius: 0 !important;
}
}
@media screen and (max-width: $no-gap-breakpoint - 285px - 1px) {
$sidebar-width: 55px;
.columns-area__panels__main {
width: calc(100% - $sidebar-width);
}
.columns-area__panels__pane--navigational {
min-width: $sidebar-width;
.columns-area__panels__pane__inner {
width: $sidebar-width;
}
.column-link span {
display: none;
}
.list-panel {
display: none;
}
}
}
.explore__search-header {
display: none;
}
@media screen and (max-width: $no-gap-breakpoint - 1px) {
.columns-area__panels__pane--compositional {
display: none;
}
.explore__search-header {
display: flex;
}
}

File diff suppressed because it is too large Load Diff

@ -1,14 +1,14 @@
.emoji-mart {
font-size: 13px;
display: inline-block;
color: $inverted-text-color;
&,
* {
box-sizing: border-box;
line-height: 1.15;
}
font-size: 13px;
display: inline-block;
color: $inverted-text-color;
.emoji-mart-emoji {
padding: 6px;
}
@ -64,17 +64,17 @@
}
.emoji-mart-anchor-bar {
bottom: 0;
bottom: -1px;
}
}
.emoji-mart-anchor-bar {
position: absolute;
bottom: -3px;
bottom: -5px;
inset-inline-start: 0;
width: 100%;
height: 3px;
background-color: darken($ui-highlight-color, 3%);
height: 4px;
background-color: $highlight-text-color;
}
.emoji-mart-anchors {
@ -173,7 +173,7 @@
}
&:hover::before {
z-index: 0;
z-index: -1;
content: '';
position: absolute;
top: 0;

@ -13,8 +13,9 @@
@import 'forms';
@import 'accounts';
@import 'statuses';
@import 'components/index';
@import 'components';
@import 'polls';
@import 'emoji_picker';
@import 'about';
@import 'tables';
@import 'admin';
@ -22,3 +23,5 @@
@import 'rtl';
@import 'dashboard';
@import 'rich_text';
@import 'glitch_local_settings';
@import 'glitch_doodle';

@ -51,16 +51,6 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST';
export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS';
export const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL';
export const REACTION_UPDATE = 'REACTION_UPDATE';
export const REACTION_ADD_REQUEST = 'REACTION_ADD_REQUEST';
export const REACTION_ADD_SUCCESS = 'REACTION_ADD_SUCCESS';
export const REACTION_ADD_FAIL = 'REACTION_ADD_FAIL';
export const REACTION_REMOVE_REQUEST = 'REACTION_REMOVE_REQUEST';
export const REACTION_REMOVE_SUCCESS = 'REACTION_REMOVE_SUCCESS';
export const REACTION_REMOVE_FAIL = 'REACTION_REMOVE_FAIL';
export function reblog(status, visibility) {
return function (dispatch, getState) {
dispatch(reblogRequest(status));
@ -526,89 +516,3 @@ export function unpinFail(status, error) {
skipLoading: true,
};
}
export function addReaction(statusId, name, url) {
return (dispatch, getState) => {
const status = getState().get('statuses').get(statusId);
let alreadyAdded = false;
if (status) {
const reaction = status.get('reactions').find(x => x.get('name') === name);
if (reaction && reaction.get('me')) {
alreadyAdded = true;
}
}
if (!alreadyAdded) {
dispatch(addReactionRequest(statusId, name, url));
}
api(getState).post(`/api/v1/statuses/${statusId}/react/${encodeURIComponent(name)}`).then(() => {
dispatch(addReactionSuccess(statusId, name));
}).catch(err => {
if (!alreadyAdded) {
dispatch(addReactionFail(statusId, name, err));
}
});
};
}
export function addReactionRequest(statusId, name, url) {
return {
type: REACTION_ADD_REQUEST,
id: statusId,
name,
url,
};
}
export function addReactionSuccess(statusId, name) {
return {
type: REACTION_ADD_SUCCESS,
id: statusId,
name,
};
}
export function addReactionFail(statusId, name, error) {
return {
type: REACTION_ADD_FAIL,
id: statusId,
name,
error,
};
}
export function removeReaction(statusId, name) {
return (dispatch, getState) => {
dispatch(removeReactionRequest(statusId, name));
api(getState).post(`/api/v1/statuses/${statusId}/unreact/${encodeURIComponent(name)}`).then(() => {
dispatch(removeReactionSuccess(statusId, name));
}).catch(err => {
dispatch(removeReactionFail(statusId, name, err));
});
};
}
export function removeReactionRequest(statusId, name) {
return {
type: REACTION_REMOVE_REQUEST,
id: statusId,
name,
};
}
export function removeReactionSuccess(statusId, name) {
return {
type: REACTION_REMOVE_SUCCESS,
id: statusId,
name,
};
}
export function removeReactionFail(statusId, name) {
return {
type: REACTION_REMOVE_FAIL,
id: statusId,
name,
};
}

@ -130,7 +130,6 @@ const excludeTypesFromFilter = filter => {
'follow',
'follow_request',
'favourite',
'reaction',
'reblog',
'mention',
'poll',

@ -1,170 +0,0 @@
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { autoPlayGif, reduceMotion } from '../initial_state';
import spring from 'react-motion/lib/spring';
import TransitionMotion from 'react-motion/lib/TransitionMotion';
import classNames from 'classnames';
import React from 'react';
import unicodeMapping from '../features/emoji/emoji_unicode_mapping_light';
import { AnimatedNumber } from './animated_number';
import { assetHost } from '../utils/config';
export default class StatusReactions extends ImmutablePureComponent {
static propTypes = {
statusId: PropTypes.string.isRequired,
reactions: ImmutablePropTypes.list.isRequired,
numVisible: PropTypes.number,
addReaction: PropTypes.func.isRequired,
canReact: PropTypes.bool.isRequired,
removeReaction: PropTypes.func.isRequired,
};
willEnter() {
return { scale: reduceMotion ? 1 : 0 };
}
willLeave() {
return { scale: reduceMotion ? 0 : spring(0, { stiffness: 170, damping: 26 }) };
}
render() {
const { reactions, numVisible } = this.props;
let visibleReactions = reactions
.filter(x => x.get('count') > 0)
.sort((a, b) => b.get('count') - a.get('count'));
if (numVisible >= 0) {
visibleReactions = visibleReactions.filter((_, i) => i < numVisible);
}
const styles = visibleReactions.map(reaction => ({
key: reaction.get('name'),
data: reaction,
style: { scale: reduceMotion ? 1 : spring(1, { stiffness: 150, damping: 13 }) },
})).toArray();
return (
<TransitionMotion styles={styles} willEnter={this.willEnter} willLeave={this.willLeave}>
{items => (
<div className={classNames('reactions-bar', { 'reactions-bar--empty': visibleReactions.isEmpty() })}>
{items.map(({ key, data, style }) => (
<Reaction
key={key}
statusId={this.props.statusId}
reaction={data}
style={{ transform: `scale(${style.scale})`, position: style.scale < 0.5 ? 'absolute' : 'static' }}
addReaction={this.props.addReaction}
removeReaction={this.props.removeReaction}
canReact={this.props.canReact}
/>
))}
</div>
)}
</TransitionMotion>
);
}
}
class Reaction extends ImmutablePureComponent {
static propTypes = {
statusId: PropTypes.string,
reaction: ImmutablePropTypes.map.isRequired,
addReaction: PropTypes.func.isRequired,
removeReaction: PropTypes.func.isRequired,
canReact: PropTypes.bool.isRequired,
style: PropTypes.object,
};
state = {
hovered: false,
};
handleClick = () => {
const { reaction, statusId, addReaction, removeReaction } = this.props;
if (reaction.get('me')) {
removeReaction(statusId, reaction.get('name'));
} else {
addReaction(statusId, reaction.get('name'));
}
}
handleMouseEnter = () => this.setState({ hovered: true })
handleMouseLeave = () => this.setState({ hovered: false })
render() {
const { reaction } = this.props;
return (
<button
className={classNames('reactions-bar__item', { active: reaction.get('me') })}
onClick={this.handleClick}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
disabled={!this.props.canReact}
style={this.props.style}
>
<span className='reactions-bar__item__emoji'>
<Emoji
hovered={this.state.hovered}
emoji={reaction.get('name')}
url={reaction.get('url')}
staticUrl={reaction.get('static_url')}
/>
</span>
<span className='reactions-bar__item__count'>
<AnimatedNumber value={reaction.get('count')} />
</span>
</button>
);
}
}
class Emoji extends React.PureComponent {
static propTypes = {
emoji: PropTypes.string.isRequired,
hovered: PropTypes.bool.isRequired,
url: PropTypes.string,
staticUrl: PropTypes.string,
};
render() {
const { emoji, hovered, url, staticUrl } = this.props;
if (unicodeMapping[emoji]) {
const { filename, shortCode } = unicodeMapping[this.props.emoji];
const title = shortCode ? `:${shortCode}:` : '';
return (
<img
draggable='false'
className='emojione'
alt={emoji}
title={title}
src={`${assetHost}/emoji/${filename}.svg`}
/>
);
} else {
const filename = (autoPlayGif || hovered) ? url : staticUrl;
const shortCode = `:${emoji}:`;
return (
<img
draggable='false'
className='emojione custom-emoji'
alt={shortCode}
title={shortCode}
src={filename}
/>
);
}
}
}

@ -30,8 +30,6 @@ import {
unbookmark,
pin,
unpin,
addReaction,
removeReaction,
} from '../actions/interactions';
import { openModal } from '../actions/modal';
import { initMuteModal } from '../actions/mutes';
@ -137,14 +135,6 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
}
},
onReactionAdd (statusId, name, url) {
dispatch(addReaction(statusId, name, url));
},
onReactionRemove (statusId, name) {
dispatch(removeReaction(statusId, name));
},
onEmbed (status) {
dispatch(openModal({
modalType: 'EMBED',

@ -322,7 +322,6 @@ class EmojiPickerDropdown extends PureComponent {
onSkinTone: PropTypes.func.isRequired,
skinTone: PropTypes.number.isRequired,
button: PropTypes.node,
disabled: PropTypes.bool,
};
state = {
@ -356,7 +355,7 @@ class EmojiPickerDropdown extends PureComponent {
};
onToggle = (e) => {
if (!this.state.disabled && !this.state.loading && (!e.key || e.key === 'Enter')) {
if (!this.state.loading && (!e.key || e.key === 'Enter')) {
if (this.state.active) {
this.onHideDropdown();
} else {

@ -119,17 +119,6 @@ export default class ColumnSettings extends PureComponent {
</div>
</div>
<div role='group' aria-labelledby='notifications-reaction'>
<span id='notifications-reaction' className='column-settings__section'><FormattedMessage id='notifications.column_settings.reaction' defaultMessage='Reactions:' /></span>
<div className='column-settings__pillbar'>
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'reaction']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'reaction']} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'reaction']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'reaction']} onChange={onChange} label={soundStr} />
</div>
</div>
<div role='group' aria-labelledby='notifications-mention'>
<span id='notifications-mention' className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>

@ -46,8 +46,6 @@ import {
unreblog,
pin,
unpin,
addReaction,
removeReaction,
} from '../../actions/interactions';
import { openModal } from '../../actions/modal';
import { initMuteModal } from '../../actions/mutes';
@ -267,19 +265,6 @@ class Status extends ImmutablePureComponent {
}
};
handleReactionAdd = (statusId, name, url) => {
const { dispatch } = this.props;
const { signedIn } = this.context.identity;
if (signedIn) {
dispatch(addReaction(statusId, name, url));
}
};
handleReactionRemove = (statusId, name) => {
this.props.dispatch(removeReaction(statusId, name));
};
handlePin = (status) => {
if (status.get('pinned')) {
this.props.dispatch(unpin(status));
@ -720,15 +705,12 @@ class Status extends ImmutablePureComponent {
status={status}
onOpenVideo={this.handleOpenVideo}
onOpenMedia={this.handleOpenMedia}
onReactionAdd={this.handleReactionAdd}
onReactionRemove={this.handleReactionRemove}
onToggleHidden={this.handleToggleHidden}
onTranslate={this.handleTranslate}
domain={domain}
showMedia={this.state.showMedia}
onToggleMediaVisibility={this.handleToggleMediaVisibility}
pictureInPicture={pictureInPicture}
emojiMap={this.props.emojiMap}
/>
<ActionBar
@ -736,7 +718,6 @@ class Status extends ImmutablePureComponent {
status={status}
onReply={this.handleReplyClick}
onFavourite={this.handleFavouriteClick}
onReactionAdd={this.handleReactionAdd}
onReblog={this.handleReblogClick}
onBookmark={this.handleBookmarkClick}
onDelete={this.handleDeleteClick}

@ -34,7 +34,6 @@ const initialState = ImmutableMap({
follow: false,
follow_request: false,
favourite: false,
reaction: false,
reblog: false,
mention: false,
poll: false,
@ -58,7 +57,6 @@ const initialState = ImmutableMap({
follow_request: false,
favourite: true,
reblog: true,
reaction: true,
mention: true,
poll: true,
status: true,
@ -72,7 +70,6 @@ const initialState = ImmutableMap({
follow_request: false,
favourite: true,
reblog: true,
reaction: true,
mention: true,
poll: true,
status: true,

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M346-140 100-386q-10-10-15-22t-5-25q0-13 5-25t15-22l230-229-106-106 62-65 400 400q10 10 14.5 22t4.5 25q0 13-4.5 25T686-386L440-140q-10 10-22 15t-25 5q-13 0-25-5t-22-15Zm47-506L179-432h428L393-646Zm399 526q-36 0-61-25.5T706-208q0-27 13.5-51t30.5-47l42-54 44 54q16 23 30 47t14 51q0 37-26 62.5T792-120Z"/></svg>

After

Width:  |  Height:  |  Size: 405 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M346-140 100-386q-10-10-15-22t-5-25q0-13 5-25t15-22l230-229-106-106 62-65 400 400q10 10 14.5 22t4.5 25q0 13-4.5 25T686-386L440-140q-10 10-22 15t-25 5q-13 0-25-5t-22-15Zm47-506L179-432h428L393-646Zm399 526q-36 0-61-25.5T706-208q0-27 13.5-51t30.5-47l42-54 44 54q16 23 30 47t14 51q0 37-26 62.5T792-120Z"/></svg>

After

Width:  |  Height:  |  Size: 405 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M280-200v-80h284q63 0 109.5-40T720-420q0-60-46.5-100T564-560H312l104 104-56 56-200-200 200-200 56 56-104 104h252q97 0 166.5 63T800-420q0 94-69.5 157T564-200H280Z"/></svg>

After

Width:  |  Height:  |  Size: 267 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M280-200v-80h284q63 0 109.5-40T720-420q0-60-46.5-100T564-560H312l104 104-56 56-200-200 200-200 56 56-104 104h252q97 0 166.5 63T800-420q0 94-69.5 157T564-200H280Z"/></svg>

After

Width:  |  Height:  |  Size: 267 B

@ -1136,7 +1136,6 @@ body > [data-popper-placement] {
.status__content,
.status__action-bar,
.reactions-bar,
.media-gallery,
.video-player,
.audio-player,
@ -1190,10 +1189,6 @@ body > [data-popper-placement] {
}
}
}
.reactions-bar--empty {
margin-top: 0;
}
}
.status__relative-time {
@ -1335,16 +1330,6 @@ body > [data-popper-placement] {
align-items: center;
gap: 18px;
margin-top: 16px;
& > .emoji-picker-dropdown > .emoji-button {
padding: 0;
}
}
.status__action-bar-button {
.fa-plus {
padding-top: 1px;
}
}
.detailed-status__action-bar-dropdown {
@ -4236,10 +4221,6 @@ a.status-card {
text-align: center;
}
.detailed-status__button .emoji-button {
padding: 0;
}
.column-settings__outer {
background: lighten($ui-base-color, 8%);
padding: 15px;

@ -4,14 +4,34 @@ module ApplicationExtension
extend ActiveSupport::Concern
included do
include Redisable
has_many :created_users, class_name: 'User', foreign_key: 'created_by_application_id', inverse_of: :created_by_application
validates :name, length: { maximum: 60 }
validates :website, url: true, length: { maximum: 2_000 }, if: :website?
validates :redirect_uri, length: { maximum: 2_000 }
# The relationship used between Applications and AccessTokens is using
# dependent: delete_all, which means the ActiveRecord callback in
# AccessTokenExtension is not run, so instead we manually announce to
# streaming that these tokens are being deleted.
before_destroy :push_to_streaming_api, prepend: true
end
def confirmation_redirect_uri
redirect_uri.lines.first.strip
end
def push_to_streaming_api
# TODO: #28793 Combine into a single topic
payload = Oj.dump(event: :kill)
access_tokens.in_batches do |tokens|
redis.pipelined do |pipeline|
tokens.ids.each do |id|
pipeline.publish("timeline:access_token:#{id}", payload)
end
end
end
end
end

@ -32,6 +32,7 @@ class StatusCacheHydrator
payload[:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: @status.id)
payload[:pinned] = StatusPin.exists?(account_id: account_id, status_id: @status.id) if @status.account_id == account_id
payload[:filtered] = mapped_applied_custom_filter(account_id, @status)
payload[:reactions] = serialized_reactions(account_id)
if payload[:poll]
payload[:poll][:voted] = @status.account_id == account_id
@ -57,6 +58,7 @@ class StatusCacheHydrator
payload[:reblog][:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: @status.reblog_of_id)
payload[:reblog][:pinned] = StatusPin.exists?(account_id: account_id, status_id: @status.reblog_of_id) if @status.reblog.account_id == account_id
payload[:reblog][:filtered] = payload[:filtered]
payload[:reblog][:reactions] = serialized_reactions(account_id)
if payload[:reblog][:poll]
if @status.reblog.account_id == account_id
@ -71,6 +73,7 @@ class StatusCacheHydrator
payload[:favourited] = payload[:reblog][:favourited]
payload[:reblogged] = payload[:reblog][:reblogged]
payload[:reactions] = payload[:reblog][:reactions]
end
end
@ -87,6 +90,16 @@ class StatusCacheHydrator
).as_json
end
def serialized_reactions(account_id)
reactions = @status.reactions(account_id)
ActiveModelSerializers::SerializableResource.new(
reactions,
each_serializer: REST::ReactionSerializer,
scope: account_id, # terrible
scope_name: :current_user
).as_json
end
def payload_application
@status.application.present? ? serialized_status_application_json : nil
end

@ -25,7 +25,15 @@ module User::LdapAuthenticable
resource = joins(:account).find_by(accounts: { username: safe_username })
if resource.blank?
resource = new(email: attributes[Devise.ldap_mail.to_sym].first, agreement: true, account_attributes: { username: safe_username }, admin: false, external: true, confirmed_at: Time.now.utc)
resource = new(
email: attributes[Devise.ldap_mail.to_sym].first,
agreement: true,
account_attributes: {
username: safe_username,
},
external: true,
confirmed_at: Time.now.utc
)
resource.save!
end

@ -19,17 +19,18 @@ module User::Omniauthable
end
class_methods do
def find_for_oauth(auth, signed_in_resource = nil)
def find_for_omniauth(auth, signed_in_resource = nil)
# EOLE-SSO Patch
auth.uid = (auth.uid[0][:uid] || auth.uid[0][:user]) if auth.uid.is_a? Hashie::Array
identity = Identity.find_for_oauth(auth)
identity = Identity.find_for_omniauth(auth)
# If a signed_in_resource is provided it always overrides the existing user
# to prevent the identity being locked with accidentally created accounts.
# Note that this may leave zombie accounts (with no associated identity) which
# can be cleaned up at a later date.
user = signed_in_resource || identity.user
user ||= create_for_oauth(auth)
user ||= reattach_for_auth(auth)
user ||= create_for_auth(auth)
if identity.user.nil?
identity.user = user
@ -39,19 +40,35 @@ module User::Omniauthable
user
end
def create_for_oauth(auth)
# Check if the user exists with provided email. If no email was provided,
# we assign a temporary email and ask the user to verify it on
# the next step via Auth::SetupController.show
private
strategy = Devise.omniauth_configs[auth.provider.to_sym].strategy
assume_verified = strategy&.security&.assume_email_is_verified
email_is_verified = auth.info.verified || auth.info.verified_email || auth.info.email_verified || assume_verified
email = auth.info.verified_email || auth.info.email
def reattach_for_auth(auth)
# If allowed, check if a user exists with the provided email address,
# and return it if they does not have an associated identity with the
# current authentication provider.
# This can be used to provide a choice of alternative auth providers
# or provide smooth gradual transition between multiple auth providers,
# but this is discouraged because any insecure provider will put *all*
# local users at risk, regardless of which provider they registered with.
return unless ENV['ALLOW_UNSAFE_AUTH_PROVIDER_REATTACH'] == 'true'
user = User.find_by(email: email) if email_is_verified
email, email_is_verified = email_from_auth(auth)
return unless email_is_verified
return user unless user.nil?
user = User.find_by(email: email)
return if user.nil? || Identity.exists?(provider: auth.provider, user_id: user.id)
user
end
def create_for_auth(auth)
# Create a user for the given auth params. If no email was provided,
# we assign a temporary email and ask the user to verify it on
# the next step via Auth::SetupController.show
email, email_is_verified = email_from_auth(auth)
user = User.new(user_params_from_auth(email, auth))
@ -66,7 +83,14 @@ module User::Omniauthable
user
end
private
def email_from_auth(auth)
strategy = Devise.omniauth_configs[auth.provider.to_sym].strategy
assume_verified = strategy&.security&.assume_email_is_verified
email_is_verified = auth.info.verified || auth.info.verified_email || auth.info.email_verified || assume_verified
email = auth.info.verified_email || auth.info.email
[email, email_is_verified]
end
def user_params_from_auth(email, auth)
{

@ -32,7 +32,6 @@ module User::PamAuthenticable
self.email = "#{account.username}@#{find_pam_suffix}" if email.nil? && find_pam_suffix
self.confirmed_at = Time.now.utc
self.admin = false
self.account = account
self.external = true

@ -17,7 +17,7 @@ class Identity < ApplicationRecord
validates :uid, presence: true, uniqueness: { scope: :provider }
validates :provider, presence: true
def self.find_for_oauth(auth)
def self.find_for_omniauth(auth)
find_or_create_by(uid: auth.uid, provider: auth.provider)
end
end

@ -283,10 +283,10 @@ class Status < ApplicationRecord
@emojis = CustomEmoji.from_text(fields.join(' '), account.domain)
end
def reactions(account = nil)
def reactions(account_id = nil)
grouped_ordered_status_reactions.select(
[:name, :custom_emoji_id, 'COUNT(*) as count'].tap do |values|
values << value_for_reaction_me_column(account)
values << value_for_reaction_me_column(account_id)
end
).to_a.tap do |records|
ActiveRecord::Associations::Preloader.new(records: records, associations: :custom_emoji).call
@ -488,15 +488,15 @@ class Status < ApplicationRecord
)
end
def value_for_reaction_me_column(account)
if account.nil?
def value_for_reaction_me_column(account_id)
if account_id.nil?
'FALSE AS me'
else
<<~SQL.squish
EXISTS(
SELECT 1
FROM status_reactions inner_reactions
WHERE inner_reactions.account_id = #{account.id}
WHERE inner_reactions.account_id = #{account_id}
AND inner_reactions.status_id = status_reactions.status_id
AND inner_reactions.name = status_reactions.name
AND (

@ -1,7 +1,7 @@
# frozen_string_literal: true
class TagFeed < PublicFeed
LIMIT_PER_MODE = 4
LIMIT_PER_MODE = (ENV['MAX_FEED_HASHTAGS'] || 4).to_i
# @param [Tag] tag
# @param [Account] account

@ -51,6 +51,8 @@ class User < ApplicationRecord
last_sign_in_ip
skip_sign_in_token
filtered_languages
admin
moderator
)
include LanguagesHelper
@ -342,6 +344,16 @@ class User < ApplicationRecord
Doorkeeper::AccessToken.by_resource_owner(self).in_batches do |batch|
batch.update_all(revoked_at: Time.now.utc)
Web::PushSubscription.where(access_token_id: batch).delete_all
# Revoke each access token for the Streaming API, since `update_all``
# doesn't trigger ActiveRecord Callbacks:
# TODO: #28793 Combine into a single topic
payload = Oj.dump(event: :kill)
redis.pipelined do |pipeline|
batch.ids.each do |id|
pipeline.publish("timeline:access_token:#{id}", payload)
end
end
end
end

@ -5,7 +5,7 @@ class InitialStateSerializer < ActiveModel::Serializer
attributes :meta, :compose, :accounts,
:media_attachments, :settings,
:max_toot_chars, :poll_limits,
:max_toot_chars, :max_feed_hashtags, :poll_limits,
:languages, :max_reactions
attribute :critical_updates_pending, if: -> { object&.role&.can?(:view_devops) && SoftwareUpdate.check_enabled? }
@ -21,6 +21,10 @@ class InitialStateSerializer < ActiveModel::Serializer
StatusReactionValidator::LIMIT
end
def max_feed_hashtags
TagFeed::LIMIT_PER_MODE
end
def poll_limits
{
min_options: PollValidator::MIN_OPTIONS,
@ -34,8 +38,8 @@ class InitialStateSerializer < ActiveModel::Serializer
def meta
store = default_meta_store
if object.current_account
store[:me] = object.current_account.id.to_s
if object_account
store[:me] = object_account.id.to_s
store[:unfollow_modal] = object_account_user.setting_unfollow_modal
store[:boost_modal] = object_account_user.setting_boost_modal
store[:favourite_modal] = object_account_user.setting_favourite_modal
@ -135,6 +139,10 @@ class InitialStateSerializer < ActiveModel::Serializer
}
end
def object_account
object.current_account
end
def object_account_user
object.current_account.user
end

@ -158,7 +158,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
end
def reactions
object.reactions(current_user&.account)
object.reactions(current_user&.account&.id)
end
private

@ -147,6 +147,7 @@ class DeleteAccountService < BaseService
purge_polls!
purge_generated_notifications!
purge_favourites!
purge_status_reactions!
purge_bookmarks!
purge_feeds!
purge_other_associations!
@ -193,6 +194,15 @@ class DeleteAccountService < BaseService
end
end
def purge_status_reactions!
@account.status_reactions.in_batches do |status_reactions|
ids = status_reactions.pluck(:status_id)
Chewy.strategy.current.update(StatusesIndex, ids) if Chewy.enabled?
Rails.cache.delete_multi(ids.map { |id| "statuses/#{id}" })
status_reactions.delete_all
end
end
def purge_bookmarks!
@account.bookmarks.in_batches do |bookmarks|
Chewy.strategy.current.update(StatusesIndex, bookmarks.pluck(:status_id)) if Chewy.enabled?

@ -44,7 +44,7 @@ class FetchResourceService < BaseService
@response_code = response.code
return nil if response.code != 200
if ['application/activity+json', 'application/ld+json'].include?(response.mime_type)
if valid_activitypub_content_type?(response)
body = response.body_with_limit
json = body_to_json(body)

@ -8,9 +8,7 @@
%td.email-inner-card-td.email-prose
%p= t @resource.approved? ? 'devise.mailer.confirmation_instructions.explanation' : 'devise.mailer.confirmation_instructions.explanation_when_pending', host: site_hostname
- if @resource.created_by_application
= render 'application/mailer/button', text: t('settings.account_settings'), url: edit_user_registration_url
= link_to confirmation_url(@resource, confirmation_token: @token, redirect_to_app: 'true') do
%span= t 'devise.mailer.confirmation_instructions.action_with_app', app: @resource.created_by_application.name
= render 'application/mailer/button', text: t('devise.mailer.confirmation_instructions.action_with_app', app: @resource.created_by_application.name), url: confirmation_url(@resource, confirmation_token: @token, redirect_to_app: 'true')
- else
= render 'application/mailer/button', text: t('devise.mailer.confirmation_instructions.action'), url: confirmation_url(@resource, confirmation_token: @token)
%p= t 'devise.mailer.confirmation_instructions.extra_html', terms_path: about_more_url, policy_path: privacy_policy_url

@ -21,9 +21,14 @@ Doorkeeper.configure do
user unless user&.otp_required_for_login?
end
# If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.
# Doorkeeper provides some administrative interfaces for managing OAuth
# Applications, allowing creation, edit, and deletion of applications from the
# server. At present, these administrative routes are not integrated into
# Mastodon, and as such, we've disabled them by always return a 403 forbidden
# response for them. This does not affect the ability for users to manage
# their own OAuth Applications.
admin_authenticator do
current_user&.admin? || redirect_to(new_user_session_url)
head 403
end
# Authorization Code expiration time (default 10 minutes).

@ -26,6 +26,7 @@ Sidekiq.configure_server do |config|
'queue' => 'scheduler',
},
}
SidekiqScheduler::Scheduler.instance.reload_schedule!
end
end

@ -12,6 +12,7 @@ en:
last_attempt: You have one more attempt before your account is locked.
locked: Your account is locked.
not_found_in_database: Invalid %{authentication_keys} or password.
omniauth_user_creation_failure: Error creating an account for this identity.
pending: Your account is still under review.
timeout: Your session expired. Please login again to continue.
unauthenticated: You need to login or sign up before continuing.

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'sidekiq_unique_jobs/web'
require 'sidekiq_unique_jobs/web' if ENV['ENABLE_SIDEKIQ_UNIQUE_JOBS_UI'] == true
require 'sidekiq-scheduler/web'
class RedirectWithVary < ActionDispatch::Routing::PathRedirect

@ -56,7 +56,7 @@ services:
web:
build: .
image: ghcr.io/mastodon/mastodon:v4.2.0
image: ghcr.io/mastodon/mastodon:v4.2.7
restart: always
env_file: .env.production
command: bundle exec puma -C config/puma.rb
@ -77,7 +77,7 @@ services:
streaming:
build: .
image: ghcr.io/mastodon/mastodon:v4.2.0
image: ghcr.io/mastodon/mastodon:v4.2.7
restart: always
env_file: .env.production
command: node ./streaming
@ -95,7 +95,7 @@ services:
sidekiq:
build: .
image: ghcr.io/mastodon/mastodon:v4.2.0
image: ghcr.io/mastodon/mastodon:v4.2.7
restart: always
env_file: .env.production
command: bundle exec sidekiq

@ -17,7 +17,7 @@ module Mastodon
end
def default_prerelease
'alpha.1'
'alpha.3'
end
def prerelease
@ -29,7 +29,7 @@ module Mastodon
end
def catstodon_revision
'1.0.15'
'1.0.0'
end
def nyastodon_revision

@ -0,0 +1,11 @@
# frozen_string_literal: true
namespace :sidekiq_unique_jobs do
task delete_all_locks: :environment do
digests = SidekiqUniqueJobs::Digests.new
digests.delete_by_pattern('*', count: digests.count)
expiring_digests = SidekiqUniqueJobs::ExpiringDigests.new
expiring_digests.delete_by_pattern('*', count: expiring_digests.count)
end
end

@ -56,15 +56,15 @@ describe JsonLdHelper do
describe '#fetch_resource' do
context 'when the second argument is false' do
it 'returns resource even if the retrieved ID and the given URI does not match' do
stub_request(:get, 'https://bob.test/').to_return body: '{"id": "https://alice.test/"}'
stub_request(:get, 'https://alice.test/').to_return body: '{"id": "https://alice.test/"}'
stub_request(:get, 'https://bob.test/').to_return(body: '{"id": "https://alice.test/"}', headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://alice.test/').to_return(body: '{"id": "https://alice.test/"}', headers: { 'Content-Type': 'application/activity+json' })
expect(fetch_resource('https://bob.test/', false)).to eq({ 'id' => 'https://alice.test/' })
end
it 'returns nil if the object identified by the given URI and the object identified by the retrieved ID does not match' do
stub_request(:get, 'https://mallory.test/').to_return body: '{"id": "https://marvin.test/"}'
stub_request(:get, 'https://marvin.test/').to_return body: '{"id": "https://alice.test/"}'
stub_request(:get, 'https://mallory.test/').to_return(body: '{"id": "https://marvin.test/"}', headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://marvin.test/').to_return(body: '{"id": "https://alice.test/"}', headers: { 'Content-Type': 'application/activity+json' })
expect(fetch_resource('https://mallory.test/', false)).to be_nil
end
@ -72,7 +72,7 @@ describe JsonLdHelper do
context 'when the second argument is true' do
it 'returns nil if the retrieved ID and the given URI does not match' do
stub_request(:get, 'https://mallory.test/').to_return body: '{"id": "https://alice.test/"}'
stub_request(:get, 'https://mallory.test/').to_return(body: '{"id": "https://alice.test/"}', headers: { 'Content-Type': 'application/activity+json' })
expect(fetch_resource('https://mallory.test/', true)).to be_nil
end
end
@ -80,12 +80,12 @@ describe JsonLdHelper do
describe '#fetch_resource_without_id_validation' do
it 'returns nil if the status code is not 200' do
stub_request(:get, 'https://host.test/').to_return status: 400, body: '{}'
stub_request(:get, 'https://host.test/').to_return(status: 400, body: '{}', headers: { 'Content-Type': 'application/activity+json' })
expect(fetch_resource_without_id_validation('https://host.test/')).to be_nil
end
it 'returns hash' do
stub_request(:get, 'https://host.test/').to_return status: 200, body: '{}'
stub_request(:get, 'https://host.test/').to_return(status: 200, body: '{}', headers: { 'Content-Type': 'application/activity+json' })
expect(fetch_resource_without_id_validation('https://host.test/')).to eq({})
end
end

@ -35,7 +35,7 @@ RSpec.describe ActivityPub::Activity::Announce do
context 'when sender is followed by a local account' do
before do
Fabricate(:account).follow!(sender)
stub_request(:get, 'https://example.com/actor/hello-world').to_return(body: Oj.dump(unknown_object_json))
stub_request(:get, 'https://example.com/actor/hello-world').to_return(body: Oj.dump(unknown_object_json), headers: { 'Content-Type': 'application/activity+json' })
subject.perform
end
@ -120,7 +120,7 @@ RSpec.describe ActivityPub::Activity::Announce do
let(:object_json) { 'https://example.com/actor/hello-world' }
before do
stub_request(:get, 'https://example.com/actor/hello-world').to_return(body: Oj.dump(unknown_object_json))
stub_request(:get, 'https://example.com/actor/hello-world').to_return(body: Oj.dump(unknown_object_json), headers: { 'Content-Type': 'application/activity+json' })
end
context 'when the relay is enabled' do

@ -3,19 +3,19 @@
require 'rails_helper'
RSpec.describe Identity do
describe '.find_for_oauth' do
describe '.find_for_omniauth' do
let(:auth) { Fabricate(:identity, user: Fabricate(:user)) }
it 'calls .find_or_create_by' do
allow(described_class).to receive(:find_or_create_by)
described_class.find_for_oauth(auth)
described_class.find_for_omniauth(auth)
expect(described_class).to have_received(:find_or_create_by).with(uid: auth.uid, provider: auth.provider)
end
it 'returns an instance of Identity' do
expect(described_class.find_for_oauth(auth)).to be_instance_of described_class
expect(described_class.find_for_omniauth(auth)).to be_instance_of described_class
end
end
end

@ -438,7 +438,10 @@ RSpec.describe User do
let!(:access_token) { Fabricate(:access_token, resource_owner_id: user.id) }
let!(:web_push_subscription) { Fabricate(:web_push_subscription, access_token: access_token) }
let(:redis_pipeline_stub) { instance_double(Redis::Namespace, publish: nil) }
before do
allow(redis).to receive(:pipelined).and_yield(redis_pipeline_stub)
user.reset_password!
end
@ -455,6 +458,10 @@ RSpec.describe User do
expect(Doorkeeper::AccessToken.active_for(user).count).to eq 0
end
it 'revokes streaming access for all access tokens' do
expect(redis_pipeline_stub).to have_received(:publish).with("timeline:access_token:#{access_token.id}", Oj.dump(event: :kill)).once
end
it 'removes push subscriptions' do
expect(Web::PushSubscription.where(user: user).or(Web::PushSubscription.where(access_token: access_token)).count).to eq 0
expect { web_push_subscription.reload }.to raise_error(ActiveRecord::RecordNotFound)

@ -0,0 +1,83 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'Disabled OAuth routes' do
# These routes are disabled via the doorkeeper configuration for
# `admin_authenticator`, as these routes should only be accessible by server
# administrators. For now, these routes are not properly designed and
# integrated into Mastodon, so we're disabling them completely
describe 'GET /oauth/applications' do
it 'returns 403 forbidden' do
get oauth_applications_path
expect(response).to have_http_status(403)
end
end
describe 'POST /oauth/applications' do
it 'returns 403 forbidden' do
post oauth_applications_path
expect(response).to have_http_status(403)
end
end
describe 'GET /oauth/applications/new' do
it 'returns 403 forbidden' do
get new_oauth_application_path
expect(response).to have_http_status(403)
end
end
describe 'GET /oauth/applications/:id' do
let(:application) { Fabricate(:application, scopes: 'read') }
it 'returns 403 forbidden' do
get oauth_application_path(application)
expect(response).to have_http_status(403)
end
end
describe 'PATCH /oauth/applications/:id' do
let(:application) { Fabricate(:application, scopes: 'read') }
it 'returns 403 forbidden' do
patch oauth_application_path(application)
expect(response).to have_http_status(403)
end
end
describe 'PUT /oauth/applications/:id' do
let(:application) { Fabricate(:application, scopes: 'read') }
it 'returns 403 forbidden' do
put oauth_application_path(application)
expect(response).to have_http_status(403)
end
end
describe 'DELETE /oauth/applications/:id' do
let(:application) { Fabricate(:application, scopes: 'read') }
it 'returns 403 forbidden' do
delete oauth_application_path(application)
expect(response).to have_http_status(403)
end
end
describe 'GET /oauth/applications/:id/edit' do
let(:application) { Fabricate(:application, scopes: 'read') }
it 'returns 403 forbidden' do
get edit_oauth_application_path(application)
expect(response).to have_http_status(403)
end
end
end

@ -39,16 +39,35 @@ describe 'OmniAuth callbacks' do
Fabricate(:user, email: 'user@host.example')
end
it 'matches the existing user, creates an identity, and redirects to root path' do
expect { subject }
.to not_change(User, :count)
.and change(Identity, :count)
.by(1)
.and change(LoginActivity, :count)
.by(1)
context 'when ALLOW_UNSAFE_AUTH_PROVIDER_REATTACH is set to true' do
around do |example|
ClimateControl.modify ALLOW_UNSAFE_AUTH_PROVIDER_REATTACH: 'true' do
example.run
end
end
it 'matches the existing user, creates an identity, and redirects to root path' do
expect { subject }
.to not_change(User, :count)
.and change(Identity, :count)
.by(1)
.and change(LoginActivity, :count)
.by(1)
expect(Identity.find_by(user: User.last).uid).to eq('123')
expect(response).to redirect_to(root_path)
end
end
expect(Identity.find_by(user: User.last).uid).to eq('123')
expect(response).to redirect_to(root_path)
context 'when ALLOW_UNSAFE_AUTH_PROVIDER_REATTACH is not set to true' do
it 'does not match the existing user or create an identity, and redirects to login page' do
expect { subject }
.to not_change(User, :count)
.and not_change(Identity, :count)
.and not_change(LoginActivity, :count)
expect(response).to redirect_to(new_user_session_url)
end
end
end
@ -96,7 +115,7 @@ describe 'OmniAuth callbacks' do
context 'when a user cannot be built' do
before do
allow(User).to receive(:find_for_oauth).and_return(User.new)
allow(User).to receive(:find_for_omniauth).and_return(User.new)
end
it 'redirects to the new user signup page' do

@ -72,11 +72,11 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do
shared_examples 'sets pinned posts' do
before do
stub_request(:get, 'https://example.com/account/pinned/known').to_return(status: 200, body: Oj.dump(status_json_pinned_known))
stub_request(:get, 'https://example.com/account/pinned/unknown-inlined').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_inlined))
stub_request(:get, 'https://example.com/account/pinned/known').to_return(status: 200, body: Oj.dump(status_json_pinned_known), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://example.com/account/pinned/unknown-inlined').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_inlined), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://example.com/account/pinned/unknown-unreachable').to_return(status: 404)
stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable))
stub_request(:get, 'https://example.com/account/collections/featured').to_return(status: 200, body: Oj.dump(featured_with_null))
stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://example.com/account/collections/featured').to_return(status: 200, body: Oj.dump(featured_with_null), headers: { 'Content-Type': 'application/activity+json' })
subject.call(actor, note: true, hashtag: false)
end
@ -94,7 +94,7 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do
describe '#call' do
context 'when the endpoint is a Collection' do
before do
stub_request(:get, actor.featured_collection_url).to_return(status: 200, body: Oj.dump(payload))
stub_request(:get, actor.featured_collection_url).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it_behaves_like 'sets pinned posts'
@ -111,7 +111,7 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do
end
before do
stub_request(:get, actor.featured_collection_url).to_return(status: 200, body: Oj.dump(payload))
stub_request(:get, actor.featured_collection_url).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it_behaves_like 'sets pinned posts'
@ -120,7 +120,7 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do
let(:items) { 'https://example.com/account/pinned/unknown-reachable' }
before do
stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable))
stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable), headers: { 'Content-Type': 'application/activity+json' })
subject.call(actor, note: true, hashtag: false)
end
@ -147,7 +147,7 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do
end
before do
stub_request(:get, actor.featured_collection_url).to_return(status: 200, body: Oj.dump(payload))
stub_request(:get, actor.featured_collection_url).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it_behaves_like 'sets pinned posts'
@ -156,7 +156,7 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do
let(:items) { 'https://example.com/account/pinned/unknown-reachable' }
before do
stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable))
stub_request(:get, 'https://example.com/account/pinned/unknown-reachable').to_return(status: 200, body: Oj.dump(status_json_pinned_unknown_reachable), headers: { 'Content-Type': 'application/activity+json' })
subject.call(actor, note: true, hashtag: false)
end

@ -38,7 +38,7 @@ RSpec.describe ActivityPub::FetchFeaturedTagsCollectionService, type: :service d
describe '#call' do
context 'when the endpoint is a Collection' do
before do
stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload))
stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it_behaves_like 'sets featured tags'
@ -46,7 +46,7 @@ RSpec.describe ActivityPub::FetchFeaturedTagsCollectionService, type: :service d
context 'when the account already has featured tags' do
before do
stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload))
stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
actor.featured_tags.create!(name: 'FoO')
actor.featured_tags.create!(name: 'baz')
@ -67,7 +67,7 @@ RSpec.describe ActivityPub::FetchFeaturedTagsCollectionService, type: :service d
end
before do
stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload))
stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it_behaves_like 'sets featured tags'
@ -88,7 +88,7 @@ RSpec.describe ActivityPub::FetchFeaturedTagsCollectionService, type: :service d
end
before do
stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload))
stub_request(:get, collection_url).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it_behaves_like 'sets featured tags'

@ -44,7 +44,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
before do
actor[:inbox] = nil
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
@ -67,7 +67,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
@ -93,7 +93,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
@ -125,7 +125,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/bob' }] } }
before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
@ -148,7 +148,7 @@ RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do
let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/bob' }] } }
before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end

@ -44,7 +44,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do
before do
actor[:inbox] = nil
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
@ -67,7 +67,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
@ -93,7 +93,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do
let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
@ -125,7 +125,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do
let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/bob' }] } }
before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
@ -148,7 +148,7 @@ RSpec.describe ActivityPub::FetchRemoteActorService, type: :service do
let!(:webfinger) { { subject: 'acct:alice@iscool.af', links: [{ rel: 'self', href: 'https://example.com/bob' }] } }
before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
stub_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end

@ -50,7 +50,7 @@ RSpec.describe ActivityPub::FetchRemoteKeyService, type: :service do
end
before do
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor))
stub_request(:get, 'https://example.com/alice').to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: Oj.dump(webfinger), headers: { 'Content-Type': 'application/jrd+json' })
end
@ -59,7 +59,7 @@ RSpec.describe ActivityPub::FetchRemoteKeyService, type: :service do
context 'when the key is a sub-object from the actor' do
before do
stub_request(:get, public_key_id).to_return(body: Oj.dump(actor))
stub_request(:get, public_key_id).to_return(body: Oj.dump(actor), headers: { 'Content-Type': 'application/activity+json' })
end
it 'returns the expected account' do
@ -71,7 +71,7 @@ RSpec.describe ActivityPub::FetchRemoteKeyService, type: :service do
let(:public_key_id) { 'https://example.com/alice-public-key.json' }
before do
stub_request(:get, public_key_id).to_return(body: Oj.dump(key_json.merge({ '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })))
stub_request(:get, public_key_id).to_return(body: Oj.dump(key_json.merge({ '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })), headers: { 'Content-Type': 'application/activity+json' })
end
it 'returns the expected account' do
@ -84,7 +84,7 @@ RSpec.describe ActivityPub::FetchRemoteKeyService, type: :service do
let(:actor_public_key) { 'https://example.com/alice-public-key.json' }
before do
stub_request(:get, public_key_id).to_return(body: Oj.dump(key_json.merge({ '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })))
stub_request(:get, public_key_id).to_return(body: Oj.dump(key_json.merge({ '@context': ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] })), headers: { 'Content-Type': 'application/activity+json' })
end
it 'returns the nil' do

@ -58,7 +58,7 @@ RSpec.describe ActivityPub::FetchRepliesService, type: :service do
context 'when passing the URL to the collection' do
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it 'spawns workers for up to 5 replies on the same server' do
@ -93,7 +93,7 @@ RSpec.describe ActivityPub::FetchRepliesService, type: :service do
context 'when passing the URL to the collection' do
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it 'spawns workers for up to 5 replies on the same server' do
@ -132,7 +132,7 @@ RSpec.describe ActivityPub::FetchRepliesService, type: :service do
context 'when passing the URL to the collection' do
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it 'spawns workers for up to 5 replies on the same server' do

@ -60,7 +60,7 @@ RSpec.describe ActivityPub::SynchronizeFollowersService, type: :service do
describe '#call' do
context 'when the endpoint is a Collection of actor URIs' do
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it_behaves_like 'synchronizes followers'
@ -77,7 +77,7 @@ RSpec.describe ActivityPub::SynchronizeFollowersService, type: :service do
end
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it_behaves_like 'synchronizes followers'
@ -98,7 +98,7 @@ RSpec.describe ActivityPub::SynchronizeFollowersService, type: :service do
end
before do
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload))
stub_request(:get, collection_uri).to_return(status: 200, body: Oj.dump(payload), headers: { 'Content-Type': 'application/activity+json' })
end
it_behaves_like 'synchronizes followers'

@ -21,7 +21,7 @@ describe ActivityPub::FetchRepliesWorker do
describe 'perform' do
it 'performs a request if the collection URI is from the same host' do
stub_request(:get, 'https://example.com/statuses_replies/1').to_return(status: 200, body: json)
stub_request(:get, 'https://example.com/statuses_replies/1').to_return(status: 200, body: json, headers: { 'Content-Type': 'application/activity+json' })
subject.perform(status.id, 'https://example.com/statuses_replies/1')
expect(a_request(:get, 'https://example.com/statuses_replies/1')).to have_been_made.once
end

Loading…
Cancel
Save